pi2dsh 0.12.2 → 0.12.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.
@@ -8,6 +8,11 @@ const rule = (level, detail) => ({
8
8
  level,
9
9
  detail
10
10
  });
11
+ const surface = (level, detail, design) => ({
12
+ level,
13
+ detail,
14
+ design
15
+ });
11
16
  const PI_CODING_AGENT_PACKAGES = Object.freeze(["@earendil-works/pi-coding-agent", "@mariozechner/pi-coding-agent"]);
12
17
  const PI_TUI_PACKAGES = Object.freeze(["@earendil-works/pi-tui", "@mariozechner/pi-tui"]);
13
18
  const PI_AI_PACKAGES = Object.freeze(["@earendil-works/pi-ai", "@mariozechner/pi-ai"]);
@@ -227,282 +232,336 @@ const HOST_IMPORT_RULES = Object.freeze({
227
232
  })
228
233
  });
229
234
  const CONTEXT_RULES = Object.freeze({
230
- cwd: rule("full", "Mapped to the active DSH agent session working directory."),
231
- signal: rule("full", "Mapped to the active DSH cancellation signal when one is available."),
232
- hasUI: rule("full", "Reports whether a human can actually answer: false when no questions service is mounted, false when the service is mounted with no provider registered (the headless posture), and false inside a child agent, which DSH refuses to let ask."),
233
- mode: rule("partial", "Reports rpc mode so Pi extensions can choose their documented headless fallback."),
234
- isIdle: rule("partial", "Command contexts report idle; tool/lifecycle contexts conservatively report non-idle."),
235
- isProjectTrusted: rule("partial", "Fails closed as untrusted because DSH does not expose Pi project-trust state."),
236
- hasPendingMessages: rule("full", "Reads the DSH agent inbox — next-step plus next-turn input — which is exactly Pi's steering plus follow-up queue."),
237
- getContextUsage: rule("partial", "Returns no Pi token-usage projection."),
238
- getSystemPrompt: rule("full", "Returns the system prompt currently assembled by the bridge."),
239
- getSystemPromptOptions: rule("partial", "Returns an empty Pi option projection in command contexts."),
240
- waitForIdle: rule("partial", "Mapped to the DSH agent idle boundary when available."),
241
- sessionManager: rule("partial", "A real read-only projection: DSH durable messages plus pi2dsh sidecar entries, exposed through Pi's exact 14-method surface as a single-branch tree. buildContextEntries is compaction-aware — entries a compaction summarized away are gone, exactly as they are for the model — while getEntries stays the append-only log, which is the same split Pi makes."),
242
- modelRegistry: rule("partial", "A live registry over the ONE model directory — the DSH llm directory — projected exactly into Pi vocabulary; package-registered Pi-native routes keep api/baseUrl and the full Model shape through the round trip. DSH describes one model across two seams (a listing for directory membership, an exact per-route resolve for capacity) while Pi puts everything on one Model object read synchronously, so the projection joins them when the directory is read: entries carry contextWindow, maxTokens and reasoning, and a settings change re-reads them rather than serving the retired numbers. Custom gateways are HOST configuration (the official llm-pi-ai adapter's settings), never a Pi-side file: Pi's ~/.pi/agent/models.json is deliberately NOT read — user-facing configuration is DSH-shaped only. getProviderAuth/getApiKeyAndHeaders run Pi's full credential chain for package-registered providers and the host's configurable-provider + credentials seams for DSH routes. Host configuration may declare \"<route>-vision\" image-admission companions: real DSH routes that admit images, replace image blocks with explicit path-carrying notices (materialized attachment files any path-taking tool can read), and forward text-only to the original route; Pi's ctx.model reports the original route for a companion selection."),
243
- model: rule("partial", "The agent's real provider/model route (a setModel() override wins), enriched from the projected catalog. When the selected route is an image-admission companion, ctx.model reports the ORIGINAL route with its true modalities — the generating model is the original text-only one, which is the truth extensions branching on input modalities (a vision bridge's activation check) need."),
244
- scopedModels: rule("full", "Empty, carrying Pi's own meaning for empty: no model scope is configured, so every available model is usable. DSH has no model-scope concept to narrow it."),
245
- hasConfiguredAuth: rule("partial", "Configuration check on the projected registry: true when the model's provider has a live route or package registration (not a key-liveness probe)."),
246
- thinkingLevel: rule("partial", "Reflects the level recorded by setThinkingLevel(); applied as reasoningEffort on the next request."),
247
- abort: rule("partial", "Mapped to agent.cancel({ kind: \"hook\" }) on the live DSH agent."),
248
- shutdown: rule("partial", "Pi defines shutdown behavior as host-provided (runner.ts bindExtensions); this host absorbs the request — the user owns DSH process exit — and informs the user once. The package keeps running."),
249
- compact: rule("partial", "Pi's fire-and-forget trigger, translated to DSH's official manual compaction (ctx.compaction.compactNow on the live agent). onComplete receives the real summary text and the shadowed-content token estimate as tokensBefore; firstKeptEntryId is empty because the DSH log has no Pi entry ids. Without a compaction service the gap flows through Pi's onError callback and the capability ledger."),
250
- newSession: rule("partial", "Really creates a DSH session (ctx.sessions.create) with parent lineage; withSession runs against a projection context bound to it, whose sendMessage/sendUserMessage/appendEntry write into THAT session rather than the one the call came from. DSH has no host-level \"current session pointer\" a plugin could move — which session the surface shows stays a host choice, announced once."),
251
- fork: rule("partial", "Really forks on DSH's official prefix-fork surface (ctx.sessions.fork with lineage metadata). Anchors are durable-log entries (projected ids \"dsh-<seq>\"); Pi's default position \"before\" is honored, and the boundary shrinks to the nearest completed-turn edge because DSH seeds must not split an open turn. Sidecar entries cannot anchor a fork."),
252
- navigateTree: rule("partial", "Expressed through DSH's session model: the DSH tree lives BETWEEN sessions (fork lineage), not inside one log, so navigation forks at the target boundary. summarize runs Pi's vendored branch summarizer over the abandoned durable slice (model call on the DSH llm bridge) and lands the summary as a branch_summary entry in the new session's projection; label lands as a label entry."),
253
- switchSession: rule("partial", "Targets a LIVE DSH session by id (or a Pi-style path whose basename is \"<id>.jsonl\"); withSession runs against its projection. Switching to persisted sessions is host-owned — resume them from the DSH surface first. Which session the surface shows stays a host choice."),
254
- reload: rule("partial", "Really remounts every mounted package's extension entries through a fresh loader (registrations replaced via the same-name path, event handlers reset), so edited plugin code takes effect. Skills, prompts, and themes are host-managed and reload with dsh itself.")
235
+ cwd: surface("full", "Mapped to the active DSH agent session working directory.", "Read off the live DSH agent's session; the bridge keeps no working directory of its own."),
236
+ signal: surface("full", "Mapped to the active DSH cancellation signal when one is available.", "The DSH cancellation signal belonging to the moment the context was built — a tool context carries its execution signal, a lifecycle context the agent's."),
237
+ hasUI: surface("full", "Reports whether a human can actually answer: false when no questions service is mounted, false when the service is mounted with no provider registered (the headless posture), and false inside a child agent, which DSH refuses to let ask.", "Probed rather than guessed. The bridge registers a throwaway provider on DSH's UserQuestionService and reads the documented DUPLICATE_PROVIDER rejection as \"a real provider is already there\", then disposes it. Inside a child agent the answer is false without probing, because DSH refuses to let child agents ask."),
238
+ mode: surface("partial", "Reports rpc mode so Pi extensions can choose their documented headless fallback.", "A constant. The bridge is an rpc host, and this is the value Pi packages branch on to take their own documented headless path."),
239
+ isIdle: surface("partial", "Command contexts report idle; tool/lifecycle contexts conservatively report non-idle.", "Derived from which context the call arrived through: a command context is outside a step, a tool or lifecycle context is inside one."),
240
+ isProjectTrusted: surface("partial", "Fails closed as untrusted because DSH does not expose Pi project-trust state.", "No DSH seam carries Pi's project-trust state, so the bridge returns the safe constant instead of inventing one. Pi's own ProjectTrustStore is vendored and available to packages that manage their own."),
241
+ hasPendingMessages: surface("full", "Reads the DSH agent inbox — next-step plus next-turn input — which is exactly Pi's steering plus follow-up queue.", "Reads the DSH agent inbox — next-step plus next-turn queues — which is the same queue sendMessage/sendUserMessage write into."),
242
+ getContextUsage: surface("partial", "Returns no Pi token-usage projection.", "Not synthesized. DSH accounts tokens in its own token-meter service, and a guessed Pi projection would be read as measurement."),
243
+ getSystemPrompt: surface("full", "Returns the system prompt currently assembled by the bridge.", "Returns the string the bridge recorded during system-prompt/assemble for the current turn. It is recorded on every assembly, before any gate, so a package that only reads the prompt still sees it."),
244
+ getSystemPromptOptions: surface("partial", "Returns an empty Pi option projection in command contexts.", "Pi's option object describes Pi's own prompt builder, which never runs here; the projection stays empty rather than reconstructed from DSH's sections."),
245
+ waitForIdle: surface("partial", "Mapped to the DSH agent idle boundary when available.", "Awaits the DSH agent's own idle boundary when the agent exposes one."),
246
+ sessionManager: surface("partial", "A real read-only projection: DSH durable messages plus pi2dsh sidecar entries, exposed through Pi's exact 14-method surface as a single-branch tree. buildContextEntries is compaction-aware — entries a compaction summarized away are gone, exactly as they are for the model — while getEntries stays the append-only log, which is the same split Pi makes.", "A read-only projection folded from two ordered sources — DSH's durable session log and the pi2dsh sidecar — into the single chain Pi's 14-method surface walks. Compaction awareness comes from the same fold: entries a compaction summarized away are dropped from the context view and kept in the log view."),
247
+ modelRegistry: surface("partial", "A live registry over the ONE model directory — the DSH llm directory — projected exactly into Pi vocabulary; package-registered Pi-native routes keep api/baseUrl and the full Model shape through the round trip. DSH describes one model across two seams (a listing for directory membership, an exact per-route resolve for capacity) while Pi puts everything on one Model object read synchronously, so the projection joins them when the directory is read: entries carry contextWindow, maxTokens and reasoning, and a settings change re-reads them rather than serving the retired numbers. Custom gateways are HOST configuration (the official llm-pi-ai adapter's settings), never a Pi-side file: Pi's ~/.pi/agent/models.json is deliberately NOT read — user-facing configuration is DSH-shaped only. getProviderAuth/getApiKeyAndHeaders run Pi's full credential chain for package-registered providers and the host's configurable-provider + credentials seams for DSH routes. Host configuration may declare \"<route>-vision\" image-admission companions: real DSH routes that admit images, replace image blocks with explicit path-carrying notices (materialized attachment files any path-taking tool can read), and forward text-only to the original route; Pi's ctx.model reports the original route for a companion selection.", "A ModelCatalog over llm.listProviders() x listModels(), joined per route with llm.resolveModelInfo() for capacity, refreshed on the llm/adapters-updated notification and cached so reads stay synchronous — Pi's getAll() is not async. Package-registered routes have their exact Pi Model restored from the registration on the way out."),
248
+ model: surface("partial", "The agent's real provider/model route (a setModel() override wins), enriched from the projected catalog. When the selected route is an image-admission companion, ctx.model reports the ORIGINAL route with its true modalities — the generating model is the original text-only one, which is the truth extensions branching on input modalities (a vision bridge's activation check) need.", "Reads the live agent's own options.provider/model (a setModel override wins) and enriches it through the same catalog projection, so a package reads one Model shape everywhere."),
249
+ scopedModels: surface("full", "Empty, carrying Pi's own meaning for empty: no model scope is configured, so every available model is usable. DSH has no model-scope concept to narrow it.", "Nothing to compute. DSH has no model-scope concept, and Pi's empty array already carries exactly that meaning."),
250
+ hasConfiguredAuth: surface("partial", "Configuration check on the projected registry: true when the model's provider has a live route or package registration (not a key-liveness probe).", "Answered from configuration alone — the package-provider map plus the live catalog — and never opens a connection, which is also what Pi's own check does."),
251
+ thinkingLevel: surface("partial", "Reflects the level recorded by setThinkingLevel(); applied as reasoningEffort on the next request.", "Bridge state written by setThinkingLevel, applied to the request on the agent/request waterfall."),
252
+ abort: surface("partial", "Mapped to agent.cancel({ kind: \"hook\" }) on the live DSH agent.", "Calls agent.cancel({ kind: 'hook' }) on the live DSH agent."),
253
+ shutdown: surface("partial", "Pi defines shutdown behavior as host-provided (runner.ts bindExtensions); this host absorbs the request — the user owns DSH process exit — and informs the user once. The package keeps running.", "Pi defines shutdown as host-provided, so this host absorbs it: the request is recorded in the capability ledger and surfaced to the user once. Nothing calls process.exit — the DSH process belongs to the user."),
254
+ compact: surface("partial", "Pi's fire-and-forget trigger, translated to DSH's official manual compaction (ctx.compaction.compactNow on the live agent). onComplete receives the real summary text and the shadowed-content token estimate as tokensBefore; firstKeptEntryId is empty because the DSH log has no Pi entry ids. Without a compaction service the gap flows through Pi's onError callback and the capability ledger.", "Calls DSH's official compaction.compactNow on the live agent and adapts the outcome onto Pi's onComplete/onError callbacks. With no compaction service mounted the gap flows through onError rather than an exception."),
255
+ newSession: surface("partial", "Really creates a DSH session (ctx.sessions.create) with parent lineage; withSession runs against a projection context bound to it, whose sendMessage/sendUserMessage/appendEntry write into THAT session rather than the one the call came from. DSH has no host-level \"current session pointer\" a plugin could move — which session the surface shows stays a host choice, announced once.", "ctx.sessions.create with parent lineage, then a projection context bound to the new session so everything inside withSession (sendMessage, appendEntry, ...) writes into THAT session."),
256
+ fork: surface("partial", "Really forks on DSH's official prefix-fork surface (ctx.sessions.fork with lineage metadata). Anchors are durable-log entries (projected ids \"dsh-<seq>\"); Pi's default position \"before\" is honored, and the boundary shrinks to the nearest completed-turn edge because DSH seeds must not split an open turn. Sidecar entries cannot anchor a fork.", "ctx.sessions.fork at a durable sequence number decoded from the projected entry id (dsh-<seq>), snapped to the nearest completed-turn edge because a DSH seed must not split an open turn."),
257
+ navigateTree: surface("partial", "Expressed through DSH's session model: the DSH tree lives BETWEEN sessions (fork lineage), not inside one log, so navigation forks at the target boundary. summarize runs Pi's vendored branch summarizer over the abandoned durable slice (model call on the DSH llm bridge) and lands the summary as a branch_summary entry in the new session's projection; label lands as a label entry.", "A fork at the target boundary, plus — when the caller asks for one — Pi's vendored branch summarizer run over the abandoned slice with the DSH llm bridge filling Pi's own streamFn injection point."),
258
+ switchSession: surface("partial", "Targets a LIVE DSH session by id (or a Pi-style path whose basename is \"<id>.jsonl\"); withSession runs against its projection. Switching to persisted sessions is host-owned — resume them from the DSH surface first. Which session the surface shows stays a host choice.", "Resolves the id (or the basename of a Pi-style <id>.jsonl path) against live DSH sessions and binds a projection context to it."),
259
+ reload: surface("partial", "Really remounts every mounted package's extension entries through a fresh loader (registrations replaced via the same-name path, event handlers reset), so edited plugin code takes effect. Skills, prompts, and themes are host-managed and reload with dsh itself.", "Remounts every mounted package's extension entries through a fresh jiti loader: registrations are replaced through the same-name path and handler lists reset, so edited plugin code takes effect without restarting DSH.")
255
260
  });
