paseo-plugin-zcode-provider 0.1.0 → 0.3.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.
@@ -1,2 +1,2 @@
1
1
  // Generated from package.json by npm prepare / prebuild.
2
- export const PROVIDER_VERSION = "0.1.0";
2
+ export const PROVIDER_VERSION = "0.3.0";
@@ -62,6 +62,23 @@ const operations = new Set([
62
62
  "attachmentChunkV4",
63
63
  "attachmentCommitV4",
64
64
  "attachmentAbortV4",
65
+ "oauth.restoreCachedSessionState",
66
+ "oauth.getActiveProvider",
67
+ "oauth.startOAuthWithPolling",
68
+ "oauth.pollPendingOAuth",
69
+ "oauth.cancelPending",
70
+ "oauth.logout",
71
+ "setting.get",
72
+ "setting.update",
73
+ "provider-settings.getView",
74
+ "provider-settings.refresh",
75
+ "provider-settings.createPersonalProvider",
76
+ "provider-settings.savePersonalProviderOverlay",
77
+ "provider-settings.deletePersonalProvider",
78
+ "provider-settings.addPersonalModel",
79
+ "provider-settings.deletePersonalModel",
80
+ "provider-settings.setPersonalModelEnabled",
81
+ "usage-stats.getEntitlementSnapshot",
65
82
  "event",
66
83
  "catalog",
67
84
  "session.create",
@@ -75,7 +75,7 @@ async function configuredPaths(environment: NodeJS.ProcessEnv): Promise<{
75
75
  if (managed === undefined)
76
76
  throw new AdapterError(
77
77
  "RUNTIME_SETUP_REQUIRED",
78
- "ZCode runtime is not set up. Open Settings → Plugins → zcode-provider to set it up.",
78
+ "ZCode runtime is not set up. Install it in Settings → Plugins → zcode-provider → Settings → Runtime.",
79
79
  );
80
80
  return { ...managed, source: "managed" };
81
81
  }
@@ -33,4 +33,4 @@ export function assessCompatibility(identity: RuntimeIdentity): {
33
33
  }
34
34
 
35
35
  // Last reviewed public changelog release; source versions are not releases.
36
- export const LAST_REVIEWED_ZCODE_RELEASE = "3.14.3";
36
+ export const LAST_REVIEWED_ZCODE_RELEASE = "3.14.4";
@@ -27,6 +27,13 @@ import {
27
27
  } from "../diagnostics.js";
28
28
  import type { Logger } from "../logger.js";
29
29
  import { PROVIDER_VERSION } from "../build-info.js";
30
+ import { UNSUPPORTED_CAPABILITY_ENVIRONMENT } from "../unsupported-capabilities.js";
31
+
32
+ export type SettingsChannel =
33
+ | "oauth"
34
+ | "setting"
35
+ | "provider-settings"
36
+ | "usage-stats";
30
37
 
31
38
  export interface HostSubscription {
32
39
  readonly id: string;
@@ -136,6 +143,7 @@ export class ZCodeHostBridge implements HostBridge {
136
143
  cwd: workspace,
137
144
  env: {
138
145
  ...runtimeEnvironment(environment),
146
+ ...UNSUPPORTED_CAPABILITY_ENVIRONMENT,
139
147
  ZCODE_SERVICE_AUTHORITY_MODE: "standalone-server",
140
148
  ZCODE_AGENT_SERVER_COMMAND: runtime.paths.executable,
141
149
  ZCODE_AGENT_SERVER_ARGS_JSON: JSON.stringify([
@@ -265,7 +273,7 @@ export class ZCodeHostBridge implements HostBridge {
265
273
  }) + "\n",
266
274
  );
267
275
  const v4 = helloMessageSchema.parse(
268
- await this.call("zcode-agent", "helloConversationV4", undefined, 10_000),
276
+ await this.call("zcode-agent", "helloConversationV4", [], 10_000),
269
277
  );
270
278
  if (
271
279
  v4.clientMode !== "desktop-continuous" ||
@@ -278,18 +286,20 @@ export class ZCodeHostBridge implements HostBridge {
278
286
  await this.call(
279
287
  "zcode-agent",
280
288
  "initializeConversationV4",
281
- {
282
- kind: "clientHello",
283
- protocolVersion: 3,
284
- clientId: "paseo-zcode-provider",
285
- appVersion: PROVIDER_VERSION,
286
- },
289
+ [
290
+ {
291
+ kind: "clientHello",
292
+ protocolVersion: 3,
293
+ clientId: "paseo-zcode-provider",
294
+ appVersion: PROVIDER_VERSION,
295
+ },
296
+ ],
287
297
  10_000,
288
298
  );
289
299
  await this.call(
290
300
  "zcode-agent",
291
301
  "syncAppRuntimePreferences",
292
- { askUserQuestionAutoResolutionEnabled: false },
302
+ [{ askUserQuestionAutoResolutionEnabled: false }],
293
303
  10_000,
294
304
  );
295
305
  }
@@ -314,7 +324,7 @@ export class ZCodeHostBridge implements HostBridge {
314
324
  private async call(
315
325
  channel: string,
316
326
  method: string,
317
- params: unknown,
327
+ args: readonly unknown[],
318
328
  timeoutMs: number,
319
329
  ): Promise<unknown> {
320
330
  if (!this.client)
@@ -323,13 +333,7 @@ export class ZCodeHostBridge implements HostBridge {
323
333
  let timer: ReturnType<typeof setTimeout> | undefined;
324
334
  try {
325
335
  return await Promise.race([
326
- this.client
327
- .getChannel(channel)
328
- .call(
329
- method,
330
- params === undefined ? [] : [params],
331
- cancellation.token,
332
- ),
336
+ this.client.getChannel(channel).call(method, args, cancellation.token),
333
337
  new Promise<never>((_, reject) => {
334
338
  timer = setTimeout(() => {
335
339
  reject(new AdapterError("NATIVE_TIMEOUT", "ZCode RPC timed out"));
@@ -342,6 +346,12 @@ export class ZCodeHostBridge implements HostBridge {
342
346
  cancellation.dispose();
343
347
  }
344
348
  }
349
+ private async ensureOpen(): Promise<void> {
350
+ await this.ready;
351
+ if (this.failure) throw this.failure;
352
+ if (this.closing)
353
+ throw new AdapterError("NATIVE_EXITED", "ZCode Server is closing");
354
+ }
345
355
  async request<Schema extends z.ZodType>(
346
356
  method: string,
347
357
  params: unknown,
@@ -349,14 +359,12 @@ export class ZCodeHostBridge implements HostBridge {
349
359
  timeoutMs = 30_000,
350
360
  ): Promise<z.output<Schema>> {
351
361
  try {
352
- await this.ready;
353
- if (this.failure) throw this.failure;
354
- if (this.closing)
355
- throw new AdapterError("NATIVE_EXITED", "ZCode Server is closing");
362
+ await this.ensureOpen();
363
+ const args = params === undefined ? [] : [params];
356
364
  let result: unknown;
357
365
  if (method === "readModelSelection")
358
366
  result = projectModelSelection(
359
- await this.call("model-selection", "getView", params, timeoutMs),
367
+ await this.call("model-selection", "getView", args, timeoutMs),
360
368
  );
361
369
  else
362
370
  result = await this.call(
@@ -365,7 +373,7 @@ export class ZCodeHostBridge implements HostBridge {
365
373
  ? "usage-stats"
366
374
  : "zcode-agent",
367
375
  method,
368
- params,
376
+ args,
369
377
  timeoutMs,
370
378
  );
371
379
  return resultSchema.parse(result);
@@ -377,6 +385,27 @@ export class ZCodeHostBridge implements HostBridge {
377
385
  });
378
386
  }
379
387
  }
388
+ /** Calls an official account or settings service with positional arguments. */
389
+ async invoke<Schema extends z.ZodType>(
390
+ channel: SettingsChannel,
391
+ method: string,
392
+ args: readonly unknown[],
393
+ resultSchema: Schema,
394
+ timeoutMs = 30_000,
395
+ ): Promise<z.output<Schema>> {
396
+ try {
397
+ await this.ensureOpen();
398
+ return resultSchema.parse(
399
+ await this.call(channel, method, args, timeoutMs),
400
+ );
401
+ } catch (error) {
402
+ throw diagnosticError(error, {
403
+ ...this.diagnostic,
404
+ stage: "request",
405
+ operation: `${channel}.${method}`,
406
+ });
407
+ }
408
+ }
380
409
  async subscribe(
381
410
  target: { workspacePath: string; sessionId: string },
382
411
  handler: (frame: ConversationTopicWireCandidate) => Promise<void> | void,
@@ -8,6 +8,7 @@ import type {
8
8
  } from "@getpaseo/plugin/server/provider";
9
9
  import type {
10
10
  ConversationRow,
11
+ HookInvocationRow,
11
12
  TimelineMarkerPayload,
12
13
  } from "./vendor/zcode/packages/shared/src/zcode-protocol-v4/rows.js";
13
14
  import type {
@@ -160,13 +161,12 @@ export function rowTimeline(
160
161
  message: `${row.displayName} (${row.artifactType}, ${row.sizeBytes} bytes)`,
161
162
  };
162
163
  case "hookInvocation":
163
- return {
164
- id,
165
- type: "notification",
166
- level: "info",
167
- message: `${row.hookEventName}: ${row.executions.map((h) => `${h.displayName} (${h.outcome ?? h.state})`).join(", ")}`,
168
- };
164
+ return hookTimeline(id, row);
169
165
  case "timelineMarker":
166
+ // Paseo appends every notification it receives, so a changing row is
167
+ // reported once, in its final state.
168
+ if (row.marker.type === "compact" && row.marker.status === "running")
169
+ return;
170
170
  return {
171
171
  id,
172
172
  type: "notification",
@@ -175,6 +175,48 @@ export function rowTimeline(
175
175
  };
176
176
  }
177
177
  }
178
+ // Like ZCode Desktop, which keeps hooks out of the conversation and lists
179
+ // them on request, successful hooks are not shown. Paseo appends every
180
+ // notification it receives, so an invocation is reported once, after it ends,
181
+ // and only when a hook that ran did not succeed.
182
+ function hookTimeline(
183
+ id: string,
184
+ row: HookInvocationRow,
185
+ ): ProviderTimelineItem | undefined {
186
+ if (
187
+ row.state === "running" ||
188
+ row.executions.some((h) => h.state === "running")
189
+ )
190
+ return;
191
+ const problems = row.executions.flatMap((hook) => {
192
+ if (!hook.didExecute) return [];
193
+ const outcome =
194
+ hook.outcome && hook.outcome !== "success"
195
+ ? hook.outcome
196
+ : hook.state === "failed"
197
+ ? "failed"
198
+ : undefined;
199
+ return outcome ? [{ hook, outcome }] : [];
200
+ });
201
+ if (problems.length === 0) return;
202
+ const outcomes = new Set(problems.map((p) => p.outcome));
203
+ return {
204
+ id,
205
+ type: "notification",
206
+ level: outcomes.has("failed")
207
+ ? "error"
208
+ : outcomes.has("blocked") || outcomes.has("timed_out")
209
+ ? "warning"
210
+ : "info",
211
+ message: `${row.hookEventName} hook ${problems
212
+ .map(
213
+ ({ hook, outcome }) =>
214
+ `${outcome.replace("_", " ")}: ${hook.displayName}${hook.blockReason ? ` (${hook.blockReason})` : ""}`,
215
+ )
216
+ .join("; ")}`,
217
+ };
218
+ }
219
+
178
220
  export function usage(state: ConversationSnapshot): ProviderUsage {
179
221
  return {
180
222
  inputTokens: state.usage.cumulative.inputTokens,
@@ -0,0 +1,81 @@
1
+ import type {
2
+ ProviderStatus,
3
+ ProviderStatusRequest,
4
+ } from "@getpaseo/plugin/server/provider";
5
+ import {
6
+ assertRuntimeSupported,
7
+ discoverRuntime,
8
+ } from "./discovery/discover.js";
9
+ import { AdapterError } from "./errors.js";
10
+ import { logger } from "./logger.js";
11
+
12
+ export interface RuntimeStatusOptions {
13
+ readonly environment?: NodeJS.ProcessEnv;
14
+ readonly discover?: typeof discoverRuntime;
15
+ /** How long a result is reused. Paseo asks on every catalog load and agent start. */
16
+ readonly ttlMs?: number;
17
+ readonly now?: () => number;
18
+ }
19
+
20
+ // Same shape as Paseo's built-in provider diagnostics; the daemon appends the
21
+ // model count and snapshot status as further indented lines.
22
+ const format = (entries: readonly [string, string][]) =>
23
+ ["ZCode", ...entries.map(([label, value]) => ` ${label}: ${value}`)].join(
24
+ "\n",
25
+ );
26
+
27
+ async function check(
28
+ environment: NodeJS.ProcessEnv,
29
+ discover: typeof discoverRuntime,
30
+ ): Promise<ProviderStatus> {
31
+ try {
32
+ const runtime = await discover({ environment });
33
+ assertRuntimeSupported(runtime);
34
+ const { appVersion, cliVersion, nodeVersion, platform } = runtime.identity;
35
+ return {
36
+ available: true,
37
+ diagnostic: format([
38
+ [
39
+ "Runtime",
40
+ `${runtime.source === "managed" ? "managed" : "PASEO_ZCODE_RUNTIME"}, Server ${appVersion}, Agent ${cliVersion}`,
41
+ ],
42
+ ["Node.js", nodeVersion],
43
+ ["Platform", platform],
44
+ ]),
45
+ };
46
+ } catch (error) {
47
+ // Discovery errors carry fixed messages; anything else stays in the log.
48
+ if (!(error instanceof AdapterError))
49
+ logger.error("zcode.provider.status.failed", error);
50
+ return {
51
+ available: false,
52
+ diagnostic: format([
53
+ [
54
+ "Error",
55
+ error instanceof AdapterError
56
+ ? error.message
57
+ : "ZCode runtime could not be inspected",
58
+ ],
59
+ ]),
60
+ };
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Availability for Paseo's provider snapshot and diagnostics (Paseo 0.11+;
66
+ * older daemons ignore it). It inspects the runtime without starting a Server,
67
+ * so sign-in problems still surface when a session opens.
68
+ */
69
+ export function createRuntimeStatus(options: RuntimeStatusOptions = {}) {
70
+ const environment = options.environment ?? process.env;
71
+ const discover = options.discover ?? discoverRuntime;
72
+ const ttlMs = options.ttlMs ?? 30_000;
73
+ const now = options.now ?? Date.now;
74
+ let cached: { at: number; result: Promise<ProviderStatus> } | undefined;
75
+ return (_request?: ProviderStatusRequest): Promise<ProviderStatus> => {
76
+ if (cached && now() - cached.at < ttlMs) return cached.result;
77
+ const entry = { at: now(), result: check(environment, discover) };
78
+ cached = entry;
79
+ return entry.result;
80
+ };
81
+ }
@@ -27,6 +27,7 @@ import {
27
27
  runRuntimeSmoke,
28
28
  } from "./discovery/discover.js";
29
29
  import { ZCodeHostBridge, type HostBridge } from "./host/bridge.js";
30
+ import { createRuntimeStatus } from "./provider-status.js";
30
31
  import { logger } from "./logger.js";
31
32
  import { MINIMUM_ZCODE_VERSION } from "./discovery/manifest.js";
32
33
  import {
@@ -68,11 +69,11 @@ export type BridgeFactory = (
68
69
  workspace?: string,
69
70
  ) => Promise<HostBridge>;
70
71
 
71
- async function createHost(
72
+ export async function createHost(
72
73
  environment: Readonly<Record<string, string>>,
73
74
  signal: AbortSignal,
74
75
  workspace?: string,
75
- ): Promise<HostBridge> {
76
+ ): Promise<ZCodeHostBridge> {
76
77
  const env = { ...process.env, ...environment };
77
78
  const runtime = await discoverRuntime({ environment: env, signal });
78
79
  assertRuntimeSupported(runtime);
@@ -114,6 +115,7 @@ interface SessionEntry {
114
115
  export function createZCodeProvider(
115
116
  bridgeFactory: BridgeFactory = createHost,
116
117
  persistenceStore = new SessionPersistenceStore(),
118
+ status: ProviderRegistration["status"] = createRuntimeStatus(),
117
119
  ): ProviderRegistration {
118
120
  return {
119
121
  id: "zcode",
@@ -121,6 +123,9 @@ export function createZCodeProvider(
121
123
  description:
122
124
  "ZCode workspace agent with native planning, tools, and model selection",
123
125
  icon: "icon.svg",
126
+ // No `command`: Paseo would look it up on PATH, but the runtime is the
127
+ // plugin's managed installation or the PASEO_ZCODE_* override.
128
+ status,
124
129
  async connect(request) {
125
130
  if (!request.versions.includes(PROVIDER_PROTOCOL_VERSION))
126
131
  throw new Error("ZCode requires provider protocol version 1");
@@ -812,7 +817,9 @@ function publicError(error: unknown): ProviderError {
812
817
  RUNTIME_DISCOVERY_FAILED:
813
818
  "ZCode installation or required host structure could not be inspected. See the diagnostic for the failure stage.",
814
819
  RUNTIME_SETUP_REQUIRED:
815
- "ZCode runtime is not set up. Open Settings → Plugins → zcode-provider to set it up.",
820
+ "ZCode runtime is not set up. Install it in Settings → Plugins → zcode-provider → Settings → Runtime.",
821
+ AUTH_REQUIRED:
822
+ "No ZCode model provider is usable. Sign in or add a provider in Settings → Plugins → zcode-provider → Settings → Account.",
816
823
  PERSISTENCE_VERSION_UNSUPPORTED:
817
824
  "ZCode persistence version is unsupported. Create a new Paseo ZCode session.",
818
825
  PERSISTENCE_INVALID:
@@ -53,12 +53,14 @@ export const MANAGED_NODE_ARCHIVES: Readonly<
53
53
  };
54
54
 
55
55
  // Published by .github/workflows/zcode-runtime-release.yml from the pinned
56
- // unmodified source. The same archive serves every managed platform.
56
+ // source with patches/zcode applied (ADR 19). The same archive serves every
57
+ // managed platform.
57
58
  export const MANAGED_ZCODE_VERSION = "3.14.3";
59
+ const MANAGED_ZCODE_RELEASE = `${MANAGED_ZCODE_VERSION}-paseo.1`;
58
60
  export const MANAGED_ZCODE_ARCHIVE: PinnedArchive = {
59
- url: `https://github.com/supermomonga/paseo-plugin-zcode-provider/releases/download/zcode-runtime-v${MANAGED_ZCODE_VERSION}/zcode-runtime-${MANAGED_ZCODE_VERSION}.tar.gz`,
60
- sha256: "a2af414592362d226f92c105d91211e5c4f138cf985b0c86da0eb9163cb9aee7",
61
- size: 79_887_659,
61
+ url: `https://github.com/supermomonga/paseo-plugin-zcode-provider/releases/download/zcode-runtime-v${MANAGED_ZCODE_RELEASE}/zcode-runtime-${MANAGED_ZCODE_RELEASE}.tar.gz`,
62
+ sha256: "c51e4547dc804894fd7d62766ab0ce3d759948b8b56cb515528691d75a8e09f9",
63
+ size: 79_883_611,
62
64
  };
63
65
 
64
66
  export function managedPlatform(
@@ -1,4 +1,3 @@
1
- import { join } from "node:path";
2
1
  import type { RuntimeSetupStatus } from "../shared/runtime-setup.js";
3
2
  import { AdapterError } from "./errors.js";
4
3
  import {
@@ -14,20 +13,6 @@ const LICENSES = {
14
13
  "Unofficial build of ZCode (Apache-2.0); third-party terms in THIRD-PARTY-NOTICES.md",
15
14
  } as const;
16
15
 
17
- // Quoted for the shell a user is most likely to paste into on that OS.
18
- export function loginCommand(
19
- executable: string,
20
- installRoot: string,
21
- windows = process.platform === "win32",
22
- ): string {
23
- const script = join(installRoot, "bin", "zcode.mjs");
24
- return windows
25
- ? `& ${powershellQuote(executable)} ${powershellQuote(script)} login`
26
- : `${posixQuote(executable)} ${posixQuote(script)} login`;
27
- }
28
- const posixQuote = (value: string) => `'${value.replaceAll("'", `'\\''`)}'`;
29
- const powershellQuote = (value: string) => `'${value.replaceAll("'", "''")}'`;
30
-
31
16
  export function createRuntimeSetupHandlers(
32
17
  installer: ManagedRuntimeInstaller,
33
18
  environment: NodeJS.ProcessEnv = process.env,
@@ -63,24 +48,14 @@ export function createRuntimeSetupHandlers(
63
48
  directory: managedRuntimeRoot(environment),
64
49
  components: [],
65
50
  };
66
- const components = [
67
- await component(layout.node),
68
- await component(layout.zcode),
69
- ];
70
51
  return {
71
52
  ...base,
72
53
  supported: true,
73
54
  directory: layout.root,
74
- components,
75
- ...(components.every((entry) => entry.installed)
76
- ? {
77
- loginCommand: loginCommand(
78
- layout.node.executable,
79
- layout.zcode.directory,
80
- layout.platform.startsWith("win-"),
81
- ),
82
- }
83
- : {}),
55
+ components: [
56
+ await component(layout.node),
57
+ await component(layout.zcode),
58
+ ],
84
59
  };
85
60
  },
86
61
  install: async () => ({ started: installer.start() }),
package/server/session.ts CHANGED
@@ -41,6 +41,7 @@ import {
41
41
  } from "./host/schemas.js";
42
42
  import type { HostBridge } from "./host/bridge.js";
43
43
  import type { Logger } from "./logger.js";
44
+ import { UNSUPPORTED_TOOLS } from "./unsupported-capabilities.js";
44
45
  import type {
45
46
  NativePromptInput,
46
47
  NativeSessionEvent,
@@ -611,6 +612,7 @@ export class ZCodeSession {
611
612
  modelSelection: config.modelSelection,
612
613
  mode: config.mode,
613
614
  planEnabled: config.planEnabled === true,
615
+ toolDisallowlist: [...UNSUPPORTED_TOOLS],
614
616
  requestedDelivery: previous
615
617
  ? attachments.length
616
618
  ? "queue"
@@ -0,0 +1,18 @@
1
+ // Official ZCode capabilities that cannot run in the provider's standalone Server
2
+ // (ADR 19). Computer Use needs the code-signed Desktop to start its Helper, and
3
+ // only the Desktop host supplies browsers to Browser Use.
4
+ export const SUPPRESSED_OFFICIAL_PLUGINS = [
5
+ "computer-use@zcode-plugins-official",
6
+ "browser-use@zcode-plugins-official",
7
+ ] as const;
8
+
9
+ // Both capabilities run through this shared tool. Hiding it per turn covers
10
+ // runtimes without the ZCode patch, such as the PASEO_ZCODE_RUNTIME override.
11
+ export const UNSUPPORTED_TOOLS = ["mcp__node_repl__js"] as const;
12
+
13
+ export const UNSUPPORTED_CAPABILITY_ENVIRONMENT = {
14
+ // Official switch: the Server no longer hands node_repl a Computer Use broker.
15
+ ZCODE_CUA_PRODUCT_HELPER: "0",
16
+ // Read by patches/zcode/0001-hide-suppressed-official-plugins.patch.
17
+ PASEO_ZCODE_SUPPRESSED_PLUGINS: SUPPRESSED_OFFICIAL_PLUGINS.join(","),
18
+ } as const;