@xmoon76/dsh-pi-tui 0.4.1 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { H as TuiAction } from "./public-types-_pe9ZH00.mjs";
1
+ import { H as TuiAction } from "./public-types-YbB5Rhku.mjs";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { Agent, ModelSelectionRef } from "@deepseek-ai/dsh-agent";
4
4
  import { ContentBlock } from "@deepseek-ai/dsh-llm";
@@ -86,36 +86,6 @@ interface DraftImageStoreLike {
86
86
  values(): readonly DraftImage[];
87
87
  }
88
88
  //#endregion
89
- //#region src/diag.d.ts
90
- interface Diag {
91
- debug(message: string, fields?: Record<string, unknown>): void;
92
- info(message: string, fields?: Record<string, unknown>): void;
93
- warn(message: string, fields?: Record<string, unknown>): void;
94
- error(message: string, fields?: Record<string, unknown>): void;
95
- /** Release the file handle, if one is open. */
96
- dispose(): void;
97
- }
98
- //#endregion
99
- //#region src/tui-app.d.ts
100
- /** The compaction lifecycle phase the working row advertises: idle (no
101
- * compaction), summarizing (compaction/start seen, the summary is being
102
- * generated), or applying (the summary landed, the compacted surface is
103
- * being committed). Derived by the runner from compaction/start →
104
- * compaction/summary → compaction/end (foldCompactionEvent). */
105
- type CompactionPhase = 'idle' | 'summarizing' | 'applying';
106
- /** One queued inbox row for the queue pane (mirrors the agent Inbox's lists). */
107
- interface QueueItem {
108
- /** The pending message id (agent inbox identity). */
109
- id: string;
110
- /** The message text, single-line display form. */
111
- text: string;
112
- /** next-turn followup vs next-step steer. */
113
- mode: 'followup' | 'steer';
114
- /** Plugin notice (e.g. a background-job completion): NOT steerable — it
115
- * renders with its own marker and the hint drops the steer verbs. */
116
- notice?: boolean;
117
- }
118
- //#endregion
119
89
  //#region src/skill-catalog.d.ts
120
90
  /**
121
91
  * The standing-scope skill catalog adapter: the SINGLE narrow seam between
@@ -153,6 +123,43 @@ interface HumanSkillCatalog {
153
123
  readonly complete: boolean;
154
124
  }
155
125
  //#endregion
126
+ //#region src/diag.d.ts
127
+ interface Diag {
128
+ debug(message: string, fields?: Record<string, unknown>): void;
129
+ info(message: string, fields?: Record<string, unknown>): void;
130
+ warn(message: string, fields?: Record<string, unknown>): void;
131
+ error(message: string, fields?: Record<string, unknown>): void;
132
+ /** Release the file handle, if one is open. */
133
+ dispose(): void;
134
+ }
135
+ //#endregion
136
+ //#region src/tui-app.d.ts
137
+ /** The compaction lifecycle phase the working row advertises: idle (no
138
+ * compaction), summarizing (compaction/start seen, the summary is being
139
+ * generated), or applying (the summary landed, the compacted surface is
140
+ * being committed). Derived by the runner from compaction/start →
141
+ * compaction/summary → compaction/end (foldCompactionEvent). */
142
+ type CompactionPhase = 'idle' | 'summarizing' | 'applying';
143
+ /**
144
+ * The WEB composer submit gestures (DSH `ComposerSubmitGesture`): plain
145
+ * Enter, or the Cmd/Ctrl-accelerated chord. The busy-Enter policy resolves
146
+ * each to a delivery mode — plain Enter to the preferred mode, the
147
+ * accelerated chord to its OPPOSITE — so the chord is never a fixed mode.
148
+ */
149
+ type ComposerSubmitGesture = 'enter' | 'accelerated';
150
+ /** One queued inbox row for the queue pane (mirrors the agent Inbox's lists). */
151
+ interface QueueItem {
152
+ /** The pending message id (agent inbox identity). */
153
+ id: string;
154
+ /** The message text, single-line display form. */
155
+ text: string;
156
+ /** next-turn followup vs next-step steer. */
157
+ mode: 'followup' | 'steer';
158
+ /** Plugin notice (e.g. a background-job completion): NOT steerable — it
159
+ * renders with its own marker and the hint drops the steer verbs. */
160
+ notice?: boolean;
161
+ }
162
+ //#endregion
156
163
  //#region src/surface-catalog.d.ts
