@gajae-code/coding-agent 0.16.4 → 0.16.6

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 (198) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/types/capability/types.d.ts +4 -0
  3. package/dist/types/cli/customize-doctor.d.ts +22 -0
  4. package/dist/types/config/file-lock.d.ts +31 -3
  5. package/dist/types/coordinator-mcp/question-state.d.ts +7 -0
  6. package/dist/types/coordinator-mcp/server.d.ts +7 -0
  7. package/dist/types/discovery/helpers.d.ts +33 -0
  8. package/dist/types/exec/bash-executor.d.ts +17 -1
  9. package/dist/types/exec/bash-shell-guardian.d.ts +15 -0
  10. package/dist/types/exec/bash-shell-runtime-entry.d.ts +2 -0
  11. package/dist/types/exec/bash-shell-signals.d.ts +2 -0
  12. package/dist/types/exec/bash-shell-supervisor-entry.d.ts +1 -0
  13. package/dist/types/exec/bash-shell-supervisor.d.ts +10 -0
  14. package/dist/types/exec/bash-shell-worker-entry.d.ts +2 -0
  15. package/dist/types/exec/bash-shell-worker-protocol.d.ts +63 -0
  16. package/dist/types/exec/bash-shell-worker.d.ts +1 -0
  17. package/dist/types/exec/isolated-shell.d.ts +19 -0
  18. package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +9 -0
  19. package/dist/types/extensibility/extensions/function-hooks.d.ts +169 -0
  20. package/dist/types/extensibility/extensions/index.d.ts +1 -0
  21. package/dist/types/extensibility/extensions/runner.d.ts +46 -6
  22. package/dist/types/extensibility/extensions/types.d.ts +16 -0
  23. package/dist/types/extensibility/extensions/wrapper.d.ts +3 -1
  24. package/dist/types/extensibility/gjc-plugins/constrained-hooks.d.ts +35 -2
  25. package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
  26. package/dist/types/extensibility/hooks/types.d.ts +4 -0
  27. package/dist/types/extensibility/runtime-skill-discovery.d.ts +4 -1
  28. package/dist/types/extensibility/skill-management.d.ts +9 -4
  29. package/dist/types/extensibility/skills.d.ts +2 -0
  30. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +4 -1
  31. package/dist/types/gjc-runtime/linux-proc.d.ts +2 -0
  32. package/dist/types/gjc-runtime/session-state-lock.d.ts +12 -0
  33. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +41 -3
  34. package/dist/types/gjc-runtime/ultragoal-succession.d.ts +251 -0
  35. package/dist/types/modes/components/bash-execution.d.ts +1 -0
  36. package/dist/types/modes/components/execution-shared.d.ts +1 -0
  37. package/dist/types/modes/interactive-mode.d.ts +2 -0
  38. package/dist/types/modes/types.d.ts +6 -0
  39. package/dist/types/sdk/broker/ensure.d.ts +32 -0
  40. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +0 -1
  41. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  42. package/dist/types/sdk/broker/startup-failure.d.ts +4 -1
  43. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -0
  44. package/dist/types/sdk/bus/chat-effect-journal.d.ts +17 -2
  45. package/dist/types/sdk/bus/conversation-store.d.ts +11 -0
  46. package/dist/types/sdk/bus/lifecycle-commands.d.ts +8 -8
  47. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  48. package/dist/types/sdk/bus/topic-registry.d.ts +8 -0
  49. package/dist/types/sdk/client/client.d.ts +6 -0
  50. package/dist/types/sdk/host/host.d.ts +3 -1
  51. package/dist/types/sdk/host/session-runtime.d.ts +4 -2
  52. package/dist/types/sdk/lifecycle/service.d.ts +1 -0
  53. package/dist/types/sdk/router/session-router.d.ts +10 -2
  54. package/dist/types/sdk/session.d.ts +5 -1
  55. package/dist/types/session/agent-session.d.ts +2 -0
  56. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  57. package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
  58. package/dist/types/session/messages.d.ts +1 -0
  59. package/dist/types/session/session-manager.d.ts +3 -1
  60. package/dist/types/setup/paseo/announce.d.ts +4 -3
  61. package/dist/types/skill-state/canonical-skills.d.ts +4 -0
  62. package/dist/types/system-prompt.d.ts +8 -0
  63. package/dist/types/task/executor.d.ts +3 -0
  64. package/dist/types/tools/bash.d.ts +1 -0
  65. package/dist/types/tools/github-cache.d.ts +2 -1
  66. package/dist/types/tools/index.d.ts +2 -0
  67. package/package.json +9 -7
  68. package/scripts/generate-sdk-operation-inventory.ts +2 -0
  69. package/src/capability/index.ts +14 -2
  70. package/src/capability/types.ts +4 -0
  71. package/src/cli/customize-doctor.ts +155 -12
  72. package/src/cli.ts +22 -0
  73. package/src/commands/sdk.ts +74 -31
  74. package/src/config/file-lock-gc.ts +40 -2
  75. package/src/config/file-lock.ts +198 -14
  76. package/src/config/model-preset-registry.ts +115 -16
  77. package/src/config/model-registry.ts +16 -12
  78. package/src/coordinator-mcp/question-state.ts +33 -2
  79. package/src/coordinator-mcp/server.ts +477 -101
  80. package/src/crash/sanitize.ts +1 -1
  81. package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -0
  82. package/src/discovery/builtin.ts +50 -62
  83. package/src/discovery/helpers.ts +297 -32
  84. package/src/exec/bash-executor.ts +330 -50
  85. package/src/exec/bash-shell-guardian.ts +388 -0
  86. package/src/exec/bash-shell-runtime-entry.ts +5 -0
  87. package/src/exec/bash-shell-signals.ts +29 -0
  88. package/src/exec/bash-shell-supervisor-entry.ts +3 -0
  89. package/src/exec/bash-shell-supervisor.ts +359 -0
  90. package/src/exec/bash-shell-worker-entry.ts +5 -0
  91. package/src/exec/bash-shell-worker-protocol.ts +36 -0
  92. package/src/exec/bash-shell-worker.ts +116 -0
  93. package/src/exec/isolated-shell.ts +393 -0
  94. package/src/extensibility/extensions/function-hooks-internal.ts +73 -0
  95. package/src/extensibility/extensions/function-hooks.ts +959 -0
  96. package/src/extensibility/extensions/index.ts +22 -0
  97. package/src/extensibility/extensions/loader.ts +53 -2
  98. package/src/extensibility/extensions/runner.ts +895 -264
  99. package/src/extensibility/extensions/types.ts +42 -0
  100. package/src/extensibility/extensions/wrapper.ts +56 -15
  101. package/src/extensibility/gjc-plugins/compiler.ts +20 -2
  102. package/src/extensibility/gjc-plugins/constrained-hooks.ts +321 -23
  103. package/src/extensibility/gjc-plugins/registry.ts +113 -1
  104. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +20 -2
  105. package/src/extensibility/gjc-plugins/schema.ts +121 -4
  106. package/src/extensibility/gjc-plugins/types.ts +25 -0
  107. package/src/extensibility/hooks/loader.ts +133 -16
  108. package/src/extensibility/hooks/types.ts +28 -0
  109. package/src/extensibility/runtime-skill-discovery.ts +80 -47
  110. package/src/extensibility/skill-management.ts +200 -52
  111. package/src/extensibility/skills.ts +29 -9
  112. package/src/gjc-runtime/empty-delete-gc.ts +94 -4
  113. package/src/gjc-runtime/gc-runtime.ts +29 -0
  114. package/src/gjc-runtime/linux-proc.ts +14 -0
  115. package/src/gjc-runtime/session-state-lock.ts +51 -8
  116. package/src/gjc-runtime/session-state-sidecar.ts +27 -11
  117. package/src/gjc-runtime/state-writer.ts +30 -29
  118. package/src/gjc-runtime/ultragoal-runtime.ts +189 -47
  119. package/src/gjc-runtime/ultragoal-succession.ts +1667 -0
  120. package/src/gjc-runtime/workflow-manifest.generated.json +52 -1
  121. package/src/gjc-runtime/workflow-manifest.ts +16 -0
  122. package/src/harness-control-plane/session-lease.ts +2 -2
  123. package/src/internal-urls/docs-index.generated.ts +6 -6
  124. package/src/lsp/config.ts +10 -3
  125. package/src/lsp/index.ts +1 -1
  126. package/src/lsp/path-trust.ts +69 -14
  127. package/src/main.ts +6 -1
  128. package/src/modes/acp/acp-agent.ts +16 -3
  129. package/src/modes/components/bash-execution.ts +4 -1
  130. package/src/modes/components/execution-shared.ts +4 -1
  131. package/src/modes/components/session-observer-overlay.ts +1 -1
  132. package/src/modes/components/tool-status-header.ts +3 -1
  133. package/src/modes/controllers/command-controller.ts +1 -0
  134. package/src/modes/controllers/event-controller.ts +3 -2
  135. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  136. package/src/modes/controllers/input-controller.ts +6 -3
  137. package/src/modes/interactive-mode.ts +6 -5
  138. package/src/modes/print-mode.ts +5 -1
  139. package/src/modes/prompt-action-autocomplete.ts +54 -27
  140. package/src/modes/runtime-init.ts +2 -0
  141. package/src/modes/types.ts +16 -3
  142. package/src/modes/utils/ui-helpers.ts +1 -0
  143. package/src/prompts/tools/subagent.md +1 -0
  144. package/src/runtime-mcp/manager.ts +20 -2
  145. package/src/runtime-mcp/pool.test.ts +20 -11
  146. package/src/sdk/broker/broker.ts +119 -28
  147. package/src/sdk/broker/ensure.ts +248 -41
  148. package/src/sdk/broker/lifecycle-ledger.ts +7 -17
  149. package/src/sdk/broker/lifecycle.ts +56 -6
  150. package/src/sdk/broker/startup-failure.ts +15 -6
  151. package/src/sdk/bus/chat-daemon-control.ts +3 -1
  152. package/src/sdk/bus/chat-daemon-runtime.ts +35 -5
  153. package/src/sdk/bus/chat-effect-journal.ts +132 -7
  154. package/src/sdk/bus/conversation-store.ts +29 -0
  155. package/src/sdk/bus/lifecycle-commands.ts +17 -11
  156. package/src/sdk/bus/slack-conversation.ts +1 -0
  157. package/src/sdk/bus/slack-daemon.ts +387 -48
  158. package/src/sdk/bus/telegram-daemon.ts +16 -2
  159. package/src/sdk/bus/topic-registry.ts +19 -0
  160. package/src/sdk/cli/master-cli.ts +1 -1
  161. package/src/sdk/cli/session-cli.ts +2 -2
  162. package/src/sdk/client/client.ts +66 -17
  163. package/src/sdk/host/host.ts +29 -9
  164. package/src/sdk/host/query/revision-store.ts +19 -13
  165. package/src/sdk/host/reverse-leases.ts +7 -4
  166. package/src/sdk/host/session-runtime.test.ts +50 -8
  167. package/src/sdk/host/session-runtime.ts +158 -51
  168. package/src/sdk/lifecycle/service.ts +4 -0
  169. package/src/sdk/protocol/operation-inventory.generated.json +11 -0
  170. package/src/sdk/router/session-router.ts +89 -38
  171. package/src/sdk/session.ts +155 -26
  172. package/src/sdk/transport/relay.ts +35 -13
  173. package/src/session/agent-session.ts +117 -52
  174. package/src/session/fallback-chain-controller.ts +38 -0
  175. package/src/session/internal/managed-session-storage.ts +19 -5
  176. package/src/session/messages.ts +5 -0
  177. package/src/session/session-manager.ts +29 -5
  178. package/src/session-import/redact.ts +5 -1
  179. package/src/setup/paseo/announce.ts +49 -43
  180. package/src/setup/paseo/setup-deps.ts +1 -1
  181. package/src/skill-state/canonical-skills.ts +12 -0
  182. package/src/skills/skills.test.ts +41 -21
  183. package/src/system-prompt.ts +25 -3
  184. package/src/task/executor.ts +5 -0
  185. package/src/task/index.ts +4 -0
  186. package/src/tools/bash.ts +28 -11
  187. package/src/tools/gh.ts +26 -3
  188. package/src/tools/github-cache.ts +68 -16
  189. package/src/tools/index.ts +2 -0
  190. package/src/tools/job.ts +13 -2
  191. package/src/tools/read.ts +14 -2
  192. package/src/tools/render-utils.ts +1 -3
  193. package/src/tools/resource-gc.ts +28 -8
  194. package/src/tools/skill-discovery.ts +5 -0
  195. package/src/tools/skill.ts +15 -0
  196. package/src/tools/subagent.ts +3 -2
  197. package/src/tools/tool-catalog.generated.ts +2 -2
  198. package/src/tools/ultragoal-ask-guard.ts +2 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,60 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.16.6] - 2026-09-07
