@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.
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyAgentActivityMerge = applyAgentActivityMerge;
4
+ exports.mirrorMcpLifecycleEvent = mirrorMcpLifecycleEvent;
5
+ exports.buildMirrorPatchFromMcp = buildMirrorPatchFromMcp;
6
+ exports.mirrorMcpToolSuccess = mirrorMcpToolSuccess;
7
+ const session_1 = require("./session");
8
+ const session_state_1 = require("./session-state");
9
+ function uniquePaths(paths) {
10
+ return [...new Set(paths.map((p) => p.trim()).filter(Boolean))].sort();
11
+ }
12
+ function truncateSummary(text, max = 120) {
13
+ const trimmed = text.trim();
14
+ if (trimmed.length <= max)
15
+ return trimmed;
16
+ return `${trimmed.slice(0, max - 1)}…`;
17
+ }
18
+ function mergeClaimedPaths(existing, incoming) {
19
+ return uniquePaths([...existing, ...incoming]);
20
+ }
21
+ function effectiveClaimedPaths(state, patch) {
22
+ const base = state.claimedPaths ?? [];
23
+ const fromActivity = state.agentActivity?.claimedPaths ?? [];
24
+ const incoming = patch.claimedPaths ?? [];
25
+ return mergeClaimedPaths(mergeClaimedPaths(base, fromActivity), incoming);
26
+ }
27
+ function buildAgentActivity(state, patch, now) {
28
+ const claimedPaths = patch.claimedPaths?.length
29
+ ? mergeClaimedPaths(state.agentActivity?.claimedPaths ?? [], patch.claimedPaths)
30
+ : state.agentActivity?.claimedPaths;
31
+ const activity = {
32
+ source: "mcp",
33
+ updatedAt: now,
34
+ lastEvent: patch.event,
35
+ workSessionId: patch.workSessionId ?? state.agentActivity?.workSessionId ?? state.serverSessionId,
36
+ changeRequestId: patch.changeRequestId ?? state.agentActivity?.changeRequestId ?? state.changeRequestId,
37
+ intent: patch.intent ?? state.agentActivity?.intent ?? state.intent,
38
+ claimedPaths,
39
+ laneDomain: patch.laneDomain !== undefined ? patch.laneDomain : state.agentActivity?.laneDomain,
40
+ laneId: patch.laneId ?? state.agentActivity?.laneId,
41
+ laneType: patch.laneType ?? state.agentActivity?.laneType,
42
+ laneValue: patch.laneValue ?? state.agentActivity?.laneValue,
43
+ summary: patch.summary !== undefined ? truncateSummary(patch.summary) : state.agentActivity?.summary,
44
+ handoffId: patch.handoffId ?? state.agentActivity?.handoffId,
45
+ closedAt: patch.closeSession ? now : state.agentActivity?.closedAt,
46
+ };
47
+ return activity;
48
+ }
49
+ function createShellSession(patch, now) {
50
+ const baseline = (0, session_1.captureLocalSessionBaseline)();
51
+ const claimedPaths = uniquePaths(patch.claimedPaths ?? []);
52
+ return {
53
+ id: `local_${Date.now().toString(36)}`,
54
+ agentId: "mcp",
55
+ laneDomain: patch.laneDomain ?? null,
56
+ intent: patch.intent?.trim() || undefined,
57
+ changeRequestId: patch.changeRequestId,
58
+ claimedPaths,
59
+ status: "active",
60
+ changedFiles: [],
61
+ crossings: [],
62
+ approvals: [],
63
+ domains: [],
64
+ serverSessionId: patch.workSessionId,
65
+ createdAt: baseline.startedAt,
66
+ updatedAt: now,
67
+ startedAt: baseline.startedAt,
68
+ gitHead: baseline.gitHead,
69
+ dirtyFilesAtStart: baseline.dirtyFilesAtStart,
70
+ fingerprintsAtStart: baseline.fingerprintsAtStart,
71
+ };
72
+ }
73
+ /** Pure merge for tests — does not write disk. */
74
+ function applyAgentActivityMerge(existing, patch, now = new Date().toISOString()) {
75
+ const needsNew = !existing || existing.status === "closed" || (patch.event === "start_work_session" && existing.status !== "active");
76
+ let state;
77
+ if (needsNew && patch.event !== "close_work_session") {
78
+ state = createShellSession(patch, now);
79
+ }
80
+ else if (!existing) {
81
+ state = createShellSession(patch, now);
82
+ }
83
+ else {
84
+ state = { ...existing };
85
+ }
86
+ const agentActivity = buildAgentActivity(state, patch, now);
87
+ state.agentActivity = agentActivity;
88
+ state.updatedAt = now;
89
+ if (patch.workSessionId) {
90
+ state.serverSessionId = patch.workSessionId;
91
+ }
92
+ if (patch.changeRequestId) {
93
+ state.changeRequestId = patch.changeRequestId;
94
+ }
95
+ if (patch.intent?.trim()) {
96
+ state.intent = patch.intent.trim();
97
+ }
98
+ const mergedClaimed = effectiveClaimedPaths(state, patch);
99
+ if (mergedClaimed.length > 0) {
100
+ state.claimedPaths = mergedClaimed;
101
+ }
102
+ if (patch.laneDomain !== undefined) {
103
+ state.laneDomain = patch.laneDomain;
104
+ }
105
+ if (patch.closeSession) {
106
+ state.status = "closed";
107
+ state.closedAt = now;
108
+ }
109
+ else if (state.status === "closed" && patch.event === "start_work_session") {
110
+ state.status = "active";
111
+ state.closedAt = undefined;
112
+ }
113
+ return state;
114
+ }
115
+ /** Mirror MCP lifecycle success into `.agentbridge/session.json`. Never throws. */
116
+ function mirrorMcpLifecycleEvent(patch) {
117
+ try {
118
+ const existing = (0, session_state_1.readSessionState)();
119
+ const now = new Date().toISOString();
120
+ const next = applyAgentActivityMerge(existing, patch, now);
121
+ (0, session_state_1.writeSessionState)(next);
122
+ }
123
+ catch {
124
+ // Swallow — MCP tools must not fail on mirror errors.
125
+ }
126
+ }
127
+ function asStringArray(value) {
128
+ if (!Array.isArray(value))
129
+ return [];
130
+ return value.filter((item) => typeof item === "string");
131
+ }
132
+ function laneDomainFromArgs(args) {
133
+ const type = typeof args.type === "string" ? args.type : undefined;
134
+ const value = typeof args.value === "string" ? args.value : undefined;
135
+ if (type === "product_area" && value)
136
+ return value;
137
+ return null;
138
+ }
139
+ function buildMirrorPatchFromMcp(toolName, args, json) {
140
+ switch (toolName) {
141
+ case "start_work_session": {
142
+ const status = typeof json.status === "string" ? json.status : "";
143
+ if (status === "pending_boundary_approval" || status === "blocked")
144
+ return null;
145
+ const workSessionId = typeof json.id === "string" ? json.id : undefined;
146
+ const intent = typeof args.intent === "string" ? args.intent : undefined;
147
+ const changeRequestId = typeof args.change_request_id === "string" ? args.change_request_id : undefined;
148
+ const claimedPaths = asStringArray(args.claimed_paths);
149
+ return {
150
+ event: "start_work_session",
151
+ workSessionId,
152
+ changeRequestId,
153
+ intent,
154
+ claimedPaths,
155
+ summary: intent,
156
+ };
157
+ }
158
+ case "start_recovery_bootstrap_session": {
159
+ const workSessionId = typeof json.work_session_id === "string"
160
+ ? json.work_session_id
161
+ : typeof json.id === "string"
162
+ ? json.id
163
+ : undefined;
164
+ const intent = typeof args.intent === "string" ? args.intent : "recovery bootstrap";
165
+ const claimedPaths = asStringArray(args.claimed_paths);
166
+ return {
167
+ event: "start_work_session",
168
+ workSessionId,
169
+ intent,
170
+ claimedPaths,
171
+ summary: intent,
172
+ };
173
+ }
174
+ case "claim_lane": {
175
+ const workSessionId = typeof args.work_session_id === "string"
176
+ ? args.work_session_id
177
+ : typeof json.work_session_id === "string"
178
+ ? json.work_session_id
179
+ : undefined;
180
+ const changedFiles = asStringArray(args.changed_files);
181
+ const type = typeof args.type === "string" ? args.type : undefined;
182
+ const value = typeof args.value === "string" ? args.value : undefined;
183
+ const laneId = typeof json.id === "string" ? json.id : undefined;
184
+ return {
185
+ event: "claim_lane",
186
+ workSessionId,
187
+ claimedPaths: changedFiles,
188
+ laneDomain: laneDomainFromArgs(args),
189
+ laneId,
190
+ laneType: type,
191
+ laneValue: value,
192
+ summary: value ? `claimed ${type ?? "lane"}: ${value}` : "claimed lane",
193
+ };
194
+ }
195
+ case "release_lane":
196
+ return {
197
+ event: "release_lane",
198
+ workSessionId: typeof args.work_session_id === "string" ? args.work_session_id : undefined,
199
+ laneId: typeof args.lane_id === "string" ? args.lane_id : undefined,
200
+ summary: "released lane",
201
+ };
202
+ case "update_work_session_scope": {
203
+ const additional = asStringArray(args.additional_paths);
204
+ return {
205
+ event: "update_work_session_scope",
206
+ workSessionId: typeof args.work_session_id === "string" ? args.work_session_id : undefined,
207
+ claimedPaths: additional,
208
+ summary: additional.length > 0 ? `scope +${additional.length} path(s)` : "updated scope",
209
+ };
210
+ }
211
+ case "post_handoff": {
212
+ const handoffId = typeof json.id === "string" ? json.id : undefined;
213
+ const summary = typeof args.completed === "string"
214
+ ? args.completed
215
+ : typeof args.summary === "string"
216
+ ? args.summary
217
+ : typeof args.handoff_summary === "string"
218
+ ? args.handoff_summary
219
+ : "handoff posted";
220
+ return {
221
+ event: "post_handoff",
222
+ workSessionId: typeof args.work_session_id === "string" ? args.work_session_id : undefined,
223
+ handoffId,
224
+ summary,
225
+ };
226
+ }
227
+ case "close_work_session":
228
+ return {
229
+ event: "close_work_session",
230
+ workSessionId: typeof args.work_session_id === "string" ? args.work_session_id : undefined,
231
+ summary: typeof args.note === "string"
232
+ ? args.note
233
+ : typeof args.summary === "string"
234
+ ? args.summary
235
+ : "session closed",
236
+ closeSession: true,
237
+ };
238
+ default:
239
+ return null;
240
+ }
241
+ }
242
+ function mirrorMcpToolSuccess(toolName, args, json) {
243
+ const patch = buildMirrorPatchFromMcp(toolName, args, json);
244
+ if (!patch)
245
+ return;
246
+ mirrorMcpLifecycleEvent(patch);
247
+ }
@@ -0,0 +1,250 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveSupervisionIntent = resolveSupervisionIntent;
4
+ exports.probeCloudCapability = probeCloudCapability;
5
+ exports.runLocalSupervision = runLocalSupervision;
6
+ const promises_1 = require("node:readline/promises");
7
+ const node_process_1 = require("node:process");
8
+ const config_1 = require("./config");
9
+ const domain_resolution_1 = require("./domain-resolution");
10
+ const git_status_1 = require("./git-status");
11
+ const local_proof_1 = require("./local-proof");
12
+ const local_memory_1 = require("./local-memory");
13
+ const session_1 = require("./session");
14
+ const session_state_1 = require("./session-state");
15
+ const server_sync_1 = require("./server-sync");
16
+ const preflight_changed_files_1 = require("./preflight-changed-files");
17
+ function resolveSupervisionIntent(prompt, currentWorkFiles, existing) {
18
+ if (prompt?.trim())
19
+ return prompt.trim();
20
+ if (existing?.intent?.trim())
21
+ return existing.intent.trim();
22
+ if (currentWorkFiles.length > 0) {
23
+ const branch = (0, git_status_1.getBranchName)();
24
+ if (branch !== "unknown")
25
+ return `Work on ${branch}`;
26
+ const preview = currentWorkFiles.slice(0, 2).join(", ");
27
+ return currentWorkFiles.length > 2 ? `Changes in ${preview}...` : `Changes in ${preview}`;
28
+ }
29
+ return undefined;
30
+ }
31
+ async function probeCloudCapability() {
32
+ const cfg = (0, config_1.readConfig)();
33
+ const projectId = cfg.projectId?.trim() || process.env.AGENTBRIDGE_PROJECT_ID?.trim();
34
+ const apiKey = cfg.apiKey?.trim() || process.env.AGENTBRIDGE_API_KEY?.trim();
35
+ const apiBaseUrl = cfg.apiBaseUrl?.trim() || process.env.AGENTBRIDGE_API_BASE_URL?.trim();
36
+ const hasActiveAgent = Boolean(cfg.activeAgentId?.trim() || process.env.AGENTBRIDGE_AGENT_ID?.trim());
37
+ const hasExecutionSurface = Boolean(cfg.executionSurfaceId?.trim());
38
+ if (!projectId || !apiKey || !apiBaseUrl) {
39
+ return {
40
+ localSupervision: "ready",
41
+ cloudSync: "not_configured",
42
+ strictTrackedWork: "unavailable",
43
+ reason: "missing project credentials",
44
+ };
45
+ }
46
+ try {
47
+ await (0, server_sync_1.fetchProjectPacket)((0, config_1.contextFromConfig)());
48
+ if (!hasActiveAgent || !hasExecutionSurface) {
49
+ return {
50
+ localSupervision: "ready",
51
+ cloudSync: "incomplete",
52
+ strictTrackedWork: "unavailable",
53
+ reason: !hasExecutionSurface ? "execution surface missing" : "active agent missing",
54
+ };
55
+ }
56
+ return {
57
+ localSupervision: "ready",
58
+ cloudSync: "ready",
59
+ strictTrackedWork: "ready",
60
+ };
61
+ }
62
+ catch {
63
+ return {
64
+ localSupervision: "ready",
65
+ cloudSync: "unreachable",
66
+ strictTrackedWork: "unavailable",
67
+ reason: "could not reach project API",
68
+ };
69
+ }
70
+ }
71
+ function cloudSyncLabel(status) {
72
+ if (status === "ready")
73
+ return "configured";
74
+ if (status === "not_configured")
75
+ return "not configured";
76
+ if (status === "incomplete")
77
+ return "incomplete";
78
+ return "unreachable";
79
+ }
80
+ function memoryCloudSync(status) {
81
+ if (status === "ready")
82
+ return "synced";
83
+ if (status === "not_configured")
84
+ return "not_configured";
85
+ return "failed";
86
+ }
87
+ function persistCurrentWorkFiles(state, currentWorkFiles) {
88
+ state.changedFiles = currentWorkFiles;
89
+ state.updatedAt = new Date().toISOString();
90
+ (0, session_state_1.writeSessionState)(state);
91
+ return currentWorkFiles;
92
+ }
93
+ async function askConfirmClose() {
94
+ if (!process.stdin.isTTY)
95
+ return false;
96
+ const rl = (0, promises_1.createInterface)({ input: node_process_1.stdin, output: node_process_1.stdout });
97
+ try {
98
+ const answer = await rl.question("Close this task and write memory? [Y/n] ");
99
+ const trimmed = answer.trim().toLowerCase();
100
+ return trimmed === "" || trimmed === "y" || trimmed === "yes";
101
+ }
102
+ finally {
103
+ rl.close();
104
+ }
105
+ }
106
+ function ensureLocalSession(intent, existing) {
107
+ if ((0, session_state_1.isActiveLocalSession)(existing)) {
108
+ if (intent && existing.intent !== intent) {
109
+ existing.intent = intent;
110
+ existing.updatedAt = new Date().toISOString();
111
+ (0, session_state_1.writeSessionState)(existing);
112
+ }
113
+ return existing;
114
+ }
115
+ const cfg = (0, config_1.readConfig)();
116
+ const lane = (0, domain_resolution_1.inferLaneFromFiles)([], cfg.domains ?? []);
117
+ return (0, session_1.openLocalSession)({
118
+ agentId: cfg.activeAgentId ?? "local",
119
+ laneDomain: lane.laneDomain,
120
+ claimedPaths: [],
121
+ domains: cfg.domains ?? [],
122
+ intent,
123
+ mode: "local_supervision",
124
+ });
125
+ }
126
+ function formatTaskLine(state, prompt) {
127
+ const task = prompt?.trim() || state.intent?.trim();
128
+ return task ? `Task: ${task}` : "Task: none yet";
129
+ }
130
+ function formatStatusLine(currentWorkFiles, blockingIssue, prompt) {
131
+ if (currentWorkFiles.length === 0) {
132
+ return prompt?.trim() ? "Status: waiting for changes" : "Status: waiting for new work";
133
+ }
134
+ if (blockingIssue) {
135
+ return `Status: proof required — ${blockingIssue.whatHappened}`;
136
+ }
137
+ return `Status: ${currentWorkFiles.length} changed file(s), proof current`;
138
+ }
139
+ function renderProofGuidance(blockingIssue) {
140
+ if (!blockingIssue)
141
+ return [];
142
+ const lines = [blockingIssue.nextAction];
143
+ if (blockingIssue.errorCode === "PROOF_STALE_AFTER_CHANGE") {
144
+ lines.push("Proof is stale because files changed after verification.");
145
+ lines.push("Record fresh proof:");
146
+ lines.push("agentbridge verify -- <test command>");
147
+ }
148
+ else if (blockingIssue.errorCode === "PROOF_MISSING") {
149
+ lines.push("Proof required for files changed after this session started.");
150
+ lines.push("Record proof:");
151
+ lines.push("agentbridge verify -- <test command>");
152
+ }
153
+ return lines;
154
+ }
155
+ async function runLocalSupervision(opts = {}) {
156
+ const cfg = (0, config_1.readConfig)();
157
+ const cloud = await probeCloudCapability();
158
+ const existing = (0, session_state_1.readSessionState)();
159
+ const isNewSession = !(0, session_state_1.isActiveLocalSession)(existing);
160
+ if (!(0, session_state_1.isActiveLocalSession)(existing) && cfg.activeChangeRequestId) {
161
+ (0, config_1.updateConfig)({ activeChangeRequestId: undefined });
162
+ process.stdout.write("Note: ignoring stale previously tracked task linkage in checkpoint mode.\n");
163
+ }
164
+ const dirtyNow = (0, git_status_1.getDirtyWorkingTreeFiles)();
165
+ const promptIntent = opts.prompt?.trim() || undefined;
166
+ const preliminaryIntent = promptIntent || ((0, session_state_1.isActiveLocalSession)(existing) ? existing?.intent : undefined);
167
+ const state = ensureLocalSession(preliminaryIntent, existing);
168
+ let currentWorkFiles = (0, preflight_changed_files_1.computeCurrentWorkFiles)(state, dirtyNow);
169
+ const derivedIntent = resolveSupervisionIntent(undefined, currentWorkFiles, state);
170
+ if (!state.intent?.trim() && derivedIntent) {
171
+ state.intent = derivedIntent;
172
+ state.updatedAt = new Date().toISOString();
173
+ (0, session_state_1.writeSessionState)(state);
174
+ }
175
+ currentWorkFiles = persistCurrentWorkFiles(state, currentWorkFiles);
176
+ const proofEval = (0, local_proof_1.evaluateLocalProof)(currentWorkFiles, state.lastLocalVerificationRun);
177
+ const blockingIssue = (0, local_proof_1.buildLocalProofBlockingIssue)(proofEval);
178
+ const lines = [];
179
+ const baselineCount = state.dirtyFilesAtStart?.length ?? 0;
180
+ if (isNewSession) {
181
+ lines.push("Task checkpoint opened.");
182
+ if (baselineCount > 0) {
183
+ lines.push(`Pre-existing dirty files found: ${baselineCount}`);
184
+ lines.push("These will be treated as baseline, not current work.");
185
+ lines.push("Waiting for new changes.");
186
+ }
187
+ lines.push("");
188
+ }
189
+ lines.push(formatTaskLine(state, opts.prompt));
190
+ lines.push("Mode: task checkpoint");
191
+ lines.push(`Cloud sync: ${cloudSyncLabel(cloud.cloudSync)}${cloud.reason ? ` (${cloud.reason})` : ""}`);
192
+ lines.push(formatStatusLine(currentWorkFiles, blockingIssue, opts.prompt));
193
+ if (currentWorkFiles.length === 0) {
194
+ lines.push("No new work detected since this checkpoint opened.");
195
+ }
196
+ if (blockingIssue) {
197
+ lines.push("");
198
+ lines.push(...renderProofGuidance(blockingIssue));
199
+ }
200
+ if (opts.details) {
201
+ lines.push("");
202
+ if (baselineCount > 0) {
203
+ lines.push("Baseline files (not current work):");
204
+ for (const file of (state.dirtyFilesAtStart ?? []).slice(0, 10)) {
205
+ lines.push(`- ${file}`);
206
+ }
207
+ if (baselineCount > 10) {
208
+ lines.push(`- ... +${baselineCount - 10} more`);
209
+ }
210
+ lines.push("");
211
+ }
212
+ if (currentWorkFiles.length > 0) {
213
+ lines.push("Current work files:");
214
+ for (const file of currentWorkFiles.slice(0, 10)) {
215
+ lines.push(`- ${file}`);
216
+ }
217
+ if (currentWorkFiles.length > 10) {
218
+ lines.push(`- ... +${currentWorkFiles.length - 10} more`);
219
+ }
220
+ }
221
+ }
222
+ lines.push("");
223
+ lines.push("For live supervision while the agent codes:");
224
+ lines.push(" agentbridge watch");
225
+ lines.push("");
226
+ lines.push("For task checkpoint/close:");
227
+ lines.push(" agentbridge start");
228
+ process.stdout.write(`${lines.join("\n")}\n`);
229
+ if (!blockingIssue && currentWorkFiles.length > 0) {
230
+ const shouldClose = opts.confirmClose === true || (await askConfirmClose());
231
+ if (shouldClose) {
232
+ state.closeReason = "completed";
233
+ const closeResult = (0, session_1.closeLocalSession)(state);
234
+ if (!closeResult.ok) {
235
+ process.stdout.write(`\nCould not close: ${closeResult.reason}\n`);
236
+ return;
237
+ }
238
+ (0, local_memory_1.writeLocalMemory)(state, { cloudSync: memoryCloudSync(cloud.cloudSync) });
239
+ process.stdout.write("\n");
240
+ process.stdout.write("Task closed.\n");
241
+ process.stdout.write("Local memory written.\n");
242
+ process.stdout.write(`Cloud sync: ${cloudSyncLabel(cloud.cloudSync)}\n`);
243
+ process.stdout.write("Run `agentbridge start \"<intent>\"` for the next task checkpoint.\n");
244
+ return;
245
+ }
246
+ }
247
+ if (cloud.cloudSync !== "ready") {
248
+ process.stdout.write("\nContinuing locally.\n");
249
+ }
250
+ }