paseo-plugin-zcode-provider 0.0.0-stage → 0.2.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 (141) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +29 -0
  3. package/README.md +112 -2
  4. package/client/account.tsx +827 -0
  5. package/client/diagnostics.tsx +379 -0
  6. package/client/runtime.tsx +296 -0
  7. package/client/settings.tsx +84 -0
  8. package/client/value.tsx +20 -0
  9. package/icon.svg +1 -0
  10. package/index.client.tsx +22 -0
  11. package/index.server.ts +39 -0
  12. package/package.json +77 -4
  13. package/paseo-plugin.json +6 -0
  14. package/server/account.ts +781 -0
  15. package/server/attachments.ts +150 -0
  16. package/server/build-info.ts +2 -0
  17. package/server/conversation.ts +319 -0
  18. package/server/diagnostics.ts +252 -0
  19. package/server/discovery/discover.ts +225 -0
  20. package/server/discovery/manifest.ts +36 -0
  21. package/server/discovery/types.ts +30 -0
  22. package/server/errors.ts +36 -0
  23. package/server/host/bridge.ts +490 -0
  24. package/server/host/schemas.ts +119 -0
  25. package/server/logger.ts +19 -0
  26. package/server/mapping.ts +270 -0
  27. package/server/models.ts +68 -0
  28. package/server/persistence.ts +146 -0
  29. package/server/presentation.ts +470 -0
  30. package/server/provider-status.ts +81 -0
  31. package/server/provider.ts +838 -0
  32. package/server/runtime/managed.ts +501 -0
  33. package/server/runtime/pins.ts +72 -0
  34. package/server/runtime-setup.ts +77 -0
  35. package/server/session-types.ts +49 -0
  36. package/server/session.ts +866 -0
  37. package/server/status.ts +89 -0
  38. package/server/timeline.ts +18 -0
  39. package/server/usage.ts +362 -0
  40. package/server/vendor/zcode/LICENSE +202 -0
  41. package/server/vendor/zcode/NOTICE.md +70 -0
  42. package/server/vendor/zcode/THIRD-PARTY-NOTICES.md +37 -0
  43. package/server/vendor/zcode/packages/model-option-map/src/compiler.ts +92 -0
  44. package/server/vendor/zcode/packages/model-option-map/src/evaluator.ts +214 -0
  45. package/server/vendor/zcode/packages/model-option-map/src/index.ts +5 -0
  46. package/server/vendor/zcode/packages/model-option-map/src/merge-patch.ts +97 -0
  47. package/server/vendor/zcode/packages/model-option-map/src/option-maps.ts +42 -0
  48. package/server/vendor/zcode/packages/model-option-map/src/parser.ts +240 -0
  49. package/server/vendor/zcode/packages/model-option-map/src/tokenizer.ts +141 -0
  50. package/server/vendor/zcode/packages/model-option-map/src/types.ts +36 -0
  51. package/server/vendor/zcode/packages/rpc/src/buffer.ts +88 -0
  52. package/server/vendor/zcode/packages/rpc/src/channelClient.ts +275 -0
  53. package/server/vendor/zcode/packages/rpc/src/channelServer.ts +283 -0
  54. package/server/vendor/zcode/packages/rpc/src/channels.shared.ts +66 -0
  55. package/server/vendor/zcode/packages/rpc/src/channels.ts +4 -0
  56. package/server/vendor/zcode/packages/rpc/src/delayedChannel.ts +17 -0
  57. package/server/vendor/zcode/packages/rpc/src/foundation.ts +260 -0
  58. package/server/vendor/zcode/packages/rpc/src/protocol.ts +397 -0
  59. package/server/vendor/zcode/packages/rpc/src/proxy-channel.ts +213 -0
  60. package/server/vendor/zcode/packages/rpc/src/serialization.ts +291 -0
  61. package/server/vendor/zcode/packages/shared/src/account-provider-state.ts +13 -0
  62. package/server/vendor/zcode/packages/shared/src/background-bash-output.ts +24 -0
  63. package/server/vendor/zcode/packages/shared/src/background-task-controls.ts +404 -0
  64. package/server/vendor/zcode/packages/shared/src/bash-output-display.ts +11 -0
  65. package/server/vendor/zcode/packages/shared/src/bots.ts +558 -0
  66. package/server/vendor/zcode/packages/shared/src/browser-use/backend.ts +80 -0
  67. package/server/vendor/zcode/packages/shared/src/browser-use/command-metadata.ts +170 -0
  68. package/server/vendor/zcode/packages/shared/src/browser-use/commands.ts +588 -0
  69. package/server/vendor/zcode/packages/shared/src/browser-use/result.ts +130 -0
  70. package/server/vendor/zcode/packages/shared/src/browser-use/snapshot.ts +77 -0
  71. package/server/vendor/zcode/packages/shared/src/command-types.ts +96 -0
  72. package/server/vendor/zcode/packages/shared/src/config-schema.ts +8 -0
  73. package/server/vendor/zcode/packages/shared/src/cuaAccessibilitySettings.ts +41 -0
  74. package/server/vendor/zcode/packages/shared/src/database-startup.ts +178 -0
  75. package/server/vendor/zcode/packages/shared/src/env.ts +63 -0
  76. package/server/vendor/zcode/packages/shared/src/execution-output-preview.ts +14 -0
  77. package/server/vendor/zcode/packages/shared/src/localTtft.ts +192 -0
  78. package/server/vendor/zcode/packages/shared/src/model-config.ts +123 -0
  79. package/server/vendor/zcode/packages/shared/src/model-execution.ts +23 -0
  80. package/server/vendor/zcode/packages/shared/src/model-provider-family.ts +171 -0
  81. package/server/vendor/zcode/packages/shared/src/model-provider-types.ts +81 -0
  82. package/server/vendor/zcode/packages/shared/src/model-selection.ts +60 -0
  83. package/server/vendor/zcode/packages/shared/src/oauth.ts +203 -0
  84. package/server/vendor/zcode/packages/shared/src/official-mcp-auth.ts +272 -0
  85. package/server/vendor/zcode/packages/shared/src/official-mcp-tool-error.ts +68 -0
  86. package/server/vendor/zcode/packages/shared/src/process-diagnostic.ts +42 -0
  87. package/server/vendor/zcode/packages/shared/src/protocol.ts +369 -0
  88. package/server/vendor/zcode/packages/shared/src/provider-family-connection-selection.ts +32 -0
  89. package/server/vendor/zcode/packages/shared/src/remoteAssetInstallMode.ts +11 -0
  90. package/server/vendor/zcode/packages/shared/src/remoteResourcePackages.ts +91 -0
  91. package/server/vendor/zcode/packages/shared/src/runtimeEnv.ts +315 -0
  92. package/server/vendor/zcode/packages/shared/src/settings-source.ts +10 -0
  93. package/server/vendor/zcode/packages/shared/src/usage-quota.ts +74 -0
  94. package/server/vendor/zcode/packages/shared/src/usage-stats.ts +462 -0
  95. package/server/vendor/zcode/packages/shared/src/validationAppSettings.ts +562 -0
  96. package/server/vendor/zcode/packages/shared/src/workspacePurpose.ts +2 -0
  97. package/server/vendor/zcode/packages/shared/src/wslUserValidation.ts +33 -0
  98. package/server/vendor/zcode/packages/shared/src/zcode-agent-policy.ts +25 -0
  99. package/server/vendor/zcode/packages/shared/src/zcode-media-policy.ts +10 -0
  100. package/server/vendor/zcode/packages/shared/src/zcode-protocol/index.ts +3720 -0
  101. package/server/vendor/zcode/packages/shared/src/zcode-protocol-legacy-types.ts +529 -0
  102. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/apply.ts +218 -0
  103. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/attachment-ref.ts +14 -0
  104. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/command.ts +482 -0
  105. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/core.ts +100 -0
  106. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/create-workflow-display.ts +156 -0
  107. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/cuaPermission.ts +41 -0
  108. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/delta.ts +147 -0
  109. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/input-intent.ts +69 -0
  110. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/rows.ts +432 -0
  111. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/session-config.ts +43 -0
  112. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/sessions-index-workflow-activity.ts +195 -0
  113. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/sessions-index.ts +85 -0
  114. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/shared-context-import.ts +38 -0
  115. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/shared-context-ref.ts +10 -0
  116. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/snapshot.ts +509 -0
  117. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/submission.ts +5 -0
  118. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/toolDisplay.ts +264 -0
  119. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/transport.ts +1152 -0
  120. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-assembler.ts +574 -0
  121. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-binary.ts +65 -0
  122. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-codec.ts +240 -0
  123. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-fault.ts +39 -0
  124. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire.ts +159 -0
  125. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-artifacts.ts +291 -0
  126. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-observation-display.ts +281 -0
  127. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-row-meta.ts +163 -0
  128. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-run-settings-command.ts +62 -0
  129. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-runs-delta.ts +517 -0
  130. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-runs.ts +512 -0
  131. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workspace-config.ts +81 -0
  132. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workspace-hook-review.ts +210 -0
  133. package/server/vendor/zcode/packages/shared/src/zcode-task-mode-schema.ts +6 -0
  134. package/server/vendor/zcode/packages/shared/src/zcode-task-types-core.ts +1187 -0
  135. package/server/vendor/zcode/packages/shared/src/zcodeEndpoint.ts +296 -0
  136. package/server/vendor/zcode/provenance.json +405 -0
  137. package/shared/account.ts +201 -0
  138. package/shared/diagnostics.ts +52 -0
  139. package/shared/runtime-setup.ts +73 -0
  140. package/vendor/acp-registry/LICENSE +176 -0
  141. package/vendor/paseo/LICENSE +211 -0
