@sagmans/dsh-tui 0.6.0 → 0.7.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/.agents/skills/dsh-tui-dogfood/SKILL.md +83 -0
- package/.agents/skills/dsh-tui-dogfood/references/home-state.md +78 -0
- package/.agents/skills/dsh-tui-dogfood/scripts/clone-links.mjs +133 -0
- package/.agents/skills/dsh-tui-dogfood/scripts/run-plugin-from-worktree.sh +417 -0
- package/.agents/skills/dsh-tui-update-models/SKILL.md +121 -0
- package/.agents/skills/dsh-tui-update-models/references/model-wiring.md +162 -0
- package/.agents/skills/dsh-tui-update-models/scripts/dump-model-catalog.mjs +178 -0
- package/README.md +99 -5
- package/lib/agent/present.d.ts.map +1 -1
- package/lib/agent/present.js +2 -1
- package/lib/agent/present.js.map +1 -1
- package/lib/cards/composition.d.ts +68 -0
- package/lib/cards/composition.d.ts.map +1 -0
- package/lib/cards/composition.js +117 -0
- package/lib/cards/composition.js.map +1 -0
- package/lib/cards/presenter.d.ts +23 -0
- package/lib/cards/presenter.d.ts.map +1 -0
- package/lib/cards/presenter.js +394 -0
- package/lib/cards/presenter.js.map +1 -0
- package/lib/cards/preview.d.ts +42 -0
- package/lib/cards/preview.d.ts.map +1 -0
- package/lib/cards/preview.js +51 -0
- package/lib/cards/preview.js.map +1 -0
- package/lib/cards.d.ts +12 -120
- package/lib/cards.d.ts.map +1 -1
- package/lib/cards.js +4 -556
- package/lib/cards.js.map +1 -1
- package/lib/compat/section.d.ts +29 -0
- package/lib/compat/section.d.ts.map +1 -0
- package/lib/compat/section.js +82 -0
- package/lib/compat/section.js.map +1 -0
- package/lib/config.d.ts +27 -2
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +61 -1
- package/lib/config.js.map +1 -1
- package/lib/contracts.d.ts +28 -0
- package/lib/contracts.d.ts.map +1 -1
- package/lib/fold-cursor.d.ts +8 -0
- package/lib/fold-cursor.d.ts.map +1 -1
- package/lib/fold-cursor.js +18 -0
- package/lib/fold-cursor.js.map +1 -1
- package/lib/gates/question-card.d.ts +77 -0
- package/lib/gates/question-card.d.ts.map +1 -0
- package/lib/gates/question-card.js +113 -0
- package/lib/gates/question-card.js.map +1 -0
- package/lib/gates/questions.d.ts +127 -0
- package/lib/gates/questions.d.ts.map +1 -0
- package/lib/gates/questions.js +418 -0
- package/lib/gates/questions.js.map +1 -0
- package/lib/gates.d.ts +1 -113
- package/lib/gates.d.ts.map +1 -1
- package/lib/gates.js +1 -484
- package/lib/gates.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +343 -2602
- package/lib/index.js.map +1 -1
- package/lib/input/action-catalog.d.ts +74 -0
- package/lib/input/action-catalog.d.ts.map +1 -0
- package/lib/input/action-catalog.js +137 -0
- package/lib/input/action-catalog.js.map +1 -0
- package/lib/input/actions.d.ts +5 -81
- package/lib/input/actions.d.ts.map +1 -1
- package/lib/input/actions.js +4 -625
- package/lib/input/actions.js.map +1 -1
- package/lib/input/completion.d.ts +1 -1
- package/lib/input/completion.d.ts.map +1 -1
- package/lib/input/completion.js +2 -1
- package/lib/input/completion.js.map +1 -1
- package/lib/input/file-index.d.ts +40 -0
- package/lib/input/file-index.d.ts.map +1 -0
- package/lib/input/file-index.js +141 -0
- package/lib/input/file-index.js.map +1 -0
- package/lib/input/file-search.d.ts +1 -38
- package/lib/input/file-search.d.ts.map +1 -1
- package/lib/input/file-search.js +1 -487
- package/lib/input/file-search.js.map +1 -1
- package/lib/input/key-press.d.ts +57 -0
- package/lib/input/key-press.d.ts.map +1 -0
- package/lib/input/key-press.js +218 -0
- package/lib/input/key-press.js.map +1 -0
- package/lib/input/keymap-conflicts.d.ts +59 -0
- package/lib/input/keymap-conflicts.d.ts.map +1 -0
- package/lib/input/keymap-conflicts.js +292 -0
- package/lib/input/keymap-conflicts.js.map +1 -0
- package/lib/input/keymap-settings.js +1 -1
- package/lib/input/keymap-settings.js.map +1 -1
- package/lib/input/keymap.d.ts.map +1 -1
- package/lib/input/keymap.js +3 -1
- package/lib/input/keymap.js.map +1 -1
- package/lib/input/workspace-files.d.ts +10 -0
- package/lib/input/workspace-files.d.ts.map +1 -0
- package/lib/input/workspace-files.js +359 -0
- package/lib/input/workspace-files.js.map +1 -0
- package/lib/install-skills.d.ts +20 -0
- package/lib/install-skills.d.ts.map +1 -0
- package/lib/install-skills.js +198 -0
- package/lib/install-skills.js.map +1 -0
- package/lib/keys-command.d.ts +2 -1
- package/lib/keys-command.d.ts.map +1 -1
- package/lib/keys-command.js +2 -1
- package/lib/keys-command.js.map +1 -1
- package/lib/model-list.d.ts +37 -0
- package/lib/model-list.d.ts.map +1 -0
- package/lib/model-list.js +60 -0
- package/lib/model-list.js.map +1 -0
- package/lib/startup.d.ts +8 -0
- package/lib/startup.d.ts.map +1 -1
- package/lib/startup.js +120 -24
- package/lib/startup.js.map +1 -1
- package/lib/subagents.d.ts +7 -3
- package/lib/subagents.d.ts.map +1 -1
- package/lib/subagents.js +28 -4
- package/lib/subagents.js.map +1 -1
- package/lib/surface/appearance-preferences.d.ts +26 -0
- package/lib/surface/appearance-preferences.d.ts.map +1 -0
- package/lib/surface/appearance-preferences.js +139 -0
- package/lib/surface/appearance-preferences.js.map +1 -0
- package/lib/surface/appearance.d.ts +87 -0
- package/lib/surface/appearance.d.ts.map +1 -0
- package/lib/surface/appearance.js +353 -0
- package/lib/surface/appearance.js.map +1 -0
- package/lib/surface/background-work.d.ts +49 -0
- package/lib/surface/background-work.d.ts.map +1 -0
- package/lib/surface/background-work.js +146 -0
- package/lib/surface/background-work.js.map +1 -0
- package/lib/surface/commands.d.ts +80 -0
- package/lib/surface/commands.d.ts.map +1 -0
- package/lib/surface/commands.js +364 -0
- package/lib/surface/commands.js.map +1 -0
- package/lib/surface/modal-input.d.ts +58 -0
- package/lib/surface/modal-input.d.ts.map +1 -0
- package/lib/surface/modal-input.js +237 -0
- package/lib/surface/modal-input.js.map +1 -0
- package/lib/surface/model-choice.d.ts +54 -0
- package/lib/surface/model-choice.d.ts.map +1 -0
- package/lib/surface/model-choice.js +325 -0
- package/lib/surface/model-choice.js.map +1 -0
- package/lib/surface/preset-choice.d.ts +78 -0
- package/lib/surface/preset-choice.d.ts.map +1 -0
- package/lib/surface/preset-choice.js +163 -0
- package/lib/surface/preset-choice.js.map +1 -0
- package/lib/surface/prompt-input.d.ts +97 -0
- package/lib/surface/prompt-input.d.ts.map +1 -0
- package/lib/surface/prompt-input.js +215 -0
- package/lib/surface/prompt-input.js.map +1 -0
- package/lib/surface/prompt-memory.d.ts +76 -0
- package/lib/surface/prompt-memory.d.ts.map +1 -0
- package/lib/surface/prompt-memory.js +167 -0
- package/lib/surface/prompt-memory.js.map +1 -0
- package/lib/surface/session-lifecycle.d.ts +120 -0
- package/lib/surface/session-lifecycle.d.ts.map +1 -0
- package/lib/surface/session-lifecycle.js +321 -0
- package/lib/surface/session-lifecycle.js.map +1 -0
- package/lib/surface/session-picker.d.ts +39 -0
- package/lib/surface/session-picker.d.ts.map +1 -0
- package/lib/surface/session-picker.js +107 -0
- package/lib/surface/session-picker.js.map +1 -0
- package/lib/surface/session-view.d.ts +86 -0
- package/lib/surface/session-view.d.ts.map +1 -0
- package/lib/surface/session-view.js +215 -0
- package/lib/surface/session-view.js.map +1 -0
- package/lib/surface/staged-turns.d.ts +81 -0
- package/lib/surface/staged-turns.d.ts.map +1 -0
- package/lib/surface/staged-turns.js +206 -0
- package/lib/surface/staged-turns.js.map +1 -0
- package/lib/surface/terminal-lifecycle.d.ts +76 -0
- package/lib/surface/terminal-lifecycle.d.ts.map +1 -0
- package/lib/surface/terminal-lifecycle.js +222 -0
- package/lib/surface/terminal-lifecycle.js.map +1 -0
- package/lib/terminal-text/scan.d.ts +44 -0
- package/lib/terminal-text/scan.d.ts.map +1 -0
- package/lib/terminal-text/scan.js +195 -0
- package/lib/terminal-text/scan.js.map +1 -0
- package/lib/terminal-text/sgr.d.ts +32 -0
- package/lib/terminal-text/sgr.d.ts.map +1 -0
- package/lib/terminal-text/sgr.js +203 -0
- package/lib/terminal-text/sgr.js.map +1 -0
- package/lib/terminal-text.d.ts +1 -1
- package/lib/terminal-text.d.ts.map +1 -1
- package/lib/terminal-text.js +2 -379
- package/lib/terminal-text.js.map +1 -1
- package/lib/theme-command.js +2 -1
- package/lib/theme-command.js.map +1 -1
- package/lib/theme-defaults.d.ts +64 -0
- package/lib/theme-defaults.d.ts.map +1 -0
- package/lib/theme-defaults.js +248 -0
- package/lib/theme-defaults.js.map +1 -0
- package/lib/theme-resolver.d.ts +36 -0
- package/lib/theme-resolver.d.ts.map +1 -0
- package/lib/theme-resolver.js +112 -0
- package/lib/theme-resolver.js.map +1 -0
- package/lib/theme-schema.d.ts.map +1 -1
- package/lib/theme-schema.js +3 -2
- package/lib/theme-schema.js.map +1 -1
- package/lib/theme-settings.d.ts +5 -7
- package/lib/theme-settings.d.ts.map +1 -1
- package/lib/theme-settings.js +27 -19
- package/lib/theme-settings.js.map +1 -1
- package/lib/theme-tokens.d.ts +0 -89
- package/lib/theme-tokens.d.ts.map +1 -1
- package/lib/theme-tokens.js +0 -356
- package/lib/theme-tokens.js.map +1 -1
- package/lib/theme.d.ts +1 -1
- package/lib/theme.d.ts.map +1 -1
- package/lib/theme.js +2 -1
- package/lib/theme.js.map +1 -1
- package/lib/tool-display.d.ts.map +1 -1
- package/lib/tool-display.js +2 -1
- package/lib/tool-display.js.map +1 -1
- package/lib/transcript/message-content.d.ts +21 -0
- package/lib/transcript/message-content.d.ts.map +1 -0
- package/lib/transcript/message-content.js +61 -0
- package/lib/transcript/message-content.js.map +1 -0
- package/lib/transcript/tool-calls.d.ts +106 -0
- package/lib/transcript/tool-calls.d.ts.map +1 -0
- package/lib/transcript/tool-calls.js +254 -0
- package/lib/transcript/tool-calls.js.map +1 -0
- package/lib/transcript.d.ts +14 -55
- package/lib/transcript.d.ts.map +1 -1
- package/lib/transcript.js +31 -253
- package/lib/transcript.js.map +1 -1
- package/lib/ui/dock.d.ts +14 -2
- package/lib/ui/dock.d.ts.map +1 -1
- package/lib/ui/dock.js +52 -10
- package/lib/ui/dock.js.map +1 -1
- package/lib/ui/editor.d.ts.map +1 -1
- package/lib/ui/editor.js +2 -1
- package/lib/ui/editor.js.map +1 -1
- package/lib/ui/keymap-picker.d.ts +2 -1
- package/lib/ui/keymap-picker.d.ts.map +1 -1
- package/lib/ui/keymap-picker.js +1 -0
- package/lib/ui/keymap-picker.js.map +1 -1
- package/lib/ui/view/gate-card.d.ts +21 -0
- package/lib/ui/view/gate-card.d.ts.map +1 -0
- package/lib/ui/view/gate-card.js +104 -0
- package/lib/ui/view/gate-card.js.map +1 -0
- package/lib/ui/view/tool-card.d.ts +138 -0
- package/lib/ui/view/tool-card.d.ts.map +1 -0
- package/lib/ui/view/tool-card.js +413 -0
- package/lib/ui/view/tool-card.js.map +1 -0
- package/lib/ui/view/transcript-message.d.ts +62 -0
- package/lib/ui/view/transcript-message.d.ts.map +1 -0
- package/lib/ui/view/transcript-message.js +158 -0
- package/lib/ui/view/transcript-message.js.map +1 -0
- package/lib/ui/view.d.ts +80 -220
- package/lib/ui/view.d.ts.map +1 -1
- package/lib/ui/view.js +81 -675
- package/lib/ui/view.js.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
3
|
+
import type { TuiAgent } from '../agent/host.ts';
|
|
4
|
+
import { type JobSummary } from '../jobs.ts';
|
|
5
|
+
import { SubagentRoster } from '../subagents.ts';
|
|
6
|
+
/**
|
|
7
|
+
* What the background-work owner needs from the surface that composes it.
|
|
8
|
+
*
|
|
9
|
+
* The agent and the session on screen are read at call time: a command, a
|
|
10
|
+
* listener, or the job watch each has to follow whatever this terminal drives
|
|
11
|
+
* now, and a captured identity would follow the one it drove at construction.
|
|
12
|
+
*/
|
|
13
|
+
export interface BackgroundWorkPorts {
|
|
14
|
+
readonly drivingAgent: () => TuiAgent | undefined;
|
|
15
|
+
readonly activeSession: () => SessionId;
|
|
16
|
+
readonly notice: (text: string) => void;
|
|
17
|
+
readonly marker: (text: string) => void;
|
|
18
|
+
readonly render: () => void;
|
|
19
|
+
/** Show the session a child runs in; the viewed-session owner performs it. */
|
|
20
|
+
readonly navigate: (id: string) => void;
|
|
21
|
+
}
|
|
22
|
+
/** The commands and live reads the composing surface routes to this owner. */
|
|
23
|
+
export interface BackgroundWork {
|
|
24
|
+
readonly roster: SubagentRoster;
|
|
25
|
+
readonly jobs: () => readonly JobSummary[];
|
|
26
|
+
readonly refresh: () => void;
|
|
27
|
+
/** Fold the parent's durable catalog, which is where a child's task name lives. */
|
|
28
|
+
readonly acceptCatalog: (info: unknown) => void;
|
|
29
|
+
readonly resetRoster: () => void;
|
|
30
|
+
readonly runSubagentsCommand: (argument: string) => void;
|
|
31
|
+
readonly runJobsCommand: (argument: string) => void;
|
|
32
|
+
/**
|
|
33
|
+
* Register the two subagent lifecycle listeners.
|
|
34
|
+
*
|
|
35
|
+
* They are returned rather than pushed here so the composition root keeps one
|
|
36
|
+
* ordered list of teardowns, which is unwound in reverse registration order.
|
|
37
|
+
*/
|
|
38
|
+
readonly subagentListeners: () => readonly (() => void)[];
|
|
39
|
+
readonly watchJobs: () => () => void;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The work that runs beside the conversation: background jobs and child agents.
|
|
43
|
+
*
|
|
44
|
+
* Neither is folded from the transcript. A job board is live state owned by a
|
|
45
|
+
* service, and a child's name arrives on its own event, so this owner keeps
|
|
46
|
+
* those reads and the two commands that show them in one place.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createBackgroundWork(ctx: Context, ports: BackgroundWorkPorts): BackgroundWork;
|
|
49
|
+
//# sourceMappingURL=background-work.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background-work.d.ts","sourceRoot":"","sources":["../../src/surface/background-work.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAML,KAAK,UAAU,EAChB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,cAAc,EAAgF,MAAM,iBAAiB,CAAA;AAO9H;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,YAAY,EAAE,MAAM,QAAQ,GAAG,SAAS,CAAA;IACjD,QAAQ,CAAC,aAAa,EAAE,MAAM,SAAS,CAAA;IACvC,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAA;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC;AAED,8EAA8E;AAC9E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,SAAS,UAAU,EAAE,CAAA;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;IAC5B,mFAAmF;IACnF,QAAQ,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAC/C,QAAQ,CAAC,WAAW,EAAE,MAAM,IAAI,CAAA;IAChC,QAAQ,CAAC,mBAAmB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IACxD,QAAQ,CAAC,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IACnD;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,SAAS,CAAC,MAAM,IAAI,CAAC,EAAE,CAAA;IACzD,QAAQ,CAAC,SAAS,EAAE,MAAM,MAAM,IAAI,CAAA;CACrC;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,mBAAmB,GAAG,cAAc,CAwI7F"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { JOB_READ_LINES, createJobDirectory, describeJobs, parseJobsArgument, } from "../jobs.js";
|
|
2
|
+
import { SubagentRoster, createSubagentControl, describeSubagents, parseSubagentsArgument, resolveRun } from "../subagents.js";
|
|
3
|
+
/** Decode a lifecycle payload the roster only takes when it carries the shape it expects. */
|
|
4
|
+
function asRecord(value) {
|
|
5
|
+
return typeof value === 'object' && value !== null ? value : undefined;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The work that runs beside the conversation: background jobs and child agents.
|
|
9
|
+
*
|
|
10
|
+
* Neither is folded from the transcript. A job board is live state owned by a
|
|
11
|
+
* service, and a child's name arrives on its own event, so this owner keeps
|
|
12
|
+
* those reads and the two commands that show them in one place.
|
|
13
|
+
*/
|
|
14
|
+
export function createBackgroundWork(ctx, ports) {
|
|
15
|
+
const jobDirectory = createJobDirectory(ctx);
|
|
16
|
+
let jobs = [];
|
|
17
|
+
const roster = new SubagentRoster();
|
|
18
|
+
const subagentControl = createSubagentControl(ctx);
|
|
19
|
+
/** Re-read the job board; it is live state, so nothing else can fold it. */
|
|
20
|
+
const refresh = () => {
|
|
21
|
+
const agent = ports.drivingAgent();
|
|
22
|
+
jobs = agent === undefined || jobDirectory === undefined ? [] : jobDirectory.list(agent.agent);
|
|
23
|
+
ports.render();
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* List or stop the delegations this session started.
|
|
27
|
+
*
|
|
28
|
+
* A child runs in the same process as an ordinary agent, so a stop is the
|
|
29
|
+
* cancel a reader's Ctrl+C sends to the parent; nothing here reaches into the
|
|
30
|
+
* child's own session, which keeps its own transcript either way.
|
|
31
|
+
*/
|
|
32
|
+
const runSubagentsCommand = (argument) => {
|
|
33
|
+
const command = parseSubagentsArgument(argument);
|
|
34
|
+
switch (command.kind) {
|
|
35
|
+
case 'list':
|
|
36
|
+
ports.notice(describeSubagents(roster.list(), Date.now()));
|
|
37
|
+
ports.render();
|
|
38
|
+
return;
|
|
39
|
+
case 'open': {
|
|
40
|
+
const run = resolveRun(roster.list(), command.id);
|
|
41
|
+
if (run === undefined) {
|
|
42
|
+
ports.notice(`${command.id}: no single child matches; /subagents lists them`);
|
|
43
|
+
ports.render();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
ports.navigate(run.id);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
case 'kill': {
|
|
50
|
+
const stopped = subagentControl?.stop(command.id) ?? false;
|
|
51
|
+
ports.notice(stopped
|
|
52
|
+
? `${command.id}: stop requested`
|
|
53
|
+
: `${command.id}: no live child with that id`);
|
|
54
|
+
ports.render();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
case 'invalid':
|
|
58
|
+
ports.notice(`/subagents: ${command.reason}`);
|
|
59
|
+
ports.render();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const runJobsCommand = (argument) => {
|
|
64
|
+
const agent = ports.drivingAgent();
|
|
65
|
+
if (jobDirectory === undefined) {
|
|
66
|
+
ports.notice('this profile has no job registry, so there is nothing to list');
|
|
67
|
+
ports.render();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (agent === undefined) {
|
|
71
|
+
ports.notice('the agent is still starting; try again in a moment');
|
|
72
|
+
ports.render();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const command = parseJobsArgument(argument);
|
|
76
|
+
switch (command.kind) {
|
|
77
|
+
case 'list':
|
|
78
|
+
refresh();
|
|
79
|
+
ports.notice(describeJobs(jobs, Date.now()));
|
|
80
|
+
ports.render();
|
|
81
|
+
return;
|
|
82
|
+
case 'read': {
|
|
83
|
+
const result = jobDirectory.read(agent.agent, command.id);
|
|
84
|
+
const text = result?.text.trim() ?? '';
|
|
85
|
+
ports.notice(text === ''
|
|
86
|
+
? `${command.id}: no output yet`
|
|
87
|
+
: `${command.id} output\n${text.split('\n').slice(-JOB_READ_LINES).join('\n')}`);
|
|
88
|
+
refresh();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
case 'kill': {
|
|
92
|
+
const outcome = jobDirectory.kill(agent.agent, command.id);
|
|
93
|
+
ports.notice(outcome === undefined
|
|
94
|
+
? `${command.id}: no such job`
|
|
95
|
+
: outcome === 'requested' ? `${command.id}: stop requested` : `${command.id} had already finished`);
|
|
96
|
+
refresh();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
case 'invalid':
|
|
100
|
+
ports.notice(`/jobs: ${command.reason}`);
|
|
101
|
+
ports.render();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Subagent lifecycle arrives as a service event rather than a session event,
|
|
107
|
+
* so it is decoration in the transcript: the parent's durable catalog
|
|
108
|
+
* carries the child's task. The name is cast so a rename in the
|
|
109
|
+
* harness cannot break compilation of this surface.
|
|
110
|
+
*/
|
|
111
|
+
const listenFor = (name, handler) => ctx.on(name, handler);
|
|
112
|
+
return {
|
|
113
|
+
roster,
|
|
114
|
+
jobs: () => jobs,
|
|
115
|
+
refresh,
|
|
116
|
+
acceptCatalog: info => roster.catalog(info),
|
|
117
|
+
resetRoster: () => roster.reset(),
|
|
118
|
+
runSubagentsCommand,
|
|
119
|
+
runJobsCommand,
|
|
120
|
+
subagentListeners: () => [
|
|
121
|
+
listenFor('subagent/start', info => {
|
|
122
|
+
roster.start(info);
|
|
123
|
+
const record = asRecord(info);
|
|
124
|
+
const provider = typeof record?.provider === 'string' ? record.provider : 'subagent';
|
|
125
|
+
const id = typeof record?.id === 'string' ? record.id : '';
|
|
126
|
+
ports.marker(`subagent ${provider} started${id === '' ? '' : ` · ${id}`}`);
|
|
127
|
+
ports.render();
|
|
128
|
+
}),
|
|
129
|
+
listenFor('subagent/end', info => {
|
|
130
|
+
roster.end(info);
|
|
131
|
+
const record = asRecord(info);
|
|
132
|
+
const provider = typeof record?.provider === 'string' ? record.provider : 'subagent';
|
|
133
|
+
const stop = typeof record?.stopReason === 'string' ? record.stopReason : undefined;
|
|
134
|
+
ports.marker(`subagent ${provider} finished${stop === undefined ? '' : ` · ${stop}`}`);
|
|
135
|
+
ports.render();
|
|
136
|
+
}),
|
|
137
|
+
],
|
|
138
|
+
// The board is live state: watch it directly rather than folding events.
|
|
139
|
+
watchJobs: () => jobDirectory?.watch(owner => {
|
|
140
|
+
if (owner !== undefined && owner.id !== ports.activeSession())
|
|
141
|
+
return;
|
|
142
|
+
refresh();
|
|
143
|
+
}) ?? (() => { }),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=background-work.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background-work.js","sourceRoot":"","sources":["../../src/surface/background-work.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
3
|
+
import type { PresetRoster } from '../agent/presets.ts';
|
|
4
|
+
import type { RegisteredCommand } from '../input/completion.ts';
|
|
5
|
+
import { type Submission } from '../input/submission.ts';
|
|
6
|
+
import type { PromptStash } from '../stash.ts';
|
|
7
|
+
import type { StatusFacts } from '../ui/status.ts';
|
|
8
|
+
import type { Appearance } from './appearance.ts';
|
|
9
|
+
import type { BackgroundWork } from './background-work.ts';
|
|
10
|
+
import type { ModalInput } from './modal-input.ts';
|
|
11
|
+
import type { ModelChoice } from './model-choice.ts';
|
|
12
|
+
import type { PresetChoice, PresetLaunch } from './preset-choice.ts';
|
|
13
|
+
import type { PromptMemory } from './prompt-memory.ts';
|
|
14
|
+
import type { SessionLifecycle } from './session-lifecycle.ts';
|
|
15
|
+
import type { SessionView } from './session-view.ts';
|
|
16
|
+
import type { StagedTurns } from './staged-turns.ts';
|
|
17
|
+
import type { TerminalLifecycle } from './terminal-lifecycle.ts';
|
|
18
|
+
/**
|
|
19
|
+
* What the command plane needs from the surface that composes it.
|
|
20
|
+
*
|
|
21
|
+
* Each owner arrives whole, because this module is the one place that knows
|
|
22
|
+
* which of their operations a typed line, a chord, or the launch maps onto; the
|
|
23
|
+
* reads it takes of them are live, so a command typed after a session switch
|
|
24
|
+
* answers for the session now on screen. The launch facts and the mode roster
|
|
25
|
+
* are the two things no owner holds, and the notice and render sinks are what a
|
|
26
|
+
* refusal and a repaint are printed through.
|
|
27
|
+
*/
|
|
28
|
+
export interface CommandsPorts {
|
|
29
|
+
/** The launch this run was started for, and the mode its own flag named. */
|
|
30
|
+
readonly launch: PresetLaunch;
|
|
31
|
+
readonly preset: string | undefined;
|
|
32
|
+
/** The roster that can say whether a stored session's mode still exists. */
|
|
33
|
+
readonly presetRoster: PresetRoster | undefined;
|
|
34
|
+
/** A composition row that is missing, printed before the screen is taken. */
|
|
35
|
+
readonly missingOptional: () => string | undefined;
|
|
36
|
+
readonly session: SessionLifecycle;
|
|
37
|
+
readonly transcript: SessionView;
|
|
38
|
+
readonly route: ModelChoice;
|
|
39
|
+
readonly modes: PresetChoice;
|
|
40
|
+
readonly work: BackgroundWork;
|
|
41
|
+
readonly staged: StagedTurns;
|
|
42
|
+
readonly memory: PromptMemory;
|
|
43
|
+
readonly appearance: Appearance;
|
|
44
|
+
/** The terminal this surface owns, which is where a copy is written. */
|
|
45
|
+
readonly terminal: TerminalLifecycle;
|
|
46
|
+
/** The one modal interaction at a time, which lends its keyboard to a list. */
|
|
47
|
+
readonly modals: ModalInput;
|
|
48
|
+
readonly statusFacts: () => StatusFacts;
|
|
49
|
+
/** The bank the parked-draft commands read, late-bound behind the picker it needs. */
|
|
50
|
+
readonly stash: () => PromptStash | undefined;
|
|
51
|
+
readonly render: () => void;
|
|
52
|
+
}
|
|
53
|
+
/** The operations the composing surface routes back into the command plane. */
|
|
54
|
+
export interface Commands {
|
|
55
|
+
/** Carry out one classified line, wherever it was asked for. */
|
|
56
|
+
readonly runSubmission: (submission: Submission) => void;
|
|
57
|
+
/**
|
|
58
|
+
* The commands one agent can run, or undefined when no registry answers.
|
|
59
|
+
*
|
|
60
|
+
* A scoped read rather than the registry itself: the registry stays with this
|
|
61
|
+
* owner, and completion only ever needs the names it offers.
|
|
62
|
+
*/
|
|
63
|
+
readonly registeredCommands: (agent: Agent) => readonly RegisteredCommand[] | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Take the screen, then open the session this run was launched for.
|
|
66
|
+
*
|
|
67
|
+
* The launch belongs to this owner because the resume it may ask is the same
|
|
68
|
+
* question the resume command asks, answered by the same list.
|
|
69
|
+
*/
|
|
70
|
+
readonly start: () => Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The router over everything the reader and the launcher can ask for.
|
|
74
|
+
*
|
|
75
|
+
* The registry is read from the composition rather than taken as a port: it is
|
|
76
|
+
* scoped to the agent in service, so its listing, lookup and dispatch all have
|
|
77
|
+
* to follow whichever agent the surface is driving now.
|
|
78
|
+
*/
|
|
79
|
+
export declare function createCommands(ctx: Context, ports: CommandsPorts): Commands;
|
|
80
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/surface/commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
4
|
+
import { defaultExportFile, transcriptToText } from "../export.js";
|
|
5
|
+
import { chordKeysLine, surfaceKeysLine } from "../input/keymap.js";
|
|
6
|
+
import { LOCAL_COMMANDS } from "../input/submission.js";
|
|
7
|
+
import { KEYMAP_LAYERS, keymapLayer } from "../keys-command.js";
|
|
8
|
+
import { clipboardSequence } from "../terminal/clipboard.js";
|
|
9
|
+
import { windowTitle } from "../terminal/title.js";
|
|
10
|
+
import { formatTokens } from "../tokens.js";
|
|
11
|
+
import { KeymapPicker } from "../ui/keymap-picker.js";
|
|
12
|
+
import { describeTodos, planSelectedActive, planToggleLine, readPlanState } from "../work.js";
|
|
13
|
+
import { createSessionPicker } from "./session-picker.js";
|
|
14
|
+
/**
|
|
15
|
+
* The router over everything the reader and the launcher can ask for.
|
|
16
|
+
*
|
|
17
|
+
* The registry is read from the composition rather than taken as a port: it is
|
|
18
|
+
* scoped to the agent in service, so its listing, lookup and dispatch all have
|
|
19
|
+
* to follow whichever agent the surface is driving now.
|
|
20
|
+
*/
|
|
21
|
+
export function createCommands(ctx, ports) {
|
|
22
|
+
const registry = () => ctx.get('commands');
|
|
23
|
+
// The copy is the one write that must land even while a child owns the tty:
|
|
24
|
+
// the reader asked for the last answer, not for a title, and a handoff that
|
|
25
|
+
// swallowed it would leave the clipboard silently empty.
|
|
26
|
+
const writeTty = (text) => ports.terminal.terminal.write(text);
|
|
27
|
+
/**
|
|
28
|
+
* Open the key map over the surface.
|
|
29
|
+
*
|
|
30
|
+
* Nothing here is a pick: a row names an action and the keys reaching it, so
|
|
31
|
+
* the id the list settles on is thrown away. What the reader came for is the
|
|
32
|
+
* list itself, and the filter that narrows it.
|
|
33
|
+
*/
|
|
34
|
+
const openKeyMap = (layer) => {
|
|
35
|
+
void ports.modals.openPicker(new KeymapPicker(ports.appearance.keymap, layer), undefined, 'popup');
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Write what the reader can see to a file.
|
|
39
|
+
*
|
|
40
|
+
* The base's `/export` downloads the log through the browser, which a
|
|
41
|
+
* terminal has no way to do, so this dumps the transcript the reader is
|
|
42
|
+
* looking at — the thing worth pasting into a message.
|
|
43
|
+
*/
|
|
44
|
+
const runExportCommand = (argument) => {
|
|
45
|
+
const path = resolve(argument === '' ? defaultExportFile(String(ports.session.activeSession())) : argument);
|
|
46
|
+
try {
|
|
47
|
+
writeFileSync(path, transcriptToText(ports.transcript.model.entries()), 'utf8');
|
|
48
|
+
ports.transcript.notice(`transcript written to ${path}`);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
ports.transcript.notice(`could not write ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
53
|
+
ports.render();
|
|
54
|
+
};
|
|
55
|
+
/** Show the todo list the agent has been keeping. */
|
|
56
|
+
const runTodoCommand = () => {
|
|
57
|
+
ports.transcript.notice(describeTodos(ports.transcript.workState().todos));
|
|
58
|
+
ports.render();
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Put the last answer on the reader's clipboard.
|
|
62
|
+
*
|
|
63
|
+
* The clipboard belongs to the terminal, so this asks it through OSC 52 —
|
|
64
|
+
* which is also the only route that works over SSH.
|
|
65
|
+
*/
|
|
66
|
+
const runCopyCommand = () => {
|
|
67
|
+
const last = [...ports.transcript.model.entries()].reverse().find(entry => entry.kind === 'assistant');
|
|
68
|
+
if (last === undefined || last.kind !== 'assistant') {
|
|
69
|
+
ports.transcript.notice('nothing to copy yet');
|
|
70
|
+
ports.render();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
writeTty(clipboardSequence(last.text));
|
|
74
|
+
ports.transcript.notice(`copied ${last.text.length} characters through the terminal`);
|
|
75
|
+
ports.render();
|
|
76
|
+
};
|
|
77
|
+
const helpText = () => {
|
|
78
|
+
const current = ports.session.drivingAgent()?.agent;
|
|
79
|
+
const registered = current === undefined || registry() === undefined
|
|
80
|
+
? []
|
|
81
|
+
: registry()?.list(current).map(command => `/${command.name}`) ?? [];
|
|
82
|
+
const commands = registered.length === 0 ? 'none registered yet' : registered.join(' ');
|
|
83
|
+
return `commands: ${commands} · surface: ${LOCAL_COMMANDS.join(' ')} · keys: ${surfaceKeysLine(ports.appearance.keymap())} · ${chordKeysLine(ports.appearance.keymap())}`;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Whether the agent this surface is driving is in plan mode.
|
|
87
|
+
*
|
|
88
|
+
* The dock's fold is the fallback, for a composition without the plan
|
|
89
|
+
* package: with the controller present its answer is the agent's own state
|
|
90
|
+
* rather than a replay of the events this surface happened to see.
|
|
91
|
+
*/
|
|
92
|
+
const planState = () => {
|
|
93
|
+
const current = ports.session.drivingAgent()?.agent;
|
|
94
|
+
if (current === undefined)
|
|
95
|
+
return undefined;
|
|
96
|
+
const presets = ctx.get('agentPresets');
|
|
97
|
+
return readPlanState({
|
|
98
|
+
direct: name => ctx.get(name),
|
|
99
|
+
forAgent: (target, name) => presets?.serviceFor(target, name),
|
|
100
|
+
}, current);
|
|
101
|
+
};
|
|
102
|
+
const planActive = () => planSelectedActive(planState(), ports.transcript.workState().planMode);
|
|
103
|
+
const runCommand = (name, line) => {
|
|
104
|
+
const current = ports.session.drivingAgent();
|
|
105
|
+
const commands = registry();
|
|
106
|
+
if (current === undefined) {
|
|
107
|
+
ports.transcript.notice('the agent is still starting; try again in a moment');
|
|
108
|
+
ports.render();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (commands === undefined || commands.find(current.agent, name) === undefined) {
|
|
112
|
+
ports.transcript.notice(`unknown command: /${name} — ${helpText()}`);
|
|
113
|
+
ports.render();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const controller = new AbortController();
|
|
117
|
+
void commands.execute(current.agent, line, [], controller.signal).then(execution => {
|
|
118
|
+
const result = execution?.result;
|
|
119
|
+
if (result === undefined)
|
|
120
|
+
return;
|
|
121
|
+
ports.transcript.notice(result.kind === 'error' ? `/${name} failed: ${result.text ?? 'no detail'}` : `/${name} ${result.text ?? 'done'}`);
|
|
122
|
+
ports.render();
|
|
123
|
+
}).catch((error) => {
|
|
124
|
+
ports.transcript.notice(`/${name} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
125
|
+
ports.render();
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Carry out one classified line, wherever it was asked for.
|
|
130
|
+
*
|
|
131
|
+
* A chord asks for the same things the command line does, so both arrive
|
|
132
|
+
* here: a chord cannot behave differently from the command it stands for.
|
|
133
|
+
*/
|
|
134
|
+
const runSubmission = (submission) => {
|
|
135
|
+
switch (submission.kind) {
|
|
136
|
+
case 'empty':
|
|
137
|
+
return;
|
|
138
|
+
case 'quit':
|
|
139
|
+
ports.terminal.requestExit(0);
|
|
140
|
+
return;
|
|
141
|
+
case 'model':
|
|
142
|
+
ports.route.runModelCommand(submission.argument);
|
|
143
|
+
return;
|
|
144
|
+
case 'preset':
|
|
145
|
+
ports.modes.runPresetCommand(submission.argument);
|
|
146
|
+
return;
|
|
147
|
+
case 'jobs':
|
|
148
|
+
ports.work.runJobsCommand(submission.argument);
|
|
149
|
+
return;
|
|
150
|
+
case 'rename':
|
|
151
|
+
ports.session.runRenameCommand(submission.title);
|
|
152
|
+
return;
|
|
153
|
+
case 'export':
|
|
154
|
+
runExportCommand(submission.path);
|
|
155
|
+
return;
|
|
156
|
+
case 'subagents':
|
|
157
|
+
ports.work.runSubagentsCommand(submission.argument);
|
|
158
|
+
return;
|
|
159
|
+
case 'fork':
|
|
160
|
+
ports.session.runForkCommand(submission.title);
|
|
161
|
+
return;
|
|
162
|
+
case 'new':
|
|
163
|
+
ports.session.runNewCommand(submission.title);
|
|
164
|
+
return;
|
|
165
|
+
case 'reload':
|
|
166
|
+
ports.session.runReloadCommand();
|
|
167
|
+
return;
|
|
168
|
+
case 'todo':
|
|
169
|
+
runTodoCommand();
|
|
170
|
+
return;
|
|
171
|
+
case 'theme':
|
|
172
|
+
ports.appearance.runThemeCommand(submission.argument);
|
|
173
|
+
return;
|
|
174
|
+
case 'keys': {
|
|
175
|
+
const layer = submission.argument === '' ? undefined : keymapLayer(submission.argument);
|
|
176
|
+
// A layer that does not exist is not a filter that matches nothing: the
|
|
177
|
+
// reader asked for something by name, so the answer names the names.
|
|
178
|
+
if (submission.argument !== '' && layer === undefined) {
|
|
179
|
+
ports.transcript.notice(`unknown layer "${submission.argument}" · ${KEYMAP_LAYERS.join(' ')}`);
|
|
180
|
+
ports.render();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
openKeyMap(layer);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
case 'copy':
|
|
187
|
+
runCopyCommand();
|
|
188
|
+
return;
|
|
189
|
+
case 'history':
|
|
190
|
+
ports.memory.runHistoryCommand(submission.argument);
|
|
191
|
+
return;
|
|
192
|
+
case 'stash':
|
|
193
|
+
// Submitting a command consumes the line it was typed on, so this path
|
|
194
|
+
// can only carry a draft it was given; parking the bar's own draft is
|
|
195
|
+
// what the chord is for.
|
|
196
|
+
if (submission.argument.trim() === '')
|
|
197
|
+
ports.transcript.notice('usage: /stash <draft>, or ctrl+x then s to park the editor');
|
|
198
|
+
else
|
|
199
|
+
void ports.stash()?.stashEditor(submission.argument);
|
|
200
|
+
return;
|
|
201
|
+
case 'stash-draft':
|
|
202
|
+
void ports.stash()?.stashEditor();
|
|
203
|
+
return;
|
|
204
|
+
case 'stash-pop':
|
|
205
|
+
void ports.stash()?.pop(submission.selector);
|
|
206
|
+
return;
|
|
207
|
+
case 'stash-apply':
|
|
208
|
+
void ports.stash()?.apply(submission.selector);
|
|
209
|
+
return;
|
|
210
|
+
case 'stash-list':
|
|
211
|
+
void ports.stash()?.list(String(ports.session.activeSession()));
|
|
212
|
+
return;
|
|
213
|
+
case 'stash-drop':
|
|
214
|
+
void ports.stash()?.drop(submission.selector);
|
|
215
|
+
return;
|
|
216
|
+
case 'stash-clear':
|
|
217
|
+
void ports.stash()?.clear();
|
|
218
|
+
return;
|
|
219
|
+
case 'editor':
|
|
220
|
+
ports.terminal.editDraft();
|
|
221
|
+
return;
|
|
222
|
+
case 'status': {
|
|
223
|
+
const facts = ports.statusFacts();
|
|
224
|
+
const context = facts.contextTokens === undefined
|
|
225
|
+
? undefined
|
|
226
|
+
: `context ${formatTokens(facts.contextTokens)}${facts.contextWindow === undefined ? '' : `/${formatTokens(facts.contextWindow)}`}`;
|
|
227
|
+
ports.transcript.notice([
|
|
228
|
+
`session ${ports.session.activeSession()}`,
|
|
229
|
+
ports.transcript.viewingChild() ? undefined : `viewing ${ports.transcript.viewed()}`,
|
|
230
|
+
facts.model === undefined
|
|
231
|
+
? undefined
|
|
232
|
+
: `model ${facts.provider === undefined ? '' : `${facts.provider}/`}${facts.model}${facts.effort === undefined ? '' : ` (${facts.effort})`}`,
|
|
233
|
+
facts.agentPreset === undefined ? undefined : `mode ${facts.agentPreset}`,
|
|
234
|
+
facts.preset === undefined ? undefined : `permissions ${facts.preset}`,
|
|
235
|
+
context,
|
|
236
|
+
facts.uncachedInputTokens === undefined && facts.outputTokens === undefined
|
|
237
|
+
? undefined
|
|
238
|
+
: `tokens in ${formatTokens(facts.uncachedInputTokens ?? 0)} out ${formatTokens(facts.outputTokens ?? 0)}`,
|
|
239
|
+
`cwd ${facts.cwd}`,
|
|
240
|
+
].filter(part => part !== undefined).join(' · '));
|
|
241
|
+
ports.render();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
case 'clear':
|
|
245
|
+
ports.transcript.reset();
|
|
246
|
+
ports.render();
|
|
247
|
+
return;
|
|
248
|
+
case 'undo':
|
|
249
|
+
ports.staged.undo();
|
|
250
|
+
return;
|
|
251
|
+
case 'redo':
|
|
252
|
+
ports.staged.redo();
|
|
253
|
+
return;
|
|
254
|
+
case 'help':
|
|
255
|
+
ports.transcript.notice(helpText());
|
|
256
|
+
ports.render();
|
|
257
|
+
return;
|
|
258
|
+
case 'resume':
|
|
259
|
+
void sessionPicker.chooseSession().then(picked => picked === undefined ? undefined : ports.session.switchSession(picked)).catch((error) => {
|
|
260
|
+
ports.transcript.notice(`could not resume: ${error instanceof Error ? error.message : String(error)}`);
|
|
261
|
+
ports.render();
|
|
262
|
+
});
|
|
263
|
+
return;
|
|
264
|
+
// Plan mode is a command the harness owns, so the chord asks the host for
|
|
265
|
+
// the state and names the command for the other one: one key, both ways.
|
|
266
|
+
case 'plan':
|
|
267
|
+
runCommand('plan', planToggleLine(planActive()));
|
|
268
|
+
return;
|
|
269
|
+
case 'command':
|
|
270
|
+
runCommand(submission.name, submission.line);
|
|
271
|
+
return;
|
|
272
|
+
case 'prompt': {
|
|
273
|
+
const driven = ports.session.drivingAgent();
|
|
274
|
+
if (driven === undefined) {
|
|
275
|
+
ports.transcript.notice('the agent is still starting; try again in a moment');
|
|
276
|
+
ports.render();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
// While a turn is running the human is steering it, not opening another.
|
|
280
|
+
if (ports.session.turnRunning())
|
|
281
|
+
driven.steer(submission.text);
|
|
282
|
+
else {
|
|
283
|
+
ports.route.adoptDefault();
|
|
284
|
+
void ports.staged.send(submission.text);
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* Where a bare `--resume` gets its list, and the ports it drives.
|
|
292
|
+
*
|
|
293
|
+
* The keyboard is taken through a port rather than here: how a picker holds
|
|
294
|
+
* it, and how long it may, is the modal owner's business.
|
|
295
|
+
*/
|
|
296
|
+
const sessionPicker = createSessionPicker(ctx, {
|
|
297
|
+
keymap: ports.appearance.keymap,
|
|
298
|
+
notice: ports.transcript.notice,
|
|
299
|
+
render: ports.render,
|
|
300
|
+
// A stored session's own mode can only disagree with one this run named, and
|
|
301
|
+
// only a roster can say whether the name it uses still exists.
|
|
302
|
+
validateStoredPreset: async (id) => {
|
|
303
|
+
if (ports.preset === undefined || ports.presetRoster === undefined)
|
|
304
|
+
return;
|
|
305
|
+
await ports.modes.presetFor(SessionId(id), true, undefined);
|
|
306
|
+
},
|
|
307
|
+
openPicker: (picker, vet) => ports.modals.openPicker(picker, vet),
|
|
308
|
+
});
|
|
309
|
+
/**
|
|
310
|
+
* Open the session this run was launched for.
|
|
311
|
+
*
|
|
312
|
+
* A bare `--resume` asks a question only the reader can answer, so the
|
|
313
|
+
* picker runs before anything is created: the agent is then opened on the
|
|
314
|
+
* chosen session rather than swapped afterwards, which would leave the first
|
|
315
|
+
* one's turn half-started.
|
|
316
|
+
*/
|
|
317
|
+
const boot = async () => {
|
|
318
|
+
if (!ports.launch.resumePicker) {
|
|
319
|
+
await ports.session.openAgent(ports.launch.sessionId, ports.launch.resume);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const picked = await sessionPicker.chooseSession();
|
|
323
|
+
if (picked === undefined) {
|
|
324
|
+
ports.terminal.requestExit(0);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
await ports.session.openAgent(picked, true);
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Take the screen, then open the session this run was launched for.
|
|
331
|
+
*
|
|
332
|
+
* A mode named on the command line is resolved FIRST: the alt screen swallows
|
|
333
|
+
* the launcher's own error output, so a mode this roster does not offer has to
|
|
334
|
+
* be answered while the shell still owns the terminal.
|
|
335
|
+
*/
|
|
336
|
+
const start = async () => {
|
|
337
|
+
// A composition row that is missing is a degradation the reader has to be
|
|
338
|
+
// told about, and this is the last moment before the alt screen closes over
|
|
339
|
+
// the shell's own output.
|
|
340
|
+
const missing = ports.missingOptional();
|
|
341
|
+
if (missing !== undefined)
|
|
342
|
+
ports.transcript.notice(missing);
|
|
343
|
+
// A named mode that disagrees with the one this session recorded is refused
|
|
344
|
+
// here as well as at the open, because the alternate screen closes over
|
|
345
|
+
// whatever was painted on it: the reader would see the failure, not the
|
|
346
|
+
// reason.
|
|
347
|
+
await ports.modes.validateLaunch(ports.launch);
|
|
348
|
+
ports.terminal.tui.start();
|
|
349
|
+
ports.terminal.writeTerminal(windowTitle(process.cwd(), 'ready'));
|
|
350
|
+
// Claiming the pane's agent row does not wait for a session: the pane is
|
|
351
|
+
// already on screen and already idle, and a session may still be chosen.
|
|
352
|
+
ports.terminal.herdr.publish();
|
|
353
|
+
// A refused settings edit is only visible now that the surface owns the
|
|
354
|
+
// screen; whatever the scope found before this point prints here instead.
|
|
355
|
+
ports.appearance.openNotices(message => ports.transcript.notice(message));
|
|
356
|
+
await boot();
|
|
357
|
+
};
|
|
358
|
+
return {
|
|
359
|
+
runSubmission,
|
|
360
|
+
registeredCommands: agent => registry()?.list(agent),
|
|
361
|
+
start,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
//# sourceMappingURL=commands.js.map
|