flowviant 0.116.0 → 0.124.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -12
- package/bin/cli.mjs +50 -17
- package/bin/lib/admission.mjs +7 -14
- package/bin/lib/agentAsk.mjs +123 -0
- package/bin/lib/agentBrief.mjs +36 -0
- package/bin/lib/agentFootprint.mjs +76 -0
- package/bin/lib/agentInternalWait.mjs +83 -0
- package/bin/lib/agentPlan.mjs +134 -31
- package/bin/lib/agentPrerequisiteContract.mjs +43 -0
- package/bin/lib/agentPublish.mjs +12 -13
- package/bin/lib/agentTaskKinds.mjs +5 -8
- package/bin/lib/agySessions.mjs +3 -3
- package/bin/lib/artifactContracts.mjs +91 -20
- package/bin/lib/artifactIteration.mjs +182 -0
- package/bin/lib/artifactPreview.mjs +92 -18
- package/bin/lib/artifactRecovery.mjs +36 -0
- package/bin/lib/artifactReporter.mjs +194 -22
- package/bin/lib/artifacts.mjs +70 -63
- package/bin/lib/authproxy.mjs +2 -1
- package/bin/lib/childEnv.mjs +9 -4
- package/bin/lib/claude.mjs +10 -20
- package/bin/lib/claudeAuth.mjs +15 -5
- package/bin/lib/claudePersonal.mjs +71 -6
- package/bin/lib/claudePosture.mjs +227 -54
- package/bin/lib/claudeSessions.mjs +7 -7
- package/bin/lib/claudeStream.mjs +27 -13
- package/bin/lib/cliLimit.mjs +2 -2
- package/bin/lib/codexHome.mjs +17 -0
- package/bin/lib/codexPersonal.mjs +44 -3
- package/bin/lib/codexProjectRead.mjs +63 -0
- package/bin/lib/codexRelay.mjs +21 -5
- package/bin/lib/codexResearch.mjs +193 -0
- package/bin/lib/commandSpecs.mjs +6 -0
- package/bin/lib/config.mjs +37 -24
- package/bin/lib/conversationIds.mjs +51 -0
- package/bin/lib/credentials.mjs +2 -7
- package/bin/lib/daemonLocks.mjs +90 -0
- package/bin/lib/daemonState.mjs +5 -5
- package/bin/lib/deploy.mjs +6 -9
- package/bin/lib/deployCheckout.mjs +26 -22
- package/bin/lib/deployRunner.mjs +54 -18
- package/bin/lib/deployWire.mjs +32 -23
- package/bin/lib/desktopContract.mjs +6 -5
- package/bin/lib/discoveryContract.mjs +19 -0
- package/bin/lib/env.mjs +104 -35
- package/bin/lib/fleet.mjs +53 -13
- package/bin/lib/fleetPost.mjs +172 -25
- package/bin/lib/fleetReports.mjs +172 -171
- package/bin/lib/fleetStartup.mjs +6 -6
- package/bin/lib/fleetWire.mjs +42 -0
- package/bin/lib/git.mjs +111 -61
- package/bin/lib/gitMergeIdentity.mjs +2 -8
- package/bin/lib/grant.mjs +23 -7
- package/bin/lib/holder.mjs +108 -81
- package/bin/lib/hooks/mcpAllow.mjs +60 -0
- package/bin/lib/hooks/readGuard.mjs +54 -11
- package/bin/lib/imageArtifact.mjs +74 -0
- package/bin/lib/install.mjs +80 -33
- package/bin/lib/instance.mjs +36 -83
- package/bin/lib/intakeDraft.mjs +4 -1
- package/bin/lib/knowledgeFileSync.mjs +31 -0
- package/bin/lib/knowledgeFiles.mjs +28 -83
- package/bin/lib/landed.mjs +22 -29
- package/bin/lib/launchCommand.mjs +91 -0
- package/bin/lib/listeners.mjs +0 -5
- package/bin/lib/localSessions.mjs +52 -39
- package/bin/lib/login.mjs +22 -12
- package/bin/lib/loginPath.mjs +22 -17
- package/bin/lib/machineListing.mjs +6 -3
- package/bin/lib/maintenance.mjs +47 -0
- package/bin/lib/maintenanceInventory.mjs +39 -0
- package/bin/lib/mcp-cli.mjs +9 -10
- package/bin/lib/mcpInstall.mjs +163 -0
- package/bin/lib/mcpInventory.mjs +119 -0
- package/bin/lib/noWindowEnv.mjs +23 -3
- package/bin/lib/ownedDirs.mjs +99 -0
- package/bin/lib/pinnedCapture.mjs +383 -0
- package/bin/lib/planDependencies.mjs +70 -0
- package/bin/lib/postureTools.mjs +85 -0
- package/bin/lib/prWorkflow.mjs +94 -17
- package/bin/lib/preflight.mjs +8 -25
- package/bin/lib/preview.mjs +19 -25
- package/bin/lib/previewGatePolicy.mjs +34 -9
- package/bin/lib/previewRegistry.mjs +4 -11
- package/bin/lib/printable.mjs +70 -7
- package/bin/lib/procRegistry.mjs +33 -13
- package/bin/lib/procScan.mjs +2 -3
- package/bin/lib/procStat.mjs +76 -0
- package/bin/lib/processGroup.mjs +48 -0
- package/bin/lib/processes.mjs +10 -24
- package/bin/lib/projectToolRuntimes.mjs +328 -19
- package/bin/lib/projectTools.mjs +109 -34
- package/bin/lib/prompts.mjs +205 -60
- package/bin/lib/repoPath.mjs +19 -0
- package/bin/lib/repoState.mjs +17 -19
- package/bin/lib/repositoryBaseline.mjs +68 -0
- package/bin/lib/runTurn.mjs +286 -209
- package/bin/lib/runtimeAntigravity.mjs +16 -5
- package/bin/lib/runtimeCapabilities.mjs +215 -21
- package/bin/lib/runtimeClaude.mjs +78 -10
- package/bin/lib/runtimeCodex.mjs +217 -209
- package/bin/lib/runtimeDetection.mjs +8 -2
- package/bin/lib/runtimeEvents.mjs +28 -11
- package/bin/lib/runtimeLimits.mjs +5 -6
- package/bin/lib/runtimeOfferings.mjs +99 -14
- package/bin/lib/runtimes.mjs +10 -0
- package/bin/lib/shipSweep.mjs +5 -3
- package/bin/lib/shot.mjs +91 -17
- package/bin/lib/sleep.mjs +11 -0
- package/bin/lib/startProject.mjs +5 -5
- package/bin/lib/statePaths.mjs +40 -0
- package/bin/lib/tty.mjs +25 -15
- package/bin/lib/turnDiagnostics.mjs +93 -0
- package/bin/lib/turnLock.mjs +7 -14
- package/bin/lib/turnProfile.mjs +16 -4
- package/bin/lib/ui.mjs +0 -3
- package/bin/lib/uninstall.mjs +30 -26
- package/bin/lib/update.mjs +79 -27
- package/bin/lib/uplinkScrub.mjs +296 -31
- package/bin/lib/vault.mjs +78 -10
- package/bin/lib/vaultDiff.mjs +5 -2
- package/bin/lib/views.mjs +37 -12
- package/bin/lib/wikiRunner.mjs +40 -61
- package/bin/lib/work.mjs +44 -13
- package/bin/lib/workAdoptCarry.mjs +2 -4
- package/bin/lib/workAgentBrief.mjs +92 -0
- package/bin/lib/workAgentBriefRunner.mjs +59 -0
- package/bin/lib/workAgentCheck.mjs +104 -57
- package/bin/lib/workAgentMerges.mjs +62 -57
- package/bin/lib/workAgentPlans.mjs +82 -199
- package/bin/lib/workAgentPrecheck.mjs +76 -138
- package/bin/lib/workAgentPublish.mjs +6 -8
- package/bin/lib/workAgentTurnBegun.mjs +15 -11
- package/bin/lib/workAgentTurnExecution.mjs +131 -67
- package/bin/lib/workAgentTurnOutcome.mjs +11 -2
- package/bin/lib/workAgentTurnReports.mjs +9 -37
- package/bin/lib/workAgentTurns.mjs +64 -7
- package/bin/lib/workArtifactReconcile.mjs +79 -0
- package/bin/lib/workAttachments.mjs +66 -11
- package/bin/lib/workBrain.mjs +9 -4
- package/bin/lib/workCappedTurn.mjs +119 -0
- package/bin/lib/workChildren.mjs +5 -5
- package/bin/lib/workCommandAudit.mjs +18 -2
- package/bin/lib/workDiffs.mjs +52 -6
- package/bin/lib/workIntake.mjs +35 -60
- package/bin/lib/workPlaceReports.mjs +7 -4
- package/bin/lib/workPlaces.mjs +113 -31
- package/bin/lib/workPreviews.mjs +47 -112
- package/bin/lib/workProcesses.mjs +8 -35
- package/bin/lib/workPullRequests.mjs +37 -56
- package/bin/lib/workReportQueue.mjs +100 -148
- package/bin/lib/workRetire.mjs +2 -1
- package/bin/lib/workSessionRuntime.mjs +38 -37
- package/bin/lib/workSessionTurns.mjs +256 -199
- package/bin/lib/workShip.mjs +43 -23
- package/bin/lib/workToolLog.mjs +70 -3
- package/bin/lib/workTurnShape.mjs +165 -0
- package/npm-shrinkwrap.json +69 -0
- package/package.json +9 -4
- package/bin/lib/fleetJobs.mjs +0 -120
- package/bin/lib/patchRevert.mjs +0 -60
package/bin/lib/agentPlan.mjs
CHANGED
|
@@ -25,9 +25,13 @@
|
|
|
25
25
|
* Run: node --test bin/lib/agentPlan.test.mjs
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
// THE UPLINK SCRUBBER, as the DEFAULT for the
|
|
29
|
-
// goes to the server (2026-09-24, the audit).
|
|
30
|
-
|
|
28
|
+
// THE UPLINK SCRUBBER, as the DEFAULT for the three readers whose every string
|
|
29
|
+
// goes to the server (2026-09-24, the audit; the precheck joined 2026-10-10).
|
|
30
|
+
// See `parseTurnResult`. `scrubCut` is the scrub-then-cut order, spelled once.
|
|
31
|
+
import { scrub as envScrub, scrubCut } from './uplinkScrub.mjs';
|
|
32
|
+
import { groupDependencies } from './planDependencies.mjs';
|
|
33
|
+
import { AGENT_TASK_KINDS } from './agentTaskKinds.mjs';
|
|
34
|
+
import { blockedAnswer } from './agentAsk.mjs';
|
|
31
35
|
|
|
32
36
|
/** The biggest a single proposal may be. Bounds on a machine, not a policy:
|
|
33
37
|
* the server caps these again at its own boundary. */
|
|
@@ -68,7 +72,8 @@ const MAX_NOTE = 1000;
|
|
|
68
72
|
* none of which a real answer comes near:
|
|
69
73
|
*
|
|
70
74
|
* · only the LAST `MAX_SCAN_CHARS` are scanned — every contract here puts the
|
|
71
|
-
* object LAST, and the fenced block is still looked for in the whole text
|
|
75
|
+
* object LAST, and the fenced block is still looked for in the whole text,
|
|
76
|
+
* by a LINEAR scan (`fencedBody`) since 2026-10-10;
|
|
72
77
|
* · each `{`'s closing brace is found ONCE (see `closers`), so the scan is a
|
|
73
78
|
* single right-to-left pass instead of one walk per brace;
|
|
74
79
|
* · only spans that can open an object (`{` then a key or `}`) are handed to
|
|
@@ -90,10 +95,10 @@ const MAX_NESTED_PARSES = 200;
|
|
|
90
95
|
const MAX_NESTED_CHARS = 4 * 1024 * 1024;
|
|
91
96
|
|
|
92
97
|
export function candidateObjects(text) {
|
|
93
|
-
const fenced = text
|
|
98
|
+
const fenced = fencedBody(text);
|
|
94
99
|
const raw = text.length > MAX_SCAN_CHARS ? text.slice(text.length - MAX_SCAN_CHARS) : text;
|
|
95
100
|
const bodies = [];
|
|
96
|
-
if (fenced) bodies.push(fenced
|
|
101
|
+
if (fenced !== null) bodies.push(fenced);
|
|
97
102
|
const close = closers(raw);
|
|
98
103
|
let topEnd = -1;
|
|
99
104
|
let nestedParses = 0;
|
|
@@ -125,6 +130,35 @@ export function candidateObjects(text) {
|
|
|
125
130
|
return out;
|
|
126
131
|
}
|
|
127
132
|
|
|
133
|
+
/**
|
|
134
|
+
* THE FIRST FENCED BLOCK'S BODY, or null — what
|
|
135
|
+
* `text.match(/```(?:json)?\s*([\s\S]*?)```/)[1]` answered, found in ONE
|
|
136
|
+
* forward pass (2026-10-10, the audit's F079).
|
|
137
|
+
*
|
|
138
|
+
* That regex ran over the WHOLE text — the only search the `MAX_SCAN_CHARS`
|
|
139
|
+
* bound above skipped — and it backtracks quadratically on an opening fence
|
|
140
|
+
* followed by a long whitespace run with no closing fence: the greedy `\s*` and
|
|
141
|
+
* the lazy body re-scan the run from every split. Measured: 80 KB took 0.6 s,
|
|
142
|
+
* 320 KB nine seconds, of synchronous CPU in which no poll, lease or other
|
|
143
|
+
* lane moved — and a turn's stdout is model-written and uncapped. Same
|
|
144
|
+
* answer, by `indexOf`: the first ``` anywhere, an optional `json`, the
|
|
145
|
+
* whitespace after it skipped, and the body runs to the next ```. If that
|
|
146
|
+
* opening has no closer, no later opening can have one either, so there is no
|
|
147
|
+
* fence. The search stays over the whole text, as the bound above promises.
|
|
148
|
+
*/
|
|
149
|
+
const FENCE_SPACE = /\s*/y;
|
|
150
|
+
function fencedBody(text) {
|
|
151
|
+
const open = text.indexOf('```');
|
|
152
|
+
if (open < 0) return null;
|
|
153
|
+
let at = open + 3;
|
|
154
|
+
if (text.startsWith('json', at)) at += 4;
|
|
155
|
+
FENCE_SPACE.lastIndex = at;
|
|
156
|
+
FENCE_SPACE.exec(text);
|
|
157
|
+
at = FENCE_SPACE.lastIndex;
|
|
158
|
+
const close = text.indexOf('```', at);
|
|
159
|
+
return close < 0 ? null : text.slice(at, close);
|
|
160
|
+
}
|
|
161
|
+
|
|
128
162
|
/**
|
|
129
163
|
* For every `{`, the index of the `}` that closes it when read as the START of
|
|
130
164
|
* an object (outside any string), or -1 — the answer `balanced` gives, for all
|
|
@@ -186,8 +220,9 @@ export function parseProposal(text, scrub = envScrub) {
|
|
|
186
220
|
if (taskIds.length === 0) continue;
|
|
187
221
|
agents.push({
|
|
188
222
|
tempId: String(g.tempId || `a${i + 1}`).slice(0, 64),
|
|
189
|
-
name:
|
|
223
|
+
name: scrubCut(g.name, MAX_NAME, scrub),
|
|
190
224
|
taskIds,
|
|
225
|
+
...(typeof g.rationale === 'string' ? { rationale: scrubCut(g.rationale, 1000, scrub) } : {}),
|
|
191
226
|
...(Number.isFinite(g.pointsBudget) && g.pointsBudget > 0
|
|
192
227
|
? { pointsBudget: Math.min(Math.round(g.pointsBudget), 100_000) }
|
|
193
228
|
: {}),
|
|
@@ -218,10 +253,25 @@ export function parseProposal(text, scrub = envScrub) {
|
|
|
218
253
|
});
|
|
219
254
|
}
|
|
220
255
|
if (agents.length === 0) return null;
|
|
256
|
+
const ids = new Set(agents.flatMap(g => g.taskIds));
|
|
257
|
+
const lines = (v) => Array.isArray(v) ? v.filter(x => typeof x === 'string').slice(0, 20).map(x => scrubCut(x, 300, scrub)) : [];
|
|
258
|
+
if (parsed.evidence !== undefined && (!Array.isArray(parsed.evidence) || parsed.evidence.length > 1200 || parsed.evidence.some(e => !ids.has(e?.taskId)))) return null;
|
|
259
|
+
const evidence = parsed.evidence?.map(e => ({ taskId: e.taskId, inspectedFiles: lines(e.inspectedFiles), affectedFiles: lines(e.affectedFiles), prerequisiteOutputs: lines(e.prerequisiteOutputs), ...(typeof e.limits === 'string' ? { limits: scrubCut(e.limits, 1000, scrub) } : {}) }));
|
|
260
|
+
const collisions = Array.isArray(parsed.collisions) ? parsed.collisions.map(c => ({ taskIds: c?.taskIds, files: lines(c?.files), rationale: scrubCut(c?.rationale, 1000, scrub) })) : parsed.collisions;
|
|
261
|
+
const grouped = groupDependencies(agents, parsed.dependencies, collisions);
|
|
262
|
+
if (!grouped) return null;
|
|
263
|
+
const dependencies = parsed.dependencies?.map(e => ({ taskId: e.taskId, requires: e.requires }));
|
|
221
264
|
return {
|
|
222
|
-
agents: uniqueTempIds(
|
|
265
|
+
agents: uniqueTempIds(grouped).map(g => ({ ...g, ...((evidence !== undefined || collisions !== undefined || parsed.dependencies !== undefined) ? { evidence: {
|
|
266
|
+
cards: (evidence ?? []).filter(e => g.taskIds.includes(e.taskId)),
|
|
267
|
+
dependencies: (dependencies ?? []).filter(e => g.taskIds.includes(e.taskId)),
|
|
268
|
+
collisions: (collisions ?? []).filter(c => c.taskIds.some(id => g.taskIds.includes(id))),
|
|
269
|
+
} } : {}) })),
|
|
270
|
+
...(parsed.dependencies !== undefined ? { dependencies } : {}),
|
|
271
|
+
...(collisions !== undefined ? { collisions } : {}),
|
|
272
|
+
...(evidence !== undefined ? { evidence } : {}),
|
|
223
273
|
...(typeof parsed.note === 'string' && parsed.note.trim()
|
|
224
|
-
? { note:
|
|
274
|
+
? { note: scrubCut(parsed.note, MAX_NOTE, scrub) }
|
|
225
275
|
: {}),
|
|
226
276
|
};
|
|
227
277
|
}
|
|
@@ -284,7 +334,7 @@ function uniqueTempIds(list) {
|
|
|
284
334
|
const MAX_PROGRESS = 8000;
|
|
285
335
|
const progressOf = (v, scrub) => {
|
|
286
336
|
const t = typeof v.progress === 'string' ? v.progress.trim() : '';
|
|
287
|
-
return t ? { progress:
|
|
337
|
+
return t ? { progress: scrubCut(t, MAX_PROGRESS, scrub) } : {};
|
|
288
338
|
};
|
|
289
339
|
|
|
290
340
|
/**
|
|
@@ -337,22 +387,32 @@ const progressOf = (v, scrub) => {
|
|
|
337
387
|
* its prefix. So the scrub rides in, as `parsePrecheck`'s does, and runs over
|
|
338
388
|
* each whole field first.
|
|
339
389
|
*
|
|
340
|
-
* DEFAULTED TO THE REAL SCRUBBER,
|
|
341
|
-
* production callers pass nothing, and a redaction that
|
|
342
|
-
* remembering is the one that is missing the day it
|
|
343
|
-
* (the caller still scrubs `answer`) is harmless — a
|
|
344
|
-
* no secret to match.
|
|
390
|
+
* DEFAULTED TO THE REAL SCRUBBER, like `parseProposal` and (since 2026-10-10)
|
|
391
|
+
* `parsePrecheck`: the production callers pass nothing, and a redaction that
|
|
392
|
+
* depends on each caller remembering is the one that is missing the day it
|
|
393
|
+
* matters. Scrubbing twice (the caller still scrubs `answer`) is harmless — a
|
|
394
|
+
* redaction marker contains no secret to match.
|
|
345
395
|
*/
|
|
346
396
|
export function parseTurnResult(text, scrub = envScrub) {
|
|
347
|
-
const clean = (s, cap) =>
|
|
397
|
+
const clean = (s, cap) => scrubCut(s, cap, scrub);
|
|
348
398
|
for (const v of candidateObjects(String(text ?? ''))) {
|
|
399
|
+
if (v.status === 'prerequisite') {
|
|
400
|
+
const r = v.prerequisite;
|
|
401
|
+
if (!r || typeof r.title !== 'string' || !r.title.trim() || typeof r.brief !== 'string' || !r.brief.trim()
|
|
402
|
+
|| typeof r.taskKind !== 'string' || !Object.hasOwn(AGENT_TASK_KINDS, r.taskKind)) continue;
|
|
403
|
+
return { outcome: 'question', answer: typeof v.summary === 'string' ? clean(v.summary, 8000) : 'Requested a prerequisite card.',
|
|
404
|
+
...progressOf(v, scrub), prerequisite: { title: clean(r.title.trim(), 300), brief: clean(r.brief.trim(), 2000), taskKind: r.taskKind } };
|
|
405
|
+
}
|
|
349
406
|
if (v.status === 'blocked') {
|
|
350
407
|
const question = typeof v.question === 'string' ? v.question.trim() : '';
|
|
351
408
|
// A "blocked" with no question is not an answer anybody can act on — it
|
|
352
409
|
// parks an agent with nothing to reply to. Treated as `nothing`, which
|
|
353
410
|
// at least says truthfully that the machine went quiet.
|
|
354
411
|
if (!question) continue;
|
|
355
|
-
|
|
412
|
+
// Its declared choices (2026-10-13) ride on as the one fence the page
|
|
413
|
+
// reads, built here (agentAsk.mjs); malformed ones leave the question
|
|
414
|
+
// as plain text.
|
|
415
|
+
return { outcome: 'question', answer: blockedAnswer(clean(question, 8000), v, scrub), ...progressOf(v, scrub) };
|
|
356
416
|
}
|
|
357
417
|
if (v.status === 'delivered') {
|
|
358
418
|
return {
|
|
@@ -377,12 +437,43 @@ export function parseTurnResult(text, scrub = envScrub) {
|
|
|
377
437
|
}
|
|
378
438
|
|
|
379
439
|
/** The precheck's own bounds — mirrored at the server boundary, which caps
|
|
380
|
-
* again
|
|
440
|
+
* again, and held to it by the release gate (check-app-parity.mjs rule 31).
|
|
441
|
+
* A note is one sentence of triage and `overall` a few short points; the
|
|
381
442
|
* numbers are the ones SYSTEM_PRECHECK asks for, stated here so a model that
|
|
382
443
|
* ignores them cannot make the row bigger than the surface can render. */
|
|
383
|
-
const MAX_PRECHECK_CARDS = 60;
|
|
384
|
-
const MAX_PRECHECK_NOTE = 400;
|
|
385
|
-
const MAX_PRECHECK_OVERALL = 1200;
|
|
444
|
+
export const MAX_PRECHECK_CARDS = 60;
|
|
445
|
+
export const MAX_PRECHECK_NOTE = 400;
|
|
446
|
+
export const MAX_PRECHECK_OVERALL = 1200;
|
|
447
|
+
/** Per-criterion results (2026-10-10): the criteria kept per card, a
|
|
448
|
+
* criterion's own text, and its one line on why. */
|
|
449
|
+
export const MAX_PRECHECK_CRITERIA = 12;
|
|
450
|
+
export const MAX_PRECHECK_CRITERION = 200;
|
|
451
|
+
export const MAX_PRECHECK_LINE = 160;
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* ONE CARD'S CRITERIA, as the reader judged them (2026-10-10).
|
|
455
|
+
*
|
|
456
|
+
* PER ENTRY, the rule cards keep: a verdict word nobody listed, a missing or
|
|
457
|
+
* blank criterion, or a line that is not a string costs that row alone. A
|
|
458
|
+
* null line is the model saying it has none, so it reads as absent. The
|
|
459
|
+
* criterion's TEXT is kept, never an index: the card's Done-when list can be
|
|
460
|
+
* edited after the reading, and the row must show what was judged.
|
|
461
|
+
*/
|
|
462
|
+
function precheckCriteria(raw, scrub) {
|
|
463
|
+
if (!Array.isArray(raw)) return [];
|
|
464
|
+
const out = [];
|
|
465
|
+
for (const k of raw) {
|
|
466
|
+
if (out.length >= MAX_PRECHECK_CRITERIA) break;
|
|
467
|
+
if (!k || typeof k !== 'object') continue;
|
|
468
|
+
if (typeof k.criterion !== 'string' || !k.criterion.trim()) continue;
|
|
469
|
+
if (k.verdict !== 'met' && k.verdict !== 'concern' && k.verdict !== 'unclear') continue;
|
|
470
|
+
if (k.line != null && typeof k.line !== 'string') continue;
|
|
471
|
+
const criterion = scrubCut(k.criterion.trim(), MAX_PRECHECK_CRITERION, scrub);
|
|
472
|
+
const line = typeof k.line === 'string' ? scrubCut(k.line.trim(), MAX_PRECHECK_LINE, scrub) : '';
|
|
473
|
+
out.push({ criterion, verdict: k.verdict, ...(line ? { line } : {}) });
|
|
474
|
+
}
|
|
475
|
+
return out;
|
|
476
|
+
}
|
|
386
477
|
|
|
387
478
|
/**
|
|
388
479
|
* READING THE AI PRE-REVIEW's ANSWER (2026-09-16).
|
|
@@ -404,6 +495,11 @@ const MAX_PRECHECK_OVERALL = 1200;
|
|
|
404
495
|
* rather than coerced — `ok` is a claim ("I looked and found nothing"), and
|
|
405
496
|
* guessing it from a word nobody listed would be the parser making that claim.
|
|
406
497
|
*
|
|
498
|
+
* EACH CARD MAY CARRY ITS CRITERIA (2026-10-10), one entry per "done when"
|
|
499
|
+
* line, beside — never instead of — its verdict and note: a server from before
|
|
500
|
+
* criteria strips the field and still draws the one-row panel from those two.
|
|
501
|
+
* No criteria is no key, so that body is byte for byte what it was.
|
|
502
|
+
*
|
|
407
503
|
* `scrub` RIDES IN, AND IT RUNS BEFORE EVERY CUT (review, 2026-09-17).
|
|
408
504
|
*
|
|
409
505
|
* The caller used to scrub afterwards — `envScrub(cd.note).slice(0, 400)` over
|
|
@@ -416,11 +512,15 @@ const MAX_PRECHECK_OVERALL = 1200;
|
|
|
416
512
|
* line is the ordinary way to reach it. The fix is `toolEventOf`'s: the scrub
|
|
417
513
|
* rides INTO the builder and runs over the whole field, before the cap.
|
|
418
514
|
*
|
|
419
|
-
* DEFAULTED TO
|
|
420
|
-
*
|
|
421
|
-
*
|
|
515
|
+
* DEFAULTED TO THE REAL SCRUBBER (2026-10-10, the audit's F072), like its two
|
|
516
|
+
* neighbours. It used to default to identity "so a caller that forgets loses
|
|
517
|
+
* redaction rather than the whole reading" — the opposite stance from
|
|
518
|
+
* `parseTurnResult`'s for the same class of model-written text, and a
|
|
519
|
+
* redaction that depends on each caller remembering is the one that is missing
|
|
520
|
+
* the day it matters. Tests that want the raw reading pass identity; the one
|
|
521
|
+
* production caller passes `envScrub`, and `work.test.mjs` pins the order.
|
|
422
522
|
*/
|
|
423
|
-
export function parsePrecheck(text, scrub =
|
|
523
|
+
export function parsePrecheck(text, scrub = envScrub) {
|
|
424
524
|
const parsed = candidateObjects(String(text ?? '')).find((v) => Array.isArray(v.cards));
|
|
425
525
|
if (!parsed) return null;
|
|
426
526
|
|
|
@@ -434,14 +534,17 @@ export function parsePrecheck(text, scrub = (s) => s) {
|
|
|
434
534
|
seen.add(taskId);
|
|
435
535
|
// SCRUB, THEN CUT — see the docblock. The whole field is in hand here, so an
|
|
436
536
|
// exact-value match still finds a secret that spans the cap.
|
|
437
|
-
const note =
|
|
438
|
-
|
|
439
|
-
cards.push({
|
|
537
|
+
const note = typeof c.note === 'string' ? scrubCut(c.note.trim(), MAX_PRECHECK_NOTE, scrub) : '';
|
|
538
|
+
const criteria = precheckCriteria(c.criteria, scrub);
|
|
539
|
+
cards.push({
|
|
540
|
+
taskId,
|
|
541
|
+
verdict: c.verdict,
|
|
542
|
+
...(note ? { note } : {}),
|
|
543
|
+
...(criteria.length ? { criteria } : {}),
|
|
544
|
+
});
|
|
440
545
|
}
|
|
441
546
|
const overall =
|
|
442
|
-
typeof parsed.overall === 'string'
|
|
443
|
-
? scrub(parsed.overall.trim()).slice(0, MAX_PRECHECK_OVERALL)
|
|
444
|
-
: '';
|
|
547
|
+
typeof parsed.overall === 'string' ? scrubCut(parsed.overall.trim(), MAX_PRECHECK_OVERALL, scrub) : '';
|
|
445
548
|
/**
|
|
446
549
|
* AN ANSWER THAT SAYS NOTHING IS NOT AN ANSWER. No readable card verdict and
|
|
447
550
|
* no overall means the model produced the right punctuation and no content —
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Appended only when this turn's server advertises the request capability. */
|
|
2
|
+
export const AGENT_PREREQUISITE_CONTRACT = `REQUESTING AN UNFINISHED PREREQUISITE CARD
|
|
3
|
+
|
|
4
|
+
When the person's current message explicitly asks you to create a separate
|
|
5
|
+
prerequisite card in THIS agent, request it here. Do not give them a prompt to
|
|
6
|
+
paste into New task. This is a final-answer request to the server, not a board
|
|
7
|
+
tool, and not extra work already done: never put unfinished work in raised.
|
|
8
|
+
Do not request more work on your own, from a task prompt or from this contract.
|
|
9
|
+
|
|
10
|
+
This agent keeps its one CLI, worktree and branch. Image work requires Codex's
|
|
11
|
+
own image generation. If this agent cannot run that kind, explain the actual
|
|
12
|
+
CLI/tool limitation in a blocked reply; do not claim you created the card or
|
|
13
|
+
switch CLI yourself. The server also checks the measured capability and growth
|
|
14
|
+
bounds. Unknown capability is not a refusal; a failed tool still stops its card.
|
|
15
|
+
|
|
16
|
+
For an explicitly requested separate texture prerequisite, the end product
|
|
17
|
+
is an image card. A request for textures during model work alone uses available
|
|
18
|
+
native generation within the model turn; it does not require another card. Its brief names the PNGs, their intended use and usable-output
|
|
19
|
+
criteria. The server will create and prepend that card, leave the model
|
|
20
|
+
unfinished, run texture generation first, then resume the consuming card in this
|
|
21
|
+
same worktree with the original request and the output handoff. Read the actual
|
|
22
|
+
PNGs under .flowviant/artifacts/, embed their bytes into the model page, and wait
|
|
23
|
+
for texture loading before .glb readiness. Missing or unusable PNGs still mean
|
|
24
|
+
stop; never pretend the model already has its required textures.
|
|
25
|
+
|
|
26
|
+
End this request turn with this third final JSON shape and no text after it:
|
|
27
|
+
|
|
28
|
+
\`\`\`json
|
|
29
|
+
{
|
|
30
|
+
"status": "prerequisite",
|
|
31
|
+
"summary": "one sentence describing the prerequisite requested, without claiming it exists or the current card is delivered",
|
|
32
|
+
"progress": "two or three past-tense sentences covering work so far",
|
|
33
|
+
"prerequisite": {
|
|
34
|
+
"title": "Generate texture PNGs",
|
|
35
|
+
"brief": "Generate the requested usable texture PNGs for this model to consume in this same worktree. Name the texture subjects and requirements here.",
|
|
36
|
+
"taskKind": "image"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
\`\`\`
|
|
40
|
+
|
|
41
|
+
One prerequisite per request. Use the requested end-product kind (code, design,
|
|
42
|
+
model, image, deck or research); do not refile the consuming card. If the person
|
|
43
|
+
needs to decide something, use blocked with a specific question instead.`;
|
package/bin/lib/agentPublish.mjs
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* SHAPE, never a path, never a value that reaches argv unread.
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
-
import {
|
|
30
|
+
import { isValidSha, sessionBranchRef, stripUrlUserinfo } from './git.mjs';
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* THE SHAPE A PUBLISHED REF MAY HAVE — the server's own `PUBLISH_REF_RE`,
|
|
@@ -52,19 +52,19 @@ export function isPublishRef(ref) {
|
|
|
52
52
|
return typeof ref === 'string' && PUBLISH_REF_RE.test(ref);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
/** The local branch an agent works on. One definition
|
|
56
|
-
* the
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
/** The local branch an agent works on. One definition — git.mjs's
|
|
56
|
+
* `sessionBranchRef`, the same name the cutter and the begun-guard read — so
|
|
57
|
+
* a push, a fetch and the guard's own `rev-parse` can never name three
|
|
58
|
+
* different branches. */
|
|
59
|
+
export const agentBranchRef = sessionBranchRef;
|
|
60
60
|
|
|
61
61
|
/** A sha this machine MEASURED, in the one form `rev-parse` prints. The lease
|
|
62
62
|
* below is built from nothing else: an unrecognised value is treated as no
|
|
63
|
-
* observation at all rather than interpolated into argv.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
* observation at all rather than interpolated into argv. git.mjs's
|
|
64
|
+
* `isValidSha`, re-exported under the name this module's readers use — it
|
|
65
|
+
* was a verbatim copy of that regex, so a tightening could land in one and
|
|
66
|
+
* not the other (audit 2026-10-10, F749). */
|
|
67
|
+
export const isSha = isValidSha;
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* PUSH THIS AGENT'S BRANCH TO ITS PUBLISHED NAME.
|
|
@@ -153,8 +153,7 @@ export function publishDeleteArgs(ref) {
|
|
|
153
153
|
*/
|
|
154
154
|
const DIAGNOSTIC_RE = /^(fatal:|error:|remote:|!)|\b(denied|rejected|refused)\b/i;
|
|
155
155
|
export function publishErrorText(raw) {
|
|
156
|
-
const lines =
|
|
157
|
-
.replace(/\/\/[^/@\s]+@/g, '//')
|
|
156
|
+
const lines = stripUrlUserinfo(raw)
|
|
158
157
|
.split('\n')
|
|
159
158
|
.map((l) => l.trim())
|
|
160
159
|
.filter(Boolean);
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
* which Claude and — since 0.115.0 — Codex declare (2026-09-29, measured:
|
|
38
38
|
* runtimeCodex.mjs `codexDesignFence`). */
|
|
39
39
|
const DESIGN_CLIS = 'mockup, 3D model and presentation cards run on Claude or Codex on this machine';
|
|
40
|
-
/**
|
|
41
|
-
*
|
|
42
|
-
const
|
|
40
|
+
/** Write-ups run on Claude or isolated Codex (0.117.0); the copied input view
|
|
41
|
+
* keeps host credentials outside the web-enabled tool fence. */
|
|
42
|
+
const RESEARCH_CLIS = 'write-up cards run on Claude or Codex on this machine';
|
|
43
43
|
/** The image kind runs on Codex: the one CLI that declares its posture, because
|
|
44
44
|
* the pictures come from Codex's own image tool on its own login. */
|
|
45
45
|
const CODEX_ONLY = 'image cards run on Codex on this machine';
|
|
@@ -90,7 +90,7 @@ export const AGENT_TASK_KINDS = Object.freeze({
|
|
|
90
90
|
posture: 'image',
|
|
91
91
|
artifact: Object.freeze({
|
|
92
92
|
match: /\.(png|webp)$/i,
|
|
93
|
-
missing: 'the turn ended without writing a PNG or WebP image under .flowviant/artifacts/',
|
|
93
|
+
missing: 'the turn ended without writing a usable PNG or WebP image under .flowviant/artifacts/',
|
|
94
94
|
}),
|
|
95
95
|
offRuntime: () => CODEX_ONLY,
|
|
96
96
|
}),
|
|
@@ -109,7 +109,7 @@ export const AGENT_TASK_KINDS = Object.freeze({
|
|
|
109
109
|
match: /\.md$/i,
|
|
110
110
|
missing: 'the turn ended without writing a write-up under .flowviant/artifacts/',
|
|
111
111
|
}),
|
|
112
|
-
offRuntime: () =>
|
|
112
|
+
offRuntime: () => RESEARCH_CLIS,
|
|
113
113
|
}),
|
|
114
114
|
});
|
|
115
115
|
|
|
@@ -144,9 +144,6 @@ export const unknownAgentTaskKind = (v) =>
|
|
|
144
144
|
? null
|
|
145
145
|
: String(v).replace(/[\u0000-\u001f\u007f]/g, '').slice(0, 40);
|
|
146
146
|
|
|
147
|
-
/** The table entry for a kind value, read the printing way (unknown → code). */
|
|
148
|
-
export const agentTaskKind = (v) => AGENT_TASK_KINDS[agentTaskKindOf(v)];
|
|
149
|
-
|
|
150
147
|
/** The postures only a card's kind selects (every kind's but code's), each
|
|
151
148
|
* once: the 3D-model and presentation kinds reuse the design posture; the
|
|
152
149
|
* image kind has its own. */
|
package/bin/lib/agySessions.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import { homedir } from 'node:os';
|
|
|
13
13
|
import { join } from 'node:path';
|
|
14
14
|
import { execFileSync } from 'node:child_process';
|
|
15
15
|
import { procPids, scanProcs } from './procScan.mjs';
|
|
16
|
+
import { AGY_CONV_RE } from './conversationIds.mjs';
|
|
16
17
|
|
|
17
18
|
// agy's store is nothing like Claude's: one SQLite db per conversation at
|
|
18
19
|
// ~/.gemini/antigravity-cli/conversations/<uuid>.db (global, not cwd-keyed),
|
|
@@ -24,13 +25,12 @@ import { procPids, scanProcs } from './procScan.mjs';
|
|
|
24
25
|
// the person at that keyboard, i.e. the one worth offering to adopt.
|
|
25
26
|
|
|
26
27
|
const AGY_DIR = () => join(homedir(), '.gemini', 'antigravity-cli');
|
|
27
|
-
const AGY_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
28
28
|
|
|
29
29
|
/** Newest write to the conversation's store — the wal carries recent turns,
|
|
30
30
|
* so its mtime (not the db's) is the real "last active" (measured: a resume
|
|
31
31
|
* touched db+wal, and never the presence lock). 0 = no such conversation. */
|
|
32
32
|
function agyLastWriteMs(id) {
|
|
33
|
-
if (!
|
|
33
|
+
if (!AGY_CONV_RE.test(id)) return 0;
|
|
34
34
|
let newest = 0;
|
|
35
35
|
for (const suffix of ['.db', '.db-wal']) {
|
|
36
36
|
try {
|
|
@@ -121,7 +121,7 @@ export function scanAgyConversations({ ours, mine, cutoff, processAlive = agyPro
|
|
|
121
121
|
if (!map || typeof map !== 'object') return out;
|
|
122
122
|
const processUp = processAlive();
|
|
123
123
|
for (const [cwd, id] of Object.entries(map)) {
|
|
124
|
-
if (typeof id !== 'string' || !
|
|
124
|
+
if (typeof id !== 'string' || !AGY_CONV_RE.test(id)) continue;
|
|
125
125
|
// The operator's own agy TAB, whose conversation is registered against
|
|
126
126
|
// the checkout exactly as a terminal one would be.
|
|
127
127
|
if (mine.has(id)) continue;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* and research contracts in prompts.mjs (which is past its size and keeps
|
|
7
7
|
* those two). `prompts.mjs` imports these into `AGENT_CONTRACTS`; the kind
|
|
8
8
|
* table (agentTaskKinds.mjs) runs both under the `design` posture — read the
|
|
9
|
-
* repo, write only
|
|
9
|
+
* repo, write only artifacts and scratch, with CLI-specific supporting tools —
|
|
10
10
|
* so "change no repository file" is enforced, not asked.
|
|
11
11
|
*
|
|
12
12
|
* THE SAME BONES AS THE DESIGN CONTRACT, deliberately: its header, its rule 3
|
|
@@ -26,13 +26,70 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import { ARTIFACT_BINARY_MODEL_EXTS, ARTIFACT_BINARY_MODEL_MAX_BYTES, ARTIFACT_MAX_BYTES } from './artifactPolicy.mjs';
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* WHEN AN AGENT STOPS TO ASK, IT HELPS THE PERSON DECIDE (2026-10-03; its
|
|
31
|
+
* choices as fields of their own, 2026-10-13).
|
|
32
|
+
*
|
|
33
|
+
* "STOP AND ASK" used to be the whole of it, so a stuck agent asked one bare
|
|
34
|
+
* question with its choices inlined as "(1) … (2) …", and the Agent page had
|
|
35
|
+
* nothing to draw but "Write my own answer". Every card-agent contract (code
|
|
36
|
+
* and every file-handing kind) closes on this: read the card, the repo and
|
|
37
|
+
* the run first; ask ONE question per turn, its reason in `context` (body
|
|
38
|
+
* text beneath the one-line question, 2026-10-13); name the choices
|
|
39
|
+
* in `options` (and `multiSelect`) beside `question`, which the daemon turns
|
|
40
|
+
* into the one fence the page reads (`agentAsk.mjs`) — the agent never writes
|
|
41
|
+
* the fence itself, the owner's ruling; re-check after each answer; never
|
|
42
|
+
* re-ask. The label rules are SYSTEM_WORK rule 5's, because the page sends a
|
|
43
|
+
* label as the answer exactly as the Terminal's card does.
|
|
44
|
+
*
|
|
45
|
+
* THE SKIP IS THE APP'S. The agent page offers "Skip this question" on every
|
|
46
|
+
* standing question and sends a reply that BEGINS with those words (Flowviant
|
|
47
|
+
* `AGENT_SKIP_REPLY`) through the ordinary answer lane — so this keys on the
|
|
48
|
+
* prefix, never the whole sentence, and the app's wording can change without
|
|
49
|
+
* a daemon release. A skip is a stated provisional default where the work can
|
|
50
|
+
* go on, and never a substitute for an input only the person holds.
|
|
51
|
+
*/
|
|
52
|
+
export const HELP_THEM_DECIDE = `WHEN YOU STOP TO ASK, HELP THEM DECIDE. The person may not know what the card
|
|
53
|
+
is missing, so you lead:
|
|
54
|
+
- Before asking, read the whole card (brief, done-when and its discussion),
|
|
55
|
+
the repository and this run, and check the card for every gap that would
|
|
56
|
+
change the work: the goal, who it is for, scope, the flows and behaviour,
|
|
57
|
+
edge cases, constraints, and how done is judged. Never ask what the card,
|
|
58
|
+
the repository or an earlier answer already settles.
|
|
59
|
+
- Ask ONE question per turn, the one that matters most. "question" is the
|
|
60
|
+
question itself — one sentence, read as a heading. In "context", say what
|
|
61
|
+
decision it settles and how it changes the work, with any findings or
|
|
62
|
+
evidence the person needs; it shows beneath the question as body text, in
|
|
63
|
+
Markdown, so lists and line breaks survive.
|
|
64
|
+
- When the answer is a pick between options you can name, put them in
|
|
65
|
+
"options" — two to eight — and the page shows each as a choice beside a
|
|
66
|
+
box for their own words. Never inline the choices in "question" as
|
|
67
|
+
"(1) … (2) …", and never write a fenced block yourself: the fields are the
|
|
68
|
+
whole of it. A label IS the answer, a few words a person would say out
|
|
69
|
+
loud, never a comma (several chosen labels arrive comma-joined, in the
|
|
70
|
+
order listed); a tradeoff goes in "description", one short sentence,
|
|
71
|
+
optional. Never add an "Other" or "Skip" option — the page offers its own
|
|
72
|
+
free-text answer and its own Skip. "multiSelect": true only for a genuine
|
|
73
|
+
check-several-of-these case ("which pages?"); otherwise false. For an open
|
|
74
|
+
question — one only their own words can answer — leave "options" out.
|
|
75
|
+
- When they answer, check the card again before you go on: ask the next
|
|
76
|
+
question that still changes the work, or carry on with the card once none
|
|
77
|
+
is left. Never ask again a question they answered or skipped.
|
|
78
|
+
- A message that begins "Skip this question" skips your last question. Where
|
|
79
|
+
the work can go on, take your best default, say it plainly as PROVISIONAL in
|
|
80
|
+
"progress" and in "summary", and carry on. Where it truly cannot go on
|
|
81
|
+
without something only they can provide — a credential, an account, an
|
|
82
|
+
outside decision — stop and name exactly that dependency; a skip never
|
|
83
|
+
supplies it.`;
|
|
84
|
+
|
|
29
85
|
/**
|
|
30
86
|
* Rule 3, rule 4 and the final JSON a file-handing contract ends with.
|
|
31
87
|
* `ask` is rule 4's body after its number (the decision only a person can
|
|
32
88
|
* make); `summary` is the delivered shape's summary gloss.
|
|
33
89
|
*/
|
|
34
90
|
export const ARTIFACT_CONTRACT_TAIL = ({ ask, summary }) => `3. CHANGE NO REPOSITORY FILE AND COMMIT NOTHING. You can only write under
|
|
35
|
-
.flowviant/artifacts
|
|
91
|
+
.flowviant/artifacts/ and the turn’s granted scratch directory; deliver
|
|
92
|
+
only artifacts. The daemon keeps those directories out of git.
|
|
36
93
|
|
|
37
94
|
4. ${ask}
|
|
38
95
|
|
|
@@ -52,12 +109,17 @@ or, if you are stopping to ask:
|
|
|
52
109
|
{
|
|
53
110
|
"status": "blocked",
|
|
54
111
|
"progress": "two or three sentences on what you have done in this run SO FAR, across every card",
|
|
55
|
-
"
|
|
112
|
+
"context": "optional: what the decision settles and why, and any findings, in Markdown",
|
|
113
|
+
"question": "the specific thing you need decided, in one sentence",
|
|
114
|
+
"options": [{ "label": "an answer they could pick", "description": "optional: its tradeoff, in one short sentence" }],
|
|
115
|
+
"multiSelect": false
|
|
56
116
|
}
|
|
57
117
|
\`\`\`
|
|
58
118
|
|
|
59
119
|
"progress" is REQUIRED on both shapes and REPLACES the previous one whole —
|
|
60
|
-
past tense, plain sentences, no card ids
|
|
120
|
+
past tense, plain sentences, no card ids.
|
|
121
|
+
|
|
122
|
+
${HELP_THEM_DECIDE}`;
|
|
61
123
|
|
|
62
124
|
/**
|
|
63
125
|
* NOBODY IS AT THIS SCREEN (2026-09-29, the owner: Chrome "kept opening on my
|
|
@@ -87,13 +149,10 @@ export const LOOK_HEADLESS = `${NO_WINDOW} To look at your page, screenshot it
|
|
|
87
149
|
* when the person presses Download .glb in Review (apps/web
|
|
88
150
|
* `workbench/glbExport.ts`, the other half of the handshake below).
|
|
89
151
|
*
|
|
90
|
-
* WHY
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* katana came back as seven files, textures no game engine imports and a
|
|
95
|
-
* generator script (2026-09-27). Built in code, drawn textures are canvases,
|
|
96
|
-
* and the exporter embeds them in the .glb as PNGs.
|
|
152
|
+
* WHY A PAGE: one self-contained viewer gives every supported CLI the same
|
|
153
|
+
* delivered product. Supporting native textures are embedded into the page;
|
|
154
|
+
* GLTFExporter embeds their pixels into the person's one downloaded GLB.
|
|
155
|
+
* Available helpers do not change that contract into a pile of model files.
|
|
97
156
|
*
|
|
98
157
|
* THE HANDSHAKE IS SPELLED WORD FOR WORD because the web keys on it: the page
|
|
99
158
|
* announces `glb-ready` once its model stands, and answers `export-glb` with
|
|
@@ -103,8 +162,8 @@ export const LOOK_HEADLESS = `${NO_WINDOW} To look at your page, screenshot it
|
|
|
103
162
|
* it is just never offered a Download .glb.
|
|
104
163
|
*/
|
|
105
164
|
export const SYSTEM_AGENT_MODEL = `You are the human's own Claude, working one 3D MODEL card in a git worktree of
|
|
106
|
-
their repository. Nobody is watching this run. You have the repo and
|
|
107
|
-
|
|
165
|
+
their repository. Nobody is watching this run. You have the repo and the tools
|
|
166
|
+
TOOLS ON THIS CARD names below — no board, no chat.
|
|
108
167
|
|
|
109
168
|
A 3D MODEL CARD HANDS BACK A MODEL YOU CAN LOOK AT, NOT A CHANGE. Nothing you
|
|
110
169
|
do here edits the product: the person turns the model around, downloads it as
|
|
@@ -121,7 +180,8 @@ WHAT TO DO:
|
|
|
121
180
|
2. Write ONE file, .flowviant/artifacts/<kebab-name>/index.html — a short
|
|
122
181
|
kebab-case name for what it is (for example
|
|
123
182
|
.flowviant/artifacts/reading-chair/index.html). Nothing else goes in that
|
|
124
|
-
folder: no model files, no
|
|
183
|
+
folder: no model files, no scripts, no notes. Generated source textures
|
|
184
|
+
may be kept elsewhere under .flowviant/artifacts/; the page is self-contained. The page BUILDS
|
|
125
185
|
THE MODEL IN CODE and shows it:
|
|
126
186
|
- three.js as ES modules from cdn.jsdelivr.net/npm, through an import map
|
|
127
187
|
for "three" and "three/addons/"; orbit controls, lights and a neutral
|
|
@@ -129,8 +189,18 @@ WHAT TO DO:
|
|
|
129
189
|
- the model is ONE THREE.Group named for what it is, built from three.js
|
|
130
190
|
geometry (lathe, extrude, tube, box and the rest, one mesh per part),
|
|
131
191
|
with MeshStandardMaterial only. Colour, roughness and metalness are
|
|
132
|
-
material values,
|
|
133
|
-
|
|
192
|
+
material values, canvas textures, or generated image textures. When the
|
|
193
|
+
person requests generated textures, USE native image generation if it is
|
|
194
|
+
available on this turn; do not require another card or change the kind.
|
|
195
|
+
Embed each texture as a data: URI in this page (no local or remote image
|
|
196
|
+
URLs), load it with THREE.TextureLoader and wait for all textures before
|
|
197
|
+
announcing glb-ready. Keep maps at 1024px or less and set colour maps to
|
|
198
|
+
THREE.SRGBColorSpace. Assign maps to the model's MeshStandardMaterial so
|
|
199
|
+
GLTFExporter embeds the texture pixels in the downloaded .glb. Await
|
|
200
|
+
image decoding before export; never announce readiness for unloaded maps.
|
|
201
|
+
If the native tool is missing or fails, explain its actual limitation.
|
|
202
|
+
A separately billed CLI/API fallback requires the person's explicit
|
|
203
|
+
choice before using it; never ask for OPENAI_API_KEY as the default path.
|
|
134
204
|
- the ground, a grid, the lights, a figure for scale and every other
|
|
135
205
|
helper stay OUTSIDE that group: they are the viewer's, not the model.
|
|
136
206
|
- no download or save button of your own: the page cannot download
|
|
@@ -168,8 +238,8 @@ ${ARTIFACT_CONTRACT_TAIL({
|
|
|
168
238
|
* Print, never a converter this machine would have to run.
|
|
169
239
|
*/
|
|
170
240
|
export const SYSTEM_AGENT_DECK = `You are the human's own Claude, working one PRESENTATION card in a git worktree
|
|
171
|
-
of their repository. Nobody is watching this run. You have the repo and
|
|
172
|
-
|
|
241
|
+
of their repository. Nobody is watching this run. You have the repo and the tools
|
|
242
|
+
TOOLS ON THIS CARD names below — no board, no chat.
|
|
173
243
|
|
|
174
244
|
A PRESENTATION CARD HANDS BACK A DECK, NOT A CHANGE. Nothing you do here edits
|
|
175
245
|
the product: the person pages through it, and decides.
|
|
@@ -232,8 +302,9 @@ export const IMAGE_CAP_WORDS = `${Math.floor(imageCapBytes / (1024 * 1024))} MB`
|
|
|
232
302
|
* to the person, quoting the tool's words, never a substitute.
|
|
233
303
|
*/
|
|
234
304
|
export const SYSTEM_AGENT_IMAGE = `You are the human's own Codex, working one IMAGE card in a git worktree of
|
|
235
|
-
their repository. Nobody is watching this run. You have the repo
|
|
236
|
-
generation tool
|
|
305
|
+
their repository. Nobody is watching this run. You have the repo, your image
|
|
306
|
+
generation tool and the tools TOOLS ON THIS CARD names below — no board, no
|
|
307
|
+
chat, no web.
|
|
237
308
|
|
|
238
309
|
AN IMAGE CARD HANDS BACK PICTURES, NOT A CHANGE. Nothing you do here edits the
|
|
239
310
|
product: the person looks at the images, downloads the ones they want, and
|