157
164
  /** One effective command's discovery metadata (the official descriptor's
158
165
  * display fields; never a handler or a definition). */
@@ -161,6 +168,11 @@ interface SurfaceCommandSummary {
161
168
  readonly description: string;
162
169
  readonly input?: {
163
170
  readonly hint: string;
171
+ /** The descriptor's attachment declaration (DSH
172
+ * `CommandInputDescriptor.attachments`): ONLY a command declaring it may
173
+ * be invoked with composer attachments (the composer-side half of the
174
+ * contract — the host executor enforces the other half at admission). */
175
+ readonly attachments?: boolean;
164
176
  };
165
177
  }
166
178
  /** One provider's detached failure text (never the thrown value itself). */
@@ -201,6 +213,34 @@ interface SurfaceCatalogContext {
201
213
  * @returns the fork seed events, or undefined.
202
214
  */
203
215
  declare function forkSeed(events: readonly SessionEvent[]): readonly SessionEvent[] | undefined;
216
+ /**
217
+ * The effective mode ONE submission resolved at the submit boundary: whether
218
+ * an agent-facing delivery steers into the running turn or queues behind it
219
+ * (web `ComposerSubmissionPolicy` parity — an idle agent queues, plain Enter
220
+ * takes the preference, and the accelerated chord takes its OPPOSITE).
221
+ *
222
+ * It is a property of the GESTURE, not of the persisted settings: a one-shot
223
+ * gesture exists only inside the dispatch that resolved it, so every
224
+ * downstream consumer (the TUI skill delivery) accepts this value instead of
225
+ * re-deriving the mode from `busyEnter`.
226
+ */
227
+ type SubmitDelivery = 'steer' | 'queue';
228
+ /**
229
+ * The CURRENT host catalog's view of ONE parsed line (the DSH client
230
+ * `CommandUiRuntime.matchEnter` decision table): whether the host CLAIMS the
231
+ * line, and — for a claimed line — the claiming descriptor's attachment
232
+ * declaration, so the dispatch can never consult two different catalog views
233
+ * for the claim and the declaration.
234
+ */
235
+ type HostCommandClaim = {
236
+ readonly claimed: true;
237
+ /** The claiming descriptor's `input.attachments`
238
+ * (DSH `CommandInputDescriptor.attachments`): only a declaring command
239
+ * may be invoked with composer attachments. */
240
+ readonly attachments: boolean;
241
+ } | {
242
+ readonly claimed: false;
243
+ };
204
244
  //#endregion
205
245
  //#region src/index.d.ts
206
246
  /** Stable Cordis plugin name. */
@@ -258,38 +298,103 @@ declare const LOCAL_COMMANDS: Set<string>;
258
298
  * @param parsed - the parsed slash command, undefined for a plain prompt.
259
299
  * @param text - the submission text.
260
300
  * @param store - the live draft store.
261
- * @param isLocal - whether the command name is a LOCAL (TUI-owned/UI)
262
- * command; skill names answer false.
301
+ * @param isLocal - whether THIS LINE is a LOCAL (TUI-owned/UI) command; skill
302
+ * names answer false. Never derived from the name alone: a host command's
303
+ * input KIND decides which line it claims (see
304
+ * {@link commandIsLocalForAttachments}).
263
305
  */
264
306
  declare function commandRejectsImages(parsed: {
265
307
  name: string;
266
- } | undefined, text: string, store: DraftImageStoreLike, isLocal: (name: string) => boolean): boolean;
308
+ rawInput?: string;
309
+ } | undefined, text: string, store: DraftImageStoreLike, isLocal: boolean): boolean;
267
310
  /**
268
- * The AUTHORITATIVE host-owned command catalog (P1-04): every command name
269
- * the TUI registers itself (registerTuiCommands in commands.ts) plus the
270
- * ownership sets (LOCAL_COMMANDS and SESSIONLESS_COMMANDS — /kill and
271
- * other core commands the TUI does not register but dispatches locally).
272
- * A plugin command contribution is validated against this catalog at
273
- * register time: an exact or near-synonym collision is rejected loudly,
274
- * so a plugin can never shadow a built-in command.
311
+ * The STATIC host-owned command catalog (P1-04): the ownership sets
312
+ * (LOCAL_COMMANDS and SESSIONLESS_COMMANDS — the TUI's own local/UI command
313
+ * names, including the ones `registerTuiCommands` registers, plus core
314
+ * commands such as /kill that the TUI does not register but dispatches
315
+ * locally) and `/plan`. A plugin command contribution is validated against
316
+ * this fixed catalog at register time: an exact or near-synonym collision is
317
+ * rejected loudly, so a plugin can never shadow a built-in command. Names
318
+ * that only the CURRENT host catalog owns (session-scoped or dynamically
319
+ * registered commands) are not in this set — those collisions surface at
320
+ * candidate synthesis time instead.
275
321
  */