@@ -0,0 +1,270 @@
1
+ import type { NativePromptInput } from "./session-types.js";
2
+ import { realpath, stat } from "node:fs/promises";
3
+ import { basename, isAbsolute, relative } from "node:path";
4
+ import { z } from "zod";
5
+
6
+ import type {
7
+ ProviderMode,
8
+ ProviderModel,
9
+ ProviderMcpServerConfig,
10
+ } from "@getpaseo/plugin/server/provider";
11
+ import { AdapterError } from "./errors.js";
12
+ import type { ModelOption, ModelSelection } from "./host/schemas.js";
13
+
14
+ export const ZCODE_PROVIDER_ID = "zcode";
15
+ export const ZCODE_MODES: ProviderMode[] = [
16
+ {
17
+ id: "build",
18
+ label: "Ask Before Changes",
19
+ description: "Ask before changing files or running tools",
20
+ },
21
+ {
22
+ id: "edit",
23
+ label: "Edit Automatically",
24
+ description: "Edit files automatically while keeping other approvals",
25
+ },
26
+ {
27
+ id: "yolo",
28
+ label: "Full Access",
29
+ description: "Run tools without approval",
30
+ },
31
+ ];
32
+
33
+ export function requireMode(mode: string): string {
34
+ if (
35
+ mode !== "plan" &&
36
+ !ZCODE_MODES.some((candidate) => candidate.id === mode)
37
+ ) {
38
+ throw new AdapterError(
39
+ "NATIVE_PROTOCOL_ERROR",
40
+ `ZCode returned an unknown mode: ${mode}`,
41
+ );
42
+ }
43
+ return mode;
44
+ }
45
+
46
+ export function encodeModel(model: ModelSelection): string {
47
+ return JSON.stringify([model.providerId, model.modelId, null]);
48
+ }
49
+
50
+ export function decodeModel(value: string): ModelSelection {
51
+ let parsed: unknown;
52
+ try {
53
+ parsed = JSON.parse(value);
54
+ } catch {
55
+ throw new AdapterError("INVALID_CONFIGURATION", "Invalid ZCode model ID");
56
+ }
57
+ if (
58
+ !Array.isArray(parsed) ||
59
+ parsed.length !== 3 ||
60
+ typeof parsed[0] !== "string" ||
61
+ parsed[0].length === 0 ||
62
+ typeof parsed[1] !== "string" ||
63
+ parsed[1].length === 0 ||
64
+ parsed[2] !== null
65
+ ) {
66
+ throw new AdapterError("INVALID_CONFIGURATION", "Invalid ZCode model ID");
67
+ }
68
+ return {
69
+ providerId: parsed[0],
70
+ modelId: parsed[1],
71
+ };
72
+ }
73
+
74
+ export function catalogModels(
75
+ available: readonly ModelOption[],
76
+ selection?: ModelSelection,
77
+ ): ProviderModel[] {
78
+ const ids = new Set<string>();
79
+ const current = selection && encodeModel(selection);
80
+ const models = available.map((model) => {
81
+ const id = encodeModel(model.ref);
82
+ if (ids.has(id)) {
83
+ throw new AdapterError(
84
+ "NATIVE_PROTOCOL_ERROR",
85
+ `Duplicate ZCode model ID: ${id}`,
86
+ );
87
+ }
88
+ ids.add(id);
89
+ const levels = model.reasoningLevels;
90
+ const level =
91
+ (id === current ? selection?.options?.reasoningLevel : undefined) ??
92
+ levels?.at(-1);
93
+ if (levels && new Set(levels).size !== levels.length) {
94
+ throw new AdapterError(
95
+ "NATIVE_PROTOCOL_ERROR",
96
+ "Duplicate ZCode reasoning level",
97
+ );
98
+ }
99
+ return {
100
+ id,
101
+ label: model.ref.modelId,
102
+ ...(model.providerLabel === undefined
103
+ ? {}
104
+ : { description: model.providerLabel }),
105
+ isDefault: id === current,
106
+ ...(levels === undefined
107
+ ? {}
108
+ : {
109
+ thinkingOptions: levels.map((value) => ({
110
+ id: value,
111
+ label: value,
112
+ isDefault: value === level,
113
+ })),
114
+ ...(level === undefined ? {} : { defaultThinkingOptionId: level }),
115
+ }),
116
+ };
117
+ });
118
+ return models;
119
+ }
120
+
121
+ export function mapMcpServers(
122
+ servers: Record<string, ProviderMcpServerConfig> | undefined,
123
+ ): Array<Record<string, unknown>> {
124
+ return Object.entries(servers ?? {}).map(([name, server]) => {
125
+ if (name.trim() === "") {
126
+ throw new AdapterError(
127
+ "INVALID_CONFIGURATION",
128
+ "MCP server name must not be empty",
129
+ );
130
+ }
131
+ if (server.alwaysLoad !== undefined) {
132
+ throw new AdapterError(
133
+ "INVALID_CONFIGURATION",
134
+ `MCP server '${name}' uses unsupported alwaysLoad`,
135
+ );
136
+ }
137
+ if (server.type === "stdio") {
138
+ if (!isAbsolute(server.command)) {
139
+ throw new AdapterError(
140
+ "INVALID_CONFIGURATION",
141
+ `MCP server '${name}' command must be absolute`,
142
+ );
143
+ }
144
+ return {
145
+ name,
146
+ command: server.command,
147
+ args: server.args ?? [],
148
+ env: Object.entries(server.env ?? {}).map(([key, value]) => ({
149
+ name: key,
150
+ value,
151
+ })),
152
+ };
153
+ }
154
+ const headers = Object.entries(server.headers ?? {}).map(
155
+ ([key, value]) => ({
156
+ name: key,
157
+ value,
158
+ }),
159
+ );
160
+ if (server.type === "http" || server.type === "sse") {
161
+ return { type: server.type, name, url: server.url, headers };
162
+ }
163
+ throw new AdapterError(
164
+ "INVALID_CONFIGURATION",
165
+ "Unsupported MCP server type",
166
+ );
167
+ });
168
+ }
169
+
170
+ export interface NativePrompt {
171
+ content: string;
172
+ attachments: Array<{
173
+ kind: "image" | "audio" | "file";
174
+ filename: string;
175
+ mimeType: string;
176
+ dataBase64?: string;
177
+ localPath?: string;
178
+ sizeBytes?: number;
179
+ }>;
180
+ }
181
+
182
+ export async function mapPrompt(
183
+ prompt: NativePromptInput,
184
+ workspace: string,
185
+ ): Promise<NativePrompt> {
186
+ if (typeof prompt === "string") return { content: prompt, attachments: [] };
187
+ const canonicalWorkspace = await realpath(workspace);
188
+ const content: string[] = [];
189
+ const attachments: NativePrompt["attachments"] = [];
190
+ for (const block of prompt) {
191
+ if (block.type === "text") {
192
+ content.push(block.text);
193
+ continue;
194
+ }
195
+ if (block.type === "image") {
196
+ attachments.push({
197
+ kind: "image",
198
+ filename: `image.${block.mimeType.split("/")[1]?.split("+")[0] ?? "bin"}`,
199
+ mimeType: block.mimeType,
200
+ dataBase64: block.data,
201
+ sizeBytes: Buffer.from(block.data, "base64").byteLength,
202
+ });
203
+ continue;
204
+ }
205
+ if (block.type === "uploaded_file") {
206
+ if (!isAbsolute(block.path)) {
207
+ throw new AdapterError(
208
+ "UNSUPPORTED_CONTENT",
209
+ "Attachment path must be absolute",
210
+ );
211
+ }
212
+ const resolved = await realpath(block.path);
213
+ const child = relative(canonicalWorkspace, resolved);
214
+ if (child.startsWith("..") || isAbsolute(child)) {
215
+ throw new AdapterError(
216
+ "UNSUPPORTED_CONTENT",
217
+ "Attachment path must remain inside the workspace",
218
+ );
219
+ }
220
+ const info = await stat(resolved);
221
+ if (!info.isFile()) {
222
+ throw new AdapterError(
223
+ "UNSUPPORTED_CONTENT",
224
+ "Attachment must be a regular file",
225
+ );
226
+ }
227
+ attachments.push({
228
+ kind: block.mimeType.startsWith("image/")
229
+ ? "image"
230
+ : block.mimeType.startsWith("audio/")
231
+ ? "audio"
232
+ : "file",
233
+ filename: block.fileName || basename(resolved),
234
+ mimeType: block.mimeType,
235
+ localPath: resolved,
236
+ sizeBytes: info.size,
237
+ });
238
+ continue;
239
+ }
240
+ throw new AdapterError(
241
+ "UNSUPPORTED_CONTENT",
242
+ `Unsupported Paseo attachment type: ${block.type}`,
243
+ );
244
+ }
245
+ return { content: content.join("\n\n"), attachments };
246
+ }
247
+
248
+ export function planMarkdown(input: unknown): string {
249
+ const plan = record(input).plan;
250
+ if (typeof plan !== "string" || plan.trim() === "") {
251
+ throw new AdapterError(
252
+ "NATIVE_PROTOCOL_ERROR",
253
+ "ZCode plan approval has no Markdown",
254
+ );
255
+ }
256
+ return plan.trim();
257
+ }
258
+
259
+ export function record(value: unknown): Record<string, unknown> {
260
+ return value !== null && typeof value === "object" && !Array.isArray(value)
261
+ ? (value as Record<string, unknown>)
262
+ : {};
263
+ }
264
+
265
+ export function jsonValue(value: unknown) {
266
+ return z.json().parse(value);
267
+ }
268
+ export function jsonObject(value: unknown) {
269
+ return z.record(z.string(), z.json()).parse(value);
270
+ }
@@ -0,0 +1,68 @@
1
+ import { AdapterError } from "./errors.js";
2
+ import type { HostBridge } from "./host/bridge.js";
3
+ import { catalogModels, encodeModel } from "./mapping.js";
4
+ import {
5
+ ModelSelectionViewSchema,
6
+ type ModelSelection,
7
+ type ModelSelectionView,
8
+ } from "./host/schemas.js";
9
+
10
+ export async function readModelSelection(
11
+ bridge: HostBridge,
12
+ selection?: ModelSelection,
13
+ ): Promise<ModelSelectionView> {
14
+ const view = await bridge.request(
15
+ "readModelSelection",
16
+ { selection },
17
+ ModelSelectionViewSchema,
18
+ 60_000,
19
+ );
20
+ catalogModels(view.models, view.preferredSelection);
21
+ if (view.models.length === 0)
22
+ throw new AdapterError(
23
+ "AUTH_REQUIRED",
24
+ "No usable ZCode model provider is configured",
25
+ );
26
+ return view;
27
+ }
28
+
29
+ export async function resolveModelSelection(
30
+ bridge: HostBridge,
31
+ view: ModelSelectionView,
32
+ selection: ModelSelection,
33
+ ): Promise<ModelSelection> {
34
+ const model = view.models.find(
35
+ (item) => encodeModel(item.ref) === encodeModel(selection),
36
+ );
37
+ if (!model)
38
+ throw new AdapterError("INVALID_CONFIGURATION", "Unknown ZCode model");
39
+ // ZCode's projectAppModelOption uses the last advertised level as default.
40
+ // Model IDs and reasoning controls remain separate in Paseo.
41
+ const reasoningLevel =
42
+ selection.options?.reasoningLevel ?? model.reasoningLevels.at(-1)!;
43
+ if (!model.reasoningLevels.includes(reasoningLevel))
44
+ throw new AdapterError(
45
+ "INVALID_CONFIGURATION",
46
+ "Unknown ZCode thinking option",
47
+ );
48
+ const resolved = await readModelSelection(bridge, {
49
+ ...selection,
50
+ options: { reasoningLevel },
51
+ });
52
+ if (resolved.selectionIssue || !resolved.effectiveSelection)
53
+ throw new AdapterError(
54
+ "INVALID_CONFIGURATION",
55
+ "ZCode model selection is unavailable",
56
+ );
57
+ if (encodeModel(resolved.effectiveSelection) !== encodeModel(selection))
58
+ throw new AdapterError(
59
+ "INVALID_CONFIGURATION",
60
+ "ZCode resolved a different model; select it explicitly",
61
+ );
62
+ if (resolved.effectiveSelection.options?.reasoningLevel !== reasoningLevel)
63
+ throw new AdapterError(
64
+ "INVALID_CONFIGURATION",
65
+ "ZCode resolved a different reasoning level",
66
+ );
67
+ return resolved.effectiveSelection;
68
+ }
@@ -0,0 +1,146 @@
1
+ import { mkdir, open, readFile, rename, rm } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { randomUUID } from "node:crypto";
5
+ import { z } from "zod";
6
+ import type { ProviderPersistence } from "@getpaseo/plugin/server/provider";
7
+ import { AdapterError } from "./errors.js";
8
+
9
+ const workspace = z.string().min(1);
10
+ const dataSchema = z.discriminatedUnion("kind", [
11
+ z
12
+ .object({ kind: z.literal("logical"), id: z.uuid(), cwd: workspace })
13
+ .strict(),
14
+ z
15
+ .object({
16
+ kind: z.literal("native"),
17
+ sessionId: z.string().min(1),
18
+ cwd: workspace,
19
+ })
20
+ .strict(),
21
+ ]);
22
+ const recordSchema = z
23
+ .object({
24
+ version: z.literal(1),
25
+ sessionId: z.string().min(1),
26
+ cwd: workspace,
27
+ })
28
+ .strict();
29
+ export type PersistenceData = z.infer<typeof dataSchema>;
30
+
31
+ export function parsePersistence(handle: ProviderPersistence): PersistenceData {
32
+ if (handle.version !== 3)
33
+ throw new AdapterError(
34
+ "PERSISTENCE_VERSION_UNSUPPORTED",
35
+ "Unsupported ZCode persistence version; expected version 3",
36
+ );
37
+ try {
38
+ return dataSchema.parse(handle.data);
39
+ } catch {
40
+ throw new AdapterError(
41
+ "PERSISTENCE_INVALID",
42
+ "Invalid ZCode persistence handle",
43
+ );
44
+ }
45
+ }
46
+
47
+ export function persistenceHandle(data: PersistenceData): ProviderPersistence {
48
+ return { version: 3, data };
49
+ }
50
+
51
+ // Outside the plugin checkout: reinstalling/building the plugin must not erase handles.
52
+ export function defaultSessionDirectory(
53
+ environment: NodeJS.ProcessEnv = process.env,
54
+ ): string {
55
+ return join(
56
+ environment.XDG_STATE_HOME ?? join(homedir(), ".local", "state"),
57
+ "paseo-plugin-zcode-provider",
58
+ "sessions-v3",
59
+ );
60
+ }
61
+
62
+ export class SessionPersistenceStore {
63
+ constructor(readonly directory: string = defaultSessionDirectory()) {}
64
+
65
+ private filename(id: string): string {
66
+ return join(this.directory, `${z.uuid().parse(id)}.json`);
67
+ }
68
+
69
+ async resolve(data: PersistenceData): Promise<string | undefined> {
70
+ if (data.kind === "native") return data.sessionId;
71
+ let content: string;
72
+ try {
73
+ content = await readFile(this.filename(data.id), "utf8");
74
+ } catch (error) {
75
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
76
+ throw new AdapterError(
77
+ "PERSISTENCE_INVALID",
78
+ "Cannot read ZCode persistence record",
79
+ {},
80
+ { cause: error },
81
+ );
82
+ }
83
+ let record: z.infer<typeof recordSchema>;
84
+ try {
85
+ record = recordSchema.parse(JSON.parse(content));
86
+ } catch (error) {
87
+ throw new AdapterError(
88
+ "PERSISTENCE_INVALID",
89
+ "Invalid ZCode persistence record",
90
+ {},
91
+ { cause: error },
92
+ );
93
+ }
94
+ if (record.cwd !== data.cwd)
95
+ throw new AdapterError(
96
+ "INVALID_WORKSPACE",
97
+ "ZCode persistence belongs to another workspace",
98
+ );
99
+ return record.sessionId;
100
+ }
101
+
102
+ async save(data: PersistenceData, sessionId: string): Promise<void> {
103
+ if (data.kind === "native") return;
104
+ const record = recordSchema.parse({ version: 1, sessionId, cwd: data.cwd });
105
+ try {
106
+ await mkdir(this.directory, { recursive: true, mode: 0o700 });
107
+ await this.write(data.id, record);
108
+ } catch (error) {
109
+ throw new AdapterError(
110
+ "PERSISTENCE_WRITE_FAILED",
111
+ "Cannot save ZCode resume information; prompt was not sent",
112
+ {},
113
+ { cause: error },
114
+ );
115
+ }
116
+ }
117
+
118
+ private async write(
119
+ id: string,
120
+ record: z.infer<typeof recordSchema>,
121
+ ): Promise<void> {
122
+ const destination = this.filename(id);
123
+ const temporary = `${destination}.${randomUUID()}.tmp`;
124
+ try {
125
+ const file = await open(temporary, "wx", 0o600);
126
+ try {
127
+ await file.writeFile(JSON.stringify(record));
128
+ await file.sync();
129
+ } finally {
130
+ await file.close();
131
+ }
132
+ await rename(temporary, destination);
133
+ // Windows does not support opening directories for fsync.
134
+ if (process.platform !== "win32") {
135
+ const directory = await open(this.directory, "r");
136
+ try {
137
+ await directory.sync();
138
+ } finally {
139
+ await directory.close();
140
+ }
141
+ }
142
+ } finally {
143
+ await rm(temporary, { force: true });
144
+ }
145
+ }
146
+ }