zidane 5.4.2 → 5.5.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 (86) hide show
  1. package/README.md +45 -1
  2. package/dist/{agent-DxBoKDba.d.ts → agent-CvImMxMQ.d.ts} +256 -5
  3. package/dist/agent-CvImMxMQ.d.ts.map +1 -0
  4. package/dist/chat.d.ts +137 -16
  5. package/dist/chat.d.ts.map +1 -1
  6. package/dist/chat.js +3 -2
  7. package/dist/contexts/docker.d.ts +1 -1
  8. package/dist/contexts-DhmMlT2W.js +472 -0
  9. package/dist/contexts-DhmMlT2W.js.map +1 -0
  10. package/dist/contexts.d.ts +3 -3
  11. package/dist/contexts.js +1 -1
  12. package/dist/{errors-Byb0F8B9.js → errors-CDwtPIMX.js} +4 -2
  13. package/dist/{errors-Byb0F8B9.js.map → errors-CDwtPIMX.js.map} +1 -1
  14. package/dist/{index-BOtXdQkW.d.ts → index-B0uc2C5x.d.ts} +9 -3
  15. package/dist/index-B0uc2C5x.d.ts.map +1 -0
  16. package/dist/{index-BiO_5Hm4.d.ts → index-CbS75MD3.d.ts} +2 -2
  17. package/dist/index-CbS75MD3.d.ts.map +1 -0
  18. package/dist/{index-B2VOOijU.d.ts → index-CtXksgqb.d.ts} +73 -4
  19. package/dist/index-CtXksgqb.d.ts.map +1 -0
  20. package/dist/index.d.ts +6 -6
  21. package/dist/index.js +11 -11
  22. package/dist/{interpolate-ERgZUxgg.js → interpolate-BaaKaKzN.js} +156 -19
  23. package/dist/interpolate-BaaKaKzN.js.map +1 -0
  24. package/dist/{login-CJbeAadS.js → login-iTy-0wYz.js} +3 -3
  25. package/dist/{login-CJbeAadS.js.map → login-iTy-0wYz.js.map} +1 -1
  26. package/dist/{mcp-DhmmJfxK.js → mcp-CNUbvbsy.js} +2 -2
  27. package/dist/{mcp-DhmmJfxK.js.map → mcp-CNUbvbsy.js.map} +1 -1
  28. package/dist/mcp.d.ts +1 -1
  29. package/dist/mcp.js +1 -1
  30. package/dist/{messages-D0xT979U.js → messages-fTR19Ga6.js} +2 -2
  31. package/dist/{messages-D0xT979U.js.map → messages-fTR19Ga6.js.map} +1 -1
  32. package/dist/{presets-MCcvxiNT.js → presets-h6UWhghO.js} +3 -2
  33. package/dist/presets-h6UWhghO.js.map +1 -0
  34. package/dist/presets.d.ts +2 -2
  35. package/dist/presets.js +1 -1
  36. package/dist/{providers-x3LZByR5.js → providers-G0VBZK9j.js} +4 -4
  37. package/dist/{providers-x3LZByR5.js.map → providers-G0VBZK9j.js.map} +1 -1
  38. package/dist/providers.d.ts +1 -1
  39. package/dist/providers.js +2 -2
  40. package/dist/session/sqlite.d.ts +1 -1
  41. package/dist/session/sqlite.d.ts.map +1 -1
  42. package/dist/session/sqlite.js +2 -1
  43. package/dist/session/sqlite.js.map +1 -1
  44. package/dist/{session-BHZwxmfr.js → session-CbkiJDlH.js} +3 -2
  45. package/dist/session-CbkiJDlH.js.map +1 -0
  46. package/dist/session.d.ts +1 -1
  47. package/dist/session.js +2 -2
  48. package/dist/skills.d.ts +2 -2
  49. package/dist/skills.js +1 -1
  50. package/dist/{tools-BNfyY14s.js → tools-D_icxa-V.js} +813 -284
  51. package/dist/tools-D_icxa-V.js.map +1 -0
  52. package/dist/tools.d.ts +3 -3
  53. package/dist/tools.js +2 -2
  54. package/dist/{transcript-anchors-DonKvoh4.d.ts → transcript-anchors-3FFw2xuk.d.ts} +98 -15
  55. package/dist/transcript-anchors-3FFw2xuk.d.ts.map +1 -0
  56. package/dist/tui.d.ts +29 -5
  57. package/dist/tui.d.ts.map +1 -1
  58. package/dist/tui.js +879 -70
  59. package/dist/tui.js.map +1 -1
  60. package/dist/{turn-operations-TKvy0q29.js → turn-operations-CtgBlBHn.js} +412 -125
  61. package/dist/turn-operations-CtgBlBHn.js.map +1 -0
  62. package/dist/types-IcokUOyC.js.map +1 -1
  63. package/dist/types-KukEp-mi.d.ts +253 -0
  64. package/dist/types-KukEp-mi.d.ts.map +1 -0
  65. package/dist/types.d.ts +4 -4
  66. package/dist/types.js +1 -1
  67. package/docs/ARCHITECTURE.md +37 -3
  68. package/docs/CHAT.md +4 -2
  69. package/docs/RUN_IN_BACKGROUND.md +612 -0
  70. package/docs/SKILL.md +83 -14
  71. package/docs/TUI.md +40 -2
  72. package/package.json +4 -4
  73. package/dist/agent-DxBoKDba.d.ts.map +0 -1
  74. package/dist/contexts-BwiHIr2w.js +0 -129
  75. package/dist/contexts-BwiHIr2w.js.map +0 -1
  76. package/dist/index-B2VOOijU.d.ts.map +0 -1
  77. package/dist/index-BOtXdQkW.d.ts.map +0 -1
  78. package/dist/index-BiO_5Hm4.d.ts.map +0 -1
  79. package/dist/interpolate-ERgZUxgg.js.map +0 -1
  80. package/dist/presets-MCcvxiNT.js.map +0 -1
  81. package/dist/session-BHZwxmfr.js.map +0 -1
  82. package/dist/tools-BNfyY14s.js.map +0 -1
  83. package/dist/transcript-anchors-DonKvoh4.d.ts.map +0 -1
  84. package/dist/turn-operations-TKvy0q29.js.map +0 -1
  85. package/dist/types-Ce78ds4h.d.ts +0 -88
  86. package/dist/types-Ce78ds4h.d.ts.map +0 -1
