flowviant 0.125.18 → 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/artifactCache.mjs +336 -0
- package/bin/lib/artifactContracts.mjs +87 -2
- package/bin/lib/artifactIteration.mjs +3 -2
- package/bin/lib/artifactPolicy.mjs +5 -1
- package/bin/lib/artifactReporter.mjs +91 -9
- package/bin/lib/artifacts.mjs +12 -6
- package/bin/lib/claudeStream.mjs +7 -0
- package/bin/lib/codexFencedMount.mjs +2 -2
- package/bin/lib/fleet.mjs +9 -0
- package/bin/lib/intermediateArtifacts.mjs +40 -8
- package/bin/lib/knowledgeLibrary.mjs +2 -1
- package/bin/lib/localCleanupReport.mjs +34 -0
- package/bin/lib/localCleanupState.mjs +68 -0
- package/bin/lib/logReplay.mjs +6 -0
- package/bin/lib/managedLocalCleanup.mjs +158 -0
- 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/resultInputs.mjs +4 -2
- package/bin/lib/runTurn.mjs +8 -0
- package/bin/lib/runtimeCodex.mjs +1 -1
- 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 +46 -3
- package/bin/lib/workAgentPlans.mjs +3 -0
- package/bin/lib/workAgentPrecheck.mjs +16 -15
- package/bin/lib/workAgentReview.mjs +2 -2
- package/bin/lib/workAgentTools.mjs +3 -2
- package/bin/lib/workAgentTurnExecution.mjs +69 -4
- package/bin/lib/workAgentTurns.mjs +3 -1
- package/bin/lib/workLocalCleanup.mjs +125 -0
- package/bin/lib/workMergeBatch.mjs +19 -4
- package/bin/lib/workSessionGroups.mjs +20 -1
- 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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reportLocalCleanup } from './localCleanupReport.mjs';
|
|
1
2
|
/**
|
|
2
3
|
* Work sessions — the Workbench tabs and the agent lanes, daemon side: THE
|
|
3
4
|
* MANAGER that builds every lane and hands each one exactly what it uses.
|
|
@@ -27,6 +28,7 @@
|
|
|
27
28
|
* whole — `workBusy` and the returned surface. A pure rule or constant
|
|
28
29
|
* (REPO_PLACE, the resume-id guards, brainFor, the turn lock) is IMPORTED by
|
|
29
30
|
* every lane that reads it, never carried through here.
|
|
31
|
+
* workLocalCleanup.mjs released managed session and agent cleanup
|
|
30
32
|
* workPlaces.mjs where a session works, its worktree, its markers
|
|
31
33
|
* workSessionGroups.mjs which process groups each tab started
|
|
32
34
|
* workChildren.mjs the live CLI children, teardown, the admission
|
|
@@ -60,6 +62,10 @@
|
|
|
60
62
|
* repositoryPlaces + agentRepositories: multi-repo places; the live brief: workAgentBrief, workAgentBriefRunner)
|
|
61
63
|
*/
|
|
62
64
|
|
|
65
|
+
import { dirname, join } from 'node:path';
|
|
66
|
+
import { placeTurnLockedByLivePid } from './turnLock.mjs';
|
|
67
|
+
import { createWorkLocalCleanup } from './workLocalCleanup.mjs';
|
|
68
|
+
import { createManagedLocalCleanup } from './managedLocalCleanup.mjs';
|
|
63
69
|
import { trackedRepositorySetup } from './repositorySetup.mjs';
|
|
64
70
|
import { createRepositoryPlaces } from './repositoryPlaces.mjs';
|
|
65
71
|
import { createAgentRepositories } from './agentRepositories.mjs';
|
|
@@ -91,7 +97,7 @@ import { createWorkSessionRuntime } from './workSessionRuntime.mjs';
|
|
|
91
97
|
import { createAdoptCarry } from './workAdoptCarry.mjs';
|
|
92
98
|
import { createWorkSessionTurns } from './workSessionTurns.mjs';
|
|
93
99
|
import { createWorkRetire } from './workRetire.mjs';
|
|
94
|
-
import { FLEET_URL, FLEET_TOKEN, USER_AGENT } from './config.mjs';
|
|
100
|
+
import { FLEET_URL, FLEET_TOKEN, USER_AGENT, PROJECT_ID } from './config.mjs';
|
|
95
101
|
import { gitNet as gitNetIn, excludeInWorktree } from './git.mjs';
|
|
96
102
|
import { createProjectLandedObserver } from './projectLanded.mjs';
|
|
97
103
|
import { createPlaceLock } from './placeLock.mjs';
|
|
@@ -110,6 +116,7 @@ export function createWorkManager({
|
|
|
110
116
|
getBaseRef,
|
|
111
117
|
getMcpUrl,
|
|
112
118
|
getLeaseTtl, exportModel,
|
|
119
|
+
getCleanupPermission = () => false,
|
|
113
120
|
/** "The repo picture changed — look again." See the caller in fleet.mjs. */
|
|
114
121
|
onRepoChanged = () => {},
|
|
115
122
|
/**
|
|
@@ -156,6 +163,7 @@ export function createWorkManager({
|
|
|
156
163
|
*/
|
|
157
164
|
const artifacts = createArtifactReporter({
|
|
158
165
|
fleetUrl: FLEET_URL,
|
|
166
|
+
projectIdentity: () => PROJECT_ID,
|
|
159
167
|
token: FLEET_TOKEN,
|
|
160
168
|
userAgent: USER_AGENT,
|
|
161
169
|
scrub: envScrub,
|
|
@@ -211,7 +219,7 @@ export function createWorkManager({
|
|
|
211
219
|
|
|
212
220
|
const { placeOf, notePlace, learnPlaces, placeDir, branchAt, placeWtFor, sessionMetaPath, worktreeBornAt } =
|
|
213
221
|
createWorkPlaces({ repoRoot, baseDir, baseRef, onRepoChanged });
|
|
214
|
-
const { sessionGroups, noteSessionGroup, pruneSessionGroups, sessionProcesses } =
|
|
222
|
+
const { sessionGroups, noteSessionGroup, pruneSessionGroups, sessionProcesses, cleanupProcessesIdle } =
|
|
215
223
|
createWorkSessionGroups({ repoRoot });
|
|
216
224
|
const { workChildren, groupKillChildren, shutdownWork, liveTurnCount, liveTurns, admit } =
|
|
217
225
|
createWorkChildren({ extraLiveTurns });
|
|
@@ -226,6 +234,39 @@ export function createWorkManager({
|
|
|
226
234
|
getArtifactsAccepted,
|
|
227
235
|
log: (line) => warn(line),
|
|
228
236
|
});
|
|
237
|
+
const { reconcileLocalCleanup, cleanupBusy } = createWorkLocalCleanup({
|
|
238
|
+
placeLocks, inPlace, permission: getCleanupPermission,
|
|
239
|
+
onResult: reportLocalCleanup, onIncomplete: reportLocalCleanup,
|
|
240
|
+
eligible: place => {
|
|
241
|
+
if (shipping.size || cleanupProcessesIdle(place) !== true) return false;
|
|
242
|
+
const wt = placeDir(place), lock = wt && sessionMetaPath(wt, 'flowviant-turn.lock');
|
|
243
|
+
return Boolean(lock) && !placeTurnLockedByLivePid(lock);
|
|
244
|
+
},
|
|
245
|
+
targetsFor: place => {
|
|
246
|
+
if (!PROJECT_ID) return [];
|
|
247
|
+
const agentId = place.slice(2), siblings = repositoryPlaces?.entries(place);
|
|
248
|
+
const roots = siblings?.length ? siblings.filter(r => r.writable).map(r => r.wt) : [placeDir(place)];
|
|
249
|
+
return roots.flatMap(wt => {
|
|
250
|
+
const statePath = wt && sessionMetaPath(wt, 'flowviant-local-cleanup', agentId);
|
|
251
|
+
if (!statePath) return [];
|
|
252
|
+
return [{ placeDir: wt, agentId, projectId: PROJECT_ID, statePath,
|
|
253
|
+
operations: ({ guard, recoveryMemo, preparedOnly }) => {
|
|
254
|
+
const output = artifacts.cacheFor({ placeDir: wt, agentId, cleanupGuard: guard, recoveryMemo, preparedOnly });
|
|
255
|
+
if (!output) return [];
|
|
256
|
+
const managed = createManagedLocalCleanup({ placeDir: wt, projectId: PROJECT_ID, agentId,
|
|
257
|
+
path: sessionMetaPath(wt, 'flowviant-managed-local', agentId),
|
|
258
|
+
codexHome: join(dirname(statePath), `flowviant-agent-codex-home-${agentId}`),
|
|
259
|
+
outputCache: output, getUses: () => guard() ? [] : ['lifecycle-unknown-or-active'],
|
|
260
|
+
});
|
|
261
|
+
return [...output.records().map(ref => ({ ref: { ...ref, kind: 'artifact' },
|
|
262
|
+
prepare: () => output.prepareEviction(ref), run: () => output.evict(ref) })),
|
|
263
|
+
...managed.records().map(ref => ({ ref,
|
|
264
|
+
prepare: () => managed.prepare(ref), run: () => managed.reclaimOne(ref) }))];
|
|
265
|
+
} }];
|
|
266
|
+
});
|
|
267
|
+
},
|
|
268
|
+
log: line => warn(line),
|
|
269
|
+
});
|
|
229
270
|
const { reportSessionWorktree, reportPlaceWorktrees, burstListeners, reportWorktrees } =
|
|
230
271
|
createWorkPlaceReports({
|
|
231
272
|
repositoryPlaces,
|
|
@@ -387,6 +428,7 @@ export function createWorkManager({
|
|
|
387
428
|
publishAgentBranch, stopAgentPublishing,
|
|
388
429
|
admit,
|
|
389
430
|
workChildren,
|
|
431
|
+
groupKillChildren,
|
|
390
432
|
agentPublished,
|
|
391
433
|
agentRemoteAt,
|
|
392
434
|
fetchAgentFiles,
|
|
@@ -455,7 +497,7 @@ export function createWorkManager({
|
|
|
455
497
|
workChildren.size > 0 ||
|
|
456
498
|
workAnswering.size > 0 ||
|
|
457
499
|
pendingWorkReports.size > 0 ||
|
|
458
|
-
pendingShipReports.size > 0;
|
|
500
|
+
pendingShipReports.size > 0 || cleanupBusy();
|
|
459
501
|
|
|
460
502
|
return {
|
|
461
503
|
flushWorkReports,
|
|
@@ -473,6 +515,7 @@ export function createWorkManager({
|
|
|
473
515
|
retireWorkSessions,
|
|
474
516
|
reportWorktrees,
|
|
475
517
|
reconcileIdleArtifacts,
|
|
518
|
+
reconcileLocalCleanup,
|
|
476
519
|
shutdownWork,
|
|
477
520
|
workBusy,
|
|
478
521
|
runningAgentCount: () => agentTurns.size,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fetchResultInputs } from './resultInputs.mjs';
|
|
1
2
|
import { planWork, reportPlanOutcome } from './workActivity.mjs';
|
|
2
3
|
/** Agent plan claims, read-only planning turns, and proposal reports. */
|
|
3
4
|
import { PLAN_RECOVERY_SYSTEM, parsePlanDiagnosis, watchPlanAttempt } from './workPlanRecovery.mjs';
|
|
@@ -287,6 +288,7 @@ export function createWorkAgentPlans({
|
|
|
287
288
|
*/
|
|
288
289
|
const liveAgents = (Array.isArray(job.liveAgents) ? job.liveAgents : []).map(a =>
|
|
289
290
|
liveAgentContext(a, measureAgentFootprint({ placeId: a?.placeId, baseDir, baseRef })));
|
|
291
|
+
await fetchResultInputs(repoRoot, job.resultInputs);
|
|
290
292
|
const knowledgeDir = knowledgeDirFor(repoRoot);
|
|
291
293
|
discovery.toolLog.baseline = measureRepositoryBaseline({ cwd: repoRoot, baseRef, knowledgeDir });
|
|
292
294
|
discovery.toolLog.captureBaseline = repositoryBaselineOf(job.captureBaseline);
|
|
@@ -351,6 +353,7 @@ export function createWorkAgentPlans({
|
|
|
351
353
|
});
|
|
352
354
|
};
|
|
353
355
|
let context = AGENT_PLAN_KICKOFF({ tasks, liveAgents, agentCap: Number.isInteger(job.agentCap) && job.agentCap > 0 ? job.agentCap : 3 });
|
|
356
|
+
if (job.resultContext) context += '\n\n' + job.resultContext;
|
|
354
357
|
if (job.recovery?.start) context += '\nSTART REPAIR: This is a supporting diagnosis for an already approved Start. Return exactly ONE group with the same ordered taskIds, attachment target (or absence) and CLI. Preserve the approval; do not start an unapproved sibling. Diagnose admission/pickup errors and repair planning evidence/context. Ask only a genuine person-only question in the diagnostic JSON. Approval and source revision (data): ' + JSON.stringify(job.recovery.start);
|
|
355
358
|
const system = withProjectContext(SYSTEM_PLAN, { knowledgeDir }) + '\n\n' + discoveryGuide(rt, 'planning') + '\n\n' + REPOSITORY_BASELINE_PARAGRAPH(discovery.toolLog.baseline, discovery.toolLog.captureBaseline, discovery.toolLog.reconciliation);
|
|
356
359
|
if (job.recovery) {
|
|
@@ -12,10 +12,12 @@ import { gitOrNull } from './git.mjs';
|
|
|
12
12
|
import { parsePrecheck } from './agentPlan.mjs';
|
|
13
13
|
import { readStash } from './agentCards.mjs';
|
|
14
14
|
import { c, note } from './ui.mjs';
|
|
15
|
-
import { runTurn } from './runTurn.mjs';
|
|
15
|
+
import { runTurn as defaultRunTurn } from './runTurn.mjs';
|
|
16
16
|
import { SYSTEM_PRECHECK, AGENT_PRECHECK_KICKOFF } from './prompts.mjs';
|
|
17
17
|
import { scrub as envScrub } from './uplinkScrub.mjs';
|
|
18
|
-
import {
|
|
18
|
+
import { detectRuntimes as defaultDetectRuntimes } from './runtimeDetection.mjs';
|
|
19
|
+
import { canRun, runtimeById } from './runtimes.mjs';
|
|
20
|
+
import { brainFor } from './workBrain.mjs';
|
|
19
21
|
import { runCappedTurn } from './workCappedTurn.mjs';
|
|
20
22
|
import { taskIdsFromMessage } from './worktreeDiff.mjs';
|
|
21
23
|
import { limitLine } from './cliLimit.mjs';
|
|
@@ -23,12 +25,12 @@ import { fleetEndpoint, isConsideredRefusal } from './fleetWire.mjs';
|
|
|
23
25
|
import { postFleetJson } from './fleetPost.mjs';
|
|
24
26
|
import { agentBrowserHome } from './noWindowEnv.mjs';
|
|
25
27
|
|
|
26
|
-
export function createWorkAgentPrecheck({ baseRef, admit, sessionMetaPath, workChildren }) {
|
|
28
|
+
export function createWorkAgentPrecheck({ baseRef, admit, sessionMetaPath, workChildren, runTurn = defaultRunTurn, detectRuntimes = defaultDetectRuntimes }) {
|
|
27
29
|
const AGENT_PRECHECK_URL = fleetEndpoint('agent-precheck', FLEET_URL);
|
|
28
30
|
|
|
29
31
|
// ── THE AI PRE-REVIEW ──────────────────────────────────────────────────────
|
|
30
32
|
//
|
|
31
|
-
// A
|
|
33
|
+
// A fresh conversation reads the branch before the human does. The owner asked for
|
|
32
34
|
// it in these words: "before having the user manually check, can we have the
|
|
33
35
|
// daemon … spawn an agent to review the work so basically we get an ai to look
|
|
34
36
|
// at the review before a human looks at it for a double check."
|
|
@@ -135,15 +137,14 @@ export function createWorkAgentPrecheck({ baseRef, admit, sessionMetaPath, workC
|
|
|
135
137
|
return { text, taskIds: [...taskIds] };
|
|
136
138
|
};
|
|
137
139
|
|
|
138
|
-
const runPrecheck = async (agentId, wt, agentName, repository) => {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
if (!rt) return;
|
|
140
|
+
const runPrecheck = async (agentId, wt, agentName, repository, source) => {
|
|
141
|
+
// A fresh read-only conversation, on this agent's CLI/model/effort.
|
|
142
|
+
// Missing provenance or a missing CLI skips this optional reading; it
|
|
143
|
+
// never licenses a machine-wide provider substitution.
|
|
144
|
+
if (!source) return;
|
|
145
|
+
const rt = source.runtime || 'claude';
|
|
146
|
+
if (!canRun(runtimeById(rt), 'consult') || !detectRuntimes().some(row => row.id === rt && row.installed)) return;
|
|
147
|
+
const brain = brainFor(source, rt);
|
|
147
148
|
/**
|
|
148
149
|
* THE PRESSURE GUARD, at the spawn, exactly as every other unattended lane
|
|
149
150
|
* asks it. `churn` and never `interactive`: nobody is watching this, and a
|
|
@@ -232,6 +233,7 @@ export function createWorkAgentPrecheck({ baseRef, admit, sessionMetaPath, workC
|
|
|
232
233
|
profile: 'consult',
|
|
233
234
|
cwd: wt,
|
|
234
235
|
runtime: rt,
|
|
236
|
+
...brain,
|
|
235
237
|
// The agent's own browser profile, in the worktree this reader stands
|
|
236
238
|
// in; no display either way (noWindowEnv.mjs, 2026-09-29).
|
|
237
239
|
browserHome: agentBrowserHome(sessionMetaPath, wt, agentId),
|
|
@@ -242,8 +244,7 @@ export function createWorkAgentPrecheck({ baseRef, admit, sessionMetaPath, workC
|
|
|
242
244
|
work: { kind: 'pre-review', id: agentId, name: agentName },
|
|
243
245
|
// The CLI's own count for this reading. Set, never accumulated: one
|
|
244
246
|
// turn is one `result` event, and the adding-up is the server's.
|
|
245
|
-
// Tagged with the CLI
|
|
246
|
-
// charged as Claude's and not folded into the agent's own figure.
|
|
247
|
+
// Tagged with the same CLI as the associated agent.
|
|
247
248
|
onUsage: (u) => {
|
|
248
249
|
usage = { ...u, runtime: rt };
|
|
249
250
|
},
|
|
@@ -29,7 +29,7 @@ export function createWorkAgentReview({ runCheck, runPrecheck }) {
|
|
|
29
29
|
* INSIDE the place's writer lock on a path whose callers settle real work —
|
|
30
30
|
* `runAgentMerge` in particular reports a claimed merge after this returns.
|
|
31
31
|
*/
|
|
32
|
-
const runReviewEntry = async (agentId, wt, agentName, repository) => {
|
|
32
|
+
const runReviewEntry = async (agentId, wt, agentName, repository, brain) => {
|
|
33
33
|
let result = { status: 'unknown' };
|
|
34
34
|
try {
|
|
35
35
|
result = await runCheck(agentId, wt, repository) ?? result;
|
|
@@ -37,7 +37,7 @@ export function createWorkAgentReview({ runCheck, runPrecheck }) {
|
|
|
37
37
|
/* the row keeps its previous check answer, which is null the first time */
|
|
38
38
|
}
|
|
39
39
|
try {
|
|
40
|
-
await runPrecheck(agentId, wt, agentName, repository);
|
|
40
|
+
await runPrecheck(agentId, wt, agentName, repository, brain);
|
|
41
41
|
} catch {
|
|
42
42
|
/* no pre-review is posted, and absence renders nothing */
|
|
43
43
|
}
|
|
@@ -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,3 +1,6 @@
|
|
|
1
|
+
import { reportLocalCleanup } from './localCleanupReport.mjs';
|
|
2
|
+
import { createManagedLocalCleanup } from './managedLocalCleanup.mjs';
|
|
3
|
+
import { createCaptureRetention } from './referenceCaptures.mjs';
|
|
1
4
|
import { imageRevisionInstructions } from './artifactIteration.mjs';
|
|
2
5
|
import { agentChildScope } from './agentQueueContext.mjs';
|
|
3
6
|
import { measureTestFindings } from './testFindings.mjs';
|
|
@@ -33,6 +36,7 @@ import { addUsage } from './turnUsage.mjs';
|
|
|
33
36
|
import { stashCard } from './agentCards.mjs';
|
|
34
37
|
import { c } from './ui.mjs';
|
|
35
38
|
import { runTurn } from './runTurn.mjs';
|
|
39
|
+
import { liveGroups, processesSupported } from './processes.mjs';
|
|
36
40
|
import { killGroup } from './processGroup.mjs';
|
|
37
41
|
import { AGENT_TASK_SPEC } from './prompts.mjs';
|
|
38
42
|
import { agentKickoff, agentFollowupKickoff, agentTurnSystem } from './workAgentTurnShape.mjs';
|
|
@@ -42,6 +46,7 @@ import { scrub as envScrub } from './uplinkScrub.mjs';
|
|
|
42
46
|
import { canRun, runtimeById } from './runtimes.mjs';
|
|
43
47
|
import { toolEventOf, CLAUDE_TOOL_PROSE_KINDS, scrubNativeText } from './runtimeEvents.mjs';
|
|
44
48
|
import { makeTraceRelay } from './trace.mjs';
|
|
49
|
+
import { createWebsiteTrace } from './websiteTrace.mjs';
|
|
45
50
|
import { prepareCardTools } from './workAgentTools.mjs';
|
|
46
51
|
import { runTurnResumingOnce } from './resumeLost.mjs';
|
|
47
52
|
import { runAgentWithInternalWaits } from './agentInternalWait.mjs';
|
|
@@ -61,6 +66,8 @@ import { knownCodexModels } from './runtimeOfferings.mjs';
|
|
|
61
66
|
import { codexSubagentsSafe } from './runtimeCodex.mjs';
|
|
62
67
|
import { measuredRuntimeVersion } from './runtimeDetection.mjs';
|
|
63
68
|
import { createIntermediateArtifacts } from './intermediateArtifacts.mjs';
|
|
69
|
+
import { createUiReview } from './uiReview.mjs';
|
|
70
|
+
import { withUiReview, appendUiReviewEvidence } from './uiReviewTurn.mjs';
|
|
64
71
|
|
|
65
72
|
export function createAgentTurnExecution({
|
|
66
73
|
admit,
|
|
@@ -80,6 +87,7 @@ export function createAgentTurnExecution({
|
|
|
80
87
|
runReviewEntry,
|
|
81
88
|
runMergeCheck,
|
|
82
89
|
workChildren,
|
|
90
|
+
groupKillChildren,
|
|
83
91
|
agentChildren,
|
|
84
92
|
postAgentTrace,
|
|
85
93
|
postAgentActivity,
|
|
@@ -89,6 +97,7 @@ export function createAgentTurnExecution({
|
|
|
89
97
|
makeLiveMessages = createLiveMessages,
|
|
90
98
|
runTurnImpl = runTurn,
|
|
91
99
|
exportModel,
|
|
100
|
+
makeUiReview = createUiReview,
|
|
92
101
|
}) {
|
|
93
102
|
const reportDelivery = createDeliveryReporter();
|
|
94
103
|
/** How long the final flush may hold the settle. Bounded because the settle
|
|
@@ -247,7 +256,7 @@ export function createAgentTurnExecution({
|
|
|
247
256
|
return;
|
|
248
257
|
}
|
|
249
258
|
/**
|
|
250
|
-
* …AND ITS SCRATCH BESIDE IT
|
|
259
|
+
* …AND ITS SCRATCH BESIDE IT (0.115.0): a fenced shell's temp files
|
|
251
260
|
* (a here-document, `mktemp`) go under `.flowviant/tmp`, which Codex's
|
|
252
261
|
* design fence grants because `/tmp` is read-only in there
|
|
253
262
|
* (`ensureFenceScratch`). Made for every file kind — one rule, no kind
|
|
@@ -356,6 +365,9 @@ export function createAgentTurnExecution({
|
|
|
356
365
|
* CLAUDE_TOOL_PROSE_KINDS.
|
|
357
366
|
*/
|
|
358
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
|
+
});
|
|
359
371
|
|
|
360
372
|
/**
|
|
361
373
|
* WHICH BRAIN THIS CONTAINER WAS PINNED TO — the same `brainFor` the tab
|
|
@@ -389,7 +401,14 @@ export function createAgentTurnExecution({
|
|
|
389
401
|
const spawnedChildren = new Set();
|
|
390
402
|
/** The CLI, unless a person already asked to stop this turn (Interrupt
|
|
391
403
|
* now): then nothing starts — no fresh retry, no continuation. */
|
|
392
|
-
|
|
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
|
+
};
|
|
393
412
|
/**
|
|
394
413
|
* WHAT THIS TURN SPENT, AS THE CLI COUNTED IT (2026-09-19).
|
|
395
414
|
*
|
|
@@ -427,10 +446,28 @@ export function createAgentTurnExecution({
|
|
|
427
446
|
artifactsBefore.imageRevision = job.imageRevision;
|
|
428
447
|
artifactInstructions += '\n\n' + imageRevisionInstructions(job.imageRevision, job.resultInputs);
|
|
429
448
|
}
|
|
449
|
+
const cleanupPath = sessionMetaPath(wt, 'flowviant-managed-local', agentId);
|
|
450
|
+
const localCleanup = PROJECT_ID && cleanupPath ? createManagedLocalCleanup({
|
|
451
|
+
placeDir: wt, projectId: PROJECT_ID, agentId, path: cleanupPath,
|
|
452
|
+
codexHome: agentResume.codexHome,
|
|
453
|
+
outputCache: artifacts.cacheFor?.({ placeDir: wt, agentId }) ?? null,
|
|
454
|
+
}) : null;
|
|
430
455
|
let projectTools = null;
|
|
431
456
|
let orchestrator = null;
|
|
432
457
|
let intermediateDelivery = null;
|
|
433
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
|
+
}
|
|
434
471
|
if (orchestrating) {
|
|
435
472
|
orchestrator = await createOrchestrator({ turnId, runtime: rt, trace, admit,
|
|
436
473
|
assignmentContext: () => agentChildScope(job),
|
|
@@ -441,8 +478,9 @@ export function createAgentTurnExecution({
|
|
|
441
478
|
notify: message => liveInput ? run.live?.inject(message) === true : false,
|
|
442
479
|
taken: id => run.live?.onTaken(id) });
|
|
443
480
|
}
|
|
444
|
-
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 });
|
|
445
482
|
projectTools = tools.projectTools;
|
|
483
|
+
localCleanup?.begin(turnId);
|
|
446
484
|
const { toolsNote } = tools;
|
|
447
485
|
const agentTurnArgs = {
|
|
448
486
|
...((liveInput || orchestrator) ? { liveMessages: (run.live = makeLiveMessages({
|
|
@@ -458,6 +496,9 @@ export function createAgentTurnExecution({
|
|
|
458
496
|
})) } : {}),
|
|
459
497
|
// The kickoff and the system prompt, composed by the agent lane's
|
|
460
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 })) }) }),
|
|
461
502
|
researchHandoff: job.researchHandoff,
|
|
462
503
|
resultInputs: job.resultInputs,
|
|
463
504
|
intermediateFiles,
|
|
@@ -573,6 +614,7 @@ export function createAgentTurnExecution({
|
|
|
573
614
|
// the builder itself (bounded window BEFORE its caps; see
|
|
574
615
|
// toolEventOf). A tool it does not know pushes nothing.
|
|
575
616
|
onToolEvent: (name, input, meta) => {
|
|
617
|
+
if (websiteTrace(name, input, meta)) return;
|
|
576
618
|
if (meta?.agentId) {
|
|
577
619
|
if (!meta.result) {
|
|
578
620
|
const event = toolEventOf(name, input, wt, envScrub);
|
|
@@ -688,6 +730,14 @@ export function createAgentTurnExecution({
|
|
|
688
730
|
// answer is scrubbed; then it can leave the machine's secret set.
|
|
689
731
|
out = envScrub(out);
|
|
690
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
|
+
}
|
|
691
741
|
await run.live?.stop();
|
|
692
742
|
await orchestrator?.close(run.interrupted);
|
|
693
743
|
await brief?.stop();
|
|
@@ -730,6 +780,7 @@ export function createAgentTurnExecution({
|
|
|
730
780
|
// The ran marker and the thread this turn spoke under, written now
|
|
731
781
|
// that the turn is over (workAgentResume.mjs).
|
|
732
782
|
agentResume.persist();
|
|
783
|
+
localCleanup?.capture();
|
|
733
784
|
/**
|
|
734
785
|
* AN ACTION THAT CHANGES WHAT THE MACHINE WOULD MEASURE MUST CAUSE A
|
|
735
786
|
* NEW MEASUREMENT — the rule every session settle keeps, and the one
|
|
@@ -793,7 +844,21 @@ export function createAgentTurnExecution({
|
|
|
793
844
|
wt,
|
|
794
845
|
modelExports: intermediateDelivery?.modelExports,
|
|
795
846
|
});
|
|
847
|
+
appendUiReviewEvidence(body, uiReviewEvidence);
|
|
796
848
|
if (body.outcome === 'delivered') intermediateDelivery?.commit();
|
|
849
|
+
// This place's writer lock is still held. Measure the exited children
|
|
850
|
+
// and their groups before releasing any file requirements. Dirty repository files are unrelated to managed scratch eligibility.
|
|
851
|
+
if (localCleanup) {
|
|
852
|
+
const cleanupResults = localCleanup.capture(); // binds newly uploaded exact image counterparts
|
|
853
|
+
const groups = new Set([...spawnedChildren].map(ch => ch.pid).filter(pid => Number.isInteger(pid) && pid > 1));
|
|
854
|
+
const drained = !run.interrupted && processesSupported() && groups.size > 0 &&
|
|
855
|
+
[...spawnedChildren].every(ch => ch.exitCode != null || ch.signalCode != null) && liveGroups(groups).size === 0;
|
|
856
|
+
cleanupResults.push(...localCleanup.completeTurn({ drained,
|
|
857
|
+
handoffComplete: body.outcome === 'delivered', uploadsComplete: artifacts.pendingCount?.() === 0 }));
|
|
858
|
+
cleanupResults.push(...await localCleanup.reclaim({ drained }));
|
|
859
|
+
for (const result of cleanupResults) reportLocalCleanup({ ...result,
|
|
860
|
+
projectId: PROJECT_ID, agentId, placeDir: wt, place, turnId });
|
|
861
|
+
}
|
|
797
862
|
if (res) setRuntimeLimit(PROJECT_ID, rt, null);
|
|
798
863
|
if (body.outcome === 'delivered' && job.task?.taskType === 'tests' && res?.testFindings) {
|
|
799
864
|
body.testFindings = measureTestFindings(res.testFindings, repositoryRun?.sourcesNow() ?? [{ wt, baseRef: baseRef(), id: job.repos?.length === 1 ? job.repos[0] : null }], envScrub);
|
|
@@ -831,7 +896,7 @@ export function createAgentTurnExecution({
|
|
|
831
896
|
// pre-review HERE, in the worktree we are already standing in and still
|
|
832
897
|
// hold the lock on — see `runReviewEntry`. Only after the settle that may
|
|
833
898
|
// say delivered: the three `nothing` settles never opened it.
|
|
834
|
-
if (final && reply?.review === true) await runReviewEntry(agentId, wt, job.agentName);
|
|
899
|
+
if (final && reply?.review === true) await runReviewEntry(agentId, wt, job.agentName, undefined, job);
|
|
835
900
|
} finally { await repositoryRun?.tool?.close(); }
|
|
836
901
|
};
|
|
837
902
|
|
|
@@ -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,
|
|
@@ -322,7 +324,7 @@ export function createWorkAgentTurns({
|
|
|
322
324
|
const wt = typeof held.body.worktree === 'string' ? held.body.worktree : null;
|
|
323
325
|
if (reply?.review === true && isSafePathSegment(place) && wt && existsSync(wt)) {
|
|
324
326
|
await inPlace(place, place.startsWith(AGENT_PLACE_PREFIX), () =>
|
|
325
|
-
runReviewEntry(String(job.agentId || ''), wt, job.agentName)
|
|
327
|
+
runReviewEntry(String(job.agentId || ''), wt, job.agentName, undefined, job)
|
|
326
328
|
);
|
|
327
329
|
}
|
|
328
330
|
})()
|