276
322
  declare const HOST_COMMAND_CATALOG: ReadonlySet<string>;
277
323
  /**
278
- * Whether one submission steers under the busy-Enter preference: NOT a
279
- * force-queued chord, NOT a TUI-owned local command, and the agent is
280
- * running with the preference set to 'steer'. Pure so the dispatch gate
281
- * (inside the runner closure) is testable headless.
324
+ * The TUI-local classification the attachment gate uses: a TUI/core local
325
+ * command or a live client command contribution is LOCAL (its line is a UI
326
+ * control — attachments are refused), while a LIVE skill wrapper is
327
+ * AGENT-FACING input (multimodal) even when a client contribution shares its
328
+ * name: the wrapper route outranks the contribution everywhere.
329
+ * @param name - the slash name.
330
+ * @param isSkillWrapper - the live skill-wrapper test (absent = none).
331
+ * @param isDynamicLocal - the live client-contribution test (absent = none).
332
+ * @param hostView - the host catalog's view of THIS LINE (`undefined` = the
333
+ * catalog does not resolve the name at all; see {@link HostCommandClaim}).
334
+ * A name the catalog RESOLVES is never a client-local line: when the
335
+ * catalog claims the line the host's own `input.attachments` declaration
336
+ * decides, and when it does not (an argued line of an execute-kind
337
+ * command) the line is an ordinary submission — never a same-named client
338
+ * contribution's.
339
+ */
340
+ declare function isLocalCommandLine(name: string, isSkillWrapper: ((name: string) => boolean) | undefined, isDynamicLocal: ((name: string) => boolean) | undefined, hostView?: HostCommandClaim | undefined): boolean;
341
+ /**
342
+ * Whether one parsed line is the BARE slash token (DSH `matchEnter`'s `bare`:
343
+ * no input follows the name — trailing whitespace is not input). It is the
344
+ * whole difference between a command invocation and an ordinary submission
345
+ * for the NAME-keyed client routes: a client command contribution claims the
346
+ * bare token only, exactly like an execute-kind host command.
347
+ * @param parsed - the parsed slash command.
348
+ * @returns whether the line carries no input after the command name.
349
+ */
350
+ declare function isBareCommandLine(parsed: {
351
+ name: string;
352
+ rawInput?: string;
353
+ }): boolean;
354
+ /**
355
+ * The attachment gate's local-command classification for ONE parsed line —
356
+ * the SINGLE classification the dispatch and its regression tests share, and
357
+ * the DSH client namespace order applied to a LINE:
358
+ * - a TUI/core local command is local;
359
+ * - `/skill <name> ...` and a LIVE skill wrapper are agent-facing
360
+ * (multimodal) even when a client contribution shares the name;
361
+ * - a name the HOST catalog RESOLVES is never local: when the catalog claims
362
+ * the line, the claiming descriptor's `input.attachments` declaration
363
+ * decides (`/goal <objective>` is claimed), and when it does not (an argued
364
+ * line of an execute-kind command, `/compact extra`) the line is an
365
+ * ordinary submission — never a command and never a same-named client
366
+ * contribution's;
367
+ * - everything else follows the live client contribution of that name — for
368
+ * the BARE token only: a contribution claims `/name`, so an argued line
369
+ * (`/deploy explain`) is an ordinary multimodal submission that keeps its
370
+ * attachments.
371
+ * @param parsed - the parsed slash command (undefined = plain prompt).
372
+ * @param isSkillWrapper - the live skill-wrapper test (absent = none).
373
+ * @param isDynamicLocal - the live client-contribution test (absent = none).
374
+ * @param hostClaim - the live HOST-catalog view of THIS LINE (absent = none).
375
+ * @returns whether the line is a local command line.
376
+ */
377
+ declare function commandIsLocalForAttachments(parsed: {
378
+ name: string;
379
+ rawInput?: string;
380
+ } | undefined, isSkillWrapper: ((name: string) => boolean) | undefined, isDynamicLocal: ((name: string) => boolean) | undefined, hostClaim?: ((parsed: {
381
+ name: string;
382
+ rawInput?: string;
383
+ }) => HostCommandClaim | undefined) | undefined): boolean;
384
+ /**
385
+ * The TUI dispatch boundary's delivery resolution: the WEB composer policy
386
+ * ({@link resolveComposerDelivery}) applied to agent-facing input, with the
387
+ * TUI's own ownership terms on top. Pure so the dispatch gate (inside the
388
+ * runner closure) is testable headless.
282
389
  * @param parsed - the parsed slash command, undefined for a plain prompt.
283
390
  * @param running - whether the live agent reports running.
391
+ * @param gesture - the composer gesture that raised the submission.
284
392
  * @param busyEnter - the persisted preference value (''/undefined = queue).
285
- * @param forceQueue - the Ctrl+Enter chord: always queue, never steer.
286
- * @param isDynamicLocal - M5: the CommandBridge's effective-local check for
287
- * plugin-declared local commands (absent = static set only).
288
393
  */
