@dsh-cc/tui 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +144 -0
- package/lib/bash-history.d.ts +31 -0
- package/lib/bash-history.d.ts.map +1 -0
- package/lib/bash-history.js +94 -0
- package/lib/bash-history.js.map +1 -0
- package/lib/compact-fold.d.ts +35 -0
- package/lib/compact-fold.d.ts.map +1 -0
- package/lib/compact-fold.js +60 -0
- package/lib/compact-fold.js.map +1 -0
- package/lib/components/arg-completers.d.ts +22 -0
- package/lib/components/arg-completers.d.ts.map +1 -0
- package/lib/components/arg-completers.js +75 -0
- package/lib/components/arg-completers.js.map +1 -0
- package/lib/components/code-theme.d.ts +25 -0
- package/lib/components/code-theme.d.ts.map +1 -0
- package/lib/components/code-theme.js +137 -0
- package/lib/components/code-theme.js.map +1 -0
- package/lib/components/completion.d.ts +50 -0
- package/lib/components/completion.d.ts.map +1 -0
- package/lib/components/completion.js +279 -0
- package/lib/components/completion.js.map +1 -0
- package/lib/components/diff-card.d.ts +29 -0
- package/lib/components/diff-card.d.ts.map +1 -0
- package/lib/components/diff-card.js +175 -0
- package/lib/components/diff-card.js.map +1 -0
- package/lib/components/diff-hunks.d.ts +44 -0
- package/lib/components/diff-hunks.d.ts.map +1 -0
- package/lib/components/diff-hunks.js +188 -0
- package/lib/components/diff-hunks.js.map +1 -0
- package/lib/components/editor-history.d.ts +23 -0
- package/lib/components/editor-history.d.ts.map +1 -0
- package/lib/components/editor-history.js +29 -0
- package/lib/components/editor-history.js.map +1 -0
- package/lib/components/markdown-theme.d.ts +11 -0
- package/lib/components/markdown-theme.d.ts.map +1 -0
- package/lib/components/markdown-theme.js +29 -0
- package/lib/components/markdown-theme.js.map +1 -0
- package/lib/components/masked-input.d.ts +24 -0
- package/lib/components/masked-input.d.ts.map +1 -0
- package/lib/components/masked-input.js +168 -0
- package/lib/components/masked-input.js.map +1 -0
- package/lib/components/overlay-host.d.ts +31 -0
- package/lib/components/overlay-host.d.ts.map +1 -0
- package/lib/components/overlay-host.js +128 -0
- package/lib/components/overlay-host.js.map +1 -0
- package/lib/components/overlays.d.ts +94 -0
- package/lib/components/overlays.d.ts.map +1 -0
- package/lib/components/overlays.js +450 -0
- package/lib/components/overlays.js.map +1 -0
- package/lib/components/provider-box.d.ts +23 -0
- package/lib/components/provider-box.d.ts.map +1 -0
- package/lib/components/provider-box.js +164 -0
- package/lib/components/provider-box.js.map +1 -0
- package/lib/components/read-group.d.ts +51 -0
- package/lib/components/read-group.d.ts.map +1 -0
- package/lib/components/read-group.js +105 -0
- package/lib/components/read-group.js.map +1 -0
- package/lib/components/root-bash.d.ts +36 -0
- package/lib/components/root-bash.d.ts.map +1 -0
- package/lib/components/root-bash.js +112 -0
- package/lib/components/root-bash.js.map +1 -0
- package/lib/components/root-types.d.ts +42 -0
- package/lib/components/root-types.d.ts.map +1 -0
- package/lib/components/root-types.js +8 -0
- package/lib/components/root-types.js.map +1 -0
- package/lib/components/root-utils.d.ts +22 -0
- package/lib/components/root-utils.d.ts.map +1 -0
- package/lib/components/root-utils.js +45 -0
- package/lib/components/root-utils.js.map +1 -0
- package/lib/components/root.d.ts +25 -0
- package/lib/components/root.d.ts.map +1 -0
- package/lib/components/root.js +357 -0
- package/lib/components/root.js.map +1 -0
- package/lib/components/theme.d.ts +61 -0
- package/lib/components/theme.d.ts.map +1 -0
- package/lib/components/theme.js +99 -0
- package/lib/components/theme.js.map +1 -0
- package/lib/components/transcript.d.ts +43 -0
- package/lib/components/transcript.d.ts.map +1 -0
- package/lib/components/transcript.js +203 -0
- package/lib/components/transcript.js.map +1 -0
- package/lib/components/working-line.d.ts +35 -0
- package/lib/components/working-line.d.ts.map +1 -0
- package/lib/components/working-line.js +66 -0
- package/lib/components/working-line.js.map +1 -0
- package/lib/effort-catalog.d.ts +27 -0
- package/lib/effort-catalog.d.ts.map +1 -0
- package/lib/effort-catalog.js +36 -0
- package/lib/effort-catalog.js.map +1 -0
- package/lib/export-markdown.d.ts +18 -0
- package/lib/export-markdown.d.ts.map +1 -0
- package/lib/export-markdown.js +124 -0
- package/lib/export-markdown.js.map +1 -0
- package/lib/harness/approval-preview.d.ts +33 -0
- package/lib/harness/approval-preview.d.ts.map +1 -0
- package/lib/harness/approval-preview.js +179 -0
- package/lib/harness/approval-preview.js.map +1 -0
- package/lib/harness/boot-banner.d.ts +14 -0
- package/lib/harness/boot-banner.d.ts.map +1 -0
- package/lib/harness/boot-banner.js +89 -0
- package/lib/harness/boot-banner.js.map +1 -0
- package/lib/harness/driver-agent.d.ts +78 -0
- package/lib/harness/driver-agent.d.ts.map +1 -0
- package/lib/harness/driver-agent.js +314 -0
- package/lib/harness/driver-agent.js.map +1 -0
- package/lib/harness/driver-agents.d.ts +20 -0
- package/lib/harness/driver-agents.d.ts.map +1 -0
- package/lib/harness/driver-agents.js +123 -0
- package/lib/harness/driver-agents.js.map +1 -0
- package/lib/harness/driver-approvals.d.ts +38 -0
- package/lib/harness/driver-approvals.d.ts.map +1 -0
- package/lib/harness/driver-approvals.js +277 -0
- package/lib/harness/driver-approvals.js.map +1 -0
- package/lib/harness/driver-bash.d.ts +15 -0
- package/lib/harness/driver-bash.d.ts.map +1 -0
- package/lib/harness/driver-bash.js +73 -0
- package/lib/harness/driver-bash.js.map +1 -0
- package/lib/harness/driver-catalog.d.ts +84 -0
- package/lib/harness/driver-catalog.d.ts.map +1 -0
- package/lib/harness/driver-catalog.js +256 -0
- package/lib/harness/driver-catalog.js.map +1 -0
- package/lib/harness/driver-ctx.d.ts +462 -0
- package/lib/harness/driver-ctx.d.ts.map +1 -0
- package/lib/harness/driver-ctx.js +11 -0
- package/lib/harness/driver-ctx.js.map +1 -0
- package/lib/harness/driver-hud.d.ts +31 -0
- package/lib/harness/driver-hud.d.ts.map +1 -0
- package/lib/harness/driver-hud.js +204 -0
- package/lib/harness/driver-hud.js.map +1 -0
- package/lib/harness/driver-live.d.ts +36 -0
- package/lib/harness/driver-live.d.ts.map +1 -0
- package/lib/harness/driver-live.js +44 -0
- package/lib/harness/driver-live.js.map +1 -0
- package/lib/harness/driver-modal.d.ts +52 -0
- package/lib/harness/driver-modal.d.ts.map +1 -0
- package/lib/harness/driver-modal.js +78 -0
- package/lib/harness/driver-modal.js.map +1 -0
- package/lib/harness/driver-mode.d.ts +35 -0
- package/lib/harness/driver-mode.d.ts.map +1 -0
- package/lib/harness/driver-mode.js +84 -0
- package/lib/harness/driver-mode.js.map +1 -0
- package/lib/harness/driver-pickers.d.ts +48 -0
- package/lib/harness/driver-pickers.d.ts.map +1 -0
- package/lib/harness/driver-pickers.js +249 -0
- package/lib/harness/driver-pickers.js.map +1 -0
- package/lib/harness/driver-promote.d.ts +29 -0
- package/lib/harness/driver-promote.d.ts.map +1 -0
- package/lib/harness/driver-promote.js +36 -0
- package/lib/harness/driver-promote.js.map +1 -0
- package/lib/harness/driver-queue.d.ts +19 -0
- package/lib/harness/driver-queue.d.ts.map +1 -0
- package/lib/harness/driver-queue.js +281 -0
- package/lib/harness/driver-queue.js.map +1 -0
- package/lib/harness/driver-run-local.d.ts +32 -0
- package/lib/harness/driver-run-local.d.ts.map +1 -0
- package/lib/harness/driver-run-local.js +366 -0
- package/lib/harness/driver-run-local.js.map +1 -0
- package/lib/harness/driver-sessions.d.ts +28 -0
- package/lib/harness/driver-sessions.d.ts.map +1 -0
- package/lib/harness/driver-sessions.js +386 -0
- package/lib/harness/driver-sessions.js.map +1 -0
- package/lib/harness/driver.d.ts +22 -0
- package/lib/harness/driver.d.ts.map +1 -0
- package/lib/harness/driver.js +459 -0
- package/lib/harness/driver.js.map +1 -0
- package/lib/harness/preset.d.ts +32 -0
- package/lib/harness/preset.d.ts.map +1 -0
- package/lib/harness/preset.js +34 -0
- package/lib/harness/preset.js.map +1 -0
- package/lib/harness/session-list.d.ts +65 -0
- package/lib/harness/session-list.d.ts.map +1 -0
- package/lib/harness/session-list.js +91 -0
- package/lib/harness/session-list.js.map +1 -0
- package/lib/harness/shell-output.d.ts +44 -0
- package/lib/harness/shell-output.d.ts.map +1 -0
- package/lib/harness/shell-output.js +90 -0
- package/lib/harness/shell-output.js.map +1 -0
- package/lib/harness/statusline-cc-transcript.d.ts +51 -0
- package/lib/harness/statusline-cc-transcript.d.ts.map +1 -0
- package/lib/harness/statusline-cc-transcript.js +191 -0
- package/lib/harness/statusline-cc-transcript.js.map +1 -0
- package/lib/harness/statusline-command.d.ts +54 -0
- package/lib/harness/statusline-command.d.ts.map +1 -0
- package/lib/harness/statusline-command.js +161 -0
- package/lib/harness/statusline-command.js.map +1 -0
- package/lib/harness/statusline-payload.d.ts +79 -0
- package/lib/harness/statusline-payload.d.ts.map +1 -0
- package/lib/harness/statusline-payload.js +93 -0
- package/lib/harness/statusline-payload.js.map +1 -0
- package/lib/harness/statusline-settings.d.ts +47 -0
- package/lib/harness/statusline-settings.d.ts.map +1 -0
- package/lib/harness/statusline-settings.js +57 -0
- package/lib/harness/statusline-settings.js.map +1 -0
- package/lib/harness/statusline-wiring.d.ts +45 -0
- package/lib/harness/statusline-wiring.d.ts.map +1 -0
- package/lib/harness/statusline-wiring.js +247 -0
- package/lib/harness/statusline-wiring.js.map +1 -0
- package/lib/harness/usage-view.d.ts +84 -0
- package/lib/harness/usage-view.d.ts.map +1 -0
- package/lib/harness/usage-view.js +199 -0
- package/lib/harness/usage-view.js.map +1 -0
- package/lib/harness/worktree-exit.d.ts +105 -0
- package/lib/harness/worktree-exit.d.ts.map +1 -0
- package/lib/harness/worktree-exit.js +168 -0
- package/lib/harness/worktree-exit.js.map +1 -0
- package/lib/history.d.ts +50 -0
- package/lib/history.d.ts.map +1 -0
- package/lib/history.js +139 -0
- package/lib/history.js.map +1 -0
- package/lib/index.d.ts +63 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +54 -0
- package/lib/index.js.map +1 -0
- package/lib/input.d.ts +158 -0
- package/lib/input.d.ts.map +1 -0
- package/lib/input.js +400 -0
- package/lib/input.js.map +1 -0
- package/lib/invariant.d.ts +14 -0
- package/lib/invariant.d.ts.map +1 -0
- package/lib/invariant.js +20 -0
- package/lib/invariant.js.map +1 -0
- package/lib/mode-cycle.d.ts +22 -0
- package/lib/mode-cycle.d.ts.map +1 -0
- package/lib/mode-cycle.js +32 -0
- package/lib/mode-cycle.js.map +1 -0
- package/lib/model-catalog.d.ts +22 -0
- package/lib/model-catalog.d.ts.map +1 -0
- package/lib/model-catalog.js +45 -0
- package/lib/model-catalog.js.map +1 -0
- package/lib/packaged-preset.d.ts +32 -0
- package/lib/packaged-preset.d.ts.map +1 -0
- package/lib/packaged-preset.js +94 -0
- package/lib/packaged-preset.js.map +1 -0
- package/lib/plugin.d.ts +11 -0
- package/lib/plugin.d.ts.map +1 -0
- package/lib/plugin.js +88 -0
- package/lib/plugin.js.map +1 -0
- package/lib/profile.d.ts +4 -0
- package/lib/profile.d.ts.map +1 -0
- package/lib/profile.js +7 -0
- package/lib/profile.js.map +1 -0
- package/lib/project-sessions.d.ts +29 -0
- package/lib/project-sessions.d.ts.map +1 -0
- package/lib/project-sessions.js +60 -0
- package/lib/project-sessions.js.map +1 -0
- package/lib/project.d.ts +75 -0
- package/lib/project.d.ts.map +1 -0
- package/lib/project.js +196 -0
- package/lib/project.js.map +1 -0
- package/lib/provider-actions.d.ts +37 -0
- package/lib/provider-actions.d.ts.map +1 -0
- package/lib/provider-actions.js +417 -0
- package/lib/provider-actions.js.map +1 -0
- package/lib/provider-command.d.ts +109 -0
- package/lib/provider-command.d.ts.map +1 -0
- package/lib/provider-command.js +220 -0
- package/lib/provider-command.js.map +1 -0
- package/lib/provider-detail.d.ts +15 -0
- package/lib/provider-detail.d.ts.map +1 -0
- package/lib/provider-detail.js +201 -0
- package/lib/provider-detail.js.map +1 -0
- package/lib/provider-flow.d.ts +191 -0
- package/lib/provider-flow.d.ts.map +1 -0
- package/lib/provider-flow.js +192 -0
- package/lib/provider-flow.js.map +1 -0
- package/lib/provider-presets.d.ts +32 -0
- package/lib/provider-presets.d.ts.map +1 -0
- package/lib/provider-presets.js +105 -0
- package/lib/provider-presets.js.map +1 -0
- package/lib/provider-read.d.ts +63 -0
- package/lib/provider-read.d.ts.map +1 -0
- package/lib/provider-read.js +115 -0
- package/lib/provider-read.js.map +1 -0
- package/lib/provider-settings.d.ts +30 -0
- package/lib/provider-settings.d.ts.map +1 -0
- package/lib/provider-settings.js +65 -0
- package/lib/provider-settings.js.map +1 -0
- package/lib/resume-target.d.ts +59 -0
- package/lib/resume-target.d.ts.map +1 -0
- package/lib/resume-target.js +143 -0
- package/lib/resume-target.js.map +1 -0
- package/lib/slash.d.ts +44 -0
- package/lib/slash.d.ts.map +1 -0
- package/lib/slash.js +81 -0
- package/lib/slash.js.map +1 -0
- package/lib/state/driver-types.d.ts +505 -0
- package/lib/state/driver-types.d.ts.map +1 -0
- package/lib/state/driver-types.js +8 -0
- package/lib/state/driver-types.js.map +1 -0
- package/lib/state/driver-usage-types.d.ts +59 -0
- package/lib/state/driver-usage-types.d.ts.map +1 -0
- package/lib/state/driver-usage-types.js +8 -0
- package/lib/state/driver-usage-types.js.map +1 -0
- package/lib/statusline.d.ts +63 -0
- package/lib/statusline.d.ts.map +1 -0
- package/lib/statusline.js +97 -0
- package/lib/statusline.js.map +1 -0
- package/lib/store/pickers.d.ts +34 -0
- package/lib/store/pickers.d.ts.map +1 -0
- package/lib/store/pickers.js +111 -0
- package/lib/store/pickers.js.map +1 -0
- package/lib/store/provider-panel.d.ts +107 -0
- package/lib/store/provider-panel.d.ts.map +1 -0
- package/lib/store/provider-panel.js +151 -0
- package/lib/store/provider-panel.js.map +1 -0
- package/lib/store/question.d.ts +21 -0
- package/lib/store/question.d.ts.map +1 -0
- package/lib/store/question.js +59 -0
- package/lib/store/question.js.map +1 -0
- package/lib/store/queue.d.ts +27 -0
- package/lib/store/queue.d.ts.map +1 -0
- package/lib/store/queue.js +34 -0
- package/lib/store/queue.js.map +1 -0
- package/lib/store/session.d.ts +73 -0
- package/lib/store/session.d.ts.map +1 -0
- package/lib/store/session.js +172 -0
- package/lib/store/session.js.map +1 -0
- package/lib/store/subagents.d.ts +16 -0
- package/lib/store/subagents.d.ts.map +1 -0
- package/lib/store/subagents.js +44 -0
- package/lib/store/subagents.js.map +1 -0
- package/lib/store/todos.d.ts +35 -0
- package/lib/store/todos.d.ts.map +1 -0
- package/lib/store/todos.js +68 -0
- package/lib/store/todos.js.map +1 -0
- package/lib/store/usage.d.ts +15 -0
- package/lib/store/usage.d.ts.map +1 -0
- package/lib/store/usage.js +27 -0
- package/lib/store/usage.js.map +1 -0
- package/lib/store/views.d.ts +419 -0
- package/lib/store/views.d.ts.map +1 -0
- package/lib/store/views.js +3 -0
- package/lib/store/views.js.map +1 -0
- package/lib/store.d.ts +19 -0
- package/lib/store.d.ts.map +1 -0
- package/lib/store.js +10 -0
- package/lib/store.js.map +1 -0
- package/lib/terminal/lease.d.ts +33 -0
- package/lib/terminal/lease.d.ts.map +1 -0
- package/lib/terminal/lease.js +99 -0
- package/lib/terminal/lease.js.map +1 -0
- package/lib/tool-card.d.ts +75 -0
- package/lib/tool-card.d.ts.map +1 -0
- package/lib/tool-card.js +109 -0
- package/lib/tool-card.js.map +1 -0
- package/lib/tool-verbs.d.ts +12 -0
- package/lib/tool-verbs.d.ts.map +1 -0
- package/lib/tool-verbs.js +29 -0
- package/lib/tool-verbs.js.map +1 -0
- package/lib/transcript.d.ts +40 -0
- package/lib/transcript.d.ts.map +1 -0
- package/lib/transcript.js +413 -0
- package/lib/transcript.js.map +1 -0
- package/lib/working-line.d.ts +44 -0
- package/lib/working-line.d.ts.map +1 -0
- package/lib/working-line.js +56 -0
- package/lib/working-line.js.map +1 -0
- package/package.json +72 -0
- package/presets/cc/.dsh-cc-managed.json +5 -0
- package/presets/cc/agent.cordis.yml +524 -0
- package/presets/cc/preset.yml +3 -0
- package/scripts/stage-preset.mjs +26 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model / effort / permission pickers for the in-process driver. Verbatim
|
|
3
|
+
* extraction from harness/driver.ts (openModelPicker / applyModelSwitch /
|
|
4
|
+
* openEffortPicker / openPermissionPicker plus the return-literal picker
|
|
5
|
+
* bodies), reading shared state through a DriverPickersCtx instead of
|
|
6
|
+
* createDriver's closures.
|
|
7
|
+
*
|
|
8
|
+
* The pickers are stateless overlays: each open parks picker state and each
|
|
9
|
+
* submit reads-then-closes before validating. The bare model switch writes
|
|
10
|
+
* synchronously; a carried effort validates detached via resolveEfforts /
|
|
11
|
+
* stalePair, kept in createDriver and passed in.
|
|
12
|
+
* @module @dsh-cc/tui/harness/driver-pickers
|
|
13
|
+
*/
|
|
14
|
+
import { ReasoningEffortId } from '@deepseek-ai/dsh-llm';
|
|
15
|
+
import { BYPASS_MODE, PERMISSION_MODE_OPTIONS } from '@dsh-cc/command-permissions';
|
|
16
|
+
import { moveEffortPickerFocus, moveModelPickerFocus, movePermissionPickerFocus, setEffortPicker, setModelPicker, setPermissionPicker, upsertRow, } from "../store.js";
|
|
17
|
+
import { formatModelCatalog } from "../model-catalog.js";
|
|
18
|
+
/**
|
|
19
|
+
* Free-function collaborator implementing the three pickers. Reads all shared
|
|
20
|
+
* state/functions off `rt` (DriverPickersCtx) so it never imports the
|
|
21
|
+
* createDriver factory. `selection` is passed by reference so writes land on
|
|
22
|
+
* createDriver's live ref across switchSession; resolveEfforts / stalePair /
|
|
23
|
+
* loadCatalog stay in createDriver and arrive on the ctx.
|
|
24
|
+
*/
|
|
25
|
+
export function createPickersSection(rt) {
|
|
26
|
+
// `/model` (no args) opens a modal picker instead of dumping a text catalog.
|
|
27
|
+
// The arg path (`/model <n|provider/id>`) stays text-based for scripts.
|
|
28
|
+
const openModelPicker = async () => {
|
|
29
|
+
const catalog = await rt.loadCatalog();
|
|
30
|
+
const currentRoute = rt.selection.current === undefined
|
|
31
|
+
? undefined
|
|
32
|
+
: { provider: rt.selection.current.provider, model: rt.selection.current.model };
|
|
33
|
+
if (catalog.length === 0) {
|
|
34
|
+
rt.emit(upsertRow(rt.state(), { kind: 'status', text: formatModelCatalog(catalog, currentRoute) }));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const entries = catalog.map(entry => ({
|
|
38
|
+
provider: entry.provider,
|
|
39
|
+
id: entry.id,
|
|
40
|
+
name: entry.name,
|
|
41
|
+
}));
|
|
42
|
+
let focused = 0;
|
|
43
|
+
if (currentRoute !== undefined) {
|
|
44
|
+
const index = entries.findIndex(entry => entry.provider === currentRoute.provider && entry.id === currentRoute.model);
|
|
45
|
+
if (index >= 0)
|
|
46
|
+
focused = index;
|
|
47
|
+
}
|
|
48
|
+
rt.emit(setModelPicker(rt.state(), {
|
|
49
|
+
entries,
|
|
50
|
+
focused,
|
|
51
|
+
...currentRoute === undefined ? {} : { current: currentRoute },
|
|
52
|
+
}));
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Apply a `/model` switch to `provider`/`model`, carrying the live effort
|
|
56
|
+
* when the new model still supports it. The bare pair needs no validation,
|
|
57
|
+
* so the no-carried-effort path writes synchronously in the caller's tick;
|
|
58
|
+
* the carried path validates via rt.resolveEfforts and degrades to a
|
|
59
|
+
* bare pair + reset notice when the effort is unsupported OR unresolvable —
|
|
60
|
+
* the switch itself never fails on validation. The stale-pair guard covers
|
|
61
|
+
* the detached continuation: a selection that moved while validation was in
|
|
62
|
+
* flight is never clobbered.
|
|
63
|
+
*/
|
|
64
|
+
const applyModelSwitch = async (provider, model) => {
|
|
65
|
+
const captured = rt.selection.current;
|
|
66
|
+
const carried = captured?.reasoningEffort;
|
|
67
|
+
if (captured === undefined || carried === undefined) {
|
|
68
|
+
rt.selection.current = { provider, model };
|
|
69
|
+
rt.emit(upsertRow(rt.state(), { kind: 'status', text: `Model is now ${provider}/${model}.` }));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const efforts = await rt.resolveEfforts(provider, model);
|
|
73
|
+
if (rt.stalePair(captured))
|
|
74
|
+
return;
|
|
75
|
+
const supported = efforts?.some(level => level.id === carried) === true;
|
|
76
|
+
rt.selection.current = supported
|
|
77
|
+
? { provider, model, reasoningEffort: ReasoningEffortId(carried) }
|
|
78
|
+
: { provider, model };
|
|
79
|
+
rt.emit(upsertRow(rt.state(), { kind: 'status', text: `Model is now ${provider}/${model}.` }));
|
|
80
|
+
if (!supported) {
|
|
81
|
+
rt.emit(upsertRow(rt.state(), {
|
|
82
|
+
kind: 'status',
|
|
83
|
+
text: `Effort "${carried}" not supported by ${model}; reset to default.`,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Open the `/effort` picker: resolve the live model's advertised efforts
|
|
89
|
+
* and park `effortPicker` state — entries are the effort ids plus the
|
|
90
|
+
* trailing reserved `default` entry, focus on the live effort (the
|
|
91
|
+
* `default` entry when none is set or it is no longer in the list). Fail
|
|
92
|
+
* closed: an unresolved model emits the no-model notice and unresolvable
|
|
93
|
+
* levels emit a notice — never a fabricated list.
|
|
94
|
+
*/
|
|
95
|
+
const openEffortPicker = async () => {
|
|
96
|
+
const route = rt.selection.current;
|
|
97
|
+
if (route === undefined) {
|
|
98
|
+
rt.emit(upsertRow(rt.state(), { kind: 'status', text: 'No model configured. Use /model first.' }));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const efforts = await rt.resolveEfforts(route.provider, route.model);
|
|
102
|
+
if (efforts === undefined || efforts.length === 0) {
|
|
103
|
+
rt.emit(upsertRow(rt.state(), { kind: 'status', text: `Cannot resolve effort levels for ${route.model}.` }));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const entries = [...efforts.map(level => level.id), 'default'];
|
|
107
|
+
const index = route.reasoningEffort === undefined ? -1 : entries.indexOf(route.reasoningEffort);
|
|
108
|
+
rt.emit(setEffortPicker(rt.state(), {
|
|
109
|
+
entries,
|
|
110
|
+
focused: index >= 0 ? index : entries.length - 1,
|
|
111
|
+
current: route.reasoningEffort,
|
|
112
|
+
}));
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Open the `/permissions` picker: park the five CC rule-engine modes,
|
|
116
|
+
* focused on the live mode (row 0 when the live mode is not in the list).
|
|
117
|
+
* The overlay always opens — an unmounted engine surfaces as a host-command
|
|
118
|
+
* error on submit, matching the argued `/permissions <mode>` path.
|
|
119
|
+
*/
|
|
120
|
+
const openPermissionPicker = () => {
|
|
121
|
+
const currentMode = rt.liveMode(rt.current.agent);
|
|
122
|
+
const index = PERMISSION_MODE_OPTIONS.findIndex(option => option.id === currentMode);
|
|
123
|
+
rt.emit(setPermissionPicker(rt.state(), {
|
|
124
|
+
entries: PERMISSION_MODE_OPTIONS,
|
|
125
|
+
focused: index >= 0 ? index : 0,
|
|
126
|
+
current: currentMode,
|
|
127
|
+
}));
|
|
128
|
+
};
|
|
129
|
+
return {
|
|
130
|
+
openModelPicker,
|
|
131
|
+
applyModelSwitch,
|
|
132
|
+
openEffortPicker,
|
|
133
|
+
openPermissionPicker,
|
|
134
|
+
modelPickerMove(delta) {
|
|
135
|
+
rt.emit(moveModelPickerFocus(rt.state(), delta));
|
|
136
|
+
},
|
|
137
|
+
modelPickerSubmit() {
|
|
138
|
+
const picker = rt.state().modelPicker;
|
|
139
|
+
if (picker === undefined)
|
|
140
|
+
return Promise.resolve();
|
|
141
|
+
const entry = picker.entries[picker.focused];
|
|
142
|
+
// Read-then-close: capture the focused entry BEFORE the synchronous
|
|
143
|
+
// close-emit so the overlay never lingers while validation runs.
|
|
144
|
+
rt.emit(setModelPicker(rt.state(), undefined));
|
|
145
|
+
if (entry === undefined)
|
|
146
|
+
return Promise.resolve();
|
|
147
|
+
// Effort-preserving switch with the stale-pair guard inside; the bare
|
|
148
|
+
// fast path writes synchronously, a carried effort continues detached.
|
|
149
|
+
return applyModelSwitch(entry.provider, entry.id);
|
|
150
|
+
},
|
|
151
|
+
modelPickerCancel() {
|
|
152
|
+
rt.emit(setModelPicker(rt.state(), undefined));
|
|
153
|
+
},
|
|
154
|
+
effortPickerMove(delta) {
|
|
155
|
+
rt.emit(moveEffortPickerFocus(rt.state(), delta));
|
|
156
|
+
},
|
|
157
|
+
async effortPickerSubmit() {
|
|
158
|
+
const picker = rt.state().effortPicker;
|
|
159
|
+
if (picker === undefined)
|
|
160
|
+
return;
|
|
161
|
+
const entry = picker.entries[picker.focused];
|
|
162
|
+
// Read-then-close (mirror modelPickerSubmit): capture the focused entry
|
|
163
|
+
// BEFORE the synchronous close-emit, then validate+write detached.
|
|
164
|
+
rt.emit(setEffortPicker(rt.state(), undefined));
|
|
165
|
+
if (entry === undefined)
|
|
166
|
+
return;
|
|
167
|
+
const captured = rt.selection.current;
|
|
168
|
+
if (captured === undefined) {
|
|
169
|
+
rt.emit(upsertRow(rt.state(), { kind: 'status', text: 'No model configured. Use /model first.' }));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// The reserved `default` entry resets to the bare pair with zero
|
|
173
|
+
// validation (the provider default is always legal); the stale-pair
|
|
174
|
+
// guard still applies.
|
|
175
|
+
if (entry === 'default') {
|
|
176
|
+
if (rt.stalePair(captured))
|
|
177
|
+
return;
|
|
178
|
+
rt.selection.current = { provider: captured.provider, model: captured.model };
|
|
179
|
+
rt.emit(upsertRow(rt.state(), { kind: 'status', text: 'Reasoning effort reset to the provider default.' }));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const efforts = await rt.resolveEfforts(captured.provider, captured.model);
|
|
183
|
+
// Stale-pair guard: the captured model must still be the live selection
|
|
184
|
+
// when the validation continuation resumes — a concurrent /model or
|
|
185
|
+
// switchSession re-seed in between refuses the write.
|
|
186
|
+
if (rt.stalePair(captured))
|
|
187
|
+
return;
|
|
188
|
+
const level = efforts?.find(candidate => candidate.id === entry);
|
|
189
|
+
if (level === undefined) {
|
|
190
|
+
rt.emit(upsertRow(rt.state(), { kind: 'status', text: `Cannot resolve effort levels for ${captured.model}.` }));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
rt.selection.current = {
|
|
194
|
+
provider: captured.provider,
|
|
195
|
+
model: captured.model,
|
|
196
|
+
reasoningEffort: ReasoningEffortId(level.id),
|
|
197
|
+
};
|
|
198
|
+
rt.emit(upsertRow(rt.state(), { kind: 'status', text: `Reasoning effort is now ${level.name}.` }));
|
|
199
|
+
},
|
|
200
|
+
effortPickerCancel() {
|
|
201
|
+
rt.emit(setEffortPicker(rt.state(), undefined));
|
|
202
|
+
},
|
|
203
|
+
permissionPickerMove(delta) {
|
|
204
|
+
rt.emit(movePermissionPickerFocus(rt.state(), delta));
|
|
205
|
+
},
|
|
206
|
+
async permissionPickerSubmit() {
|
|
207
|
+
const picker = rt.state().permissionPicker;
|
|
208
|
+
if (picker === undefined)
|
|
209
|
+
return;
|
|
210
|
+
const entry = picker.entries[picker.focused];
|
|
211
|
+
if (entry === undefined) {
|
|
212
|
+
rt.emit(setPermissionPicker(rt.state(), undefined));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
// bypassPermissions parks an in-overlay confirmation first; a second
|
|
216
|
+
// enter (or any other mode) closes then writes through the host command.
|
|
217
|
+
if (entry.id === BYPASS_MODE && picker.confirmingBypass !== true) {
|
|
218
|
+
rt.emit(setPermissionPicker(rt.state(), { ...picker, confirmingBypass: true }));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
rt.emit(setPermissionPicker(rt.state(), undefined));
|
|
222
|
+
await rt.runHarness(`/permissions ${entry.id}`);
|
|
223
|
+
},
|
|
224
|
+
permissionPickerCancel() {
|
|
225
|
+
const picker = rt.state().permissionPicker;
|
|
226
|
+
if (picker === undefined)
|
|
227
|
+
return;
|
|
228
|
+
if (picker.confirmingBypass === true) {
|
|
229
|
+
const { confirmingBypass: _dropped, ...rest } = picker;
|
|
230
|
+
rt.emit(setPermissionPicker(rt.state(), rest));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
rt.emit(setPermissionPicker(rt.state(), undefined));
|
|
234
|
+
},
|
|
235
|
+
async loadModelCatalog() {
|
|
236
|
+
return rt.loadCatalog();
|
|
237
|
+
},
|
|
238
|
+
async loadModelEfforts() {
|
|
239
|
+
const route = rt.selection.current;
|
|
240
|
+
if (route === undefined)
|
|
241
|
+
return [];
|
|
242
|
+
const efforts = await rt.resolveEfforts(route.provider, route.model);
|
|
243
|
+
if (efforts === undefined)
|
|
244
|
+
return [];
|
|
245
|
+
return [...efforts.map(level => level.id), 'default'];
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=driver-pickers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driver-pickers.js","sourceRoot":"","sources":["../../src/harness/driver-pickers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ctrl+B promotion section (UX plan §3.4): promote every armed foreground
|
|
3
|
+
* subagent collect of the CURRENT session to background. Structural split of
|
|
4
|
+
* the same logic from `./driver.ts`.
|
|
5
|
+
* @module @dsh-cc/tui/harness/driver-promote
|
|
6
|
+
*/
|
|
7
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
8
|
+
/**
|
|
9
|
+
* Create the promote handler for one driver instance. The collect path (Task
|
|
10
|
+
* tool) registers each in-flight foreground collect under parentSessionId +
|
|
11
|
+
* toolCallToken in the root-realm `ccCollectorRegistry` (published by the
|
|
12
|
+
* cc-subagent-task plugin, CcPlugins pattern); `promote()` on a handle
|
|
13
|
+
* releases the wait (`async_launched` + `backgroundedByUser: true` tool
|
|
14
|
+
* result) and leaves the child untouched — its settlement notice later
|
|
15
|
+
* delivers normally. Gated by the env kill switch; an absent registry or
|
|
16
|
+
* session change degrades to 0 (nothing promotable).
|
|
17
|
+
*
|
|
18
|
+
* The session filter compares against the LIVE agent's session id via the
|
|
19
|
+
* `current` holder, so a switch after construction promotes the new session's
|
|
20
|
+
* collects, exactly like the previous inline closure.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createPromoteSection(ctx: Context, current: {
|
|
23
|
+
agent: {
|
|
24
|
+
session: {
|
|
25
|
+
id: unknown;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
}): () => number;
|
|
29
|
+
//# sourceMappingURL=driver-promote.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driver-promote.d.ts","sourceRoot":"","sources":["../../src/harness/driver-promote.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAGlD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE;IAAE,KAAK,EAAE;QAAE,OAAO,EAAE;YAAE,EAAE,EAAE,OAAO,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,GAC/C,MAAM,MAAM,CAYd"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ctrl+B promotion section (UX plan §3.4): promote every armed foreground
|
|
3
|
+
* subagent collect of the CURRENT session to background. Structural split of
|
|
4
|
+
* the same logic from `./driver.ts`.
|
|
5
|
+
* @module @dsh-cc/tui/harness/driver-promote
|
|
6
|
+
*/
|
|
7
|
+
import { backgroundTasksDisabled } from '@dsh-cc/subagent-task';
|
|
8
|
+
/**
|
|
9
|
+
* Create the promote handler for one driver instance. The collect path (Task
|
|
10
|
+
* tool) registers each in-flight foreground collect under parentSessionId +
|
|
11
|
+
* toolCallToken in the root-realm `ccCollectorRegistry` (published by the
|
|
12
|
+
* cc-subagent-task plugin, CcPlugins pattern); `promote()` on a handle
|
|
13
|
+
* releases the wait (`async_launched` + `backgroundedByUser: true` tool
|
|
14
|
+
* result) and leaves the child untouched — its settlement notice later
|
|
15
|
+
* delivers normally. Gated by the env kill switch; an absent registry or
|
|
16
|
+
* session change degrades to 0 (nothing promotable).
|
|
17
|
+
*
|
|
18
|
+
* The session filter compares against the LIVE agent's session id via the
|
|
19
|
+
* `current` holder, so a switch after construction promotes the new session's
|
|
20
|
+
* collects, exactly like the previous inline closure.
|
|
21
|
+
*/
|
|
22
|
+
export function createPromoteSection(ctx, current) {
|
|
23
|
+
const promoteForegroundCollects = () => {
|
|
24
|
+
if (backgroundTasksDisabled())
|
|
25
|
+
return 0;
|
|
26
|
+
const registry = ctx.get('ccCollectorRegistry');
|
|
27
|
+
if (registry?.collectorsForSession === undefined)
|
|
28
|
+
return 0;
|
|
29
|
+
const armed = registry.collectorsForSession(String(current.agent.session.id));
|
|
30
|
+
for (const handle of armed)
|
|
31
|
+
handle.promote();
|
|
32
|
+
return armed.length;
|
|
33
|
+
};
|
|
34
|
+
return promoteForegroundCollects;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=driver-promote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driver-promote.js","sourceRoot":"","sources":["../../src/harness/driver-promote.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AAE/D;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,oBAAoB,CAClC,GAAY,EACZ,OAAgD;IAEhD,MAAM,yBAAyB,GAAG,GAAW,EAAE;QAC7C,IAAI,uBAAuB,EAAE;YAAE,OAAO,CAAC,CAAA;QACvC,MAAM,QAAQ,GAAI,GAAG,CAAC,GAAG,CAAC,qBAAqB,CAEjC,CAAA;QACd,IAAI,QAAQ,EAAE,oBAAoB,KAAK,SAAS;YAAE,OAAO,CAAC,CAAA;QAC1D,MAAM,KAAK,GAAG,QAAQ,CAAC,oBAAoB,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAA;QAC7E,KAAK,MAAM,MAAM,IAAI,KAAK;YAAE,MAAM,CAAC,OAAO,EAAE,CAAA;QAC5C,OAAO,KAAK,CAAC,MAAM,CAAA;IACrB,CAAC,CAAA;IACD,OAAO,yBAAyB,CAAA;AAClC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbox queue, submit, and interrupt pipeline extracted from harness/driver.ts.
|
|
3
|
+
* Free-function collaborator: takes a {@link DriverQueueCtx} instead of closing
|
|
4
|
+
* over createDriver's locals, so the harness factory stays out of this leaf.
|
|
5
|
+
* @module @dsh-cc/tui/harness/driver-queue
|
|
6
|
+
*/
|
|
7
|
+
import type { DriverQueueCtx } from './driver-ctx.ts';
|
|
8
|
+
/**
|
|
9
|
+
* Build the queue/sumit/interrupt section of createDriver. `rt` supplies live
|
|
10
|
+
* state, emit, and neighbor-section seams (bash/local/harness/permission picker).
|
|
11
|
+
*/
|
|
12
|
+
export declare function createQueueSection(rt: DriverQueueCtx): {
|
|
13
|
+
flushQueue(): void;
|
|
14
|
+
steerQueued(): void;
|
|
15
|
+
recallQueued(): string | undefined;
|
|
16
|
+
submit(text?: string): Promise<void>;
|
|
17
|
+
interrupt(): void;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=driver-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driver-queue.d.ts","sourceRoot":"","sources":["../../src/harness/driver-queue.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAeH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AA2QrD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,cAAc,GAAG;IACtD,UAAU,IAAI,IAAI,CAAA;IAClB,WAAW,IAAI,IAAI,CAAA;IACnB,YAAY,IAAI,MAAM,GAAG,SAAS,CAAA;IAClC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpC,SAAS,IAAI,IAAI,CAAA;CAClB,CAQA"}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbox queue, submit, and interrupt pipeline extracted from harness/driver.ts.
|
|
3
|
+
* Free-function collaborator: takes a {@link DriverQueueCtx} instead of closing
|
|
4
|
+
* over createDriver's locals, so the harness factory stays out of this leaf.
|
|
5
|
+
* @module @dsh-cc/tui/harness/driver-queue
|
|
6
|
+
*/
|
|
7
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
8
|
+
import { LOCAL_SLASH, parseSlash } from "../slash.js";
|
|
9
|
+
import { saveHistory } from "../history.js";
|
|
10
|
+
import { clearQueue, clearTurn, enqueue, popQueued, setBusy, setDraft, setTurnActive, upsertRow, } from "../store.js";
|
|
11
|
+
const asUserMessage = (text) => createUserMessage({
|
|
12
|
+
content: [{ type: 'text', text }],
|
|
13
|
+
source: { kind: 'user' },
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Dispatch one queued outbox entry. A queued plugin command (`/codex:review
|
|
17
|
+
* args`) is re-classified here instead of being forwarded verbatim: flush and
|
|
18
|
+
* steer otherwise leak the raw slash line to the model as prompt text. A local
|
|
19
|
+
* name that is NOT a TUI-owned LOCAL_SLASH entry is a plugin command — it
|
|
20
|
+
* routes through the ccPlugins service. Anything else (plain prose, ordinary
|
|
21
|
+
* local names) sends as the original followup/steer, unchanged.
|
|
22
|
+
*
|
|
23
|
+
* Returns whether the entry was handed off to the agent (raw send or a
|
|
24
|
+
* successful plugin dispatch) — false means dropped with a notice. flushQueue
|
|
25
|
+
* uses this to avoid anchoring a busy turn nothing will arrive for.
|
|
26
|
+
*/
|
|
27
|
+
const dispatchQueued = (rt, text, mode) => {
|
|
28
|
+
const send = (raw) => {
|
|
29
|
+
const message = asUserMessage(raw);
|
|
30
|
+
if (mode === 'followup')
|
|
31
|
+
rt.current.agent.followup(message);
|
|
32
|
+
else
|
|
33
|
+
rt.current.agent.steer(message);
|
|
34
|
+
return true;
|
|
35
|
+
};
|
|
36
|
+
const parsed = parseSlash(text);
|
|
37
|
+
if (parsed.kind !== 'local') {
|
|
38
|
+
return send(text);
|
|
39
|
+
}
|
|
40
|
+
if (LOCAL_SLASH.includes(parsed.name)) {
|
|
41
|
+
// Ordinary TUI-local names never enter the outbox while busy, but if one
|
|
42
|
+
// ever does, keep the historical behavior: forward the text verbatim.
|
|
43
|
+
return send(text);
|
|
44
|
+
}
|
|
45
|
+
// Plugin command. The raw-line fallback is reserved for names that are no
|
|
46
|
+
// longer in the plugin command table (uninstalled between enqueue and
|
|
47
|
+
// flush) or a vanished service — the same unknown-slash fall-through
|
|
48
|
+
// philosophy as the submit path. A name that IS still registered but fails
|
|
49
|
+
// ({ok:false} or reject) must never reach the model as raw slash text: it
|
|
50
|
+
// surfaces a notice instead and the queued line is dropped.
|
|
51
|
+
const plugins = rt.ctx.get('ccPlugins');
|
|
52
|
+
if (plugins === undefined || typeof plugins.runPluginCommand !== 'function') {
|
|
53
|
+
return send(text);
|
|
54
|
+
}
|
|
55
|
+
const { name, rawInput } = parsed;
|
|
56
|
+
// Live membership check against the CURRENT table, mirrored at failure
|
|
57
|
+
// time: a name that left the table degrades to the raw line; a name that is
|
|
58
|
+
// still registered keeps the line local.
|
|
59
|
+
const stillListed = () => {
|
|
60
|
+
if (typeof plugins.listPluginCommands !== 'function')
|
|
61
|
+
return true;
|
|
62
|
+
try {
|
|
63
|
+
return plugins.listPluginCommands().some(c => c.name.toLowerCase() === name);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return true; // undecidable → keep the line local, never raw-send
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const failWithNotice = (reason) => {
|
|
70
|
+
rt.showNotice(`Plugin command /${name} failed: ${reason}`);
|
|
71
|
+
return false;
|
|
72
|
+
};
|
|
73
|
+
return plugins.runPluginCommand(name, { agent: rt.current.agent, rawInput })
|
|
74
|
+
.then((result) => {
|
|
75
|
+
if (!(result !== null && typeof result === 'object' && result.ok === false))
|
|
76
|
+
return true;
|
|
77
|
+
if (!stillListed())
|
|
78
|
+
return send(text);
|
|
79
|
+
const reason = result.reason ?? 'unknown error';
|
|
80
|
+
return failWithNotice(reason);
|
|
81
|
+
})
|
|
82
|
+
.catch((error) => {
|
|
83
|
+
if (!stillListed())
|
|
84
|
+
return send(text);
|
|
85
|
+
return failWithNotice(error instanceof Error ? error.message : String(error));
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Outbox flush, anchored to the durable `turn/end` event: snapshot the queue,
|
|
90
|
+
* dispatch every entry FIFO through `followup`, and clear the queue in one
|
|
91
|
+
* atomic stroke — so the queue never holds an entry that was already sent and
|
|
92
|
+
* ↑ recall cannot race a flush. Busy is re-asserted optimistically (the
|
|
93
|
+
* flushed followups start a new turn immediately; the fold's `turn/end`
|
|
94
|
+
* handling just set it false), but only when at least one entry was handed
|
|
95
|
+
* off.
|
|
96
|
+
*
|
|
97
|
+
* The stroke is deferred until the agent converges to idle (agent.whenIdle,
|
|
98
|
+
* falling back to one microtask for hosts without it). Two hazards force the
|
|
99
|
+
* wait, both proven by live e2e:
|
|
100
|
+
*
|
|
101
|
+
* 1. The call sites are `session/event` observers running INSIDE the session
|
|
102
|
+
* append publication window; `followup` → `inbox.splice` appends
|
|
103
|
+
* `agent/inbox/spliced` synchronously and hits the session reentrancy
|
|
104
|
+
* guard ("session append cannot reenter while another append is being
|
|
105
|
+
* published").
|
|
106
|
+
* 2. A bare microtask lands in the driver teardown gap: kick()'s loop already
|
|
107
|
+
* made its last inbox claim but setPhase(idle) hasn't run, so wakeDriver
|
|
108
|
+
* takes the non-idle branch — which neither latches the wake (not an
|
|
109
|
+
* abort/maintenance) nor has a live driver to claim the work. The spliced
|
|
110
|
+
* message strands in the inbox and the UI sits on a zombie busy anchor.
|
|
111
|
+
* whenIdle resolves only after kick's finally sets the idle phase, where
|
|
112
|
+
* wakeDriver's idle path reliably starts the next driver.
|
|
113
|
+
*/
|
|
114
|
+
const flushQueue = (rt) => {
|
|
115
|
+
const flush = () => {
|
|
116
|
+
const s = rt.state();
|
|
117
|
+
const pending = [...s.queued];
|
|
118
|
+
if (pending.length === 0)
|
|
119
|
+
return;
|
|
120
|
+
rt.emit(clearQueue(s));
|
|
121
|
+
const handedOff = pending.map(text => dispatchQueued(rt, text, 'followup'));
|
|
122
|
+
// Anchor the followup turn only once we know at least one entry was
|
|
123
|
+
// actually handed off: an all-dropped flush (e.g. a plugin command that
|
|
124
|
+
// failed with a notice) must not leave a zombie busy spinner behind.
|
|
125
|
+
void Promise.all(handedOff).then(results => {
|
|
126
|
+
if (!results.some(Boolean))
|
|
127
|
+
return;
|
|
128
|
+
rt.emit(setTurnActive(setBusy(rt.state(), true), { startedAt: Date.now(), outputBase: s.hud?.tokens?.output }));
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
// Await the agent that ENDED the turn (captured now); dispatchQueued reads
|
|
132
|
+
// rt.current.agent at fire time, so a session switch still targets the
|
|
133
|
+
// live session. A rejected whenIdle must not strand the queue.
|
|
134
|
+
const endingAgent = rt.current.agent;
|
|
135
|
+
if (typeof endingAgent.whenIdle === 'function') {
|
|
136
|
+
void endingAgent.whenIdle().then(flush, flush);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
queueMicrotask(flush);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* Ctrl+S queue-jump: inject every queued entry into the RUNNING turn
|
|
144
|
+
* immediately — same synchronous snapshot-then-clear discipline as
|
|
145
|
+
* {@link flushQueue}, but via `agent.steer` and without a busy flip (the
|
|
146
|
+
* turn is already running).
|
|
147
|
+
*/
|
|
148
|
+
const steerQueued = (rt) => {
|
|
149
|
+
const s = rt.state();
|
|
150
|
+
const pending = [...s.queued];
|
|
151
|
+
if (pending.length === 0)
|
|
152
|
+
return;
|
|
153
|
+
for (const text of pending) {
|
|
154
|
+
// Semantic trade-off: a queued plugin command reaches the running turn as
|
|
155
|
+
// a followup (queued work) rather than a steer — running it through the
|
|
156
|
+
// plugin seam is more important than steering semantics.
|
|
157
|
+
void dispatchQueued(rt, text, 'steer');
|
|
158
|
+
}
|
|
159
|
+
rt.emit(clearQueue(s));
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Recall for editing: pop the most recent queued entry back out of the outbox
|
|
163
|
+
* and hand it to the caller (root.ts puts it into the composer). Race-free by
|
|
164
|
+
* construction — flush and steer always clear synchronously, so the queue only
|
|
165
|
+
* ever holds entries that were never sent.
|
|
166
|
+
*/
|
|
167
|
+
const recallQueued = (rt) => {
|
|
168
|
+
const popped = popQueued(rt.state());
|
|
169
|
+
if (popped.text === undefined)
|
|
170
|
+
return undefined;
|
|
171
|
+
rt.emit(popped.state);
|
|
172
|
+
return popped.text;
|
|
173
|
+
};
|
|
174
|
+
const submit = async (rt, text) => {
|
|
175
|
+
const draft = text ?? rt.state().draft;
|
|
176
|
+
if (draft.trim().length === 0)
|
|
177
|
+
return;
|
|
178
|
+
rt.emit(setDraft(rt.state(), ''));
|
|
179
|
+
// A leading `!` marks a LOCAL shell command no matter how the text was
|
|
180
|
+
// entered — typed in shell mode or pasted wholesale. It runs even while
|
|
181
|
+
// the agent is busy (a local command never touches the turn) and is neither
|
|
182
|
+
// a prompt nor a slash command.
|
|
183
|
+
if (draft.startsWith('!')) {
|
|
184
|
+
await rt.runShellCommand(draft.slice(1));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const parsed = parseSlash(draft);
|
|
188
|
+
if (parsed.kind === 'local') {
|
|
189
|
+
await rt.runLocal(parsed.name, parsed.rawInput);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (parsed.kind === 'harness') {
|
|
193
|
+
// Bare `/permissions` is the TUI analogue of the browser popupSelect
|
|
194
|
+
// decoration: open the overlay instead of dumping the rule listing.
|
|
195
|
+
// `/permissions <mode>` stays scriptable through the host command.
|
|
196
|
+
if (/^\/permissions$/i.test(parsed.line)) {
|
|
197
|
+
rt.openPermissionPicker();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
// An empty name segment (bare `/` or `/ `) is not a command, a skill,
|
|
201
|
+
// or a prompt worth a model turn.
|
|
202
|
+
const name = parsed.line.slice(1).split(/\s/, 1)[0] ?? '';
|
|
203
|
+
if (name.length === 0) {
|
|
204
|
+
rt.showNotice('Empty slash command.');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const result = await rt.runHarness(parsed.line);
|
|
208
|
+
if (result !== undefined) {
|
|
209
|
+
// A result object (success or error) means a known command ran in the
|
|
210
|
+
// command plane; `null` means no command registry is mounted (runHarness
|
|
211
|
+
// already noticed). Neither becomes a prompt.
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// Unknown name: fall through to the prompt path below. This is the whole
|
|
215
|
+
// user-invocable-skill mechanism — the TUI never decides "is this a
|
|
216
|
+
// skill?"; the host's closed-set matching does. The followup message
|
|
217
|
+
// below MUST keep `source: { kind: 'user' }` because dsh-tool-skill's
|
|
218
|
+
// pre-step gesture boundary only scans `source.kind === 'user'` messages;
|
|
219
|
+
// if the name is a user-invocable skill it injects <skill_content>, and
|
|
220
|
+
// otherwise the line stays ordinary prose.
|
|
221
|
+
}
|
|
222
|
+
// W4 await-late seam: the boot seed (deployment default model) must be
|
|
223
|
+
// SETTLED before this turn is enqueued or dispatched — the harness
|
|
224
|
+
// snapshots `selection.current` at the start of prompt assembly
|
|
225
|
+
// (@deepseek-ai/dsh-agent model-selection.ts `system-prompt/assemble`),
|
|
226
|
+
// so dispatching earlier would run the turn with an undefined model.
|
|
227
|
+
// Local `!` shell lines and slash commands above already returned.
|
|
228
|
+
await rt.waitForModel();
|
|
229
|
+
// Persist the prompt (not slash commands — they are commands, not prompts,
|
|
230
|
+
// and would dilute the recall signal; an unknown slash IS a prompt and
|
|
231
|
+
// persists here via the fall-through above). Consecutive duplicates and the
|
|
232
|
+
// cap are handled inside saveHistory. This is also the first real-content
|
|
233
|
+
// signal: mark the session so the launcher can resume it.
|
|
234
|
+
rt.setHistory(saveHistory([...rt.getHistory(), draft], rt.historyDir));
|
|
235
|
+
rt.setMarkedContent(true);
|
|
236
|
+
rt.persistResumeTarget();
|
|
237
|
+
const s = rt.state();
|
|
238
|
+
if (s.busy) {
|
|
239
|
+
// Outbox: park the text as a pending chip only. It reaches the agent on
|
|
240
|
+
// the next durable `turn/end` (flushQueue) or immediately via Ctrl+S
|
|
241
|
+
// (steerQueued). No injection into the running turn here — that is what
|
|
242
|
+
// makes recall-then-edit meaningful.
|
|
243
|
+
rt.emit(enqueue(s, draft));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// Idle sends bypass the outbox entirely — the row surfaces from the durable
|
|
247
|
+
// `user/message` event, and a sent text must not stay recallable.
|
|
248
|
+
rt.current.agent.followup(createUserMessage({
|
|
249
|
+
content: [{ type: 'text', text: draft }],
|
|
250
|
+
source: { kind: 'user' },
|
|
251
|
+
}));
|
|
252
|
+
// Anchor the working line at dispatch: elapsed counts from here, the token
|
|
253
|
+
// delta from the current HUD total (undefined when unseeded — the tokenUsage
|
|
254
|
+
// rebase pins it on the first change).
|
|
255
|
+
rt.emit(setTurnActive(setBusy(rt.state(), true), { startedAt: Date.now(), outputBase: s.hud?.tokens?.output }));
|
|
256
|
+
};
|
|
257
|
+
const interrupt = (rt) => {
|
|
258
|
+
rt.current.agent.cancel({ kind: 'user' });
|
|
259
|
+
// cancel discards queued/steering inbox items; mirror that in UI state.
|
|
260
|
+
// Clearing BEFORE the abort's turn/end lands also guarantees the flush
|
|
261
|
+
// anchor finds an empty queue — an interrupt never resurrects entries.
|
|
262
|
+
// The working-line anchor clears with the turn.
|
|
263
|
+
rt.emit(upsertRow(clearTurn(clearQueue(setBusy(rt.state(), false))), {
|
|
264
|
+
kind: 'status',
|
|
265
|
+
text: 'Interrupted by user.',
|
|
266
|
+
}));
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* Build the queue/sumit/interrupt section of createDriver. `rt` supplies live
|
|
270
|
+
* state, emit, and neighbor-section seams (bash/local/harness/permission picker).
|
|
271
|
+
*/
|
|
272
|
+
export function createQueueSection(rt) {
|
|
273
|
+
return {
|
|
274
|
+
flushQueue: () => flushQueue(rt),
|
|
275
|
+
steerQueued: () => steerQueued(rt),
|
|
276
|
+
recallQueued: () => recallQueued(rt),
|
|
277
|
+
submit: (text) => submit(rt, text),
|
|
278
|
+
interrupt: () => interrupt(rt),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=driver-queue.js.map
|