@agentclientprotocol/claude-agent-acp 0.85.2-preview.9 → 0.86.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/dist/acp-agent.d.ts +67 -21
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +264 -68
- package/dist/async-tasks.d.ts +52 -3
- package/dist/async-tasks.d.ts.map +1 -1
- package/dist/async-tasks.js +185 -42
- package/dist/clear-context-coordinator.d.ts +1 -0
- package/dist/clear-context-coordinator.d.ts.map +1 -1
- package/dist/clear-context-coordinator.js +2 -1
- package/dist/diff.d.ts +45 -8
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +111 -30
- package/dist/native-subagents.d.ts +7 -4
- package/dist/native-subagents.d.ts.map +1 -1
- package/dist/native-subagents.js +22 -5
- package/dist/permissions/presentation.d.ts +17 -2
- package/dist/permissions/presentation.d.ts.map +1 -1
- package/dist/permissions/presentation.js +4 -0
- package/dist/session-config-ids.d.ts +1 -0
- package/dist/session-config-ids.d.ts.map +1 -1
- package/dist/session-config-ids.js +1 -0
- package/dist/tool-calls/background.d.ts +5 -2
- package/dist/tool-calls/background.d.ts.map +1 -1
- package/dist/tool-calls/background.js +6 -11
- package/dist/tool-calls/client-capabilities.d.ts +20 -2
- package/dist/tool-calls/client-capabilities.d.ts.map +1 -1
- package/dist/tool-calls/client-capabilities.js +14 -4
- package/dist/tool-calls/facts.d.ts +8 -1
- package/dist/tool-calls/facts.d.ts.map +1 -1
- package/dist/tool-calls/renderer.d.ts +5 -1
- package/dist/tool-calls/renderer.d.ts.map +1 -1
- package/dist/tool-calls/renderer.js +14 -3
- package/dist/tool-calls/reporters/bash.d.ts.map +1 -1
- package/dist/tool-calls/reporters/bash.js +32 -5
- package/dist/tool-calls/reporters/file-edit.d.ts +1 -1
- package/dist/tool-calls/reporters/file-edit.d.ts.map +1 -1
- package/dist/tool-calls/reporters/file-edit.js +20 -14
- package/dist/v2/agent.d.ts +2 -0
- package/dist/v2/agent.d.ts.map +1 -1
- package/dist/v2/agent.js +109 -14
- package/dist/v2/initialize.d.ts +2 -5
- package/dist/v2/initialize.d.ts.map +1 -1
- package/dist/v2/initialize.js +42 -11
- package/dist/v2/permission.d.ts +25 -0
- package/dist/v2/permission.d.ts.map +1 -0
- package/dist/v2/permission.js +38 -0
- package/dist/v2/session-update.d.ts +6 -0
- package/dist/v2/session-update.d.ts.map +1 -1
- package/dist/v2/session-update.js +19 -0
- package/dist/v2/session.d.ts +29 -4
- package/dist/v2/session.d.ts.map +1 -1
- package/dist/v2/session.js +41 -6
- package/dist/v2/terminal.d.ts +42 -0
- package/dist/v2/terminal.d.ts.map +1 -0
- package/dist/v2/terminal.js +95 -0
- package/dist/v2/tool-call.d.ts +38 -0
- package/dist/v2/tool-call.d.ts.map +1 -0
- package/dist/v2/tool-call.js +49 -0
- package/package.json +1 -1
|
@@ -118,8 +118,9 @@ export class AcpToolCallRenderer {
|
|
|
118
118
|
permissionToolCall(toolUse, options = {}) {
|
|
119
119
|
const facts = this.facts(toolUse, options.cwd);
|
|
120
120
|
if (!this.capabilities.air.client) {
|
|
121
|
-
// The upstream shape: the whole tool call again.
|
|
122
|
-
|
|
121
|
+
// The upstream shape: the whole tool call again. A v2 client also gets
|
|
122
|
+
// the exact preview patch, which the tool call does not show.
|
|
123
|
+
const content = options.previewContent ?? this.toolUseContent(toolUse.id, facts);
|
|
123
124
|
const locations = [...(facts.locations ?? []), ...(options.extraLocations ?? [])];
|
|
124
125
|
return {
|
|
125
126
|
toolCallId: toolUse.id,
|
|
@@ -191,7 +192,17 @@ export class AcpToolCallRenderer {
|
|
|
191
192
|
...(this.capabilities.terminalOutputDelta
|
|
192
193
|
? { terminal_output_delta: output }
|
|
193
194
|
: { terminal_output: output }),
|
|
194
|
-
terminal_exit: {
|
|
195
|
+
terminal_exit: {
|
|
196
|
+
terminal_id: terminalId,
|
|
197
|
+
// AIR keeps the numbers it always got: 1 for a failed or
|
|
198
|
+
// interrupted command, else the exit code, or 0 when unknown.
|
|
199
|
+
exit_code: this.capabilities.air.client
|
|
200
|
+
? result.is_error === true || command.interrupted
|
|
201
|
+
? 1
|
|
202
|
+
: (command.exitCode ?? 0)
|
|
203
|
+
: (command.exitCode ?? null),
|
|
204
|
+
signal: null,
|
|
205
|
+
},
|
|
195
206
|
},
|
|
196
207
|
};
|
|
197
208
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../../src/tool-calls/reporters/bash.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAElG,gFAAgF;AAChF,qBAAa,YAAa,YAAW,YAAY;IAC/C,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY;IAUrC,iFAAiF;IACjF,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG,eAAe,GAAG,SAAS;IAIpE,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,iBAAiB,GAAG,eAAe;
|
|
1
|
+
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../../src/tool-calls/reporters/bash.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAElG,gFAAgF;AAChF,qBAAa,YAAa,YAAW,YAAY;IAC/C,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY;IAUrC,iFAAiF;IACjF,WAAW,CAAC,OAAO,EAAE,iBAAiB,GAAG,eAAe,GAAG,SAAS;IAIpE,UAAU,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,iBAAiB,GAAG,eAAe;CAsFvE"}
|
|
@@ -30,7 +30,11 @@ export class BashReporter {
|
|
|
30
30
|
// 4. Array content: text blocks for stdout, or image blocks when the
|
|
31
31
|
// command produces an image.
|
|
32
32
|
let output = "";
|
|
33
|
-
|
|
33
|
+
// Claude Code accepts a non-zero code as a success only with a
|
|
34
|
+
// `returnCodeInterpretation` (grep's "No matches found"), so a success
|
|
35
|
+
// without one exited with 0.
|
|
36
|
+
let exitCode = isError ? undefined : 0;
|
|
37
|
+
let interrupted = false;
|
|
34
38
|
const structuredBash = structuredResult(structured);
|
|
35
39
|
if (structuredBash &&
|
|
36
40
|
typeof structuredBash.stdout === "string" &&
|
|
@@ -38,12 +42,15 @@ export class BashReporter {
|
|
|
38
42
|
!structuredBash.isImage &&
|
|
39
43
|
structuredBash.backgroundTaskId === undefined) {
|
|
40
44
|
output = [structuredBash.stdout, structuredBash.stderr].filter(Boolean).join("\n");
|
|
41
|
-
// The CLI appends its abort marker only to the model-facing text
|
|
42
|
-
//
|
|
45
|
+
// The CLI appends its abort marker only to the model-facing text. The
|
|
46
|
+
// result does not carry the code of the aborted command.
|
|
43
47
|
if (structuredBash.interrupted) {
|
|
44
48
|
output = [output, "[Command was aborted before completion]"].filter(Boolean).join("\n");
|
|
45
|
-
exitCode =
|
|
49
|
+
exitCode = undefined;
|
|
50
|
+
interrupted = true;
|
|
46
51
|
}
|
|
52
|
+
if (structuredBash.returnCodeInterpretation !== undefined)
|
|
53
|
+
exitCode = undefined;
|
|
47
54
|
// Structured stdout is clipped when the full output was persisted to
|
|
48
55
|
// disk. Without this note the clip is silent.
|
|
49
56
|
if (typeof structuredBash.persistedOutputPath === "string") {
|
|
@@ -78,6 +85,26 @@ export class BashReporter {
|
|
|
78
85
|
}
|
|
79
86
|
output = content.map((c) => c.text).join("\n");
|
|
80
87
|
}
|
|
81
|
-
|
|
88
|
+
if (isError && exitCode === undefined)
|
|
89
|
+
exitCode = failureExitCode(output);
|
|
90
|
+
// A backgrounded command is still running: the result only announces it.
|
|
91
|
+
if (structuredBash?.backgroundTaskId !== undefined)
|
|
92
|
+
exitCode = undefined;
|
|
93
|
+
return {
|
|
94
|
+
command: {
|
|
95
|
+
output,
|
|
96
|
+
...(exitCode !== undefined ? { exitCode } : {}),
|
|
97
|
+
...(interrupted ? { interrupted } : {}),
|
|
98
|
+
},
|
|
99
|
+
};
|
|
82
100
|
}
|
|
83
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* The exit code that the text of a failed command names. Claude Code starts the
|
|
104
|
+
* text of a command that exited with a failing code with `Exit code N`. Other
|
|
105
|
+
* failures, such as a denial or a command that could not start, name none.
|
|
106
|
+
*/
|
|
107
|
+
function failureExitCode(text) {
|
|
108
|
+
const match = /^Exit code (\d+)(?:\n|$)/.exec(text);
|
|
109
|
+
return match ? Number(match[1]) : undefined;
|
|
110
|
+
}
|
|
@@ -22,7 +22,7 @@ type NormalizedWriteInput = Partial<FileWriteInput> & {
|
|
|
22
22
|
export declare function normalizeWriteInput(input: unknown): NormalizedWriteInput | undefined;
|
|
23
23
|
/** Edit: the diff holds the old and the new text. */
|
|
24
24
|
export declare class EditReporter implements ToolReporter {
|
|
25
|
-
toolUse(input: unknown, { cwd }: ToolUseContext): ToolUseFacts;
|
|
25
|
+
toolUse(input: unknown, { cwd, capabilities }: ToolUseContext): ToolUseFacts;
|
|
26
26
|
toolResult(): ToolResultFacts;
|
|
27
27
|
hookResult(toolResponse: unknown, context: ToolUseContext): Promise<ToolResultFacts>;
|
|
28
28
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-edit.d.ts","sourceRoot":"","sources":["../../../src/tool-calls/reporters/file-edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,cAAc,EAEf,MAAM,6CAA6C,CAAC;
|
|
1
|
+
{"version":3,"file":"file-edit.d.ts","sourceRoot":"","sources":["../../../src/tool-calls/reporters/file-edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,cAAc,EAEf,MAAM,6CAA6C,CAAC;AAQrD,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,YAAY,EACb,MAAM,aAAa,CAAC;AAErB;;;GAGG;AACH,qBAAa,aAAc,YAAW,YAAY;IAChD,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,cAAc,GAAG,YAAY;IAoCpF,UAAU,IAAI,eAAe;IAI7B,UAAU,CAAC,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;CAGrF;AAED,yFAAyF;AACzF,KAAK,oBAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAI9E;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,GAAG,SAAS,CAYpF;AAED,qDAAqD;AACrD,qBAAa,YAAa,YAAW,YAAY;IAC/C,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE,cAAc,GAAG,YAAY;IA0B5E,UAAU,IAAI,eAAe;IAI7B,UAAU,CAAC,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;CAGrF;AAyCD;;;;;;;GAOG;AACH,qBAAa,oBAAqB,YAAW,YAAY;IACvD,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,EAAE,cAAc,GAAG,YAAY;IAsB5E;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,eAAe;CAMxD"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { patchUpdateFromDiffToolResponse, toolUpdateFromDiffToolResponse } from "../../diff.js";
|
|
1
|
+
import { patchUpdateFromDiffToolResponse, toolUpdateFromDiffToolResponse, v2UpdateFromDiffToolResponse, } from "../../diff.js";
|
|
2
2
|
import { markdownEscape, resultText, textContent, toDisplayPath } from "../content.js";
|
|
3
3
|
/**
|
|
4
4
|
* Write: the diff holds the file text. The PostToolUse hook sends the final
|
|
@@ -14,12 +14,15 @@ export class WriteReporter {
|
|
|
14
14
|
locations: write?.file_path ? [{ path: write.file_path }] : [],
|
|
15
15
|
};
|
|
16
16
|
if (write?.file_path) {
|
|
17
|
-
// A negotiated client
|
|
18
|
-
// from the PostToolUse hook. The input does not tell
|
|
19
|
-
// exists, so the live tool call shows no diff: a diff
|
|
20
|
-
// text would claim a creation. The adapter reads the
|
|
21
|
-
// preview. A replay has no preview and no hook, so it
|
|
22
|
-
|
|
17
|
+
// A negotiated client and a v2 client get the exact patch from the
|
|
18
|
+
// approval preview or from the PostToolUse hook. The input does not tell
|
|
19
|
+
// whether the file exists, so the live tool call shows no diff: a diff
|
|
20
|
+
// without the old text would claim a creation. The adapter reads the
|
|
21
|
+
// file only for the preview. A replay has no preview and no hook, so it
|
|
22
|
+
// keeps the diff, except for a v2 client: a v2 diff names the operation,
|
|
23
|
+
// which the input does not tell.
|
|
24
|
+
const inputDiff = capabilities.v2 ? false : !capabilities.diffPatch || replay;
|
|
25
|
+
if (inputDiff) {
|
|
23
26
|
facts.change = [
|
|
24
27
|
{
|
|
25
28
|
type: "diff",
|
|
@@ -68,7 +71,7 @@ export function normalizeWriteInput(input) {
|
|
|
68
71
|
}
|
|
69
72
|
/** Edit: the diff holds the old and the new text. */
|
|
70
73
|
export class EditReporter {
|
|
71
|
-
toolUse(input, { cwd }) {
|
|
74
|
+
toolUse(input, { cwd, capabilities }) {
|
|
72
75
|
const edit = input;
|
|
73
76
|
const displayPath = edit?.file_path ? toDisplayPath(edit.file_path, cwd) : undefined;
|
|
74
77
|
const facts = {
|
|
@@ -76,10 +79,11 @@ export class EditReporter {
|
|
|
76
79
|
kind: "edit",
|
|
77
80
|
locations: edit?.file_path ? [{ path: edit.file_path }] : [],
|
|
78
81
|
};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
// The standard diff, also for a client that negotiated patches: the input
|
|
83
|
+
// holds a snippet, not the file, so a patch would need line numbers that
|
|
84
|
+
// the adapter does not know here. A v2 diff cannot hold a snippet, so a v2
|
|
85
|
+
// client gets the patch from the PostToolUse hook alone.
|
|
86
|
+
if (edit?.file_path && (edit.old_string || edit.new_string) && !capabilities.v2) {
|
|
83
87
|
facts.change = [
|
|
84
88
|
{
|
|
85
89
|
type: "diff",
|
|
@@ -104,12 +108,14 @@ export class EditReporter {
|
|
|
104
108
|
* PostToolUse `tool_response`. For Write it replaces the optimistic creation
|
|
105
109
|
* diff with the real diff of an updated file. A negotiated client gets an
|
|
106
110
|
* exact git patch built from the written file, or the standard diff when none
|
|
107
|
-
* can be built.
|
|
111
|
+
* can be built. A v2 client gets a v2 diff.
|
|
108
112
|
*/
|
|
109
113
|
async function finalChange(toolResponse, { capabilities }) {
|
|
114
|
+
if (capabilities.v2)
|
|
115
|
+
return v2UpdateFromDiffToolResponse(toolResponse);
|
|
110
116
|
if (!capabilities.diffPatch)
|
|
111
117
|
return toolUpdateFromDiffToolResponse(toolResponse);
|
|
112
|
-
const patch = await patchUpdateFromDiffToolResponse(toolResponse);
|
|
118
|
+
const patch = await patchUpdateFromDiffToolResponse(toolResponse, "air");
|
|
113
119
|
if (patch)
|
|
114
120
|
return patch;
|
|
115
121
|
const created = createdFileDiff(toolResponse);
|
package/dist/v2/agent.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { ClaudeAcpAgent, type Logger } from "../acp-agent.js";
|
|
|
6
6
|
* As in `v1AgentApp`, the agent of the connection is created when the
|
|
7
7
|
* connection opens, before the connection processes any inbound message.
|
|
8
8
|
* `onAgent` receives it for the owner of the process (shutdown).
|
|
9
|
+
*
|
|
10
|
+
* The provider methods pass through: their types are the same in v1 and v2.
|
|
9
11
|
*/
|
|
10
12
|
export declare function v2AgentApp(logger: Logger | undefined, onAgent: (agent: ClaudeAcpAgent) => void): v2.AgentApp;
|
|
11
13
|
//# sourceMappingURL=agent.d.ts.map
|
package/dist/v2/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/v2/agent.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/v2/agent.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAkB,KAAK,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAkB9E;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GACvC,EAAE,CAAC,QAAQ,CA4Db"}
|
package/dist/v2/agent.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
|
|
2
2
|
import { ClaudeAcpAgent } from "../acp-agent.js";
|
|
3
3
|
import { v1InitializeRequest, v2InitializeResponse } from "./initialize.js";
|
|
4
|
+
import { v1PermissionResponse, v2PermissionRequest } from "./permission.js";
|
|
4
5
|
import { v2Prompt } from "./prompt.js";
|
|
5
|
-
import { v1NewSessionRequest,
|
|
6
|
+
import { v1ForkSessionRequests, v1NewSessionRequest, v1RestoreSessionRequest, v1SetSessionConfigOptionRequest, v2ConfigOptions, v2NewSessionResponse, v2ResumeSessionResponse, } from "./session.js";
|
|
6
7
|
import { v2SessionUpdate } from "./session-update.js";
|
|
8
|
+
import { V2Terminals } from "./terminal.js";
|
|
7
9
|
/**
|
|
8
10
|
* The ACP v2 surface, for one connection.
|
|
9
11
|
*
|
|
10
12
|
* As in `v1AgentApp`, the agent of the connection is created when the
|
|
11
13
|
* connection opens, before the connection processes any inbound message.
|
|
12
14
|
* `onAgent` receives it for the owner of the process (shutdown).
|
|
15
|
+
*
|
|
16
|
+
* The provider methods pass through: their types are the same in v1 and v2.
|
|
13
17
|
*/
|
|
14
18
|
export function v2AgentApp(logger, onAgent) {
|
|
15
19
|
let agent;
|
|
@@ -18,16 +22,36 @@ export function v2AgentApp(logger, onAgent) {
|
|
|
18
22
|
.agent({ name: "claude-code-acp" })
|
|
19
23
|
.onConnect((connection) => {
|
|
20
24
|
client = new V2ClientConnection(connection.client, logger ?? console);
|
|
21
|
-
agent = new ClaudeAcpAgent(client, logger);
|
|
25
|
+
agent = new ClaudeAcpAgent(client, logger, { v2: true });
|
|
22
26
|
onAgent(agent);
|
|
23
27
|
})
|
|
24
28
|
.onRequest(v2.methods.agent.initialize, async ({ params }) => v2InitializeResponse(await agent.initialize(v1InitializeRequest(params))))
|
|
25
29
|
.onRequest(v2.methods.agent.auth.login, ({ params }) => agent.authenticate(params))
|
|
26
30
|
.onRequest(v2.methods.agent.auth.logout, ({ params }) => agent.logout(params))
|
|
31
|
+
.onRequest(v2.methods.agent.providers.list, ({ params }) => agent.unstable_listProviders(params))
|
|
32
|
+
.onRequest(v2.methods.agent.providers.set, ({ params }) => agent.unstable_setProvider(params))
|
|
33
|
+
.onRequest(v2.methods.agent.providers.disable, ({ params }) => agent.unstable_disableProvider(params))
|
|
27
34
|
.onRequest(v2.methods.agent.session.new, async ({ params }) => v2NewSessionResponse(await agent.newSession(v1NewSessionRequest(params))))
|
|
28
35
|
.onRequest(v2.methods.agent.session.list, ({ params }) => agent.listSessions(params))
|
|
29
|
-
.onRequest(v2.methods.agent.session.resume, async ({ params }) =>
|
|
30
|
-
|
|
36
|
+
.onRequest(v2.methods.agent.session.resume, async ({ params }) => {
|
|
37
|
+
const restore = v1RestoreSessionRequest(params);
|
|
38
|
+
return v2ResumeSessionResponse(restore.method === "resume"
|
|
39
|
+
? await agent.resumeSession(restore.request)
|
|
40
|
+
: await client.replaying(params.sessionId, () => agent.loadSession(restore.request)));
|
|
41
|
+
})
|
|
42
|
+
.onRequest(v2.methods.agent.session.fork, async ({ params }) => {
|
|
43
|
+
const requests = v1ForkSessionRequests(params);
|
|
44
|
+
const { sessionId } = await agent.unstable_forkSession(requests.fork);
|
|
45
|
+
return {
|
|
46
|
+
sessionId,
|
|
47
|
+
...v2ResumeSessionResponse(await agent.resumeSession(requests.resume(sessionId))),
|
|
48
|
+
};
|
|
49
|
+
})
|
|
50
|
+
.onRequest(v2.methods.agent.session.close, async ({ params }) => {
|
|
51
|
+
const response = await agent.closeSession(params);
|
|
52
|
+
client.terminals.forget(params.sessionId);
|
|
53
|
+
return response;
|
|
54
|
+
})
|
|
31
55
|
.onRequest(v2.methods.agent.session.delete, ({ params }) => agent.deleteSession(params))
|
|
32
56
|
.onRequest(v2.methods.agent.session.setConfigOption, async ({ params }) => {
|
|
33
57
|
const { configOptions, ...response } = await agent.setSessionConfigOption(v1SetSessionConfigOptionRequest(params));
|
|
@@ -41,16 +65,38 @@ export function v2AgentApp(logger, onAgent) {
|
|
|
41
65
|
/**
|
|
42
66
|
* The {@link AcpClient} of an ACP v2 connection: it sends the v1 messages of
|
|
43
67
|
* the agent as v2 messages.
|
|
44
|
-
*
|
|
45
|
-
* Permission requests still reject until the v2 surface translates them.
|
|
46
68
|
*/
|
|
47
69
|
class V2ClientConnection {
|
|
48
70
|
ctx;
|
|
49
71
|
logger;
|
|
72
|
+
/**
|
|
73
|
+
* The sessions whose history replays, each with the messages that the
|
|
74
|
+
* replay has cleared so far.
|
|
75
|
+
*/
|
|
76
|
+
replays = new Map();
|
|
77
|
+
/** The display terminals of the commands that the agent runs. */
|
|
78
|
+
terminals = new V2Terminals();
|
|
50
79
|
constructor(ctx, logger) {
|
|
51
80
|
this.ctx = ctx;
|
|
52
81
|
this.logger = logger;
|
|
53
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Runs `restore`, which replays the history of `sessionId`.
|
|
85
|
+
*
|
|
86
|
+
* The agent replays messages as chunks. A v2 client may still hold a
|
|
87
|
+
* replayed message, and chunks append, so before the first replayed chunk of
|
|
88
|
+
* each message the client gets the message with no content, which clears it,
|
|
89
|
+
* as v2 requires of a message replayed as chunks.
|
|
90
|
+
*/
|
|
91
|
+
async replaying(sessionId, restore) {
|
|
92
|
+
this.replays.set(sessionId, new Set());
|
|
93
|
+
try {
|
|
94
|
+
return await restore();
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
this.replays.delete(sessionId);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
54
100
|
/**
|
|
55
101
|
* Sends a v2 session update, and logs rather than rejects when that fails.
|
|
56
102
|
* It and {@link sessionUpdate} hand updates to the connection synchronously,
|
|
@@ -72,16 +118,48 @@ class V2ClientConnection {
|
|
|
72
118
|
return this.ctx.notify(method, params);
|
|
73
119
|
}
|
|
74
120
|
async sessionUpdate({ update, ...notification }) {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
121
|
+
const updates = this.v2Updates(notification.sessionId, update).flatMap((v2Update) => {
|
|
122
|
+
const clear = this.replayClear(notification.sessionId, v2Update);
|
|
123
|
+
return clear ? [clear, v2Update] : [v2Update];
|
|
124
|
+
});
|
|
125
|
+
// Every update goes to the connection before any is awaited, so an update
|
|
126
|
+
// that another call sends meanwhile cannot come between them.
|
|
127
|
+
await Promise.all(updates.map((v2Update) => this.ctx.notify(v2.methods.client.session.update, { ...notification, update: v2Update })));
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The v2 updates of a v1 update: for a tool call report, the updates of its
|
|
131
|
+
* terminal first, then the report itself (see {@link V2Terminals}).
|
|
132
|
+
*/
|
|
133
|
+
v2Updates(sessionId, update) {
|
|
134
|
+
if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") {
|
|
135
|
+
const v2Update = v2SessionUpdate(update);
|
|
136
|
+
return v2Update ? [v2Update] : [];
|
|
81
137
|
}
|
|
138
|
+
const { terminal, report } = this.terminals.split(sessionId, update);
|
|
139
|
+
const reportUpdate = report && v2SessionUpdate(report);
|
|
140
|
+
return [...terminal, ...(reportUpdate ? [reportUpdate] : [])];
|
|
82
141
|
}
|
|
83
|
-
|
|
84
|
-
|
|
142
|
+
/**
|
|
143
|
+
* The update that clears the message of a replayed chunk, when it is the
|
|
144
|
+
* first replayed chunk of that message (see {@link replaying}).
|
|
145
|
+
*/
|
|
146
|
+
replayClear(sessionId, update) {
|
|
147
|
+
const cleared = this.replays.get(sessionId);
|
|
148
|
+
if (!cleared)
|
|
149
|
+
return undefined;
|
|
150
|
+
const kind = MESSAGE_OF_CHUNK.get(update.sessionUpdate);
|
|
151
|
+
if (!kind)
|
|
152
|
+
return undefined;
|
|
153
|
+
const { messageId } = update;
|
|
154
|
+
const key = `${kind} ${messageId}`;
|
|
155
|
+
if (cleared.has(key))
|
|
156
|
+
return undefined;
|
|
157
|
+
cleared.add(key);
|
|
158
|
+
return emptyMessage(kind, messageId);
|
|
159
|
+
}
|
|
160
|
+
async requestPermission(params, signal) {
|
|
161
|
+
const response = await this.ctx.request(v2.methods.client.session.requestPermission, v2PermissionRequest(params), { cancellationSignal: signal });
|
|
162
|
+
return v1PermissionResponse(response);
|
|
85
163
|
}
|
|
86
164
|
// Elicitation is the same in v1 and v2.
|
|
87
165
|
createElicitation(params, signal) {
|
|
@@ -103,6 +181,23 @@ class V2ClientConnection {
|
|
|
103
181
|
return Promise.reject(new Error("ACP v2 has no client file system"));
|
|
104
182
|
}
|
|
105
183
|
}
|
|
184
|
+
/** The message update of each message chunk. */
|
|
185
|
+
const MESSAGE_OF_CHUNK = new Map([
|
|
186
|
+
["user_message_chunk", "user_message"],
|
|
187
|
+
["agent_message_chunk", "agent_message"],
|
|
188
|
+
["agent_thought_chunk", "agent_thought"],
|
|
189
|
+
]);
|
|
190
|
+
function emptyMessage(kind, messageId) {
|
|
191
|
+
const message = { messageId, content: [] };
|
|
192
|
+
switch (kind) {
|
|
193
|
+
case "user_message":
|
|
194
|
+
return { ...message, sessionUpdate: kind };
|
|
195
|
+
case "agent_message":
|
|
196
|
+
return { ...message, sessionUpdate: kind };
|
|
197
|
+
default:
|
|
198
|
+
return { ...message, sessionUpdate: kind };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
106
201
|
function isExtensionMethod(method) {
|
|
107
202
|
return method.startsWith("_");
|
|
108
203
|
}
|
package/dist/v2/initialize.d.ts
CHANGED
|
@@ -14,11 +14,8 @@ export declare function v1InitializeRequest(request: v2.InitializeRequest): Init
|
|
|
14
14
|
/**
|
|
15
15
|
* The v2 `initialize` response for the v1 response of the agent.
|
|
16
16
|
*
|
|
17
|
-
* It
|
|
18
|
-
*
|
|
19
|
-
* `resume`, `close`, `prompt`, `cancel`, and `update`), and the v2 surface of
|
|
20
|
-
* the adapter does not serve it yet. Nor does it advertise the unstable
|
|
21
|
-
* `providers` methods, which the v2 surface does not serve yet either.
|
|
17
|
+
* It does not advertise what the v2 surface does not serve yet: the
|
|
18
|
+
* extensions of {@link UNSERVED_EXTENSION_KEYS}.
|
|
22
19
|
*/
|
|
23
20
|
export declare function v2InitializeResponse(response: InitializeResponse): v2.InitializeResponse;
|
|
24
21
|
//# 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,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAE/D,0DAA0D;AAC1D,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,
|
|
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,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAE/D,0DAA0D;AAC1D,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,CA0BpF;AASD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,EAAE,CAAC,kBAAkB,CAuBxF"}
|
package/dist/v2/initialize.js
CHANGED
|
@@ -20,10 +20,12 @@ export function v1InitializeRequest(request) {
|
|
|
20
20
|
// reads, writes, and runs commands itself.
|
|
21
21
|
fs: { readTextFile: false, writeTextFile: false },
|
|
22
22
|
terminal: false,
|
|
23
|
-
// Every v2 client handles boolean config options
|
|
24
|
-
// for a marker. With
|
|
25
|
-
// rather than as transcript messages
|
|
26
|
-
|
|
23
|
+
// Every v2 client handles boolean config options, notices, and
|
|
24
|
+
// compaction updates; v1 asks for a marker. With them, the agent reports
|
|
25
|
+
// advisories as notices rather than as transcript messages, and a
|
|
26
|
+
// compaction as a compaction entity rather than as a "Compact
|
|
27
|
+
// conversation" tool call, also on replay.
|
|
28
|
+
session: { configOptions: { boolean: {} }, notices: {}, compaction: {} },
|
|
27
29
|
// v1 marks terminal auth support with a boolean, v2 with an object.
|
|
28
30
|
auth: {
|
|
29
31
|
terminal: capabilities?.auth?.terminal != null,
|
|
@@ -35,30 +37,59 @@ export function v1InitializeRequest(request) {
|
|
|
35
37
|
...(request._meta != null ? { _meta: request._meta } : {}),
|
|
36
38
|
};
|
|
37
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* The top-level `_meta` keys of the agent's v1 `initialize` response that
|
|
42
|
+
* advertise an extension the v2 surface does not serve yet: `steering`, for
|
|
43
|
+
* `_session/steering`.
|
|
44
|
+
*/
|
|
45
|
+
const UNSERVED_EXTENSION_KEYS = ["steering"];
|
|
38
46
|
/**
|
|
39
47
|
* The v2 `initialize` response for the v1 response of the agent.
|
|
40
48
|
*
|
|
41
|
-
* It
|
|
42
|
-
*
|
|
43
|
-
* `resume`, `close`, `prompt`, `cancel`, and `update`), and the v2 surface of
|
|
44
|
-
* the adapter does not serve it yet. Nor does it advertise the unstable
|
|
45
|
-
* `providers` methods, which the v2 surface does not serve yet either.
|
|
49
|
+
* It does not advertise what the v2 surface does not serve yet: the
|
|
50
|
+
* extensions of {@link UNSERVED_EXTENSION_KEYS}.
|
|
46
51
|
*/
|
|
47
52
|
export function v2InitializeResponse(response) {
|
|
48
53
|
if (!response.agentInfo) {
|
|
49
54
|
throw new Error("ACP v2 requires agentInfo, and the agent reported none");
|
|
50
55
|
}
|
|
51
56
|
const meta = response.agentCapabilities?._meta;
|
|
57
|
+
const topMeta = Object.fromEntries(Object.entries(response._meta ?? {}).filter(([key]) => !UNSERVED_EXTENSION_KEYS.includes(key)));
|
|
52
58
|
return {
|
|
53
59
|
protocolVersion: v2.PROTOCOL_VERSION,
|
|
54
60
|
info: response.agentInfo,
|
|
55
61
|
// v1 `auth.logout` has no v2 counterpart: a v2 agent that advertises auth
|
|
56
62
|
// methods must serve `auth/logout`.
|
|
57
|
-
capabilities:
|
|
63
|
+
capabilities: {
|
|
64
|
+
session: V2_SESSION_CAPABILITIES,
|
|
65
|
+
// `providers/list`, `set`, and `disable`, which the agent serves for
|
|
66
|
+
// every client.
|
|
67
|
+
providers: {},
|
|
68
|
+
...(meta != null ? { _meta: meta } : {}),
|
|
69
|
+
},
|
|
58
70
|
authMethods: (response.authMethods ?? []).map(v2AuthMethod),
|
|
59
|
-
...(
|
|
71
|
+
...(Object.keys(topMeta).length > 0 ? { _meta: topMeta } : {}),
|
|
60
72
|
};
|
|
61
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* The session capabilities of the v2 surface, which serves each through a v1
|
|
76
|
+
* method of the agent.
|
|
77
|
+
*
|
|
78
|
+
* - The session baseline: `session/new`, `list`, `resume` (with replay),
|
|
79
|
+
* `close`, `prompt`, `cancel`, and `update`.
|
|
80
|
+
* - `session/delete`, `session/fork` (which returns a session the client can
|
|
81
|
+
* prompt, see `v1ForkSessionRequests`), and `additionalDirectories`.
|
|
82
|
+
* - The prompt content and MCP transports that the agent's v1 `initialize`
|
|
83
|
+
* lists: images, embedded context, and HTTP servers. Stdio servers are in the
|
|
84
|
+
* v1 baseline; v2 has no SSE transport.
|
|
85
|
+
*/
|
|
86
|
+
const V2_SESSION_CAPABILITIES = {
|
|
87
|
+
prompt: { image: {}, embeddedContext: {} },
|
|
88
|
+
mcp: { stdio: {}, http: {} },
|
|
89
|
+
delete: {},
|
|
90
|
+
fork: {},
|
|
91
|
+
additionalDirectories: {},
|
|
92
|
+
};
|
|
62
93
|
/** v2 names the id of a method `methodId`, requires `type`, and lists `env` as name and value pairs. */
|
|
63
94
|
function v2AuthMethod(method) {
|
|
64
95
|
if ("type" in method && method.type === "terminal") {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The v2 form of the permission requests that `ClaudeAcpAgent` sends, and the
|
|
3
|
+
* v1 form of their answers.
|
|
4
|
+
*/
|
|
5
|
+
import type { RequestPermissionResponse } from "@agentclientprotocol/sdk";
|
|
6
|
+
import type * as v2 from "@agentclientprotocol/sdk/experimental/v2";
|
|
7
|
+
import type { AcpPermissionRequest } from "../permissions/presentation.js";
|
|
8
|
+
/**
|
|
9
|
+
* The v2 form of a permission request of the agent.
|
|
10
|
+
*
|
|
11
|
+
* v1 asks about a tool call, whose title the prompt shows. v2 gives the prompt
|
|
12
|
+
* its own `title` and `description`, which the agent sets for a v2 client
|
|
13
|
+
* (`AcpPermissionRequest`), and asks about a `subject`: here always the tool
|
|
14
|
+
* call, as the same patch that a `tool_call_update` carries.
|
|
15
|
+
*/
|
|
16
|
+
export declare function v2PermissionRequest(request: AcpPermissionRequest): v2.RequestPermissionRequest;
|
|
17
|
+
/**
|
|
18
|
+
* The v1 form of the answer to a permission request.
|
|
19
|
+
*
|
|
20
|
+
* v2 adds outcomes that v1 does not know, which the draft says are no
|
|
21
|
+
* approval. They become `cancelled`, which the agent reads as an aborted tool
|
|
22
|
+
* use, as it does a cancelled prompt.
|
|
23
|
+
*/
|
|
24
|
+
export declare function v1PermissionResponse(response: v2.RequestPermissionResponse): RequestPermissionResponse;
|
|
25
|
+
//# sourceMappingURL=permission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../../src/v2/permission.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,KAAK,KAAK,EAAE,MAAM,0CAA0C,CAAC;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAG3E;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,EAAE,CAAC,wBAAwB,CAY9F;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,EAAE,CAAC,yBAAyB,GACrC,yBAAyB,CAS3B"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { v2ToolCallPatch } from "./tool-call.js";
|
|
2
|
+
/**
|
|
3
|
+
* The v2 form of a permission request of the agent.
|
|
4
|
+
*
|
|
5
|
+
* v1 asks about a tool call, whose title the prompt shows. v2 gives the prompt
|
|
6
|
+
* its own `title` and `description`, which the agent sets for a v2 client
|
|
7
|
+
* (`AcpPermissionRequest`), and asks about a `subject`: here always the tool
|
|
8
|
+
* call, as the same patch that a `tool_call_update` carries.
|
|
9
|
+
*/
|
|
10
|
+
export function v2PermissionRequest(request) {
|
|
11
|
+
if (request.title === undefined) {
|
|
12
|
+
throw new Error("An ACP v2 permission request needs a title");
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
sessionId: request.sessionId,
|
|
16
|
+
title: request.title,
|
|
17
|
+
...(request.description ? { description: request.description } : {}),
|
|
18
|
+
subject: { type: "tool_call", toolCall: v2ToolCallPatch(request.toolCall) },
|
|
19
|
+
options: request.options,
|
|
20
|
+
...(request._meta != null ? { _meta: request._meta } : {}),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The v1 form of the answer to a permission request.
|
|
25
|
+
*
|
|
26
|
+
* v2 adds outcomes that v1 does not know, which the draft says are no
|
|
27
|
+
* approval. They become `cancelled`, which the agent reads as an aborted tool
|
|
28
|
+
* use, as it does a cancelled prompt.
|
|
29
|
+
*/
|
|
30
|
+
export function v1PermissionResponse(response) {
|
|
31
|
+
const outcome = response.outcome;
|
|
32
|
+
return {
|
|
33
|
+
outcome: outcome.outcome === "selected"
|
|
34
|
+
? outcome
|
|
35
|
+
: { outcome: "cancelled" },
|
|
36
|
+
...(response._meta != null ? { _meta: response._meta } : {}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
|
|
6
6
|
import type { AcpSessionNotification } from "../acp-subagents.js";
|
|
7
|
+
/**
|
|
8
|
+
* The id of the plan of a session. v1 has one plan per session, which every
|
|
9
|
+
* plan update replaces whole, from TodoWrite and from the Task tools alike.
|
|
10
|
+
* v2 tracks plans by id, so that plan gets one.
|
|
11
|
+
*/
|
|
12
|
+
export declare const SESSION_PLAN_ID = "plan";
|
|
7
13
|
/**
|
|
8
14
|
* The v2 form of a v1 session update, or `undefined` when v2 has no
|
|
9
15
|
* counterpart and the update tells a v2 client nothing new.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-update.d.ts","sourceRoot":"","sources":["../../src/v2/session-update.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAC/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"session-update.d.ts","sourceRoot":"","sources":["../../src/v2/session-update.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAC/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAIlE;;;;GAIG;AACH,eAAO,MAAM,eAAe,SAAS,CAAC;AAEtC;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GACvC,EAAE,CAAC,aAAa,GAAG,SAAS,CA0C9B"}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { v2AvailableCommands, v2ConfigOptions } from "./session.js";
|
|
2
|
+
import { v2ToolCallUpdate } from "./tool-call.js";
|
|
3
|
+
/**
|
|
4
|
+
* The id of the plan of a session. v1 has one plan per session, which every
|
|
5
|
+
* plan update replaces whole, from TodoWrite and from the Task tools alike.
|
|
6
|
+
* v2 tracks plans by id, so that plan gets one.
|
|
7
|
+
*/
|
|
8
|
+
export const SESSION_PLAN_ID = "plan";
|
|
2
9
|
/**
|
|
3
10
|
* The v2 form of a v1 session update, or `undefined` when v2 has no
|
|
4
11
|
* counterpart and the update tells a v2 client nothing new.
|
|
@@ -15,6 +22,8 @@ export function v2SessionUpdate(update) {
|
|
|
15
22
|
case "session_info_update":
|
|
16
23
|
case "usage_update":
|
|
17
24
|
case "notice":
|
|
25
|
+
case "compaction_update":
|
|
26
|
+
case "compaction_summary_chunk":
|
|
18
27
|
return update;
|
|
19
28
|
case "agent_message_chunk":
|
|
20
29
|
case "agent_thought_chunk":
|
|
@@ -26,6 +35,16 @@ export function v2SessionUpdate(update) {
|
|
|
26
35
|
}
|
|
27
36
|
return { ...chunk, messageId };
|
|
28
37
|
}
|
|
38
|
+
case "tool_call":
|
|
39
|
+
case "tool_call_update":
|
|
40
|
+
return v2ToolCallUpdate(update);
|
|
41
|
+
case "plan":
|
|
42
|
+
// A v2 plan of items is replaced whole as well.
|
|
43
|
+
return {
|
|
44
|
+
sessionUpdate: "plan_update",
|
|
45
|
+
plan: { type: "items", planId: SESSION_PLAN_ID, entries: update.entries },
|
|
46
|
+
...(update._meta != null ? { _meta: update._meta } : {}),
|
|
47
|
+
};
|
|
29
48
|
case "current_mode_update":
|
|
30
49
|
// v2 has no modes: the mode is the `mode` config option. Every mode
|
|
31
50
|
// change also reaches the client as a `config_option_update` or in the
|