mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-14 07:06:45 +03:00
Cross-harness, cross-OS: boot-time tool detection and native-first image gen
context.mjs now probes cwebp/sips/magick/ffmpeg once (which/where per OS) and prints IMAGE_TOOLS, replacing macOS-specific prose; the IMAGE_GEN_AVAILABLE directive leads with the harness-native tool so a present OpenAI key stops reading as an instruction to bill it; and the sandboxed board-start guidance sheds codex vocabulary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
af56fae571
commit
b1c5707fde
@@ -27,6 +27,7 @@
|
||||
* shape rather than the markdown block.
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
@@ -1146,6 +1147,7 @@ async function cli() {
|
||||
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
|
||||
parts.push(buildMissingTargetDirective());
|
||||
}
|
||||
appendImageToolsDirective(parts);
|
||||
appendStalenessDirective(parts, ctx, cliOptions);
|
||||
if (updateDirective) parts.push(updateDirective);
|
||||
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
|
||||
@@ -1180,6 +1182,7 @@ async function cli() {
|
||||
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
|
||||
);
|
||||
}
|
||||
appendImageToolsDirective(parts);
|
||||
appendStalenessDirective(parts, ctx, cliOptions);
|
||||
if (!ctx.platform) {
|
||||
// A `## Platform` section that names something we don't recognize (a
|
||||
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
|
||||
if (!process.env.OPENAI_API_KEY) return;
|
||||
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
|
||||
parts.push([
|
||||
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
|
||||
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
|
||||
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
|
||||
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
|
||||
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
|
||||
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
|
||||
'Visualizing a direction before building it measurably strengthens the result.',
|
||||
].join(' '));
|
||||
}
|
||||
|
||||
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
|
||||
// markdown already in memory, a bounded set of stats, or one of the small JSON
|
||||
// files the boot reads regardless. The deep pass (git drift, token divergence,
|
||||
// cross-workspace sweep) belongs to the doctor command, not to every session.
|
||||
// One boot-time probe replaces every session re-deriving its image toolchain:
|
||||
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
|
||||
// agent should read this line instead of running command -v per image.
|
||||
function appendImageToolsDirective(parts) {
|
||||
const probe = process.platform === 'win32' ? 'where' : 'which';
|
||||
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
|
||||
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
|
||||
});
|
||||
parts.push(found.length
|
||||
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
|
||||
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
|
||||
}
|
||||
|
||||
function appendStalenessDirective(parts, ctx, options) {
|
||||
const projectRoot = ctx.projectRoot || process.cwd();
|
||||
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
|
||||
|
||||
@@ -3766,7 +3766,10 @@
|
||||
const container = copyEditContainerContext(contextElement);
|
||||
if (container) for (const op of ops) op.container = container;
|
||||
try {
|
||||
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
|
||||
// Token in the query string as well as the body: the URL token is what
|
||||
// authorizes the CORS preflight when the page runs on a non-loopback
|
||||
// dev host (ddev, Valet), since the preflight carries no request body.
|
||||
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -7150,7 +7153,10 @@
|
||||
console.debug('[impeccable] Dropped optional live event:', err);
|
||||
return null;
|
||||
}
|
||||
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
|
||||
// Token in the query string as well as the body: the URL token is what
|
||||
// authorizes the CORS preflight when the page runs on a non-loopback
|
||||
// dev host (ddev, Valet), since the preflight carries no request body.
|
||||
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(msg),
|
||||
|
||||
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
|
||||
function createRequestHandler({ detectScript, liveScriptParts }) {
|
||||
return (req, res) => {
|
||||
const url = new URL(req.url, `http://localhost:${state.port}`);
|
||||
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
|
||||
// loopback origin, always paired with `Vary: Origin` so an intermediary
|
||||
// cache never serves a response authorized for one origin to another. A
|
||||
// remote page (e.g. https://evil.example probing the port from a tab open
|
||||
// on the same machine) gets no Access-Control-Allow-Origin, so its
|
||||
// JS-initiated fetch cannot read any response. Requests with no Origin
|
||||
// header (script tags, curl, the agent's own fetches) are not subject to
|
||||
// CORS and keep working; no ACAO header is needed for them.
|
||||
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
|
||||
// loopback origin OR the request carries the valid session token, always
|
||||
// paired with `Vary: Origin` so an intermediary cache never serves a
|
||||
// response authorized for one origin to another. A remote page (e.g.
|
||||
// https://evil.example probing the port from a tab open on the same
|
||||
// machine) has no token and gets no Access-Control-Allow-Origin, so its
|
||||
// JS-initiated fetch cannot read any response. The token branch exists for
|
||||
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
|
||||
// Valet's *.test, hosts-file entries): the injected classic <script src>
|
||||
// delivers the token to the page regardless of origin, every overlay
|
||||
// request carries it in the query string (preflights included, since
|
||||
// OPTIONS hits the same URL), and a token bearer is already fully
|
||||
// authorized on every route — the token is the security boundary, not the
|
||||
// origin. Requests with no Origin header (script tags, curl, the agent's
|
||||
// own fetches) are not subject to CORS and keep working; no ACAO header
|
||||
// is needed for them.
|
||||
const origin = req.headers.origin;
|
||||
if (origin && isLoopbackOrigin(origin)) {
|
||||
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
|
||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||
res.setHeader('Vary', 'Origin');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user