flowviant 0.125.20 → 0.125.21
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 +11 -1
- package/bin/cli.mjs +13 -0
- package/bin/lib/artifactContracts.mjs +87 -2
- package/bin/lib/artifactIteration.mjs +2 -1
- package/bin/lib/artifactReporter.mjs +2 -2
- package/bin/lib/artifacts.mjs +8 -1
- package/bin/lib/claudeStream.mjs +7 -0
- package/bin/lib/codexFencedMount.mjs +2 -2
- package/bin/lib/pinnedCapture.mjs +19 -8
- package/bin/lib/postureTools.mjs +2 -2
- package/bin/lib/publicReferenceBrowser.mjs +180 -0
- package/bin/lib/publicReferenceNetwork.mjs +131 -0
- package/bin/lib/publicReferenceProtocol.mjs +55 -0
- package/bin/lib/publicReferenceRuntime.mjs +65 -0
- package/bin/lib/publicReferenceTool.mjs +89 -0
- package/bin/lib/referenceCaptureProtocol.mjs +12 -0
- package/bin/lib/referenceCaptures.mjs +140 -0
- package/bin/lib/runTurn.mjs +8 -0
- package/bin/lib/runtimeEvents.mjs +24 -2
- package/bin/lib/uiReview.mjs +305 -0
- package/bin/lib/uiReviewBrowser.mjs +212 -0
- package/bin/lib/uiReviewContract.mjs +51 -0
- package/bin/lib/uiReviewTurn.mjs +15 -0
- package/bin/lib/websiteAccess.mjs +68 -0
- package/bin/lib/websiteTrace.mjs +36 -0
- package/bin/lib/work.mjs +1 -0
- package/bin/lib/workAgentTools.mjs +3 -2
- package/bin/lib/workAgentTurnExecution.mjs +43 -2
- package/bin/lib/workAgentTurns.mjs +2 -0
- package/bin/lib/workToolLog.mjs +24 -3
- package/package.json +1 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Bounded website metadata only. Neither assistant prose nor page bodies are
|
|
2
|
+
* evidence of a loaded page. Browser integrations supply explicit observations
|
|
3
|
+
* through result metadata, never by embedding claims in a tool input. */
|
|
4
|
+
import { scrubCut } from './uplinkScrub.mjs';
|
|
5
|
+
import { REFERENCE_DIAGNOSTICS } from './publicReferenceProtocol.mjs';
|
|
6
|
+
|
|
7
|
+
export function websiteUrl(value, scrub = s => s) {
|
|
8
|
+
if (typeof value !== 'string' || value.length > 8_192) return undefined;
|
|
9
|
+
try {
|
|
10
|
+
// Scrub before URL serialization can escape a known secret in its path.
|
|
11
|
+
const url = new URL(scrub(value));
|
|
12
|
+
if (!['http:', 'https:'].includes(url.protocol)) return undefined;
|
|
13
|
+
url.username = ''; url.password = ''; url.search = ''; url.hash = '';
|
|
14
|
+
return scrub(url.href).slice(0, 500);
|
|
15
|
+
} catch { return undefined; }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function websiteRequest(name, input, scrub) {
|
|
19
|
+
const method = name === 'WebSearch' ? 'search' : name === 'WebFetch' ? 'fetch' :
|
|
20
|
+
name === 'WebsiteAccess' ? (['search', 'fetch', 'browser', 'tool', 'capability'].includes(input.method) ? input.method : 'tool') :
|
|
21
|
+
/^(?:mcp__|[^.]+\.)/.test(name ?? '') && websiteUrl(input?.url) ? 'tool' : null;
|
|
22
|
+
if (!method) return null;
|
|
23
|
+
const requestedUrl = websiteUrl(input?.url, scrub);
|
|
24
|
+
return {
|
|
25
|
+
method, tool: scrubCut(name === 'WebsiteAccess' ? input.tool ?? 'website' : name, 120, scrub),
|
|
26
|
+
...(requestedUrl ? { requestedUrl } : {}), access: 'unknown',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Only fixed, recognized runtime errors can leave the machine. Never retain
|
|
31
|
+
* arbitrary tool output, even when is_error is set by the native runtime. */
|
|
32
|
+
export function observeWebsite(e, meta, scrub) {
|
|
33
|
+
const w = e.website;
|
|
34
|
+
const output = scrubCut(meta.output ?? '', 16_384, scrub);
|
|
35
|
+
const lines = output.split('\n', 80);
|
|
36
|
+
const diagnostic = lines.find(line => Object.values(REFERENCE_DIAGNOSTICS).includes(line) || /^(?:Error: )?(?:MCP tool call requires approval, but approval policy is never|Tool (?:WebFetch|WebSearch) is not available|Browser executable not found|Browser sandbox unavailable|Request timed out|fetch failed|Network request failed|Not accessible via this tool)\.?$/i.test(line) || line.startsWith('flowviant: this turn is read-only, so '));
|
|
37
|
+
const failed = meta.isError === true || (Number.isSafeInteger(meta.exitCode) && meta.exitCode !== 0);
|
|
38
|
+
w.access = failed ? 'failed' : 'unknown';
|
|
39
|
+
if (failed && diagnostic) {
|
|
40
|
+
e.errorMessage = diagnostic.slice(0, 500);
|
|
41
|
+
if (diagnostic.length > 500) e.errorTruncated = true;
|
|
42
|
+
if (/^flowviant:/.test(diagnostic)) {
|
|
43
|
+
w.access = 'permission_denied'; w.permissionSource = 'flowviant';
|
|
44
|
+
} else if (/requires approval/.test(diagnostic)) {
|
|
45
|
+
w.access = 'permission_denied'; w.permissionSource = 'runtime';
|
|
46
|
+
} else if (/not available|not found|sandbox unavailable/i.test(diagnostic)) w.access = 'unavailable';
|
|
47
|
+
}
|
|
48
|
+
// Explicit daemon-owned integration result, not a tool-input or prose field.
|
|
49
|
+
// This is the emission seam used by the isolated browser/capture consumers.
|
|
50
|
+
const observed = meta.website;
|
|
51
|
+
if (meta.result) w.observedAt = new Date().toISOString();
|
|
52
|
+
if (meta.result && observed && typeof observed === 'object') {
|
|
53
|
+
if (['unknown', 'loaded', 'search_results', 'failed', 'unavailable', 'permission_denied', 'challenge', 'partial'].includes(observed.access)) w.access = observed.access;
|
|
54
|
+
const finalUrl = websiteUrl(observed.finalUrl, scrub);
|
|
55
|
+
if (finalUrl) w.finalUrl = finalUrl;
|
|
56
|
+
if (Number.isInteger(observed.httpStatus) && observed.httpStatus >= 100 && observed.httpStatus <= 599) w.httpStatus = observed.httpStatus;
|
|
57
|
+
if (['flowviant', 'runtime', 'unknown'].includes(observed.permissionSource)) w.permissionSource = observed.permissionSource;
|
|
58
|
+
if (['advertised', 'unavailable', 'unknown'].includes(observed.availability)) w.availability = observed.availability;
|
|
59
|
+
if (typeof observed.observedAt === 'string') w.observedAt = scrubCut(observed.observedAt, 40, scrub);
|
|
60
|
+
}
|
|
61
|
+
// Search completion is evidence of a search, never of opening its hits.
|
|
62
|
+
if (w.method === 'search' && w.access === 'unknown' && !failed && meta.result && meta.toolSuccess === true) w.access = 'search_results';
|
|
63
|
+
if (w.method === 'search' && w.access === 'loaded') w.access = 'unknown';
|
|
64
|
+
if (!meta.result) w.access = 'unknown';
|
|
65
|
+
e.outcome = !meta.result ? 'unknown' : ['failed', 'unavailable', 'permission_denied'].includes(w.access) ? 'failed' :
|
|
66
|
+
failed ? 'failed' : meta.toolSuccess === true || ['loaded', 'search_results', 'challenge', 'partial'].includes(w.access) ? 'success' : 'unknown';
|
|
67
|
+
if (e.outcome === 'failed') e.errorCategory = w.access === 'permission_denied' ? 'denied' : w.access === 'unavailable' ? 'unavailable_tool' : 'tool_error';
|
|
68
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createToolLog } from './workToolLog.mjs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
|
|
4
|
+
/** Reuse the turn collector for agent research too. Trace entries are snapshots:
|
|
5
|
+
* later results must not mutate a request already queued or sent. */
|
|
6
|
+
export function createWebsiteTrace(cwd, trace, attributed) {
|
|
7
|
+
const { pushToolEvent } = createToolLog(cwd);
|
|
8
|
+
const inventory = new Map();
|
|
9
|
+
const emit = (event, owner) => {
|
|
10
|
+
const send = id => trace.tool(event, id);
|
|
11
|
+
if (owner && attributed) attributed(owner, send); else send(owner);
|
|
12
|
+
};
|
|
13
|
+
return (name, input, meta = {}) => {
|
|
14
|
+
const e = pushToolEvent(name, input, meta);
|
|
15
|
+
if (!e?.website) return false;
|
|
16
|
+
const event = structuredClone(e);
|
|
17
|
+
if (event.id) {
|
|
18
|
+
// The trace scopes/caps ids. Hash the complete native identity first so
|
|
19
|
+
// long child prefixes cannot hide the call id at that boundary.
|
|
20
|
+
const id = createHash('sha256').update(JSON.stringify([meta.agentId ?? null, event.id])).digest('hex');
|
|
21
|
+
event.id = trace.agentId(`web:${id}`);
|
|
22
|
+
}
|
|
23
|
+
// Startup inventory belongs beside a reference attempt, not in every
|
|
24
|
+
// coding turn. An actual unavailable/denied capability result still emits
|
|
25
|
+
// immediately, even if no request can follow it.
|
|
26
|
+
if (event.website.method === 'capability' && !event.website.requestedUrl && event.website.access === 'unknown') {
|
|
27
|
+
inventory.set(event.website.tool, { event, owner: meta.agentId });
|
|
28
|
+
if (inventory.size > 12) inventory.delete(inventory.keys().next().value);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
for (const pending of inventory.values()) emit(pending.event, pending.owner);
|
|
32
|
+
inventory.clear();
|
|
33
|
+
emit(event, meta.agentId);
|
|
34
|
+
return true;
|
|
35
|
+
};
|
|
36
|
+
}
|
package/bin/lib/work.mjs
CHANGED
|
@@ -31,7 +31,7 @@ import { ORCHESTRATOR_SERVER } from './orchestratorProtocol.mjs';
|
|
|
31
31
|
* agent calls them: a multi-repository card's add_repository, and an
|
|
32
32
|
* orchestrated card's routing. Never a project server's, whatever the base
|
|
33
33
|
* `.mcp.json` names. */
|
|
34
|
-
export const CARD_TURN_SERVERS = Object.freeze({ repositories: 'card_repositories', orchestrator: ORCHESTRATOR_SERVER });
|
|
34
|
+
export const CARD_TURN_SERVERS = Object.freeze({ repositories: 'card_repositories', orchestrator: ORCHESTRATOR_SERVER, uiReview: 'card_ui_review' });
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* The card turn's tools, prepared, and what it is told of them.
|
|
@@ -44,7 +44,7 @@ export const CARD_TURN_SERVERS = Object.freeze({ repositories: 'card_repositorie
|
|
|
44
44
|
* from here), the note, the mount's part of the CLI's `mcpEnv` and whether it
|
|
45
45
|
* needs one, and the keys the runner takes the mount as.
|
|
46
46
|
*/
|
|
47
|
-
export function prepareCardTools({ rt, posture, wt, repoRoot, baseRef, sources = [], repositoryTool = null, orchestratorConfig = null, orchestrated = false, codexHome = null, env = process.env, machineServers = machineMcpServers }) {
|
|
47
|
+
export function prepareCardTools({ rt, posture, wt, repoRoot, baseRef, sources = [], repositoryTool = null, orchestratorConfig = null, uiReviewConfig = null, orchestrated = false, codexHome = null, env = process.env, machineServers = machineMcpServers }) {
|
|
48
48
|
// A turn reads the base ref afresh. Changes the agent has made to its
|
|
49
49
|
// own worktree cannot silently change the next turn's tools.
|
|
50
50
|
const source = sources.find((r) => r.wt === wt);
|
|
@@ -56,6 +56,7 @@ export function prepareCardTools({ rt, posture, wt, repoRoot, baseRef, sources =
|
|
|
56
56
|
const projectTools = prepareAgentTools(snapshot, rt, env, wt, { codexHome, fenced: posture !== 'build', turnServers: {
|
|
57
57
|
...(repositoryTool ? { [CARD_TURN_SERVERS.repositories]: repositoryTool.config } : {}),
|
|
58
58
|
...(orchestratorConfig ? { [CARD_TURN_SERVERS.orchestrator]: orchestratorConfig } : {}),
|
|
59
|
+
...(posture === 'build' && uiReviewConfig ? { [CARD_TURN_SERVERS.uiReview]: uiReviewConfig } : {}),
|
|
59
60
|
} });
|
|
60
61
|
try {
|
|
61
62
|
const mountsProjectMcp = TURN_PROFILES[posture]?.projectMcp === true && runnerToolCapabilities(rt).mcp;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { reportLocalCleanup } from './localCleanupReport.mjs';
|
|
2
2
|
import { createManagedLocalCleanup } from './managedLocalCleanup.mjs';
|
|
3
|
+
import { createCaptureRetention } from './referenceCaptures.mjs';
|
|
3
4
|
import { imageRevisionInstructions } from './artifactIteration.mjs';
|
|
4
5
|
import { agentChildScope } from './agentQueueContext.mjs';
|
|
5
6
|
import { measureTestFindings } from './testFindings.mjs';
|
|
@@ -45,6 +46,7 @@ import { scrub as envScrub } from './uplinkScrub.mjs';
|
|
|
45
46
|
import { canRun, runtimeById } from './runtimes.mjs';
|
|
46
47
|
import { toolEventOf, CLAUDE_TOOL_PROSE_KINDS, scrubNativeText } from './runtimeEvents.mjs';
|
|
47
48
|
import { makeTraceRelay } from './trace.mjs';
|
|
49
|
+
import { createWebsiteTrace } from './websiteTrace.mjs';
|
|
48
50
|
import { prepareCardTools } from './workAgentTools.mjs';
|
|
49
51
|
import { runTurnResumingOnce } from './resumeLost.mjs';
|
|
50
52
|
import { runAgentWithInternalWaits } from './agentInternalWait.mjs';
|
|
@@ -64,6 +66,8 @@ import { knownCodexModels } from './runtimeOfferings.mjs';
|
|
|
64
66
|
import { codexSubagentsSafe } from './runtimeCodex.mjs';
|
|
65
67
|
import { measuredRuntimeVersion } from './runtimeDetection.mjs';
|
|
66
68
|
import { createIntermediateArtifacts } from './intermediateArtifacts.mjs';
|
|
69
|
+
import { createUiReview } from './uiReview.mjs';
|
|
70
|
+
import { withUiReview, appendUiReviewEvidence } from './uiReviewTurn.mjs';
|
|
67
71
|
|
|
68
72
|
export function createAgentTurnExecution({
|
|
69
73
|
admit,
|
|
@@ -83,6 +87,7 @@ export function createAgentTurnExecution({
|
|
|
83
87
|
runReviewEntry,
|
|
84
88
|
runMergeCheck,
|
|
85
89
|
workChildren,
|
|
90
|
+
groupKillChildren,
|
|
86
91
|
agentChildren,
|
|
87
92
|
postAgentTrace,
|
|
88
93
|
postAgentActivity,
|
|
@@ -92,6 +97,7 @@ export function createAgentTurnExecution({
|
|
|
92
97
|
makeLiveMessages = createLiveMessages,
|
|
93
98
|
runTurnImpl = runTurn,
|
|
94
99
|
exportModel,
|
|
100
|
+
makeUiReview = createUiReview,
|
|
95
101
|
}) {
|
|
96
102
|
const reportDelivery = createDeliveryReporter();
|
|
97
103
|
/** How long the final flush may hold the settle. Bounded because the settle
|
|
@@ -359,6 +365,9 @@ export function createAgentTurnExecution({
|
|
|
359
365
|
* CLAUDE_TOOL_PROSE_KINDS.
|
|
360
366
|
*/
|
|
361
367
|
const doubledKinds = runtimeById(rt)?.parse ? null : CLAUDE_TOOL_PROSE_KINDS;
|
|
368
|
+
const websiteTrace = createWebsiteTrace(wt, trace, (id, emit) => {
|
|
369
|
+
if (orchestrator) orchestrator.attributed(id, emit); else emit(id);
|
|
370
|
+
});
|
|
362
371
|
|
|
363
372
|
/**
|
|
364
373
|
* WHICH BRAIN THIS CONTAINER WAS PINNED TO — the same `brainFor` the tab
|
|
@@ -392,7 +401,14 @@ export function createAgentTurnExecution({
|
|
|
392
401
|
const spawnedChildren = new Set();
|
|
393
402
|
/** The CLI, unless a person already asked to stop this turn (Interrupt
|
|
394
403
|
* now): then nothing starts — no fresh retry, no continuation. */
|
|
395
|
-
|
|
404
|
+
let uiReview = null;
|
|
405
|
+
let uiReviewNote = '';
|
|
406
|
+
let uiReviewEvidence = '';
|
|
407
|
+
const runCli = (args) => {
|
|
408
|
+
if (run.interrupted) return Promise.resolve('');
|
|
409
|
+
args = withUiReview(args, uiReviewNote, uiReview?.env);
|
|
410
|
+
return runTurnImpl(args);
|
|
411
|
+
};
|
|
396
412
|
/**
|
|
397
413
|
* WHAT THIS TURN SPENT, AS THE CLI COUNTED IT (2026-09-19).
|
|
398
414
|
*
|
|
@@ -440,6 +456,18 @@ export function createAgentTurnExecution({
|
|
|
440
456
|
let orchestrator = null;
|
|
441
457
|
let intermediateDelivery = null;
|
|
442
458
|
try {
|
|
459
|
+
if (posture === 'build' && job.kind !== 'merge_resolve') {
|
|
460
|
+
try {
|
|
461
|
+
uiReview = await makeUiReview({ wt, signal: setupController.signal, onChild: ch => {
|
|
462
|
+
workChildren.set(ch, agentId); groupKillChildren?.add(ch);
|
|
463
|
+
return () => { workChildren.delete(ch); groupKillChildren?.delete(ch); };
|
|
464
|
+
} });
|
|
465
|
+
uiReviewNote = uiReview.instructions;
|
|
466
|
+
} catch (error) {
|
|
467
|
+
uiReviewNote = `Local UI self-review is unavailable: ${envScrub(error.message)}. Report browser, image and interaction checks as unrun with this reason when relevant; do not install tools or weaken isolation.`;
|
|
468
|
+
uiReviewEvidence = uiReviewNote;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
443
471
|
if (orchestrating) {
|
|
444
472
|
orchestrator = await createOrchestrator({ turnId, runtime: rt, trace, admit,
|
|
445
473
|
assignmentContext: () => agentChildScope(job),
|
|
@@ -450,7 +478,7 @@ export function createAgentTurnExecution({
|
|
|
450
478
|
notify: message => liveInput ? run.live?.inject(message) === true : false,
|
|
451
479
|
taken: id => run.live?.onTaken(id) });
|
|
452
480
|
}
|
|
453
|
-
const tools = prepareCardTools({ rt, posture, wt, repoRoot, baseRef, sources: repositoryRun?.sources ?? [], repositoryTool: repositoryRun?.tool ?? null, orchestratorConfig: orchestrator?.config ?? null, orchestrated: Boolean(orchestrator), codexHome: agentResume.codexHome });
|
|
481
|
+
const tools = prepareCardTools({ rt, posture, wt, repoRoot, baseRef, sources: repositoryRun?.sources ?? [], repositoryTool: repositoryRun?.tool ?? null, orchestratorConfig: orchestrator?.config ?? null, uiReviewConfig: uiReview?.config, orchestrated: Boolean(orchestrator), codexHome: agentResume.codexHome });
|
|
454
482
|
projectTools = tools.projectTools;
|
|
455
483
|
localCleanup?.begin(turnId);
|
|
456
484
|
const { toolsNote } = tools;
|
|
@@ -468,6 +496,9 @@ export function createAgentTurnExecution({
|
|
|
468
496
|
})) } : {}),
|
|
469
497
|
// The kickoff and the system prompt, composed by the agent lane's
|
|
470
498
|
// shape (workAgentTurnShape.mjs) from what this run measured.
|
|
499
|
+
signal: setupController.signal,
|
|
500
|
+
onReferenceCapture: createCaptureRetention({ report: (files, { placeDir }) => artifacts.report({ placeDir, before: new Map(), agentId, turnId, requireStored: true, referenceCapture: true,
|
|
501
|
+
includeNames: new Set(files.map(f => f.name)), requiredOutputs: files.map(({ name, sha256 }) => ({ name, sha256 })) }) }),
|
|
471
502
|
researchHandoff: job.researchHandoff,
|
|
472
503
|
resultInputs: job.resultInputs,
|
|
473
504
|
intermediateFiles,
|
|
@@ -583,6 +614,7 @@ export function createAgentTurnExecution({
|
|
|
583
614
|
// the builder itself (bounded window BEFORE its caps; see
|
|
584
615
|
// toolEventOf). A tool it does not know pushes nothing.
|
|
585
616
|
onToolEvent: (name, input, meta) => {
|
|
617
|
+
if (websiteTrace(name, input, meta)) return;
|
|
586
618
|
if (meta?.agentId) {
|
|
587
619
|
if (!meta.result) {
|
|
588
620
|
const event = toolEventOf(name, input, wt, envScrub);
|
|
@@ -698,6 +730,14 @@ export function createAgentTurnExecution({
|
|
|
698
730
|
// answer is scrubbed; then it can leave the machine's secret set.
|
|
699
731
|
out = envScrub(out);
|
|
700
732
|
if (lastAnswer) lastAnswer = envScrub(lastAnswer);
|
|
733
|
+
// The builder has finished its fix/recheck loop. Close only resources
|
|
734
|
+
// owned by its review tool, then bind evidence to the final source
|
|
735
|
+
// before the existing artifact upload and delivery settlement.
|
|
736
|
+
try { await uiReview?.close(); }
|
|
737
|
+
finally {
|
|
738
|
+
try { uiReviewEvidence = uiReview?.finalize() ?? uiReviewEvidence; }
|
|
739
|
+
catch (error) { uiReviewEvidence = `Local UI review final source/evidence measurement unavailable: ${envScrub(error.message)}. Earlier checks do not establish the final source.`; }
|
|
740
|
+
}
|
|
701
741
|
await run.live?.stop();
|
|
702
742
|
await orchestrator?.close(run.interrupted);
|
|
703
743
|
await brief?.stop();
|
|
@@ -804,6 +844,7 @@ export function createAgentTurnExecution({
|
|
|
804
844
|
wt,
|
|
805
845
|
modelExports: intermediateDelivery?.modelExports,
|
|
806
846
|
});
|
|
847
|
+
appendUiReviewEvidence(body, uiReviewEvidence);
|
|
807
848
|
if (body.outcome === 'delivered') intermediateDelivery?.commit();
|
|
808
849
|
// This place's writer lock is still held. Measure the exited children
|
|
809
850
|
// and their groups before releasing any file requirements. Dirty repository files are unrelated to managed scratch eligibility.
|
|
@@ -53,6 +53,7 @@ export function createWorkAgentTurns({
|
|
|
53
53
|
stopAgentPublishing = () => {},
|
|
54
54
|
admit,
|
|
55
55
|
workChildren,
|
|
56
|
+
groupKillChildren,
|
|
56
57
|
agentPublished,
|
|
57
58
|
agentRemoteAt,
|
|
58
59
|
/** The turn's files, fetched into its worktree (workAttachments.mjs, 0.112.0). */
|
|
@@ -138,6 +139,7 @@ export function createWorkAgentTurns({
|
|
|
138
139
|
runMergeCheck,
|
|
139
140
|
exportModel,
|
|
140
141
|
workChildren,
|
|
142
|
+
groupKillChildren,
|
|
141
143
|
agentChildren,
|
|
142
144
|
postAgentTrace,
|
|
143
145
|
postAgentActivity,
|
package/bin/lib/workToolLog.mjs
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { toolEventOf } from './runtimeEvents.mjs';
|
|
11
11
|
import { scrubCut, scrub as envScrub } from './uplinkScrub.mjs';
|
|
12
|
+
import { observeWebsite } from './websiteAccess.mjs';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* THE TURN'S TOOL LOG — the structured relay behind the transcript's
|
|
@@ -47,7 +48,15 @@ export function createToolLog(cwd) {
|
|
|
47
48
|
e.outcome = !meta.result ? 'unknown' : failed ? 'failed' :
|
|
48
49
|
meta.runtime === 'codex' && code === undefined ? 'unknown' : 'success';
|
|
49
50
|
if (code !== undefined) e.exitCode = code;
|
|
50
|
-
if (
|
|
51
|
+
if (e.website) {
|
|
52
|
+
observeWebsite(e, meta, envScrub);
|
|
53
|
+
if (e.errorTruncated) toolLog.state = 'partial';
|
|
54
|
+
}
|
|
55
|
+
if (e.outcome === 'failed') {
|
|
56
|
+
if (e.website) {
|
|
57
|
+
if (!toolLog.firstFailure) { toolLog.firstFailure = structuredClone(e); toolLog.recovery = []; }
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
51
60
|
const output = scrubCut(meta.output ?? '', 16_384);
|
|
52
61
|
const guardAt = output.indexOf('flowviant: this turn is read-only, so ');
|
|
53
62
|
const guard = guardAt >= 0 ? output.slice(guardAt).split('\n', 1)[0] : undefined;
|
|
@@ -67,8 +76,19 @@ export function createToolLog(cwd) {
|
|
|
67
76
|
};
|
|
68
77
|
const pushToolEvent = (name, input, meta = {}) => {
|
|
69
78
|
const id = typeof meta.id === 'string' && meta.id && meta.id.length <= 160 ? meta.id : undefined;
|
|
70
|
-
const
|
|
79
|
+
const key = id && JSON.stringify([meta.agentId ?? null, id]);
|
|
80
|
+
const existing = key && correlated.get(key);
|
|
71
81
|
if (existing) {
|
|
82
|
+
// Codex may only name the web action/URL on completion. Enrich that
|
|
83
|
+
// same native request instead of keeping its provisional search label.
|
|
84
|
+
if (existing.website && name) {
|
|
85
|
+
const request = toolEventOf(name, input, cwd, envScrub)?.website;
|
|
86
|
+
if (request) {
|
|
87
|
+
existing.website.method = request.method;
|
|
88
|
+
existing.website.tool = request.tool;
|
|
89
|
+
if (request.requestedUrl) existing.website.requestedUrl = request.requestedUrl;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
72
92
|
if (meta.result) observe(existing, meta);
|
|
73
93
|
refresh();
|
|
74
94
|
return existing;
|
|
@@ -87,7 +107,7 @@ export function createToolLog(cwd) {
|
|
|
87
107
|
if (typeof meta.agentId === 'string' && meta.agentId) e.agentId = scrubCut(meta.agentId, 160);
|
|
88
108
|
if (e.agentId && typeof meta.agentTitle === 'string' && meta.agentTitle) e.agentTitle = scrubCut(meta.agentTitle, 120);
|
|
89
109
|
if (e.t !== 'plan') {
|
|
90
|
-
if (id) { e.id = scrubCut(id, 160); correlated.set(
|
|
110
|
+
if (id) { e.id = scrubCut(id, 160); correlated.set(key, e); }
|
|
91
111
|
if (name === 'Read') for (const key of ['offset', 'limit']) {
|
|
92
112
|
if (Number.isSafeInteger(input?.[key]) && input[key] >= 0) e[key] = input[key];
|
|
93
113
|
}
|
|
@@ -115,6 +135,7 @@ export function createToolLog(cwd) {
|
|
|
115
135
|
last.errorCategory === e.errorCategory &&
|
|
116
136
|
last.offset === e.offset && last.limit === e.limit &&
|
|
117
137
|
last.t === e.t &&
|
|
138
|
+
!e.website && !last.website &&
|
|
118
139
|
last.p === e.p &&
|
|
119
140
|
last.q === e.q &&
|
|
120
141
|
last.c === e.c;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.125.
|
|
3
|
+
"version": "0.125.21",
|
|
4
4
|
"description": "Run your own coding CLIs as build agents for Flowviant \u2014 Claude Code, Codex or Antigravity, on your own credentials. Holds your sessions, keeps a worktree per tab, and ships branches on your word.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|