@@ -142,7 +142,9 @@ flowchart TB
142
142
  VC -->|yes| VCH["validation:coerce hook\n(observational)"]
143
143
  VC -->|no| T7
144
144
  VCH --> T7["tool:before hook\n(ctx.coercions when present, runToolCounts)"]
145
- T7 --> T8["toolDef.execute(coercedInput, ctx)"]
145
+ T7 --> T8["toolDef.execute(coercedInput, ctx)\nracing against perCallAbort.signal"]
146
+ T8 -->|cancelled by\nagent.cancelTool| TC["tool:cancelled hook\n(reason, runToolCounts)"]
147
+ TC --> TCR["Return TOOL_USE_CANCELLED_MESSAGE\n(isError: true; skips tool:transform/after)"]
146
148
  T8 -->|error| T9["tool:error hook\n(can substitute result)"]
147
149
  T8 -->|ok| T10["tool:transform hook\n(can mutate result;\nctx.outputBytes pre-mutation)"]
148
150
  T9 --> T10
@@ -158,6 +160,8 @@ flowchart TB
158
160
 
159
161
  **`tool:gate` mutations.** Set `block` to refuse (`Blocked: reason`), `result` to substitute and skip execute, or neither to run normally. `block` wins over `result` so a policy gate always beats a consumer cache. The substitute path skips `tool:before` + validate + execute but fires `tool:transform` + `tool:after` — budgets and telemetry stay consistent with executed calls.
160
162
 
163
+ **Per-call cancellation.** Each dispatch registers an `AbortController` in `LoopContext.pendingToolCancels` keyed by `callId`; the tool body's `ctx.signal` is `AbortSignal.any([ctx.signal, perCallAbort.signal])`. `agent.cancelTool(callId, reason?)` flips the per-call signal, the body promise races against the cancellation, the loop emits `tool:cancelled`, and the wire result becomes `TOOL_USE_CANCELLED_MESSAGE` with `isError: true`. Other in-flight tools in the same batch keep running — distinct from `agent.abort()` (whole-run abort → `INTERRUPT_MESSAGE_FOR_TOOL_USE` at the batch layer) and `TOOL_USE_SKIPPED_MESSAGE` (steered out before dispatch). The map entry is removed in a `finally` so it only ever holds currently-dispatching calls; the body promise gets a no-op `.catch` so misbehaving tools that ignore the signal can't surface as unhandled rejections after the loop has moved on.
164
+
161
165
  **`runToolCounts`** is a frozen pre-call snapshot, scoped to `runId`. Includes dedup + gate-`result` substitutes; excludes blocked calls. Resumed sessions reset the counter. Concurrent fleets see the same pre-batch snapshot — built-in budget/dedup middleware uses its own gate-time reservation counter so `behavior.toolBudgets` stays atomic across the fleet.
