@vellumai/assistant 0.12.2-staging.3 → 0.12.2-staging.5
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/package.json +1 -1
- package/src/acp/session-snapshot.ts +260 -0
- package/src/config/bundled-skills/acp/SKILL.md +1 -1
- package/src/config/bundled-skills/acp/TOOLS.json +2 -2
- package/src/config/bundled-skills/skill-management/TOOLS.json +1 -1
- package/src/live-voice/__tests__/live-voice-look-follow-up.test.ts +374 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +34 -0
- package/src/live-voice/__tests__/protocol.test.ts +48 -0
- package/src/live-voice/__tests__/session-controls.test.ts +25 -0
- package/src/live-voice/live-voice-session.ts +210 -12
- package/src/live-voice/protocol.ts +20 -0
- package/src/live-voice/session-controls.ts +55 -1
- package/src/notifications/__tests__/decision-engine.test.ts +175 -0
- package/src/notifications/decision-engine.ts +43 -8
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +10 -0
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/runtime/routes/__tests__/acp-routes.test.ts +19 -0
- package/src/runtime/routes/acp-routes.ts +17 -161
- package/src/skills/managed-store.ts +56 -0
- package/src/tools/acp/status.test.ts +276 -21
- package/src/tools/acp/status.ts +98 -32
- package/src/tools/skills/find-similar-skills.test.ts +211 -2
- package/src/tools/skills/find-similar-skills.ts +62 -9
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for the `acp_status` tool's
|
|
2
|
+
* Tests for the `acp_status` tool's live and persisted session projection.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The session manager owns live process state. Completed sessions move to
|
|
5
|
+
* durable history, where the status tool must keep them discoverable for
|
|
6
|
+
* follow-up work without making internal lifecycle guards treat them as live.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { describe, expect, mock, test } from "bun:test";
|
|
9
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
10
10
|
|
|
11
11
|
import type { AcpSessionState } from "../../acp/types.js";
|
|
12
12
|
import type { ToolContext } from "../types.js";
|
|
@@ -26,24 +26,49 @@ const RUNNING_STATE: AcpSessionState = {
|
|
|
26
26
|
],
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
let
|
|
29
|
+
let liveStates: AcpSessionState[] = [];
|
|
30
|
+
let fakeStoredCredential: string | undefined;
|
|
30
31
|
|
|
31
32
|
const realAcpModule = await import("../../acp/index.js");
|
|
32
33
|
mock.module("../../acp/index.js", () => ({
|
|
33
34
|
...realAcpModule,
|
|
34
|
-
getAcpSessionManager: () => ({
|
|
35
|
+
getAcpSessionManager: () => ({
|
|
36
|
+
getStatus: () => liveStates,
|
|
37
|
+
getBufferedUpdates: () => [],
|
|
38
|
+
}),
|
|
35
39
|
}));
|
|
36
40
|
|
|
41
|
+
const realClaudeOauth = await import("../../acp/acp-claude-oauth.js");
|
|
42
|
+
mock.module("../../acp/acp-claude-oauth.js", () => ({
|
|
43
|
+
...realClaudeOauth,
|
|
44
|
+
storedClaudeTokenDigest: async () => fakeStoredCredential,
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
import {
|
|
48
|
+
clearHistory,
|
|
49
|
+
insertHistoryRow,
|
|
50
|
+
} from "../../acp/__tests__/helpers/acp-history-db.js";
|
|
51
|
+
import { claudeTokenDigest } from "../../acp/acp-auth-marker-store.js";
|
|
52
|
+
import { initializeDb } from "../../persistence/db-init.js";
|
|
53
|
+
|
|
37
54
|
const { executeAcpStatus } = await import("./status.js");
|
|
38
55
|
|
|
56
|
+
await initializeDb();
|
|
57
|
+
|
|
39
58
|
const context = {
|
|
40
59
|
conversationId: "conv-1",
|
|
41
60
|
workingDir: "/tmp",
|
|
42
61
|
} as unknown as ToolContext;
|
|
43
62
|
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
liveStates = [];
|
|
65
|
+
fakeStoredCredential = undefined;
|
|
66
|
+
clearHistory();
|
|
67
|
+
});
|
|
68
|
+
|
|
44
69
|
describe("executeAcpStatus", () => {
|
|
45
|
-
test("a
|
|
46
|
-
|
|
70
|
+
test("a live session reports its model but not the picker", async () => {
|
|
71
|
+
liveStates = [RUNNING_STATE];
|
|
47
72
|
|
|
48
73
|
const result = await executeAcpStatus({ acp_session_id: "acp-1" }, context);
|
|
49
74
|
|
|
@@ -62,33 +87,263 @@ describe("executeAcpStatus", () => {
|
|
|
62
87
|
expect(result.content).not.toContain("Most capable");
|
|
63
88
|
});
|
|
64
89
|
|
|
65
|
-
test("
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
90
|
+
test("a cleanly completed resumable session is returned as idle", async () => {
|
|
91
|
+
insertHistoryRow({
|
|
92
|
+
id: "completed-1",
|
|
93
|
+
acpSessionId: "proto-completed-1",
|
|
94
|
+
startedAt: 2000,
|
|
95
|
+
completedAt: 3000,
|
|
96
|
+
status: "completed",
|
|
97
|
+
stopReason: "end_turn",
|
|
98
|
+
cwd: "/tmp/project",
|
|
99
|
+
task: "implement the parser",
|
|
100
|
+
usedTokens: 1200,
|
|
101
|
+
contextSize: 10000,
|
|
102
|
+
});
|
|
70
103
|
|
|
71
|
-
const result = await executeAcpStatus(
|
|
104
|
+
const result = await executeAcpStatus(
|
|
105
|
+
{ acp_session_id: "completed-1" },
|
|
106
|
+
context,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(result.isError).toBe(false);
|
|
110
|
+
expect(JSON.parse(result.content)).toEqual({
|
|
111
|
+
id: "completed-1",
|
|
112
|
+
agentId: "claude",
|
|
113
|
+
acpSessionId: "proto-completed-1",
|
|
114
|
+
parentConversationId: "conv-1",
|
|
115
|
+
status: "idle",
|
|
116
|
+
startedAt: 2000,
|
|
117
|
+
completedAt: 3000,
|
|
118
|
+
stopReason: "end_turn",
|
|
119
|
+
task: "implement the parser",
|
|
120
|
+
latestUsage: {
|
|
121
|
+
usedTokens: 1200,
|
|
122
|
+
contextSize: 10000,
|
|
123
|
+
},
|
|
124
|
+
resumable: true,
|
|
125
|
+
lastRunStatus: "completed",
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("failed and cancelled history rows keep their terminal status", async () => {
|
|
130
|
+
insertHistoryRow({
|
|
131
|
+
id: "failed-1",
|
|
132
|
+
status: "failed",
|
|
133
|
+
error: "adapter failed",
|
|
134
|
+
cwd: "/tmp/project",
|
|
135
|
+
});
|
|
136
|
+
insertHistoryRow({
|
|
137
|
+
id: "cancelled-1",
|
|
138
|
+
status: "cancelled",
|
|
139
|
+
stopReason: "cancelled",
|
|
140
|
+
cwd: "/tmp/project",
|
|
141
|
+
});
|
|
72
142
|
|
|
143
|
+
const failed = await executeAcpStatus(
|
|
144
|
+
{ acp_session_id: "failed-1" },
|
|
145
|
+
context,
|
|
146
|
+
);
|
|
147
|
+
const cancelled = await executeAcpStatus(
|
|
148
|
+
{ acp_session_id: "cancelled-1" },
|
|
149
|
+
context,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
expect(JSON.parse(failed.content)).toMatchObject({
|
|
153
|
+
status: "failed",
|
|
154
|
+
resumable: true,
|
|
155
|
+
lastRunStatus: "failed",
|
|
156
|
+
error: "adapter failed",
|
|
157
|
+
});
|
|
158
|
+
expect(JSON.parse(cancelled.content)).toMatchObject({
|
|
159
|
+
status: "cancelled",
|
|
160
|
+
resumable: true,
|
|
161
|
+
lastRunStatus: "cancelled",
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("a completed legacy row without cwd stays terminal", async () => {
|
|
166
|
+
insertHistoryRow({ id: "legacy-1", status: "completed", cwd: null });
|
|
167
|
+
|
|
168
|
+
const result = await executeAcpStatus(
|
|
169
|
+
{ acp_session_id: "legacy-1" },
|
|
170
|
+
context,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
expect(JSON.parse(result.content)).toMatchObject({
|
|
174
|
+
status: "completed",
|
|
175
|
+
resumable: false,
|
|
176
|
+
lastRunStatus: "completed",
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("a completed run stopped by cancellation stays terminal", async () => {
|
|
181
|
+
insertHistoryRow({
|
|
182
|
+
id: "partial-1",
|
|
183
|
+
status: "completed",
|
|
184
|
+
stopReason: "cancelled",
|
|
185
|
+
cwd: "/tmp/project",
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const result = await executeAcpStatus(
|
|
189
|
+
{ acp_session_id: "partial-1" },
|
|
190
|
+
context,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
expect(JSON.parse(result.content)).toMatchObject({
|
|
194
|
+
status: "completed",
|
|
195
|
+
resumable: true,
|
|
196
|
+
lastRunStatus: "completed",
|
|
197
|
+
stopReason: "cancelled",
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("the listing includes live and persisted idle sessions", async () => {
|
|
202
|
+
liveStates = [RUNNING_STATE];
|
|
203
|
+
insertHistoryRow({
|
|
204
|
+
id: "idle-1",
|
|
205
|
+
startedAt: 2000,
|
|
206
|
+
status: "completed",
|
|
207
|
+
cwd: "/tmp/project",
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const result = await executeAcpStatus({}, context);
|
|
73
211
|
const payload = JSON.parse(result.content) as Array<
|
|
74
212
|
Record<string, unknown>
|
|
75
213
|
>;
|
|
76
|
-
|
|
77
|
-
expect(payload.map((entry) => entry.
|
|
78
|
-
"
|
|
79
|
-
"
|
|
214
|
+
|
|
215
|
+
expect(payload.map((entry) => [entry.id, entry.status])).toEqual([
|
|
216
|
+
["idle-1", "idle"],
|
|
217
|
+
["acp-1", "running"],
|
|
80
218
|
]);
|
|
81
219
|
for (const entry of payload) {
|
|
82
220
|
expect(entry).not.toHaveProperty("availableModels");
|
|
221
|
+
expect(entry).not.toHaveProperty("eventLog");
|
|
222
|
+
expect(entry).not.toHaveProperty("cwd");
|
|
223
|
+
expect(entry).not.toHaveProperty("source");
|
|
224
|
+
expect(entry).not.toHaveProperty("authErrorCredential");
|
|
83
225
|
}
|
|
84
226
|
});
|
|
85
227
|
|
|
86
|
-
test("
|
|
87
|
-
|
|
228
|
+
test("live state wins over history for the same id", async () => {
|
|
229
|
+
liveStates = [RUNNING_STATE];
|
|
230
|
+
insertHistoryRow({
|
|
231
|
+
id: RUNNING_STATE.id,
|
|
232
|
+
status: "completed",
|
|
233
|
+
cwd: "/tmp/project",
|
|
234
|
+
});
|
|
88
235
|
|
|
236
|
+
const result = await executeAcpStatus(
|
|
237
|
+
{ acp_session_id: RUNNING_STATE.id },
|
|
238
|
+
context,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
expect(JSON.parse(result.content)).toMatchObject({
|
|
242
|
+
id: RUNNING_STATE.id,
|
|
243
|
+
status: "running",
|
|
244
|
+
agentId: RUNNING_STATE.agentId,
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("a truly unknown id keeps the not-found error", async () => {
|
|
249
|
+
const result = await executeAcpStatus(
|
|
250
|
+
{ acp_session_id: "missing-1" },
|
|
251
|
+
context,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
expect(result).toEqual({
|
|
255
|
+
content: 'ACP session "missing-1" not found',
|
|
256
|
+
isError: true,
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("an empty listing says so rather than rendering an empty array", async () => {
|
|
89
261
|
const result = await executeAcpStatus({}, context);
|
|
90
262
|
|
|
91
263
|
expect(result.content).toBe("No ACP sessions found.");
|
|
92
264
|
expect(result.isError).toBe(false);
|
|
93
265
|
});
|
|
266
|
+
|
|
267
|
+
test("withholds a persisted auth marker after the credential is replaced", async () => {
|
|
268
|
+
const refused = claudeTokenDigest("sk-ant-oat-refused");
|
|
269
|
+
insertHistoryRow({
|
|
270
|
+
id: "auth-failed-1",
|
|
271
|
+
status: "failed",
|
|
272
|
+
error: "authentication required",
|
|
273
|
+
cwd: "/tmp/project",
|
|
274
|
+
authErrorCode: "acp_claude_auth_required",
|
|
275
|
+
authErrorCredential: refused,
|
|
276
|
+
});
|
|
277
|
+
fakeStoredCredential = claudeTokenDigest("sk-ant-oat-replacement");
|
|
278
|
+
|
|
279
|
+
const result = await executeAcpStatus(
|
|
280
|
+
{ acp_session_id: "auth-failed-1" },
|
|
281
|
+
context,
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
expect(JSON.parse(result.content)).toMatchObject({
|
|
285
|
+
id: "auth-failed-1",
|
|
286
|
+
status: "failed",
|
|
287
|
+
lastRunStatus: "failed",
|
|
288
|
+
});
|
|
289
|
+
expect(JSON.parse(result.content).authErrorCode).toBeUndefined();
|
|
290
|
+
expect(result.content).not.toContain("authErrorCredential");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("keeps a persisted auth marker while the refused credential is current", async () => {
|
|
294
|
+
const refused = claudeTokenDigest("sk-ant-oat-refused");
|
|
295
|
+
insertHistoryRow({
|
|
296
|
+
id: "auth-failed-2",
|
|
297
|
+
status: "failed",
|
|
298
|
+
cwd: "/tmp/project",
|
|
299
|
+
authErrorCode: "acp_claude_auth_required",
|
|
300
|
+
authErrorCredential: refused,
|
|
301
|
+
});
|
|
302
|
+
fakeStoredCredential = refused;
|
|
303
|
+
|
|
304
|
+
const result = await executeAcpStatus(
|
|
305
|
+
{ acp_session_id: "auth-failed-2" },
|
|
306
|
+
context,
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
expect(JSON.parse(result.content)).toMatchObject({
|
|
310
|
+
id: "auth-failed-2",
|
|
311
|
+
status: "failed",
|
|
312
|
+
authErrorCode: "acp_claude_auth_required",
|
|
313
|
+
});
|
|
314
|
+
expect(result.content).not.toContain("authErrorCredential");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("keeps an older live session when 50 newer history rows exist", async () => {
|
|
318
|
+
liveStates = [
|
|
319
|
+
{
|
|
320
|
+
...RUNNING_STATE,
|
|
321
|
+
id: "old-live",
|
|
322
|
+
startedAt: 1,
|
|
323
|
+
},
|
|
324
|
+
];
|
|
325
|
+
for (let i = 0; i < 50; i++) {
|
|
326
|
+
insertHistoryRow({
|
|
327
|
+
id: `hist-${i}`,
|
|
328
|
+
acpSessionId: `proto-hist-${i}`,
|
|
329
|
+
startedAt: 1000 + i,
|
|
330
|
+
status: "completed",
|
|
331
|
+
cwd: "/tmp/project",
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const result = await executeAcpStatus({}, context);
|
|
336
|
+
const payload = JSON.parse(result.content) as Array<{
|
|
337
|
+
id: string;
|
|
338
|
+
status: string;
|
|
339
|
+
}>;
|
|
340
|
+
|
|
341
|
+
expect(payload).toHaveLength(50);
|
|
342
|
+
expect(payload.some((entry) => entry.id === "old-live")).toBe(true);
|
|
343
|
+
expect(payload.find((entry) => entry.id === "old-live")?.status).toBe(
|
|
344
|
+
"running",
|
|
345
|
+
);
|
|
346
|
+
expect(payload[0]?.id).toBe("hist-49");
|
|
347
|
+
expect(payload.some((entry) => entry.id === "hist-0")).toBe(false);
|
|
348
|
+
});
|
|
94
349
|
});
|
package/src/tools/acp/status.ts
CHANGED
|
@@ -1,13 +1,37 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
3
|
+
import { resolvedClaudeCredentialDigest } from "../../acp/prepare-agent-env.js";
|
|
4
|
+
import {
|
|
5
|
+
type AcpSessionSnapshot,
|
|
6
|
+
getAcpSessionSnapshot,
|
|
7
|
+
listAcpSessionSnapshots,
|
|
8
|
+
withCurrentAuthMarkers,
|
|
9
|
+
} from "../../acp/session-snapshot.js";
|
|
5
10
|
import {
|
|
6
11
|
invalidToolInputResult,
|
|
7
12
|
nullAsOmitted,
|
|
8
13
|
} from "../shared/zod-tool-schema.js";
|
|
9
14
|
import type { ToolContext, ToolExecutionResult } from "../types.js";
|
|
10
15
|
|
|
16
|
+
const STATUS_LIST_LIMIT = 50;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Keep every live session visible, then fill remaining capacity from durable
|
|
20
|
+
* history. An older active run must not be displaced by newer historical rows.
|
|
21
|
+
*/
|
|
22
|
+
function selectStatusSnapshots(
|
|
23
|
+
sessions: AcpSessionSnapshot[],
|
|
24
|
+
): AcpSessionSnapshot[] {
|
|
25
|
+
const live = sessions.filter((session) => session.source === "live");
|
|
26
|
+
const history = sessions
|
|
27
|
+
.filter((session) => session.source === "history")
|
|
28
|
+
.sort((a, b) => b.startedAt - a.startedAt);
|
|
29
|
+
const remaining = Math.max(0, STATUS_LIST_LIMIT - live.length);
|
|
30
|
+
return [...live, ...history.slice(0, remaining)].sort(
|
|
31
|
+
(a, b) => b.startedAt - a.startedAt,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
11
35
|
/**
|
|
12
36
|
* Model-input schema, `safeParse`d at the top of {@link executeAcpStatus}.
|
|
13
37
|
* Same in-tool pattern and TOOLS.json drift guard as the other bundled-skill
|
|
@@ -24,33 +48,48 @@ export const acpStatusInputSchema = z.looseObject({
|
|
|
24
48
|
* `AcpSessionState` for those surfaces should not reach this one by default.
|
|
25
49
|
* `model` stays: which model a session is running on is answerable.
|
|
26
50
|
*/
|
|
27
|
-
function projectSession(
|
|
51
|
+
function projectSession(snapshot: AcpSessionSnapshot) {
|
|
52
|
+
const fromHistory = snapshot.source === "history";
|
|
53
|
+
const idle =
|
|
54
|
+
fromHistory &&
|
|
55
|
+
snapshot.status === "completed" &&
|
|
56
|
+
snapshot.stopReason !== "cancelled" &&
|
|
57
|
+
snapshot.resumable;
|
|
58
|
+
const latestUsage =
|
|
59
|
+
snapshot.usedTokens !== undefined && snapshot.contextSize !== undefined
|
|
60
|
+
? {
|
|
61
|
+
usedTokens: snapshot.usedTokens,
|
|
62
|
+
contextSize: snapshot.contextSize,
|
|
63
|
+
costAmount: snapshot.costAmount,
|
|
64
|
+
costCurrency: snapshot.costCurrency,
|
|
65
|
+
inputTokens: snapshot.inputTokens,
|
|
66
|
+
outputTokens: snapshot.outputTokens,
|
|
67
|
+
}
|
|
68
|
+
: undefined;
|
|
28
69
|
return {
|
|
29
|
-
id:
|
|
30
|
-
agentId:
|
|
31
|
-
acpSessionId:
|
|
32
|
-
parentConversationId:
|
|
33
|
-
status:
|
|
34
|
-
startedAt:
|
|
35
|
-
completedAt:
|
|
36
|
-
error:
|
|
37
|
-
stopReason:
|
|
38
|
-
task:
|
|
39
|
-
parentToolUseId:
|
|
40
|
-
authErrorCode:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
70
|
+
id: snapshot.id,
|
|
71
|
+
agentId: snapshot.agentId,
|
|
72
|
+
acpSessionId: snapshot.acpSessionId,
|
|
73
|
+
parentConversationId: snapshot.parentConversationId,
|
|
74
|
+
status: idle ? "idle" : snapshot.status,
|
|
75
|
+
startedAt: snapshot.startedAt,
|
|
76
|
+
completedAt: snapshot.completedAt ?? undefined,
|
|
77
|
+
error: snapshot.error ?? undefined,
|
|
78
|
+
stopReason: snapshot.stopReason ?? undefined,
|
|
79
|
+
task: snapshot.task,
|
|
80
|
+
parentToolUseId: snapshot.parentToolUseId,
|
|
81
|
+
authErrorCode: snapshot.authErrorCode,
|
|
82
|
+
latestUsage,
|
|
83
|
+
model: snapshot.model,
|
|
84
|
+
...(fromHistory
|
|
85
|
+
? {
|
|
86
|
+
resumable: snapshot.resumable,
|
|
87
|
+
lastRunStatus: snapshot.status,
|
|
88
|
+
}
|
|
89
|
+
: {}),
|
|
44
90
|
};
|
|
45
91
|
}
|
|
46
92
|
|
|
47
|
-
/** Projects either shape `getStatus` answers with. */
|
|
48
|
-
function projectStatus(status: AcpSessionState | AcpSessionState[]): unknown {
|
|
49
|
-
return Array.isArray(status)
|
|
50
|
-
? status.map(projectSession)
|
|
51
|
-
: projectSession(status);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
93
|
export async function executeAcpStatus(
|
|
55
94
|
input: Record<string, unknown>,
|
|
56
95
|
_context: ToolContext,
|
|
@@ -60,24 +99,51 @@ export async function executeAcpStatus(
|
|
|
60
99
|
return invalidToolInputResult("acp_status", parsedInput.error);
|
|
61
100
|
}
|
|
62
101
|
const acpSessionId = parsedInput.data.acp_session_id;
|
|
63
|
-
const manager = getAcpSessionManager();
|
|
64
|
-
|
|
65
102
|
try {
|
|
66
103
|
if (acpSessionId) {
|
|
104
|
+
const snapshot = getAcpSessionSnapshot(acpSessionId, {
|
|
105
|
+
includeEventLog: false,
|
|
106
|
+
});
|
|
107
|
+
if (!snapshot) {
|
|
108
|
+
return {
|
|
109
|
+
content: `ACP session "${acpSessionId}" not found`,
|
|
110
|
+
isError: true,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const judged = (
|
|
114
|
+
await withCurrentAuthMarkers(
|
|
115
|
+
[snapshot],
|
|
116
|
+
resolvedClaudeCredentialDigest,
|
|
117
|
+
)
|
|
118
|
+
)[0];
|
|
119
|
+
if (!judged) {
|
|
120
|
+
return {
|
|
121
|
+
content: `ACP session "${acpSessionId}" not found`,
|
|
122
|
+
isError: true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
67
125
|
return {
|
|
68
|
-
content: JSON.stringify(
|
|
126
|
+
content: JSON.stringify(projectSession(judged)),
|
|
69
127
|
isError: false,
|
|
70
128
|
};
|
|
71
129
|
}
|
|
72
130
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
131
|
+
const snapshots = selectStatusSnapshots(
|
|
132
|
+
listAcpSessionSnapshots({
|
|
133
|
+
limit: STATUS_LIST_LIMIT,
|
|
134
|
+
includeEventLog: false,
|
|
135
|
+
}).sessions,
|
|
136
|
+
);
|
|
137
|
+
if (snapshots.length === 0) {
|
|
76
138
|
return { content: "No ACP sessions found.", isError: false };
|
|
77
139
|
}
|
|
78
140
|
|
|
141
|
+
const judged = await withCurrentAuthMarkers(
|
|
142
|
+
snapshots,
|
|
143
|
+
resolvedClaudeCredentialDigest,
|
|
144
|
+
);
|
|
79
145
|
return {
|
|
80
|
-
content: JSON.stringify(
|
|
146
|
+
content: JSON.stringify(judged.map(projectSession)),
|
|
81
147
|
isError: false,
|
|
82
148
|
};
|
|
83
149
|
} catch (err) {
|