@agentbridge1/cli 0.0.4 → 0.0.6
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/dist/build-info.json +4 -4
- package/dist/commands/accept.js +4 -4
- package/dist/commands/check.js +10 -1
- package/dist/commands/connect.js +192 -13
- package/dist/commands/doctor.js +163 -29
- package/dist/commands/proof-guidance.js +30 -0
- package/dist/commands/recover.js +171 -22
- package/dist/commands/setup-mcp.js +22 -1
- package/dist/commands/start.js +57 -63
- package/dist/commands/verify.js +124 -91
- package/dist/commands/watch.js +428 -113
- package/dist/error-catalog.js +57 -16
- package/dist/gates.js +3 -3
- package/dist/git-evidence.js +2 -0
- package/dist/http.js +29 -0
- package/dist/index.js +47 -30
- package/dist/init.js +204 -30
- package/dist/local-memory.js +33 -0
- package/dist/local-proof.js +158 -0
- package/dist/local-session-mirror.js +247 -0
- package/dist/local-supervision.js +250 -0
- package/dist/mcp/agentbridge-mcp.js +22947 -0
- package/dist/mcp/agentbridge-mcp.js.map +7 -0
- package/dist/mcp-runtime.js +31 -0
- package/dist/preflight-changed-files.js +24 -17
- package/dist/proof-obligations.js +155 -0
- package/dist/recovery-reconcile.js +183 -0
- package/dist/server-sync.js +36 -0
- package/dist/session-state.js +119 -21
- package/dist/session.js +9 -2
- package/dist/supervision.js +100 -6
- package/package.json +5 -2
package/dist/commands/watch.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.getDirtyWorkingTreeFiles = void 0;
|
|
|
4
4
|
exports.renderScopedApprovalConfirmation = renderScopedApprovalConfirmation;
|
|
5
5
|
exports.renderCrossingContext = renderCrossingContext;
|
|
6
6
|
exports.shouldRenderSupervisionSummary = shouldRenderSupervisionSummary;
|
|
7
|
+
exports.finalizeWatchSupervision = finalizeWatchSupervision;
|
|
7
8
|
exports.syncAndBuildSupervisionSnapshot = syncAndBuildSupervisionSnapshot;
|
|
8
9
|
exports.renderWatchTaskScopeUsage = renderWatchTaskScopeUsage;
|
|
9
10
|
exports.renderWatchStartupHeader = renderWatchStartupHeader;
|
|
@@ -14,6 +15,7 @@ exports.isHandoffRequiredCloseError = isHandoffRequiredCloseError;
|
|
|
14
15
|
exports.renderIdleCloseFailure = renderIdleCloseFailure;
|
|
15
16
|
exports.openOrResumeServerSessionForWatch = openOrResumeServerSessionForWatch;
|
|
16
17
|
exports.normalizeDirtyWorkingTreeFiles = normalizeDirtyWorkingTreeFiles;
|
|
18
|
+
exports.startupDirtyClassification = startupDirtyClassification;
|
|
17
19
|
exports.ensureWatchRepoClean = ensureWatchRepoClean;
|
|
18
20
|
exports.runWatch = runWatch;
|
|
19
21
|
const node_fs_1 = require("node:fs");
|
|
@@ -35,9 +37,12 @@ const watch_packet_handshake_1 = require("../watch-packet-handshake");
|
|
|
35
37
|
const work_context_resolver_1 = require("../work-context-resolver");
|
|
36
38
|
const test_runner_1 = require("../test-runner");
|
|
37
39
|
const revert_crossing_1 = require("../revert-crossing");
|
|
40
|
+
const local_proof_1 = require("../local-proof");
|
|
38
41
|
const supervision_1 = require("../supervision");
|
|
42
|
+
const preflight_changed_files_1 = require("../preflight-changed-files");
|
|
39
43
|
const http_1 = require("../http");
|
|
40
44
|
const error_catalog_1 = require("../error-catalog");
|
|
45
|
+
const proof_obligations_1 = require("../proof-obligations");
|
|
41
46
|
const session_state_2 = require("../session-state");
|
|
42
47
|
const file_fingerprints_1 = require("../file-fingerprints");
|
|
43
48
|
const start_1 = require("./start");
|
|
@@ -119,19 +124,22 @@ function shouldRenderSupervisionSummary(lastSignature, snapshot) {
|
|
|
119
124
|
nextSignature,
|
|
120
125
|
};
|
|
121
126
|
}
|
|
127
|
+
function finalizeWatchSupervision(state, supervision) {
|
|
128
|
+
return (0, supervision_1.enrichSupervisionWithLocalState)(state, supervision);
|
|
129
|
+
}
|
|
122
130
|
async function syncAndBuildSupervisionSnapshot(input) {
|
|
123
131
|
const { ctx, state, cfgDomains, changedFile, outcome, changeRequestId, unresolvedProtectedCrossing, rollout, timing, } = input;
|
|
124
132
|
const contextError = (err) => (err instanceof Error ? err.message : String(err));
|
|
125
133
|
if (!state.serverSessionId || !ctx) {
|
|
126
134
|
return {
|
|
127
|
-
supervision: (0, supervision_1.fallbackSupervisionSnapshot)({
|
|
135
|
+
supervision: finalizeWatchSupervision(state, (0, supervision_1.fallbackSupervisionSnapshot)({
|
|
128
136
|
workSessionId: state.serverSessionId ?? state.id,
|
|
129
137
|
changeRequestId,
|
|
130
138
|
changedFiles: [...state.changedFiles],
|
|
131
139
|
domains: cfgDomains,
|
|
132
140
|
unresolvedProtectedCrossing,
|
|
133
141
|
blocked: state.status === "blocked",
|
|
134
|
-
}),
|
|
142
|
+
})),
|
|
135
143
|
acceptanceReport: null,
|
|
136
144
|
};
|
|
137
145
|
}
|
|
@@ -191,14 +199,14 @@ async function syncAndBuildSupervisionSnapshot(input) {
|
|
|
191
199
|
? await timing.trackAsync("acceptance_check_fetch", fetchAcceptance)
|
|
192
200
|
: await fetchAcceptance();
|
|
193
201
|
return {
|
|
194
|
-
supervision: overlayLocalChangedFilesOnSupervision((0, supervision_1.supervisionFromAcceptance)(report, cfgDomains), state.changedFiles),
|
|
202
|
+
supervision: finalizeWatchSupervision(state, overlayLocalChangedFilesOnSupervision((0, supervision_1.supervisionFromAcceptance)(report, cfgDomains), state.changedFiles)),
|
|
195
203
|
syncError,
|
|
196
204
|
acceptanceReport: report,
|
|
197
205
|
};
|
|
198
206
|
}
|
|
199
207
|
catch (err) {
|
|
200
208
|
return {
|
|
201
|
-
supervision: (0, supervision_1.fallbackSupervisionSnapshot)({
|
|
209
|
+
supervision: finalizeWatchSupervision(state, (0, supervision_1.fallbackSupervisionSnapshot)({
|
|
202
210
|
workSessionId: state.serverSessionId ?? state.id,
|
|
203
211
|
changeRequestId,
|
|
204
212
|
changedFiles: [...state.changedFiles],
|
|
@@ -206,7 +214,7 @@ async function syncAndBuildSupervisionSnapshot(input) {
|
|
|
206
214
|
unresolvedProtectedCrossing,
|
|
207
215
|
blocked: state.status === "blocked",
|
|
208
216
|
serverAcceptanceUnavailable: contextError(err),
|
|
209
|
-
}),
|
|
217
|
+
})),
|
|
210
218
|
syncError,
|
|
211
219
|
acceptanceReport: null,
|
|
212
220
|
};
|
|
@@ -218,9 +226,6 @@ function normalizeInline(value) {
|
|
|
218
226
|
function normalizeTaskSummary(value) {
|
|
219
227
|
return value.trim().replace(/\s+/g, " ").toLowerCase();
|
|
220
228
|
}
|
|
221
|
-
function isActiveLocalSession(state) {
|
|
222
|
-
return Boolean(state && state.status !== "closed" && state.id !== "none");
|
|
223
|
-
}
|
|
224
229
|
function renderWatchTaskScopeUsage() {
|
|
225
230
|
return [
|
|
226
231
|
"Usage:",
|
|
@@ -256,23 +261,172 @@ function inferTaskFromFiles(files, domains) {
|
|
|
256
261
|
}
|
|
257
262
|
return "Inferred from changed files/current work";
|
|
258
263
|
}
|
|
264
|
+
const NOISY_PATH_PREFIXES = [
|
|
265
|
+
"node_modules/",
|
|
266
|
+
"dist/",
|
|
267
|
+
"build/",
|
|
268
|
+
"coverage/",
|
|
269
|
+
".next/",
|
|
270
|
+
".turbo/",
|
|
271
|
+
".cache/",
|
|
272
|
+
".git/",
|
|
273
|
+
];
|
|
274
|
+
const HIGH_RISK_PATH_HINTS = [
|
|
275
|
+
"auth",
|
|
276
|
+
"security",
|
|
277
|
+
"payment",
|
|
278
|
+
"billing",
|
|
279
|
+
"database",
|
|
280
|
+
"db",
|
|
281
|
+
"migration",
|
|
282
|
+
"prisma",
|
|
283
|
+
"supabase",
|
|
284
|
+
"package.json",
|
|
285
|
+
"package-lock.json",
|
|
286
|
+
"pnpm-lock.yaml",
|
|
287
|
+
"yarn.lock",
|
|
288
|
+
"config",
|
|
289
|
+
"agentbridge",
|
|
290
|
+
"session",
|
|
291
|
+
"proof",
|
|
292
|
+
"protocol",
|
|
293
|
+
];
|
|
294
|
+
function isGeneratedOrNoisyPath(file) {
|
|
295
|
+
const normalized = normalizeInline(file);
|
|
296
|
+
if (NOISY_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix)))
|
|
297
|
+
return true;
|
|
298
|
+
if (normalized.endsWith(".tmp") || normalized.endsWith(".cache"))
|
|
299
|
+
return true;
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
function classifyDirtyWorkspace(input) {
|
|
303
|
+
const localChanged = new Set(input.localState?.changedFiles ?? []);
|
|
304
|
+
const mtimeByFile = new Map();
|
|
305
|
+
let newestMtime = null;
|
|
306
|
+
for (const file of input.files) {
|
|
307
|
+
const mtime = mtimeMsForFile(file);
|
|
308
|
+
mtimeByFile.set(file, mtime);
|
|
309
|
+
if (typeof mtime === "number" && (newestMtime == null || mtime > newestMtime)) {
|
|
310
|
+
newestMtime = mtime;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const classified = input.files.map((file) => {
|
|
314
|
+
const normalized = normalizeInline(file);
|
|
315
|
+
const noisy = isGeneratedOrNoisyPath(normalized);
|
|
316
|
+
const resolved = (0, domain_resolution_1.resolveDomainForFile)(normalized, input.domains);
|
|
317
|
+
const tier = resolved.tier ?? null;
|
|
318
|
+
const isHighRiskTier = tier === "tier_a" || tier === "tier_b";
|
|
319
|
+
const isHighRiskPath = HIGH_RISK_PATH_HINTS.some((hint) => normalized.includes(hint));
|
|
320
|
+
const risk = isHighRiskTier || isHighRiskPath ? "high" : "normal";
|
|
321
|
+
const reasons = [];
|
|
322
|
+
let bucket = "background_dirty";
|
|
323
|
+
if (noisy) {
|
|
324
|
+
bucket = "generated_noisy";
|
|
325
|
+
reasons.push("generated/noisy path");
|
|
326
|
+
}
|
|
327
|
+
else if (localChanged.has(normalized)) {
|
|
328
|
+
bucket = "likely_current_work";
|
|
329
|
+
reasons.push("already tracked in local session");
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
const mtime = mtimeByFile.get(normalized) ?? null;
|
|
333
|
+
const RECENT_WINDOW_MS = 15 * 60 * 1000;
|
|
334
|
+
if (newestMtime != null && mtime != null && newestMtime - mtime <= RECENT_WINDOW_MS) {
|
|
335
|
+
bucket = "likely_current_work";
|
|
336
|
+
reasons.push("recently modified");
|
|
337
|
+
}
|
|
338
|
+
else if (/^(src|app|server|modules|cli|docs|config|scripts)\//.test(normalized)) {
|
|
339
|
+
bucket = "likely_current_work";
|
|
340
|
+
reasons.push("source/docs/config path");
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
reasons.push("pre-existing or unrelated dirty background");
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (risk === "high")
|
|
347
|
+
reasons.push("high-risk path/domain");
|
|
348
|
+
return {
|
|
349
|
+
file: normalized,
|
|
350
|
+
bucket,
|
|
351
|
+
reasons,
|
|
352
|
+
domain: resolved.domain ?? null,
|
|
353
|
+
tier,
|
|
354
|
+
risk,
|
|
355
|
+
mtimeMs: mtimeByFile.get(normalized) ?? null,
|
|
356
|
+
};
|
|
357
|
+
});
|
|
358
|
+
const rankScore = (entry) => {
|
|
359
|
+
const riskScore = entry.risk === "high" ? 1000 : 0;
|
|
360
|
+
const bucketScore = entry.bucket === "likely_current_work" ? 200 : entry.bucket === "background_dirty" ? 100 : 0;
|
|
361
|
+
const mtimeScore = entry.mtimeMs ?? 0;
|
|
362
|
+
return riskScore + bucketScore + mtimeScore;
|
|
363
|
+
};
|
|
364
|
+
const sortedRelevant = classified
|
|
365
|
+
.filter((entry) => entry.bucket !== "generated_noisy")
|
|
366
|
+
.sort((a, b) => rankScore(b) - rankScore(a));
|
|
367
|
+
const topRelevant = sortedRelevant.slice(0, 10).map((entry) => entry.file);
|
|
368
|
+
const hiddenCount = Math.max(0, classified.length - topRelevant.length);
|
|
369
|
+
const highRiskTop = sortedRelevant.filter((entry) => entry.risk === "high").slice(0, 3).map((entry) => entry.file);
|
|
370
|
+
return { classified, topRelevant, hiddenCount, highRiskTop };
|
|
371
|
+
}
|
|
372
|
+
function renderDirtyWorkspaceDetails(classified) {
|
|
373
|
+
const renderBucket = (bucket, title) => {
|
|
374
|
+
const entries = classified.filter((entry) => entry.bucket === bucket);
|
|
375
|
+
if (entries.length === 0)
|
|
376
|
+
return [];
|
|
377
|
+
return [
|
|
378
|
+
`${title}:`,
|
|
379
|
+
...entries.map((entry) => `- ${entry.file} [risk=${entry.risk}; domain=${entry.domain ?? "Unknown"}; reason=${entry.reasons.join("; ")}]`),
|
|
380
|
+
"",
|
|
381
|
+
];
|
|
382
|
+
};
|
|
383
|
+
return [
|
|
384
|
+
"Details (dirty workspace classification):",
|
|
385
|
+
...renderBucket("likely_current_work", "Likely current work"),
|
|
386
|
+
...renderBucket("background_dirty", "Background dirty"),
|
|
387
|
+
...renderBucket("generated_noisy", "Generated/noisy"),
|
|
388
|
+
].join("\n");
|
|
389
|
+
}
|
|
259
390
|
function renderBrainstormingStatus() {
|
|
260
391
|
return [
|
|
261
|
-
"No coding changes detected
|
|
392
|
+
"No coding changes detected.",
|
|
262
393
|
"Mode: brainstorming/planning",
|
|
263
394
|
"Next: When your agent starts changing files, AgentBridge will review proof/scope risk.",
|
|
264
395
|
"",
|
|
265
396
|
].join("\n");
|
|
266
397
|
}
|
|
267
|
-
function renderCodingDetection(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
398
|
+
function renderCodingDetection(input) {
|
|
399
|
+
const summary = classifyDirtyWorkspace({
|
|
400
|
+
files: input.changedFiles,
|
|
401
|
+
localState: input.localState,
|
|
402
|
+
domains: input.domains,
|
|
403
|
+
});
|
|
404
|
+
const hasBaseline = Boolean(input.localState && input.localState.id !== "none" && input.localState.status !== "closed");
|
|
405
|
+
const lines = [
|
|
406
|
+
"AgentBridge is watching.",
|
|
407
|
+
"Mode:",
|
|
408
|
+
"Coding changes detected",
|
|
409
|
+
`Task: ${input.inferredTask}`,
|
|
272
410
|
"Changed files:",
|
|
273
|
-
...
|
|
274
|
-
|
|
275
|
-
|
|
411
|
+
...summary.topRelevant.map((file) => `- ${file}`),
|
|
412
|
+
...(summary.hiddenCount > 0
|
|
413
|
+
? [
|
|
414
|
+
`+ ${summary.hiddenCount} more changed files hidden.`,
|
|
415
|
+
"Run `agentbridge watch --details` to inspect everything.",
|
|
416
|
+
]
|
|
417
|
+
: []),
|
|
418
|
+
];
|
|
419
|
+
if (!hasBaseline && input.changedFiles.length > 0) {
|
|
420
|
+
lines.push("", "AgentBridge found existing changes.", "If these changes happened before AgentBridge started, verify them or clean them up before trusting the work.", "AgentBridge found an already-dirty workspace.", "Some changes may be from before AgentBridge started.", "AgentBridge will still flag proof/drift risk, but this may include older work.");
|
|
421
|
+
}
|
|
422
|
+
if (summary.highRiskTop.length > 0) {
|
|
423
|
+
lines.push("", `High-risk changes prioritized: ${summary.highRiskTop.join(", ")}`);
|
|
424
|
+
}
|
|
425
|
+
if (input.details) {
|
|
426
|
+
lines.push("", renderDirtyWorkspaceDetails(summary.classified));
|
|
427
|
+
}
|
|
428
|
+
lines.push("");
|
|
429
|
+
return lines.join("\n");
|
|
276
430
|
}
|
|
277
431
|
function detectInferredDomainDrift(files, domains) {
|
|
278
432
|
if (files.length <= 1)
|
|
@@ -330,6 +484,10 @@ function renderWatchStartupHeader(input) {
|
|
|
330
484
|
`Current status: ${input.currentStatus}`,
|
|
331
485
|
`Next guidance: ${input.nextGuidance}`,
|
|
332
486
|
"",
|
|
487
|
+
"Live supervision: keep this watch session running beside Cursor.",
|
|
488
|
+
"Task checkpoint/close: agentbridge start",
|
|
489
|
+
"Record proof: agentbridge verify -- <test command>",
|
|
490
|
+
"",
|
|
333
491
|
].join("\n");
|
|
334
492
|
}
|
|
335
493
|
function renderStartupPhase(step, status) {
|
|
@@ -358,6 +516,15 @@ async function flushStdout() {
|
|
|
358
516
|
await new Promise((resolveTick) => setImmediate(resolveTick));
|
|
359
517
|
}
|
|
360
518
|
function buildWatchBlockingIssue(report, options) {
|
|
519
|
+
const summarizePromptFiles = (files) => {
|
|
520
|
+
const unique = [...new Set(files)];
|
|
521
|
+
if (unique.length === 0)
|
|
522
|
+
return "current files";
|
|
523
|
+
const visible = unique.slice(0, 3);
|
|
524
|
+
if (unique.length <= 3)
|
|
525
|
+
return visible.join(", ");
|
|
526
|
+
return `${visible.join(", ")} (+${unique.length - visible.length} more)`;
|
|
527
|
+
};
|
|
361
528
|
if (options.strictScope && (report.out_of_scope_files ?? []).length > 0) {
|
|
362
529
|
const files = [...new Set(report.out_of_scope_files ?? [])];
|
|
363
530
|
return {
|
|
@@ -368,9 +535,9 @@ function buildWatchBlockingIssue(report, options) {
|
|
|
368
535
|
suggestedPrompt: [
|
|
369
536
|
"You changed files outside the allowed scope.",
|
|
370
537
|
"Revert them, justify why they are required and ask to expand scope, or split them into a separate task.",
|
|
371
|
-
`Out-of-scope files: ${files
|
|
538
|
+
`Out-of-scope files: ${summarizePromptFiles(files)}`,
|
|
372
539
|
].join("\n"),
|
|
373
|
-
nextAction: "Revert out-of-scope files, then
|
|
540
|
+
nextAction: "Revert out-of-scope files, then rerun: agentbridge watch",
|
|
374
541
|
};
|
|
375
542
|
}
|
|
376
543
|
if (report.decision === "stale_evidence") {
|
|
@@ -378,12 +545,12 @@ function buildWatchBlockingIssue(report, options) {
|
|
|
378
545
|
return {
|
|
379
546
|
errorCode: "PROOF_STALE_AFTER_CHANGE",
|
|
380
547
|
whatHappened: "Verification proof is stale because files changed after the last passing verify.",
|
|
381
|
-
whyItMatters: "
|
|
548
|
+
whyItMatters: "Proof must match the final edited files before you can trust the agent is done.",
|
|
382
549
|
files: staleFiles,
|
|
383
550
|
suggestedPrompt: [
|
|
384
551
|
"Your proof is stale because files changed after verification.",
|
|
385
552
|
"Rerun verification after your final edit, then rerun AgentBridge watch.",
|
|
386
|
-
`Files: ${staleFiles
|
|
553
|
+
`Files: ${summarizePromptFiles(staleFiles)}`,
|
|
387
554
|
].join("\n"),
|
|
388
555
|
nextAction: "Run a verification command for the final changed files.",
|
|
389
556
|
};
|
|
@@ -392,26 +559,30 @@ function buildWatchBlockingIssue(report, options) {
|
|
|
392
559
|
return {
|
|
393
560
|
errorCode: "VERIFICATION_FAILED",
|
|
394
561
|
whatHappened: "verification failed",
|
|
395
|
-
whyItMatters: "Failed verification cannot
|
|
562
|
+
whyItMatters: "Failed verification cannot count as proof.",
|
|
396
563
|
files: [...new Set(report.changed_files)],
|
|
397
564
|
suggestedPrompt: [
|
|
398
565
|
"The last verification command failed.",
|
|
399
|
-
"Please fix the failure and rerun verification with a passing result
|
|
566
|
+
"Please fix the failure and rerun verification with a passing result, then rerun AgentBridge watch.",
|
|
400
567
|
].join("\n"),
|
|
401
568
|
nextAction: "Fix verification failures, then rerun verification on final files.",
|
|
402
569
|
};
|
|
403
570
|
}
|
|
571
|
+
const blockingObligation = (0, proof_obligations_1.primaryBlockingObligation)(report);
|
|
572
|
+
if (blockingObligation) {
|
|
573
|
+
return (0, proof_obligations_1.buildWatchBlockingIssueFromObligation)(report, blockingObligation);
|
|
574
|
+
}
|
|
404
575
|
if (report.decision === "needs_proof") {
|
|
405
576
|
const files = [...new Set(report.changed_files)];
|
|
406
577
|
return {
|
|
407
578
|
errorCode: "PROOF_MISSING",
|
|
408
579
|
whatHappened: "No valid verification proof is attached to the current work.",
|
|
409
|
-
whyItMatters: "
|
|
580
|
+
whyItMatters: "Coding changes need AgentBridge-recorded proof before they are safe to trust.",
|
|
410
581
|
files,
|
|
411
582
|
suggestedPrompt: [
|
|
412
|
-
|
|
583
|
+
`AgentBridge found ${files.length} changed files without proof.`,
|
|
413
584
|
"Run verification for the changed files, then rerun AgentBridge watch.",
|
|
414
|
-
`Files: ${files
|
|
585
|
+
`Files: ${summarizePromptFiles(files)}`,
|
|
415
586
|
].join("\n"),
|
|
416
587
|
nextAction: "Run a verification command for the changed files.",
|
|
417
588
|
};
|
|
@@ -419,16 +590,25 @@ function buildWatchBlockingIssue(report, options) {
|
|
|
419
590
|
return null;
|
|
420
591
|
}
|
|
421
592
|
function buildFallbackWatchBlockingIssue(supervision) {
|
|
593
|
+
const summarizePromptFiles = (files) => {
|
|
594
|
+
const unique = [...new Set(files)];
|
|
595
|
+
if (unique.length === 0)
|
|
596
|
+
return "current files";
|
|
597
|
+
const visible = unique.slice(0, 3);
|
|
598
|
+
if (unique.length <= 3)
|
|
599
|
+
return visible.join(", ");
|
|
600
|
+
return `${visible.join(", ")} (+${unique.length - visible.length} more)`;
|
|
601
|
+
};
|
|
422
602
|
if (supervision.decision === "needs_proof") {
|
|
423
603
|
return {
|
|
424
604
|
errorCode: "PROOF_MISSING",
|
|
425
605
|
whatHappened: "No valid verification proof is attached to the current work.",
|
|
426
|
-
whyItMatters: "
|
|
606
|
+
whyItMatters: "Coding changes need AgentBridge-recorded proof before they are safe to trust.",
|
|
427
607
|
files: [],
|
|
428
608
|
suggestedPrompt: [
|
|
429
|
-
|
|
609
|
+
`AgentBridge found ${supervision.changedFiles.length} changed files without proof.`,
|
|
430
610
|
"Run verification for the changed files, then rerun AgentBridge watch.",
|
|
431
|
-
`Files: ${supervision.changedFiles
|
|
611
|
+
`Files: ${summarizePromptFiles(supervision.changedFiles)}`,
|
|
432
612
|
].join("\n"),
|
|
433
613
|
nextAction: "Run a verification command for the changed files.",
|
|
434
614
|
};
|
|
@@ -437,11 +617,11 @@ function buildFallbackWatchBlockingIssue(supervision) {
|
|
|
437
617
|
return {
|
|
438
618
|
errorCode: "VERIFICATION_FAILED",
|
|
439
619
|
whatHappened: "Current task run is blocked and cannot complete.",
|
|
440
|
-
whyItMatters: "Blocked
|
|
620
|
+
whyItMatters: "Blocked proof or scope issues must be resolved before the work is safe to trust.",
|
|
441
621
|
files: supervision.changedFiles,
|
|
442
622
|
suggestedPrompt: [
|
|
443
|
-
"Resolve the
|
|
444
|
-
`Files in current blocked state: ${supervision.changedFiles
|
|
623
|
+
"Resolve the verification or scope problem, then rerun AgentBridge watch.",
|
|
624
|
+
`Files in current blocked state: ${summarizePromptFiles(supervision.changedFiles)}`,
|
|
445
625
|
].join("\n"),
|
|
446
626
|
nextAction: "Resolve proof/scope problems, then rerun watch.",
|
|
447
627
|
};
|
|
@@ -449,12 +629,16 @@ function buildFallbackWatchBlockingIssue(supervision) {
|
|
|
449
629
|
return null;
|
|
450
630
|
}
|
|
451
631
|
function renderWatchBlockingIssue(issue) {
|
|
452
|
-
const
|
|
632
|
+
const files = [...new Set(issue.files)];
|
|
633
|
+
const visibleFiles = files.slice(0, 10);
|
|
634
|
+
const hiddenCount = Math.max(0, files.length - visibleFiles.length);
|
|
635
|
+
const filesLine = visibleFiles.length > 0 ? visibleFiles.join(", ") : "none listed";
|
|
453
636
|
return [
|
|
454
637
|
"AgentBridge caught an issue",
|
|
455
638
|
`What happened: ${issue.whatHappened}`,
|
|
456
639
|
`Why it matters: ${issue.whyItMatters}`,
|
|
457
640
|
`Files: ${filesLine}`,
|
|
641
|
+
...(hiddenCount > 0 ? [`+ ${hiddenCount} more files hidden (run \`agentbridge watch --details\`).`] : []),
|
|
458
642
|
`Error code: ${issue.errorCode}`,
|
|
459
643
|
"Suggested prompt to send back to agent:",
|
|
460
644
|
"```text",
|
|
@@ -662,9 +846,11 @@ function fileWithinScopedPaths(file, scopedPaths) {
|
|
|
662
846
|
function startupDirtyClassification(input) {
|
|
663
847
|
const workspaceDirtySnapshot = [...new Set(input.dirtyFiles)].sort();
|
|
664
848
|
const localChanged = new Set(input.localState?.changedFiles ?? []);
|
|
665
|
-
const localClaimedPaths =
|
|
666
|
-
|
|
667
|
-
.
|
|
849
|
+
const localClaimedPaths = input.serverReportedEmptyScope
|
|
850
|
+
? []
|
|
851
|
+
: [...new Set(input.localState?.claimedPaths ?? [])]
|
|
852
|
+
.map((claim) => claim.trim())
|
|
853
|
+
.filter(Boolean);
|
|
668
854
|
const lanePatterns = input.localState?.laneDomain != null
|
|
669
855
|
? (input.domains.find((domain) => domain.domain === input.localState?.laneDomain)?.pathPatterns ?? [])
|
|
670
856
|
: [];
|
|
@@ -672,20 +858,25 @@ function startupDirtyClassification(input) {
|
|
|
672
858
|
.map((claim) => claim.trim())
|
|
673
859
|
.filter(Boolean);
|
|
674
860
|
const scopeProven = claimedPaths.length > 0 || lanePatterns.length > 0 || (input.localState?.changedFiles.length ?? 0) > 0;
|
|
675
|
-
if (input.inferredMode) {
|
|
861
|
+
if (input.inferredMode && claimedPaths.length === 0) {
|
|
862
|
+
const postBaseline = input.localState?.startedAt != null
|
|
863
|
+
? (0, preflight_changed_files_1.computeCurrentWorkFiles)(input.localState, workspaceDirtySnapshot)
|
|
864
|
+
: workspaceDirtySnapshot;
|
|
676
865
|
return {
|
|
677
|
-
acceptanceCandidateFiles:
|
|
866
|
+
acceptanceCandidateFiles: postBaseline,
|
|
678
867
|
workspaceDirtySnapshot,
|
|
679
868
|
unscopedStartupDirty: [],
|
|
680
|
-
scopeProven:
|
|
869
|
+
scopeProven: postBaseline.length > 0,
|
|
681
870
|
};
|
|
682
871
|
}
|
|
683
872
|
const acceptanceCandidateFiles = workspaceDirtySnapshot.filter((file) => {
|
|
684
|
-
if (localChanged.has(file))
|
|
873
|
+
if (!input.inferredMode && localChanged.has(file))
|
|
685
874
|
return true;
|
|
686
875
|
if (claimedPaths.length > 0) {
|
|
687
876
|
return fileWithinScopedPaths(file, claimedPaths);
|
|
688
877
|
}
|
|
878
|
+
if (localChanged.has(file))
|
|
879
|
+
return true;
|
|
689
880
|
if (fileWithinScopedPaths(file, lanePatterns))
|
|
690
881
|
return true;
|
|
691
882
|
return false;
|
|
@@ -750,7 +941,7 @@ function buildStartupScopeDriftIssue(input) {
|
|
|
750
941
|
return {
|
|
751
942
|
errorCode: "SCOPE_DRIFT_OUT_OF_SCOPE_FILE",
|
|
752
943
|
whatHappened: "Outside promised scope.",
|
|
753
|
-
whyItMatters: "Out-of-scope edits break the declared
|
|
944
|
+
whyItMatters: "Out-of-scope edits break the declared scope boundary and are not safe to trust.",
|
|
754
945
|
files,
|
|
755
946
|
suggestedPrompt: [
|
|
756
947
|
"You changed files outside the allowed scope.",
|
|
@@ -856,7 +1047,7 @@ async function runWatchOnceFastPath(input) {
|
|
|
856
1047
|
category: "WATCH_ERROR",
|
|
857
1048
|
what: "Watch startup timed out while running the one-shot review.",
|
|
858
1049
|
why: "The server did not return the consolidated review in time.",
|
|
859
|
-
next: "Run `agentbridge doctor`, then retry `agentbridge
|
|
1050
|
+
next: "Run `agentbridge doctor`, then retry `agentbridge watch`.",
|
|
860
1051
|
suggestedPrompt: "AgentBridge could not start watching this task quickly. Run doctor, confirm project access, then retry.",
|
|
861
1052
|
})));
|
|
862
1053
|
}
|
|
@@ -872,7 +1063,7 @@ async function runWatchOnceFastPath(input) {
|
|
|
872
1063
|
// surface the structured catalog error instead of crashing with a raw throw.
|
|
873
1064
|
if ((0, http_1.isCliHttpError)(err)) {
|
|
874
1065
|
const parsed = (0, http_1.parseCliHttpErrorBody)(err);
|
|
875
|
-
const rawCode =
|
|
1066
|
+
const rawCode = (0, http_1.extractHttpErrorCode)(parsed);
|
|
876
1067
|
const catalogCode = (0, error_catalog_1.mapServerCodeToCatalog)(rawCode);
|
|
877
1068
|
const SESSION_BINDING_ERRORS = new Set([
|
|
878
1069
|
"WORK_CONTEXT_SCOPE_MISMATCH",
|
|
@@ -889,7 +1080,7 @@ async function runWatchOnceFastPath(input) {
|
|
|
889
1080
|
category: entry?.category ?? "WORK_CONTEXT_ERROR",
|
|
890
1081
|
what: entry?.what ?? err.message,
|
|
891
1082
|
why: entry?.why ?? "The server rejected the local session binding.",
|
|
892
|
-
next: entry?.next ?? 'Run `agentbridge
|
|
1083
|
+
next: entry?.next ?? 'Run `agentbridge watch --allow-dirty` to review current changes.',
|
|
893
1084
|
suggestedPrompt: "AgentBridge found a different active task/scope. Do not continue on the wrong task. Clear or resolve the current run, then start the intended task again.",
|
|
894
1085
|
});
|
|
895
1086
|
}
|
|
@@ -919,7 +1110,10 @@ async function runWatchOnceFastPath(input) {
|
|
|
919
1110
|
await flushStdout();
|
|
920
1111
|
let hadBlockingIssue = false;
|
|
921
1112
|
const acceptance = result.acceptance;
|
|
922
|
-
const
|
|
1113
|
+
const persistedState = (0, session_state_1.readSessionState)();
|
|
1114
|
+
const supervision = persistedState
|
|
1115
|
+
? finalizeWatchSupervision(persistedState, (0, supervision_1.supervisionFromAcceptance)(acceptance, cfg.domains ?? []))
|
|
1116
|
+
: (0, supervision_1.supervisionFromAcceptance)(acceptance, cfg.domains ?? []);
|
|
923
1117
|
timing.trackSync("render_output", () => {
|
|
924
1118
|
process.stdout.write(`${(0, supervision_1.renderSupervisionSummary)(supervision)}\n`);
|
|
925
1119
|
});
|
|
@@ -958,7 +1152,7 @@ async function runWatch(options = {}) {
|
|
|
958
1152
|
}
|
|
959
1153
|
const gatesState = (0, gates_1.ensureGatesFile)(repoRoot);
|
|
960
1154
|
if (gatesState.malformed) {
|
|
961
|
-
process.stdout.write("[agentbridge] WARNING: .agentbridge/gates.json is unreadable or malformed — treating all gates as
|
|
1155
|
+
process.stdout.write("[agentbridge] WARNING: .agentbridge/gates.json is unreadable or malformed — treating all gates as hard_fail.\n");
|
|
962
1156
|
process.stderr.write(`[agentbridge:audit] ${JSON.stringify({
|
|
963
1157
|
eventType: "GATES_FILE_PARSE_ERROR",
|
|
964
1158
|
message: gatesState.parseError ?? "unknown parse error",
|
|
@@ -968,7 +1162,11 @@ async function runWatch(options = {}) {
|
|
|
968
1162
|
const acceptanceRolloutOptions = acceptanceRolloutOptionsFromGates(gatesState.gates);
|
|
969
1163
|
const taskScopeInput = parseTaskScopeInput(options);
|
|
970
1164
|
const localSession = timing.trackSync("local_session_read", () => (0, session_state_1.readSessionState)());
|
|
971
|
-
const hasActiveLocalSession = isActiveLocalSession(localSession);
|
|
1165
|
+
const hasActiveLocalSession = (0, session_state_1.isActiveLocalSession)(localSession);
|
|
1166
|
+
if (hasActiveLocalSession && localSession && cfg.domains?.length) {
|
|
1167
|
+
localSession.domains = cfg.domains;
|
|
1168
|
+
(0, session_state_1.writeSessionState)(localSession);
|
|
1169
|
+
}
|
|
972
1170
|
const explicitStrictMode = Boolean(taskScopeInput.task && taskScopeInput.scope);
|
|
973
1171
|
if (hasActiveLocalSession && explicitStrictMode) {
|
|
974
1172
|
const activeLocalSession = localSession;
|
|
@@ -986,8 +1184,8 @@ async function runWatch(options = {}) {
|
|
|
986
1184
|
`Current scope: ${(activeLocalSession.claimedPaths ?? []).join(", ") || "(unknown)"}`,
|
|
987
1185
|
"",
|
|
988
1186
|
"Next action:",
|
|
989
|
-
"- Finish
|
|
990
|
-
'- Or
|
|
1187
|
+
"- Finish or clear the current session (agentbridge done or agentbridge session abandon --reason \"...\")",
|
|
1188
|
+
'- Or run watch with a new explicit task: agentbridge watch --task "<new task>" --scope "<new scope>" --allow-dirty',
|
|
991
1189
|
"",
|
|
992
1190
|
].join("\n"));
|
|
993
1191
|
process.exitCode = 1;
|
|
@@ -1012,27 +1210,27 @@ async function runWatch(options = {}) {
|
|
|
1012
1210
|
await flushStdout();
|
|
1013
1211
|
process.stdout.write(renderStartupPhase("resolving project", "starting"));
|
|
1014
1212
|
await flushStdout();
|
|
1213
|
+
const hasRecoveredDomainMap = Boolean(cfg.domains && cfg.domains.length > 0);
|
|
1015
1214
|
timing.trackSync("identity_resolution", () => {
|
|
1016
|
-
if (!cfg.activeAgentId) {
|
|
1215
|
+
if (explicitStrictMode && !cfg.activeAgentId) {
|
|
1017
1216
|
throw new errors_1.SafeCliError({
|
|
1018
1217
|
code: "IDENTITY_NO_ACTIVE_AGENT",
|
|
1019
1218
|
category: "IDENTITY_ERROR",
|
|
1020
|
-
what: "No active agent configured.",
|
|
1021
|
-
why: "
|
|
1219
|
+
what: "No active agent configured for strict watch mode.",
|
|
1220
|
+
why: "Strict watch mode needs an internal WorkIdentity selection for scoped task ownership.",
|
|
1022
1221
|
next: "Run `agentbridge identity list` then `agentbridge use <agent-id>`.",
|
|
1023
1222
|
});
|
|
1024
1223
|
}
|
|
1025
|
-
if (!cfg.domains || cfg.domains.length === 0) {
|
|
1026
|
-
throw new errors_1.SafeCliError({
|
|
1027
|
-
code: "CONFIG_NO_DOMAIN_MAP",
|
|
1028
|
-
category: "CONFIG_ERROR",
|
|
1029
|
-
what: "No recovered domain map found in .agentbridge/config.json.",
|
|
1030
|
-
why: "Watch uses domain boundaries to enforce scoped file tracking.",
|
|
1031
|
-
next: "Run `agentbridge init` to recover domains for this repo.",
|
|
1032
|
-
});
|
|
1033
|
-
}
|
|
1034
1224
|
});
|
|
1035
|
-
|
|
1225
|
+
if (!explicitStrictMode && !cfg.activeAgentId) {
|
|
1226
|
+
process.stdout.write("Startup note: no active internal agent selected. Inferred mode will continue using room-level connection and local supervision.\n");
|
|
1227
|
+
await flushStdout();
|
|
1228
|
+
}
|
|
1229
|
+
if (!hasRecoveredDomainMap) {
|
|
1230
|
+
process.stdout.write("Startup note: domain map is not fully recovered yet. AgentBridge will keep watching with best-effort drift checks.\n");
|
|
1231
|
+
await flushStdout();
|
|
1232
|
+
}
|
|
1233
|
+
let activeAgentId = cfg.activeAgentId ?? "inferred-room-connection";
|
|
1036
1234
|
if (options.changeRequestId || options.executionSurfaceId) {
|
|
1037
1235
|
(0, config_1.updateConfig)({
|
|
1038
1236
|
...(options.changeRequestId ? { activeChangeRequestId: options.changeRequestId } : {}),
|
|
@@ -1058,7 +1256,7 @@ async function runWatch(options = {}) {
|
|
|
1058
1256
|
category: "WATCH_ERROR",
|
|
1059
1257
|
what: "Watch startup timed out while resolving project context.",
|
|
1060
1258
|
why: "Project/session context could not be resolved in time.",
|
|
1061
|
-
next: "Run `agentbridge doctor`, then retry `agentbridge
|
|
1259
|
+
next: "Run `agentbridge doctor`, then retry `agentbridge watch`.",
|
|
1062
1260
|
suggestedPrompt: "AgentBridge could not start watching this task quickly. Run doctor, confirm project access, then retry.",
|
|
1063
1261
|
})));
|
|
1064
1262
|
}
|
|
@@ -1076,7 +1274,7 @@ async function runWatch(options = {}) {
|
|
|
1076
1274
|
const canUseFastPath = acceptanceRolloutOptions.rolloutProofTooWeak === "warn_only" &&
|
|
1077
1275
|
acceptanceRolloutOptions.rolloutProofNotRelevant === "warn_only" &&
|
|
1078
1276
|
acceptanceRolloutOptions.rolloutImpactCoverageGap === "warn_only";
|
|
1079
|
-
if (options.once && explicitStrictMode
|
|
1277
|
+
if (options.once && explicitStrictMode) {
|
|
1080
1278
|
process.stdout.write(renderStartupPhase("starting task", "starting"));
|
|
1081
1279
|
await flushStdout();
|
|
1082
1280
|
startingTaskAnnounced = true;
|
|
@@ -1117,7 +1315,7 @@ async function runWatch(options = {}) {
|
|
|
1117
1315
|
category: "WATCH_ERROR",
|
|
1118
1316
|
what: "Watch startup timed out while starting scoped task/session.",
|
|
1119
1317
|
why: "Internal start did not complete before timeout.",
|
|
1120
|
-
next: "Run `agentbridge
|
|
1318
|
+
next: "Run `agentbridge watch --task \"...\" --scope \"...\" --allow-dirty` for strict mode, or retry watch.",
|
|
1121
1319
|
suggestedPrompt: "AgentBridge could not start watching this task quickly. Run doctor, confirm project access, then retry.",
|
|
1122
1320
|
})));
|
|
1123
1321
|
}
|
|
@@ -1130,7 +1328,7 @@ async function runWatch(options = {}) {
|
|
|
1130
1328
|
category: "WATCH_ERROR",
|
|
1131
1329
|
what: "Watch could not create or resume the scoped task/session.",
|
|
1132
1330
|
why: reason,
|
|
1133
|
-
next: "Run `agentbridge
|
|
1331
|
+
next: "Run `agentbridge watch --task \"...\" --scope \"...\" --allow-dirty` and retry.",
|
|
1134
1332
|
});
|
|
1135
1333
|
}
|
|
1136
1334
|
process.stdout.write(renderStartupPhase("starting task", "done"));
|
|
@@ -1142,16 +1340,24 @@ async function runWatch(options = {}) {
|
|
|
1142
1340
|
const startupSession = (0, session_state_1.readSessionState)();
|
|
1143
1341
|
if (explicitStrictMode) {
|
|
1144
1342
|
if (!startupSession || startupSession.status === "closed" || startupSession.id === "none") {
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1343
|
+
if (options.once) {
|
|
1344
|
+
process.stdout.write("[startup] session bootstrap unavailable; continuing one-shot strict supervision without local session binding.\n");
|
|
1345
|
+
await flushStdout();
|
|
1346
|
+
}
|
|
1347
|
+
else {
|
|
1348
|
+
throw new errors_1.SafeCliError({
|
|
1349
|
+
code: "WATCH_STARTUP_SESSION_MISSING",
|
|
1350
|
+
category: "WATCH_ERROR",
|
|
1351
|
+
what: "Watch startup could not find an active local session.",
|
|
1352
|
+
why: "Session creation/resume did not complete.",
|
|
1353
|
+
next: "Run `agentbridge watch --task \"...\" --scope \"...\" --allow-dirty` and retry.",
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
if (startupSession?.id) {
|
|
1358
|
+
process.stdout.write(`[startup] session created/resumed: ${startupSession.id}\n`);
|
|
1359
|
+
await flushStdout();
|
|
1152
1360
|
}
|
|
1153
|
-
process.stdout.write(`[startup] session created/resumed: ${startupSession.id}\n`);
|
|
1154
|
-
await flushStdout();
|
|
1155
1361
|
}
|
|
1156
1362
|
else {
|
|
1157
1363
|
process.stdout.write("[startup] session: inferred mode (will start tracking when coding begins)\n");
|
|
@@ -1281,29 +1487,34 @@ async function runWatch(options = {}) {
|
|
|
1281
1487
|
: options.changeRequestId ?? state?.changeRequestId;
|
|
1282
1488
|
const executionSurfaceId = options.executionSurfaceId ?? cfg.executionSurfaceId;
|
|
1283
1489
|
if (changeRequestId && executionSurfaceId) {
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1490
|
+
if (activeAgentId === "inferred-room-connection") {
|
|
1491
|
+
process.stdout.write("Server session sync deferred: internal agent selection is unavailable in inferred mode.\n");
|
|
1492
|
+
}
|
|
1493
|
+
else {
|
|
1494
|
+
try {
|
|
1495
|
+
const ctx = (0, config_1.contextFromConfig)();
|
|
1496
|
+
const opened = await openOrResumeServerSessionForWatch({
|
|
1497
|
+
ctx,
|
|
1498
|
+
changeRequestId,
|
|
1499
|
+
executionSurfaceId,
|
|
1500
|
+
file,
|
|
1501
|
+
activeAgentId,
|
|
1502
|
+
inferredLaneDomain: lane.laneDomain,
|
|
1503
|
+
});
|
|
1504
|
+
if (opened) {
|
|
1505
|
+
serverSessionId = opened.workSessionId;
|
|
1506
|
+
if (opened.resumed) {
|
|
1507
|
+
process.stdout.write(`Resumed existing run: ${opened.workSessionId}\n`);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
else {
|
|
1511
|
+
process.stdout.write("Task is already running but no unique active run could be resolved; continuing local-only.\n");
|
|
1298
1512
|
}
|
|
1299
1513
|
}
|
|
1300
|
-
|
|
1301
|
-
process.stdout.write(
|
|
1514
|
+
catch (err) {
|
|
1515
|
+
process.stdout.write(`Server session open failed; continuing local-only: ${String(err)}\n`);
|
|
1302
1516
|
}
|
|
1303
1517
|
}
|
|
1304
|
-
catch (err) {
|
|
1305
|
-
process.stdout.write(`Server session open failed; continuing local-only: ${String(err)}\n`);
|
|
1306
|
-
}
|
|
1307
1518
|
}
|
|
1308
1519
|
state = (0, session_1.openLocalSession)({
|
|
1309
1520
|
agentId: activeAgentId,
|
|
@@ -1312,16 +1523,17 @@ async function runWatch(options = {}) {
|
|
|
1312
1523
|
domains: cfg.domains ?? [],
|
|
1313
1524
|
serverSessionId,
|
|
1314
1525
|
});
|
|
1315
|
-
|
|
1526
|
+
const sessionBannerLines = [
|
|
1316
1527
|
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
|
1317
1528
|
" AgentBridge session started",
|
|
1318
1529
|
"",
|
|
1319
|
-
`
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
"
|
|
1323
|
-
|
|
1324
|
-
|
|
1530
|
+
` Mode ${explicitStrictMode ? "strict/scoped" : "inferred"}`,
|
|
1531
|
+
];
|
|
1532
|
+
if (explicitStrictMode || options.details) {
|
|
1533
|
+
sessionBannerLines.push(` Allowed lane ${state.laneDomain ?? "unclassified"}`, ` Server sync ${state.serverSessionId ? "enabled" : "local-only"}`);
|
|
1534
|
+
}
|
|
1535
|
+
sessionBannerLines.push("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━", "");
|
|
1536
|
+
process.stdout.write(sessionBannerLines.join("\n"));
|
|
1325
1537
|
}
|
|
1326
1538
|
const outcome = (0, watch_core_1.applyFileChange)(state, file);
|
|
1327
1539
|
(0, session_state_1.writeSessionState)(state);
|
|
@@ -1354,20 +1566,28 @@ async function runWatch(options = {}) {
|
|
|
1354
1566
|
const nextSummary = shouldRenderSupervisionSummary(lastSupervisionSignature, supervision);
|
|
1355
1567
|
if (nextSummary.shouldRender) {
|
|
1356
1568
|
timing.trackSync("render_output", () => {
|
|
1357
|
-
process.stdout.write(`${(0, supervision_1.renderSupervisionSummary)(supervision
|
|
1569
|
+
process.stdout.write(`${(0, supervision_1.renderSupervisionSummary)(supervision, {
|
|
1570
|
+
compact: !explicitStrictMode && !options.details,
|
|
1571
|
+
})}\n`);
|
|
1358
1572
|
});
|
|
1359
1573
|
lastSupervisionSignature = nextSummary.nextSignature;
|
|
1360
1574
|
}
|
|
1575
|
+
const renderSupervisionProofIssue = explicitStrictMode || !options.once;
|
|
1361
1576
|
if (supervisionResult.acceptanceReport) {
|
|
1362
1577
|
const blockingIssue = timing.trackSync("proof_fingerprint_comparison", () => buildWatchBlockingIssue(supervisionResult.acceptanceReport, {
|
|
1363
1578
|
strictScope: explicitStrictMode,
|
|
1364
1579
|
}));
|
|
1365
1580
|
const issueSignature = blockingIssue ? JSON.stringify(blockingIssue) : null;
|
|
1366
|
-
if (blockingIssue &&
|
|
1581
|
+
if (blockingIssue &&
|
|
1582
|
+
issueSignature &&
|
|
1583
|
+
issueSignature !== lastBlockingIssueSignature &&
|
|
1584
|
+
renderSupervisionProofIssue) {
|
|
1367
1585
|
timing.trackSync("render_output", () => {
|
|
1368
1586
|
process.stdout.write(renderWatchBlockingIssue(blockingIssue));
|
|
1369
1587
|
});
|
|
1370
|
-
|
|
1588
|
+
if (explicitStrictMode) {
|
|
1589
|
+
hadBlockingIssue = true;
|
|
1590
|
+
}
|
|
1371
1591
|
lastBlockingIssueSignature = issueSignature;
|
|
1372
1592
|
}
|
|
1373
1593
|
else if (!issueSignature) {
|
|
@@ -1377,11 +1597,16 @@ async function runWatch(options = {}) {
|
|
|
1377
1597
|
else {
|
|
1378
1598
|
const blockingIssue = timing.trackSync("proof_fingerprint_comparison", () => buildFallbackWatchBlockingIssue(supervision));
|
|
1379
1599
|
const issueSignature = blockingIssue ? JSON.stringify(blockingIssue) : null;
|
|
1380
|
-
if (blockingIssue &&
|
|
1600
|
+
if (blockingIssue &&
|
|
1601
|
+
issueSignature &&
|
|
1602
|
+
issueSignature !== lastBlockingIssueSignature &&
|
|
1603
|
+
renderSupervisionProofIssue) {
|
|
1381
1604
|
timing.trackSync("render_output", () => {
|
|
1382
1605
|
process.stdout.write(renderWatchBlockingIssue(blockingIssue));
|
|
1383
1606
|
});
|
|
1384
|
-
|
|
1607
|
+
if (explicitStrictMode) {
|
|
1608
|
+
hadBlockingIssue = true;
|
|
1609
|
+
}
|
|
1385
1610
|
lastBlockingIssueSignature = issueSignature;
|
|
1386
1611
|
}
|
|
1387
1612
|
else if (!issueSignature) {
|
|
@@ -1407,6 +1632,10 @@ async function runWatch(options = {}) {
|
|
|
1407
1632
|
if (!outcome.requiresAuthorityPrompt)
|
|
1408
1633
|
return;
|
|
1409
1634
|
if (outcome.requestType === "handshake") {
|
|
1635
|
+
if (!explicitStrictMode &&
|
|
1636
|
+
(outcome.crossingTier === "tier_d" || outcome.crossingTier === "tier_c")) {
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1410
1639
|
const crossingDomain = outcome.crossingDomain;
|
|
1411
1640
|
if (crossingDomain && !domainPacketsLoaded.has(crossingDomain)) {
|
|
1412
1641
|
domainPacketsLoaded.add(crossingDomain);
|
|
@@ -1451,9 +1680,13 @@ async function runWatch(options = {}) {
|
|
|
1451
1680
|
file,
|
|
1452
1681
|
tier: outcome.crossingTier,
|
|
1453
1682
|
})}\n`);
|
|
1454
|
-
const
|
|
1683
|
+
const skipInteractivePrompt = options.daemon || (options.once && !explicitStrictMode);
|
|
1684
|
+
const response = skipInteractivePrompt
|
|
1455
1685
|
? { decision: "deny" }
|
|
1456
1686
|
: await promptDecision();
|
|
1687
|
+
if (skipInteractivePrompt && !options.daemon) {
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1457
1690
|
if (options.daemon) {
|
|
1458
1691
|
process.stdout.write(`[daemon] Domain crossing denied automatically (no interactive prompt in daemon mode). Use the dashboard to approve.\n`);
|
|
1459
1692
|
}
|
|
@@ -1585,16 +1818,29 @@ async function runWatch(options = {}) {
|
|
|
1585
1818
|
return;
|
|
1586
1819
|
}
|
|
1587
1820
|
let startupState = (0, session_state_1.readSessionState)();
|
|
1821
|
+
if (!explicitStrictMode && !(0, session_state_1.isActiveLocalSession)(startupState)) {
|
|
1822
|
+
const lane = (0, domain_resolution_1.inferLaneFromFiles)(startupDirtyFiles, cfg.domains ?? []);
|
|
1823
|
+
startupState = (0, session_1.openLocalSession)({
|
|
1824
|
+
agentId: activeAgentId,
|
|
1825
|
+
laneDomain: lane.laneDomain,
|
|
1826
|
+
claimedPaths: [...(startupState?.claimedPaths ?? [])],
|
|
1827
|
+
domains: cfg.domains ?? [],
|
|
1828
|
+
serverSessionId: startupState?.serverSessionId,
|
|
1829
|
+
changeRequestId: startupState?.changeRequestId,
|
|
1830
|
+
});
|
|
1831
|
+
(0, session_state_1.writeSessionState)(startupState);
|
|
1832
|
+
}
|
|
1588
1833
|
const changeRequestId = explicitStrictMode
|
|
1589
1834
|
? options.changeRequestId ?? cfg.activeChangeRequestId ?? null
|
|
1590
1835
|
: options.changeRequestId ?? startupState?.changeRequestId ?? null;
|
|
1591
1836
|
let startupClaimedPaths = [];
|
|
1592
1837
|
let startupServerSessionId = null;
|
|
1838
|
+
let serverReportedEmptyScope = false;
|
|
1593
1839
|
startupClaimedPaths = [...(startupState?.claimedPaths ?? [])];
|
|
1594
1840
|
if (startupState?.serverSessionId) {
|
|
1595
1841
|
startupServerSessionId = startupState.serverSessionId;
|
|
1596
1842
|
}
|
|
1597
|
-
else if (changeRequestId) {
|
|
1843
|
+
else if (changeRequestId && explicitStrictMode) {
|
|
1598
1844
|
try {
|
|
1599
1845
|
const ctx = (0, config_1.contextFromConfig)();
|
|
1600
1846
|
const resolved = await (0, work_context_resolver_1.findSingleActiveSessionForChangeRequest)(ctx, changeRequestId);
|
|
@@ -1606,11 +1852,22 @@ async function runWatch(options = {}) {
|
|
|
1606
1852
|
startupServerSessionId = null;
|
|
1607
1853
|
}
|
|
1608
1854
|
}
|
|
1609
|
-
if (startupServerSessionId) {
|
|
1855
|
+
if (startupServerSessionId && (explicitStrictMode || startupState?.serverSessionId)) {
|
|
1610
1856
|
try {
|
|
1611
1857
|
const ctx = (0, config_1.contextFromConfig)();
|
|
1612
1858
|
const report = await timing.trackAsync("acceptance_check_fetch", async () => (0, server_sync_1.fetchAcceptanceCheck)(ctx, { workSessionId: startupServerSessionId, ...acceptanceRolloutOptions }));
|
|
1613
|
-
|
|
1859
|
+
const serverClaimedPaths = report.claimed_paths ?? [];
|
|
1860
|
+
if (serverClaimedPaths.length > 0) {
|
|
1861
|
+
startupClaimedPaths = [...new Set([...startupClaimedPaths, ...serverClaimedPaths])];
|
|
1862
|
+
}
|
|
1863
|
+
else if (explicitStrictMode) {
|
|
1864
|
+
startupClaimedPaths = [...new Set(startupClaimedPaths)];
|
|
1865
|
+
}
|
|
1866
|
+
else {
|
|
1867
|
+
// Server session exists but reports no claimed scope — default mode treats all dirty files as in play.
|
|
1868
|
+
startupClaimedPaths = [];
|
|
1869
|
+
serverReportedEmptyScope = true;
|
|
1870
|
+
}
|
|
1614
1871
|
}
|
|
1615
1872
|
catch {
|
|
1616
1873
|
// Keep local claimed paths when server claimed paths are unavailable.
|
|
@@ -1622,13 +1879,20 @@ async function runWatch(options = {}) {
|
|
|
1622
1879
|
claimedPaths: startupClaimedPaths,
|
|
1623
1880
|
domains: cfg.domains ?? [],
|
|
1624
1881
|
inferredMode: !explicitStrictMode,
|
|
1882
|
+
serverReportedEmptyScope,
|
|
1625
1883
|
}));
|
|
1626
1884
|
if (startupClassification.workspaceDirtySnapshot.length === 0 && !explicitStrictMode) {
|
|
1627
1885
|
process.stdout.write(renderBrainstormingStatus());
|
|
1628
1886
|
}
|
|
1629
1887
|
else if (!explicitStrictMode) {
|
|
1630
1888
|
const inferredTask = inferTaskFromFiles(startupClassification.workspaceDirtySnapshot, cfg.domains ?? []);
|
|
1631
|
-
process.stdout.write(renderCodingDetection(
|
|
1889
|
+
process.stdout.write(renderCodingDetection({
|
|
1890
|
+
changedFiles: startupClassification.workspaceDirtySnapshot,
|
|
1891
|
+
inferredTask,
|
|
1892
|
+
localState: startupState,
|
|
1893
|
+
domains: cfg.domains ?? [],
|
|
1894
|
+
details: options.details,
|
|
1895
|
+
}));
|
|
1632
1896
|
}
|
|
1633
1897
|
else {
|
|
1634
1898
|
process.stdout.write(`Startup dirty snapshot detected ${startupClassification.workspaceDirtySnapshot.length} file(s).\n`);
|
|
@@ -1679,8 +1943,59 @@ async function runWatch(options = {}) {
|
|
|
1679
1943
|
(0, session_state_1.writeSessionState)(startupState);
|
|
1680
1944
|
process.stdout.write(`Linked local session ${startupState.id} to server session ${startupServerSessionId} before startup supervision.\n`);
|
|
1681
1945
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1946
|
+
if (options.once && !explicitStrictMode) {
|
|
1947
|
+
const postBaselineFiles = startupClassification.acceptanceCandidateFiles;
|
|
1948
|
+
const firstChanged = postBaselineFiles[0];
|
|
1949
|
+
if (firstChanged) {
|
|
1950
|
+
await onAbsolutePathChange((0, node_path_1.resolve)((0, node_process_1.cwd)(), firstChanged));
|
|
1951
|
+
}
|
|
1952
|
+
const seededState = (0, session_state_1.readSessionState)();
|
|
1953
|
+
if (seededState) {
|
|
1954
|
+
seededState.changedFiles = [...postBaselineFiles];
|
|
1955
|
+
(0, session_state_1.writeSessionState)(seededState);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
else {
|
|
1959
|
+
const startupFilesForChangeHandler = startupClassification.acceptanceCandidateFiles;
|
|
1960
|
+
for (const file of startupFilesForChangeHandler) {
|
|
1961
|
+
await onAbsolutePathChange((0, node_path_1.resolve)((0, node_process_1.cwd)(), file));
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
if (options.once && !explicitStrictMode && startupClassification.acceptanceCandidateFiles.length > 0) {
|
|
1965
|
+
const localProofEvaluation = timing.trackSync("local_proof_evaluation", () => (0, local_proof_1.evaluateLocalProof)(startupClassification.acceptanceCandidateFiles, (0, session_state_1.readSessionState)()?.lastLocalVerificationRun));
|
|
1966
|
+
const localProofIssue = (0, local_proof_1.buildLocalProofBlockingIssue)(localProofEvaluation);
|
|
1967
|
+
if (localProofIssue) {
|
|
1968
|
+
timing.trackSync("render_output", () => {
|
|
1969
|
+
process.stdout.write(renderWatchBlockingIssue(localProofIssue));
|
|
1970
|
+
});
|
|
1971
|
+
hadBlockingIssue = true;
|
|
1972
|
+
}
|
|
1973
|
+
const domainDrift = detectInferredDomainDrift(startupClassification.acceptanceCandidateFiles, cfg.domains ?? []);
|
|
1974
|
+
if (domainDrift) {
|
|
1975
|
+
timing.trackSync("render_output", () => {
|
|
1976
|
+
process.stdout.write(renderWatchBlockingIssue(domainDrift.issue));
|
|
1977
|
+
});
|
|
1978
|
+
if (domainDrift.blocking) {
|
|
1979
|
+
hadBlockingIssue = true;
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
if (options.once && !explicitStrictMode) {
|
|
1984
|
+
const finalState = (0, session_state_1.readSessionState)();
|
|
1985
|
+
if (finalState && (0, session_state_1.isActiveLocalSession)(finalState)) {
|
|
1986
|
+
const postBaseline = timing.trackSync("baseline_classification", () => (0, preflight_changed_files_1.computeCurrentWorkFiles)(finalState));
|
|
1987
|
+
const supervision = finalizeWatchSupervision(finalState, (0, supervision_1.fallbackSupervisionSnapshot)({
|
|
1988
|
+
workSessionId: finalState.serverSessionId ?? finalState.id,
|
|
1989
|
+
changeRequestId: finalState.changeRequestId ?? changeRequestId ?? null,
|
|
1990
|
+
changedFiles: postBaseline.length > 0 ? postBaseline : [...(finalState.changedFiles ?? [])],
|
|
1991
|
+
domains: cfg.domains ?? [],
|
|
1992
|
+
unresolvedProtectedCrossing: false,
|
|
1993
|
+
blocked: finalState.status === "blocked",
|
|
1994
|
+
}));
|
|
1995
|
+
timing.trackSync("render_output", () => {
|
|
1996
|
+
process.stdout.write(`${(0, supervision_1.renderSupervisionSummary)(supervision, { compact: true })}\n`);
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1684
1999
|
}
|
|
1685
2000
|
};
|
|
1686
2001
|
let stop = null;
|