256
261
  const UI_CONTEXT_RULES = Object.freeze({
257
- notify: rule("full", "Captured as a command result when applicable and emitted through DSH logging at the severity the caller passed (warning and error log as warnings)."),
258
- setStatus: rule("partial", "Accepted as a no-op because DSH owns status presentation."),
259
- setWidget: rule("partial", "Accepted as a no-op because Pi terminal widgets cannot render in DSH."),
260
- select: rule("full", "Mapped to one native DSH userQuestions single-select request."),
261
- confirm: rule("full", "Mapped to one native DSH userQuestions Yes/No request."),
262
- input: rule("full", "Mapped to one native DSH userQuestions free-text request."),
263
- editor: rule("partial", "Mapped to one DSH userQuestions free-text request. The prefill is shown as context but is NOT editable text: the caller receives what the user typed fresh, not an edit of the prefill."),
264
- custom: rule("partial", "Resolves undefined, exactly like Pi's own rpc mode; guarded fallbacks keep working."),
265
- onTerminalInput: rule("partial", "Raw terminal input is absent; feature-detected listeners remain disabled."),
266
- setWorkingMessage: rule("partial", "Accepted as a no-op; DSH owns progress presentation."),
267
- setWorkingVisible: rule("partial", "Accepted as a no-op; DSH owns progress presentation."),
268
- setWorkingIndicator: rule("partial", "Accepted as a no-op; DSH owns progress presentation."),
269
- setHiddenThinkingLabel: rule("partial", "Accepted as a no-op; DSH owns thinking presentation."),
270
- setFooter: rule("partial", "Accepted as a no-op; DSH owns footer presentation."),
271
- setHeader: rule("partial", "Accepted as a no-op; DSH owns header presentation."),
272
- setTitle: rule("partial", "Accepted as a no-op; DSH owns window titles."),
273
- pasteToEditor: rule("partial", "Appends to a per-agent editor buffer readable through getEditorText()."),
274
- setEditorText: rule("partial", "Stored in a per-agent editor buffer readable through getEditorText()."),
275
- getEditorText: rule("partial", "Reads the per-agent editor buffer maintained by the bridge."),
276
- addAutocompleteProvider: rule("partial", "Registration is recorded; no DSH surface requests suggestions."),
277
- setEditorComponent: rule("partial", "Registration is recorded; no DSH surface mounts a Pi editor component."),
278
- getEditorComponent: rule("partial", "Returns the recorded factory."),
279
- theme: rule("partial", "A headless theme whose styling calls return unstyled text."),
280
- getAllThemes: rule("partial", "Lists the single headless theme."),
281
- getTheme: rule("partial", "Resolves only the headless theme."),
282
- setTheme: rule("partial", "Accepts the headless theme; other names report an explicit error result."),
283
- getToolsExpanded: rule("partial", "A bridge-local presentation flag."),
284
- setToolsExpanded: rule("partial", "A bridge-local presentation flag.")
262
+ notify: surface("full", "Captured as a command result when applicable and emitted through DSH logging at the severity the caller passed (warning and error log as warnings).", "Written to the DSH logger at the severity the caller passed, and returned as the command result when the call happens inside one."),
263
+ setStatus: surface("partial", "Accepted as a no-op because DSH owns status presentation.", "Not wired yet, and not because there is nowhere to put it: DSH's browser half has slot seats for frame-wide and in-conversation surfaces, and pi2dsh already ships a client half that takes one (the side-conversation panel). The call is accepted and returns so a package that decorates a terminal keeps running; when a real consumer appears, this lands on the same client half rather than staying a no-op."),
264
+ setWidget: surface("partial", "Accepted as a no-op because Pi terminal widgets cannot render in DSH.", "Not wired yet, and not because there is nowhere to put it: DSH's browser half has slot seats for frame-wide and in-conversation surfaces, and pi2dsh already ships a client half that takes one (the side-conversation panel). The call is accepted and returns so a package that decorates a terminal keeps running; when a real consumer appears, this lands on the same client half rather than staying a no-op."),
265
+ select: surface("full", "Mapped to one native DSH userQuestions single-select request.", "One native DSH UserQuestionService request carrying Pi's options as the choices. The turn really blocks until a human answers."),
266
+ confirm: surface("full", "Mapped to one native DSH userQuestions Yes/No request.", "One native DSH UserQuestionService request with two choices."),
267
+ input: surface("full", "Mapped to one native DSH userQuestions free-text request.", "One native DSH UserQuestionService free-text request."),
268
+ editor: surface("partial", "Mapped to one DSH userQuestions free-text request. The prefill is shown as context but is NOT editable text: the caller receives what the user typed fresh, not an edit of the prefill.", "One native DSH UserQuestionService free-text request. DSH has no editable-prefill question type, so the prefill is shown in the question body and the answer comes back as fresh text."),
269
+ custom: surface("partial", "Resolves undefined, exactly like Pi's own rpc mode; guarded fallbacks keep working. A Pi component cannot be forwarded to a browser — but the SHAPE packages use this for (a focused side panel over the conversation) is drawn natively by the bridge's own browser half.", "Two halves. The call itself resolves undefined, matching Pi's rpc mode, because a Pi TUI component has no meaning in a browser. Separately, pi2dsh ships a client half (`dsh.client` + `exports[\"./client\"]`) that takes DSH's frame-wide `shell.overlay` seat and renders a side conversation as a floating panel, fed by this package's own route (`/pi2dsh/side-panel`). So the capability lands as a native DSH surface rather than as a relayed Pi component."),
270
+ onTerminalInput: surface("partial", "Raw terminal input is absent; feature-detected listeners remain disabled.", "Recorded; no DSH surface produces raw terminal input, and feature-detecting packages keep the listener disabled."),
271
+ setWorkingMessage: surface("partial", "Accepted as a no-op; DSH owns progress presentation.", "Not wired yet, and not because there is nowhere to put it: DSH's browser half has slot seats for frame-wide and in-conversation surfaces, and pi2dsh already ships a client half that takes one (the side-conversation panel). The call is accepted and returns so a package that decorates a terminal keeps running; when a real consumer appears, this lands on the same client half rather than staying a no-op."),
272
+ setWorkingVisible: surface("partial", "Accepted as a no-op; DSH owns progress presentation.", "Not wired yet, and not because there is nowhere to put it: DSH's browser half has slot seats for frame-wide and in-conversation surfaces, and pi2dsh already ships a client half that takes one (the side-conversation panel). The call is accepted and returns so a package that decorates a terminal keeps running; when a real consumer appears, this lands on the same client half rather than staying a no-op."),
273
+ setWorkingIndicator: surface("partial", "Accepted as a no-op; DSH owns progress presentation.", "Not wired yet, and not because there is nowhere to put it: DSH's browser half has slot seats for frame-wide and in-conversation surfaces, and pi2dsh already ships a client half that takes one (the side-conversation panel). The call is accepted and returns so a package that decorates a terminal keeps running; when a real consumer appears, this lands on the same client half rather than staying a no-op."),
274
+ setHiddenThinkingLabel: surface("partial", "Accepted as a no-op; DSH owns thinking presentation.", "Not wired yet, and not because there is nowhere to put it: DSH's browser half has slot seats for frame-wide and in-conversation surfaces, and pi2dsh already ships a client half that takes one (the side-conversation panel). The call is accepted and returns so a package that decorates a terminal keeps running; when a real consumer appears, this lands on the same client half rather than staying a no-op."),
275
+ setFooter: surface("partial", "Accepted as a no-op; DSH owns footer presentation.", "Not wired yet, and not because there is nowhere to put it: DSH's browser half has slot seats for frame-wide and in-conversation surfaces, and pi2dsh already ships a client half that takes one (the side-conversation panel). The call is accepted and returns so a package that decorates a terminal keeps running; when a real consumer appears, this lands on the same client half rather than staying a no-op."),
276
+ setHeader: surface("partial", "Accepted as a no-op; DSH owns header presentation.", "Not wired yet, and not because there is nowhere to put it: DSH's browser half has slot seats for frame-wide and in-conversation surfaces, and pi2dsh already ships a client half that takes one (the side-conversation panel). The call is accepted and returns so a package that decorates a terminal keeps running; when a real consumer appears, this lands on the same client half rather than staying a no-op."),
277
+ setTitle: surface("partial", "Accepted as a no-op; DSH owns window titles.", "Not wired yet, and not because there is nowhere to put it: DSH's browser half has slot seats for frame-wide and in-conversation surfaces, and pi2dsh already ships a client half that takes one (the side-conversation panel). The call is accepted and returns so a package that decorates a terminal keeps running; when a real consumer appears, this lands on the same client half rather than staying a no-op."),
278
+ pasteToEditor: surface("partial", "Appends to a per-agent editor buffer readable through getEditorText().", "A per-agent text buffer inside the bridge; the three editor calls only talk to each other."),
279
+ setEditorText: surface("partial", "Stored in a per-agent editor buffer readable through getEditorText().", "A per-agent text buffer inside the bridge; the three editor calls only talk to each other."),
280
+ getEditorText: surface("partial", "Reads the per-agent editor buffer maintained by the bridge.", "Reads the per-agent buffer the other two editor calls write."),
281
+ addAutocompleteProvider: surface("partial", "Registration is recorded; no DSH surface requests suggestions.", "Kept in bridge state and readable back; no DSH surface consumes it."),
282
+ setEditorComponent: surface("partial", "Registration is recorded; no DSH surface mounts a Pi editor component.", "Kept in bridge state and readable back; no DSH surface consumes it."),
283
+ getEditorComponent: surface("partial", "Returns the recorded factory.", "Returns the factory setEditorComponent recorded."),
284
+ theme: surface("partial", "A headless theme whose styling calls return unstyled text.", "A single headless Theme object whose styling functions return their input unchanged; DSH does the rendering."),
285
+ getAllThemes: surface("partial", "Lists the single headless theme.", "Lists the one headless theme the bridge owns."),
286
+ getTheme: surface("partial", "Resolves only the headless theme.", "Resolves the one headless theme by name."),
287
+ setTheme: surface("partial", "Accepts the headless theme; other names report an explicit error result.", "Accepts the headless theme and returns Pi's explicit error result for any other name, rather than pretending a switch happened."),
288
+ getToolsExpanded: surface("partial", "A bridge-local presentation flag.", "A bridge-local flag; nothing renders from it."),
289
+ setToolsExpanded: surface("partial", "A bridge-local presentation flag.", "A bridge-local flag; nothing renders from it.")
285
290
  });
