flowviant 0.125.29 → 0.125.31
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 +364 -0
- package/bin/cli.mjs +23 -0
- package/bin/lib/agentPlan.mjs +8 -0
- package/bin/lib/agentQueueContext.mjs +6 -0
- package/bin/lib/agentSplit.mjs +90 -0
- package/bin/lib/architectureContract.mjs +57 -0
- package/bin/lib/architectureReport.mjs +38 -0
- package/bin/lib/artifactContracts.mjs +21 -2
- package/bin/lib/claudeInput.mjs +1 -1
- package/bin/lib/claudeSubagents.mjs +46 -6
- package/bin/lib/claudeWorkflows.mjs +94 -0
- package/bin/lib/cliLimit.mjs +5 -0
- package/bin/lib/codexInput.mjs +191 -0
- package/bin/lib/codexSubagentEvents.mjs +3 -1
- package/bin/lib/codexSubagents.mjs +34 -9
- package/bin/lib/conversationOutput.mjs +14 -0
- package/bin/lib/crossCliChild.mjs +327 -0
- package/bin/lib/crossCliLaunch.mjs +92 -0
- package/bin/lib/crossCliPosture.mjs +55 -0
- package/bin/lib/crossCliResearch.mjs +19 -0
- package/bin/lib/environmentProcess.mjs +44 -0
- package/bin/lib/fixtures/workflow-ultracode.jsonl +22 -0
- package/bin/lib/fleet.mjs +2 -0
- package/bin/lib/linuxDesktopBridge.mjs +62 -0
- package/bin/lib/managedBrowser.mjs +50 -10
- package/bin/lib/mergeBatch.mjs +1 -1
- package/bin/lib/nativeAgentTools.mjs +12 -5
- package/bin/lib/noWindowEnv.mjs +4 -2
- package/bin/lib/orchestrator.mjs +24 -7
- package/bin/lib/orchestratorClient.mjs +8 -2
- package/bin/lib/orchestratorPrompt.mjs +13 -5
- package/bin/lib/orchestratorProtocol.mjs +18 -0
- package/bin/lib/outsideCli.mjs +124 -0
- package/bin/lib/projectToolRuntimes.mjs +4 -2
- package/bin/lib/projectTools.mjs +2 -2
- package/bin/lib/prompts.mjs +7 -5
- package/bin/lib/repositoryPlaces.mjs +1 -1
- package/bin/lib/repositorySetup.mjs +15 -24
- package/bin/lib/reviewBridge.mjs +110 -0
- package/bin/lib/reviewDatabase.mjs +112 -0
- package/bin/lib/reviewDesktop.mjs +153 -0
- package/bin/lib/reviewEnvironment.mjs +286 -0
- package/bin/lib/reviewEnvironmentConfig.mjs +104 -0
- package/bin/lib/reviewEnvironmentMain.mjs +38 -0
- package/bin/lib/reviewImage.mjs +58 -0
- package/bin/lib/reviewMcpClient.mjs +69 -0
- package/bin/lib/reviewProcess.mjs +51 -0
- package/bin/lib/reviewTargets.mjs +50 -0
- package/bin/lib/robloxReview.mjs +234 -0
- package/bin/lib/robloxStudioBridge.mjs +68 -0
- package/bin/lib/runTurn.mjs +30 -8
- package/bin/lib/runtimeClaude.mjs +7 -3
- package/bin/lib/runtimeCodex.mjs +12 -6
- package/bin/lib/runtimeLimits.mjs +59 -1
- package/bin/lib/runtimeReview.mjs +185 -0
- package/bin/lib/splitGate.mjs +115 -0
- package/bin/lib/subagentBounds.mjs +26 -0
- package/bin/lib/subagentLedger.mjs +110 -0
- package/bin/lib/trace.mjs +134 -6
- package/bin/lib/turnDeclaration.mjs +4 -4
- package/bin/lib/turnGaps.mjs +6 -2
- package/bin/lib/turnSubagents.mjs +23 -9
- package/bin/lib/uiReview.mjs +5 -2
- package/bin/lib/wikiDraft.mjs +9 -0
- package/bin/lib/work.mjs +22 -1
- package/bin/lib/workAgentCheck.mjs +24 -1
- package/bin/lib/workAgentResume.mjs +11 -2
- package/bin/lib/workAgentTools.mjs +6 -4
- package/bin/lib/workAgentTurnExecution.mjs +77 -9
- package/bin/lib/workAgentTurnOutcome.mjs +11 -0
- package/bin/lib/workAgentTurnReports.mjs +5 -2
- package/package.json +1 -1
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SUBAGENTS ON ANOTHER CLI (2026-11-06, D21, E7 S7.2–S7.5): a Claude lead
|
|
3
|
+
* starts Codex children, and a Codex lead Claude ones, through four
|
|
4
|
+
* card_orchestrator tools (orchestratorProtocol.mjs `CROSS_CLI_TOOLS`):
|
|
5
|
+
*
|
|
6
|
+
* start_cli_agent{cli, title, kind, brief, model?, effort?} → {agentId, status}
|
|
7
|
+
* followup_cli_agent{agentId, message} → {agentId, status}
|
|
8
|
+
* stop_cli_agent{agentId} → {agentId, status}
|
|
9
|
+
* cli_agent_result{agentId, waitSeconds≤300} → {agentId, cli, status,
|
|
10
|
+
* model, effort, result, filesChanged, error}
|
|
11
|
+
*
|
|
12
|
+
* THE DAEMON STARTS AND FENCES EVERY SUCH CHILD ITSELF (spike S7.1): a child
|
|
13
|
+
* the lead's MCP server started would run outside the lead's sandbox, so the
|
|
14
|
+
* child runs through runTurn with the card's own posture for the CHILD's CLI
|
|
15
|
+
* (crossCliLaunch.mjs, crossCliPosture.mjs). Start is asynchronous: the lead's
|
|
16
|
+
* edits and its MCP calls run one at a time, so a blocking start would stop
|
|
17
|
+
* it working beside the child. The child's result returns as the tool result.
|
|
18
|
+
*
|
|
19
|
+
* WHAT THE RECORD SAYS IS MEASURED, never what was asked: the key is the
|
|
20
|
+
* child's own thread or session id (`codex:<thread>`, `claude:<session>`),
|
|
21
|
+
* its model what its stream or rollout named, its effort its rollout's (Claude
|
|
22
|
+
* states none). It is a subagent record like a native one (`cli` the child's,
|
|
23
|
+
* `parent` 'lead'), so the folds and the card tab's Agents list draw it.
|
|
24
|
+
*
|
|
25
|
+
* ONLY ON THE PERSON'S CHOICE (D30): a child on another CLI starts only under
|
|
26
|
+
* an approved split or the Always switch whose batch CLI is that CLI, and
|
|
27
|
+
* only on a CLI this machine measured ready (D16). Its model is checked
|
|
28
|
+
* against the child CLI's own measured list and its effort is never above the
|
|
29
|
+
* card's; a refusal answers in words. It spends the machine's own login for
|
|
30
|
+
* its CLI and a slot in the project's Turns at once pool, said at the child
|
|
31
|
+
* while it waits. Its CLI's plan limit (S7.5) settles only the child —
|
|
32
|
+
* `limited`, with the CLI's words and a measured reset when there is one —
|
|
33
|
+
* never the lead, which is told in the tool result and may retry or do the
|
|
34
|
+
* part itself.
|
|
35
|
+
*/
|
|
36
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
37
|
+
import { CLAUDE_SESSION_RE, CODEX_THREAD_RE } from './conversationIds.mjs';
|
|
38
|
+
import { limitLine } from './cliLimit.mjs';
|
|
39
|
+
import { codexRolloutFile, limitResetAt } from './runtimeLimits.mjs';
|
|
40
|
+
import { runtimeById } from './runtimes.mjs';
|
|
41
|
+
import { claudeFamily, EFFORT_LADDER } from './orchestratorNative.mjs';
|
|
42
|
+
import { SUBAGENT_CAP, SUBAGENT_CLIS, SUBAGENT_FIELD_CAPS } from './subagentBounds.mjs';
|
|
43
|
+
import { subagentKey } from './turnSubagents.mjs';
|
|
44
|
+
import { TURN_MODEL_RE } from './turnModel.mjs';
|
|
45
|
+
import { interruptThenKill } from './processGroup.mjs';
|
|
46
|
+
|
|
47
|
+
export const CROSS_CLI_ACTIONS = Object.freeze(['start_cli_agent', 'followup_cli_agent', 'cli_agent_result', 'stop_cli_agent']);
|
|
48
|
+
export const CROSS_CLI_RESULT_WAIT_MAX_S = 300;
|
|
49
|
+
/** How long a start waits for the child's own identity before answering. */
|
|
50
|
+
const START_WAIT_MS = 10_000;
|
|
51
|
+
const WAIT = 'Waiting for a free slot on the machine';
|
|
52
|
+
const LABEL = { claude: 'Claude Code', codex: 'Codex' };
|
|
53
|
+
const label = cli => LABEL[cli] ?? cli;
|
|
54
|
+
const filled = s => typeof s === 'string' && s.trim().length > 0;
|
|
55
|
+
const rank = e => EFFORT_LADDER.indexOf(String(e ?? '').toLowerCase());
|
|
56
|
+
const sleep = (ms, signal) => new Promise(r => { const t = setTimeout(r, ms); t.unref?.(); signal?.then?.(() => { clearTimeout(t); r(); }); });
|
|
57
|
+
|
|
58
|
+
/** The words a refused start or follow-up answers in, as the client relays them. */
|
|
59
|
+
class Refusal extends Error {}
|
|
60
|
+
|
|
61
|
+
/** The model and effort of a Codex thread's newest `turn_context`, or nulls. */
|
|
62
|
+
export async function codexThreadBrain(thread, { env = process.env } = {}) {
|
|
63
|
+
try {
|
|
64
|
+
const file = await codexRolloutFile(thread, { env });
|
|
65
|
+
if (!file || (await stat(file)).size > 16 * 1024 * 1024) return { model: null, effort: null };
|
|
66
|
+
let model = null, effort = null;
|
|
67
|
+
for (const line of (await readFile(file, 'utf8')).split('\n')) {
|
|
68
|
+
if (!line.includes('"turn_context"')) continue;
|
|
69
|
+
let ev; try { ev = JSON.parse(line); } catch { continue; }
|
|
70
|
+
if (ev?.type !== 'turn_context') continue;
|
|
71
|
+
const p = ev.payload ?? {}, e = p.effort ?? p.collaboration_mode?.settings?.reasoning_effort;
|
|
72
|
+
model = typeof p.model === 'string' && TURN_MODEL_RE.test(p.model) ? p.model : null;
|
|
73
|
+
effort = typeof e === 'string' && /^[a-z]{1,24}$/.test(e) ? e : null;
|
|
74
|
+
}
|
|
75
|
+
return { model, effort };
|
|
76
|
+
} catch { return { model: null, effort: null }; }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Bounded question returned only to the lead; never the person's question path. */
|
|
80
|
+
export function questionOf(value) {
|
|
81
|
+
let q = value;
|
|
82
|
+
if (typeof q === 'string') {
|
|
83
|
+
try { q = JSON.parse(q.replace(/^```(?:json)?\s*|\s*```$/g, '')); }
|
|
84
|
+
catch { return q.trim().endsWith('?') ? { question: q.trim().slice(-4000), options: [] } : null; }
|
|
85
|
+
}
|
|
86
|
+
if (Array.isArray(q?.questions)) q = q.questions[0];
|
|
87
|
+
if (!q || typeof q.question !== 'string' || !q.question.trim()) return null;
|
|
88
|
+
return { question: q.question.trim().slice(0, 4000), options: Array.isArray(q.options) ? q.options.slice(0, 8).map(o =>
|
|
89
|
+
typeof o === 'string' ? o.slice(0, 500) : { label: String(o?.label ?? '').slice(0, 200), description: String(o?.description ?? '').slice(0, 500) }) : [] };
|
|
90
|
+
}
|
|
91
|
+
async function codexThreadQuestion(thread, since) {
|
|
92
|
+
try {
|
|
93
|
+
const file = await codexRolloutFile(thread);
|
|
94
|
+
if (!file || (await stat(file)).size > 16 * 1024 * 1024) return null;
|
|
95
|
+
let question = null;
|
|
96
|
+
for (const line of (await readFile(file, 'utf8')).split('\n')) {
|
|
97
|
+
if (!line.includes('request_user_input')) continue;
|
|
98
|
+
let ev; try { ev = JSON.parse(line); } catch { continue; }
|
|
99
|
+
if (Date.parse(ev.timestamp) < since || !Number.isFinite(Date.parse(ev.timestamp))) continue;
|
|
100
|
+
const p = ev.payload;
|
|
101
|
+
if (p?.type === 'function_call' && /(?:^|[.])request_user_input$/.test(p.name ?? '')) {
|
|
102
|
+
let args; try { args = JSON.parse(p.arguments); } catch { continue; }
|
|
103
|
+
question = questionOf(args);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return question;
|
|
107
|
+
} catch { return null; }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The run's children on other CLIs. `lead` is the lead's CLI; `launch` is
|
|
112
|
+
* crossCliLaunch.mjs's, which spawns one child turn; `ready()` the CLIs this
|
|
113
|
+
* machine measured drivable; `catalog()` the measured Codex model list; `card`
|
|
114
|
+
* the lead's `{ model, effort }`; `split` the job's policy (splitGate.mjs
|
|
115
|
+
* `splitPolicy`); `emit` the controller's record writer; `admit` its pool.
|
|
116
|
+
*/
|
|
117
|
+
export function createCrossCliChildren({ lead, launch, ready = () => [], catalog = () => [], card = {}, split = null, emit, count = () => 0,
|
|
118
|
+
admit, ledger = null, wt = null, access = 'edit', announce = () => {}, interval = 500, startWaitMs = START_WAIT_MS, threadBrain = codexThreadBrain, resetAt = limitResetAt }) {
|
|
119
|
+
const kids = new Map();
|
|
120
|
+
let closed = false, n = 0, wake;
|
|
121
|
+
const ended = new Promise(r => { wake = r; });
|
|
122
|
+
const offered = () => SUBAGENT_CLIS.filter(cli => cli !== lead && ready().includes(cli));
|
|
123
|
+
const view = st => ({ agentId: st.id, ...(st.key ? { key: st.key } : {}), cli: st.cli, status: st.status, model: st.model ?? null, effort: st.effort ?? null,
|
|
124
|
+
result: st.status === 'running' || st.status === 'waiting' ? null : st.result ?? null, filesChanged: [...st.files],
|
|
125
|
+
error: st.error ?? null, ...(st.resetsAt ? { resetsAt: st.resetsAt } : {}), ...(st.status === 'waiting' ? { now: WAIT } : {}), ...(st.question ? { question: st.question.question, options: st.question.options } : {}) });
|
|
126
|
+
|
|
127
|
+
/** Whether the person's choice for this batch lets this CLI run a child. */
|
|
128
|
+
function choice(cli) {
|
|
129
|
+
if (access === 'read') return null;
|
|
130
|
+
if (!split || split.mode === 'ask') return `A subagent on ${label(cli)} edits files in this worktree, and the person has not approved a split, so it was not started. Propose the split instead: end your turn with the split JSON (status "split"); the person picks the CLI the batch runs on. Or do this part yourself.`;
|
|
131
|
+
if (split.runtime !== cli) return `The person chose ${label(split.runtime ?? lead)} for this batch's subagents, so ${split.runtime && split.runtime !== lead ? `start them with start_cli_agent and cli "${split.runtime}"` : 'start them as native subagents'}, not on ${label(cli)}.`;
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
function brainFor(cli, args) {
|
|
135
|
+
const own = split?.runtime === cli ? split : {};
|
|
136
|
+
const model = filled(args.model) ? args.model.trim() : own.model ?? null, effort = filled(args.effort) ? args.effort.trim().toLowerCase() : own.effort ?? null;
|
|
137
|
+
if (model && !TURN_MODEL_RE.test(model)) throw new Refusal('That model name is not one a CLI takes.');
|
|
138
|
+
if (model && cli === 'claude' && !claudeFamily(model)) throw new Refusal('Name a Claude model for a Claude Code subagent (opus, sonnet, haiku or a full claude- id), or leave it out for the daemon’s pin.');
|
|
139
|
+
const listed = cli === 'codex' ? catalog() : [];
|
|
140
|
+
// An unmeasured list refuses nothing: the CLI answers for itself.
|
|
141
|
+
const row = listed.find(m => m?.slug === model);
|
|
142
|
+
if (model && cli === 'codex' && listed.length && !row) throw new Refusal(`${model} is not in this machine's Codex model list (${listed.map(m => m.slug).filter(Boolean).slice(0, 12).join(', ')}).`);
|
|
143
|
+
if (effort) {
|
|
144
|
+
const efforts = Array.isArray(row?.efforts) && row.efforts.length ? row.efforts : runtimeById(cli)?.efforts ?? [];
|
|
145
|
+
if (!efforts.includes(effort)) throw new Refusal(`${label(cli)} takes ${efforts.join(', ')} as an effort${row ? ` on ${model}` : ''}.`);
|
|
146
|
+
if (rank(card.effort) >= 0 && rank(effort) > rank(card.effort)) throw new Refusal(`Choose an effort at or below this card's (${card.effort}) for a subagent, or leave it out.`);
|
|
147
|
+
}
|
|
148
|
+
return { model, effort: effort ?? (rank(card.effort) >= 0 && (runtimeById(cli)?.efforts ?? []).includes(card.effort) ? card.effort : null) };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function remember(st) {
|
|
152
|
+
if (st.key) ledger?.rememberCli(st.key, { id: st.id, cli: st.cli, native: st.native, wt,
|
|
153
|
+
title: st.title, kind: st.kind, model: st.model ?? null, effort: st.effort ?? null });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function settle(st, { out = '', error = null, failed = false, rejected = false, code = null, signal = null, answer = null }) {
|
|
157
|
+
st.proc = null;
|
|
158
|
+
// Closed: the controller already said 'Stopped with the run' or 'Run ended'.
|
|
159
|
+
if (closed || st.stopped) { st.active = false; st.status = 'stopped'; st.wake?.(); return; }
|
|
160
|
+
st.finishing = true;
|
|
161
|
+
const words = (error?.message ?? st.lastError ?? String(out).trim().split('\n').filter(Boolean).at(-1) ?? '').trim();
|
|
162
|
+
const failedRun = Boolean(error) || failed || rejected || (code !== null && code !== 0) || Boolean(signal);
|
|
163
|
+
const limit = failedRun ? limitLine(st.lastError) ?? limitLine(out) ?? (rejected ? words || null : null) : null;
|
|
164
|
+
if (limit) { st.status = 'limited'; st.error = limit; }
|
|
165
|
+
else if (failedRun) { st.status = 'stopped'; st.error = words || (signal ? `${label(st.cli)} stopped (${signal})` : `${label(st.cli)} exited with code ${code}`); }
|
|
166
|
+
else st.status = 'done';
|
|
167
|
+
st.result = (answer ?? (st.cli === 'claude' ? String(out).trim() : '')) || st.result || null;
|
|
168
|
+
void (async () => {
|
|
169
|
+
if (st.cli === 'codex' && st.native) {
|
|
170
|
+
const brain = await threadBrain(st.native);
|
|
171
|
+
st.model = brain.model ?? st.model; st.effort = brain.effort ?? st.effort;
|
|
172
|
+
}
|
|
173
|
+
if (st.status !== 'limited') {
|
|
174
|
+
st.question = (!failedRun ? questionOf(st.result) : null) ?? st.question ?? (st.cli === 'codex' ? await codexThreadQuestion(st.native, st.startedAt) : null);
|
|
175
|
+
if (st.question) st.status = 'asking';
|
|
176
|
+
}
|
|
177
|
+
if (st.stopped) { st.wake?.(); return; }
|
|
178
|
+
if (st.status === 'limited') st.resetsAt = await resetAt(st.cli, { threadId: st.native ?? undefined });
|
|
179
|
+
if (closed || st.stopped) return;
|
|
180
|
+
remember(st);
|
|
181
|
+
emit({ id: st.id, status: st.status === 'asking' ? 'asked' : st.status === 'done' ? 'done' : 'stopped', limited: st.status === 'limited',
|
|
182
|
+
...(st.status === 'asking' ? { now: st.question.question } : st.status === 'done' ? {} : { now: st.error }), ...(st.result ? { result: st.result } : {}),
|
|
183
|
+
...(st.model ? { model: st.model } : {}), ...(st.effort ? { effort: st.effort } : {}) });
|
|
184
|
+
})().finally(() => { st.active = false; st.finishing = false; st.wake?.(); });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function run(st, prompt) {
|
|
188
|
+
st.active = true; st.stopped = false; st.question = null; st.startedAt = Date.now();
|
|
189
|
+
st.status = 'running'; st.lastError = null; st.error = null; st.resetsAt = null; st.result = null;
|
|
190
|
+
const resume = Boolean(st.native);
|
|
191
|
+
emit({ id: st.id, status: 'running', limited: false, now: resume ? 'Continuing with the lead’s message' : st.title });
|
|
192
|
+
let release = admit.reserve(), seen;
|
|
193
|
+
st.release = () => { release?.(); release = null; };
|
|
194
|
+
const identified = new Promise(r => { seen = r; });
|
|
195
|
+
const flags = { failed: false, rejected: false, code: null, signal: null, answer: null };
|
|
196
|
+
st.done = new Promise(r => { st.wake = r; });
|
|
197
|
+
let turn;
|
|
198
|
+
try {
|
|
199
|
+
turn = Promise.resolve(launch({ runtime: st.cli, prompt, recordId: st.id, model: st.brain.model, effort: st.brain.effort,
|
|
200
|
+
...(resume ? { resumeThreadId: st.native } : {}),
|
|
201
|
+
onProcess: ch => { st.proc = ch; st.release(); if (closed || st.stopped) interruptThenKill(ch); },
|
|
202
|
+
onIdentity: native => {
|
|
203
|
+
if (!(st.cli === 'codex' ? CODEX_THREAD_RE : CLAUDE_SESSION_RE).test(native ?? '')) return;
|
|
204
|
+
if (st.native && st.native !== native) {
|
|
205
|
+
flags.failed = true; st.lastError = `${label(st.cli)} could not resume native session ${st.native}; it opened a different conversation. Start a new child explicitly.`;
|
|
206
|
+
if (st.proc) interruptThenKill(st.proc);
|
|
207
|
+
seen(); return;
|
|
208
|
+
}
|
|
209
|
+
if (!st.native) { st.native = native; st.key = subagentKey(st.cli, st.cli === 'codex' ? { id: native } : { nativeId: native }); if (st.key) emit({ id: st.id, key: st.key }); }
|
|
210
|
+
remember(st);
|
|
211
|
+
seen();
|
|
212
|
+
},
|
|
213
|
+
onQuestion: q => { st.question = questionOf(q); },
|
|
214
|
+
onModel: m => { if (typeof m === 'string' && TURN_MODEL_RE.test(m)) { st.model = m; remember(st); emit({ id: st.id, model: m }); } },
|
|
215
|
+
onAnswer: t => { if (typeof t === 'string' && t.trim()) flags.answer = t; },
|
|
216
|
+
onError: w => { if (filled(w)) st.lastError = w.trim().slice(0, 600); },
|
|
217
|
+
onFile: p => { if (filled(p) && st.files.size < 200) st.files.add(p); },
|
|
218
|
+
onRateLimit: info => { if (info?.status === 'rejected') flags.rejected = true; },
|
|
219
|
+
onCliFailure: () => { flags.failed = true; },
|
|
220
|
+
onExit: (code, signal) => { flags.code = code; flags.signal = signal; },
|
|
221
|
+
}));
|
|
222
|
+
} catch (error) { turn = Promise.reject(error); }
|
|
223
|
+
turn.then(out => settle(st, { out, ...flags }), error => settle(st, { error, ...flags }))
|
|
224
|
+
.finally(() => { release?.(); release = null; seen(); });
|
|
225
|
+
await Promise.race([identified, st.done, sleep(startWaitMs, ended)]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Admitted now, or held at the child until the pool has a slot. */
|
|
229
|
+
async function admitThen(st, prompt) {
|
|
230
|
+
if (admit('churn')) { st.status = 'waiting'; st.pending = prompt; emit({ id: st.id, status: 'waiting', now: WAIT }); return; }
|
|
231
|
+
st.pending = null;
|
|
232
|
+
await run(st, prompt);
|
|
233
|
+
}
|
|
234
|
+
const timer = setInterval(() => {
|
|
235
|
+
if (closed) return;
|
|
236
|
+
for (const st of kids.values()) {
|
|
237
|
+
if (st.status !== 'waiting' || !st.pending || admit('churn')) continue;
|
|
238
|
+
const prompt = st.pending; st.pending = null;
|
|
239
|
+
void run(st, prompt).then(() => announce(`Subagent ${st.id} on ${label(st.cli)} has a slot and is working now; read it with cli_agent_result.`));
|
|
240
|
+
}
|
|
241
|
+
}, interval);
|
|
242
|
+
timer.unref?.();
|
|
243
|
+
|
|
244
|
+
const kidOf = id => {
|
|
245
|
+
const st = kids.get(String(id ?? '')) ?? [...kids.values()].find(k => k.key === id || k.originalId === id);
|
|
246
|
+
if (st) return st;
|
|
247
|
+
const kept = ledger?.cliChild(id, wt);
|
|
248
|
+
if (!kept) throw new Refusal('That names no subagent this run started with start_cli_agent or remembered in this worktree.');
|
|
249
|
+
if (kept.cli === lead) throw new Refusal('That child now runs on the lead’s CLI; use its native follow-up tool.');
|
|
250
|
+
if (!ready().includes(kept.cli)) throw new Refusal(`${label(kept.cli)} is not ready on this machine.`);
|
|
251
|
+
if (count() >= SUBAGENT_CAP) throw new Refusal(`This run already has ${SUBAGENT_CAP} agent records.`);
|
|
252
|
+
// A later run uses the stable key as its record id: short ids can repeat.
|
|
253
|
+
const restored = { ...kept, originalId: kept.id, id: kept.key, brain: brainFor(kept.cli, kept), files: new Set(), status: 'done' };
|
|
254
|
+
kids.set(restored.id, restored);
|
|
255
|
+
emit({ id: restored.id, key: kept.key, cli: kept.cli, parent: 'lead', title: kept.title, kind: kept.kind, status: 'done' });
|
|
256
|
+
return restored;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
async function start(args) {
|
|
260
|
+
const cli = args.cli;
|
|
261
|
+
if (!SUBAGENT_CLIS.includes(cli)) throw new Refusal(`Name the CLI: ${SUBAGENT_CLIS.join(' or ')}.`);
|
|
262
|
+
if (cli === lead) throw new Refusal(`This card's lead runs on ${label(lead)}: start a ${label(lead)} subagent with your native tool. start_cli_agent is for another CLI.`);
|
|
263
|
+
if (!ready().includes(cli)) throw new Refusal(`${label(cli)} is not ready on this machine (not installed, or not one this daemon can drive), so it cannot run a subagent. Do this part yourself.`);
|
|
264
|
+
const held = choice(cli); if (held) throw new Refusal(held);
|
|
265
|
+
for (const [k, cap] of [['title', SUBAGENT_FIELD_CAPS.title], ['kind', SUBAGENT_FIELD_CAPS.kind], ['brief', SUBAGENT_FIELD_CAPS.brief]])
|
|
266
|
+
if (!filled(args[k]) || args[k].length > cap) throw new Refusal(`Give the subagent a ${k} of at most ${cap} characters.`);
|
|
267
|
+
const brain = brainFor(cli, args);
|
|
268
|
+
if (count() >= SUBAGENT_CAP) throw new Refusal(`This run already has ${SUBAGENT_CAP} agent records. Continue the work yourself.`);
|
|
269
|
+
let id; do { id = `${cli}-${++n}`; } while (kids.has(id) || ledger?.cliChild(id, wt));
|
|
270
|
+
const st = { id, cli, title: args.title.trim(), kind: args.kind.trim(), brief: args.brief, brain, files: new Set(), status: 'waiting', native: null, key: null };
|
|
271
|
+
kids.set(st.id, st);
|
|
272
|
+
emit({ id: st.id, kind: st.kind, title: st.title, brief: st.brief, cli, parent: 'lead', status: 'waiting', now: WAIT });
|
|
273
|
+
await admitThen(st, args.brief);
|
|
274
|
+
return view(st);
|
|
275
|
+
}
|
|
276
|
+
async function followup(args) {
|
|
277
|
+
const st = kidOf(args.agentId);
|
|
278
|
+
if (st.status === 'running' || st.status === 'waiting' || st.active) throw new Refusal('It is still working: read cli_agent_result first, then follow up.');
|
|
279
|
+
if (!st.native) throw new Refusal(`It never started on ${label(st.cli)}, so there is nothing to continue: start a new one.`);
|
|
280
|
+
if (!filled(args.message) || args.message.length > SUBAGENT_FIELD_CAPS.brief) throw new Refusal(`Give the follow-up as text of at most ${SUBAGENT_FIELD_CAPS.brief} characters.`);
|
|
281
|
+
const held = choice(st.cli); if (held) throw new Refusal(held);
|
|
282
|
+
await admitThen(st, args.message);
|
|
283
|
+
if (st.status === 'stopped' && /(?:could not resume native session|no rollout found|session (?:not found|does not exist)|no conversation found|failed to (?:load|find).*session|cannot find.*session)/i.test(st.error ?? ''))
|
|
284
|
+
throw new Refusal(`${label(st.cli)} cannot resume that child: ${st.error}`);
|
|
285
|
+
return view(st);
|
|
286
|
+
}
|
|
287
|
+
async function stop(args) {
|
|
288
|
+
const st = kidOf(args.agentId);
|
|
289
|
+
st.stopped = true; st.question = null; st.pending = null; st.status = 'stopped';
|
|
290
|
+
st.release?.();
|
|
291
|
+
if (st.proc) interruptThenKill(st.proc);
|
|
292
|
+
emit({ id: st.id, status: 'stopped', now: 'Stopped by the lead' });
|
|
293
|
+
st.wake?.();
|
|
294
|
+
return { agentId: st.id, status: 'stopped' };
|
|
295
|
+
}
|
|
296
|
+
async function result(args) {
|
|
297
|
+
const st = kidOf(args.agentId);
|
|
298
|
+
const wait = Math.max(0, Math.min(Number.isFinite(Number(args.waitSeconds)) ? Number(args.waitSeconds) : 60, CROSS_CLI_RESULT_WAIT_MAX_S));
|
|
299
|
+
if ((st.status === 'running' || st.status === 'waiting' || st.finishing) && wait > 0) {
|
|
300
|
+
const deadline = Date.now() + wait * 1000;
|
|
301
|
+
// A waiting child has no turn yet: poll until it starts, then wait on it.
|
|
302
|
+
while ((st.status === 'running' || st.status === 'waiting' || st.finishing) && Date.now() < deadline && !closed)
|
|
303
|
+
await Promise.race([st.status === 'running' || st.finishing ? st.done : sleep(interval), sleep(deadline - Date.now(), ended)]);
|
|
304
|
+
}
|
|
305
|
+
return view(st);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
/** The CLIs a subagent may start on here, for the contract and the client. */
|
|
310
|
+
offered,
|
|
311
|
+
asking: id => kids.get(id)?.status === 'asking',
|
|
312
|
+
async handle(action, args = {}) {
|
|
313
|
+
if (closed) throw new Refusal('This run stopped.');
|
|
314
|
+
if (action === 'start_cli_agent') return start(args);
|
|
315
|
+
if (action === 'followup_cli_agent') return followup(args);
|
|
316
|
+
if (action === 'stop_cli_agent') return stop(args);
|
|
317
|
+
if (action === 'cli_agent_result') return result(args);
|
|
318
|
+
throw new Refusal('Unknown orchestration action.');
|
|
319
|
+
},
|
|
320
|
+
/** The run closed or was stopped: every child's process group goes. */
|
|
321
|
+
close() {
|
|
322
|
+
if (closed) return;
|
|
323
|
+
closed = true; clearInterval(timer); wake();
|
|
324
|
+
for (const st of kids.values()) { st.release?.(); if (st.proc) interruptThenKill(st.proc); }
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ONE TURN OF A SUBAGENT ON ANOTHER CLI (2026-11-06, D21, E7 S7.2), as the
|
|
3
|
+
* agent lane starts it: runTurn with the card's posture for the
|
|
4
|
+
* CHILD's CLI (crossCliPosture.mjs), the card's worktree as cwd, remeasured
|
|
5
|
+
* before every spawn as the lead's own are, and the machine's own login for
|
|
6
|
+
* that CLI. The child mounts no MCP server (`--strict-mcp-config` on Claude)
|
|
7
|
+
* and holds no orchestrator capability: the daemon knows who it is because
|
|
8
|
+
* it started it. Its steps stream onto its record in the lead's trace; its
|
|
9
|
+
* process joins the machine's live children while it runs, so the pool
|
|
10
|
+
* counts it and the daemon's teardown reaches its group.
|
|
11
|
+
*/
|
|
12
|
+
import { relative, isAbsolute } from 'node:path';
|
|
13
|
+
import { runTurn as defaultRunTurn } from './runTurn.mjs';
|
|
14
|
+
import { runtimeById } from './runtimes.mjs';
|
|
15
|
+
import { toolEventOf, CLAUDE_TOOL_PROSE_KINDS, scrubNativeText } from './runtimeEvents.mjs';
|
|
16
|
+
import { detectRuntimes } from './runtimeDetection.mjs';
|
|
17
|
+
import { knownCodexModels } from './runtimeOfferings.mjs';
|
|
18
|
+
import { crossCliResearchView } from './crossCliResearch.mjs';
|
|
19
|
+
import { isPersonQuestionTool } from './nativeAgentTools.mjs';
|
|
20
|
+
import { assertWorktree } from './worktreeHealth.mjs';
|
|
21
|
+
|
|
22
|
+
const WRITE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
|
|
23
|
+
const LABEL = { claude: 'Claude Code', codex: 'Codex' };
|
|
24
|
+
|
|
25
|
+
/** What a child on another CLI is told about its place: the lead's contract
|
|
26
|
+
* for children, minus the card_orchestrator tools it does not mount. */
|
|
27
|
+
export const crossCliChildSystem = lead => `You are a subagent of this card's lead, which runs on ${LABEL[lead] ?? lead}. Do only the part you are given, in this worktree, which you share with the lead and perhaps other subagents: preserve others' edits and touch only the files your part needs. Do not commit, push, or change branches: the lead owns the card's commit. You cannot start subagents of your own, and nobody can answer you mid-task: if something blocks you, end with your question instead of guessing. Return a JSON final message {"status":"asking","question":"what you need","options":["option one","option two"]}. The lead answers through a follow-up or asks the person; never ask the person directly. End with your result as your final message — what you changed, file by file, or what you found, and anything you could not do. That message is how your work reaches the lead.`;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* `wt` the card's worktree and `verify()` its remeasure (assertWorktree);
|
|
31
|
+
* `trace` the lead's relay; `scrub` the lane's env scrubber; `onChild(ch)`
|
|
32
|
+
* registers a live child and returns its release.
|
|
33
|
+
*/
|
|
34
|
+
export function crossCliLauncher({ wt, verify = () => {}, trace, scrub = s => s, onChild = () => () => {}, lead, posture = 'build', researchView = crossCliResearchView, runTurn = defaultRunTurn }) {
|
|
35
|
+
const rel = p => { const r = isAbsolute(p) ? relative(wt, p) : p; return r && !r.startsWith('..') ? r : p; };
|
|
36
|
+
return function launch(spec) {
|
|
37
|
+
verify();
|
|
38
|
+
const id = spec.recordId;
|
|
39
|
+
// Claude's stream says each tool twice, as prose and as an event (CLAUDE_TOOL_PROSE_KINDS).
|
|
40
|
+
const doubled = runtimeById(spec.runtime)?.parse ? null : CLAUDE_TOOL_PROSE_KINDS;
|
|
41
|
+
return (async () => {
|
|
42
|
+
const view = posture === 'research' && spec.runtime === 'codex' ? await researchView(wt) : null;
|
|
43
|
+
try { return await runTurn({
|
|
44
|
+
runtime: spec.runtime, prompt: spec.prompt, system: crossCliChildSystem(lead) + (posture === 'research' ? ' This is a research card: write no file, edit nothing, and return findings and sources to the lead.' : ''),
|
|
45
|
+
cwd: view?.cwd ?? wt, ...(view ? { researchIsolation: view.researchIsolation } : {}), verifiedWorktree: true, profile: posture, cardTurn: true, crossCliChild: true,
|
|
46
|
+
...(spec.model ? { model: spec.model } : {}), ...(spec.effort ? { effort: spec.effort } : {}),
|
|
47
|
+
...(spec.resumeThreadId ? { resumeThreadId: spec.resumeThreadId } : {}),
|
|
48
|
+
...(spec.runtime === 'claude' ? { mcpArgs: ['--strict-mcp-config'] } : {}),
|
|
49
|
+
answerFromResult: true,
|
|
50
|
+
onSpawn: ch => { const done = onChild(ch); ch.once('close', () => done?.()); spec.onProcess?.(ch); },
|
|
51
|
+
onInit: i => spec.onIdentity?.(i?.sessionId),
|
|
52
|
+
onThreadId: t => spec.onIdentity?.(t),
|
|
53
|
+
onModel: spec.onModel, onAnswer: spec.onAnswer, onRateLimit: spec.onRateLimit,
|
|
54
|
+
onCliFailure: spec.onCliFailure, onExit: spec.onExit,
|
|
55
|
+
onActivity: a => {
|
|
56
|
+
if (!a || a.agentId) return;
|
|
57
|
+
if (a.kind === 'write' && a.path) spec.onFile?.(rel(a.path));
|
|
58
|
+
if (a.kind === 'error') spec.onError?.(scrubNativeText(a.full ?? a.label, 600, scrub));
|
|
59
|
+
if (doubled?.has(a.kind)) return;
|
|
60
|
+
const body = scrubNativeText(a.full ?? a.label, 4_000, scrub);
|
|
61
|
+
trace.agent({ id, now: body });
|
|
62
|
+
if (a.kind === 'doing') trace.doing(id, body); else trace.prose(a.kind, body, id);
|
|
63
|
+
},
|
|
64
|
+
onToolEvent: (name, input, meta) => {
|
|
65
|
+
if (meta?.agentId || meta?.result || !name) return;
|
|
66
|
+
if (isPersonQuestionTool(name)) spec.onQuestion?.(input);
|
|
67
|
+
if (WRITE_TOOLS.has(name) && typeof input?.file_path === 'string') spec.onFile?.(rel(input.file_path));
|
|
68
|
+
const event = toolEventOf(name, input, wt, scrub);
|
|
69
|
+
trace.tool(event, id);
|
|
70
|
+
const now = event?.c ? `$ ${event.c}` : event?.p ?? event?.q;
|
|
71
|
+
if (now) trace.agent({ id, now });
|
|
72
|
+
},
|
|
73
|
+
}); } finally { await view?.close(); }
|
|
74
|
+
})();
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* What the agent lane hands its controller (orchestrator.mjs `crossCli`): a
|
|
80
|
+
* code or research card's launcher, the CLIs this machine measured drivable (the same
|
|
81
|
+
* report the roster sends), and the measured Codex model list. Any other
|
|
82
|
+
* posture starts none.
|
|
83
|
+
*/
|
|
84
|
+
export function crossCliFor({ rt, posture, wt, worktreeRepo, trace, scrub, onChild, ledger }) {
|
|
85
|
+
if (!['build', 'research'].includes(posture)) return null;
|
|
86
|
+
return {
|
|
87
|
+
wt, ledger, access: posture === 'research' ? 'read' : 'edit',
|
|
88
|
+
launch: crossCliLauncher({ wt, verify: () => assertWorktree(wt, worktreeRepo), trace, scrub, onChild, lead: rt, posture }),
|
|
89
|
+
ready: () => { try { return detectRuntimes().filter(r => r.dispatchable).map(r => r.id); } catch { return []; } },
|
|
90
|
+
catalog: () => knownCodexModels(),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE POSTURE A SUBAGENT ON ANOTHER CLI RUNS UNDER (2026-11-06, D21, E7
|
|
3
|
+
* S7.2). The daemon starts and fences every such child itself, with the
|
|
4
|
+
* card's own posture for the CHILD's CLI: spike S7.1 measured that a Claude
|
|
5
|
+
* child started from a Codex lead's MCP server escaped both its network and
|
|
6
|
+
* its worktree, because Codex runs MCP servers outside its own sandbox. So
|
|
7
|
+
* the lead's sandbox never carries over, and nothing here reads it.
|
|
8
|
+
*
|
|
9
|
+
* Code and research cards may start one. The build posture, per CLI:
|
|
10
|
+
* · CODEX: the build's own sandbox (danger-full-access, or workspace-write
|
|
11
|
+
* under FLOWVIANT_SAFE=1), `approval_policy="never"`, and no native
|
|
12
|
+
* children of its own (`noSubagents`, no nesting across CLIs). A fenced
|
|
13
|
+
* (SAFE) build also excludes /tmp and $TMPDIR from the writable roots,
|
|
14
|
+
* which plain workspace-write otherwise grants (measured, codex-cli
|
|
15
|
+
* 0.166.0: with these two keys a write elsewhere in /tmp failed
|
|
16
|
+
* "Read-only file system").
|
|
17
|
+
* · CLAUDE: the build's list (`--dangerously-skip-permissions`, or the SAFE
|
|
18
|
+
* curated list with its Edit and Write scoped to the worktree's absolute
|
|
19
|
+
* path — measured, a bare `--allowedTools Write` wrote outside it), no
|
|
20
|
+
* Agent or Task (no nesting), `--setting-sources user` so the agent-
|
|
21
|
+
* editable worktree's settings and hooks never load, and
|
|
22
|
+
* `--strict-mcp-config` (the lane passes it).
|
|
23
|
+
* Research uses the child CLI’s research list/fence with every write grant
|
|
24
|
+
* removed. A leaf: the two adapters import it, and it imports nothing of theirs.
|
|
25
|
+
*/
|
|
26
|
+
import { NATIVE_AGENT_TOOLS } from './nativeAgentTools.mjs';
|
|
27
|
+
|
|
28
|
+
/** Codex workspace-write's /tmp and $TMPDIR grants, closed. */
|
|
29
|
+
export const CROSS_CLI_TMP_FENCE = Object.freeze(['-c', 'sandbox_workspace_write.exclude_slash_tmp=true',
|
|
30
|
+
'-c', 'sandbox_workspace_write.exclude_tmpdir_env_var=true']);
|
|
31
|
+
|
|
32
|
+
/** Claude Code's spawn tools, which a child on another CLI never gets. */
|
|
33
|
+
export const CROSS_CLI_NO_CHILDREN = NATIVE_AGENT_TOOLS.claude.spawn;
|
|
34
|
+
|
|
35
|
+
/** A path a permission rule can name (claudePosture.mjs's rule, repeated
|
|
36
|
+
* here so this leaf imports no adapter): absolute, no glob or rule syntax. */
|
|
37
|
+
const ruleSafe = (p) => typeof p === 'string' && p.startsWith('/') && !/[*?[\]{}()\\\n]/.test(p) ? p.replace(/\/+$/, '') : null;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Claude's build list for a child on another CLI's card: `perm` is
|
|
41
|
+
* `claudePermFor('build')`, `cwd` the card's worktree. A curated list whose
|
|
42
|
+
* worktree no rule can name is refused in words, never run unscoped.
|
|
43
|
+
*/
|
|
44
|
+
export function crossCliClaudePerm(perm, cwd, profile = 'build') {
|
|
45
|
+
const list = profile === 'research' ? perm.filter(p => !/^(?:Edit|Write|MultiEdit|NotebookEdit)(?:\(|$)/.test(p)) : [...perm];
|
|
46
|
+
if (profile === 'build' && !list.includes('--dangerously-skip-permissions')) {
|
|
47
|
+
const wt = ruleSafe(cwd);
|
|
48
|
+
if (!wt) throw new Error(`a subagent's writes must be scoped to its worktree, and ${JSON.stringify(cwd)} is not a path a Claude Code rule can name`);
|
|
49
|
+
for (let i = 0; i < list.length; i++) if (list[i] === 'Edit' || list[i] === 'Write') list[i] = `${list[i]}(/${wt}/**)`;
|
|
50
|
+
}
|
|
51
|
+
const denied = [...CROSS_CLI_NO_CHILDREN, ...(profile === 'research' ? ['Edit', 'Write', 'MultiEdit', 'NotebookEdit'] : [])];
|
|
52
|
+
const at = list.indexOf('--disallowedTools');
|
|
53
|
+
if (at >= 0) { list.splice(at + 1, 0, ...denied); return list; }
|
|
54
|
+
return [...list, '--disallowedTools', ...denied];
|
|
55
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Research children read a fresh, secret-free snapshot and publish no files.
|
|
2
|
+
* Each child gets its own copy: concurrent readers never remove each other's
|
|
3
|
+
* input view. Native session ids still resume the same conversation. */
|
|
4
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { copyResearchView, codexResearchHelper } from './codexResearch.mjs';
|
|
8
|
+
|
|
9
|
+
export async function crossCliResearchView(wt, { helper = codexResearchHelper() } = {}) {
|
|
10
|
+
const dir = await mkdtemp(join(tmpdir(), 'flowviant-child-research-'));
|
|
11
|
+
try {
|
|
12
|
+
const cwd = join(dir, 'repo');
|
|
13
|
+
copyResearchView(wt, cwd);
|
|
14
|
+
return { cwd, researchIsolation: { cwd, helper }, close: () => rm(dir, { recursive: true, force: true }) };
|
|
15
|
+
} catch (error) {
|
|
16
|
+
await rm(dir, { recursive: true, force: true });
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Bounded, tracked setup children. Secrets travel in env/stdin, never argv. */
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { killGroup } from './processGroup.mjs';
|
|
4
|
+
import { unattendedEnv } from './noWindowEnv.mjs';
|
|
5
|
+
import { workLanesStopped } from './standDownGate.mjs';
|
|
6
|
+
import { scrub } from './uplinkScrub.mjs';
|
|
7
|
+
|
|
8
|
+
export function environmentCommand(argv, { cwd, env, input = '', signal, onSpawn = () => {}, onClose = () => {}, timeoutMs = 600_000, label = 'Environment command', captureOutput = true, includeDiagnostics = false } = {}) {
|
|
9
|
+
signal?.throwIfAborted();
|
|
10
|
+
if (workLanesStopped()) throw new Error('The daemon is stopping. Environment setup has not started.');
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
let child, timer, settled = false, output = '', diagnostics = '';
|
|
13
|
+
const diagnostic = chunk => { diagnostics = scrub(diagnostics + chunk.toString()).slice(-2000); };
|
|
14
|
+
const failed = message => new Error(message + (includeDiagnostics && diagnostics ? '\n' + diagnostics : ''));
|
|
15
|
+
const abort = () => { killGroup(child); finish(signal?.reason ?? new Error(`${label} was interrupted.`)); };
|
|
16
|
+
const finish = (error) => {
|
|
17
|
+
if (settled) return;
|
|
18
|
+
settled = true; clearTimeout(timer);
|
|
19
|
+
signal?.removeEventListener('abort', abort);
|
|
20
|
+
if (child) onClose(child);
|
|
21
|
+
error ? reject(error) : resolve(output);
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
child = spawn(argv[0], argv.slice(1), { cwd, env: unattendedEnv({ ...env, GIT_TERMINAL_PROMPT: '0' }), detached: true, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
25
|
+
child.once('error', () => finish(new Error(`${label} could not start (${argv[0]}). Check that it is installed.`)));
|
|
26
|
+
child.once('close', code => finish(code === 0 ? null : failed(`${label} failed (exit ${code ?? 'signal'}).`)));
|
|
27
|
+
// Provider operations leave diagnostics off: output can contain newly
|
|
28
|
+
// minted credentials even on failure. Runtime hooks retain scrubbed tails.
|
|
29
|
+
if (includeDiagnostics) child.stderr.on('data', diagnostic);
|
|
30
|
+
else child.stderr.resume();
|
|
31
|
+
child.stdout.on('data', chunk => {
|
|
32
|
+
if (!captureOutput) { if (includeDiagnostics) diagnostic(chunk); return; }
|
|
33
|
+
output += chunk;
|
|
34
|
+
if (Buffer.byteLength(output) > 256 * 1024) { killGroup(child); finish(new Error(`${label} returned too much output.`)); }
|
|
35
|
+
});
|
|
36
|
+
child.stdin.on('error', () => {});
|
|
37
|
+
child.stdin.end(input);
|
|
38
|
+
onSpawn(child);
|
|
39
|
+
timer = setTimeout(() => { killGroup(child); finish(failed(`${label} timed out.`)); }, timeoutMs);
|
|
40
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
41
|
+
if (signal?.aborted) abort();
|
|
42
|
+
} catch (error) { if (child) killGroup(child); finish(error); }
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{"type":"assistant","message":{"content":[{"type":"tool_use","id":"toolu_014a7i5Zk7x6ve94RgGruzs5","name":"Workflow","input":{"description":"Summarize files"}}]}}
|
|
2
|
+
{"type":"system","subtype":"background_tasks_changed","tasks":[{"task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","task_type":"local_workflow","description":"Summarize 4 txt files, one agent each"}]}
|
|
3
|
+
{"type":"system","subtype":"task_started","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","description":"Summarize 4 txt files, one agent each","task_type":"local_workflow","workflow_name":"summarize-txt"}
|
|
4
|
+
{"type":"system","subtype":"task_progress","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","description":"Summarize: apples","usage":{"total_tokens":0,"tool_uses":0,"duration_ms":29},"last_tool_name":"apples","summary":"Summarize 4 txt files, one agent each","workflow_progress":[{"type":"workflow_phase","index":1,"title":"Summarize"},{"type":"workflow_agent","index":1,"label":"apples","phaseIndex":1,"phaseTitle":"Summarize","agentId":"a5de451e94efbaa96","model":"claude-sonnet-5-5","state":"start","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"promptPreview":"Read apples.txt in the current directory and write a one-line summary into SUMMARY-apples.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992643526},{"type":"workflow_agent","index":2,"label":"bread","phaseIndex":1,"phaseTitle":"Summarize","model":"claude-sonnet-5-5","state":"start","queuedAt":1793992643523,"promptPreview":"Read bread.txt in the current directory and write a one-line summary into SUMMARY-bread.txt (single line, newline-terminated). Do nothing else.","lastProgressAt":1793992643523},{"type":"workflow_agent","index":3,"label":"lighthouse","phaseIndex":1,"phaseTitle":"Summarize","model":"claude-sonnet-5-5","state":"start","queuedAt":1793992643523,"promptPreview":"Read lighthouse.txt in the current directory and write a one-line summary into SUMMARY-lighthouse.txt (single line, newline-terminated). Do nothing else.","lastProgressAt":1793992643523},{"type":"workflow_agent","index":4,"label":"rivers","phaseIndex":1,"phaseTitle":"Summarize","model":"claude-sonnet-5-5","state":"start","queuedAt":1793992643523,"promptPreview":"Read rivers.txt in the current directory and write a one-line summary into SUMMARY-rivers.txt (single line, newline-terminated). Do nothing else.","lastProgressAt":1793992643523}]}
|
|
5
|
+
{"type":"system","subtype":"task_progress","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","description":"Summarize: rivers","usage":{"total_tokens":0,"tool_uses":0,"duration_ms":2315},"last_tool_name":"rivers","summary":"Summarize 4 txt files, one agent each","workflow_progress":[{"type":"workflow_phase","index":1,"title":"Summarize"},{"type":"workflow_agent","index":1,"label":"apples","phaseIndex":1,"phaseTitle":"Summarize","agentId":"a5de451e94efbaa96","model":"claude-sonnet-5-5","state":"start","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"promptPreview":"Read apples.txt in the current directory and write a one-line summary into SUMMARY-apples.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992643526},{"type":"workflow_agent","index":2,"label":"bread","phaseIndex":1,"phaseTitle":"Summarize","agentId":"ad1b6b5bd1a647735","model":"claude-sonnet-5-5","state":"start","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"promptPreview":"Read bread.txt in the current directory and write a one-line summary into SUMMARY-bread.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992645792},{"type":"workflow_agent","index":3,"label":"lighthouse","phaseIndex":1,"phaseTitle":"Summarize","agentId":"a9fddb0976cb132a1","model":"claude-sonnet-5-5","state":"start","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"promptPreview":"Read lighthouse.txt in the current directory and write a one-line summary into SUMMARY-lighthouse.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992645792},{"type":"workflow_agent","index":4,"label":"rivers","phaseIndex":1,"phaseTitle":"Summarize","agentId":"adb125546e346c21a","model":"claude-sonnet-5-5","state":"start","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"promptPreview":"Read rivers.txt in the current directory and write a one-line summary into SUMMARY-rivers.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992645793}]}
|
|
6
|
+
{"type":"system","subtype":"task_progress","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","description":"Summarize: apples","usage":{"total_tokens":15683,"tool_uses":1,"duration_ms":3350},"last_tool_name":"apples","summary":"Summarize 4 txt files, one agent each"}
|
|
7
|
+
{"type":"system","subtype":"task_progress","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","description":"Summarize: apples","usage":{"total_tokens":15848,"tool_uses":2,"duration_ms":5274},"last_tool_name":"apples","summary":"Summarize 4 txt files, one agent each"}
|
|
8
|
+
{"type":"system","subtype":"task_progress","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","description":"Summarize: rivers","usage":{"total_tokens":31538,"tool_uses":3,"duration_ms":6096},"last_tool_name":"rivers","summary":"Summarize 4 txt files, one agent each"}
|
|
9
|
+
{"type":"system","subtype":"task_progress","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","description":"Summarize: apples","usage":{"total_tokens":31951,"tool_uses":3,"duration_ms":8379},"last_tool_name":"apples","summary":"Summarize 4 txt files, one agent each","workflow_progress":[{"type":"workflow_phase","index":1,"title":"Summarize"},{"type":"workflow_agent","index":1,"label":"apples","phaseIndex":1,"phaseTitle":"Summarize","agentId":"a5de451e94efbaa96","model":"claude-sonnet-5-5","state":"done","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"lastToolName":"Write","lastToolSummary":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4\u2026","promptPreview":"Read apples.txt in the current directory and write a one-line summary into SUMMARY-apples.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992651884,"tokens":16261,"toolCalls":2,"durationMs":8358,"resultPreview":"I wrote the summary to `/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4d05-ead4-4e49-914d-b49e2ca0e6b5/scratchpad/spike-s43/repo/SUMMARY-apples.txt`"},{"type":"workflow_agent","index":2,"label":"bread","phaseIndex":1,"phaseTitle":"Summarize","agentId":"ad1b6b5bd1a647735","model":"claude-sonnet-5-5","state":"start","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"promptPreview":"Read bread.txt in the current directory and write a one-line summary into SUMMARY-bread.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992645792},{"type":"workflow_agent","index":3,"label":"lighthouse","phaseIndex":1,"phaseTitle":"Summarize","agentId":"a9fddb0976cb132a1","model":"claude-sonnet-5-5","state":"start","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"promptPreview":"Read lighthouse.txt in the current directory and write a one-line summary into SUMMARY-lighthouse.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992645792},{"type":"workflow_agent","index":4,"label":"rivers","phaseIndex":1,"phaseTitle":"Summarize","agentId":"adb125546e346c21a","model":"claude-sonnet-5-5","state":"progress","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"lastToolName":"Read","lastToolSummary":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4\u2026","promptPreview":"Read rivers.txt in the current directory and write a one-line summary into SUMMARY-rivers.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992649583,"tokens":15690,"toolCalls":1}]}
|
|
10
|
+
{"type":"system","subtype":"task_progress","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","description":"Summarize: bread","usage":{"total_tokens":47634,"tool_uses":4,"duration_ms":8945},"last_tool_name":"bread","summary":"Summarize 4 txt files, one agent each"}
|
|
11
|
+
{"type":"system","subtype":"task_progress","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","description":"Summarize: lighthouse","usage":{"total_tokens":63323,"tool_uses":5,"duration_ms":9612},"last_tool_name":"lighthouse","summary":"Summarize 4 txt files, one agent each"}
|
|
12
|
+
{"type":"system","subtype":"task_progress","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","description":"Summarize: bread","usage":{"total_tokens":63478,"tool_uses":6,"duration_ms":10754},"last_tool_name":"bread","summary":"Summarize 4 txt files, one agent each"}
|
|
13
|
+
{"type":"system","subtype":"task_progress","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","description":"Summarize: lighthouse","usage":{"total_tokens":63634,"tool_uses":7,"duration_ms":11185},"last_tool_name":"lighthouse","summary":"Summarize 4 txt files, one agent each"}
|
|
14
|
+
{"type":"system","subtype":"task_progress","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","description":"Summarize: lighthouse","usage":{"total_tokens":64004,"tool_uses":7,"duration_ms":12185},"last_tool_name":"lighthouse","summary":"Summarize 4 txt files, one agent each","workflow_progress":[{"type":"workflow_phase","index":1,"title":"Summarize"},{"type":"workflow_agent","index":1,"label":"apples","phaseIndex":1,"phaseTitle":"Summarize","agentId":"a5de451e94efbaa96","model":"claude-sonnet-5-5","state":"done","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"lastToolName":"Write","lastToolSummary":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4\u2026","promptPreview":"Read apples.txt in the current directory and write a one-line summary into SUMMARY-apples.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992651884,"tokens":16261,"toolCalls":2,"durationMs":8358,"resultPreview":"I wrote the summary to `/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4d05-ead4-4e49-914d-b49e2ca0e6b5/scratchpad/spike-s43/repo/SUMMARY-apples.txt`"},{"type":"workflow_agent","index":2,"label":"bread","phaseIndex":1,"phaseTitle":"Summarize","agentId":"ad1b6b5bd1a647735","model":"claude-sonnet-5-5","state":"progress","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"lastToolName":"Write","lastToolSummary":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4\u2026","promptPreview":"Read bread.txt in the current directory and write a one-line summary into SUMMARY-bread.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992654241,"tokens":15838,"toolCalls":2},{"type":"workflow_agent","index":3,"label":"lighthouse","phaseIndex":1,"phaseTitle":"Summarize","agentId":"a9fddb0976cb132a1","model":"claude-sonnet-5-5","state":"done","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"lastToolName":"Write","lastToolSummary":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4\u2026","promptPreview":"Read lighthouse.txt in the current directory and write a one-line summary into SUMMARY-lighthouse.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992655682,"tokens":16215,"toolCalls":2,"durationMs":9890,"resultPreview":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4d05-ead4-4e49-914d-b49e2ca0e6b5/scratchpad/spike-s43/repo/SUMMARY-lighthouse.txt"},{"type":"workflow_agent","index":4,"label":"rivers","phaseIndex":1,"phaseTitle":"Summarize","agentId":"adb125546e346c21a","model":"claude-sonnet-5-5","state":"progress","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"lastToolName":"Read","lastToolSummary":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4\u2026","promptPreview":"Read rivers.txt in the current directory and write a one-line summary into SUMMARY-rivers.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992649583,"tokens":15690,"toolCalls":1}]}
|
|
15
|
+
{"type":"system","subtype":"task_progress","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","description":"Summarize: rivers","usage":{"total_tokens":64152,"tool_uses":8,"duration_ms":12515},"last_tool_name":"rivers","summary":"Summarize 4 txt files, one agent each"}
|
|
16
|
+
{"type":"system","subtype":"task_progress","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","description":"Summarize: bread","usage":{"total_tokens":64584,"tool_uses":8,"duration_ms":13229},"last_tool_name":"bread","summary":"Summarize 4 txt files, one agent each","workflow_progress":[{"type":"workflow_phase","index":1,"title":"Summarize"},{"type":"workflow_agent","index":1,"label":"apples","phaseIndex":1,"phaseTitle":"Summarize","agentId":"a5de451e94efbaa96","model":"claude-sonnet-5-5","state":"done","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"lastToolName":"Write","lastToolSummary":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4\u2026","promptPreview":"Read apples.txt in the current directory and write a one-line summary into SUMMARY-apples.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992651884,"tokens":16261,"toolCalls":2,"durationMs":8358,"resultPreview":"I wrote the summary to `/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4d05-ead4-4e49-914d-b49e2ca0e6b5/scratchpad/spike-s43/repo/SUMMARY-apples.txt`"},{"type":"workflow_agent","index":2,"label":"bread","phaseIndex":1,"phaseTitle":"Summarize","agentId":"ad1b6b5bd1a647735","model":"claude-sonnet-5-5","state":"done","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"lastToolName":"Write","lastToolSummary":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4\u2026","promptPreview":"Read bread.txt in the current directory and write a one-line summary into SUMMARY-bread.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992656729,"tokens":16270,"toolCalls":2,"durationMs":10937,"resultPreview":"I read bread.txt and wrote a one-line, newline-terminated summary to SUMMARY-bread.txt: \"Bread is made by baking a dough of flour, water, yeast and salt.\"\n\nPath"},{"type":"workflow_agent","index":3,"label":"lighthouse","phaseIndex":1,"phaseTitle":"Summarize","agentId":"a9fddb0976cb132a1","model":"claude-sonnet-5-5","state":"done","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"lastToolName":"Write","lastToolSummary":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4\u2026","promptPreview":"Read lighthouse.txt in the current directory and write a one-line summary into SUMMARY-lighthouse.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992655682,"tokens":16215,"toolCalls":2,"durationMs":9890,"resultPreview":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4d05-ead4-4e49-914d-b49e2ca0e6b5/scratchpad/spike-s43/repo/SUMMARY-lighthouse.txt"},{"type":"workflow_agent","index":4,"label":"rivers","phaseIndex":1,"phaseTitle":"Summarize","agentId":"adb125546e346c21a","model":"claude-sonnet-5-5","state":"progress","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"lastToolName":"Write","lastToolSummary":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4\u2026","promptPreview":"Read rivers.txt in the current directory and write a one-line summary into SUMMARY-rivers.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992656001,"tokens":15838,"toolCalls":2}]}
|
|
17
|
+
{"type":"system","subtype":"task_progress","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","description":"Summarize: rivers","usage":{"total_tokens":65002,"tool_uses":8,"duration_ms":15959},"last_tool_name":"rivers","summary":"Summarize 4 txt files, one agent each","workflow_progress":[{"type":"workflow_phase","index":1,"title":"Summarize"},{"type":"workflow_agent","index":1,"label":"apples","phaseIndex":1,"phaseTitle":"Summarize","agentId":"a5de451e94efbaa96","model":"claude-sonnet-5-5","state":"done","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"lastToolName":"Write","lastToolSummary":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4\u2026","promptPreview":"Read apples.txt in the current directory and write a one-line summary into SUMMARY-apples.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992651884,"tokens":16261,"toolCalls":2,"durationMs":8358,"resultPreview":"I wrote the summary to `/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4d05-ead4-4e49-914d-b49e2ca0e6b5/scratchpad/spike-s43/repo/SUMMARY-apples.txt`"},{"type":"workflow_agent","index":2,"label":"bread","phaseIndex":1,"phaseTitle":"Summarize","agentId":"ad1b6b5bd1a647735","model":"claude-sonnet-5-5","state":"done","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"lastToolName":"Write","lastToolSummary":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4\u2026","promptPreview":"Read bread.txt in the current directory and write a one-line summary into SUMMARY-bread.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992656729,"tokens":16270,"toolCalls":2,"durationMs":10937,"resultPreview":"I read bread.txt and wrote a one-line, newline-terminated summary to SUMMARY-bread.txt: \"Bread is made by baking a dough of flour, water, yeast and salt.\"\n\nPath"},{"type":"workflow_agent","index":3,"label":"lighthouse","phaseIndex":1,"phaseTitle":"Summarize","agentId":"a9fddb0976cb132a1","model":"claude-sonnet-5-5","state":"done","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"lastToolName":"Write","lastToolSummary":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4\u2026","promptPreview":"Read lighthouse.txt in the current directory and write a one-line summary into SUMMARY-lighthouse.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992655682,"tokens":16215,"toolCalls":2,"durationMs":9890,"resultPreview":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4d05-ead4-4e49-914d-b49e2ca0e6b5/scratchpad/spike-s43/repo/SUMMARY-lighthouse.txt"},{"type":"workflow_agent","index":4,"label":"rivers","phaseIndex":1,"phaseTitle":"Summarize","agentId":"adb125546e346c21a","model":"claude-sonnet-5-5","state":"done","startedAt":1793992643526,"queuedAt":1793992643523,"attempt":1,"lastToolName":"Write","lastToolSummary":"/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4\u2026","promptPreview":"Read rivers.txt in the current directory and write a one-line summary into SUMMARY-rivers.txt (single line, newline-terminated). Do nothing else.","promptFramed":true,"lastProgressAt":1793992659463,"tokens":16256,"toolCalls":2,"durationMs":13670,"resultPreview":"I read `rivers.txt` and wrote a one-line summary to `/tmp/claude-1000/-home-wayleem-code-startup-flowviant/c44e4d05-ead4-4e49-914d-b49e2ca0e6b5/scratchpad/spike"}]}
|
|
18
|
+
{"type":"system","subtype":"task_updated","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","patch":{"status":"completed","end_time":1793992659464}}
|
|
19
|
+
{"type":"system","subtype":"task_notification","task_id":"wsj4ml1cp","run_id":"0mw5cwey9-553ca5b2","tool_use_id":"toolu_014a7i5Zk7x6ve94RgGruzs5","status":"completed","summary":"Dynamic workflow \"Summarize 4 txt files, one agent each\" completed","usage":{"total_tokens":65002,"tool_uses":8,"duration_ms":15952}}
|
|
20
|
+
{"type":"system","subtype":"background_tasks_changed","tasks":[]}
|
|
21
|
+
{"usage":{"input_tokens":8,"cache_creation_input_tokens":20078,"cache_read_input_tokens":65888,"output_tokens":396,"output_tokens_details":{"thinking_tokens":0},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":20078,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":39,"cache_read_input_tokens":30408,"cache_creation_input_tokens":1052,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":1052},"type":"message"}],"speed":"standard","fallback_credit":null},"is_error":false,"type":"result"}
|
|
22
|
+
{"usage":{"input_tokens":6,"cache_creation_input_tokens":1309,"cache_read_input_tokens":64012,"output_tokens":335,"output_tokens_details":{"thinking_tokens":0},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":1309,"ephemeral_5m_input_tokens":0},"inference_geo":"global","iterations":[{"input_tokens":2,"output_tokens":213,"cache_read_input_tokens":32552,"cache_creation_input_tokens":217,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":217},"type":"message"}],"speed":"standard","fallback_credit":null},"is_error":false,"type":"result"}
|