162
166
 
163
167
  **Output shape.** `string | ToolResultContent[]`. Text tools return strings; multimodal tools (MCP browsers, screenshots) return `[{ type: 'text' }, { type: 'image', mediaType, data }]`. Providers with `capabilities.imageInToolResult: true` (Anthropic, OpenAI Codex) route arrays natively; OpenAI-compat emits a companion `user` message with `image_url` parts. Non-vision providers swap image blocks for a text marker before the hook fires.
@@ -359,15 +363,22 @@ per run():
359
363
  skills_use({ name }) → state.activate(skill, 'model')
360
364
  → skills:activate (via: 'model')
361
365
  → <skill_content> wrapper
366
+ skills_use({ name, mode: 'deactivate' }) → state.deactivate(skill)
367
+ → skills:deactivate (reason: 'model')
368
+ → "Skill X deactivated" confirmation
362
369
  skills_read / skills_run_script → gated on state.isActive + path sandbox
363
370
  run end: deactivateAllSkills() → skills:deactivate (reason: 'run-end')
364
371
 
365
372
  agent.activateSkill(name) → skills:activate (via: 'explicit')
366
373
  agent.deactivateSkill(name) → skills:deactivate (reason: 'explicit')
367
374
  agent.reset() → skills:deactivate (reason: 'reset')
375
+
376
+ session-resume rehydration:
377
+ last-mode-wins per skill across history's skills_use blocks
378
+ → trailing mode:'deactivate' STAYS deactivated; activate/deactivate/activate ends active
368
379
  ```
369
380
 
370
- **allowed-tools.** When any active skill declares `allowed-tools`, a `tool:gate` handler blocks calls outside the union (the three skills tools are always implicitly allowed).
381
+ **allowed-tools.** When any active skill declares `allowed-tools`, a `tool:gate` handler blocks calls outside the union (the three skills tools are always implicitly allowed). Block-list (`- entry`) and flow-list (`[a, b]`) authoring shapes both normalize to `string[]` via the frontmatter parser's array-aware path; `AgentToolNotAllowedError.message` carries a recovery hint pointing the model at `skills_use({ mode: 'deactivate', name })` to release the offending skill without waiting for a run boundary.
371
382
 
372
383
  **Body-only delivery.** `skills_use` returns the body (frontmatter stripped) wrapped in `<skill_content>` for host-SDK context protection. Body includes shell-interpolated instructions (`` !`cmd` ``, fresh per activation), the skill directory, the resource listing (not eagerly loaded), and compatibility + allowed-tools when present.
373
384
 
@@ -420,7 +431,7 @@ run end: uninstallLazyDisclosureGate; unlocked GC-eligible on next run()
420
431
  skills:resolve ← once per agent, after discovery (warmup / run / activateSkill — first wins)
421
432
  skills:catalog [mutable: catalog] ← once per agent, after system-prompt catalog built (same trigger)
422
433
  skills:activate ← per activation; { skill, via: 'model' | 'explicit' | 'resume' }
423
- skills:deactivate ← per deactivation; { skill, reason: 'run-end' | 'explicit' | 'reset' }
434
+ skills:deactivate ← per deactivation; { skill, reason: 'run-end' | 'explicit' | 'reset' | 'model' }
424
435
  ```
425
436
 
426
437
  Every run ends with an implicit deactivate-all pass (`reason: 'run-end'`); activation state never leaks across runs unless re-asserted via `agent.activateSkill()`.
@@ -453,6 +464,7 @@ turn:after ← always fires (incl. errors)
453
464
  tool:transform [mutable: result, isError] ← + outputBytes pre-mutation
454
465
  tool:after ← + outputBytes post-mutation
455
466
  tool:error [mutable: result?] ← on execute throw; ctx.result substitutes
467
+ tool:cancelled ← agent.cancelTool(callId); skips transform/after; wire = TOOL_USE_CANCELLED_MESSAGE
456
468
  tool-results:after ← after the tool-results user turn is pushed (persistence seam)
457
469
  usage ← running totals
458
470
  output ← when behavior.schema set
