zcode-acp-server 0.1.0

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.
Files changed (108) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +167 -0
  3. package/README.zh-CN.md +163 -0
  4. package/dist/backend/client.d.ts +103 -0
  5. package/dist/backend/client.d.ts.map +1 -0
  6. package/dist/backend/client.js +344 -0
  7. package/dist/backend/client.js.map +1 -0
  8. package/dist/backend/credentials.d.ts +31 -0
  9. package/dist/backend/credentials.d.ts.map +1 -0
  10. package/dist/backend/credentials.js +93 -0
  11. package/dist/backend/credentials.js.map +1 -0
  12. package/dist/backend/index.d.ts +7 -0
  13. package/dist/backend/index.d.ts.map +1 -0
  14. package/dist/backend/index.js +6 -0
  15. package/dist/backend/index.js.map +1 -0
  16. package/dist/backend/listener.d.ts +63 -0
  17. package/dist/backend/listener.d.ts.map +1 -0
  18. package/dist/backend/listener.js +138 -0
  19. package/dist/backend/listener.js.map +1 -0
  20. package/dist/backend/resolve.d.ts +11 -0
  21. package/dist/backend/resolve.d.ts.map +1 -0
  22. package/dist/backend/resolve.js +116 -0
  23. package/dist/backend/resolve.js.map +1 -0
  24. package/dist/backend/types.d.ts +164 -0
  25. package/dist/backend/types.d.ts.map +1 -0
  26. package/dist/backend/types.js +15 -0
  27. package/dist/backend/types.js.map +1 -0
  28. package/dist/config/model-cache.d.ts +20 -0
  29. package/dist/config/model-cache.d.ts.map +1 -0
  30. package/dist/config/model-cache.js +62 -0
  31. package/dist/config/model-cache.js.map +1 -0
  32. package/dist/config/options.d.ts +36 -0
  33. package/dist/config/options.d.ts.map +1 -0
  34. package/dist/config/options.js +171 -0
  35. package/dist/config/options.js.map +1 -0
  36. package/dist/config/runtime-model.d.ts +33 -0
  37. package/dist/config/runtime-model.d.ts.map +1 -0
  38. package/dist/config/runtime-model.js +96 -0
  39. package/dist/config/runtime-model.js.map +1 -0
  40. package/dist/handlers/dispatch.d.ts +15 -0
  41. package/dist/handlers/dispatch.d.ts.map +1 -0
  42. package/dist/handlers/dispatch.js +183 -0
  43. package/dist/handlers/dispatch.js.map +1 -0
  44. package/dist/handlers/extensions.d.ts +43 -0
  45. package/dist/handlers/extensions.d.ts.map +1 -0
  46. package/dist/handlers/extensions.js +310 -0
  47. package/dist/handlers/extensions.js.map +1 -0
  48. package/dist/handlers/io.d.ts +40 -0
  49. package/dist/handlers/io.d.ts.map +1 -0
  50. package/dist/handlers/io.js +55 -0
  51. package/dist/handlers/io.js.map +1 -0
  52. package/dist/handlers/server-requests.d.ts +34 -0
  53. package/dist/handlers/server-requests.d.ts.map +1 -0
  54. package/dist/handlers/server-requests.js +357 -0
  55. package/dist/handlers/server-requests.js.map +1 -0
  56. package/dist/handlers/session.d.ts +46 -0
  57. package/dist/handlers/session.d.ts.map +1 -0
  58. package/dist/handlers/session.js +738 -0
  59. package/dist/handlers/session.js.map +1 -0
  60. package/dist/handlers/slash.d.ts +16 -0
  61. package/dist/handlers/slash.d.ts.map +1 -0
  62. package/dist/handlers/slash.js +107 -0
  63. package/dist/handlers/slash.js.map +1 -0
  64. package/dist/index.d.ts +11 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +93 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/interaction/adapter.d.ts +136 -0
  69. package/dist/interaction/adapter.d.ts.map +1 -0
  70. package/dist/interaction/adapter.js +353 -0
  71. package/dist/interaction/adapter.js.map +1 -0
  72. package/dist/server.d.ts +73 -0
  73. package/dist/server.d.ts.map +1 -0
  74. package/dist/server.js +97 -0
  75. package/dist/server.js.map +1 -0
  76. package/dist/tasks-index.d.ts +39 -0
  77. package/dist/tasks-index.d.ts.map +1 -0
  78. package/dist/tasks-index.js +152 -0
  79. package/dist/tasks-index.js.map +1 -0
  80. package/dist/translators/event-translator.d.ts +40 -0
  81. package/dist/translators/event-translator.d.ts.map +1 -0
  82. package/dist/translators/event-translator.js +214 -0
  83. package/dist/translators/event-translator.js.map +1 -0
  84. package/dist/translators/index.d.ts +6 -0
  85. package/dist/translators/index.d.ts.map +1 -0
  86. package/dist/translators/index.js +5 -0
  87. package/dist/translators/index.js.map +1 -0
  88. package/dist/translators/projection-differ.d.ts +48 -0
  89. package/dist/translators/projection-differ.d.ts.map +1 -0
  90. package/dist/translators/projection-differ.js +239 -0
  91. package/dist/translators/projection-differ.js.map +1 -0
  92. package/dist/translators/tool-helpers.d.ts +60 -0
  93. package/dist/translators/tool-helpers.d.ts.map +1 -0
  94. package/dist/translators/tool-helpers.js +308 -0
  95. package/dist/translators/tool-helpers.js.map +1 -0
  96. package/dist/translators/types.d.ts +58 -0
  97. package/dist/translators/types.d.ts.map +1 -0
  98. package/dist/translators/types.js +27 -0
  99. package/dist/translators/types.js.map +1 -0
  100. package/dist/utils.d.ts +111 -0
  101. package/dist/utils.d.ts.map +1 -0
  102. package/dist/utils.js +110 -0
  103. package/dist/utils.js.map +1 -0
  104. package/docs/ARCHITECTURE.md +299 -0
  105. package/docs/DEVELOPMENT.md +193 -0
  106. package/docs/PROTOCOL.md +649 -0
  107. package/docs/TROUBLESHOOTING.md +251 -0
  108. package/package.json +66 -0
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Internal event-dict shape — the seam between translators and the dispatcher.
3
+ *
4
+ * Both `EventTranslator` (event-stream path) and `ProjectionDiffer` (snapshot
5
+ * path) emit these discriminated unions; `dispatchEvent` consumes them and
6
+ * serialises each into an ACP `session/update` notification.
7
+ */
8
+ import type { PlanEntry, ToolCallContent, ToolCallLocation, ToolCallStatus, ToolKind } from "@agentclientprotocol/sdk";
9
+ export interface ToolCallNewEvent {
10
+ kind: "ToolCallNew";
11
+ callId: string;
12
+ tool: string;
13
+ acpKind: ToolKind;
14
+ status: ToolCallStatus;
15
+ title: string;
16
+ input?: unknown;
17
+ output?: string;
18
+ content?: ToolCallContent[];
19
+ diffContent?: ToolCallContent[];
20
+ locations?: ToolCallLocation[];
21
+ }
22
+ export interface ToolCallUpdateEvent {
23
+ kind: "ToolCallUpdate";
24
+ callId: string;
25
+ tool?: string;
26
+ status: ToolCallStatus;
27
+ output?: string;
28
+ rawOutput?: unknown;
29
+ rawResult?: unknown;
30
+ content?: ToolCallContent[];
31
+ diffContent?: ToolCallContent[];
32
+ locations?: ToolCallLocation[];
33
+ }
34
+ export interface UsageDeltaEvent {
35
+ kind: "UsageDelta";
36
+ used: number;
37
+ size: number;
38
+ }
39
+ export interface TextDeltaEvent {
40
+ kind: "TextDelta";
41
+ text: string;
42
+ }
43
+ export interface ReasoningDeltaEvent {
44
+ kind: "ReasoningDelta";
45
+ text: string;
46
+ }
47
+ export interface PlanUpdateEvent {
48
+ kind: "PlanUpdate";
49
+ entries: PlanEntry[];
50
+ }
51
+ /** A plan entry being built before dispatch (status/priority are normalised here). */
52
+ export declare function makePlanEntry(content: string, status: string, priority?: string): PlanEntry;
53
+ export interface FilesChangedEvent {
54
+ kind: "FilesChanged";
55
+ files: string[];
56
+ }
57
+ export type InternalEvent = ToolCallNewEvent | ToolCallUpdateEvent | UsageDeltaEvent | TextDeltaEvent | ReasoningDeltaEvent | PlanUpdateEvent | FilesChangedEvent;
58
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/translators/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,SAAS,EAGT,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,QAAQ,EACT,MAAM,0BAA0B,CAAC;AAElC,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,QAAQ,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,gBAAgB,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,YAAY,CAAC;IACnB,OAAO,EAAE,SAAS,EAAE,CAAC;CACtB;AAED,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAM3F;AAcD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,MAAM,aAAa,GACrB,gBAAgB,GAChB,mBAAmB,GACnB,eAAe,GACf,cAAc,GACd,mBAAmB,GACnB,eAAe,GACf,iBAAiB,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Internal event-dict shape — the seam between translators and the dispatcher.
3
+ *
4
+ * Both `EventTranslator` (event-stream path) and `ProjectionDiffer` (snapshot
5
+ * path) emit these discriminated unions; `dispatchEvent` consumes them and
6
+ * serialises each into an ACP `session/update` notification.
7
+ */
8
+ /** A plan entry being built before dispatch (status/priority are normalised here). */
9
+ export function makePlanEntry(content, status, priority) {
10
+ return {
11
+ content,
12
+ status: normalisePlanStatus(status),
13
+ priority: normalisePlanPriority(priority),
14
+ };
15
+ }
16
+ function normalisePlanStatus(s) {
17
+ if (s === "completed" || s === "in_progress" || s === "pending") {
18
+ return s;
19
+ }
20
+ return "pending";
21
+ }
22
+ function normalisePlanPriority(p) {
23
+ if (p === "high" || p === "medium" || p === "low")
24
+ return p;
25
+ return "medium";
26
+ }
27
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/translators/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA4DH,sFAAsF;AACtF,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,MAAc,EAAE,QAAiB;IAC9E,OAAO;QACL,OAAO;QACP,MAAM,EAAE,mBAAmB,CAAC,MAAM,CAAC;QACnC,QAAQ,EAAE,qBAAqB,CAAC,QAAQ,CAAC;KAC1C,CAAC;AACJ,CAAC;AAED,SAAS,mBAAmB,CAAC,CAAS;IACpC,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC,KAAK,aAAa,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;QAChE,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,qBAAqB,CAAC,CAAqB;IAClD,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,KAAK;QAAE,OAAO,CAAC,CAAC;IAC5D,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Shared utilities: logging and project-wide constants.
3
+ *
4
+ * Logging goes to stderr so it never corrupts the stdout ACP protocol stream.
5
+ */
6
+ /** ACP protocol version this server speaks. */
7
+ export declare const PROTOCOL_VERSION = 1;
8
+ /** Agent identity advertised in the initialize response. */
9
+ export declare const AGENT_INFO: {
10
+ readonly name: "zcode-acp-server";
11
+ readonly title: "ZCode";
12
+ readonly version: "0.1.0";
13
+ };
14
+ /** Path to the ZCode v2 config (credentials + provider/model metadata). */
15
+ export declare const ZCODE_CREDS_PATH: string;
16
+ /**
17
+ * Slash commands surfaced to the editor. Each maps to a ZCode session method
18
+ * that the server forwards when the user types the command.
19
+ */
20
+ export declare const SLASH_COMMANDS: readonly [{
21
+ readonly name: "compact";
22
+ readonly description: "Compress conversation context (free up tokens)";
23
+ }, {
24
+ readonly name: "goal";
25
+ readonly description: "Set or show the session goal";
26
+ readonly input: {
27
+ readonly hint: "goal description";
28
+ };
29
+ }, {
30
+ readonly name: "fork";
31
+ readonly description: "Fork the session at the latest checkpoint";
32
+ }, {
33
+ readonly name: "rewind";
34
+ readonly description: "Rewind workspace files to the latest checkpoint";
35
+ }, {
36
+ readonly name: "steer";
37
+ readonly description: "Append an instruction to a running turn";
38
+ readonly input: {
39
+ readonly hint: "content";
40
+ };
41
+ }, {
42
+ readonly name: "mode";
43
+ readonly description: "Switch permission mode (plan/build/edit/yolo)";
44
+ readonly input: {
45
+ readonly hint: "plan|build|edit|yolo";
46
+ };
47
+ }, {
48
+ readonly name: "model";
49
+ readonly description: "Switch the session model";
50
+ readonly input: {
51
+ readonly hint: "GLM-5.2|GLM-5-Turbo";
52
+ };
53
+ }, {
54
+ readonly name: "thought";
55
+ readonly description: "Set the reasoning effort";
56
+ readonly input: {
57
+ readonly hint: "max|high|nothink";
58
+ };
59
+ }];
60
+ /** Static metadata for the configOptions selects (model/mode/thought). */
61
+ export declare const CONFIG_META: {
62
+ readonly model: {
63
+ readonly name: "Model";
64
+ readonly category: "model";
65
+ readonly options: Array<{
66
+ value: string;
67
+ name: string;
68
+ }>;
69
+ };
70
+ readonly mode: {
71
+ readonly name: "Mode";
72
+ readonly category: "mode";
73
+ readonly options: readonly [{
74
+ readonly value: "plan";
75
+ readonly name: "plan";
76
+ }, {
77
+ readonly value: "build";
78
+ readonly name: "build";
79
+ }, {
80
+ readonly value: "edit";
81
+ readonly name: "edit";
82
+ }, {
83
+ readonly value: "yolo";
84
+ readonly name: "yolo";
85
+ }];
86
+ };
87
+ readonly thought: {
88
+ readonly name: "Thought Level";
89
+ readonly category: "thought_level";
90
+ readonly options: readonly [{
91
+ readonly value: "max";
92
+ readonly name: "max";
93
+ }, {
94
+ readonly value: "high";
95
+ readonly name: "high";
96
+ }, {
97
+ readonly value: "nothink";
98
+ readonly name: "nothink";
99
+ }];
100
+ };
101
+ };
102
+ /** configId → zcode method + param key (model deliberately absent — switch via runtimeModel). */
103
+ export declare const CONFIG_DISPATCH: Record<string, {
104
+ method: string;
105
+ paramKey: string;
106
+ }>;
107
+ /** Verbose diagnostic log. Only emitted when `ZCODE_ACP_DEBUG=1`. */
108
+ export declare function log(msg: string): void;
109
+ /** Warning — always emitted. For perceivable failures. */
110
+ export declare function warn(msg: string): void;
111
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,+CAA+C;AAC/C,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,4DAA4D;AAC5D,eAAO,MAAM,UAAU;;;;CAIb,CAAC;AAEX,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,QAK5B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BjB,CAAC;AAEX,0EAA0E;AAC1E,eAAO,MAAM,WAAW;;;;0BAIL,KAAK,CAAC;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqB/C,CAAC;AAEX,iGAAiG;AACjG,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAGhF,CAAC;AAoBF,qEAAqE;AACrE,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAGrC;AAED,0DAA0D;AAC1D,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEtC"}
package/dist/utils.js ADDED
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Shared utilities: logging and project-wide constants.
3
+ *
4
+ * Logging goes to stderr so it never corrupts the stdout ACP protocol stream.
5
+ */
6
+ import path from "node:path";
7
+ import process from "node:process";
8
+ /** ACP protocol version this server speaks. */
9
+ export const PROTOCOL_VERSION = 1;
10
+ /** Agent identity advertised in the initialize response. */
11
+ export const AGENT_INFO = {
12
+ name: "zcode-acp-server",
13
+ title: "ZCode",
14
+ version: "0.1.0",
15
+ };
16
+ /** Path to the ZCode v2 config (credentials + provider/model metadata). */
17
+ export const ZCODE_CREDS_PATH = path.join(process.env.HOME || process.env.USERPROFILE || "~", ".zcode", "v2", "config.json");
18
+ /**
19
+ * Slash commands surfaced to the editor. Each maps to a ZCode session method
20
+ * that the server forwards when the user types the command.
21
+ */
22
+ export const SLASH_COMMANDS = [
23
+ { name: "compact", description: "Compress conversation context (free up tokens)" },
24
+ {
25
+ name: "goal",
26
+ description: "Set or show the session goal",
27
+ input: { hint: "goal description" },
28
+ },
29
+ { name: "fork", description: "Fork the session at the latest checkpoint" },
30
+ { name: "rewind", description: "Rewind workspace files to the latest checkpoint" },
31
+ {
32
+ name: "steer",
33
+ description: "Append an instruction to a running turn",
34
+ input: { hint: "content" },
35
+ },
36
+ {
37
+ name: "mode",
38
+ description: "Switch permission mode (plan/build/edit/yolo)",
39
+ input: { hint: "plan|build|edit|yolo" },
40
+ },
41
+ {
42
+ name: "model",
43
+ description: "Switch the session model",
44
+ input: { hint: "GLM-5.2|GLM-5-Turbo" },
45
+ },
46
+ {
47
+ name: "thought",
48
+ description: "Set the reasoning effort",
49
+ input: { hint: "max|high|nothink" },
50
+ },
51
+ ];
52
+ /** Static metadata for the configOptions selects (model/mode/thought). */
53
+ export const CONFIG_META = {
54
+ model: {
55
+ name: "Model",
56
+ category: "model",
57
+ options: [],
58
+ },
59
+ mode: {
60
+ name: "Mode",
61
+ category: "mode",
62
+ options: [
63
+ { value: "plan", name: "plan" },
64
+ { value: "build", name: "build" },
65
+ { value: "edit", name: "edit" },
66
+ { value: "yolo", name: "yolo" },
67
+ ],
68
+ },
69
+ thought: {
70
+ name: "Thought Level",
71
+ category: "thought_level",
72
+ options: [
73
+ { value: "max", name: "max" },
74
+ { value: "high", name: "high" },
75
+ { value: "nothink", name: "nothink" },
76
+ ],
77
+ },
78
+ };
79
+ /** configId → zcode method + param key (model deliberately absent — switch via runtimeModel). */
80
+ export const CONFIG_DISPATCH = {
81
+ mode: { method: "session/setMode", paramKey: "mode" },
82
+ thought: { method: "session/setThoughtLevel", paramKey: "thoughtLevel" },
83
+ };
84
+ /**
85
+ * Logging. Two levels, both write to stderr (stdout is reserved for the ACP
86
+ * JSON-RPC stream):
87
+ * - `warn(msg)`: always emitted — failures the user can perceive (fatal
88
+ * exit, broken backend pipe, command/permission failures, etc.).
89
+ * - `log(msg)`: verbose diagnostic detail — only emitted when
90
+ * `ZCODE_ACP_DEBUG=1` is set. Default (unset) keeps the log quiet so
91
+ * a stable bridge doesn't spam `Zed.log`.
92
+ *
93
+ * Never use `console.log` — it would corrupt the stdout protocol stream.
94
+ */
95
+ /** True when the user opted into verbose diagnostics.
96
+ * Read at call time so tests can flip it without re-importing the module. */
97
+ function isDebug() {
98
+ return process.env.ZCODE_ACP_DEBUG === "1";
99
+ }
100
+ /** Verbose diagnostic log. Only emitted when `ZCODE_ACP_DEBUG=1`. */
101
+ export function log(msg) {
102
+ if (!isDebug())
103
+ return;
104
+ process.stderr.write(`[zcode-acp] ${msg}\n`);
105
+ }
106
+ /** Warning — always emitted. For perceivable failures. */
107
+ export function warn(msg) {
108
+ process.stderr.write(`[zcode-acp] ${msg}\n`);
109
+ }
110
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,299 @@
1
+ # zcode-acp-server Architecture
2
+
3
+ ## Overview
4
+
5
+ `zcode-acp-server` bridges the headless ZCode CLI (`zcode app-server --stdio`) to
6
+ ACP (Agent Client Protocol) compatible editors. It is the translation layer
7
+ between ZCode's internal JSON-RPC event stream and the standard ACP protocol.
8
+
9
+ ## Layered Architecture
10
+
11
+ ```
12
+ application-client (Zed / JetBrains)
13
+ session/update
14
+ |
15
+ v
16
+ zcode-acp-server (stdio JSON-RPC ACP)
17
+ |-- handlers/ session, extensions, dispatch, server-requests, io, slash
18
+ |-- translators/ event-translator, projection-differ, tool-helpers
19
+ |-- interaction/ adapter
20
+ |-- config/ options, runtime-model, model-cache
21
+ |-- backend/ client, listener, types
22
+ |-- server.ts ZcodeAcpServer
23
+ |
24
+ v
25
+ zcode app-server --stdio (line-delimited JSON)
26
+ ```
27
+
28
+ ## Core Data Flow
29
+
30
+ ### 1. Session lifecycle
31
+
32
+ ```
33
+ session/new → session/create → register EventListener
34
+ |
35
+ prompt request → session/send → EventTranslator translates → dispatchEvent
36
+ | |
37
+ end_turn / cancelled session/update notification
38
+ ```
39
+
40
+ ### 2. Event stream subscription
41
+
42
+ ```
43
+ EventStreamListener.subscribe()
44
+ |
45
+ session/subscribe (deliveryKind: "desktop-continuous")
46
+ |
47
+ ZCode pushes session/event → handleEvent()
48
+ |
49
+ pollEvent() consumes → EventTranslator.translate()
50
+ ```
51
+
52
+ ### 3. Dual-path event handling
53
+
54
+ #### Real-time path (EventTranslator)
55
+ - Listens to zcode `session/event` pushes
56
+ - Translates each event to an ACP `session/update` in real time
57
+ - Maintains `seenToolIds` to avoid duplicates
58
+
59
+ #### Snapshot path (ProjectionDiffer)
60
+ - On turn completion, builds a snapshot from `session/messages` + `session/read`
61
+ - Diffs two snapshots to produce new events (PlanUpdate / TextDelta / ToolCallNew, etc.)
62
+ - Used for turn-completion triage and stall recovery
63
+
64
+ ### 4. Dual-path deduplication
65
+
66
+ ```
67
+ EventTranslator (real-time path)
68
+ ├── seenToolIds: Set<string>
69
+ └── turnDone: boolean
70
+ |
71
+ v
72
+ ProjectionDiffer (snapshot path)
73
+ ├── seenToolIds: Set<string>
74
+ ├── lastToolStatus: Map<string, string>
75
+ └── seenMessageIds: Set<string>
76
+ |
77
+ v
78
+ dispatchEvent (single exit point)
79
+ ```
80
+
81
+ Key: **`seenToolIds` synchronization**
82
+
83
+ In `session.ts:629`, after the event path finishes processing, the state is
84
+ synced to the differ:
85
+ ```typescript
86
+ for (const seenId of translator.seenToolIds) {
87
+ differ.markToolSeen(seenId);
88
+ }
89
+ ```
90
+
91
+ This ensures the snapshot diff does not re-emit tools already handled by the
92
+ event path, preventing Bash terminal output from being overwritten by a
93
+ content-less ToolCallNew.
94
+
95
+ ## Module Responsibilities
96
+
97
+ ### `backend/` — ZCode process communication
98
+
99
+ | File | Responsibility |
100
+ |------|------|
101
+ | `client.ts` | Spawn/manage the zcode subprocess, reader-loop, request/response multiplexing, process watchdog |
102
+ | `listener.ts` | EventStreamListener (subscribe/consume the event stream) and TurnMonitor (snapshot polling) |
103
+ | `types.ts` | ZCode JSON-RPC message type definitions |
104
+
105
+ ### `translators/` — Event translation
106
+
107
+ | File | Responsibility |
108
+ |------|------|
109
+ | `event-translator.ts` | Translate zcode events to InternalEvent (real-time path) |
110
+ | `projection-differ.ts` | Diff two snapshots to produce InternalEvent (snapshot path) |
111
+ | `tool-helpers.ts` | Tool-related pure functions: title generation, output rendering, diff parsing, location extraction |
112
+ | `types.ts` | InternalEvent union type and plan entry builders |
113
+
114
+ ### `handlers/` — ACP method handling
115
+
116
+ | File | Responsibility |
117
+ |------|------|
118
+ | `session.ts` | session/new/list/resume/load/prompt/set_config_option/cancel |
119
+ | `extensions.ts` | fork/rewind/rewindCascade/goal/compact/steer/cancelBackgroundTask/setModel/setMode/setThoughtLevel |
120
+ | `dispatch.ts` | dispatchEvent single exit point: InternalEvent → ACP session/update |
121
+ | `server-requests.ts` | Handle zcode interaction/* requests (tool auth, ExitPlanMode, AskUserQuestion), protocol negotiation routing |
122
+ | `io.ts` | ACP notification helpers (including `sendAvailableCommandsDeferred` deferred notification) |
123
+ | `slash.ts` | Interception of `/`-prefixed commands (/compact /goal /fork /rewind /steer /model /mode /thought) |
124
+
125
+ ### `interaction/` — Interaction bridging
126
+
127
+ | File | Responsibility |
128
+ |------|------|
129
+ | `adapter.ts` | Conversion adapter from zcode interaction requests to ACP (requestPermission + elicitation form) |
130
+
131
+ ### `config/` — Configuration management
132
+
133
+ | File | Responsibility |
134
+ |------|------|
135
+ | `options.ts` | configOptions / modes construction, set_config_option dispatch |
136
+ | `runtime-model.ts` | runtimeModel overlay construction and application |
137
+ | `model-cache.ts` | Model ID cache and usage initialization |
138
+
139
+ ## Key State Machines
140
+
141
+ ### Turn state
142
+
143
+ ```
144
+ subscribe
145
+ |
146
+ turn.started
147
+ |
148
+ +---------------------+
149
+ | model.streaming |
150
+ | tool.updated |
151
+ | session.updated |
152
+ +---------------------+
153
+ |
154
+ +---------------------+
155
+ | turn.completed | -> end_turn
156
+ | turn.failed | -> error
157
+ | turn.cancelled | -> cancelled
158
+ | timeout (120s) | -> max_turn_requests
159
+ | manual cancel | -> cancelled
160
+ +---------------------+
161
+ ```
162
+
163
+ ### Tool lifecycle
164
+
165
+ ```
166
+ scheduled -> started -> progress -> result/error
167
+ | | |
168
+ ToolCallNew status=in_progress output (stdoutTail)
169
+ |
170
+ (seenToolIds.add)
171
+ ```
172
+
173
+ ## Interaction Protocol Negotiation
174
+
175
+ ZCode `interaction/*` requests are routed to different ACP client interaction
176
+ mechanisms via protocol negotiation:
177
+
178
+ ```
179
+ zcode interaction request received
180
+
181
+ ├─ Tool auth (interaction/requestPermission)
182
+ │ └─ Always uses session/request_permission (its native purpose)
183
+
184
+ ├─ ExitPlanMode (interaction/requestUserInput + plan_approval)
185
+ │ ├─ Client supports elicitation.form → elicitation/create (approve/reject form)
186
+ │ └─ Otherwise → session/request_permission (fallback)
187
+
188
+ └─ AskUserQuestion (interaction/requestUserInput)
189
+ ├─ Client supports elicitation.form → elicitation/create (single form)
190
+ └─ Otherwise → per-question session/request_permission (fallback)
191
+ ```
192
+
193
+ **Key**: `server.supportsElicitationForm()` is detected at `initialize` time from
194
+ `clientCapabilities.elicitation.form`. Tool auth always goes through
195
+ request_permission, since that is its native purpose.
196
+
197
+ ## Deferred Notification Mechanism
198
+
199
+ The `available_commands_update` notification must be sent **after** the session
200
+ response; otherwise the client's session state machine is not yet ready and
201
+ drops the notification, leaving the `/` completion menu empty.
202
+
203
+ ```
204
+ session/new|resume|load handler
205
+
206
+ ├─ call newSession()/resumeSession()/loadSession()
207
+
208
+ ├─ sendAvailableCommandsDeferred(cx, sid, SLASH_COMMANDS)
209
+ │ └─ enqueue, send after 50ms (fire-and-forget)
210
+
211
+ └─ return response
212
+
213
+ └─ after the response is written to stdout, the 50ms timer fires sendAvailableCommands
214
+ ```
215
+
216
+ `sendAvailableCommandsDeferred` (`io.ts`) encapsulates the 50ms delay logic,
217
+ mirroring the Python bridge's `_pending_post_notifs` queue +
218
+ `_drain_post_notifs` mechanism.
219
+
220
+ ## Mode Reconciliation
221
+
222
+ The session mode can change through four entry points, all of which must
223
+ notify the editor UI:
224
+
225
+ | Trigger | Path | Notifies UI |
226
+ |------|------|:---:|
227
+ | `session/setMode` request | `extensions.ts:setMode` | yes |
228
+ | `session/set_config_option` (mode) | `session.ts:setConfigOptionHandler` → `emitConfigOptionUpdate` | yes |
229
+ | `/mode` slash command | `slash.ts` → `emitConfigOptionUpdate` | yes |
230
+ | In-turn `EnterPlanMode`/`ExitPlanMode` | reconciled at turn completion | yes |
231
+
232
+ The in-turn path bypasses the bridge entirely, so `prompt()` runs
233
+ `emitModeIfChanged` (`session.ts`) at turn completion: it re-reads the
234
+ authoritative mode via `buildModes`, compares against `server.lastMode`
235
+ (the value last advertised to the client), and emits
236
+ `current_mode_update` + `config_option_update` when they differ. Failures are
237
+ swallowed so they cannot break the turn-completion path.
238
+
239
+ ## Prompt-Lock Release on Stop
240
+
241
+ `session/stop` is fire-and-forget, but ZCode has a startup delay: when stop
242
+ arrives before the turn truly holds the lock the backend ignores it, the turn
243
+ runs on, and the lock leaks (next `session/send` fails with
244
+ "A prompt is already running").
245
+
246
+ `ensureTurnStopped` (`session.ts`) closes this gap. It mirrors the
247
+ `expectLock:true` strategy from `waitForTurnIdle` (`extensions.ts`):
248
+
249
+ 1. send `session/stop`
250
+ 2. poll `session/goal show`; first REQUIRE seeing "prompt is running" once
251
+ (proves the turn started), then wait for it to clear
252
+ 3. if an 8s grace window elapses without ever seeing the lock, the turn never
253
+ started (stop caught it in time) or already ended → treat as released
254
+ 4. hard timeout 30s
255
+
256
+ It is used at every stop site: the cancel check, the stall no-output path,
257
+ the turn cancelled/failed result, the 120s no-progress timeout, and the
258
+ `session/send` error path. Never throws (failures only log) so it cannot
259
+ break the cancel path.
260
+
261
+ ## Process Watchdog
262
+
263
+ `close()` reaps the zcode process group via `process.kill(-pid)`, but only
264
+ when the bridge exits cleanly enough for its signal handlers to fire
265
+ (SIGTERM/SIGINT). If the bridge is SIGKILLed (Zed force-kill on reconnect,
266
+ crash, OOM), the handler never runs and the zcode subprocess group is
267
+ orphaned.
268
+
269
+ The watchdog (`backend/client.ts:startWatchdog`) closes that gap. It is a tiny
270
+ detached child that polls the bridge pid every 2s and, once the bridge is
271
+ gone, sends SIGKILL to the zcode process group, then exits. It is its own
272
+ process-group leader and `unref`'d, so it never holds the event loop open and
273
+ is not part of the zcode group it kills. It self-terminates as soon as the
274
+ zcode process exits, so a normal shutdown leaves no lingering watchdog.
275
+
276
+ ## Design Decisions
277
+
278
+ ### Why a dual path?
279
+
280
+ | Scenario | Real-time path | Snapshot path |
281
+ |------|---------|----------|
282
+ | Normal streaming | Low latency | Must wait for turn end |
283
+ | Lost events | Loses data | Recovers from snapshot |
284
+ | Deduplication | seenToolIds | seenMessageIds + markToolSeen() |
285
+ | Turn-completion triage | Not triggered | PlanUpdate / usage_update |
286
+
287
+ ### Why no polling fallback?
288
+
289
+ ZCode CLI 0.14.5 ~ 0.14.7 used `session/read` polling to emulate streaming.
290
+ 0.14.8+ introduced `session/subscribe` event push, which has lower latency and
291
+ is more reliable. This project supports only 0.14.8+ and has removed the
292
+ polling fallback code.
293
+
294
+ ### Why does ProjectionDiffer need to persist across turns?
295
+
296
+ - `seenMessageIds`: prevents historical messages from being re-emitted after resume
297
+ - `lastToolStatus` + `seenToolIds`: ensures tool state is not lost across turns
298
+ - `lastUsage`: avoids duplicate usage_update pushes
299
+ - `lastPlanSig`: emits only when the plan changes