paseo-plugin-zcode-provider 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -81,6 +81,7 @@ The source baseline is `3c5b227649371507ddc4eb46ac5133afe6db1cab` (ZCode 3.16.2,
81
81
  - Models and reasoning options come from ZCode. Editing modes are `build`, `edit`, `guarded` and `yolo`; `settings.plan_mode` is independent. `guarded` (ZCode's Autonomous mode, Server 3.15.1 or later) runs tools without approval but asks once, each time, before a command that ZCode's safety rules consider risky; it is not offered on older runtimes.
82
82
  - Text, thoughts, tools, subagent progress, usage and history use V4 snapshots and deltas. Tool output is marked when ZCode truncates it. Usage is cumulative; repeated snapshots do not add it again.
83
83
  - Text submitted during generation uses native guidance. Attachments use the native queue. Paseo reports one run across these native turns and completes it only after all accepted input has been consumed and foreground work has ended.
84
+ - Paseo sends every message typed in the app during a run as a steer that asks to clear pending permissions. The plugin queues it first, then declines pending permissions, questions and Plan approvals with their deny action until ZCode reads the message, including requests opened in the meantime. Requests that appear after it is read are left to the user. Declining a Plan approval in Plan mode ends ZCode's turn; the message then starts the next native turn in the same Paseo run (ADR 22).
84
85
  - Acceptance is reported once, after the native acknowledgement. Unknown delivery results are never automatically resent. Stop identifies the current native execution, disables automatic queue execution and removes pending input. A new explicit run enables the queue again.
85
86
  - As in ZCode Desktop, successful hooks are not shown. A finished hook invocation appears once, as a notification, only when a hook was blocked, failed, timed out or was cancelled; context compaction also appears once, when it ends. Paseo appends every notification it receives, so progress is not reported.
86
87
  - Native permission IDs, option values, questions and Plan approval retain their meanings. Question auto-resolution is disabled for each owned Server. Workspace hook trust is reviewed through the official CLI; it is not represented as “allow once.”
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paseo-plugin-zcode-provider",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Unofficial Paseo provider plugin for ZCode, using the official stdio Services Server of the integrated ZCode CLI.",
5
5
  "keywords": [
6
6
  "paseo",
@@ -1,2 +1,2 @@
1
1
  // Generated from package.json by npm prepare / prebuild.
2
- export const PROVIDER_VERSION = "0.4.2";
2
+ export const PROVIDER_VERSION = "0.4.3";
@@ -233,6 +233,8 @@ export function usage(state: ConversationSnapshot): ProviderUsage {
233
233
  export interface PresentedInteraction {
234
234
  request: ProviderPermissionRequest;
235
235
  answer(response: ProviderPermissionResponse): Record<string, unknown>;
236
+ /** Native answer of the first deny action, without interrupting. */
237
+ decline: Record<string, unknown>;
236
238
  }
237
239
  export function presentInteraction(
238
240
  interaction: PendingInteraction,
@@ -262,7 +264,8 @@ export function presentInteraction(
262
264
  variant: behavior === "allow" ? "primary" : "danger",
263
265
  };
264
266
  });
265
- if (!actions.some((a) => a.behavior === "deny"))
267
+ const deny = actions.find((a) => a.behavior === "deny");
268
+ if (!deny)
266
269
  throw new AdapterError(
267
270
  "NATIVE_PROTOCOL_ERROR",
268
271
  "ZCode permission has no deny option",
@@ -299,6 +302,7 @@ export function presentInteraction(
299
302
  );
300
303
  return { optionId: action.id };
301
304
  },
305
+ decline: { optionId: deny.id },
302
306
  };
303
307
  }
304
308
  if (p.sensitive)
@@ -414,6 +418,7 @@ export function presentInteraction(
414
418
  },
415
419
  };
416
420
  },
421
+ decline: { action: "decline" },
417
422
  };
418
423
  }
419
424
  return {
@@ -445,6 +450,7 @@ export function presentInteraction(
445
450
  content: answerContent(record(response.updatedInput?.answers)),
446
451
  };
447
452
  },
453
+ decline: { action: "decline" },
448
454
  };
449
455
  }
450
456
 