@@ -481,6 +493,27 @@ session:end ← run finished (completed | aborted | err
481
493
  includes turnRange [start, end] for cleanup
482
494
  ```
483
495
 
496
+ ### Background task lifecycle
497
+
498
+ Fires when the model invokes `shell({ run_in_background: true })`. The shell tool dispatches via `ExecutionContext.execBackground`, which spawns a process group, opens a `WriteStream` to `<userDir>/<sessionId>/tasks/<task-id>.<context-timestamp>.log` (the timestamp segment is `YYYYMMDD-HHMMSS-mmm` UTC, shared by every task in the same `ExecutionContext` so two contexts on the same session never collide), and returns immediately with a `TaskHandle`. The agent listens to `background:exit` and queues a `<task-notification>` block for injection into the next user turn (see "Notification queue" below).
499
+
500
+ **Host opt-in / opt-out.** Background mode is auto-disabled at the schema level (the `run_in_background` field is dropped from the `shell` tool's input schema and the related description paragraphs are stripped) when either `behavior.tasksDir` is unset or `behavior.disableBackgroundTasks: true` is set. `createAgent` performs the rewrite once per run as it indexes tools by spec name — only for the identity-equal framework `shell` constant, so host-customized shell-named tools are left untouched. The runtime check in `runBackground` stays as defense-in-depth (forged inputs that smuggle the flag in past the schema fall through to a clean error, not a silent fallthrough to foreground). Hosts that want explicit control should call `createShellTool({ allowBackground })` and register the tailored variant directly.
501
+
502
+ ```
503
+ background:start ← spawn succeeded; payload carries taskId + pid + outputPath
504
+ (observational; tools don't wait on listeners)
505
+ background:exit ← at-most-once per task; the context's settle latch
506
+ guarantees no duplicate fires (close-or-error race-safe)
507
+ ```
508
+
509
+ **Notification queue.** The agent owns a `Map<taskId, TaskExitInfo>` populated by the `background:exit` listener. At the start of every `agent.run()`, the queue drains and each entry becomes a leading `text` block on the seeded user turn (`<task-notification>…</task-notification>` XML — see `renderTaskNotificationXml` in `src/agent.ts`). The same agent listens to `tool:after` for `shell_kill` (deletes by `task_id` from input) and `read_file` / `read` (deletes by matching the `path` argument against any pending entry's `outputPath`) so the model never receives a redundant signal when it already learned about the exit through other means. The drain happens once per entry — `Map.set` overwrites by id, so a defensive double-enqueue can't multiply notifications.
510
+
511
+ **Replay synthesis.** `eventsFromTurns` detects the `<task-notification>` XML in persisted user-turn text blocks (anchored regex — no false positives mid-prompt) and emits a structured `'task-notification'` `StreamEvent` instead of the generic `user-prompt` event. The XML text block is dropped from the rendered stream so the banner doesn't double-paint alongside the raw XML. Field decoding is tolerant — every tag falls back to a safe default if the persisted block predates a later schema addition.
512
+
513
+ **Destroy ordering.** `agent.destroy()` runs cleanup INSIDE-the-session first (notification queue, per-call cancels) then NEEDED-by-the-session (MCP connection, execution handle, skills cache). The execution context's own `destroy(handle)` walks its background-task registry, SIGTERMs each survivor's process group, and only resolves once every output stream has flushed + closed. The reverse order would tear down the handle underneath still-flushing tasks and corrupt log files. The queue is re-cleared AFTER `executionContext.destroy()` because the `background:exit` hook listener stays bound for the agent's lifetime and may re-populate the map mid-teardown.
514
+
515
+ **Orphan reaper.** `ProcessContext` lazy-registers a `process.on('exit')` handler on the first `execBackground` call that synchronously SIGTERMs every still-running task's process group. Without it, the `detached: true` spawn flag (required for the kill-tree semantics) would leak children when the host process exits without calling `destroy()` (Ctrl+C, uncaught exception, OOM kill). The handler deregisters in `destroy()` so reconstructed contexts don't accumulate listeners.
516
+
484
517
  ### Once (lazy on first `warmup()` / `run()` / `activateSkill()` — or eager when `eager: true`)
485
518
 
486
519
  ```
@@ -509,6 +542,7 @@ The child's lifecycle also bubbles to the parent hook surface with `childId` + `
509
542
  ```
510
543
  child:stream:text / child:stream:thinking / child:stream:end / child:stream:error
511
544
  child:tool:gate / child:mcp:tool:gate ← share the child's ctx — parent mutations propagate
545
+ child:tool:cancelled ← bubbled per-call cancel from a subagent's tool
512
546
  child:tool:transform ← share the child's ctx — parent mutations propagate
513
547
  child:tool:before / child:tool:after / child:tool:error
514
548
  child:turn:after
package/docs/CHAT.md CHANGED
@@ -419,7 +419,7 @@ type ChipColorMap = { default: ChipColor } & Partial<Record<string, ChipColor>>
419
419
 
420
420
  Per-tool approval gate with a per-project safelist. The agent emits a `tool:gate` hook; the chat layer's wired handler:
421
421
 
422
- 1. If `IMPLICITLY_SAFE_TOOLS` covers the call → allow.
422
+ 1. If `IMPLICITLY_SAFE_TOOLS` covers the call → allow. The list is `['read_file', 'list_files', 'glob', 'grep', 'ask_user', 'present_plan', 'todowrite', 'todoread', 'skills_use']` — pure reads, interaction prompts (the picker IS the gate), todo metadata, and skill activation (mutates a per-agent Map; no shell/disk/network — gating it would also break the model's recovery path when it deactivates a skill after an `AgentToolNotAllowedError`). `skills_read` (touches disk) and `skills_run_script` (executes scripts) are intentionally NOT on the list.
423
423
  2. If the project safelist (`~/.zidane/projects.json`) matches → allow.
424
424
  3. Else push an `ApprovalRequest` onto `SafeModeProvider`'s queue and await.
425
425
 
@@ -958,6 +958,7 @@ Render path expectations on the `'tool'` `StreamEvent`:
958
958
  - Tags every child-run event with `{ childId: 'child-N', depth }` where `child-N` is chronologically assigned by `startedAt`. Same labels the live spawn tool emits.
959
959
  - Strips the redundant `Tokens: …` line from spawn tool-result bodies — only when guarded by the `[sub-agent <id>] …` header, so a body line that legitimately starts with `Tokens:` is preserved.
960
960
  - Emits a `'compact-summary'` event for every `compact-summary` content block, with `compact` metadata (`replacedCount`, `model`, `compactedAt`, `inputTokens`, `outputTokens`, `cacheReadTokens`, `cacheCreationTokens`).
961
+ - Detects leading `<task-notification>` text blocks (the wire format `renderTaskNotificationXml` emits) on user turns and synthesizes a `'task-notification'` event with structured `task` metadata (`taskId`, `status`, `exitCode`, `outputPath`, `command`, `durationMs`). The raw text block is suppressed from the `'user-prompt'` stream so the banner doesn't double-paint. Detection is anchored — a stray notification-shaped phrase mid-prompt won't trigger.
961
962
 
962
963
  `lastContextSizeFromTurns(turns, runs)` returns the most recent **depth-0** assistant turn's input token total (cache-aware: `input + cacheRead + cacheCreation`) — the next prompt feeds the parent, so a session that ended mid-spawn doesn't surface the child's window in the footer.
963
964
 
@@ -1016,7 +1017,7 @@ The discriminated union the renderer consumes. `kind` values:
1016
1017
  'thinking' | 'tool' | 'tool-result' | 'error'
1017
1018
  | 'user-prompt' | 'info' | 'separator'
1018
1019
  | 'markdown' | 'spawn-start' | 'spawn-end'
1019
- | 'compact-summary'
1020
+ | 'compact-summary' | 'task-notification'
1020
1021
  ```
1021
1022
 
1022
1023
  Notable fields:
@@ -1030,6 +1031,7 @@ Notable fields:
1030
1031
  - `refs?: { start, end, providerId }[]` — chip-highlight spans on `'user-prompt'` events.
1031
1032
  - `turnId?: string` — the `SessionTurn.id` the event was emitted by; drives select-turn highlighting.
1032
1033
  - `compact?: { replacedCount, model, compactedAt, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens }` — metadata for `'compact-summary'` boundary cards.
1034
+ - `task?: { taskId, status: 'exited' | 'killed', exitCode, outputPath, command, durationMs }` — metadata for `'task-notification'` banners. Synthesized in `eventsFromTurns` from persisted `<task-notification>` XML in user-turn text blocks; the raw XML block is dropped from the user-prompt stream so the banner doesn't double-paint alongside it.
1033
1035
 
1034
1036
  `'user-prompt'` carries the **raw** user prompt without a `❯` prefix; renderers add their own chevron. `refs` offsets are aligned to the raw text.
1035
1037