289
- declare function shouldSteerOnEnter(parsed: {
394
+ declare function resolveSubmitDelivery(parsed: {
290
395
  name: string;
291
396
  rawInput?: string;
292
- } | undefined, running: boolean, busyEnter: string | undefined, forceQueue: boolean, isDynamicLocal?: (name: string) => boolean): boolean;
397
+ } | undefined, running: boolean, gesture: ComposerSubmitGesture, busyEnter: string | undefined): SubmitDelivery;
293
398
  /**
294
399
  * Normalize an explicit `/skill <name> <args>` invocation to the skill's
295
400
  * own slash line `/<name> <args>` (review finding 2). The harness's
@@ -313,7 +418,10 @@ declare function normalizeSkillInvocation(text: string): string | undefined;
313
418
  * unadvertised miss keeps the existing plain-input fallback (the user may
314
419
  * deliberately send slash text to the model).
315
420
  * @param execution - the settled `commands.execute` outcome.
316
- * @param wasAdvertised - the claim captured BEFORE session creation.
421
+ * @param wasAdvertised - whether the submission was an advertised command
422
+ * INVOCATION: the submit-time name claim AND the command plane's final
423
+ * ownership of the line (an argued line of an execute-kind command never
424
+ * reached the plane, so it is an ordinary submission).
317
425
  * @returns whether the miss must be consumed as an advertised miss.
318
426
  */