@@ -220,7 +220,9 @@ export class ZCodeConnection implements ProviderConnection {
220
220
  if (input.type === "session.prompt") {
221
221
  const promptEntry = entry ?? this.sessions.get(input.sessionId);
222
222
  if (promptEntry?.messages.get(input.prompt.clientMessageId)) return;
223
- promptEntry?.messages.set(input.prompt.clientMessageId, true);
223
+ // The input was not admitted. Paseo's interrupt-and-replace fallback
224
+ // for an unavailable steer resends it with the same ID.
225
+ promptEntry?.messages.delete(input.prompt.clientMessageId);
224
226
  this.emit({
225
227
  type: "session.prompt_result",
226
228
  sessionId: input.sessionId,
@@ -374,11 +376,6 @@ export class ZCodeConnection implements ProviderConnection {
374
376
  switch (input.type) {
375
377
  case "session.prompt": {
376
378
  const prompt = input.prompt.input;
377
- if (input.prompt.clearPendingPermissions)
378
- throw new AdapterError(
379
- "INTERACTION_UNSUPPORTED",
380
- "Steering does not resolve pending ZCode permissions",
381
- );
382
379
  if (input.prompt.delivery === "steer" && !entry.native.hasActiveTurn())
383
380
  throw new AdapterError(
384
381
  "SESSION_BUSY",
@@ -439,6 +436,7 @@ export class ZCodeConnection implements ProviderConnection {
439
436
  {
440
437
  clientMessageId: input.prompt.clientMessageId,
441
438
  delivery: input.prompt.delivery,
439
+ clearPendingPermissions: input.prompt.clearPendingPermissions,
442
440
  beforeSend: async () => {
443
441
  if (!entry.submitted) {
444
442
  await this.persistenceStore.save(
package/server/session.ts CHANGED
@@ -55,6 +55,8 @@ interface Admission {
55
55
  accepted: boolean;
56
56
  consumed: boolean;
57
57
  productTurnId?: string;
58
+ /** Paseo's clearPendingPermissions: decline interactions until consumed. */
59
+ releasesInteractions?: boolean;
58
60
  }
59
61
  interface Run {
60
62
  id: string;
@@ -286,6 +288,7 @@ export class ZCodeSession {
286
288
  });
287
289
  }
288
290
  }
291
+ this.releaseInteractions();
289
292
  if (
290
293
  this.state.workspaceHookAdmission ||
291
294
  this.state.pendingInteractions.some(
@@ -323,6 +326,29 @@ export class ZCodeSession {
323
326
  }
324
327
  this.maybeComplete();
325
328
  }
329
+ /**
330
+ * ZCode reads a steer only after every tool call of the model step settles,
331
+ * so a parked turn never reads it. While an accepted steer that asked to clear
332
+ * permissions is unread, decline each pending interaction, including requests
333
+ * opened after an earlier decline.
334
+ */
335
+ private releaseInteractions(): void {
336
+ const run = this.active;
337
+ if (
338
+ !run ||
339
+ run.cancelled ||
340
+ ![...run.inputs.values()].some(
341
+ (i) => i.releasesInteractions && i.accepted && !i.consumed,
342
+ )
343
+ )
344
+ return;
345
+ for (const [id, interaction] of this.pending) {
346
+ if (this.answering.has(id)) continue;
347
+ this.resolveInteraction(id, interaction.decline).catch((error) =>
348
+ this.runtimeFailed(diagnosticError(error, this.bridge.diagnostic)),
349
+ );
350
+ }
351
+ }
326
352
  private maybeComplete(): void {
327
353
  const run = this.active;
328
354
  if (
@@ -537,6 +563,7 @@ export class ZCodeSession {
537
563
  options?: {
538
564
  clientMessageId: string;
539
565
  delivery?: "auto" | "steer";
566
+ clearPendingPermissions?: boolean;
540
567
  beforeSend?: () => Promise<void>;
541
568
  },
542
569
  ): Promise<{ turnId: string }> {
@@ -634,6 +661,7 @@ export class ZCodeSession {
634
661
  );
635
662
  this.assertOpen();
636
663
  input.accepted = true;
664
+ input.releasesInteractions = options?.clearPendingPermissions === true;
637
665
  if (options?.clientMessageId)
638
666
  this.emit({
639
667
  type: "prompt_accepted",
@@ -701,7 +729,12 @@ export class ZCodeSession {
701
729
  "SESSION_NOT_FOUND",
702
730
  "ZCode interaction is no longer pending",
703
731
  );
704
- const answer = pending.answer(response);
732
+ await this.resolveInteraction(id, pending.answer(response));
733
+ }
734
+ private async resolveInteraction(
735
+ id: string,
736
+ answer: Record<string, unknown>,
737
+ ): Promise<void> {
705
738
  this.answering.add(id);
706
739
  try {
707
740
  await this.control("resolveInteraction", { interactionId: id, answer });