@agentbridge1/cli 0.0.1
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/bin/agentbridge.js +11 -0
- package/dist/acceptance-block.js +21 -0
- package/dist/acceptance-preflight.js +91 -0
- package/dist/api-client.js +6 -0
- package/dist/authority-request.js +25 -0
- package/dist/briefing.js +26 -0
- package/dist/bug-registry.js +350 -0
- package/dist/build-info.json +6 -0
- package/dist/canonical-state.js +11 -0
- package/dist/claimed-paths.js +42 -0
- package/dist/cli-failure-log.js +34 -0
- package/dist/commands/accept.js +241 -0
- package/dist/commands/attention.js +85 -0
- package/dist/commands/autopilot.js +93 -0
- package/dist/commands/bug.js +106 -0
- package/dist/commands/check.js +283 -0
- package/dist/commands/connect.js +159 -0
- package/dist/commands/dist-freshness.js +105 -0
- package/dist/commands/doctor.js +300 -0
- package/dist/commands/done.js +292 -0
- package/dist/commands/handoff.js +189 -0
- package/dist/commands/handshake.js +78 -0
- package/dist/commands/health.js +154 -0
- package/dist/commands/identity.js +57 -0
- package/dist/commands/init.js +5 -0
- package/dist/commands/memory.js +400 -0
- package/dist/commands/next.js +21 -0
- package/dist/commands/precommit-check.js +17 -0
- package/dist/commands/recover.js +116 -0
- package/dist/commands/session.js +229 -0
- package/dist/commands/setup-mcp.js +56 -0
- package/dist/commands/start.js +626 -0
- package/dist/commands/status.js +486 -0
- package/dist/commands/use.js +13 -0
- package/dist/commands/verify.js +264 -0
- package/dist/commands/version.js +32 -0
- package/dist/commands/watch.js +1718 -0
- package/dist/config.js +55 -0
- package/dist/domain-resolution.js +63 -0
- package/dist/error-catalog.js +494 -0
- package/dist/errors.js +276 -0
- package/dist/file-fingerprints.js +45 -0
- package/dist/gates.js +200 -0
- package/dist/git-evidence.js +285 -0
- package/dist/git-status.js +81 -0
- package/dist/http.js +151 -0
- package/dist/index.js +622 -0
- package/dist/init.js +458 -0
- package/dist/memory-context-render.js +51 -0
- package/dist/operator-snapshot.js +99 -0
- package/dist/precommit.js +72 -0
- package/dist/preflight-changed-files.js +109 -0
- package/dist/proof-guidance.js +110 -0
- package/dist/redact-secrets.js +15 -0
- package/dist/revert-crossing.js +73 -0
- package/dist/server-sync.js +433 -0
- package/dist/session-state.js +138 -0
- package/dist/session.js +89 -0
- package/dist/supervision.js +212 -0
- package/dist/terminal-ui.js +18 -0
- package/dist/test-runner.js +62 -0
- package/dist/types.js +2 -0
- package/dist/verification-conditions.js +185 -0
- package/dist/watch-core.js +208 -0
- package/dist/watch-packet-handshake.js +71 -0
- package/dist/watcher.js +62 -0
- package/dist/work-context-resolver.js +412 -0
- package/dist/work-contract.js +110 -0
- package/package.json +44 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.openServerSession = openServerSession;
|
|
4
|
+
exports.createChangeRequest = createChangeRequest;
|
|
5
|
+
exports.getChangeRequest = getChangeRequest;
|
|
6
|
+
exports.updateChangeRequestStatus = updateChangeRequestStatus;
|
|
7
|
+
exports.updateChangeRequest = updateChangeRequest;
|
|
8
|
+
exports.listChangeRequests = listChangeRequests;
|
|
9
|
+
exports.listWorkIdentities = listWorkIdentities;
|
|
10
|
+
exports.fetchCallerIdentityPacket = fetchCallerIdentityPacket;
|
|
11
|
+
exports.postObservedDiff = postObservedDiff;
|
|
12
|
+
exports.postScopedApproval = postScopedApproval;
|
|
13
|
+
exports.closeServerSession = closeServerSession;
|
|
14
|
+
exports.fetchAcceptanceCheck = fetchAcceptanceCheck;
|
|
15
|
+
exports.reviewOnceForWatch = reviewOnceForWatch;
|
|
16
|
+
exports.isWatchFastPathUnavailable = isWatchFastPathUnavailable;
|
|
17
|
+
exports.fetchMemorySuggestion = fetchMemorySuggestion;
|
|
18
|
+
exports.applyMemoryCandidateRequest = applyMemoryCandidateRequest;
|
|
19
|
+
exports.rejectMemoryCandidateRequest = rejectMemoryCandidateRequest;
|
|
20
|
+
exports.deferMemoryCandidateRequest = deferMemoryCandidateRequest;
|
|
21
|
+
exports.revertMemoryCandidateRequest = revertMemoryCandidateRequest;
|
|
22
|
+
exports.intakeEscapedBugRequest = intakeEscapedBugRequest;
|
|
23
|
+
exports.resolveCurrentServerSession = resolveCurrentServerSession;
|
|
24
|
+
exports.listWorkSessions = listWorkSessions;
|
|
25
|
+
exports.getWorkSession = getWorkSession;
|
|
26
|
+
exports.cancelWorkSession = cancelWorkSession;
|
|
27
|
+
exports.postVerificationRun = postVerificationRun;
|
|
28
|
+
exports.postHandoff = postHandoff;
|
|
29
|
+
exports.acceptWorkSession = acceptWorkSession;
|
|
30
|
+
exports.createAgentHandshake = createAgentHandshake;
|
|
31
|
+
exports.fetchProjectPacket = fetchProjectPacket;
|
|
32
|
+
exports.fetchDomainPacket = fetchDomainPacket;
|
|
33
|
+
const http_1 = require("./http");
|
|
34
|
+
const http_2 = require("./http");
|
|
35
|
+
async function openServerSession(ctx, input) {
|
|
36
|
+
const baselinePayload = input.sessionBaseline
|
|
37
|
+
? {
|
|
38
|
+
git_head_at_start: input.sessionBaseline.gitHeadAtStart ?? null,
|
|
39
|
+
dirty_files_at_start: input.sessionBaseline.dirtyFilesAtStart,
|
|
40
|
+
file_fingerprints_at_start: input.sessionBaseline.dirtyFilesAtStart.map((path) => {
|
|
41
|
+
const fingerprint = input.sessionBaseline?.fingerprintsAtStart[path];
|
|
42
|
+
const looksLikeSha = typeof fingerprint === "string" && /^[a-f0-9]{64}$/i.test(fingerprint);
|
|
43
|
+
const mtime = !looksLikeSha && fingerprint ? Number.parseFloat(fingerprint) : Number.NaN;
|
|
44
|
+
return {
|
|
45
|
+
path,
|
|
46
|
+
exists: true,
|
|
47
|
+
sha256: looksLikeSha ? fingerprint : null,
|
|
48
|
+
mtime_ms: Number.isFinite(mtime) ? mtime : null,
|
|
49
|
+
size: null,
|
|
50
|
+
file_type: "file",
|
|
51
|
+
};
|
|
52
|
+
}),
|
|
53
|
+
claimed_paths: input.sessionBaseline.claimedPaths,
|
|
54
|
+
started_at: input.sessionBaseline.startedAt,
|
|
55
|
+
}
|
|
56
|
+
: undefined;
|
|
57
|
+
const res = await (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/work-sessions`, {
|
|
58
|
+
mode: "execution",
|
|
59
|
+
change_request_id: input.changeRequestId,
|
|
60
|
+
execution_surface_id: input.executionSurfaceId,
|
|
61
|
+
intent: input.intent,
|
|
62
|
+
declared_intent: input.intent,
|
|
63
|
+
task_summary: input.intent,
|
|
64
|
+
active_agent_id: input.activeAgentId,
|
|
65
|
+
inferred_lane_domain: input.inferredLaneDomain ?? null,
|
|
66
|
+
claimed_paths: input.claimedPaths,
|
|
67
|
+
session_baseline: baselinePayload,
|
|
68
|
+
});
|
|
69
|
+
const workSessionId = res.id ?? res.session_id;
|
|
70
|
+
if (!workSessionId) {
|
|
71
|
+
throw new Error("Server did not return work session id.");
|
|
72
|
+
}
|
|
73
|
+
return { workSessionId };
|
|
74
|
+
}
|
|
75
|
+
async function createChangeRequest(ctx, input) {
|
|
76
|
+
const res = await (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/change-requests`, {
|
|
77
|
+
title: input.title,
|
|
78
|
+
reason: input.reason,
|
|
79
|
+
affected_domains: input.affectedDomains ?? [],
|
|
80
|
+
risk_level: input.riskLevel ?? "medium",
|
|
81
|
+
scope: input.scope ?? [],
|
|
82
|
+
owner_work_identity_id: input.ownerWorkIdentityId,
|
|
83
|
+
});
|
|
84
|
+
return res.change_request;
|
|
85
|
+
}
|
|
86
|
+
async function getChangeRequest(ctx, changeRequestId) {
|
|
87
|
+
const res = await (0, http_1.getJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/change-requests/${encodeURIComponent(changeRequestId)}`);
|
|
88
|
+
return res.change_request;
|
|
89
|
+
}
|
|
90
|
+
async function updateChangeRequestStatus(ctx, changeRequestId, status) {
|
|
91
|
+
return updateChangeRequest(ctx, changeRequestId, { status });
|
|
92
|
+
}
|
|
93
|
+
async function updateChangeRequest(ctx, changeRequestId, patch) {
|
|
94
|
+
const res = await (0, http_1.putJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/change-requests/${encodeURIComponent(changeRequestId)}`, {
|
|
95
|
+
...(patch.status !== undefined ? { status: patch.status } : {}),
|
|
96
|
+
...(patch.ownerWorkIdentityId !== undefined
|
|
97
|
+
? { owner_work_identity_id: patch.ownerWorkIdentityId }
|
|
98
|
+
: {}),
|
|
99
|
+
...(patch.affectedDomains !== undefined
|
|
100
|
+
? { affected_domains: patch.affectedDomains }
|
|
101
|
+
: {}),
|
|
102
|
+
...(patch.scope !== undefined ? { scope: patch.scope } : {}),
|
|
103
|
+
});
|
|
104
|
+
return res.change_request;
|
|
105
|
+
}
|
|
106
|
+
async function listChangeRequests(ctx, options) {
|
|
107
|
+
const params = new URLSearchParams();
|
|
108
|
+
if (options?.status?.trim())
|
|
109
|
+
params.set("status", options.status.trim());
|
|
110
|
+
const qs = params.toString();
|
|
111
|
+
const res = await (0, http_1.getJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/change-requests${qs ? `?${qs}` : ""}`);
|
|
112
|
+
return res.change_requests ?? [];
|
|
113
|
+
}
|
|
114
|
+
async function listWorkIdentities(ctx) {
|
|
115
|
+
const res = await (0, http_1.getJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/identities`);
|
|
116
|
+
return res.identities ?? [];
|
|
117
|
+
}
|
|
118
|
+
async function fetchCallerIdentityPacket(ctx) {
|
|
119
|
+
const response = await (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/hello`, { tool_type: "cli" });
|
|
120
|
+
if (response.identity_model !== "work_identity" || typeof response.work_identity !== "object") {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const workIdentity = response.work_identity;
|
|
124
|
+
if (typeof workIdentity.id !== "string" || typeof workIdentity.name !== "string") {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const domainCandidate = typeof workIdentity.domain === "object" && workIdentity.domain !== null
|
|
128
|
+
? workIdentity.domain
|
|
129
|
+
: null;
|
|
130
|
+
const domain = domainCandidate &&
|
|
131
|
+
typeof domainCandidate.id === "string" &&
|
|
132
|
+
typeof domainCandidate.name === "string"
|
|
133
|
+
? { id: domainCandidate.id, name: domainCandidate.name }
|
|
134
|
+
: null;
|
|
135
|
+
return {
|
|
136
|
+
identity_model: "work_identity",
|
|
137
|
+
work_identity: {
|
|
138
|
+
id: workIdentity.id,
|
|
139
|
+
name: workIdentity.name,
|
|
140
|
+
domain,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async function postObservedDiff(ctx, binding, payload) {
|
|
145
|
+
await (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/work-sessions/${encodeURIComponent(binding.workSessionId)}/observed-diff`, {
|
|
146
|
+
changed_files: payload.changedFiles,
|
|
147
|
+
active_lane_domain: payload.activeLaneDomain,
|
|
148
|
+
source_head: payload.sourceHead,
|
|
149
|
+
changed_file_entries: payload.changedFileEntries?.map((entry) => ({
|
|
150
|
+
path: entry.path,
|
|
151
|
+
status: entry.status,
|
|
152
|
+
owning_domain: entry.owningDomain,
|
|
153
|
+
active_lane_domain: entry.activeLaneDomain,
|
|
154
|
+
crossing: entry.crossing,
|
|
155
|
+
severity: entry.severity,
|
|
156
|
+
protection_tier: entry.severity,
|
|
157
|
+
})),
|
|
158
|
+
boundary_crossings: payload.boundaryCrossings.map((crossing) => ({
|
|
159
|
+
file: crossing.file,
|
|
160
|
+
from_domain: crossing.fromDomain,
|
|
161
|
+
to_domain: crossing.toDomain,
|
|
162
|
+
crossing: true,
|
|
163
|
+
severity: crossing.severity,
|
|
164
|
+
protection_tier: crossing.severity,
|
|
165
|
+
})),
|
|
166
|
+
file_fingerprints: (payload.fileFingerprints ?? []).map((fingerprint) => ({
|
|
167
|
+
path: fingerprint.path,
|
|
168
|
+
exists: fingerprint.exists,
|
|
169
|
+
file_type: fingerprint.fileType,
|
|
170
|
+
size: fingerprint.size,
|
|
171
|
+
mtime_ms: fingerprint.mtimeMs,
|
|
172
|
+
sha256: fingerprint.sha256,
|
|
173
|
+
})),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
async function postScopedApproval(ctx, binding, payload) {
|
|
177
|
+
const decision = payload.decision === "approve"
|
|
178
|
+
? "approved"
|
|
179
|
+
: payload.decision === "deny"
|
|
180
|
+
? "denied"
|
|
181
|
+
: payload.decision === "limit_scope"
|
|
182
|
+
? "limited"
|
|
183
|
+
: payload.decision === "handoff"
|
|
184
|
+
? "handoff_required"
|
|
185
|
+
: "abandoned";
|
|
186
|
+
await (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/work-sessions/${encodeURIComponent(binding.workSessionId)}/approvals`, {
|
|
187
|
+
kind: "scoped_approval",
|
|
188
|
+
session_id: binding.workSessionId,
|
|
189
|
+
requesting_agent: payload.activeAgentId,
|
|
190
|
+
owner_agent: payload.ownerAgentId,
|
|
191
|
+
decision,
|
|
192
|
+
expires_on_session_close: true,
|
|
193
|
+
target_domain: payload.targetDomain,
|
|
194
|
+
approved_files: payload.approvedFiles,
|
|
195
|
+
limit_mode: payload.limitMode,
|
|
196
|
+
reason: payload.reason,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
async function closeServerSession(ctx, binding, payload) {
|
|
200
|
+
await (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/work-sessions/${encodeURIComponent(binding.workSessionId)}/close`, {
|
|
201
|
+
close_outcome: payload.closeOutcome,
|
|
202
|
+
close_reason: payload.closeReason ?? payload.closeOutcome,
|
|
203
|
+
note: payload.note,
|
|
204
|
+
target_agent_id: payload.targetAgentId,
|
|
205
|
+
target_domain: payload.targetDomain,
|
|
206
|
+
files_involved: payload.filesInvolved,
|
|
207
|
+
changed_files: payload.changedFiles,
|
|
208
|
+
domain_breakdown: payload.domainBreakdown,
|
|
209
|
+
crossings: payload.crossings?.map((crossing) => ({
|
|
210
|
+
file: crossing.file,
|
|
211
|
+
domain: crossing.domain,
|
|
212
|
+
status: crossing.status,
|
|
213
|
+
tier: crossing.tier,
|
|
214
|
+
})),
|
|
215
|
+
scoped_approvals: payload.scopedApprovals,
|
|
216
|
+
test_summary: payload.testSummary,
|
|
217
|
+
implementation_handoff: payload.implementationHandoff,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async function fetchAcceptanceCheck(ctx, options) {
|
|
221
|
+
const rolloutParams = new URLSearchParams();
|
|
222
|
+
if (options?.rolloutProofTooWeak) {
|
|
223
|
+
rolloutParams.set("rollout_proof_too_weak", options.rolloutProofTooWeak);
|
|
224
|
+
}
|
|
225
|
+
if (options?.rolloutProofNotRelevant) {
|
|
226
|
+
rolloutParams.set("rollout_proof_not_relevant", options.rolloutProofNotRelevant);
|
|
227
|
+
}
|
|
228
|
+
if (options?.rolloutImpactCoverageGap) {
|
|
229
|
+
rolloutParams.set("rollout_impact_coverage_gap", options.rolloutImpactCoverageGap);
|
|
230
|
+
}
|
|
231
|
+
const rolloutSuffix = rolloutParams.toString();
|
|
232
|
+
if (options?.workSessionId) {
|
|
233
|
+
const qs = rolloutSuffix ? `?${rolloutSuffix}` : "";
|
|
234
|
+
return (0, http_1.getJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/work-sessions/${encodeURIComponent(options.workSessionId)}/acceptance-check${qs}`);
|
|
235
|
+
}
|
|
236
|
+
const queryParams = new URLSearchParams(rolloutParams);
|
|
237
|
+
if (options?.changeRequestId && options.changeRequestId.trim().length > 0) {
|
|
238
|
+
queryParams.set("change_request_id", options.changeRequestId);
|
|
239
|
+
}
|
|
240
|
+
const query = queryParams.toString();
|
|
241
|
+
return (0, http_1.getJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/work-sessions/current/acceptance-check${query ? `?${query}` : ""}`);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* One-shot Agent Watch review fast path. Collapses cold-create / resume +
|
|
245
|
+
* observed-diff sync + acceptance check into a single bounded server call.
|
|
246
|
+
* Throws CliHttpError(404) when the server build predates this endpoint;
|
|
247
|
+
* callers should treat that as "fall back to the legacy multi-call flow".
|
|
248
|
+
*/
|
|
249
|
+
async function reviewOnceForWatch(ctx, input) {
|
|
250
|
+
return (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/watch/review-once`, {
|
|
251
|
+
task_summary: input.taskSummary,
|
|
252
|
+
scope: input.scope,
|
|
253
|
+
domain: input.domain ?? null,
|
|
254
|
+
local_session_id: input.localSessionId ?? null,
|
|
255
|
+
local_change_request_id: input.localChangeRequestId ?? null,
|
|
256
|
+
changed_files: input.changedFiles,
|
|
257
|
+
scoped_files: input.scopedFiles,
|
|
258
|
+
out_of_scope_files: input.outOfScopeFiles,
|
|
259
|
+
file_fingerprints: (input.fileFingerprints ?? []).map((fingerprint) => ({
|
|
260
|
+
path: fingerprint.path,
|
|
261
|
+
exists: fingerprint.exists,
|
|
262
|
+
file_type: fingerprint.fileType,
|
|
263
|
+
size: fingerprint.size,
|
|
264
|
+
mtime_ms: fingerprint.mtimeMs,
|
|
265
|
+
sha256: fingerprint.sha256,
|
|
266
|
+
})),
|
|
267
|
+
source_head: input.sourceHead ?? null,
|
|
268
|
+
allow_dirty: input.allowDirty,
|
|
269
|
+
once: true,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* True when an error means the fast review-once endpoint is unavailable on the
|
|
274
|
+
* connected server (older build), so the caller should fall back to the legacy
|
|
275
|
+
* start → observed-diff → acceptance-check flow.
|
|
276
|
+
*/
|
|
277
|
+
function isWatchFastPathUnavailable(err) {
|
|
278
|
+
return err instanceof http_2.CliHttpError && (err.status === 404 || err.status === 405);
|
|
279
|
+
}
|
|
280
|
+
async function fetchMemorySuggestion(ctx, options) {
|
|
281
|
+
const params = new URLSearchParams();
|
|
282
|
+
if (options?.workSessionId?.trim()) {
|
|
283
|
+
params.set("work_session_id", options.workSessionId.trim());
|
|
284
|
+
}
|
|
285
|
+
if (options?.changeRequestId?.trim()) {
|
|
286
|
+
params.set("change_request_id", options.changeRequestId.trim());
|
|
287
|
+
}
|
|
288
|
+
if (options?.domain?.trim()) {
|
|
289
|
+
params.set("domain", options.domain.trim());
|
|
290
|
+
}
|
|
291
|
+
const qs = params.toString();
|
|
292
|
+
return (0, http_1.getJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/memory/suggest${qs ? `?${qs}` : ""}`);
|
|
293
|
+
}
|
|
294
|
+
async function applyMemoryCandidateRequest(ctx, input) {
|
|
295
|
+
return (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/memory/candidates/apply`, {
|
|
296
|
+
scope: input.scope,
|
|
297
|
+
domain: input.domain,
|
|
298
|
+
items: input.items,
|
|
299
|
+
reviewer_note: input.reviewerNote,
|
|
300
|
+
candidate: input.candidate,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
async function rejectMemoryCandidateRequest(ctx, input) {
|
|
304
|
+
return (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/memory/candidates/reject`, {
|
|
305
|
+
reviewer_note: input.reviewerNote,
|
|
306
|
+
candidate: input.candidate,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
async function deferMemoryCandidateRequest(ctx, input) {
|
|
310
|
+
return (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/memory/candidates/defer`, {
|
|
311
|
+
reviewer_note: input.reviewerNote,
|
|
312
|
+
candidate: input.candidate,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
async function revertMemoryCandidateRequest(ctx, input) {
|
|
316
|
+
return (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/memory/candidates/revert`, {
|
|
317
|
+
approval_audit_id: input.approvalAuditId,
|
|
318
|
+
reviewer_note: input.reviewerNote,
|
|
319
|
+
force: input.force,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
async function intakeEscapedBugRequest(ctx, finding) {
|
|
323
|
+
return (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/memory/escaped-bug`, finding);
|
|
324
|
+
}
|
|
325
|
+
function isNoActiveSessionError(err) {
|
|
326
|
+
if (!(err instanceof http_2.CliHttpError) || err.status !== 404)
|
|
327
|
+
return false;
|
|
328
|
+
try {
|
|
329
|
+
const parsed = JSON.parse(err.body);
|
|
330
|
+
return parsed.error === "active_work_session_not_found" || parsed.code === "active_work_session_not_found";
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* @deprecated Prefer resolveWorkContext — never resolves cross-CR sessions without explicit CR.
|
|
338
|
+
*/
|
|
339
|
+
async function resolveCurrentServerSession(ctx, options) {
|
|
340
|
+
if (!options?.changeRequestId?.trim()) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
const report = await fetchAcceptanceCheck(ctx, {
|
|
345
|
+
changeRequestId: options.changeRequestId,
|
|
346
|
+
});
|
|
347
|
+
return { workSessionId: report.work_session_id };
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
if (isNoActiveSessionError(err))
|
|
351
|
+
return null;
|
|
352
|
+
throw err;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async function listWorkSessions(ctx, options) {
|
|
356
|
+
const params = new URLSearchParams();
|
|
357
|
+
if (options?.status)
|
|
358
|
+
params.set("status", options.status);
|
|
359
|
+
if (options?.crId?.trim())
|
|
360
|
+
params.set("cr_id", options.crId.trim());
|
|
361
|
+
const qs = params.toString();
|
|
362
|
+
const res = await (0, http_1.getJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/work-sessions${qs ? `?${qs}` : ""}`);
|
|
363
|
+
return res.sessions ?? [];
|
|
364
|
+
}
|
|
365
|
+
async function getWorkSession(ctx, workSessionId) {
|
|
366
|
+
return (0, http_1.getJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/work-sessions/${encodeURIComponent(workSessionId)}`);
|
|
367
|
+
}
|
|
368
|
+
async function cancelWorkSession(ctx, workSessionId, reason) {
|
|
369
|
+
return (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/work-sessions/${encodeURIComponent(workSessionId)}/cancel`, { reason });
|
|
370
|
+
}
|
|
371
|
+
async function postVerificationRun(ctx, binding, payload) {
|
|
372
|
+
await (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/work-sessions/${encodeURIComponent(binding.workSessionId)}/verification-runs`, {
|
|
373
|
+
command: payload.command,
|
|
374
|
+
started_at: payload.startedAt,
|
|
375
|
+
finished_at: payload.finishedAt,
|
|
376
|
+
exit_code: payload.exitCode,
|
|
377
|
+
stdout_excerpt: payload.stdoutExcerpt,
|
|
378
|
+
stderr_excerpt: payload.stderrExcerpt,
|
|
379
|
+
git_head: payload.gitHead,
|
|
380
|
+
changed_files_snapshot: payload.changedFilesSnapshot,
|
|
381
|
+
proof_scope_files: payload.proofScopeFiles,
|
|
382
|
+
proof_scope_source: payload.proofScopeSource,
|
|
383
|
+
repo_dirty_snapshot: payload.repoDirtySnapshot,
|
|
384
|
+
proof_scope_fingerprints: (payload.proofScopeFingerprints ?? []).map((fingerprint) => ({
|
|
385
|
+
path: fingerprint.path,
|
|
386
|
+
exists: fingerprint.exists,
|
|
387
|
+
file_type: fingerprint.fileType,
|
|
388
|
+
size: fingerprint.size,
|
|
389
|
+
mtime_ms: fingerprint.mtimeMs,
|
|
390
|
+
sha256: fingerprint.sha256,
|
|
391
|
+
})),
|
|
392
|
+
status: payload.status,
|
|
393
|
+
...(payload.conditions ? { conditions: payload.conditions } : {}),
|
|
394
|
+
...(payload.confidence ? { confidence: payload.confidence } : {}),
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
async function postHandoff(ctx, payload) {
|
|
398
|
+
return (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/handoffs`, {
|
|
399
|
+
completed: payload.summary,
|
|
400
|
+
changed_files: payload.changedFiles,
|
|
401
|
+
commands_run: payload.commandsRun,
|
|
402
|
+
domains_touched: payload.domainsTouched,
|
|
403
|
+
review_required: payload.reviewRequired,
|
|
404
|
+
risks_remaining: payload.risksRemaining ?? null,
|
|
405
|
+
next_cr_recommended: payload.nextCrRecommended ?? null,
|
|
406
|
+
do_not_touch_notes: payload.doNotTouchNotes ?? null,
|
|
407
|
+
work_session_id: payload.workSessionId,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
async function acceptWorkSession(ctx, binding) {
|
|
411
|
+
return (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/work-sessions/${encodeURIComponent(binding.workSessionId)}/accept`, {});
|
|
412
|
+
}
|
|
413
|
+
async function createAgentHandshake(ctx, input) {
|
|
414
|
+
return (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/handshakes`, {
|
|
415
|
+
change_request_id: input.changeRequestId,
|
|
416
|
+
work_session_id: input.workSessionId,
|
|
417
|
+
requested_domain: input.requestedDomain,
|
|
418
|
+
requested_action: input.requestedAction,
|
|
419
|
+
reason: input.reason,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
async function fetchProjectPacket(ctx) {
|
|
423
|
+
return (0, http_1.getJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/packet`);
|
|
424
|
+
}
|
|
425
|
+
async function fetchDomainPacket(ctx, payload) {
|
|
426
|
+
return (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/packet/domain`, {
|
|
427
|
+
domain_name: payload.domainName,
|
|
428
|
+
domains: payload.domains,
|
|
429
|
+
intent: payload.intent,
|
|
430
|
+
claimed_paths: payload.claimedPaths,
|
|
431
|
+
limit: payload.limit,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readSessionState = readSessionState;
|
|
4
|
+
exports.writeSessionState = writeSessionState;
|
|
5
|
+
exports.archiveClosedSession = archiveClosedSession;
|
|
6
|
+
exports.archiveHandoff = archiveHandoff;
|
|
7
|
+
exports.readRecentHandoffs = readRecentHandoffs;
|
|
8
|
+
exports.clearSessionState = clearSessionState;
|
|
9
|
+
const node_fs_1 = require("node:fs");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
const SESSION_PATH = (0, node_path_1.resolve)(process.cwd(), ".agentbridge", "session.json");
|
|
12
|
+
const SESSIONS_DIR = (0, node_path_1.resolve)(process.cwd(), ".agentbridge", "sessions");
|
|
13
|
+
const HANDOFFS_DIR = (0, node_path_1.resolve)(process.cwd(), ".agentbridge", "handoffs");
|
|
14
|
+
const SESSION_DIR = (0, node_path_1.resolve)(process.cwd(), ".agentbridge");
|
|
15
|
+
const SESSION_BACKUP_PATH = (0, node_path_1.resolve)(SESSION_DIR, "session.json.bak");
|
|
16
|
+
function isRecord(value) {
|
|
17
|
+
return typeof value === "object" && value !== null;
|
|
18
|
+
}
|
|
19
|
+
function isStringArray(value) {
|
|
20
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
21
|
+
}
|
|
22
|
+
function isDomainOwnershipArray(value) {
|
|
23
|
+
return Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
function isBoundaryCrossingArray(value) {
|
|
26
|
+
return Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
function isScopedApprovalArray(value) {
|
|
29
|
+
return Array.isArray(value);
|
|
30
|
+
}
|
|
31
|
+
function isLocalSessionState(value) {
|
|
32
|
+
if (!isRecord(value))
|
|
33
|
+
return false;
|
|
34
|
+
if (typeof value.id !== "string" || value.id.length === 0)
|
|
35
|
+
return false;
|
|
36
|
+
if (typeof value.agentId !== "string" || value.agentId.length === 0)
|
|
37
|
+
return false;
|
|
38
|
+
if (value.laneDomain !== null && typeof value.laneDomain !== "string")
|
|
39
|
+
return false;
|
|
40
|
+
if (!["active", "blocked", "closed"].includes(String(value.status)))
|
|
41
|
+
return false;
|
|
42
|
+
if (!isStringArray(value.changedFiles))
|
|
43
|
+
return false;
|
|
44
|
+
if (!isBoundaryCrossingArray(value.crossings))
|
|
45
|
+
return false;
|
|
46
|
+
if (!isScopedApprovalArray(value.approvals))
|
|
47
|
+
return false;
|
|
48
|
+
if (!isDomainOwnershipArray(value.domains))
|
|
49
|
+
return false;
|
|
50
|
+
if (typeof value.createdAt !== "string" || value.createdAt.length === 0)
|
|
51
|
+
return false;
|
|
52
|
+
if (value.claimedPaths !== undefined && !isStringArray(value.claimedPaths))
|
|
53
|
+
return false;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
function readJsonFile(path) {
|
|
57
|
+
return JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
|
|
58
|
+
}
|
|
59
|
+
function quarantineInvalidSessionFile() {
|
|
60
|
+
if (!(0, node_fs_1.existsSync)(SESSION_PATH))
|
|
61
|
+
return;
|
|
62
|
+
const quarantinedPath = (0, node_path_1.resolve)(SESSION_DIR, `session.invalid-${new Date().toISOString().replace(/[:.]/g, "-")}.json`);
|
|
63
|
+
(0, node_fs_1.renameSync)(SESSION_PATH, quarantinedPath);
|
|
64
|
+
}
|
|
65
|
+
function readSessionState() {
|
|
66
|
+
if (!(0, node_fs_1.existsSync)(SESSION_PATH)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const parsed = readJsonFile(SESSION_PATH);
|
|
71
|
+
if (!isLocalSessionState(parsed)) {
|
|
72
|
+
quarantineInvalidSessionFile();
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return parsed;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
quarantineInvalidSessionFile();
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function writeSessionState(state) {
|
|
83
|
+
if (!isLocalSessionState(state)) {
|
|
84
|
+
throw new Error("Refusing to write invalid session state.");
|
|
85
|
+
}
|
|
86
|
+
(0, node_fs_1.mkdirSync)(SESSION_DIR, { recursive: true });
|
|
87
|
+
const tempPath = (0, node_path_1.resolve)(SESSION_DIR, `session.json.tmp-${process.pid.toString()}-${Date.now().toString(36)}`);
|
|
88
|
+
const serialized = `${JSON.stringify(state, null, 2)}\n`;
|
|
89
|
+
(0, node_fs_1.writeFileSync)(tempPath, serialized, "utf8");
|
|
90
|
+
const tempParsed = readJsonFile(tempPath);
|
|
91
|
+
if (!isLocalSessionState(tempParsed)) {
|
|
92
|
+
(0, node_fs_1.rmSync)(tempPath, { force: true });
|
|
93
|
+
throw new Error("Refusing to persist invalid session state payload.");
|
|
94
|
+
}
|
|
95
|
+
if ((0, node_fs_1.existsSync)(SESSION_PATH)) {
|
|
96
|
+
(0, node_fs_1.renameSync)(SESSION_PATH, SESSION_BACKUP_PATH);
|
|
97
|
+
}
|
|
98
|
+
(0, node_fs_1.renameSync)(tempPath, SESSION_PATH);
|
|
99
|
+
}
|
|
100
|
+
function timestampPrefix(iso) {
|
|
101
|
+
return iso.replace(/[:.]/g, "-");
|
|
102
|
+
}
|
|
103
|
+
function writeArchive(dir, state) {
|
|
104
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
105
|
+
const at = state.closeReason ? new Date().toISOString() : state.createdAt;
|
|
106
|
+
const fileName = `${timestampPrefix(at)}-${state.id}.json`;
|
|
107
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(dir, fileName), `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
108
|
+
}
|
|
109
|
+
function archiveClosedSession(state) {
|
|
110
|
+
writeArchive(SESSIONS_DIR, state);
|
|
111
|
+
}
|
|
112
|
+
function archiveHandoff(state) {
|
|
113
|
+
writeArchive(HANDOFFS_DIR, state);
|
|
114
|
+
}
|
|
115
|
+
function readRecentHandoffs(limit = 1) {
|
|
116
|
+
if (!(0, node_fs_1.existsSync)(HANDOFFS_DIR)) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const files = (0, node_fs_1.readdirSync)(HANDOFFS_DIR)
|
|
120
|
+
.filter((file) => file.endsWith(".json"))
|
|
121
|
+
.sort((a, b) => b.localeCompare(a))
|
|
122
|
+
.slice(0, Math.max(0, limit));
|
|
123
|
+
return files.map((file) => JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.resolve)(HANDOFFS_DIR, file), "utf8")));
|
|
124
|
+
}
|
|
125
|
+
function clearSessionState() {
|
|
126
|
+
writeSessionState({
|
|
127
|
+
id: "none",
|
|
128
|
+
agentId: "none",
|
|
129
|
+
laneDomain: null,
|
|
130
|
+
status: "closed",
|
|
131
|
+
closeReason: "completed",
|
|
132
|
+
changedFiles: [],
|
|
133
|
+
crossings: [],
|
|
134
|
+
approvals: [],
|
|
135
|
+
domains: [],
|
|
136
|
+
createdAt: new Date(0).toISOString(),
|
|
137
|
+
});
|
|
138
|
+
}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.openExecutionSession = openExecutionSession;
|
|
4
|
+
exports.captureLocalSessionBaseline = captureLocalSessionBaseline;
|
|
5
|
+
exports.openLocalSession = openLocalSession;
|
|
6
|
+
exports.closeLocalSession = closeLocalSession;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const http_1 = require("./http");
|
|
9
|
+
const session_state_1 = require("./session-state");
|
|
10
|
+
const watch_core_1 = require("./watch-core");
|
|
11
|
+
const file_fingerprints_1 = require("./file-fingerprints");
|
|
12
|
+
const git_status_1 = require("./git-status");
|
|
13
|
+
async function openExecutionSession(ctx, input) {
|
|
14
|
+
return (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/work-sessions`, {
|
|
15
|
+
mode: "execution",
|
|
16
|
+
change_request_id: input.changeRequestId,
|
|
17
|
+
execution_surface_id: input.executionSurfaceId,
|
|
18
|
+
intent: input.intent,
|
|
19
|
+
claimed_paths: input.claimedPaths,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function captureGitDirtyFiles() {
|
|
23
|
+
return (0, git_status_1.repoDirtySnapshotFromGitStatus)();
|
|
24
|
+
}
|
|
25
|
+
function captureGitHead() {
|
|
26
|
+
try {
|
|
27
|
+
return (0, node_child_process_1.execSync)("git rev-parse HEAD", {
|
|
28
|
+
encoding: "utf8",
|
|
29
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
30
|
+
}).trim();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function captureLocalSessionBaseline() {
|
|
37
|
+
const startedAt = new Date().toISOString();
|
|
38
|
+
const dirtyFilesAtStart = captureGitDirtyFiles();
|
|
39
|
+
const gitHead = captureGitHead() ?? undefined;
|
|
40
|
+
const fingerprintsAtStart = {};
|
|
41
|
+
for (const fp of (0, file_fingerprints_1.computeFileFingerprints)(dirtyFilesAtStart)) {
|
|
42
|
+
if (fp.exists) {
|
|
43
|
+
fingerprintsAtStart[fp.path] = fp.sha256 ?? fp.mtimeMs?.toString() ?? "";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
startedAt,
|
|
48
|
+
gitHead,
|
|
49
|
+
dirtyFilesAtStart,
|
|
50
|
+
fingerprintsAtStart,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function openLocalSession(input) {
|
|
54
|
+
const claimedPaths = [...new Set((input.claimedPaths ?? []).map((path) => path.trim()).filter(Boolean))];
|
|
55
|
+
const baseline = captureLocalSessionBaseline();
|
|
56
|
+
const state = {
|
|
57
|
+
id: `local_${Date.now().toString(36)}`,
|
|
58
|
+
agentId: input.agentId,
|
|
59
|
+
laneDomain: input.laneDomain,
|
|
60
|
+
changeRequestId: input.changeRequestId,
|
|
61
|
+
claimedPaths,
|
|
62
|
+
status: "active",
|
|
63
|
+
changedFiles: [],
|
|
64
|
+
crossings: [],
|
|
65
|
+
approvals: [],
|
|
66
|
+
domains: input.domains,
|
|
67
|
+
serverSessionId: input.serverSessionId,
|
|
68
|
+
pendingHandoffToAgent: input.pendingHandoffToAgent,
|
|
69
|
+
pendingHandoffDomain: input.pendingHandoffDomain,
|
|
70
|
+
createdAt: baseline.startedAt,
|
|
71
|
+
startedAt: baseline.startedAt,
|
|
72
|
+
gitHead: baseline.gitHead,
|
|
73
|
+
dirtyFilesAtStart: baseline.dirtyFilesAtStart,
|
|
74
|
+
fingerprintsAtStart: baseline.fingerprintsAtStart,
|
|
75
|
+
};
|
|
76
|
+
(0, session_state_1.writeSessionState)(state);
|
|
77
|
+
return state;
|
|
78
|
+
}
|
|
79
|
+
function closeLocalSession(state) {
|
|
80
|
+
const closable = (0, watch_core_1.canCloseSession)(state);
|
|
81
|
+
if (!closable.ok) {
|
|
82
|
+
return { ok: false, reason: closable.reason ?? "Session blocked" };
|
|
83
|
+
}
|
|
84
|
+
state.status = "closed";
|
|
85
|
+
state.closeReason = state.closeReason ?? "completed";
|
|
86
|
+
(0, session_state_1.archiveClosedSession)(state);
|
|
87
|
+
(0, session_state_1.writeSessionState)(state);
|
|
88
|
+
return { ok: true };
|
|
89
|
+
}
|