319
427
  declare function shouldConsumeAdvertisedMiss(execution: {
@@ -584,17 +692,6 @@ declare function runningProfile(argv?: readonly string[], fallback?: string): st
584
692
  * @returns the resume command line, or undefined without a session.
585
693
  */
586
694
  declare function resumeCommand(profile: string, sessionId: string): string | undefined;
587
- /**
588
- * A launch-time open-lock refusal: the requested session is held by another
589
- * live dsh process (or its lock cannot be verified). Unlike a stale/missing
590
- * session id, this is NOT recoverable by falling back to a fresh session —
591
- * the user asked for a specific session and must resolve the holder first.
592
- * Thrown from the resume path and re-thrown past the resume catch so the
593
- * runner exits with the refusal as the error message.
594
- */
595
- declare class SessionLockRefusedError extends Error {
596
- constructor(message: string);
597
- }
598
695
  /** One fold of a compaction lifecycle event over the runner's in-flight
599
696
  * compaction state. Pure (the firehose applies the returned surface
600
697
  * effects): dsh-compaction is not a peer, so the event is read
@@ -680,6 +777,16 @@ interface AgentComposition {
680
777
  /** Preset id for the session header, absent when the deployment composes no roster. */
681
778
  agentPreset?: string;
682
779
  /** Agent-factory setup: model selection, then the preset mount when composed. */
780
+ setup: (agentCtx: Context, agent: Agent) => Promise<void> | void;
781
+ }
782
+ /**
783
+ * Source-compatible composition returned for standalone callers that provide
784
+ * their own ModelSelectionRef instead of an Agent-local installer.
785
+ */
786
+ interface LegacyAgentComposition {
787
+ /** Preset id for the session header, absent when the deployment composes no roster. */
788
+ agentPreset?: string;
789
+ /** Standalone setup installs the caller-owned model selection ref. */
683
790
  setup: (agentCtx: Context) => Promise<void> | void;
684
791
  }
685
792
  /**
@@ -696,9 +803,10 @@ interface AgentComposition {
696
803
  * host composition, which is the behavior before presets existed.
697
804
  * @param ctx - the runner context (services read through `ctx.get`).
698
805
  * @param installSelection - installs a fresh Agent-local model selection ref
699
- * during setup. A ModelSelectionRef is still accepted for source
700
- * compatibility with standalone composition callers; the runner always
701
- * supplies the Agent-local installer.
806
+ * during setup using the explicit Agent identity supplied by DSH. A
807
+ * ModelSelectionRef is also accepted for source compatibility with
808
+ * standalone composition callers; that branch installs the caller-owned ref
809
+ * and does not require an Agent.
702
810
  * @param presetId - the requested preset, or `undefined` for the default.
703
811
  * @param focusState - the shared Focus runtime state (STRUCTURAL on
704
812
  * purpose: the public declaration bundle must not inline src/focus.ts —
@@ -711,12 +819,15 @@ interface AgentComposition {
711
819
  * @returns the id to record on the header (absent without a roster) and the setup callback.
712
820
  * @throws when the roster supplies no such preset.
713
821
  */
714
- declare function composeAgent(ctx: Context, installSelection: ((agentCtx: Context) => void) | ModelSelectionRef, presetId?: string, focusState?: {
822
+ declare function composeAgent(ctx: Context, installSelection: ModelSelectionRef, presetId?: string, focusState?: {
823
+ enabled: boolean;
824
+ }, diag?: Diag): Promise<LegacyAgentComposition>;
825
+ declare function composeAgent(ctx: Context, installSelection: (agentCtx: Context, agent: Agent) => void, presetId?: string, focusState?: {
715
826
  enabled: boolean;
716
827
  }, diag?: Diag): Promise<AgentComposition>;
717
828
  /**
718
- * The preset a persisted session actually runs, resolved by the DSH 0.1.2+
719
- * session projection (header initialization plus the latest selection event).
829
+ * The preset a persisted session actually runs, read from DSH 0.1.5-rc.1's
830
+ * V3 session projection (header initialization plus the latest selection event).
720
831
  * @param ctx - the runner context.
721
832
  * @param sessionId - the persisted session id.
722
833
  * @returns the recorded preset id, or undefined to compose the default.
@@ -762,4 +873,4 @@ declare function recomposeBlank(ctx: Context, agent: {
762
873
  */
763
874
  declare function apply(ctx: Context, config: Config): void;
764
875
  //#endregion
765
- export { AgentComposition, CompactionFold, CompactionSettleSurface, Config, HOST_COMMAND_CATALOG, InitialCatalogResolution, InterruptAgentLike, InterruptWriterLike, LOCAL_COMMANDS, PendingSubagentCall, QueueFoldResult, QueueInboxMessage, QueueNoticeSource, RecomposableSession, RecomposeOutcome, ResolveInitialCatalogOptions, SESSIONLESS_COMMANDS, SessionLockRefusedError, ViewerOpenToken, apply, busyAfterTurnBoundary, commandRejectsImages, compactingFromLog, composeAgent, contextRefreshKind, createViewerOpenToken, dangerCommand, foldCompactionEvent, foldQueueRows, forkSeed, inject, interruptAgent, isPlainExitPrompt, isSubagentSettlementNotice, isUserQueueInput, matchPendingSubagentCall, name, normalizeSkillInvocation, recomposeBlank, recordedPreset, resolveInitialCatalog, resumeCommand, runningProfile, settleCompactionSurface, shouldConsumeAdvertisedMiss, shouldSteerOnEnter, subagentJobTranscriptId, subagentJobViewHint, subagentNoticeIsFailure, teardownViewerForSessionSwap, viewerActionCapability };
876
+ export { AgentComposition, CompactionFold, CompactionSettleSurface, Config, HOST_COMMAND_CATALOG, InitialCatalogResolution, InterruptAgentLike, InterruptWriterLike, LOCAL_COMMANDS, PendingSubagentCall, QueueFoldResult, QueueInboxMessage, QueueNoticeSource, RecomposableSession, RecomposeOutcome, ResolveInitialCatalogOptions, SESSIONLESS_COMMANDS, ViewerOpenToken, apply, busyAfterTurnBoundary, commandIsLocalForAttachments, commandRejectsImages, compactingFromLog, composeAgent, contextRefreshKind, createViewerOpenToken, dangerCommand, foldCompactionEvent, foldQueueRows, forkSeed, inject, interruptAgent, isBareCommandLine, isLocalCommandLine, isPlainExitPrompt, isSubagentSettlementNotice, isUserQueueInput, matchPendingSubagentCall, name, normalizeSkillInvocation, recomposeBlank, recordedPreset, resolveInitialCatalog, resolveSubmitDelivery, resumeCommand, runningProfile, settleCompactionSurface, shouldConsumeAdvertisedMiss, subagentJobTranscriptId, subagentJobViewHint, subagentNoticeIsFailure, teardownViewerForSessionSwap, viewerActionCapability };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { A as shouldConsumeAdvertisedMiss, C as normalizeSkillInvocation, D as resumeCommand, E as resolveInitialCatalog, F as teardownViewerForSessionSwap, I as viewerActionCapability, M as subagentJobTranscriptId, N as subagentJobViewHint, O as runningProfile, P as subagentNoticeIsFailure, R as forkSeed, S as name, T as recordedPreset, _ as interruptAgent, a as SessionLockRefusedError, b as isUserQueueInput, c as commandRejectsImages, d as contextRefreshKind, f as createViewerOpenToken, g as inject, h as foldQueueRows, i as SESSIONLESS_COMMANDS, j as shouldSteerOnEnter, k as settleCompactionSurface, l as compactingFromLog, m as foldCompactionEvent, n as HOST_COMMAND_CATALOG, o as apply, p as dangerCommand, r as LOCAL_COMMANDS, s as busyAfterTurnBoundary, t as Config, u as composeAgent, v as isPlainExitPrompt, w as recomposeBlank, x as matchPendingSubagentCall, y as isSubagentSettlementNotice } from "./src-CeGK2mHo.mjs";
1
+ import { A as resumeCommand, B as forkSeed, C as matchPendingSubagentCall, D as recordedPreset, E as recomposeBlank, F as subagentJobViewHint, I as subagentNoticeIsFailure, L as teardownViewerForSessionSwap, M as settleCompactionSurface, N as shouldConsumeAdvertisedMiss, O as resolveInitialCatalog, P as subagentJobTranscriptId, R as viewerActionCapability, S as isUserQueueInput, T as normalizeSkillInvocation, _ as interruptAgent, a as apply, b as isPlainExitPrompt, c as commandRejectsImages, d as contextRefreshKind, f as createViewerOpenToken, g as inject, h as foldQueueRows, i as SESSIONLESS_COMMANDS, j as runningProfile, k as resolveSubmitDelivery, l as compactingFromLog, m as foldCompactionEvent, n as HOST_COMMAND_CATALOG, o as busyAfterTurnBoundary, p as dangerCommand, r as LOCAL_COMMANDS, s as commandIsLocalForAttachments, t as Config, u as composeAgent, v as isBareCommandLine, w as name, x as isSubagentSettlementNotice, y as isLocalCommandLine } from "./src-B65x1oP2.mjs";
2
2
  import "./startup.mjs";
3
- export { Config, HOST_COMMAND_CATALOG, LOCAL_COMMANDS, SESSIONLESS_COMMANDS, SessionLockRefusedError, apply, busyAfterTurnBoundary, commandRejectsImages, compactingFromLog, composeAgent, contextRefreshKind, createViewerOpenToken, dangerCommand, foldCompactionEvent, foldQueueRows, forkSeed, inject, interruptAgent, isPlainExitPrompt, isSubagentSettlementNotice, isUserQueueInput, matchPendingSubagentCall, name, normalizeSkillInvocation, recomposeBlank, recordedPreset, resolveInitialCatalog, resumeCommand, runningProfile, settleCompactionSurface, shouldConsumeAdvertisedMiss, shouldSteerOnEnter, subagentJobTranscriptId, subagentJobViewHint, subagentNoticeIsFailure, teardownViewerForSessionSwap, viewerActionCapability };
3
+ export { Config, HOST_COMMAND_CATALOG, LOCAL_COMMANDS, SESSIONLESS_COMMANDS, apply, busyAfterTurnBoundary, commandIsLocalForAttachments, commandRejectsImages, compactingFromLog, composeAgent, contextRefreshKind, createViewerOpenToken, dangerCommand, foldCompactionEvent, foldQueueRows, forkSeed, inject, interruptAgent, isBareCommandLine, isLocalCommandLine, isPlainExitPrompt, isSubagentSettlementNotice, isUserQueueInput, matchPendingSubagentCall, name, normalizeSkillInvocation, recomposeBlank, recordedPreset, resolveInitialCatalog, resolveSubmitDelivery, resumeCommand, runningProfile, settleCompactionSurface, shouldConsumeAdvertisedMiss, subagentJobTranscriptId, subagentJobViewHint, subagentNoticeIsFailure, teardownViewerForSessionSwap, viewerActionCapability };
@@ -67,8 +67,16 @@ interface TuiAutocompleteQuery {
67
67
  interface TuiAutocompleteProvider {
68
68
  getSuggestions(query: TuiAutocompleteQuery): Promise<TuiAutocompleteSuggestions | null>;
69
69
  }
70
- /** API version of the extension surface (bumped only on breaking changes). */
71
- declare const API_VERSION: 1;
70
+ /**
71
+ * API version of the extension surface (bumped only on breaking changes).
72
+ * `2` is the client-command contribution contract: `execution` and the
73
+ * never-wired `argumentProvider` are REMOVED, `handler` is required, and a
74
+ * stale plugin still declaring the removed ownership shape is rejected at
75
+ * registration. `1` was the M0–M3 foundation. A plugin branching on
76
+ * `api().apiVersion` can tell the two schemas apart — the host never reports
77
+ * a version it does not implement.
78
+ */
79
+ declare const API_VERSION: 2;
72
80
  /** Capability identifiers, feature-detected via {@link PiTuiApiInfo}. */
73
81
  type PiTuiCapability = 'slot.chrome.header.badge' | 'slot.input.dock.item' | 'slot.chrome.footer.status' | 'slot.chrome.footer.item' | 'slot.input.widget' | 'surface.snapshot' | 'advanced.input.capture' | 'advanced.ui.interactive' | 'advanced.editor.control' | 'unstable.input.raw' | 'unstable.surface.handle';
74
82
  /** Slot identities this bundle knows; unknown names are rejected at registration. */
@@ -79,7 +87,8 @@ type PiTuiSlotSemantic = 'list' | 'single';
79
87
  type ExtensionTier = 'stable' | 'advanced' | 'unstable';
80
88
  /** What a plugin may know about the host (M1: version + capabilities only). */
81
89
  interface PiTuiApiInfo {
82
- /** The extension API version; 1 for the M0–M3 foundation. */
90
+ /** The extension API version: 2 for the client-command contribution
91
+ * contract, 1 for the M0–M3 foundation (see {@link API_VERSION}). */
83
92
  readonly apiVersion: typeof API_VERSION;
84
93
  /** The `@xmoon76/dsh-pi-tui` bundle version (semver string). */
85
94
  readonly hostVersion: string;
@@ -335,29 +344,40 @@ interface InputWidget {
335
344
  /** Preferred max rows (budget); the host may grant fewer. */
336
345
  readonly maxHeight?: number;
337
346
  }
338
- /** One command contribution (plan §10): ownership metadata over an
339
- * existing command. The bridge does NOT execute — the commands service
340
- * does. `/name args...` ALWAYS keeps `invocation.rawInput` verbatim. */
347
+ /** One CLIENT-OWNED command contribution (plan §10) — the DSH client
348
+ * command contribution shape: a slash name whose behavior lives entirely on
349
+ * the client (no host descriptor). It appears in the `/` menu merged with
350
+ * the host catalog and executes through its own `handler`, never steered.
351
+ * A contribution whose name is a host command FAILS LOUD at candidate
352
+ * synthesis and never shadows it (the host command keeps its claim).
353
+ * A contribution is a slash-MENU entry, so it claims the BARE `/name` token
354
+ * only (DSH `matchEnter`): `/name args...` is NOT an invocation — it is an
355
+ * ordinary submission that reaches the model, and `handler` never runs for
356
+ * it. */
341
357
  interface TuiCommandContribution {
342
358
  readonly id: string;
343
359
  /** The slash-command name WITHOUT the leading slash. */
344
360
  readonly name: string;
345
361
  readonly description: string;
346
- /** Execution ownership: local (never steered) vs submission (session
347
- * policy). Busy Enter classifies by the EFFECTIVE ownership. */
348
- readonly execution: 'local' | 'submission';
349
- /** Whether the command may run without a live session. */
362
+ /** Whether the command may run BEFORE a live session exists (default
363
+ * false). A sessionless contribution executes immediately; every other
364
+ * contribution resolves/creates the session first, then executes — the
365
+ * host command surface is session-keyed upstream, so this is an explicit
366
+ * TUI extension for pure client commands (an overlay toggle, a picker). */
350
367
  readonly sessionless?: boolean;
351
- /** Optional autocomplete provider for this command's arguments
352
- * (the structural {@link TuiAutocompleteProvider}). */
353
- readonly argumentProvider?: TuiAutocompleteProvider;
354
- /** Optional local handler; absent = metadata-only ownership (the
355
- * commands service handler runs). */
356
- readonly handler?: TuiLocalCommandHandler;
368
+ /** The command's client behavior (required): the TUI runs it locally for the
369
+ * BARE `/name` line, passing `invocation.rawInput` verbatim (it carries no
370
+ * non-whitespace input, since an argued line is never an invocation, though
371
+ * trailing whitespace may be preserved), with or without a live session
372
+ * according to {@link sessionless}. */
373
+ readonly handler: TuiLocalCommandHandler;
357
374
  }
358
375
  /** The local command handler signature (invocation carries the VERBATIM
359
- * raw input — never re-parsed or rewritten). Returns the commands
360
- * service's result shape (`{ kind: 'success' }` / `{ kind: 'error',
376
+ * raw input — never re-parsed or rewritten). A client command CONTRIBUTION is
377
+ * invoked by its bare token alone, so its `rawInput` never carries
378
+ * non-whitespace input (trailing whitespace is preserved verbatim); TUI-owned
379
+ * local commands (`/export json`) still receive their arguments here. Returns the
380
+ * commands service's result shape (`{ kind: 'success' }` / `{ kind: 'error',
361
381
  * text }`) so the runner's notify path is shared. */
362
382
  interface TuiLocalCommandHandler {
363
383
  (invocation: {
@@ -380,9 +400,13 @@ interface TuiLocalCommandHandler {
380
400
  readonly text: string;
381
401
  }>;
382
402
  }
383
- /** A live handle on one command contribution. */
403
+ /** A live handle on ONE command contribution REGISTRATION. */
384
404
  interface TuiCommandHandle {
385
405
  readonly id: string;
406
+ /** Dispose THIS registration (idempotent). A repeated or late call — e.g.
407
+ * a fiber disposer running after an HMR reload re-registered the same id —
408
+ * never removes a NEWER registration: the handle names one generation, not
409
+ * the id. */
386
410
  dispose(): void;
387
411
  }
388
412
  /** The command bridge's observable snapshot. */
@@ -391,9 +415,12 @@ interface TuiCommandBridgeSnapshot {
391
415
  readonly id: string;
392
416
  readonly name: string;
393
417
  readonly description: string;
394
- readonly execution: 'local' | 'submission';
395
418
  readonly sessionless: boolean;
396
419
  readonly owner: string;
420
+ /** Monotonic per-registration generation (diagnostics + the notice
421
+ * identity): a dispose + re-register under the same id/owner is a NEW
422
+ * generation, even when both happen inside one coalesced flush. */
423
+ readonly generation: number;
397
424
  }[];
398
425
  readonly revision: number;
399
426
  }