286
291
  const API_RULES = Object.freeze({
287
292
  registerTool: {
288
293
  level: "partial",
289
- detail: "Registered as a native DSH tool. Text and image results use native DSH content/attachments; unsupported JSON Schema constraints and Pi-only error details are explicitly degraded."
294
+ detail: "Registered as a native DSH tool. Text and image results use native DSH content/attachments; unsupported JSON Schema constraints and Pi-only error details are explicitly degraded.",
295
+ design: "Registered on DSH's own tool registry, so the model sees it in the same catalog and the loop runs it through the same permission and sandbox path. Arguments go through Pi's vendored validateToolArguments inside DSH's prepareArguments hook, which is what gives a Pi tool the coercions (\"7\" to 7) Pi's own gate would have made before its handler ran."
290
296
  },
291
297
  unregisterTool: {
292
298
  level: "full",
293
- detail: "Disposes the exact native DSH tool registration and removes it from the migrated package registry."
299
+ detail: "Disposes the exact native DSH tool registration and removes it from the migrated package registry.",
300
+ design: "Disposes the exact registration handle kept when the tool was registered, and drops it from the package's own registry."
294
301
  },
295
302
  registerCommand: {
296
303
  level: "partial",
297
- detail: "Registered in ctx.commands with Pi's never-throw collision semantics: a package re-registering its own name replaces it, and cross-source collisions mount under Pi's numbered scheme (/name-2 — the earlier registration keeps the bare name, where Pi renumbers both). ui.notify becomes the result, while interactive Pi TUI methods fail explicitly in headless DSH."
304
+ detail: "Registered in ctx.commands with Pi's never-throw collision semantics: a package re-registering its own name replaces it, and cross-source collisions mount under Pi's numbered scheme (/name-2 — the earlier registration keeps the bare name, where Pi renumbers both). ui.notify becomes the result, while interactive Pi TUI methods fail explicitly in headless DSH.",
305
+ design: "Registered on ctx.commands with an input descriptor. The descriptor is what makes /name <arguments> parse as a command in the web app instead of being sent as chat, so it is not optional detail."
298
306
  },
299
307
  registerShortcut: {
300
308
  level: "partial",
301
- detail: "Registration is recorded and introspectable; DSH surfaces feed no terminal key input, so handlers never fire — the same as Pi's non-TUI modes."
309
+ detail: "Registration is recorded and introspectable; DSH surfaces feed no terminal key input, so handlers never fire — the same as Pi's non-TUI modes.",
310
+ design: "Recorded in bridge state; no DSH surface feeds terminal key events, exactly as in Pi's own non-TUI modes."
302
311
  },
303
312
  registerFlag: {
304
313
  level: "partial",
305
- detail: "The declared default is available through getFlag; Pi process flags cannot be added to the DSH launcher."
314
+ detail: "The declared default is available through getFlag; Pi process flags cannot be added to the DSH launcher.",
315
+ design: "The declared default goes into a bridge map. DSH's launcher exposes no seam for adding process flags, so the flag exists for the package's own reads only."
306
316
  },
307
317
  getFlag: {
308
318
  level: "partial",
309
- detail: "Returns the migrated flag default because DSH cannot register the original Pi CLI flag."
319
+ detail: "Returns the migrated flag default because DSH cannot register the original Pi CLI flag.",
320
+ design: "Reads the bridge map registerFlag wrote."
310
321
  },
311
322
  registerProvider: {
312
323
  level: "partial",
313
- detail: "Two outcomes, by whether the provider carries its own transport. WITH a transport (pi-ai createProvider and friends): it becomes a real DSH llm route through llm.registerAdapter, and from then on the package's own HTTP client carries the turn — its API key or OAuth token is resolved by Pi's credential chain and persisted in the bridge's auth.json, not by DSH credentials. WITHOUT one (catalog-only): the declaration is recorded and introspectable, no bridge transport is synthesized, and model calls stay on native DSH llm adapters and credentials."
324
+ detail: "Two outcomes, by whether the provider carries its own transport. WITH a transport (pi-ai createProvider and friends): it becomes a real DSH llm route through llm.registerAdapter, and from then on the package's own HTTP client carries the turn — its API key or OAuth token is resolved by Pi's credential chain and persisted in the bridge's auth.json, not by DSH credentials. WITHOUT one (catalog-only): the declaration is recorded and introspectable, no bridge transport is synthesized, and model calls stay on native DSH llm adapters and credentials.",
325
+ design: "Two mechanisms behind one call. A provider carrying its own transport becomes a real DSH route through llm.registerAdapter, and the package's own HTTP client then carries the turn with its key resolved by Pi's credential chain into the bridge's auth.json. A catalog-only declaration registers no transport at all: those models are served by the host's adapters and DSH credentials, and the declaration only contributes directory entries."
314
326
  },
315
327
  unregisterProvider: {
316
328
  level: "partial",
317
- detail: "Removes the recorded provider declaration."
329
+ detail: "Removes the recorded provider declaration.",
330
+ design: "Disposes the route registration when one was made and drops the recorded declaration."
318
331
  },
319
332
  registerMessageRenderer: {
320
333
  level: "partial",
321
- detail: "Registration is accepted; DSH owns presentation, so the renderer is never invoked — matching Pi's non-TUI surfaces."
334
+ detail: "Registration is accepted; DSH owns presentation, so the renderer is never invoked — matching Pi's non-TUI surfaces.",
335
+ design: "Kept in bridge state and readable back; no DSH surface consumes it."
322
336
  },
323
337
  registerEntryRenderer: {
324
338
  level: "partial",
325
- detail: "Registration is accepted; DSH owns presentation, so the renderer is never invoked — matching Pi's non-TUI surfaces."
339
+ detail: "Registration is accepted; DSH owns presentation, so the renderer is never invoked — matching Pi's non-TUI surfaces.",
340
+ design: "Kept in bridge state and readable back; no DSH surface consumes it."
326
341
  },
327
342
  registerMarkdownTransformer: {
328
343
  level: "partial",
329
- detail: "Registration is accepted; DSH owns presentation, so the transformer is never invoked — matching Pi's non-TUI surfaces."
344
+ detail: "Registration is accepted; DSH owns presentation, so the transformer is never invoked — matching Pi's non-TUI surfaces.",
345
+ design: "Kept in bridge state and readable back; no DSH surface consumes it."
330
346
  },
331
347
  sendMessage: {
332
348
  level: "partial",
333
- detail: "Durable by the time it returns, as in Pi: the no-turn call appends to the session log and announces its message events immediately, so the message IS in the conversation when the call resolves. Steering and follow-up drive a turn through the agent. Pi display/details metadata awaits the custom session-entry seam."
349
+ detail: "Durable by the time it returns, as in Pi: the no-turn call appends to the session log and announces its message events immediately, so the message IS in the conversation when the call resolves. Steering and follow-up drive a turn through the agent. Pi display/details metadata awaits the custom session-entry seam.",
350
+ design: "Two paths under one name. The no-turn call appends through Session.append(type, data, { surfaceOp: 'append' }) — the public marker DSH requires for surface-eligible types — so the message is durable and visible by the time the call resolves, then announces its own message_start/message_end. Steering and follow-up go through the DSH agent inbox instead, which is what actually drives a turn."
334
351
  },
335
352
  sendUserMessage: {
336
353
  level: "full",
337
- detail: "Mapped to native DSH steer/followup delivery with text and attachment-backed image content."
354
+ detail: "Mapped to native DSH steer/followup delivery with text and attachment-backed image content.",
355
+ design: "The DSH agent inbox (steer or follow-up by option), with image content materialized as DSH attachments first."
338
356
  },
339
357
  appendEntry: {
340
358
  level: "partial",
341
- detail: "Persisted in a pi2dsh sidecar next to the DSH session and replayed on session start; DSH's main log stays untouched because it has no out-of-repo plugin-event channel yet."
359
+ detail: "Persisted in a pi2dsh sidecar next to the DSH session and replayed on session start; DSH's main log stays untouched because it has no out-of-repo plugin-event channel yet.",
360
+ design: "A pi2dsh sidecar file beside the DSH session, replayed at session start. DSH's own log has no channel for event types declared outside the harness, and writing unknown types into it corrupts the session for every other reader."
342
361
  },
343
362
  setSessionName: {
344
363
  level: "full",
345
- detail: "Renames the DSH session through ctx.sessionTitle, so every DSH surface shows it and the title is pinned against automatic regeneration, and announces it through session_info_changed. A composition that mounts no title service falls back to the pi2dsh sidecar, as does a blank name (DSH requires visible characters in a title; Pi does not)."
364
+ detail: "Renames the DSH session through ctx.sessionTitle, so every DSH surface shows it and the title is pinned against automatic regeneration, and announces it through session_info_changed. A composition that mounts no title service falls back to the pi2dsh sidecar, as does a blank name (DSH requires visible characters in a title; Pi does not).",
365
+ design: "ctx.sessionTitle.rename, which is also what pins the title against DSH's automatic regeneration, followed by a session_info_changed dispatch. A composition with no title service — or a blank name, which DSH refuses and Pi allows — falls back to the sidecar."
346
366
  },
347
367
  getSessionName: {
348
368
  level: "full",
349
- detail: "Reads DSH's own session title, so it agrees with what DSH displays and sees titles DSH generated itself; falls back to the sidecar when no title service is mounted."
369
+ detail: "Reads DSH's own session title, so it agrees with what DSH displays and sees titles DSH generated itself; falls back to the sidecar when no title service is mounted.",
370
+ design: "ctx.sessionTitle.get, so the answer agrees with what DSH displays and includes titles DSH generated itself; sidecar fallback when no title service is mounted."
350
371
  },
351
372
  setLabel: {
352
373
  level: "partial",
353
- detail: "Persisted in the pi2dsh sidecar and reflected by the sessionManager projection."
374
+ detail: "Persisted in the pi2dsh sidecar and reflected by the sessionManager projection.",
375
+ design: "Stored in the pi2dsh sidecar and read back through the sessionManager projection."
354
376
  },
355
377
  exec: {
356
378
  level: "partial",
357
- detail: "Mapped to ctx.subprocess, so the selected local/E2B provider owns execution, isolation, cancellation, and tree cleanup; output is bounded to 64 MiB per stream."
379
+ detail: "Mapped to ctx.subprocess, so the selected local/E2B provider owns execution, isolation, cancellation, and tree cleanup; output is bounded to 64 MiB per stream.",
380
+ design: "Handed to ctx.subprocess, so the selected local or E2B provider owns execution, isolation, cancellation and process-tree cleanup — the bridge never spawns a child itself."
358
381
  },
359
382
  getActiveTools: {
360
383
  level: "partial",
361
- detail: "Returns every tool visible in the current DSH agent scope, including native and migrated tools; scope-local tools follow DSH composition rules."
384
+ detail: "Returns every tool visible in the current DSH agent scope, including native and migrated tools; scope-local tools follow DSH composition rules.",
385
+ design: "Lists the DSH tool scope the current context belongs to, native and migrated tools alike."
362
386
  },
363
387
  getAllTools: {
364
388
  level: "partial",
365
- detail: "Returns metadata for all tools visible in the current DSH scope, without Pi-specific prompt guidelines unavailable from DSH schemas."
389
+ detail: "Returns metadata for all tools visible in the current DSH scope, without Pi-specific prompt guidelines unavailable from DSH schemas.",
390
+ design: "Reads schemas from the same DSH tool scope; Pi's prompt-guideline fields have no DSH source and are left out rather than invented."
366
391
  },
367
392
  setActiveTools: {
368
393
  level: "partial",
369
- detail: "Mapped to the active DSH agent scope through tools.restrict, per-agent and without mutating other agents. Names DSH does not know are skipped exactly as Pi skips them. A tool DSH does not permit restricting (a scope's own registration, or a reserved transport name) cannot be deactivated at all and is reported once rather than silently left running."
394
+ detail: "Mapped to the active DSH agent scope through tools.restrict, per-agent and without mutating other agents. Names DSH does not know are skipped exactly as Pi skips them. A tool DSH does not permit restricting (a scope's own registration, or a reserved transport name) cannot be deactivated at all and is reported once rather than silently left running.",
395
+ design: "DSH's scoped tools.restrict, narrowed first. Pi silently skips names its registry does not know, while DSH fails the whole restrict call on a name it cannot restrict — so the list is filtered against what the scope reports as restrictable before it is applied, and a visible-but-unrestrictable tool is reported once instead of being silently left running. Called before an agent exists, the intent is remembered and applied when one starts."
370
396
  },
371
397
  getCommands: {
372
398
  level: "partial",
373
- detail: "Returns commands registered by this migrated Pi package, not every command visible in the DSH scope."
399
+ detail: "Returns commands registered by this migrated Pi package, not every command visible in the DSH scope.",
400
+ design: "Reads the bridge's own command map, which holds this package's registrations rather than the whole DSH scope."
374
401
  },
375
402
  setModel: {
376
403
  level: "partial",
377
- detail: "Recorded as a per-agent override applied through the agent/request waterfall on the next model call; DSH remains authoritative for provider routing."
404
+ detail: "Recorded as a per-agent override applied through the agent/request waterfall on the next model call; DSH remains authoritative for provider routing.",
405
+ design: "Recorded as a per-agent override and applied on the agent/request waterfall, so the next model call carries it while DSH stays authoritative for routing."
378
406
  },
379
407
  getThinkingLevel: {
380
408
  level: "partial",
381
- detail: "Returns the level recorded by setThinkingLevel (default off)."
409
+ detail: "Returns the level recorded by setThinkingLevel (default off).",
410
+ design: "Reads the per-agent level the bridge recorded."
382
411
  },
383
412
  setThinkingLevel: {
384
413
  level: "partial",
385
- detail: "Recorded per agent and applied as reasoningEffort through the agent/request waterfall; DSH validates the effort id at the request boundary."
414
+ detail: "Recorded per agent and applied as reasoningEffort through the agent/request waterfall; DSH validates the effort id at the request boundary.",
415
+ design: "Recorded per agent and applied as reasoningEffort on the agent/request waterfall; DSH validates the effort id at the request boundary."
386
416
  },
387
417
  events: {
388
418
  level: "full",
389
- detail: "Package-local Pi extension event-bus emit/on semantics are preserved for migrated extensions in the same bundle."
419
+ detail: "Package-local Pi extension event-bus emit/on semantics are preserved for migrated extensions in the same bundle.",
420
+ design: "A package-local emitter inside the bridge, so extensions bundled together talk to each other exactly as they do under Pi."
390
421
  }
391
422
  });