6
+ ### Fixed
7
+
8
+ - `gjc --smoke-test` no longer fails on a cold or loaded host. The isolated-shell smoke wrote its descendant marker empty while the in-shell wait loop tested for a non-empty file, so the probe command could never reach its parked `sleep` and always burned the full 5s budget, leaving the 5s readiness poll no slack; the 1s descendant-reap window was equally tight. The marker now carries content, readiness and reaping use explicit 30s/15s deadlines, and the probe gets a 60s budget it still never needs. This failed the darwin-x64 release binary check on v0.16.5, which published nothing.
9
+ - SDK lifecycle replay no longer rejects a live endpoint as replaced because of sub-millisecond filesystem metadata drift. The ready authority re-derived the endpoint mtime from a fresh `stat` while every other field came from the indexed record, so the two floats could differ in their last digits and replay hashing reported `endpoint_stale` for the endpoint it had just validated. Replacement fencing is unchanged: the on-disk file must still match the indexed record before that authority exists.
10
+
11
+ ## [0.16.5] - 2026-09-07
12
+ ### Added
13
+
14
+ - `gjc ultragoal succession offer|adopt|status` gives an approved Ultragoal an explicit, audited path from the repository it was planned in to the repository its implementation belongs to (#5353). Previously the repository-binding guard correctly refused to execute the plan from the second repository and the only ways forward — editing `repositoryBinding` or copying the runtime directory — both defeated that boundary and laundered verification provenance. `offer` records the source repository/session, the selected unfinished goal ids, verified source artifact digests, and the complete carryover (the source brief and each objective verbatim, plus the unresolved obligations recorded against those goals), then installs a durable outgoing ownership fence: the source run immediately refuses to schedule or checkpoint the handed-off goals, while goals outside the selection stay schedulable. The source `brief.md`, `goals.json` and `ledger.jsonl` are never written — not even appended to — so their recorded digests stay verifiable, and adoption performs no write to the source repository at all. `adopt` starts a fresh run under the target repository's own binding with new pending goals and no inherited receipts, quality gates, validation batches, steering, owner identity or activated objective; historical statuses, reviews and approvals travel as provenance only. Digests are integrity evidence, never authorization: adoption additionally requires the source fence, a bounded `--authorize`/`--authorized-by` statement, and an explicit goal selection. Because the fence deliberately leaves unselected goals schedulable, admission compares a snapshot of the offered work — the brief plus each selected goal's record and unresolved obligations — rather than whole-file digests, so ordinary source progress cannot permanently strand the handed-off work; whole-file digests remain recorded as provenance and observed drift is written into the adoption record. A selection that takes only part of a validation batch, or splits a review blocker from the goal it still blocks, is rejected rather than silently dropping that obligation, and dependency groups travel as explicit obligations with source-to-target id mapping while the source's batch metadata and receipts stay behind. Terminal-critic blockers are carried as unmet requirements even though the verdict itself is not inherited. Exclusion is a single repository-wide `O_EXCL` claim per operation rather than a directory scan, so two simultaneous sessions cannot both become owners, and adoption is pinned to the exact worktree the offer named because linked worktrees keep separate `.gjc` state. Until publication completes the target refuses to execute — a visible `goals.json` is not admission evidence, the adoption record is. Adoption fails closed on changed offered work, an altered or unfenced offer, an offer path that escapes the source worktree, a target the offer does not name, an occupied target session, and any duplicate or simultaneous adoption in the target repository. A retry reconciles only this operation's own partial bytes: a matching operation id is never authority to overwrite target work it did not write, and a published plan whose files have disappeared is refused rather than reconstructed. There is deliberately no `revoke` verb, because any read-then-delete of the fence races a concurrent adoption and could revive the source while the target is starting.
15
+
16
+ ### Fixed
17
+
18
+ - Concurrent print-mode resumes rejected by the managed transcript append fence now exit with one retry diagnostic instead of crashing during disposal, including failures while persisting startup model overrides before print mode begins. A pre-write identity mismatch disowns the stale transcript without rewriting another process's successor; genuinely uncertain I/O closure still fails closed.
19
+ - Disk artifact GC and `gjc gc --empty-delete-receipts` now reclaim empty artifact `.removing` directories left by interrupted session deletion, including projects with no remaining transcripts. Only the two generated quarantine-name shapes qualify; no-follow directory snapshots prove emptiness, and non-recursive removal retains non-empty, symlinked, or raced entries with explicit reasons.
20
+ - SDK `session.spawn` now publishes the child PID/incarnation-bound lifecycle marker before waiting for host registration, so headless and managed-tmux children can become ready. Incomplete substrate proofs are rejected and released before seed handoff; registration failures include validated startup phase/reason diagnostics without task text.
21
+ - File-lock acquisition and GC now reclaim orphaned `.lock.pending.<pid>.<uuid>` directories after their owner dies, with a bounded acquisition sweep and exact-identity deletion. Live, unknown, in-process, malformed, symlinked, and host-qualified owners remain protected; cleanup failures cannot block acquisition (related #5198).
22
+ - File-lock acquisition timeouts now expose a typed error with the lock path, retry count, and holder description. Lease mutation and SDK shutdown classify contention without depending on operator-facing wording, and unrelated shutdown failures still propagate.
23
+ - Broker `session.list` pagination cursors now evict the oldest entry when the 32-cursor budget is full instead of failing new paginations with `cursor capacity is exhausted` (#5370). Abandoned or partial paginations degrade gracefully (the evicted cursor reports `cursor is expired or invalid` on next use) so unrelated session ops never fail once the index exceeds one page; the obsolete capacity-exhausted path is removed.
24
+ - `session.delete` uncertain-cleanup fences are now identity-scoped: an unbound `terminal_uncertain` ledger entry no longer blanket-fences deletes for every other session id, and a delete refusal records its own target session so the fence it leaves is scoped to that session. Existing poisoned ledgers need no migration — unbound rows are ignored in place on the next read and stop amplifying (#5364).
25
+ - Runtime state persistence now identifies contradictory `ready_for_input`/`live` fields with their lifecycle state and expected value instead of reporting only a generic invalid/unreadable marker error. Invalid markers remain untouched; this does not migrate or repair old session state.
26
+ - LSP auto-discovery now launches trusted executable symlinks through their discovered paths instead of canonicalizing them to their targets, preserving proxy invocation names such as `rust-analyzer` when installed through rustup. The LSP status action now labels these entries as configured rather than active, since a client has not necessarily started (#5354).
27
+ - SDK snapshot pages now read and integrity-check one contiguous span instead of rereading chunks per row. Reverse-provider disconnects discard connection-scoped registration receipts; relay shutdown releases backpressure listeners without destroying caller-owned sinks, and fragmented request frames are assembled once per newline.
28
+
29
+ - The TUI working spinner and tool intent remain visible when a run continues after automatic context compaction, a compaction-hook veto, or a successful managed model retry. Temporary status replacement no longer marks the foreground run as finished; actual completion and cancellation still clear it.
30
+ - Coordinator delegates recover a creation-WAL crash before the initial prompt claim without recreating the session or replaying compacted requests. Recurring pin cleanup bounds each readiness pass to 32 session WALs and 32 pinned receipts, skips live caller-key locks, and revisits the namespace after clean scans so surviving coordinators discover later peer crashes as well as previously skipped pins.
31
+ - Session import no longer stalls on large credential-free content. The import redactor's URL-credential rule accepted an unbounded scheme before the literal `://`, so a long alphabetic run re-tried every prefix and scanning cost grew quadratically with the input (50k/100k/200k characters took 0.7s/2.7s/10.6s on text containing no credential at all). Every imported string crosses this fail-closed redactor, so one large transcript message was enough to stall an import of Claude Code or Codex history. The scheme is now length-bounded, which restores linear scanning while `https://`, `postgres://`, and `git+ssh://` credentials are still redacted.
32
+ - SDK saved-session resume now enforces a supplied transcript `sessionIdentity` for both live reattach and cold launch instead of silently replacing it with the broker's current snapshot. Lifecycle create/resume results also expose the opaque `endpointIncarnation`, and same-key replay refuses an endpoint replacement rather than refreshing success by session ID when a numeric generation is reused.
33
+ - An ACP client that declares no MCP servers no longer loses `session/new`, `session/load`, and `session/fork` to the broker's bare readiness default. Only the MCP branch asked for a readiness budget, so an MCP-less launch kept the 10s default while a host cold start measurably crosses it whenever a second host starts alongside the first: the broker then returned `terminal_uncertain` ("Lifecycle startup could not be proven"), which Paseo recorded as a failed provider snapshot and left the GJC provider reading `error` instead of `available`, so imported terminal sessions attached to nothing and showed no transcript. Every ACP launch now requests the same budget the MCP path already used, independent of whether the client declared servers.
34
+ - `subagent await` now accepts integer heartbeat intervals up to 60,000 ms, including 10,000 ms, instead of rejecting them at an undocumented 5,000 ms schema limit. Zero still disables heartbeat updates and the default cadence is unchanged.
35
+ - Coordinator delegates recover a creation-WAL crash before the initial prompt claim without recreating the session or replaying compacted requests. Startup pin cleanup skips live observations without waiting on their caller-key locks, and revisits skipped pins so a surviving coordinator can clean up after a peer commits its response and crashes.
36
+ - Interrupted standalone-binary installs now reclaim an installer lock only when its recorded process ID is no longer running. Live or malformed locks remain protected and fail closed, so a cancelled `gjc update` no longer permanently blocks the next update while concurrent installers remain serialized.
37
+ - Disconnecting an MCP server during its initial connection now returns after cancelling the acquisition instead of waiting forever when an uncooperative transport ignores abort. The pool still owns and closes any late connection, retains cleanup failures for retry, and releases the manager's pending-cleanup fence after successful settlement (#5329).
38
+ - SDK session routing now uses the Broker's endpoint timestamp comparison, accepting sub-millisecond representation differences while retaining device/inode and exact file-replacement checks. Valid live sessions no longer become unavailable solely because timestamp conversions round differently.
39
+ - Paseo announcement now honors `PASEO_LISTEN` ahead of daemon pid metadata, uses `$PASEO_HOME` consistently in default path construction, and removes redundant target-selection and SDK frame-delivery indirection.
40
+ - When every auto-compaction candidate fails, the reported error now leads with the first candidate's failure and lists each candidate that was tried with its own error. The chain starts at the session model and ends on a same-provider largest-context fallback, so surfacing only the final error named a model the user never chose (e.g. `openai-codex/gpt-5.4` on an `astra` preset session) and hid that their own model had already failed the same way.
41
+ - Registry fingerprints now enforce the existing regular-file and 32 MiB admission limits with bounded reads. Model activation, thinking overlay lookup, transcript navigation, slash autocomplete, and observer digest updates avoid repeated allocations; status lines without a git segment no longer request git status.
42
+ - Telegram `/session_recent` now distinguishes sessions currently connected to the notification daemon from saved history and prints a ready-to-copy resume command. Inside a session's existing topic, `/session_resume` and `/session_close` can omit the session ID, including after that topic became inactive.
43
+ - POSIX bash tool sessions now run the embedded persistent brush shell behind a guardian, supervisor, and runtime boundary. Shell self-signals such as `kill -TRAP $$`, external self-signals, process-group signals, and runtime/supervisor death terminate only the owned boundary, reap ordinary and new-session descendants, authenticate control records, return and persist signal-derived status metadata, retire the affected session, and leave the GJC host available for subsequent commands.
44
+ - Memory-guard samples finishing after disable/re-enable or session re-registration no longer restore stale GC latches or restart-advisory windows that suppress the next lifecycle's pressure response.
45
+ - Added ordered, capability-scoped Function Hooks middleware to the canonical extension event runner, with monotonic grant attenuation, target-aware transforms and denials, bounded timeout/abort behavior, redacted provenance-aware audit evidence, and activation-generation fencing. Plugin-bundle Function Hook declarations are validated and hashed but fail closed before host-realm import until a genuine capability-enforcing isolate is available; legacy constrained plugin hooks remain compatible.
46
+ - Coordinator MCP delegation now releases the session transition lock after durable admission so force/queue prompts can dispatch during bounded completion observation. New and reused delegates persist and replay the complete bounded response, including timeout and waiting snapshots, while receipt-first crash recovery preserves accepted prompt identity and successor terminal fences without redispatch. Pending delegate responses pin their source prompt/turn through age and size compaction until final response commit; resumed keys with missing live authority remain unsealed and fail closed instead of redispatching or sealing a busy error.
47
+ - Concurrent SDK CLI cold starts now single-flight broker spawning through an atomically published, process-incarnation-bound lock. Coordination owners use a machine identity keyed from the shared agent root, so separate config profiles recover one another's dead claims without allowing another machine to reclaim a live generation. Every discovery probe is bounded by the startup deadline, and a dedicated bootstrap watchdog terminates a child that outlives its startup fence. A crashed spawner is reclaimed without letting simultaneous healers delete a successor claim; retained legacy broker-lock quarantines no longer collide with takeover; incomplete owner records fail closed; and PID reuse no longer wedges broker startup. (#5198)
48
+ - Skill discovery now reports project `.claude/skills` / `.codex/skills` import candidates before user-home ones, so a populated operator home (hundreds of convention skills is ordinary) can no longer spend the bounded diagnostic budget before the project's own convention skill is named.
49
+
50
+ ### Fixed
51
+
52
+ - SDK submissions that reject during post-turn cleanup no longer publish a contradictory second terminal after their correlation has already settled.
53
+ - Paseo session imports discard unused CLI stdout instead of leaving an unread pipe.
54
+ - SDK live-attach streams preserve producer order across text deltas, final messages, and tool events even when asynchronous extension handlers are slow, without replaying or broadcasting turn content. Content observed while the correlated `agent_start` is still being durably recorded is held and released after the start frame, so no consumer sees turn content before the turn it belongs to; held content is delivered only to the owners that held the run when it was produced, and the hold is bounded (256 events, oldest dropped with one warning) so a wedged start write can neither invert the start/content boundary nor accumulate a whole response in memory.
55
+ - SDK prompts consumed together after queued steering is interrupted now each receive correlated starts, streamed content, and one terminal, without adopting unrelated pending submissions.
56
+
57
+ - Tool hot paths avoid overlap-table allocations for equal/append-only ACP output, redundant UTF-8 tail previews, repeated diagnostics suffix scans, and duplicate background GitHub-cache refreshes. Job polling now releases watches and timers when a progress callback throws.
58
+
5
59
  ## [0.16.4] - 2026-09-05
6
60
  ### Added
7
61
 
@@ -28,6 +28,8 @@ export interface LoadContext {
28
28
  * produced. This includes MCP registrations and user-installed skills.
29
29
  */
30
30
  userAgentDir?: string;
31
+ /** Resolver-owned classification for the selected user agent directory. */
32
+ profileAuthority?: "default" | "custom";
31
33
  /** Git repository root (directory containing .git), or null if not in a repo */
32
34
  repoRoot: string | null;
33
35
  /** Owning session settings for provider policy decisions. */
@@ -81,6 +83,8 @@ export interface LoadOptions {
81
83
  * process-wide one (`createAgentSession({ agentDir })`).
82
84
  */
83
85
  agentDir?: string;
86
+ /** Resolver-owned classification for `agentDir`; preserve it across HOME/config refreshes. */
87
+ profileAuthority?: "default" | "custom";
84
88
  /** Include items even if they fail validation. Default: false */
85
89
  includeInvalid?: boolean;
86
90
  /** Include items disabled via settings. Default: false */
@@ -89,6 +89,28 @@ export interface CustomizeDoctorItem {
89
89
  mcp?: McpSafeSummary;
90
90
  /** Present only for quarantined plugin-bundle surfaces. */
91
91
  quarantineCode?: string;
92
+ /** Metadata-only Function Hook policy projection; hook code is never executed. */
93
+ functionHooks?: CustomizeFunctionHookSummary[];
94
+ }
95
+ export interface CustomizeFunctionHookSummary {
96
+ extensionId: string;
97
+ order: number;
98
+ event: string;
99
+ target?: string;
100
+ phase?: "before" | "after";
101
+ scope: "user" | "project";
102
+ plugin: string;
103
+ relativePath: string;
104
+ status: "disabled" | "quarantined" | "blocked-isolate" | "legacy-active";
105
+ requestedCapabilities: string[];
106
+ effectiveCapabilities: string[];
107
+ attenuateDownstream: string[];
108
+ networkDestinations: string[];
109
+ filesystemRoots: string[];
110
+ capabilityHash?: string;
111
+ implementationHash?: string;
112
+ quarantineCode?: string;
113
+ remediation: string;
92
114
  }
93
115
  export interface CustomizeDoctorSurface {
94
116
  kind: CustomizeSurfaceKind;
@@ -11,9 +11,17 @@ export interface FileLockOptions {
11
11
  /** Previous local host identities accepted only when deciding stale-owner reclamation. */
12
12
  previousOwnerHostIds?: readonly string[];
13
13
  }
14
- /** Release retries cover transient Windows/Dropbox handle denial without extending the lock indefinitely. */
15
- export declare const FILE_LOCK_RELEASE_RETRY_ATTEMPTS = 5;
16
- export declare const FILE_LOCK_RELEASE_RETRY_DELAY_MS = 10;
14
+ export declare class FileLockAcquireError extends Error {
15
+ readonly filePath: string;
16
+ readonly lockPath: string;
17
+ readonly attempts: number;
18
+ readonly holder: string;
19
+ readonly code = "acquire_timeout";
20
+ constructor(filePath: string, lockPath: string, attempts: number, holder: string);
21
+ }
22
+ /** Release retries cover transient handle denial and a competing exact-removal quarantine cleanup. */
23
+ export declare const FILE_LOCK_RELEASE_RETRY_ATTEMPTS = 20;
24
+ export declare const FILE_LOCK_RELEASE_RETRY_DELAY_MS = 25;
17
25
  export declare const FileLockTestHooks: {
18
26
  afterParentMkdir?: (lockPath: string) => void | Promise<void>;
19
27
  nativePublicationBindings?: () => {
@@ -47,6 +55,8 @@ export declare function readFileLockInfoForGc(lockDir: string): Promise<FileLock
47
55
  /** Owner identity stamped into a `<file>.lock/info` record. */
48
56
  export interface FileLockOwnerToken {
49
57
  pid: number;
58
+ /** Kernel-derived identity for the exact process generation owning `pid`. */
59
+ process_incarnation?: string;
50
60
  start_time?: string;
51
61
  /** Encoding marker for the canonical UTC process-start identity. */
52
62
  start_time_format?: string;
@@ -93,6 +103,7 @@ export type GenericFileLockDirStaleVerdict = {
93
103
  * but the reclaim-after-stale scenario the issue describes is now guarded.
94
104
  */
95
105
  export declare function removeFileLockDirForGc(lockDir: string, expected: FileLockOwnerToken, preVerdictIdentity?: GenericFileLockDirIdentity): Promise<FileLockGcRemoval>;
106
+ type OwnerLiveness = "alive" | "dead" | "unknown";
96
107
  /**
97
108
  * @internal
98
109
  * READ-ONLY verdict on an EXISTING generic `<file>.lock/` directory that another lock
@@ -122,6 +133,7 @@ type NativeFileLockBindings = {
122
133
  snapshotDirectoryTree(lockPath: string): NativeDirectoryTreeResult;
123
134
  exactRemoveDirectoryTree(lockPath: string, snapshot: NonNullable<NativeDirectoryTreeResult["snapshot"]>): NativeExactUnlinkResult;
124
135
  };
136
+ export declare function acquireFileLock(filePath: string, options?: FileLockOptions): Promise<() => Promise<void>>;
125
137
  /**
126
138
  * Serializes all contenders, including callers in the same process. Because this
127
139
  * API exposes no ownership token, recursive acquisition is indistinguishable
@@ -129,4 +141,20 @@ type NativeFileLockBindings = {
129
141
  * that fact through its own `lockHeld` path instead of acquiring it again.
130
142
  */
131
143
  export declare function withFileLock<T>(filePath: string, fn: () => Promise<T>, options?: FileLockOptions): Promise<T>;
144
+ /** Strictly recognize the staging names emitted by tryAcquireLock. */
145
+ export declare function fileLockStagingOwnerPid(name: string): number | null;
146
+ export interface FileLockStagingResult {
147
+ path: string;
148
+ pid?: number;
149
+ status: OwnerLiveness;
150
+ removed: boolean;
151
+ reason: string;
152
+ }
153
+ /** Observe identity before probing; neither a replacement nor a symlink inherits the verdict. */
154
+ export declare function inspectFileLockStagingDir(stagingPath: string, probe?: (pid: number) => OwnerLiveness, remove?: boolean): Promise<FileLockStagingResult>;
155
+ /** Bounded opportunistic cleanup; the acquisition caller treats failures as diagnostic only. */
156
+ export declare function reapOrphanedLockStagingDirs(lockPath: string): Promise<{
157
+ removed: string[];
158
+ retained: FileLockStagingResult[];
159
+ }>;
132
160
  export {};
@@ -187,6 +187,8 @@ export interface PromptRequestV1 {
187
187
  turn_id: string;
188
188
  };
189
189
  coordinator_turn_id?: string;
190
+ /** Retain prompt and source turn until the delegate's outer response is durable. */
191
+ delegate_response_pending?: boolean;
190
192
  safe_response?: Record<string, unknown>;
191
193
  error_code?: PublicReason | "idempotency_conflict";
192
194
  created_at: string;
@@ -275,6 +277,11 @@ export interface CoordinatorSessionTransactionV1 {
275
277
  recovery: {
276
278
  prompt_watermark_at: string | null;
277
279
  last_repaired_at: string | null;
280
+ /** One-shot authority for the initial delegate prompt, consumed atomically with its claim. */
281
+ initial_delegate_request?: {
282
+ key_digest: string;
283
+ request_digest: string;
284
+ };
278
285
  };
279
286
  }
280
287
  export type CanonicalCreateIntentV1 = {
@@ -72,6 +72,13 @@ interface CoordinatorServices {
72
72
  afterCanonicalReportCommit?: (sessionId: string) => void | Promise<void>;
73
73
  /** Test barrier invoked after a canonical report safe response is durable and before outer idempotency completion. */
74
74
  afterCanonicalReportSafeResponse?: (sessionId: string, response: Record<string, unknown>) => void | Promise<void>;
75
+ /** Test barriers around delegate admission and outer response durability. */
76
+ afterDelegateAdmission?: (sessionId: string) => void | Promise<void>;
77
+ afterDelegateCreationWalCommitted?: (sessionId: string) => void | Promise<void>;
78
+ afterDelegateCreationCompleted?: () => void | Promise<void>;
79
+ beforeDelegateResponseCommit?: () => void | Promise<void>;
80
+ afterDelegateResponseCommit?: () => void | Promise<void>;
81
+ readCoordinatorSessionEntries?: (directory: string) => Promise<string[]>;
75
82
  }
76
83
  type CoordinatorModelResolution = {
77
84
  ok: true;
@@ -59,11 +59,34 @@ export declare const SOURCE_PATHS: {
59
59
  readonly projectDir: ".vscode";
60
60
  };
61
61
  };
62
+ /** Ancestors from `cwd` through `stopAt`, excluding one normalized authority boundary. */
63
+ export declare function getAncestorDirs(cwd: string, stopAt?: string | null, excludeDir?: string): Array<{
64
+ dir: string;
65
+ depth: number;
66
+ }>;
62
67
  export type SourceId = keyof typeof SOURCE_PATHS;
68
+ export type ProfileAuthority = "default" | "custom";
63
69
  /**
64
70
  * Get user-level path for a source.
65
71
  */
66
72
  export declare function getUserPath(ctx: LoadContext, source: SourceId, subpath: string): string | null;
73
+ /**
74
+ * Deterministic user-scope skill scan directories, highest precedence first.
75
+ *
76
+ * The canonical root is the agent directory (`getAgentDir()`, moved by
77
+ * `--agent-dir` / `GJC_CODING_AGENT_DIR` / `setAgentDir()`) — the target of
78
+ * every user-scope skill writer (`gjc migrate`, `gjc skill`). An agent-directory
79
+ * profile is a *separate* user scope, the same contract as MCP user config
80
+ * (#4768): its legacy home-relative roots are not scanned, so a profile cannot
81
+ * pick up the default profile's skills (and vice versa). The resolver-owned
82
+ * `profileAuthority` classification is authoritative when supplied, so a
83
+ * custom profile remains isolated even if a later HOME/config-root refresh
84
+ * makes its path look like the current default. In the default profile the
85
+ * agent directory is `<home>/<configDir>/agent` and the configured legacy roots
86
+ * below it are still honored, exactly as before.
87
+ */
88
+ export declare function resolveUserAgentDir(home: string, userAgentDir?: string): string;
89
+ export declare function getUserSkillScanDirs(home: string, userAgentDir?: string, profileAuthority?: ProfileAuthority): string[];
67
90
  /**
68
91
  * Get project-level path for a source (cwd only).
69
92
  */
@@ -115,6 +138,8 @@ export interface ParsedAgentFields {
115
138
  export declare function parseAgentFields(frontmatter: Record<string, unknown>): ParsedAgentFields | null;
116
139
  export interface ScanSkillsFromDirOptions {
117
140
  dir: string;
141
+ /** Selected authority root whose own identity must not be a symlink. */
142
+ authorityRoot?: string;
118
143
  providerId: string;
119
144
  level: "user" | "project";
120
145
  requireDescription?: boolean;
@@ -124,7 +149,15 @@ export declare function compareSkillOrder(aName: string, aPath: string, bName: s
124
149
  export declare const SKILL_FRONTMATTER_SCAN_BYTES: number;
125
150
  /** Maximum total bytes read while seeking the frontmatter closing delimiter. */
126
151
  export declare const SKILL_FRONTMATTER_SCAN_TOTAL_BYTES: number;
152
+ export declare const SkillDiscoveryTestHooks: {
153
+ afterCandidateValidated?: (candidatePath: string) => void | Promise<void>;
154
+ afterAuthorityRootValidated?: (root: string) => void | Promise<void>;
155
+ afterAuthorityRootMissing?: (root: string) => void | Promise<void>;
156
+ afterContainedRootValidated?: (root: string) => void | Promise<void>;
157
+ };
127
158
  export declare function scanSkillsFromDir(_ctx: LoadContext, options: ScanSkillsFromDirOptions): Promise<LoadResult<Skill>>;
159
+ /** Read a regular single-link file that remains contained by its configured root. */
160
+ export declare function readContainedFile(root: string, filePath: string): Promise<string | null>;
128
161
  /**
129
162
  * Recursively expand environment variables in an object.
130
163
  */
@@ -1,6 +1,19 @@
1
- import type { MinimizerOptions } from "@gajae-code/natives";
1
+ import type { MinimizerOptions, ShellOptions, ShellRunOptions, ShellRunResult } from "@gajae-code/natives";
2
2
  import { Settings, type ShellMinimizerSettings } from "../config/settings";
3
3
  import { type TerminalArtifactPublisher } from "../session/streaming-output";
4
+ type BashShellRunResult = ShellRunResult & {
5
+ signal?: string;
6
+ };
7
+ type Shell = {
8
+ run(options: ShellRunOptions, onChunk?: (error: Error | null, chunk: string) => void): Promise<BashShellRunResult>;
9
+ abort(): Promise<void>;
10
+ close(): Promise<void>;
11
+ isTerminal?(): boolean;
12
+ ready?(): Promise<void>;
13
+ isRunSignalActive?(signal: AbortSignal): boolean;
14
+ };
15
+ type ShellFactory = (options?: ShellOptions) => Shell | Promise<Shell>;
16
+ export declare function setShellFactoryForTests(factory: ShellFactory | undefined): void;
4
17
  export interface BashArtifactSaveSummary {
5
18
  artifactId: string;
6
19
  complete: boolean;
@@ -74,6 +87,8 @@ export interface BashExecutorOptions {
74
87
  export interface BashResult {
75
88
  output: string;
76
89
  exitCode: number | undefined;
90
+ /** POSIX signal that terminated the isolated shell execution boundary. */
91
+ signal?: string;
77
92
  cancelled: boolean;
78
93
  truncated: boolean;
79
94
  totalLines: number;
@@ -97,3 +112,4 @@ export declare function disposeAllShellSessions(): Promise<void>;
97
112
  /** Translate `ShellMinimizerSettings` into native `MinimizerOptions`, or `undefined` when disabled. */
98
113
  export declare function buildMinimizerOptions(group: ShellMinimizerSettings): MinimizerOptions | undefined;
99
114
  export declare function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult>;
115
+ export {};
@@ -0,0 +1,15 @@
1
+ import type { Process as NativeProcess } from "@gajae-code/natives";
2
+ type OwnershipRecord = {
3
+ pid: number;
4
+ incarnation: string;
5
+ darwinUniqueId?: string | null;
6
+ signature: string;
7
+ };
8
+ export declare function parseOwnershipRecord(line: string): OwnershipRecord | undefined;
9
+ export declare function retainOwnedProcess<T extends Pick<NativeProcess, "pid" | "incarnation">>(owned: Map<string, T>, processRef: T, guardianPid?: number): boolean;
10
+ export declare function extendOwnedDarwinAncestry<T extends {
11
+ uniqueId: bigint;
12
+ parentUniqueId: bigint;
13
+ }>(knownUniqueIds: Set<bigint>, candidates: ReadonlyMap<number, T>): number[];
14
+ export declare function runBashShellGuardian(): Promise<void>;
15
+ export {};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare function fatalCatchableSignals(): NodeJS.Signals[];
2
+ export declare function signalExitCode(signal: NodeJS.Signals): number | undefined;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { type BashShellWorkerResponse } from "./bash-shell-worker-protocol";
2
+ export declare function createUtf8LineDecoder(onLine: (line: string) => void, options?: {
3
+ maxBufferedBytes?: number;
4
+ onOverflow?: () => void;
5
+ }): {
6
+ push(chunk: Uint8Array): void;
7
+ end(): void;
8
+ };
9
+ export declare function parseAuthenticatedWorkerResponse(line: string, protocolToken: string | undefined): BashShellWorkerResponse | undefined;
10
+ export declare function runBashShellSupervisor(): Promise<void>;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};
@@ -0,0 +1,63 @@
1
+ import type { MinimizerOptions, ShellOptions, ShellRunOptions, ShellRunResult } from "@gajae-code/natives";
2
+ export declare const BASH_SHELL_WORKER_ARG = "--internal-bash-shell-worker";
3
+ export declare const BASH_SHELL_SUPERVISOR_ARG = "--internal-bash-shell-supervisor";
4
+ export declare const BASH_SHELL_RUNTIME_ARG = "--internal-bash-shell-runtime";
5
+ export type IsolatedShellOptions = Pick<ShellOptions, "sessionEnv" | "snapshotPath"> & {
6
+ minimizer?: MinimizerOptions;
7
+ containedProcessGroup?: boolean;
8
+ };
9
+ export type IsolatedShellRunOptions = Omit<ShellRunOptions, "signal">;
10
+ export type IsolatedShellRunResult = ShellRunResult & {
11
+ /** POSIX signal that terminated the isolated shell worker, when applicable. */
12
+ signal?: string;
13
+ };
14
+ export type BashShellWorkerRequest = {
15
+ type: "init";
16
+ token: string;
17
+ options?: IsolatedShellOptions;
18
+ ownershipLedger?: {
19
+ path: string;
20
+ token: string;
21
+ };
22
+ } | {
23
+ type: "run";
24
+ token: string;
25
+ id: number;
26
+ options: IsolatedShellRunOptions;
27
+ } | {
28
+ type: "abort";
29
+ token: string;
30
+ id: number;
31
+ } | {
32
+ type: "close";
33
+ token: string;
34
+ id: number;
35
+ };
36
+ export type BashShellWorkerResponse = {
37
+ type: "ready";
38
+ token: string;
39
+ supervisorPid?: number;
40
+ } | {
41
+ type: "chunk";
42
+ token: string;
43
+ id: number;
44
+ chunk: string;
45
+ } | {
46
+ type: "result";
47
+ token: string;
48
+ id: number;
49
+ result: IsolatedShellRunResult;
50
+ } | {
51
+ type: "void";
52
+ token: string;
53
+ id: number;
54
+ } | {
55
+ type: "retiring";
56
+ token: string;
57
+ message: string;
58
+ } | {
59
+ type: "error";
60
+ token: string;
61
+ id?: number;
62
+ message: string;
63
+ };
@@ -0,0 +1 @@
1
+ export declare function runBashShellWorker(): Promise<void>;
@@ -0,0 +1,19 @@
1
+ import type { ShellRunOptions } from "@gajae-code/natives";
2
+ import { type IsolatedShellOptions, type IsolatedShellRunResult } from "./bash-shell-worker-protocol";
3
+ export interface IsolatedShellLifecycle {
4
+ onTerminal?: (shell: IsolatedShell) => void;
5
+ workerArgv?: string[];
6
+ }
7
+ export declare class IsolatedShell {
8
+ #private;
9
+ constructor(options?: IsolatedShellOptions, lifecycle?: IsolatedShellLifecycle);
10
+ isTerminal(): boolean;
11
+ ready(): Promise<void>;
12
+ supervisorPid(): number | undefined;
13
+ isRunSignalActive(signal: AbortSignal): boolean;
14
+ wasRunSignalDispatched(signal: AbortSignal): boolean;
15
+ run(options: ShellRunOptions, onChunk?: (error: Error | null, chunk: string) => void): Promise<IsolatedShellRunResult>;
16
+ abort(): Promise<void>;
17
+ close(): Promise<void>;
18
+ }
19
+ export declare function smokeTestIsolatedShell(): Promise<void>;
@@ -0,0 +1,9 @@
1
+ import { type FunctionHookRegistration } from "./function-hooks";
2
+ export type TaggedFunctionHookHandler = (...args: unknown[]) => Promise<unknown>;
3
+ export declare function tagExtensionHandlerRegistrationOrder<T extends (...args: never[]) => unknown>(handler: T, registrationOrder: number): T;
4
+ export declare function wrapExtensionHandlerRegistration<T extends (...args: never[]) => unknown>(handler: T, registrationOrder: number): T;
5
+ export declare function getExtensionHandlerRegistrationOrder(value: unknown): number | undefined;
6
+ export declare function tagFunctionHookHandler(registration: FunctionHookRegistration): TaggedFunctionHookHandler;
7
+ export declare function getFunctionHookRegistration(value: unknown): FunctionHookRegistration | undefined;
8
+ /** Host-only root-confined read used by the mediated filesystem capability. */
9
+ export declare function readConstrainedFunctionHookFile(filePath: string, cwd: string, roots: readonly string[]): Promise<string>;
@@ -0,0 +1,169 @@
1
+ import type { ImageContent, TextContent } from "@gajae-code/ai/core";
2
+ import type { CustomMessage } from "../../session/messages";
3
+ import type { ExtensionEvent, ExtensionSessionMetadata, ExtensionUIContext } from "./types";
4
+ /** Function hooks use the existing extension event stream; `*` is observation middleware. */
5
+ export type FunctionHookEventType = ExtensionEvent["type"] | "*";
6
+ export type FunctionHookPayloadFor<T extends FunctionHookEventType> = T extends "*" ? ExtensionEvent : Extract<ExtensionEvent, {
7
+ type: T;
8
+ }>;
9
+ /** Narrow capability vocabulary. Parent names expand only to their children. */
10
+ export type FunctionHookCapability = "tool" | "tool.inspect" | "tool.transform" | "tool.deny" | "ui" | "ui.status" | "ui.widget" | "ui.notify" | "ui.transform" | "session" | "session.read" | "session.message" | "session.deny" | "audit" | "audit.append" | "network" | "network.fetch" | "filesystem" | "filesystem.read";
11
+ export declare const FUNCTION_HOOK_CAPABILITIES: readonly FunctionHookCapability[];
12
+ export type FunctionHookOperation = Exclude<FunctionHookCapability, "tool" | "ui" | "session" | "audit" | "network" | "filesystem">;
13
+ export interface FunctionHookGrantInput {
14
+ capabilities?: readonly FunctionHookCapability[];
15
+ networkDestinations?: readonly string[];
16
+ filesystemRoots?: readonly string[];
17
+ /** An attenuation boundary can only remove downstream operations. */
18
+ attenuateDownstream?: readonly FunctionHookCapability[];
19
+ }
20
+ export interface FunctionHookGrant {
21
+ readonly capabilities: readonly FunctionHookCapability[];
22
+ readonly networkDestinations: readonly string[];
23
+ readonly filesystemRoots: readonly string[];
24
+ readonly attenuateDownstream?: readonly FunctionHookCapability[];
25
+ }
26
+ export interface FunctionHookRegistrationOptions extends FunctionHookGrantInput {
27
+ target?: string;
28
+ registrationId?: string;
29
+ }
30
+ export interface FunctionHookProvenance {
31
+ readonly source: "builtin" | "user" | "project" | "extension" | "plugin-bundle";
32
+ readonly scope?: "user" | "project" | "native";
33
+ readonly plugin?: string;
34
+ readonly extensionId?: string;
35
+ readonly path: string;
36
+ readonly activationGeneration?: number;
37
+ }
38
+ export interface FunctionHookInvocation<TEvent extends ExtensionEvent = ExtensionEvent> {
39
+ readonly eventId: string;
40
+ readonly correlationId: string;
41
+ readonly eventType: TEvent["type"];
42
+ readonly provenance: FunctionHookProvenance;
43
+ readonly registrationOrder: number;
44
+ readonly payload: Readonly<TEvent>;
45
+ readonly signal: AbortSignal;
46
+ }
47
+ export interface FunctionHookMessage {
48
+ customType: string;
49
+ content: string | (TextContent | ImageContent)[];
50
+ display?: boolean;
51
+ details?: unknown;
52
+ attribution?: CustomMessage["attribution"];
53
+ triggerTurn?: boolean;
54
+ deliverAs?: "steer" | "followUp";
55
+ }
56
+ export interface FunctionHookAuditRecord {
57
+ readonly sequence: number;
58
+ readonly eventId: string;
59
+ readonly correlationId: string;
60
+ readonly eventType: string;
61
+ readonly action: "continue" | "deny" | "return" | "error" | "timeout";
62
+ readonly registrationOrder: number;
63
+ readonly provenance: {
64
+ source: FunctionHookProvenance["source"];
65
+ scope?: FunctionHookProvenance["scope"];
66
+ plugin?: string;
67
+ extensionId?: string;
68
+ path: string;
69
+ };
70
+ readonly reason?: string;
71
+ readonly payloadHash?: string;
72
+ readonly evidence?: unknown;
73
+ readonly requestedCapabilities?: readonly FunctionHookCapability[];
74
+ readonly effectiveCapabilities?: readonly FunctionHookCapability[];
75
+ readonly capabilityHash?: string;
76
+ readonly activationGeneration?: number;
77
+ }
78
+ export interface FunctionHookCapabilities {
79
+ readonly tool?: {
80
+ readonly canInspect: boolean;
81
+ readonly canTransform: boolean;
82
+ readonly canDeny: boolean;
83
+ };
84
+ readonly ui?: {
85
+ readonly canTransform: boolean;
86
+ readonly setStatus: (key: string, text: string | undefined) => void;
87
+ readonly setWidget: (key: string, content: string[] | undefined) => void;
88
+ readonly notify: (message: string, type?: "info" | "warning" | "error") => void;
89
+ };
90
+ readonly session?: {
91
+ readonly canRead: boolean;
92
+ readonly canDeny: boolean;
93
+ readonly metadata: Readonly<ExtensionSessionMetadata> | undefined;
94
+ readonly emitMessage: (message: FunctionHookMessage, options?: {
95
+ triggerTurn?: boolean;
96
+ deliverAs?: "steer" | "followUp";
97
+ }) => void;
98
+ };
99
+ readonly audit?: {
100
+ readonly append: (evidence: Record<string, unknown>) => void;
101
+ };
102
+ readonly network?: {
103
+ readonly fetch: (input: string | URL, init?: RequestInit) => Promise<Response>;
104
+ };
105
+ readonly filesystem?: {
106
+ readonly readFile: (filePath: string) => Promise<string>;
107
+ };
108
+ }
109
+ export type FunctionHookResult<TEvent extends ExtensionEvent = ExtensionEvent> = {
110
+ action: "continue";
111
+ event?: TEvent;
112
+ } | {
113
+ action: "deny";
114
+ reason: string;
115
+ } | {
116
+ action: "return";
117
+ value: unknown;
118
+ };
119
+ export type FunctionHookNext<TEvent extends ExtensionEvent = ExtensionEvent> = (event?: TEvent) => Promise<FunctionHookResult<TEvent>>;
120
+ export type FunctionHook<TEvent extends ExtensionEvent = ExtensionEvent> = (invocation: FunctionHookInvocation<TEvent>, capabilities: FunctionHookCapabilities, next: FunctionHookNext<TEvent>) => FunctionHookResult<TEvent> | undefined | Promise<FunctionHookResult<TEvent> | undefined>;
121
+ export interface FunctionHookRegistration {
122
+ readonly event: FunctionHookEventType;
123
+ readonly target?: string;
124
+ readonly registrationId?: string;
125
+ readonly handler: FunctionHook;
126
+ readonly grant: FunctionHookGrant;
127
+ readonly provenance: FunctionHookProvenance;
128
+ readonly registrationOrder?: number;
129
+ }
130
+ export declare function validateFunctionHookTarget(target: string): string;
131
+ export declare function normalizeFunctionHookGrant(input?: FunctionHookGrantInput): FunctionHookGrant;
132
+ export declare function compatibilityFunctionHookGrant(event: string): FunctionHookGrant;
133
+ export declare function functionHookGrantOperations(grant: FunctionHookGrant): ReadonlySet<FunctionHookOperation>;
134
+ export declare function intersectFunctionHookGrants(grant: FunctionHookGrant, allowed: FunctionHookGrant | undefined): FunctionHookGrant;
135
+ /** Host-owned ceiling for caller-registered in-process extension and hook middleware. */
136
+ export declare const DEFAULT_EXTENSION_FUNCTION_HOOK_GRANT: FunctionHookGrant;
137
+ export declare function attenuateFunctionHookGrant(grant: FunctionHookGrant, removed: readonly FunctionHookCapability[] | undefined): FunctionHookGrant;
138
+ export declare function functionHookGrantHash(grant: FunctionHookGrant): string;
139
+ /** Bounded JSON-shaped redaction for diagnostics and audit sinks. Accessors are never invoked. */
140
+ export declare function redactFunctionHookValue(value: unknown, depth?: number, seen?: WeakSet<object>): unknown;
141
+ export declare function functionHookPayloadHash(value: unknown): string;
142
+ export declare function isPlainFunctionHookData(value: unknown, seen?: WeakSet<object>): boolean;
143
+ export declare function isSafeFunctionHookValue(value: unknown, seen?: WeakSet<object>): boolean;
144
+ export declare function isValidFunctionHookEventValue(event: ExtensionEvent): boolean;
145
+ export declare function functionHookEventIdentityMatches(original: ExtensionEvent, candidate: ExtensionEvent): boolean;
146
+ export declare function cloneFunctionHookData<T>(value: T): T;
147
+ /** Security-boundary clone for transformed events; never aliases caller-owned data. */
148
+ export declare function cloneFunctionHookDataStrict<T>(value: T): T;
149
+ export interface FunctionHookCapabilityBindings {
150
+ readonly cwd: string;
151
+ readonly ui: ExtensionUIContext;
152
+ readonly sessionMetadata?: ExtensionSessionMetadata;
153
+ readonly isActive?: () => boolean;
154
+ readonly emitMessage: (message: FunctionHookMessage, options?: {
155
+ triggerTurn?: boolean;
156
+ deliverAs?: "steer" | "followUp";
157
+ }) => void;
158
+ readonly appendAudit: (evidence: Record<string, unknown>) => void;
159
+ readonly fetch: (input: string | URL, init?: RequestInit) => Promise<Response>;
160
+ readonly readFile: (filePath: string, roots: readonly string[]) => Promise<string>;
161
+ }
162
+ export declare function createFunctionHookCapabilities(grant: FunctionHookGrant, bindings: FunctionHookCapabilityBindings): FunctionHookCapabilities;
163
+ export declare function compatibilityPayloadForFunctionHook(event: ExtensionEvent, grant: FunctionHookGrant, wildcard: boolean): ExtensionEvent;
164
+ export declare function functionHookInspectAllowed(event: ExtensionEvent, grant: FunctionHookGrant, wildcard: boolean): boolean;
165
+ export declare function functionHookTransformAllowed(event: ExtensionEvent, grant: FunctionHookGrant): boolean;
166
+ export declare function functionHookDenyAllowed(event: ExtensionEvent, grant: FunctionHookGrant): boolean;
167
+ export declare function sanitizeFunctionHookReason(reason: unknown, fallback: string): string;
168
+ /** Validate terminal short-circuit values before they enter event-specific host control flow. */
169
+ export declare function isValidFunctionHookReturnValue(event: ExtensionEvent, value: unknown): boolean;