@agentclientprotocol/claude-agent-acp 0.99.2-preview.5 → 0.99.2-preview.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/acp-agent.d.ts +8 -0
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +68 -10
- package/dist/fork-session.d.ts.map +1 -1
- package/dist/fork-session.js +23 -0
- package/dist/native-subagents.d.ts +49 -3
- package/dist/native-subagents.d.ts.map +1 -1
- package/dist/native-subagents.js +118 -4
- package/dist/session-index/service.d.ts +3 -1
- package/dist/session-index/service.d.ts.map +1 -1
- package/dist/session-index/service.js +5 -10
- package/dist/subagent-history.d.ts +8 -1
- package/dist/subagent-history.d.ts.map +1 -1
- package/dist/subagent-history.js +40 -1
- package/dist/subagent-replay.d.ts +19 -3
- package/dist/subagent-replay.d.ts.map +1 -1
- package/dist/subagent-replay.js +46 -4
- package/dist/v2/initialize.d.ts +16 -1
- package/dist/v2/initialize.d.ts.map +1 -1
- package/dist/v2/initialize.js +26 -3
- package/dist/v2/prompt.d.ts +2 -1
- package/dist/v2/prompt.d.ts.map +1 -1
- package/dist/v2/prompt.js +5 -1
- package/dist/v2/session.d.ts.map +1 -1
- package/dist/v2/session.js +21 -9
- package/package.json +1 -1
package/dist/native-subagents.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { RequestError, } from "@agentclientprotocol/sdk/unstable";
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import { AIR_SUBAGENT_KEY, airExtensionMeta } from "./air-extension.js";
|
|
3
5
|
import { MODEL_CONFIG_ID } from "./session-model.js";
|
|
4
6
|
import { isFinalToolCallStatus } from "./tool-calls/renderer.js";
|
|
@@ -28,6 +30,11 @@ export class NativeSubagentRuntime {
|
|
|
28
30
|
identityByToolUse = new Map();
|
|
29
31
|
controlByToolUse = new Map();
|
|
30
32
|
configBySession = new Map();
|
|
33
|
+
/**
|
|
34
|
+
* The RFD form: the working directory of each child, by session id, as its
|
|
35
|
+
* hooks last reported it. A hook can come before the child is announced.
|
|
36
|
+
*/
|
|
37
|
+
cwdBySession = new Map();
|
|
31
38
|
childByParentToolUse = new Map();
|
|
32
39
|
/**
|
|
33
40
|
* The child session of each tool call that went to a child session. A later
|
|
@@ -224,6 +231,85 @@ export class NativeSubagentRuntime {
|
|
|
224
231
|
config.publishing = publishing.catch(() => { });
|
|
225
232
|
await publishing;
|
|
226
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* The RFD form: takes the working directory of the child `agentId`, the
|
|
236
|
+
* `cwd` of one of its hooks. A child that Claude Code runs in its own
|
|
237
|
+
* worktree (Agent `isolation: "worktree"`) reports the worktree. The client
|
|
238
|
+
* gets the `cwd` only when it differs from the parent's, in the
|
|
239
|
+
* announcement or in a `subagent_update` right after it. Claude Code runs a
|
|
240
|
+
* child's SubagentStart hook before its first model request, so the `cwd`
|
|
241
|
+
* reaches the client before the child's first tool call.
|
|
242
|
+
*/
|
|
243
|
+
async reportCwd(agentId, cwd) {
|
|
244
|
+
if (this.form !== "rfd" || agentId === this.rootSessionId || !path.isAbsolute(cwd))
|
|
245
|
+
return;
|
|
246
|
+
const known = this.children.get(agentId) ?? this.childOfSession(agentId);
|
|
247
|
+
const sessionId = known?.sessionId ?? agentId;
|
|
248
|
+
if (!known && !this.cwdBySession.has(sessionId)) {
|
|
249
|
+
const pending = [...this.cwdBySession.keys()].filter((id) => !this.childOfSession(id)).length;
|
|
250
|
+
if (pending >= MAX_PENDING_PARENTS) {
|
|
251
|
+
this.logger.log(`Session ${this.rootSessionId}: dropping cwd for unannounced subagent ${agentId}; pending buffer limit reached`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
this.cwdBySession.set(sessionId, cwd);
|
|
256
|
+
if (!known)
|
|
257
|
+
return;
|
|
258
|
+
// An announcement in flight may have been built without this `cwd`.
|
|
259
|
+
if (known.announcePromise)
|
|
260
|
+
await known.announcePromise.catch(() => { });
|
|
261
|
+
if (known.announced)
|
|
262
|
+
await this.publishCwd(known);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The `cwd` of `child` that the client does not hold yet: its working
|
|
266
|
+
* directory when that differs from its parent's, `null` when it is back in
|
|
267
|
+
* the parent's after it reported another, else undefined.
|
|
268
|
+
*/
|
|
269
|
+
async unreportedCwd(child) {
|
|
270
|
+
const cwd = this.cwdBySession.get(child.sessionId);
|
|
271
|
+
if (cwd === undefined)
|
|
272
|
+
return undefined;
|
|
273
|
+
const inherited = this.effectiveCwd(child.parentSessionId);
|
|
274
|
+
if (inherited !== undefined && (await sameDirectory(cwd, inherited))) {
|
|
275
|
+
return child.reportedCwd ? null : undefined;
|
|
276
|
+
}
|
|
277
|
+
return cwd === child.reportedCwd ? undefined : cwd;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* The working directory of the session `sessionId` as the client holds it:
|
|
281
|
+
* the root's from session setup, a child's reported `cwd`, else its
|
|
282
|
+
* parent's.
|
|
283
|
+
*/
|
|
284
|
+
effectiveCwd(sessionId) {
|
|
285
|
+
const seen = new Set();
|
|
286
|
+
let child = this.childOfSession(sessionId);
|
|
287
|
+
while (child && !seen.has(child.sessionId)) {
|
|
288
|
+
if (child.reportedCwd)
|
|
289
|
+
return child.reportedCwd;
|
|
290
|
+
seen.add(child.sessionId);
|
|
291
|
+
child = this.childOfSession(child.parentSessionId);
|
|
292
|
+
}
|
|
293
|
+
return this.session.cwd;
|
|
294
|
+
}
|
|
295
|
+
/** The RFD form: sends the `cwd` of the announced `child` that the client does not hold. */
|
|
296
|
+
async publishCwd(child) {
|
|
297
|
+
if (this.form !== "rfd")
|
|
298
|
+
return;
|
|
299
|
+
// Concurrent hooks of one child report the same `cwd` once.
|
|
300
|
+
const publishing = (child.cwdPublishing ?? Promise.resolve()).then(async () => {
|
|
301
|
+
const cwd = await this.unreportedCwd(child);
|
|
302
|
+
if (cwd === undefined)
|
|
303
|
+
return;
|
|
304
|
+
await this.publish({
|
|
305
|
+
sessionId: child.parentSessionId,
|
|
306
|
+
update: { sessionUpdate: "subagent_update", sessionId: child.sessionId, cwd },
|
|
307
|
+
});
|
|
308
|
+
child.reportedCwd = cwd;
|
|
309
|
+
});
|
|
310
|
+
child.cwdPublishing = publishing.catch(() => { });
|
|
311
|
+
await publishing;
|
|
312
|
+
}
|
|
227
313
|
async routeUpdate(notification, deliver, forcedSessionId) {
|
|
228
314
|
const { update } = notification;
|
|
229
315
|
const claudeMeta = update._meta?.claudeCode;
|
|
@@ -686,10 +772,15 @@ export class NativeSubagentRuntime {
|
|
|
686
772
|
}
|
|
687
773
|
if (child.announced)
|
|
688
774
|
continue;
|
|
775
|
+
// A hook of the resumed agent may have reported a newer `cwd`.
|
|
776
|
+
if (entry.cwd && !this.cwdBySession.has(child.sessionId)) {
|
|
777
|
+
this.cwdBySession.set(child.sessionId, entry.cwd);
|
|
778
|
+
}
|
|
689
779
|
if (entry.agentId === agentId) {
|
|
690
780
|
await this.announce(child);
|
|
691
781
|
}
|
|
692
782
|
else {
|
|
783
|
+
const cwd = await this.unreportedCwd(child);
|
|
693
784
|
await this.publish({
|
|
694
785
|
sessionId: child.parentSessionId,
|
|
695
786
|
update: {
|
|
@@ -697,8 +788,11 @@ export class NativeSubagentRuntime {
|
|
|
697
788
|
sessionId: child.sessionId,
|
|
698
789
|
...(child.title ? { title: child.title } : {}),
|
|
699
790
|
...(child.description ? { description: child.description } : {}),
|
|
791
|
+
...(cwd ? { cwd } : {}),
|
|
700
792
|
},
|
|
701
793
|
});
|
|
794
|
+
if (cwd)
|
|
795
|
+
child.reportedCwd = cwd;
|
|
702
796
|
child.announced = true;
|
|
703
797
|
}
|
|
704
798
|
for (const pending of this.takePending(entry.launchId))
|
|
@@ -846,6 +940,7 @@ export class NativeSubagentRuntime {
|
|
|
846
940
|
withdraw(taskId, child) {
|
|
847
941
|
this.children.delete(taskId);
|
|
848
942
|
this.configBySession.delete(child.sessionId);
|
|
943
|
+
this.cwdBySession.delete(child.sessionId);
|
|
849
944
|
if (child.parentToolUseId) {
|
|
850
945
|
this.takePending(child.parentToolUseId);
|
|
851
946
|
this.cleanupControl(child.parentToolUseId);
|
|
@@ -863,13 +958,16 @@ export class NativeSubagentRuntime {
|
|
|
863
958
|
await this.reportState(child, rfdEndState("failed", { error }));
|
|
864
959
|
}
|
|
865
960
|
/**
|
|
866
|
-
* Announces `child` in the client's form,
|
|
867
|
-
* without a form never does.
|
|
961
|
+
* Announces `child` in the client's form, with its `cwd` in the RFD form,
|
|
962
|
+
* then its configuration. A runtime without a form never does.
|
|
868
963
|
*/
|
|
869
964
|
async announce(child) {
|
|
870
965
|
if (!this.form)
|
|
871
966
|
return;
|
|
872
|
-
|
|
967
|
+
const cwd = this.form === "rfd" && !child.announced ? await this.unreportedCwd(child) : undefined;
|
|
968
|
+
await announceNativeSubagent(child, this.publish, this.form, cwd ?? undefined);
|
|
969
|
+
// A hook that reported a `cwd` while the announcement was being built.
|
|
970
|
+
await this.publishCwd(child);
|
|
873
971
|
await this.publishConfiguration(child);
|
|
874
972
|
}
|
|
875
973
|
/** The RFD form: publishes the work state of `child` on its parent. */
|
|
@@ -1105,6 +1203,7 @@ export class NativeSubagentRuntime {
|
|
|
1105
1203
|
this.identityByToolUse.clear();
|
|
1106
1204
|
this.controlByToolUse.clear();
|
|
1107
1205
|
this.configBySession.clear();
|
|
1206
|
+
this.cwdBySession.clear();
|
|
1108
1207
|
this.childByParentToolUse.clear();
|
|
1109
1208
|
this.taskFinishPromises.clear();
|
|
1110
1209
|
this.senderByToolUse.clear();
|
|
@@ -1272,7 +1371,7 @@ export function responseModelEntry(model, models) {
|
|
|
1272
1371
|
* `NativeSubagentRuntime.cancelChild`), and nothing else. AIR's draft grants
|
|
1273
1372
|
* no capabilities.
|
|
1274
1373
|
*/
|
|
1275
|
-
export async function announceNativeSubagent(child, publish, form) {
|
|
1374
|
+
export async function announceNativeSubagent(child, publish, form, cwd) {
|
|
1276
1375
|
if (child.announced)
|
|
1277
1376
|
return;
|
|
1278
1377
|
if (child.announcePromise)
|
|
@@ -1287,10 +1386,13 @@ export async function announceNativeSubagent(child, publish, form) {
|
|
|
1287
1386
|
sessionId: child.sessionId,
|
|
1288
1387
|
...(child.title ? { title: child.title } : {}),
|
|
1289
1388
|
...(child.description ? { description: child.description } : {}),
|
|
1389
|
+
...(cwd ? { cwd } : {}),
|
|
1290
1390
|
capabilities: { cancel: {} },
|
|
1291
1391
|
state: { state: "running" },
|
|
1292
1392
|
},
|
|
1293
1393
|
});
|
|
1394
|
+
if (cwd)
|
|
1395
|
+
child.reportedCwd = cwd;
|
|
1294
1396
|
child.announced = true;
|
|
1295
1397
|
// The immediate parent made the Agent/Task call that launched the child.
|
|
1296
1398
|
if (child.prompt) {
|
|
@@ -1723,3 +1825,15 @@ function promptField(prompt) {
|
|
|
1723
1825
|
function nonBlankString(value) {
|
|
1724
1826
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
1725
1827
|
}
|
|
1828
|
+
/**
|
|
1829
|
+
* Whether `left` and `right` are the same directory, also when one is
|
|
1830
|
+
* spelled through a symlink, e.g. `/tmp/project` and `/private/tmp/project`
|
|
1831
|
+
* on macOS. A path that does not exist is compared as written.
|
|
1832
|
+
*/
|
|
1833
|
+
export async function sameDirectory(left, right) {
|
|
1834
|
+
if (path.resolve(left) === path.resolve(right))
|
|
1835
|
+
return true;
|
|
1836
|
+
const real = (dir) => realpath(dir).catch(() => path.resolve(dir));
|
|
1837
|
+
const [realLeft, realRight] = await Promise.all([real(left), real(right)]);
|
|
1838
|
+
return realLeft === realRight;
|
|
1839
|
+
}
|
|
@@ -77,7 +77,9 @@ export declare function deleteSessionIfStored(sessionId: string, deleteSession?:
|
|
|
77
77
|
/**
|
|
78
78
|
* `session/delete` of an AIR client without `sessionIndex`, which uses delete
|
|
79
79
|
* to mark a session done: archives it instead, so the transcript survives. A
|
|
80
|
-
* session without a transcript
|
|
80
|
+
* session without a transcript has nothing to archive and succeeds, as ACP
|
|
81
|
+
* asks of a delete of a session that never existed (see
|
|
82
|
+
* {@link deleteSessionIfStored}).
|
|
81
83
|
*/
|
|
82
84
|
export declare function archiveInsteadOfDelete(sessionId: string, service: SessionIndexService): Promise<void>;
|
|
83
85
|
/** How {@link SessionIndexService.retitle} reaches the session. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/session-index/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EACL,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,WAAW,EACjB,MAAM,mCAAmC,CAAC;AAI3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAQrD,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAiBzD,OAAO,EAKL,YAAY,EACZ,KAAK,cAAc,EACnB,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAG5B,eAAO,MAAM,qBAAqB,oBAAoB,CAAC;AACvD,eAAO,MAAM,sBAAsB,qBAAqB,CAAC;AACzD,eAAO,MAAM,wBAAwB,uBAAuB,CAAC;AAC7D,eAAO,MAAM,wBAAwB,uBAAuB,CAAC;AAO7D,MAAM,MAAM,gBAAgB,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AACrD,MAAM,MAAM,oBAAoB,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AACxE,MAAM,MAAM,uBAAuB,GAAG;IAAE,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAC/D,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB,CAAC;AAEF,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,uBAAuB,CAepF;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,CAE/D;AAQD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAMtE;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAM9E;AAED,KAAK,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,cAAc,CAAC;IAAC,gBAAgB,EAAE,OAAO,CAAA;CAAE,CAAC;AAE1F,0EAA0E;AAC1E,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,QAAQ,EAAE,cAAc,CAAC;IACzB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AA0BF,oDAAoD;AACpD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,WAAW,CAgB3D;AAWD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAU7E;AAED;+CAC+C;AAC/C,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,UAAU,CA0B7E;AAQD;;;uEAGuE;AACvE,wBAAsB,uBAAuB,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,iBAgBlF;AA0FD;sEACsE;AACtE,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAEjG;6DAC6D;AAC7D,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAGnD;AAED;8EAC8E;AAC9E,wBAAgB,SAAS,CAAC,QAAQ,EAAE,OAAO,GAAG,WAAW,CAKxD;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/session-index/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -270,13 +270,6 @@ async function isTranscriptOf(transcript, paths) {
|
|
|
270
270
|
function isSdkNotFound(error) {
|
|
271
271
|
return error instanceof Error && /^Session \S+ not found in /.test(error.message);
|
|
272
272
|
}
|
|
273
|
-
/** The error the SDK `deleteSession` gives for a session id it rejects or
|
|
274
|
-
* does not find, which a client without `sessionIndex` got before. */
|
|
275
|
-
function sdkDeleteError(sessionId) {
|
|
276
|
-
return new Error(isSessionId(sessionId)
|
|
277
|
-
? `Session ${sessionId} not found in any project directory`
|
|
278
|
-
: `Invalid sessionId: ${sessionId}`);
|
|
279
|
-
}
|
|
280
273
|
/**
|
|
281
274
|
* `session/delete` of a client that is neither AIR nor a `sessionIndex` client:
|
|
282
275
|
* the SDK delete of the session's transcript. A session without one succeeds,
|
|
@@ -301,15 +294,17 @@ export async function deleteSessionIfStored(sessionId, deleteSession = sdkDelete
|
|
|
301
294
|
/**
|
|
302
295
|
* `session/delete` of an AIR client without `sessionIndex`, which uses delete
|
|
303
296
|
* to mark a session done: archives it instead, so the transcript survives. A
|
|
304
|
-
* session without a transcript
|
|
297
|
+
* session without a transcript has nothing to archive and succeeds, as ACP
|
|
298
|
+
* asks of a delete of a session that never existed (see
|
|
299
|
+
* {@link deleteSessionIfStored}).
|
|
305
300
|
*/
|
|
306
301
|
export async function archiveInsteadOfDelete(sessionId, service) {
|
|
307
302
|
if (!isSessionId(sessionId))
|
|
308
|
-
|
|
303
|
+
return;
|
|
309
304
|
// The SDK delete skips empty transcripts, so they do not count.
|
|
310
305
|
const found = await service.index.findTranscripts(sessionId);
|
|
311
306
|
if (found.length === 0)
|
|
312
|
-
|
|
307
|
+
return;
|
|
313
308
|
await service.retitle(sessionId, archiveTo(true), { sidecar: "existing" });
|
|
314
309
|
}
|
|
315
310
|
export class SessionIndexService {
|
|
@@ -14,6 +14,11 @@ export interface SubagentRecord {
|
|
|
14
14
|
background: boolean;
|
|
15
15
|
/** Whether the user stopped it. */
|
|
16
16
|
stoppedByUser: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Its working directory, when Claude Code placed it outside its parent's:
|
|
19
|
+
* in a worktree of its own or of an ancestor, or in a `cwd` of its own.
|
|
20
|
+
*/
|
|
21
|
+
cwd?: string;
|
|
17
22
|
}
|
|
18
23
|
/** Where the subagent transcripts of a session are. */
|
|
19
24
|
export interface SubagentHistory {
|
|
@@ -31,7 +36,9 @@ export interface SubagentHistory {
|
|
|
31
36
|
* Claude keeps the history of a subagent in its own transcript,
|
|
32
37
|
* `<session>/subagents/agent-<id>.jsonl`, next to the session transcript.
|
|
33
38
|
* The `agent-<id>.meta.json` file beside it names the launching tool use.
|
|
34
|
-
*
|
|
39
|
+
* For a child that Claude Code placed elsewhere, it has `worktreePath`,
|
|
40
|
+
* `inheritedWorktreePath` or `cwd`; then the child's working directory is the
|
|
41
|
+
* `cwd` of its transcript's records. The ids are empty for a session without a local transcript.
|
|
35
42
|
*/
|
|
36
43
|
export declare function subagentHistory(sessionId: string): Promise<SubagentHistory>;
|
|
37
44
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subagent-history.d.ts","sourceRoot":"","sources":["../src/subagent-history.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,gCAAgC,CAAC;AAKxC,OAAO,EAA+B,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG7F,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,UAAU,EAAE,OAAO,CAAC;IACpB,mCAAmC;IACnC,aAAa,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"subagent-history.d.ts","sourceRoot":"","sources":["../src/subagent-history.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,gCAAgC,CAAC;AAKxC,OAAO,EAA+B,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG7F,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,UAAU,EAAE,OAAO,CAAC;IACpB,mCAAmC;IACnC,aAAa,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACpC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CA+CjF;AAgCD;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE7B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE;IAAE,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;CAAE,GAC1C,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAoD/B;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,CAW5D;AA0DD;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE;IAAE,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;CAAE,GAC1C,OAAO,CAAC,cAAc,EAAE,CAAC,CAW3B;AAyBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,cAAc,EAAE,CAkB5E"}
|
package/dist/subagent-history.js
CHANGED
|
@@ -11,7 +11,9 @@ import { findTranscript } from "./resumed-session.js";
|
|
|
11
11
|
* Claude keeps the history of a subagent in its own transcript,
|
|
12
12
|
* `<session>/subagents/agent-<id>.jsonl`, next to the session transcript.
|
|
13
13
|
* The `agent-<id>.meta.json` file beside it names the launching tool use.
|
|
14
|
-
*
|
|
14
|
+
* For a child that Claude Code placed elsewhere, it has `worktreePath`,
|
|
15
|
+
* `inheritedWorktreePath` or `cwd`; then the child's working directory is the
|
|
16
|
+
* `cwd` of its transcript's records. The ids are empty for a session without a local transcript.
|
|
15
17
|
*/
|
|
16
18
|
export async function subagentHistory(sessionId) {
|
|
17
19
|
const agents = new Map();
|
|
@@ -35,6 +37,12 @@ export async function subagentHistory(sessionId) {
|
|
|
35
37
|
if (typeof meta?.toolUseId !== "string")
|
|
36
38
|
return;
|
|
37
39
|
const text = (value) => (typeof value === "string" ? value : undefined);
|
|
40
|
+
const placement = [meta.cwd, meta.worktreePath, meta.inheritedWorktreePath]
|
|
41
|
+
.map(text)
|
|
42
|
+
.find((value) => value && path.isAbsolute(value));
|
|
43
|
+
const cwd = placement
|
|
44
|
+
? ((await recordedCwd(path.join(directory, `agent-${match[1]}.jsonl`))) ?? placement)
|
|
45
|
+
: undefined;
|
|
38
46
|
agents.set(meta.toolUseId, {
|
|
39
47
|
agentId: match[1],
|
|
40
48
|
name: text(meta.name),
|
|
@@ -42,6 +50,7 @@ export async function subagentHistory(sessionId) {
|
|
|
42
50
|
description: text(meta.description),
|
|
43
51
|
background: meta.requestShape === "background",
|
|
44
52
|
stoppedByUser: meta.stoppedByUser === true,
|
|
53
|
+
...(cwd ? { cwd } : {}),
|
|
45
54
|
});
|
|
46
55
|
}
|
|
47
56
|
catch {
|
|
@@ -55,6 +64,35 @@ export async function subagentHistory(sessionId) {
|
|
|
55
64
|
const dir = (await getSessionInfo(sessionId).catch(() => undefined))?.cwd;
|
|
56
65
|
return dir ? { agents, dir } : { agents };
|
|
57
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* The `cwd` of the first record of the transcript `file` that has one, or
|
|
69
|
+
* undefined when the file cannot be read. Claude Code writes the working
|
|
70
|
+
* directory of the session or subagent on each record.
|
|
71
|
+
*/
|
|
72
|
+
async function recordedCwd(file) {
|
|
73
|
+
const stream = createReadStream(file, { encoding: "utf8" });
|
|
74
|
+
try {
|
|
75
|
+
for await (const line of readline.createInterface({ input: stream, crlfDelay: Infinity })) {
|
|
76
|
+
if (!line.includes('"cwd"'))
|
|
77
|
+
continue;
|
|
78
|
+
try {
|
|
79
|
+
const cwd = JSON.parse(line)?.cwd;
|
|
80
|
+
if (typeof cwd === "string" && path.isAbsolute(cwd))
|
|
81
|
+
return cwd;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// A damaged record; a later one can have the `cwd`.
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// An unreadable transcript; the caller falls back to the metadata's path.
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
stream.destroy();
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
58
96
|
/** The folder of the subagent transcripts, next to the session transcript `transcript`. */
|
|
59
97
|
function subagentsDirectory(transcript, sessionId) {
|
|
60
98
|
return path.join(path.dirname(transcript), sessionId, "subagents");
|
|
@@ -121,6 +159,7 @@ export async function subagentLineage(sessionId, agentId, logger) {
|
|
|
121
159
|
agentId: current,
|
|
122
160
|
launchId: launch.launchId,
|
|
123
161
|
...(launcher !== null ? { parentAgentId: launcher } : {}),
|
|
162
|
+
...(launch.record.cwd ? { cwd: launch.record.cwd } : {}),
|
|
124
163
|
...(launch.record.name ? { name: launch.record.name } : {}),
|
|
125
164
|
...(launch.record.agentType ? { agentType: launch.record.agentType } : {}),
|
|
126
165
|
...(launch.record.description ? { description: launch.record.description } : {}),
|
|
@@ -54,6 +54,8 @@ export type ReplayChild = {
|
|
|
54
54
|
/** The state of the child's last persisted task notification. */
|
|
55
55
|
notified?: SubagentState;
|
|
56
56
|
announced: boolean;
|
|
57
|
+
/** The `cwd` that the replay reported for it; undefined while it inherits its parent's. */
|
|
58
|
+
cwd?: string;
|
|
57
59
|
promptSent: boolean;
|
|
58
60
|
/** Whether it handed a report to its parent with a SubagentHandback call. */
|
|
59
61
|
handedBack?: boolean;
|
|
@@ -72,6 +74,8 @@ export declare class RfdSubagentReplay {
|
|
|
72
74
|
* {@link currentStates}, after the load response.
|
|
73
75
|
*/
|
|
74
76
|
private readonly mapsDuringReplay;
|
|
77
|
+
/** The working directory of the root session, which a child inherits unless it reports its own. */
|
|
78
|
+
private readonly rootCwd?;
|
|
75
79
|
/** The children by the id of the tool use that launched each. */
|
|
76
80
|
private readonly children;
|
|
77
81
|
private readonly byAgentId;
|
|
@@ -88,7 +92,9 @@ export declare class RfdSubagentReplay {
|
|
|
88
92
|
* A load lacks the list until the CLI started, so it maps only in
|
|
89
93
|
* {@link currentStates}, after the load response.
|
|
90
94
|
*/
|
|
91
|
-
mapsDuringReplay?: boolean
|
|
95
|
+
mapsDuringReplay?: boolean,
|
|
96
|
+
/** The working directory of the root session, which a child inherits unless it reports its own. */
|
|
97
|
+
rootCwd?: string | undefined);
|
|
92
98
|
/**
|
|
93
99
|
* Registers the launches, the launch results, and the SendMessage calls in
|
|
94
100
|
* `messages`, the transcript of the session `ownerSessionId`.
|
|
@@ -98,10 +104,16 @@ export declare class RfdSubagentReplay {
|
|
|
98
104
|
childOfLaunch(toolUseId: string): ReplayChild | undefined;
|
|
99
105
|
/**
|
|
100
106
|
* Announces `child` on its parent, as live: running, with the parent's
|
|
101
|
-
* labels
|
|
102
|
-
*
|
|
107
|
+
* labels, and with its recorded `cwd` when that differs from its parent's.
|
|
108
|
+
* Then it sends `firstModel`, the model of the child's first response, when
|
|
109
|
+
* the transcript has one.
|
|
103
110
|
*/
|
|
104
111
|
announce(child: ReplayChild, firstModel?: string): Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* The working directory of the session `sessionId` as the client holds it:
|
|
114
|
+
* a child's reported `cwd`, else its parent's, up to the root's.
|
|
115
|
+
*/
|
|
116
|
+
private effectiveCwd;
|
|
105
117
|
/**
|
|
106
118
|
* Takes the model of a replayed response of the child `owner`. A child
|
|
107
119
|
* whose model changes gets its `model` option again.
|
|
@@ -156,6 +168,10 @@ export declare class RfdSubagentReplay {
|
|
|
156
168
|
* when a live session is loaded again. Such a child reports that state and
|
|
157
169
|
* can be cancelled, as live. Any other child cannot be running, because the
|
|
158
170
|
* process that ran it is gone, so its replayed end is its current state.
|
|
171
|
+
*
|
|
172
|
+
* A replayed `cwd` whose directory is gone, such as a worktree that Claude
|
|
173
|
+
* Code removed when the child finished, is cleared with `cwd: null`, as the
|
|
174
|
+
* RFD requires after a reconnection.
|
|
159
175
|
*/
|
|
160
176
|
currentStates(live: (sessionId: string) => WorkState["state"] | undefined, liveConfiguration?: (sessionId: string) => SessionConfigOption[] | undefined): AcpSessionNotification[];
|
|
161
177
|
private registerLaunch;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subagent-replay.d.ts","sourceRoot":"","sources":["../src/subagent-replay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"subagent-replay.d.ts","sourceRoot":"","sources":["../src/subagent-replay.ts"],"names":[],"mappings":"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"}
|
package/dist/subagent-replay.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
|
+
import { childModelOption, HANDBACK_TOOL, handbackMessage, isNativeSubagentControlTool, launchReply, linkedSessionMessages, nativeSubagentState, responseModelEntry, rfdEndState, rfdLabels, sameDirectory, } from "./native-subagents.js";
|
|
2
3
|
import { noticeUpdate } from "./session-notices.js";
|
|
3
4
|
export class RfdSubagentReplay {
|
|
4
5
|
history;
|
|
5
6
|
publish;
|
|
6
7
|
models;
|
|
7
8
|
mapsDuringReplay;
|
|
9
|
+
rootCwd;
|
|
8
10
|
/** The children by the id of the tool use that launched each. */
|
|
9
11
|
children = new Map();
|
|
10
12
|
byAgentId = new Map();
|
|
@@ -21,11 +23,14 @@ export class RfdSubagentReplay {
|
|
|
21
23
|
* A load lacks the list until the CLI started, so it maps only in
|
|
22
24
|
* {@link currentStates}, after the load response.
|
|
23
25
|
*/
|
|
24
|
-
mapsDuringReplay = true
|
|
26
|
+
mapsDuringReplay = true,
|
|
27
|
+
/** The working directory of the root session, which a child inherits unless it reports its own. */
|
|
28
|
+
rootCwd) {
|
|
25
29
|
this.history = history;
|
|
26
30
|
this.publish = publish;
|
|
27
31
|
this.models = models;
|
|
28
32
|
this.mapsDuringReplay = mapsDuringReplay;
|
|
33
|
+
this.rootCwd = rootCwd;
|
|
29
34
|
}
|
|
30
35
|
/**
|
|
31
36
|
* Registers the launches, the launch results, and the SendMessage calls in
|
|
@@ -76,13 +81,19 @@ export class RfdSubagentReplay {
|
|
|
76
81
|
}
|
|
77
82
|
/**
|
|
78
83
|
* Announces `child` on its parent, as live: running, with the parent's
|
|
79
|
-
* labels
|
|
80
|
-
*
|
|
84
|
+
* labels, and with its recorded `cwd` when that differs from its parent's.
|
|
85
|
+
* Then it sends `firstModel`, the model of the child's first response, when
|
|
86
|
+
* the transcript has one.
|
|
81
87
|
*/
|
|
82
88
|
async announce(child, firstModel) {
|
|
83
89
|
if (child.announced)
|
|
84
90
|
return;
|
|
85
91
|
const labels = rfdLabels(child.record.name ?? child.input.name, child.record.agentType ?? child.input.subagent_type, child.record.description ?? child.input.description);
|
|
92
|
+
const inherited = this.effectiveCwd(child.parentSessionId);
|
|
93
|
+
const cwd = child.record.cwd &&
|
|
94
|
+
(inherited === undefined || !(await sameDirectory(child.record.cwd, inherited)))
|
|
95
|
+
? child.record.cwd
|
|
96
|
+
: undefined;
|
|
86
97
|
await this.publish({
|
|
87
98
|
sessionId: child.parentSessionId,
|
|
88
99
|
update: {
|
|
@@ -90,12 +101,29 @@ export class RfdSubagentReplay {
|
|
|
90
101
|
sessionId: child.sessionId,
|
|
91
102
|
...(labels.title ? { title: labels.title } : {}),
|
|
92
103
|
...(labels.description ? { description: labels.description } : {}),
|
|
104
|
+
...(cwd ? { cwd } : {}),
|
|
93
105
|
state: { state: "running" },
|
|
94
106
|
},
|
|
95
107
|
});
|
|
108
|
+
child.cwd = cwd;
|
|
96
109
|
child.announced = true;
|
|
97
110
|
await this.responseModel(child, firstModel);
|
|
98
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* The working directory of the session `sessionId` as the client holds it:
|
|
114
|
+
* a child's reported `cwd`, else its parent's, up to the root's.
|
|
115
|
+
*/
|
|
116
|
+
effectiveCwd(sessionId) {
|
|
117
|
+
const seen = new Set();
|
|
118
|
+
let child = this.byAgentId.get(sessionId);
|
|
119
|
+
while (child && !seen.has(child.sessionId)) {
|
|
120
|
+
if (child.cwd)
|
|
121
|
+
return child.cwd;
|
|
122
|
+
seen.add(child.sessionId);
|
|
123
|
+
child = this.byAgentId.get(child.parentSessionId);
|
|
124
|
+
}
|
|
125
|
+
return this.rootCwd;
|
|
126
|
+
}
|
|
99
127
|
/**
|
|
100
128
|
* Takes the model of a replayed response of the child `owner`. A child
|
|
101
129
|
* whose model changes gets its `model` option again.
|
|
@@ -258,6 +286,10 @@ export class RfdSubagentReplay {
|
|
|
258
286
|
* when a live session is loaded again. Such a child reports that state and
|
|
259
287
|
* can be cancelled, as live. Any other child cannot be running, because the
|
|
260
288
|
* process that ran it is gone, so its replayed end is its current state.
|
|
289
|
+
*
|
|
290
|
+
* A replayed `cwd` whose directory is gone, such as a worktree that Claude
|
|
291
|
+
* Code removed when the child finished, is cleared with `cwd: null`, as the
|
|
292
|
+
* RFD requires after a reconnection.
|
|
261
293
|
*/
|
|
262
294
|
currentStates(live, liveConfiguration) {
|
|
263
295
|
return [...this.children.values()]
|
|
@@ -278,6 +310,7 @@ export class RfdSubagentReplay {
|
|
|
278
310
|
sessionUpdate: "subagent_update",
|
|
279
311
|
sessionId: child.sessionId,
|
|
280
312
|
...(liveState !== undefined ? { capabilities: { cancel: {} } } : {}),
|
|
313
|
+
...(child.cwd && !isDirectory(child.cwd) ? { cwd: null } : {}),
|
|
281
314
|
state,
|
|
282
315
|
},
|
|
283
316
|
},
|
|
@@ -427,6 +460,15 @@ function textOf(content) {
|
|
|
427
460
|
.filter((text) => typeof text === "string")
|
|
428
461
|
.join("\n");
|
|
429
462
|
}
|
|
463
|
+
/** Whether `dir` is an existing directory. */
|
|
464
|
+
function isDirectory(dir) {
|
|
465
|
+
try {
|
|
466
|
+
return statSync(dir).isDirectory();
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
430
472
|
/** A message that a SendMessage call delivered to the child. */
|
|
431
473
|
function isDelivery(message) {
|
|
432
474
|
return message.origin?.kind === "coordinator";
|
package/dist/v2/initialize.d.ts
CHANGED
|
@@ -15,7 +15,22 @@ export declare function v1InitializeRequest(request: v2.InitializeRequest): Init
|
|
|
15
15
|
* The v2 `initialize` response for the v1 response of the agent.
|
|
16
16
|
*
|
|
17
17
|
* It does not advertise what the v2 surface does not serve yet: the
|
|
18
|
-
* extensions of {@link UNSERVED_EXTENSION_KEYS}
|
|
18
|
+
* extensions of {@link UNSERVED_EXTENSION_KEYS}, and the AIR extensions of
|
|
19
|
+
* {@link withoutUnservedAirExtensions}.
|
|
19
20
|
*/
|
|
20
21
|
export declare function v2InitializeResponse(response: InitializeResponse): v2.InitializeResponse;
|
|
22
|
+
/**
|
|
23
|
+
* The session capabilities of the v2 surface, which serves each through a v1
|
|
24
|
+
* method of the agent.
|
|
25
|
+
*
|
|
26
|
+
* - The session baseline: `session/new`, `list`, `resume` (with replay),
|
|
27
|
+
* `close`, `prompt`, `cancel`, and `update`.
|
|
28
|
+
* - `session/delete`, `session/fork` (which returns a session the client can
|
|
29
|
+
* prompt at once), and `additionalDirectories`.
|
|
30
|
+
* - The prompt content and MCP transports that the agent's v1 `initialize`
|
|
31
|
+
* lists: images, embedded context, and HTTP servers. Stdio servers are in the
|
|
32
|
+
* v1 baseline; v2 has no SSE transport.
|
|
33
|
+
* - `systemPrompt`, in `append` and `override` mode.
|
|
34
|
+
*/
|
|
35
|
+
export declare const V2_SESSION_CAPABILITIES: v2.SessionCapabilities;
|
|
21
36
|
//# sourceMappingURL=initialize.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initialize.d.ts","sourceRoot":"","sources":["../../src/v2/initialize.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,mDAAmD,CAAC;
|
|
1
|
+
{"version":3,"file":"initialize.d.ts","sourceRoot":"","sources":["../../src/v2/initialize.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,mDAAmD,CAAC;AAIxE,0DAA0D;AAC1D,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,CAmCpF;AASD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,EAAE,CAAC,kBAAkB,CA2BxF;AA0BD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,EAAE,EAAE,CAAC,mBAOxC,CAAC"}
|