@agentclientprotocol/claude-agent-acp 0.89.2-preview.9 → 0.90.1-preview.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/README.md +56 -8
- package/dist/acp-agent.d.ts +50 -1
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +493 -147
- package/dist/acp-subagents.d.ts +16 -6
- package/dist/acp-subagents.d.ts.map +1 -1
- package/dist/acp-subagents.js +8 -2
- package/dist/async-tasks.d.ts +4 -1
- package/dist/async-tasks.d.ts.map +1 -1
- package/dist/auth-status.d.ts +2 -2
- package/dist/auth-status.d.ts.map +1 -1
- package/dist/auth-status.js +18 -4
- package/dist/exit-plan.d.ts +17 -0
- package/dist/exit-plan.d.ts.map +1 -1
- package/dist/exit-plan.js +34 -0
- package/dist/native-subagents.d.ts +228 -11
- package/dist/native-subagents.d.ts.map +1 -1
- package/dist/native-subagents.js +489 -40
- package/dist/settings.d.ts +6 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +13 -0
- package/dist/subagent-history.d.ts +44 -2
- package/dist/subagent-history.d.ts.map +1 -1
- package/dist/subagent-history.js +116 -10
- package/dist/subagent-replay.d.ts +108 -0
- package/dist/subagent-replay.d.ts.map +1 -0
- package/dist/subagent-replay.js +261 -0
- package/dist/tool-calls/background.d.ts +9 -0
- package/dist/tool-calls/background.d.ts.map +1 -1
- package/dist/tool-calls/background.js +25 -0
- package/dist/tool-calls/renderer.d.ts +30 -9
- package/dist/tool-calls/renderer.d.ts.map +1 -1
- package/dist/tool-calls/renderer.js +48 -22
- package/dist/tool-calls/reporters/bash.d.ts +7 -0
- package/dist/tool-calls/reporters/bash.d.ts.map +1 -1
- package/dist/tool-calls/reporters/bash.js +16 -1
- package/dist/tool-calls/reporters/search.d.ts.map +1 -1
- package/dist/tool-calls/reporters/search.js +20 -2
- package/dist/v2/agent.d.ts.map +1 -1
- package/dist/v2/agent.js +45 -12
- package/dist/v2/initialize.d.ts.map +1 -1
- package/dist/v2/initialize.js +10 -1
- package/dist/v2/session-update.d.ts +11 -0
- package/dist/v2/session-update.d.ts.map +1 -1
- package/dist/v2/session-update.js +67 -0
- package/dist/v2/session.d.ts +3 -12
- package/dist/v2/session.d.ts.map +1 -1
- package/dist/v2/session.js +5 -18
- package/dist/v2/tool-call-meta.d.ts +44 -0
- package/dist/v2/tool-call-meta.d.ts.map +1 -0
- package/dist/v2/tool-call-meta.js +72 -0
- package/package.json +9 -9
package/dist/settings.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare class SettingsManager {
|
|
|
19
19
|
private cwd;
|
|
20
20
|
private effective;
|
|
21
21
|
private managedDeniedModels;
|
|
22
|
+
private managedApiKeyHelper?;
|
|
22
23
|
private watchers;
|
|
23
24
|
private onChange?;
|
|
24
25
|
private logger;
|
|
@@ -53,6 +54,11 @@ export declare class SettingsManager {
|
|
|
53
54
|
* Returns the current merged settings
|
|
54
55
|
*/
|
|
55
56
|
getSettings(): Settings;
|
|
57
|
+
/**
|
|
58
|
+
* The `apiKeyHelper` the CLI runs: managed settings outrank the `--settings`
|
|
59
|
+
* override, which outranks the user/project/local files.
|
|
60
|
+
*/
|
|
61
|
+
getApiKeyHelper(settingsOverride?: Settings): string | undefined;
|
|
56
62
|
/**
|
|
57
63
|
* `deniedModels` entries from the managed tier, the only tier the CLI
|
|
58
64
|
* honors them from.
|
package/dist/settings.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,gCAAgC,CAAC;AA0BxC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CAC7E;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,mBAAmB,CAAgB;IAC3C,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,QAAQ,CAAC,CAAa;IAC9B,OAAO,CAAC,MAAM,CAAqE;IACnF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,WAAW,CAA8B;gBAErC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAMzD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBjC;;;OAGG;IACH,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,eAAe;
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,gCAAgC,CAAC;AA0BxC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CAC7E;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,mBAAmB,CAAgB;IAC3C,OAAO,CAAC,mBAAmB,CAAC,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,QAAQ,CAAC,CAAa;IAC9B,OAAO,CAAC,MAAM,CAAqE;IACnF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,WAAW,CAA8B;gBAErC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAMzD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBjC;;;OAGG;IACH,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,eAAe;IAoB7B;;OAEG;IACH,OAAO,CAAC,aAAa;IAyBrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAqB5B;;OAEG;IACH,WAAW,IAAI,QAAQ;IAIvB;;;OAGG;IACH,eAAe,CAAC,gBAAgB,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS;IAMhE;;;OAGG;IACH,sBAAsB,IAAI,MAAM,EAAE;IAIlC;;OAEG;IACH,MAAM,IAAI,MAAM;IAIhB;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUxC;;OAEG;IACH,OAAO,IAAI,IAAI;CAehB"}
|
package/dist/settings.js
CHANGED
|
@@ -38,6 +38,7 @@ export class SettingsManager {
|
|
|
38
38
|
cwd;
|
|
39
39
|
effective = {};
|
|
40
40
|
managedDeniedModels = [];
|
|
41
|
+
managedApiKeyHelper;
|
|
41
42
|
watchers = [];
|
|
42
43
|
onChange;
|
|
43
44
|
logger;
|
|
@@ -94,11 +95,16 @@ export class SettingsManager {
|
|
|
94
95
|
this.managedDeniedModels = resolved.sources
|
|
95
96
|
.filter((entry) => entry.source === "managed")
|
|
96
97
|
.flatMap((entry) => entry.settings.deniedModels ?? []);
|
|
98
|
+
this.managedApiKeyHelper =
|
|
99
|
+
resolved.provenance.apiKeyHelper?.source === "managed"
|
|
100
|
+
? resolved.effective.apiKeyHelper
|
|
101
|
+
: undefined;
|
|
97
102
|
}
|
|
98
103
|
catch (error) {
|
|
99
104
|
this.logger.error("Failed to resolve settings:", error);
|
|
100
105
|
this.effective = {};
|
|
101
106
|
this.managedDeniedModels = [];
|
|
107
|
+
this.managedApiKeyHelper = undefined;
|
|
102
108
|
}
|
|
103
109
|
}
|
|
104
110
|
/**
|
|
@@ -155,6 +161,13 @@ export class SettingsManager {
|
|
|
155
161
|
getSettings() {
|
|
156
162
|
return this.effective;
|
|
157
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* The `apiKeyHelper` the CLI runs: managed settings outrank the `--settings`
|
|
166
|
+
* override, which outranks the user/project/local files.
|
|
167
|
+
*/
|
|
168
|
+
getApiKeyHelper(settingsOverride) {
|
|
169
|
+
return (this.managedApiKeyHelper ?? settingsOverride?.apiKeyHelper ?? this.effective.apiKeyHelper);
|
|
170
|
+
}
|
|
158
171
|
/**
|
|
159
172
|
* `deniedModels` entries from the managed tier, the only tier the CLI
|
|
160
173
|
* honors them from.
|
|
@@ -1,7 +1,24 @@
|
|
|
1
|
+
import { type SessionMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import { type HistoricalSubagent } from "./native-subagents.js";
|
|
3
|
+
/** What the metadata file of a subagent says about it. */
|
|
4
|
+
export interface SubagentRecord {
|
|
5
|
+
/** The SDK agent id, the name of its transcript. */
|
|
6
|
+
agentId: string;
|
|
7
|
+
/** The `name` that the Agent call gave it. */
|
|
8
|
+
name?: string;
|
|
9
|
+
/** Its role, the `subagent_type` of the Agent call. */
|
|
10
|
+
agentType?: string;
|
|
11
|
+
/** The short `description` of the Agent call. */
|
|
12
|
+
description?: string;
|
|
13
|
+
/** Whether the Agent call ran it in the background. */
|
|
14
|
+
background: boolean;
|
|
15
|
+
/** Whether the user stopped it. */
|
|
16
|
+
stoppedByUser: boolean;
|
|
17
|
+
}
|
|
1
18
|
/** Where the subagent transcripts of a session are. */
|
|
2
19
|
export interface SubagentHistory {
|
|
3
|
-
/** The subagent
|
|
4
|
-
|
|
20
|
+
/** The subagent of each Agent or Task tool use that launched one. */
|
|
21
|
+
agents: Map<string, SubagentRecord>;
|
|
5
22
|
/**
|
|
6
23
|
* The project directory of the session, as the SDK takes it in `dir`. With
|
|
7
24
|
* it, the SDK reads a subagent transcript without a search of every project.
|
|
@@ -17,4 +34,29 @@ export interface SubagentHistory {
|
|
|
17
34
|
* The ids are empty for a session without a local transcript.
|
|
18
35
|
*/
|
|
19
36
|
export declare function subagentHistory(sessionId: string): Promise<SubagentHistory>;
|
|
37
|
+
/**
|
|
38
|
+
* The agent `agentId` and its ancestors, root first, from the subagent history
|
|
39
|
+
* of the session `sessionId`. The runtime restores an agent from it when Claude
|
|
40
|
+
* Code resumes one that ran before the session was loaded or resumed.
|
|
41
|
+
*
|
|
42
|
+
* The metadata file of an agent names the call that launched it, but not the
|
|
43
|
+
* agent that made the call. So the parent of a launch is the session whose
|
|
44
|
+
* transcript has the call, as in the replay: the session's own transcript, or
|
|
45
|
+
* an agent's. The list is empty when the agent or one of its ancestors has no
|
|
46
|
+
* metadata, or no transcript has its launch.
|
|
47
|
+
*/
|
|
48
|
+
export declare function subagentLineage(sessionId: string, agentId: string, logger: {
|
|
49
|
+
error(...args: unknown[]): void;
|
|
50
|
+
}): Promise<HistoricalSubagent[]>;
|
|
51
|
+
/** The ids of the Agent and Task calls that launched subagents in `content`. */
|
|
52
|
+
export declare function subagentLaunchIds(content: unknown): string[];
|
|
53
|
+
/**
|
|
54
|
+
* The transcript of the subagent `agentId` of a session, or an empty list
|
|
55
|
+
* when it cannot be read. With the project directory, the SDK skips its
|
|
56
|
+
* search of every project; a session opened from another directory falls
|
|
57
|
+
* back to that search.
|
|
58
|
+
*/
|
|
59
|
+
export declare function readSubagentMessages(sessionId: string, agentId: string, history: SubagentHistory, logger: {
|
|
60
|
+
error(...args: unknown[]): void;
|
|
61
|
+
}): Promise<SessionMessage[]>;
|
|
20
62
|
//# sourceMappingURL=subagent-history.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subagent-history.d.ts","sourceRoot":"","sources":["../src/subagent-history.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"subagent-history.d.ts","sourceRoot":"","sources":["../src/subagent-history.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,gCAAgC,CAAC;AAGxC,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;CACxB;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;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAwCjF;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,CAqD/B;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,CAW5D;AAQD;;;;;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"}
|
package/dist/subagent-history.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { getSessionInfo } from "@anthropic-ai/claude-agent-sdk";
|
|
1
|
+
import { getSessionInfo, getSessionMessages, getSubagentMessages, } from "@anthropic-ai/claude-agent-sdk";
|
|
2
2
|
import { readdir, readFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { isNativeSubagentControlTool } from "./native-subagents.js";
|
|
4
5
|
import { findTranscript } from "./resumed-session.js";
|
|
5
6
|
/**
|
|
6
7
|
* Finds the subagent transcripts of a session.
|
|
@@ -11,17 +12,17 @@ import { findTranscript } from "./resumed-session.js";
|
|
|
11
12
|
* The ids are empty for a session without a local transcript.
|
|
12
13
|
*/
|
|
13
14
|
export async function subagentHistory(sessionId) {
|
|
14
|
-
const
|
|
15
|
+
const agents = new Map();
|
|
15
16
|
const transcript = await findTranscript(sessionId);
|
|
16
17
|
if (!transcript)
|
|
17
|
-
return {
|
|
18
|
+
return { agents };
|
|
18
19
|
const directory = path.join(path.dirname(transcript), sessionId, "subagents");
|
|
19
20
|
let names;
|
|
20
21
|
try {
|
|
21
22
|
names = await readdir(directory);
|
|
22
23
|
}
|
|
23
24
|
catch {
|
|
24
|
-
return {
|
|
25
|
+
return { agents };
|
|
25
26
|
}
|
|
26
27
|
await Promise.all(names.map(async (name) => {
|
|
27
28
|
const match = /^agent-(.+)\.meta\.json$/.exec(name);
|
|
@@ -29,18 +30,123 @@ export async function subagentHistory(sessionId) {
|
|
|
29
30
|
return;
|
|
30
31
|
try {
|
|
31
32
|
const meta = JSON.parse(await readFile(path.join(directory, name), "utf8"));
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
if (typeof meta?.toolUseId !== "string")
|
|
34
|
+
return;
|
|
35
|
+
const text = (value) => (typeof value === "string" ? value : undefined);
|
|
36
|
+
agents.set(meta.toolUseId, {
|
|
37
|
+
agentId: match[1],
|
|
38
|
+
name: text(meta.name),
|
|
39
|
+
agentType: text(meta.agentType),
|
|
40
|
+
description: text(meta.description),
|
|
41
|
+
background: meta.requestShape === "background",
|
|
42
|
+
stoppedByUser: meta.stoppedByUser === true,
|
|
43
|
+
});
|
|
35
44
|
}
|
|
36
45
|
catch {
|
|
37
46
|
// A damaged meta file leaves its subagent without history.
|
|
38
47
|
}
|
|
39
48
|
}));
|
|
40
|
-
if (
|
|
41
|
-
return {
|
|
49
|
+
if (agents.size === 0)
|
|
50
|
+
return { agents };
|
|
42
51
|
// The folder name encodes the project directory, and the encoding loses
|
|
43
52
|
// characters. The session info reads it back from the transcript.
|
|
44
53
|
const dir = (await getSessionInfo(sessionId).catch(() => undefined))?.cwd;
|
|
45
|
-
return dir ? {
|
|
54
|
+
return dir ? { agents, dir } : { agents };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The agent `agentId` and its ancestors, root first, from the subagent history
|
|
58
|
+
* of the session `sessionId`. The runtime restores an agent from it when Claude
|
|
59
|
+
* Code resumes one that ran before the session was loaded or resumed.
|
|
60
|
+
*
|
|
61
|
+
* The metadata file of an agent names the call that launched it, but not the
|
|
62
|
+
* agent that made the call. So the parent of a launch is the session whose
|
|
63
|
+
* transcript has the call, as in the replay: the session's own transcript, or
|
|
64
|
+
* an agent's. The list is empty when the agent or one of its ancestors has no
|
|
65
|
+
* metadata, or no transcript has its launch.
|
|
66
|
+
*/
|
|
67
|
+
export async function subagentLineage(sessionId, agentId, logger) {
|
|
68
|
+
const history = await subagentHistory(sessionId);
|
|
69
|
+
const launchOf = new Map();
|
|
70
|
+
for (const [launchId, record] of history.agents) {
|
|
71
|
+
launchOf.set(record.agentId, { launchId, record });
|
|
72
|
+
}
|
|
73
|
+
if (!launchOf.has(agentId))
|
|
74
|
+
return [];
|
|
75
|
+
// Without the project directory, as the replay reads it (`readResumedSession`).
|
|
76
|
+
const rootLaunches = new Set(launchIdsIn(await getSessionMessages(sessionId).catch((error) => {
|
|
77
|
+
logger.error(`Failed to read the transcript of session ${sessionId}:`, error);
|
|
78
|
+
return [];
|
|
79
|
+
})));
|
|
80
|
+
const agentLaunches = new Map();
|
|
81
|
+
/** The agent whose transcript has the launch `launchId`, `null` for the root's. */
|
|
82
|
+
const launcherOf = async (launchId) => {
|
|
83
|
+
if (rootLaunches.has(launchId))
|
|
84
|
+
return null;
|
|
85
|
+
for (const candidate of launchOf.keys()) {
|
|
86
|
+
let launches = agentLaunches.get(candidate);
|
|
87
|
+
if (!launches) {
|
|
88
|
+
launches = new Set(launchIdsIn(await readSubagentMessages(sessionId, candidate, history, logger)));
|
|
89
|
+
agentLaunches.set(candidate, launches);
|
|
90
|
+
}
|
|
91
|
+
if (launches.has(launchId))
|
|
92
|
+
return candidate;
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
95
|
+
};
|
|
96
|
+
const lineage = [];
|
|
97
|
+
let current = agentId;
|
|
98
|
+
while (current !== null) {
|
|
99
|
+
const launch = launchOf.get(current);
|
|
100
|
+
// A cycle, or an ancestor without metadata.
|
|
101
|
+
if (!launch || lineage.some((entry) => entry.agentId === current))
|
|
102
|
+
return [];
|
|
103
|
+
const launcher = await launcherOf(launch.launchId);
|
|
104
|
+
if (launcher === undefined)
|
|
105
|
+
return [];
|
|
106
|
+
lineage.unshift({
|
|
107
|
+
agentId: current,
|
|
108
|
+
launchId: launch.launchId,
|
|
109
|
+
...(launcher !== null ? { parentAgentId: launcher } : {}),
|
|
110
|
+
...(launch.record.name ? { name: launch.record.name } : {}),
|
|
111
|
+
...(launch.record.agentType ? { agentType: launch.record.agentType } : {}),
|
|
112
|
+
...(launch.record.description ? { description: launch.record.description } : {}),
|
|
113
|
+
});
|
|
114
|
+
current = launcher;
|
|
115
|
+
}
|
|
116
|
+
return lineage;
|
|
117
|
+
}
|
|
118
|
+
/** The ids of the Agent and Task calls that launched subagents in `content`. */
|
|
119
|
+
export function subagentLaunchIds(content) {
|
|
120
|
+
if (!Array.isArray(content))
|
|
121
|
+
return [];
|
|
122
|
+
return content.flatMap((block) => typeof block === "object" &&
|
|
123
|
+
block !== null &&
|
|
124
|
+
block.type === "tool_use" &&
|
|
125
|
+
typeof block.id === "string" &&
|
|
126
|
+
isNativeSubagentControlTool(block.name)
|
|
127
|
+
? [block.id]
|
|
128
|
+
: []);
|
|
129
|
+
}
|
|
130
|
+
function launchIdsIn(messages) {
|
|
131
|
+
return messages.flatMap((message) => subagentLaunchIds(message.message?.content));
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The transcript of the subagent `agentId` of a session, or an empty list
|
|
135
|
+
* when it cannot be read. With the project directory, the SDK skips its
|
|
136
|
+
* search of every project; a session opened from another directory falls
|
|
137
|
+
* back to that search.
|
|
138
|
+
*/
|
|
139
|
+
export async function readSubagentMessages(sessionId, agentId, history, logger) {
|
|
140
|
+
try {
|
|
141
|
+
if (history.dir) {
|
|
142
|
+
const messages = await getSubagentMessages(sessionId, agentId, { dir: history.dir });
|
|
143
|
+
if (messages.length > 0)
|
|
144
|
+
return messages;
|
|
145
|
+
}
|
|
146
|
+
return await getSubagentMessages(sessionId, agentId);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
logger.error(`Failed to read the history of subagent ${agentId}:`, error);
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
46
152
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The replay of subagents to a client on the subagents RFD form.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code keeps each subagent's history in its own transcript, and the
|
|
5
|
+
* subagent's metadata file names the Agent or Task call that launched it (see
|
|
6
|
+
* `subagentHistory`). The replay announces each child right after that call,
|
|
7
|
+
* under the session id it had live, the SDK agent id, and sends the child's
|
|
8
|
+
* history to the child session. Every child's end goes out before the replay
|
|
9
|
+
* ends, since no subagent from before the reload still runs.
|
|
10
|
+
*
|
|
11
|
+
* A launch without metadata has no known id, so it is not announced: the RFD
|
|
12
|
+
* forbids inventing one. Its Agent call stays an ordinary tool call of the
|
|
13
|
+
* parent, and a notice says that subagent history is missing.
|
|
14
|
+
*/
|
|
15
|
+
import type { SessionMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
16
|
+
import type { AcpSessionNotification, SubagentState } from "./acp-subagents.js";
|
|
17
|
+
import type { SubagentHistory, SubagentRecord } from "./subagent-history.js";
|
|
18
|
+
type Publish = (notification: AcpSessionNotification) => Promise<void>;
|
|
19
|
+
/** A child that the replay shows: its launch has metadata. */
|
|
20
|
+
export type ReplayChild = {
|
|
21
|
+
/** The Agent or Task tool use that launched it. */
|
|
22
|
+
launchId: string;
|
|
23
|
+
/** Its session id: the SDK agent id, as live. */
|
|
24
|
+
sessionId: string;
|
|
25
|
+
record: SubagentRecord;
|
|
26
|
+
/** The session that made the launch. */
|
|
27
|
+
parentSessionId: string;
|
|
28
|
+
/** The input of the launch call. */
|
|
29
|
+
input: Record<string, unknown>;
|
|
30
|
+
/** How the launch's tool result ended a foreground child's run. */
|
|
31
|
+
launchEnd?: SubagentState;
|
|
32
|
+
/** The state of the child's last persisted task notification. */
|
|
33
|
+
notified?: SubagentState;
|
|
34
|
+
announced: boolean;
|
|
35
|
+
promptSent: boolean;
|
|
36
|
+
};
|
|
37
|
+
export declare class RfdSubagentReplay {
|
|
38
|
+
private readonly history;
|
|
39
|
+
private readonly publish;
|
|
40
|
+
/** The children by the id of the tool use that launched each. */
|
|
41
|
+
private readonly children;
|
|
42
|
+
private readonly byAgentId;
|
|
43
|
+
private readonly sendMessages;
|
|
44
|
+
/** Launches without metadata, which the replay leaves out. */
|
|
45
|
+
private readonly omitted;
|
|
46
|
+
/** Children whose transcript could not be read: only their association is replayed. */
|
|
47
|
+
private withoutHistory;
|
|
48
|
+
constructor(history: SubagentHistory, publish: Publish);
|
|
49
|
+
/**
|
|
50
|
+
* Registers the launches, the launch results, and the SendMessage calls in
|
|
51
|
+
* `messages`, the transcript of the session `ownerSessionId`.
|
|
52
|
+
*/
|
|
53
|
+
scan(messages: readonly SessionMessage[], ownerSessionId: string): void;
|
|
54
|
+
/** The child that the tool use `toolUseId` launched, when the replay shows it. */
|
|
55
|
+
childOfLaunch(toolUseId: string): ReplayChild | undefined;
|
|
56
|
+
/** Announces `child` on its parent, as live: running, with the parent's labels. */
|
|
57
|
+
announce(child: ReplayChild): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* The rest of a child's user message to replay, or `null` when nothing is
|
|
60
|
+
* left, after its text went out as the child's incoming `session_message`.
|
|
61
|
+
*
|
|
62
|
+
* A child session has no user, so the text of its user messages is never a
|
|
63
|
+
* user message. The first text is the prompt of the launch. A message that
|
|
64
|
+
* a SendMessage call delivered (`origin.kind: "coordinator"`) is another
|
|
65
|
+
* incoming message. Any other text is harness text, such as "[Request
|
|
66
|
+
* interrupted by user for tool use]", which the live child session does not
|
|
67
|
+
* show. Tool results stay.
|
|
68
|
+
*/
|
|
69
|
+
childContent(child: ReplayChild, message: SessionMessage, content: unknown): Promise<unknown>;
|
|
70
|
+
/**
|
|
71
|
+
* Ends the replay of `child`'s history. `messages` is its transcript: when
|
|
72
|
+
* it is empty, the replay has only the child's association, and the prompt
|
|
73
|
+
* comes from the launch call.
|
|
74
|
+
*/
|
|
75
|
+
childReplayed(child: ReplayChild, messages: readonly SessionMessage[]): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Takes a persisted task notification. Returns whether it was a child's:
|
|
78
|
+
* its task id is the agent id.
|
|
79
|
+
*/
|
|
80
|
+
taskNotification(taskId: string, status: string): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Sends the end of every announced child, and a notice when subagent history
|
|
83
|
+
* is missing. The end is the state of the child's last task notification,
|
|
84
|
+
* else `cancelled` for a child the user stopped, else the result of its
|
|
85
|
+
* foreground launch, else `idle` with no stop reason.
|
|
86
|
+
*/
|
|
87
|
+
finish(supportsNotices: boolean, rootSessionId: string): Promise<void>;
|
|
88
|
+
private registerLaunch;
|
|
89
|
+
/** The prompt of the launch, with the launch's id, as live. */
|
|
90
|
+
private sendPrompt;
|
|
91
|
+
/**
|
|
92
|
+
* A message that a SendMessage call delivered. Claude Code frames it for the
|
|
93
|
+
* child's model ("The coordinator sent a message while you were working:",
|
|
94
|
+
* then the message, then an instruction), so the call it came from is the
|
|
95
|
+
* first undelivered SendMessage to the child whose message the delivered
|
|
96
|
+
* text contains. With that call, the message has the call's id and text and
|
|
97
|
+
* its sender, as live; without it, its transcript id and the delivered text.
|
|
98
|
+
*/
|
|
99
|
+
private sendDelivery;
|
|
100
|
+
private sendMessage;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The end of a foreground launch from its tool result: an error result that
|
|
104
|
+
* names a stop is a cancellation, any other error a failure.
|
|
105
|
+
*/
|
|
106
|
+
export declare function replaySubagentTerminalState(block: Record<string, unknown>): "completed" | "failed" | "cancelled";
|
|
107
|
+
export {};
|
|
108
|
+
//# sourceMappingURL=subagent-replay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-replay.d.ts","sourceRoot":"","sources":["../src/subagent-replay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAShF,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE7E,KAAK,OAAO,GAAG,CAAC,YAAY,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvE,8DAA8D;AAC9D,MAAM,MAAM,WAAW,GAAG;IACxB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;IACvB,wCAAwC;IACxC,eAAe,EAAE,MAAM,CAAC;IACxB,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,mEAAmE;IACnE,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,iEAAiE;IACjE,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAUF,qBAAa,iBAAiB;IAW1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAX1B,iEAAiE;IACjE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAC5D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyB;IACtD,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,uFAAuF;IACvF,OAAO,CAAC,cAAc,CAAK;gBAGR,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,OAAO;IAGnC;;;OAGG;IACH,IAAI,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI;IA6BvE,kFAAkF;IAClF,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAIzD,mFAAmF;IAC7E,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBjD;;;;;;;;;;OAUG;IACG,YAAY,CAChB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,OAAO,CAAC;IAanB;;;;OAIG;IACG,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAO3F;;;OAGG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO;IAOzD;;;;;OAKG;IACG,MAAM,CAAC,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0B5E,OAAO,CAAC,cAAc;IAwBtB,+DAA+D;YACjD,UAAU;IAKxB;;;;;;;OAOG;YACW,YAAY;YAoBZ,WAAW;CAiB1B;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,WAAW,GAAG,QAAQ,GAAG,WAAW,CAMtC"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { isNativeSubagentControlTool, nativeSubagentState, rfdEndState, rfdLabels, } from "./native-subagents.js";
|
|
2
|
+
import { noticeUpdate } from "./session-notices.js";
|
|
3
|
+
export class RfdSubagentReplay {
|
|
4
|
+
history;
|
|
5
|
+
publish;
|
|
6
|
+
/** The children by the id of the tool use that launched each. */
|
|
7
|
+
children = new Map();
|
|
8
|
+
byAgentId = new Map();
|
|
9
|
+
sendMessages = [];
|
|
10
|
+
/** Launches without metadata, which the replay leaves out. */
|
|
11
|
+
omitted = new Set();
|
|
12
|
+
/** Children whose transcript could not be read: only their association is replayed. */
|
|
13
|
+
withoutHistory = 0;
|
|
14
|
+
constructor(history, publish) {
|
|
15
|
+
this.history = history;
|
|
16
|
+
this.publish = publish;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Registers the launches, the launch results, and the SendMessage calls in
|
|
20
|
+
* `messages`, the transcript of the session `ownerSessionId`.
|
|
21
|
+
*/
|
|
22
|
+
scan(messages, ownerSessionId) {
|
|
23
|
+
for (const message of messages) {
|
|
24
|
+
for (const block of contentBlocks(message)) {
|
|
25
|
+
if (block.type === "tool_use" && typeof block.id === "string") {
|
|
26
|
+
if (isNativeSubagentControlTool(block.name)) {
|
|
27
|
+
this.registerLaunch(block.id, inputOf(block), ownerSessionId);
|
|
28
|
+
}
|
|
29
|
+
else if (block.name === "SendMessage") {
|
|
30
|
+
const input = inputOf(block);
|
|
31
|
+
if (typeof input.to === "string" && typeof input.message === "string") {
|
|
32
|
+
this.sendMessages.push({
|
|
33
|
+
toolUseId: block.id,
|
|
34
|
+
to: input.to,
|
|
35
|
+
message: input.message,
|
|
36
|
+
senderSessionId: ownerSessionId,
|
|
37
|
+
delivered: false,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
|
43
|
+
const child = this.children.get(block.tool_use_id);
|
|
44
|
+
// A background launch answers at once; its result is not the end.
|
|
45
|
+
if (child && !child.record.background) {
|
|
46
|
+
child.launchEnd = replaySubagentTerminalState(block);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** The child that the tool use `toolUseId` launched, when the replay shows it. */
|
|
53
|
+
childOfLaunch(toolUseId) {
|
|
54
|
+
return this.children.get(toolUseId);
|
|
55
|
+
}
|
|
56
|
+
/** Announces `child` on its parent, as live: running, with the parent's labels. */
|
|
57
|
+
async announce(child) {
|
|
58
|
+
if (child.announced)
|
|
59
|
+
return;
|
|
60
|
+
const labels = rfdLabels(child.record.name ?? child.input.name, child.record.agentType ?? child.input.subagent_type, child.record.description ?? child.input.description);
|
|
61
|
+
await this.publish({
|
|
62
|
+
sessionId: child.parentSessionId,
|
|
63
|
+
update: {
|
|
64
|
+
sessionUpdate: "subagent_update",
|
|
65
|
+
sessionId: child.sessionId,
|
|
66
|
+
...(labels.title ? { title: labels.title } : {}),
|
|
67
|
+
...(labels.description ? { description: labels.description } : {}),
|
|
68
|
+
state: { state: "running" },
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
child.announced = true;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The rest of a child's user message to replay, or `null` when nothing is
|
|
75
|
+
* left, after its text went out as the child's incoming `session_message`.
|
|
76
|
+
*
|
|
77
|
+
* A child session has no user, so the text of its user messages is never a
|
|
78
|
+
* user message. The first text is the prompt of the launch. A message that
|
|
79
|
+
* a SendMessage call delivered (`origin.kind: "coordinator"`) is another
|
|
80
|
+
* incoming message. Any other text is harness text, such as "[Request
|
|
81
|
+
* interrupted by user for tool use]", which the live child session does not
|
|
82
|
+
* show. Tool results stay.
|
|
83
|
+
*/
|
|
84
|
+
async childContent(child, message, content) {
|
|
85
|
+
const text = textOf(content);
|
|
86
|
+
if (text.trim()) {
|
|
87
|
+
if (isDelivery(message))
|
|
88
|
+
await this.sendDelivery(child, message, text);
|
|
89
|
+
else if (!child.promptSent)
|
|
90
|
+
await this.sendPrompt(child, text);
|
|
91
|
+
}
|
|
92
|
+
if (!Array.isArray(content))
|
|
93
|
+
return null;
|
|
94
|
+
const rest = content.filter((block) => !(typeof block === "object" && block !== null && block.type === "text"));
|
|
95
|
+
return rest.length > 0 ? rest : null;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Ends the replay of `child`'s history. `messages` is its transcript: when
|
|
99
|
+
* it is empty, the replay has only the child's association, and the prompt
|
|
100
|
+
* comes from the launch call.
|
|
101
|
+
*/
|
|
102
|
+
async childReplayed(child, messages) {
|
|
103
|
+
if (messages.length === 0)
|
|
104
|
+
this.withoutHistory++;
|
|
105
|
+
if (!child.promptSent && typeof child.input.prompt === "string") {
|
|
106
|
+
await this.sendPrompt(child, child.input.prompt);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Takes a persisted task notification. Returns whether it was a child's:
|
|
111
|
+
* its task id is the agent id.
|
|
112
|
+
*/
|
|
113
|
+
taskNotification(taskId, status) {
|
|
114
|
+
const child = this.byAgentId.get(taskId);
|
|
115
|
+
if (!child)
|
|
116
|
+
return false;
|
|
117
|
+
child.notified = nativeSubagentState(status) ?? child.notified;
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Sends the end of every announced child, and a notice when subagent history
|
|
122
|
+
* is missing. The end is the state of the child's last task notification,
|
|
123
|
+
* else `cancelled` for a child the user stopped, else the result of its
|
|
124
|
+
* foreground launch, else `idle` with no stop reason.
|
|
125
|
+
*/
|
|
126
|
+
async finish(supportsNotices, rootSessionId) {
|
|
127
|
+
for (const child of [...this.children.values()].reverse()) {
|
|
128
|
+
if (!child.announced)
|
|
129
|
+
continue;
|
|
130
|
+
const end = child.notified ?? (child.record.stoppedByUser ? "cancelled" : child.launchEnd);
|
|
131
|
+
const state = end ? rfdEndState(end, undefined) : { state: "idle" };
|
|
132
|
+
await this.publish({
|
|
133
|
+
sessionId: child.parentSessionId,
|
|
134
|
+
update: { sessionUpdate: "subagent_update", sessionId: child.sessionId, state },
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
const missing = this.omitted.size + this.withoutHistory;
|
|
138
|
+
if (missing > 0 && supportsNotices) {
|
|
139
|
+
await this.publish({
|
|
140
|
+
sessionId: rootSessionId,
|
|
141
|
+
update: noticeUpdate({
|
|
142
|
+
severity: "info",
|
|
143
|
+
title: "Some subagent history is not available",
|
|
144
|
+
description: missing === 1
|
|
145
|
+
? "The history of 1 subagent of this session could not be restored."
|
|
146
|
+
: `The history of ${missing} subagents of this session could not be restored.`,
|
|
147
|
+
}),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
registerLaunch(toolUseId, input, parentSessionId) {
|
|
152
|
+
if (this.children.has(toolUseId))
|
|
153
|
+
return;
|
|
154
|
+
const record = this.history.agents.get(toolUseId);
|
|
155
|
+
if (!record) {
|
|
156
|
+
this.omitted.add(toolUseId);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const child = {
|
|
160
|
+
launchId: toolUseId,
|
|
161
|
+
sessionId: record.agentId,
|
|
162
|
+
record,
|
|
163
|
+
parentSessionId,
|
|
164
|
+
input,
|
|
165
|
+
announced: false,
|
|
166
|
+
promptSent: false,
|
|
167
|
+
};
|
|
168
|
+
this.children.set(toolUseId, child);
|
|
169
|
+
this.byAgentId.set(record.agentId, child);
|
|
170
|
+
}
|
|
171
|
+
/** The prompt of the launch, with the launch's id, as live. */
|
|
172
|
+
async sendPrompt(child, text) {
|
|
173
|
+
child.promptSent = true;
|
|
174
|
+
await this.sendMessage(child, child.launchId, text, child.parentSessionId);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* A message that a SendMessage call delivered. Claude Code frames it for the
|
|
178
|
+
* child's model ("The coordinator sent a message while you were working:",
|
|
179
|
+
* then the message, then an instruction), so the call it came from is the
|
|
180
|
+
* first undelivered SendMessage to the child whose message the delivered
|
|
181
|
+
* text contains. With that call, the message has the call's id and text and
|
|
182
|
+
* its sender, as live; without it, its transcript id and the delivered text.
|
|
183
|
+
*/
|
|
184
|
+
async sendDelivery(child, message, text) {
|
|
185
|
+
const names = [child.sessionId, child.record.name, child.input.name];
|
|
186
|
+
const call = this.sendMessages.find((candidate) => !candidate.delivered &&
|
|
187
|
+
names.includes(candidate.to) &&
|
|
188
|
+
text.includes(candidate.message.trim()));
|
|
189
|
+
if (call) {
|
|
190
|
+
call.delivered = true;
|
|
191
|
+
await this.sendMessage(child, call.toolUseId, call.message, call.senderSessionId);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
await this.sendMessage(child, message.uuid, text, undefined);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async sendMessage(child, messageId, text, sender) {
|
|
198
|
+
await this.publish({
|
|
199
|
+
sessionId: child.sessionId,
|
|
200
|
+
update: {
|
|
201
|
+
sessionUpdate: "session_message",
|
|
202
|
+
messageId,
|
|
203
|
+
...(sender ? { senderSessionId: sender } : {}),
|
|
204
|
+
recipientSessionId: child.sessionId,
|
|
205
|
+
content: [{ type: "text", text }],
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The end of a foreground launch from its tool result: an error result that
|
|
212
|
+
* names a stop is a cancellation, any other error a failure.
|
|
213
|
+
*/
|
|
214
|
+
export function replaySubagentTerminalState(block) {
|
|
215
|
+
if (block.is_error !== true)
|
|
216
|
+
return "completed";
|
|
217
|
+
const text = replayContentText(block.content).toLowerCase();
|
|
218
|
+
return /\b(?:cancelled|canceled|interrupted|stopped|killed)\b/.test(text)
|
|
219
|
+
? "cancelled"
|
|
220
|
+
: "failed";
|
|
221
|
+
}
|
|
222
|
+
function replayContentText(value, seen = new Set()) {
|
|
223
|
+
if (typeof value === "string")
|
|
224
|
+
return value;
|
|
225
|
+
if (typeof value !== "object" || value === null || seen.has(value))
|
|
226
|
+
return "";
|
|
227
|
+
seen.add(value);
|
|
228
|
+
if (Array.isArray(value))
|
|
229
|
+
return value.map((item) => replayContentText(item, seen)).join(" ");
|
|
230
|
+
const record = value;
|
|
231
|
+
return [record.text, record.content, record.message]
|
|
232
|
+
.map((item) => replayContentText(item, seen))
|
|
233
|
+
.filter(Boolean)
|
|
234
|
+
.join(" ");
|
|
235
|
+
}
|
|
236
|
+
function contentBlocks(message) {
|
|
237
|
+
const content = message.message?.content;
|
|
238
|
+
return Array.isArray(content)
|
|
239
|
+
? content.filter((block) => typeof block === "object" && block !== null)
|
|
240
|
+
: [];
|
|
241
|
+
}
|
|
242
|
+
function inputOf(block) {
|
|
243
|
+
return typeof block.input === "object" && block.input !== null
|
|
244
|
+
? block.input
|
|
245
|
+
: {};
|
|
246
|
+
}
|
|
247
|
+
/** The text of user message content: a string, or its text blocks. */
|
|
248
|
+
function textOf(content) {
|
|
249
|
+
if (typeof content === "string")
|
|
250
|
+
return content;
|
|
251
|
+
if (!Array.isArray(content))
|
|
252
|
+
return "";
|
|
253
|
+
return content
|
|
254
|
+
.flatMap((block) => typeof block === "object" && block !== null && block.type === "text" ? [block.text] : [])
|
|
255
|
+
.filter((text) => typeof text === "string")
|
|
256
|
+
.join("\n");
|
|
257
|
+
}
|
|
258
|
+
/** A message that a SendMessage call delivered to the child. */
|
|
259
|
+
function isDelivery(message) {
|
|
260
|
+
return message.origin?.kind === "coordinator";
|
|
261
|
+
}
|