392
423
  const OBSERVED_NEVER_FIRES = (moment) => ({
393
424
  level: "partial",
394
- detail: `Registration is accepted; ${moment} never occurs on DSH surfaces, so the handler never fires. Loading is unaffected.`
425
+ detail: `Registration is accepted; ${moment} never occurs on DSH surfaces, so the handler never fires. Loading is unaffected.`,
426
+ design: `The handler goes into the bridge's handler map like any other, and nothing dispatches it: no DSH seam produces ${moment}. Registration is kept rather than refused so a package that subscribes at load time still loads.`
395
427
  });
396
428
  const EVENT_RULES = Object.freeze({
397
429
  session_start: {
398
430
  level: "full",
399
- detail: "Mapped to agent/session-start."
431
+ detail: "Mapped to agent/session-start.",
432
+ design: "Dispatched from the cordis agent/session-start notification."
400
433
  },
401
434
  session_shutdown: {
402
435
  level: "full",
403
- detail: "Mapped to agent disposal and plugin teardown with duplicate suppression."
436
+ detail: "Mapped to agent disposal and plugin teardown with duplicate suppression.",
437
+ design: "Dispatched from agent/disposed and from plugin teardown, with duplicate suppression so a package that sees both gets one event."
404
438
  },
405
439
  session_info_changed: {
406
440
  level: "partial",
407
- detail: "Fired by setSessionName() and projected from DSH session/title events."
441
+ detail: "Fired by setSessionName() and projected from DSH session/title events.",
442
+ design: "Two sources, one event: the bridge's own setSessionName, and DSH's durable session title event projected into Pi's shape."
408
443
  },
409
444
  agent_start: {
410
445
  level: "full",
411
- detail: "Mapped to the DSH turn/start boundary."
446
+ detail: "Mapped to the DSH turn/start boundary.",
447
+ design: "The DSH turn/start boundary from the durable session/event stream. DSH's turn is the whole prompt, which is what Pi calls an agent run."
412
448
  },
413
449
  agent_settled: {
414
450
  level: "full",
415
- detail: "Mapped to the DSH turn/end boundary."
451
+ detail: "Mapped to the DSH turn/end boundary.",
452
+ design: "The DSH turn/end boundary."
416
453
  },
417
454
  turn_start: {
418
455
  level: "full",
419
- detail: "Fires once per MODEL CALL, as in Pi — DSH calls that a step — with turnIndex counting from zero and resetting at each new prompt."
456
+ detail: "Fires once per MODEL CALL, as in Pi — DSH calls that a step — with turnIndex counting from zero and resetting at each new prompt.",
457
+ design: "The DSH step/start boundary — one step is one model call, which is what Pi calls a turn. The index resets when a new prompt is claimed off the inbox, matching Pi's reset at agent_start."
420
458
  },
421
459
  tool_execution_start: {
422
460
  level: "full",
423
- detail: "Mapped from durable tool/call events."
461
+ detail: "Mapped from durable tool/call events.",
462
+ design: "Projected from the durable tool/call event, so a handler sees exactly what was written to the session log."
424
463
  },
425
464
  tool_execution_end: {
426
465
  level: "full",
427
- detail: "Mapped from finalized tools/result events."
466
+ detail: "Mapped from finalized tools/result events.",
467
+ design: "Dispatched on DSH's tools/post-execute waterfall and awaited there. Riding the durable result emit instead let the handler land after turn_end, which is the opposite of Pi's order; the waterfall is the moment that is guaranteed to run before the caller sees the result."
428
468
  },
429
469
  tool_execution_update: {
430
470
  level: "partial",
431
- detail: "Fired from migrated Pi tools' own onUpdate callbacks; DSH-native tools expose no partial-result stream."
471
+ detail: "Fired from migrated Pi tools' own onUpdate callbacks; DSH-native tools expose no partial-result stream.",
472
+ design: "Fed by migrated Pi tools' own onUpdate callbacks. DSH-native tools expose no partial-result stream, so nothing is synthesized for them."
432
473
  },
433
474
  tool_call: {
434
475
  level: "partial",
435
- detail: "Blocking is supported, in-place argument mutation reaches migrated Pi tools, and `terminate` follows Pi's batch rule — the loop stops after a tool batch only when every call in it was blocked asking to stop. Mutating a DSH-native tool's arguments is rejected because DSH logs arguments before policy."
476
+ detail: "Blocking is supported, in-place argument mutation reaches migrated Pi tools, and `terminate` follows Pi's batch rule — the loop stops after a tool batch only when every call in it was blocked asking to stop. Mutating a DSH-native tool's arguments is rejected because DSH logs arguments before policy.",
477
+ design: "DSH's tools/pre-execute waterfall, whose decision type carries exactly the two outcomes Pi needs (proceed, deny with a reason). Pi's in-place argument mutation is applied to migrated Pi tools; for a DSH-native tool it is refused, because DSH logs arguments before policy runs and the log would then disagree with what executed. Pi's batch rule for terminate is reimplemented verbatim: the loop stops only when every finalized call in the batch asked to stop."
436
478
  },
437
479
  tool_result: {
438
480
  level: "partial",
439
- detail: "Text replacement and success-to-error blocking are supported; arbitrary details and error recovery are not."
481
+ detail: "Text replacement and success-to-error blocking are supported; arbitrary details and error recovery are not.",
482
+ design: "The same tools/post-execute waterfall, which is where a result can still be rewritten before the caller reads it."
440
483
  },
441
484
  before_agent_start: {
442
485
  level: "full",
443
- detail: "Fires once per user prompt, inside the assembly of the turn it belongs to, with the real prompt text and image attachments; returned custom messages enter that same turn beside the user message, and a returned systemPrompt overrides that turn's own assembly and resets at the next turn."
486
+ detail: "Fires once per user prompt, inside the assembly of the turn it belongs to, with the real prompt text and image attachments; returned custom messages enter that same turn beside the user message, and a returned systemPrompt overrides that turn's own assembly and resets at the next turn.",
487
+ design: "DSH's system-prompt/assemble waterfall — an async waterfall that runs while the prompt is being assembled and whose return value is authoritative. That is why a returned systemPrompt reaches the very turn the handler fired for; the later agent/pre-step waterfall would have been one turn too late. Firing is gated on the inbox claim so it happens once per user prompt, and returned custom messages are held and injected into that same step."
444
488
  },
445
489
  agent_end: {
446
490
  level: "partial",
447
- detail: "The lifecycle boundary is mapped, but the reconstructed Pi message history is intentionally minimal."
491
+ detail: "The lifecycle boundary is mapped, but the reconstructed Pi message history is intentionally minimal.",
492
+ design: "The DSH turn/end boundary. Pi's message history on this event is reconstructed minimally rather than replayed, because the durable log is the honest source and packages that need it read the projection."
448
493
  },
449
494
  turn_end: {
450
495
  level: "full",
451
- detail: "Fires once per MODEL CALL, as in Pi — DSH calls that a step — carrying that call's own assistant message and its own tool results, with turnIndex counting model calls from zero and resetting each prompt."
496
+ detail: "Fires once per MODEL CALL, as in Pi — DSH calls that a step — carrying that call's own assistant message and its own tool results, with turnIndex counting model calls from zero and resetting each prompt.",
497
+ design: "The DSH step/end boundary, carrying that model call's own assistant message and the tool results belonging to it."
452
498
  },
453
499
  message_start: {
454
500
  level: "partial",
455
- detail: "Durable user, assistant, and tool-result messages are mapped without Pi-specific provider metadata."
501
+ detail: "Durable user, assistant, and tool-result messages are mapped without Pi-specific provider metadata.",
502
+ design: "Projected from durable message events in the session/event stream."
456
503
  },
457
504
  message_end: {
458
505
  level: "partial",
459
- detail: "Durable messages are observed, but message replacement is not supported."
506
+ detail: "Durable messages are observed, but message replacement is not supported.",
507
+ design: "Projected from the same durable message events; DSH's log is append-only, so Pi's message replacement has nowhere to land."
460
508
  },
461
509
  message_update: {
462
510
  level: "partial",
463
- detail: "Projected from DSH assistant/chunk events with accumulated text; Pi's full AgentMessage accumulation state is approximated."
511
+ detail: "Projected from DSH assistant/chunk events with accumulated text; Pi's full AgentMessage accumulation state is approximated.",
512
+ design: "Projected from DSH assistant/chunk events with text accumulated by the bridge. Pi's full AgentMessage accumulation state is approximated, not reconstructed."
464
513
  },
465
514
  session_before_compact: {
466
515
  level: "partial",
467
- detail: "Projected from DSH compaction/start as a notification; cancel/replace cannot reach DSH's compactor."
516
+ detail: "Projected from DSH compaction/start as a notification; cancel/replace cannot reach DSH's compactor.",
517
+ design: "Projected from DSH's compaction/start as a notification. It is an emit, not a waterfall, so a cancel or a replacement has no channel to reach DSH's compactor and is not pretended."
468
518
  },
469
519
  session_compact: {
470
520
  level: "partial",
471
- detail: "Fires once per SUCCESSFUL compaction, from DSH's summary event, with the summary rendered to the string Pi's CompactionEntry declares. A manual compaction is identified as manual; DSH does not record which automatic trigger fired, so automatic ones report \"threshold\" and willRetry is always false."
521
+ detail: "Fires once per SUCCESSFUL compaction, from DSH's summary event, with the summary rendered to the string Pi's CompactionEntry declares. A manual compaction is identified as manual; DSH does not record which automatic trigger fired, so automatic ones report \"threshold\" and willRetry is always false.",
522
+ design: "Projected from DSH's compaction summary event, rendered to the exact string Pi's CompactionEntry declares. Manual compactions are identifiable; DSH does not record which automatic trigger fired, so automatic ones report Pi's threshold reason."
472
523
  },
473
524
  model_select: {
474
525
  level: "partial",
475
- detail: "Fired by setModel() and projected from request/header model changes in the durable log."
526
+ detail: "Fired by setModel() and projected from request/header model changes in the durable log.",
527
+ design: "Fired by the bridge's own setModel, and projected from model changes in the durable request/header record — which is the call configuration DSH logs, not the HTTP body."
476
528
  },
477
529
  thinking_level_select: {
478
530
  level: "partial",
479
- detail: "Fired by setThinkingLevel(); DSH-side reasoning changes surface through request/header projection."
531
+ detail: "Fired by setThinkingLevel(); DSH-side reasoning changes surface through request/header projection.",
532
+ design: "Fired by the bridge's own setThinkingLevel; host-side reasoning changes arrive through the same request/header projection."
480
533
  },
481
534
  context: {
482
535
  level: "partial",
483
- detail: "Fires before each step with the full message projection; the transform applies to the step's not-yet-entered messages (the slice packages rewrite), while already-entered history stays read-only under DSH's append-only log."
536
+ detail: "Fires before each step with the full message projection; the transform applies to the step's not-yet-entered messages (the slice packages rewrite), while already-entered history stays read-only under DSH's append-only log.",
537
+ design: "DSH's agent/pre-step waterfall, whose decision type distinguishes entering a step from rejecting it. The transform applies to the messages that have not entered the step yet — the slice Pi packages actually rewrite — while entered history stays read-only under DSH's append-only log."
484
538
  },
485
539
  before_provider_request: {
486
540
  level: "unsupported",
487
- detail: "Provider payload mutation belongs in a native DSH LLM adapter; the handler is accepted but never fires."
541
+ detail: "Provider payload mutation belongs in a native DSH LLM adapter; the handler is accepted but never fires.",
542
+ design: "Deliberately not wired. The request body is built inside a DSH llm adapter, and a package that needs to shape it should be one (or register its own provider, which this bridge does support). Faking the moment on the bridge side would let a handler edit a body that is not the one sent."
488
543
  },
489
544
  before_provider_headers: {
490
545
  level: "unsupported",
491
- detail: "Provider header mutation belongs in a native DSH LLM adapter; the handler is accepted but never fires."
546
+ detail: "Provider header mutation belongs in a native DSH LLM adapter; the handler is accepted but never fires.",
547
+ design: "Deliberately not wired, for the same reason as before_provider_request: headers belong to the adapter that owns the transport."
492
548
  },
493
549
  after_provider_response: {
494
550
  level: "unsupported",
495
- detail: "Provider response interception belongs in a native DSH LLM adapter; the handler is accepted but never fires."
551
+ detail: "Provider response interception belongs in a native DSH LLM adapter; the handler is accepted but never fires.",
552
+ design: "Deliberately not wired: the response is consumed inside the adapter, and interception there is an adapter concern."
496
553
  },
497
554
  user_bash: OBSERVED_NEVER_FIRES("Pi's ! command surface"),
498
555
  input: OBSERVED_NEVER_FIRES("raw Pi terminal input"),
499
556
  project_trust: {
500
557
  level: "unsupported",
501
- detail: "Project trust must remain owned by the DSH host; the handler is accepted but never consulted."
558
+ detail: "Project trust must remain owned by the DSH host; the handler is accepted but never consulted.",
559
+ design: "Accepted and never consulted. Trust is a host decision in DSH, and letting a package answer it would move the decision to the code being trusted."
502
560
  },
503
561
  resources_discover: {
504
562
  level: "unsupported",
505
- detail: "Dynamic resource discovery must be converted into DSH providers; the handler is accepted but never fires."
563
+ detail: "Dynamic resource discovery must be converted into DSH providers; the handler is accepted but never fires.",
564
+ design: "Accepted and never fired. Dynamic resource discovery in DSH is a provider registration, which is a different (and official) seam."
506
565
  },
507
566
  session_before_switch: OBSERVED_NEVER_FIRES("Pi session switching"),
508
567
  session_before_fork: OBSERVED_NEVER_FIRES("Pi tree forking"),
@@ -704,4 +763,4 @@ function renderMcpPatch(result) {
704
763
  //#endregion
705
764
  export { CONTEXT_RULES as a, PI_AI_PACKAGES as c, UI_CONTEXT_RULES as d, ruleForApi as f, ruleForUiContextProperty as g, ruleForHostImport as h, API_RULES as i, PI_CODING_AGENT_PACKAGES as l, ruleForEvent as m, convertPiMcpConfig as n, EVENT_RULES as o, ruleForContextProperty as p, renderMcpPatch as r, HOST_IMPORT_RULES as s, collectPiMcpServers as t, PI_TUI_PACKAGES as u };
706
765
 
707
- //# sourceMappingURL=mcp-config-DHkfjdvX.mjs.map
766
+ //# sourceMappingURL=mcp-config-Cjae0vot.mjs.map