@itookit/dsht 0.3.8 → 0.5.2
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/README.i18n.yaml +2 -2
- package/README.md +33 -12
- package/README.zh.md +35 -14
- package/dist/catalog/controller.d.ts +26 -6
- package/dist/catalog/controller.js +73 -45
- package/dist/catalog/index.d.ts +1 -0
- package/dist/cli/dsht.js +206 -18
- package/dist/cli/startup.d.ts +40 -0
- package/dist/cli/startup.js +314 -0
- package/dist/cli/trace-summary.d.ts +78 -0
- package/dist/cli/trace-summary.js +241 -0
- package/dist/cli/verifier.d.ts +64 -0
- package/dist/cli/verifier.js +265 -0
- package/dist/contracts.d.ts +359 -0
- package/dist/contracts.js +1 -0
- package/dist/controller/commands.d.ts +47 -0
- package/dist/controller/commands.js +322 -0
- package/dist/controller/connection-streams.d.ts +22 -0
- package/dist/controller/connection-streams.js +105 -0
- package/dist/controller/connection.d.ts +24 -31
- package/dist/controller/connection.js +48 -111
- package/dist/controller/controller.d.ts +412 -178
- package/dist/controller/controller.js +713 -167
- package/dist/controller/foreground.d.ts +44 -0
- package/dist/controller/foreground.js +79 -0
- package/dist/controller/index.d.ts +8 -1
- package/dist/controller/index.js +5 -0
- package/dist/controller/loop-contract.d.ts +136 -0
- package/dist/controller/loop-contract.js +308 -0
- package/dist/controller/loop-coordinator.d.ts +48 -0
- package/dist/controller/loop-coordinator.js +647 -0
- package/dist/controller/loop-prompts-schema.d.ts +56 -0
- package/dist/controller/loop-prompts-schema.js +144 -0
- package/dist/controller/loop-prompts.d.ts +55 -0
- package/dist/controller/loop-prompts.generated.d.ts +104 -0
- package/dist/controller/loop-prompts.generated.js +185 -0
- package/dist/controller/loop-prompts.js +104 -0
- package/dist/controller/loop-protocols.d.ts +39 -0
- package/dist/controller/loop-protocols.js +115 -0
- package/dist/controller/loop.d.ts +275 -0
- package/dist/controller/loop.js +378 -0
- package/dist/controller/prompts.d.ts +54 -0
- package/dist/controller/prompts.js +162 -0
- package/dist/controller/trace-log.d.ts +45 -0
- package/dist/controller/trace-log.js +144 -0
- package/dist/controller/verifier.d.ts +130 -0
- package/dist/controller/verifier.js +75 -0
- package/dist/cost/controller.d.ts +1 -1
- package/dist/cost/controller.js +12 -5
- package/dist/cost/index.d.ts +1 -1
- package/dist/cost/index.js +1 -1
- package/dist/cost/ledger.d.ts +0 -1
- package/dist/cost/ledger.js +0 -1
- package/dist/cost/scanner.js +1 -0
- package/dist/json.d.ts +18 -0
- package/dist/json.js +19 -0
- package/dist/references.d.ts +25 -0
- package/dist/references.js +26 -0
- package/dist/session/connection-view.d.ts +2 -11
- package/dist/session/controller.d.ts +94 -105
- package/dist/session/controller.js +262 -536
- package/dist/session/history-reader.d.ts +32 -0
- package/dist/session/history-reader.js +170 -0
- package/dist/session/history.d.ts +6 -18
- package/dist/session/history.js +1 -24
- package/dist/session/index.d.ts +9 -4
- package/dist/session/index.js +7 -3
- package/dist/session/info.d.ts +20 -82
- package/dist/session/info.js +52 -25
- package/dist/session/interactions.d.ts +26 -0
- package/dist/session/interactions.js +75 -0
- package/dist/session/markdown.js +1 -1
- package/dist/session/math.js +1 -1
- package/dist/session/mutation-gate.d.ts +51 -0
- package/dist/session/mutation-gate.js +73 -0
- package/dist/session/navigation.d.ts +2 -89
- package/dist/session/navigation.js +2 -129
- package/dist/session/navigator.d.ts +47 -0
- package/dist/session/navigator.js +158 -0
- package/dist/session/peek.d.ts +38 -0
- package/dist/session/peek.js +103 -0
- package/dist/session/prompt-backfill.d.ts +23 -0
- package/dist/session/prompt-backfill.js +88 -0
- package/dist/session/references.d.ts +2 -20
- package/dist/session/references.js +1 -26
- package/dist/session/runtime.d.ts +26 -0
- package/dist/session/runtime.js +28 -0
- package/dist/session/state.d.ts +20 -0
- package/dist/session/state.js +1 -0
- package/dist/session/telemetry.d.ts +25 -17
- package/dist/session/telemetry.js +66 -60
- package/dist/session/transcript.d.ts +5 -7
- package/dist/session/transcript.js +2 -15
- package/dist/session/types.d.ts +25 -0
- package/dist/session/types.js +0 -1
- package/dist/session-title.d.ts +9 -0
- package/dist/session-title.js +21 -0
- package/dist/shell/controller.d.ts +31 -1
- package/dist/shell/controller.js +34 -2
- package/dist/shell/index.d.ts +3 -3
- package/dist/shell/index.js +2 -2
- package/dist/shell/runner.d.ts +10 -0
- package/dist/shell/runner.js +48 -9
- package/dist/slash/index.d.ts +10 -0
- package/dist/slash/index.js +7 -0
- package/dist/slash/parse.d.ts +42 -0
- package/dist/slash/parse.js +259 -0
- package/dist/slash/pipeline.d.ts +140 -0
- package/dist/slash/pipeline.js +115 -0
- package/dist/slash/registry.d.ts +88 -0
- package/dist/slash/registry.js +177 -0
- package/dist/slash/types.d.ts +126 -0
- package/dist/slash/types.js +1 -0
- package/dist/state.d.ts +16 -18
- package/dist/state.js +4 -3
- package/dist/text.d.ts +28 -0
- package/dist/text.js +55 -0
- package/dist/transport/client.d.ts +4 -3
- package/dist/transport/client.js +71 -25
- package/dist/transport/events.d.ts +104 -0
- package/dist/transport/events.js +149 -0
- package/dist/transport/wire.d.ts +9 -17
- package/dist/transport/wire.js +2 -27
- package/dist/ui/app.js +750 -550
- package/dist/ui/chat/header.js +1 -1
- package/dist/ui/chat/history-view.d.ts +1 -1
- package/dist/ui/chat/loop-status.d.ts +11 -0
- package/dist/ui/chat/loop-status.js +28 -0
- package/dist/ui/chat/navigation-model.d.ts +86 -0
- package/dist/ui/chat/navigation-model.js +107 -0
- package/dist/ui/chat/shell-view.d.ts +17 -2
- package/dist/ui/chat/shell-view.js +45 -3
- package/dist/ui/chat/status.d.ts +47 -3
- package/dist/ui/chat/status.js +65 -50
- package/dist/ui/chat/use-history-view.d.ts +69 -0
- package/dist/ui/chat/use-history-view.js +123 -0
- package/dist/ui/chat/viewport.d.ts +1 -1
- package/dist/ui/dialogs/cost.d.ts +21 -4
- package/dist/ui/dialogs/cost.js +7 -12
- package/dist/ui/dialogs/index.d.ts +22 -5
- package/dist/ui/dialogs/index.js +19 -3
- package/dist/ui/dialogs/loop.d.ts +43 -0
- package/dist/ui/dialogs/loop.js +224 -0
- package/dist/ui/dialogs/peek.d.ts +25 -0
- package/dist/ui/dialogs/peek.js +35 -0
- package/dist/ui/dialogs/picker.d.ts +2 -0
- package/dist/ui/dialogs/picker.js +4 -2
- package/dist/ui/dialogs/use-panels.d.ts +53 -0
- package/dist/ui/dialogs/use-panels.js +51 -0
- package/dist/ui/input/mouse.d.ts +12 -2
- package/dist/ui/input/mouse.js +20 -7
- package/dist/ui/input/references.d.ts +1 -1
- package/dist/ui/input/use-composer.d.ts +35 -0
- package/dist/ui/input/use-composer.js +109 -0
- package/dist/ui/input/use-deferred-lines.d.ts +16 -0
- package/dist/ui/input/use-deferred-lines.js +54 -0
- package/dist/ui/input/use-history-recall.d.ts +20 -0
- package/dist/ui/input/use-history-recall.js +47 -0
- package/dist/ui/status/model.d.ts +7 -0
- package/dist/ui/status/model.js +5 -0
- package/dist/ui/theme/index.d.ts +1 -1
- package/package.json +6 -4
- package/dist/ui/commands/parse.d.ts +0 -104
- package/dist/ui/commands/parse.js +0 -135
- package/dist/ui/commands/registry.d.ts +0 -33
- package/dist/ui/commands/registry.js +0 -73
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/** Command discovery catalog shared by Tab completion and the `/help` panel. */
|
|
2
|
+
export const COMMAND_HINTS = [
|
|
3
|
+
{ command: '/ws', usage: '[name or ID]', description: 'List/switch workspaces; --delete name removes registration' },
|
|
4
|
+
{ command: '/resume', usage: '[title or ID]', description: 'List/switch sessions; --delete ID archives with confirmation' },
|
|
5
|
+
{ command: '/model', usage: '[provider model [effort]]', description: 'Choose a model and reasoning effort for subsequent requests' },
|
|
6
|
+
{ command: '/new', description: 'Create a session in the selected workspace' },
|
|
7
|
+
{ command: '/copy', description: 'Freeze for native selection; Esc resumes (Ctrl+S shortcut)' },
|
|
8
|
+
{ command: '/latest', description: 'Return to the live conversation' },
|
|
9
|
+
{ command: '/older', description: 'Load earlier history' },
|
|
10
|
+
{ command: '/history', usage: '[text]', description: 'List your prompts, optionally filtered' },
|
|
11
|
+
{ command: '/prompt', usage: '[text]', description: 'Use a saved shortcut prompt, or save the text as one' },
|
|
12
|
+
{ command: '/search', usage: 'text', description: 'Search history page by page and open a match' },
|
|
13
|
+
{ command: '/ssearch', usage: 'text', description: 'Search sessions in the current workspace' },
|
|
14
|
+
{ command: '/wsearch', usage: 'text', description: 'Search sessions across all workspaces' },
|
|
15
|
+
{ command: '/compact', description: 'Compact older history while the session is idle' },
|
|
16
|
+
{ command: '/cancel', description: 'Cancel the active turn' },
|
|
17
|
+
{ command: '/queue', description: 'View and remove pending input' },
|
|
18
|
+
{ command: '/plan', usage: '[off|message]', description: 'Enter or leave host plan mode' },
|
|
19
|
+
{ command: '/goal', usage: '[action|objective]', description: 'View or manage the host task goal' },
|
|
20
|
+
{ command: '/permission', usage: '[preset]', description: 'View or switch the host permission preset' },
|
|
21
|
+
{ command: '/feedback', usage: 'text', description: 'Record feedback about the session' },
|
|
22
|
+
{ command: '/handoff', description: 'Delete local HANDOFF.md, then have the agent write a handoff' },
|
|
23
|
+
{ command: '/loop', usage: '[name|stop] [score] [tries]', description: 'Run a loop.yaml record; confirm defaults; stop/answer/abort', exactOnly: true },
|
|
24
|
+
{ command: '/export', usage: '[local.zip]', description: 'Save the session log ZIP to a new local file' },
|
|
25
|
+
{ command: '/export-html', usage: '[local.html]', description: 'Save loaded conversation with diagrams and math as offline HTML' },
|
|
26
|
+
{ command: '/coredump', usage: '[tag]', description: 'Write a V8 heap snapshot for memory diagnosis' },
|
|
27
|
+
{ command: '/allow', description: 'Approve the pending request once', exactOnly: true },
|
|
28
|
+
{ command: '/deny', description: 'Reject the pending request', exactOnly: true },
|
|
29
|
+
{ command: '/status', description: 'Show full session status details' },
|
|
30
|
+
{ command: '/cost', description: 'Show cost estimates and refresh usage' },
|
|
31
|
+
{ command: '/think', usage: '[seq or live]', description: 'Inspect reasoning with user prompt summaries' },
|
|
32
|
+
{ command: '/help', description: 'Show this command list' },
|
|
33
|
+
{ command: '/quit', description: 'Exit dsht', exactOnly: true },
|
|
34
|
+
];
|
|
35
|
+
/** Command names only, in catalog order. */
|
|
36
|
+
export const COMMANDS = COMMAND_HINTS.map(hint => hint.command);
|
|
37
|
+
/** Command column text per hint, aligned in the `/help` panel. */
|
|
38
|
+
export const COMMAND_LABELS = COMMAND_HINTS.map(hint => hint.usage === undefined ? hint.command : `${hint.command} ${hint.usage}`);
|
|
39
|
+
/** Widest command column, so descriptions start on one column. */
|
|
40
|
+
export const COMMAND_LABEL_WIDTH = Math.max(...COMMAND_LABELS.map(label => label.length)) + 2;
|
|
41
|
+
/** Commands that may only run when typed in full; a prefix of one is never executed on Enter. */
|
|
42
|
+
const EXACT_ONLY = new Set(COMMAND_HINTS.filter(hint => hint.exactOnly).map(hint => hint.command));
|
|
43
|
+
/** Commands whose name starts with one token, in catalog order.
|
|
44
|
+
* @param token - First word of a draft, such as `/pro`.
|
|
45
|
+
* @returns Matching command names, empty when the token names nothing.
|
|
46
|
+
*/
|
|
47
|
+
export function commandMatches(token) {
|
|
48
|
+
return COMMANDS.filter(command => command.startsWith(token));
|
|
49
|
+
}
|
|
50
|
+
/** Resolve one typed command token to the command it names.
|
|
51
|
+
*
|
|
52
|
+
* An exact command resolves to itself. A prefix that matches exactly one command resolves to that
|
|
53
|
+
* command too, so Enter can run it without typing the whole name; an unknown or ambiguous token,
|
|
54
|
+
* and any prefix of an `exactOnly` command, resolves to undefined so the draft stays as typed.
|
|
55
|
+
* @param token - First word of a draft, without its arguments.
|
|
56
|
+
* @returns The command name, or undefined when the token does not name one command.
|
|
57
|
+
*/
|
|
58
|
+
export function resolveCommand(token) {
|
|
59
|
+
if (COMMANDS.includes(token))
|
|
60
|
+
return token;
|
|
61
|
+
const matches = commandMatches(token);
|
|
62
|
+
const only = matches.length === 1 ? matches[0] : undefined;
|
|
63
|
+
return only !== undefined && !EXACT_ONLY.has(only) ? only : undefined;
|
|
64
|
+
}
|
|
65
|
+
/** Routing policy per parsed command kind.
|
|
66
|
+
*
|
|
67
|
+
* The pipeline reads this table instead of enumerating kinds itself, so a new command declares its
|
|
68
|
+
* constraints next to its syntax and no other module learns about it. A kind absent here has no
|
|
69
|
+
* constraint and may run from any screen, even while an answer is pending.
|
|
70
|
+
*/
|
|
71
|
+
/** A command that would write to the conversation another turn is already writing.
|
|
72
|
+
*
|
|
73
|
+
* `compact`, `handoff` and a `/loop` run each submit work of their own to the same session, so starting
|
|
74
|
+
* one mid-turn would interleave two writers on one conversation. The list is deliberately short: the
|
|
75
|
+
* host owns the busy rules of its own commands (`/plan`, `/goal`, `/model`, …), and a client-side deny
|
|
76
|
+
* there would contradict what the host would have accepted.
|
|
77
|
+
*/
|
|
78
|
+
/** A command that writes to the conversation another turn is writing: it runs when that turn ends.
|
|
79
|
+
*
|
|
80
|
+
* These are the operator's own commands — a compaction, a handoff, a review to start — and refusing
|
|
81
|
+
* them outright would make "I want this next" impossible to express while an agent works.
|
|
82
|
+
*/
|
|
83
|
+
const QUEUES_WHILE_RUNNING = { duringTurn: 'queue', duringLoop: 'queue' };
|
|
84
|
+
/** A command that must not run while another turn or loop owns the conversation at all. */
|
|
85
|
+
const CONFLICTS_WITH_RUNNING = { duringTurn: 'deny', duringLoop: 'deny' };
|
|
86
|
+
/** Commands that answer the operator or the host and must reach the session while it is busy.
|
|
87
|
+
*
|
|
88
|
+
* `whileBusy: 'run'` is the foreground-slot equivalent: cancelling an agent turn or settling the
|
|
89
|
+
* interaction holding it is independent of whatever long operation the client is showing, and the
|
|
90
|
+
* operator must never be told to wait for an export before they can stop the agent.
|
|
91
|
+
*/
|
|
92
|
+
const ANSWERS_WHILE_RUNNING = { duringTurn: 'run', duringLoop: 'run' };
|
|
93
|
+
const ANSWERS_WHILE_BUSY = { whileBusy: 'run' };
|
|
94
|
+
export const COMMAND_POLICY = {
|
|
95
|
+
shell: { requiresSession: true },
|
|
96
|
+
models: { requiresSession: true },
|
|
97
|
+
queue: { requiresSession: true, requiresNoInteraction: true },
|
|
98
|
+
history: { requiresSession: true },
|
|
99
|
+
historySearch: { requiresSession: true },
|
|
100
|
+
prompts: { requiresSession: true },
|
|
101
|
+
// Reading reasoning while the agent works is the point of the panel.
|
|
102
|
+
think: { requiresSession: true, ...ANSWERS_WHILE_RUNNING },
|
|
103
|
+
panel: { ...ANSWERS_WHILE_RUNNING },
|
|
104
|
+
copy: { ...ANSWERS_WHILE_RUNNING },
|
|
105
|
+
// Cancelling the turn, or settling the interaction that is holding it, must never be refused.
|
|
106
|
+
cancel: { ...ANSWERS_WHILE_RUNNING, ...ANSWERS_WHILE_BUSY },
|
|
107
|
+
approval: { ...ANSWERS_WHILE_RUNNING, ...ANSWERS_WHILE_BUSY },
|
|
108
|
+
compact: { requiresSession: true, ...QUEUES_WHILE_RUNNING },
|
|
109
|
+
handoff: { requiresSession: true, requiresNoInteraction: true, ...QUEUES_WHILE_RUNNING },
|
|
110
|
+
loop: { requiresSession: true, requiresNoInteraction: true, ...QUEUES_WHILE_RUNNING },
|
|
111
|
+
// A record list is a surface for the draft being typed; by the time a turn ends, the operator has
|
|
112
|
+
// moved on, so offering it later would be noise rather than help.
|
|
113
|
+
loops: { requiresSession: true, requiresNoInteraction: true, ...CONFLICTS_WITH_RUNNING },
|
|
114
|
+
// The host decides whether its own registered commands may run while a turn is in flight.
|
|
115
|
+
hostCommand: { requiresSession: true, requiresNoInteraction: true },
|
|
116
|
+
// Stopping is a control-lane action: it stays allowed while an approval waits, and while the very
|
|
117
|
+
// line it is meant to interrupt still owns the controller, because that is exactly when it is needed.
|
|
118
|
+
loopStop: { requiresSession: true, control: true, ...ANSWERS_WHILE_RUNNING, ...ANSWERS_WHILE_BUSY },
|
|
119
|
+
// Answering a paused run and ending it must both work while the loop is what is running.
|
|
120
|
+
loopAnswer: { requiresSession: true, ...ANSWERS_WHILE_RUNNING, ...ANSWERS_WHILE_BUSY },
|
|
121
|
+
export: { requiresSession: true },
|
|
122
|
+
exportHtml: { requiresSession: true },
|
|
123
|
+
};
|
|
124
|
+
/** Longest common prefix of the candidate commands, so Tab can extend an ambiguous draft.
|
|
125
|
+
* @param values - Command candidates.
|
|
126
|
+
* @returns The shared leading prefix.
|
|
127
|
+
*/
|
|
128
|
+
export function commonPrefix(values) {
|
|
129
|
+
let prefix = values[0] ?? '';
|
|
130
|
+
for (const value of values) {
|
|
131
|
+
let index = 0;
|
|
132
|
+
while (index < prefix.length && index < value.length && prefix[index] === value[index])
|
|
133
|
+
index++;
|
|
134
|
+
prefix = prefix.slice(0, index);
|
|
135
|
+
}
|
|
136
|
+
return prefix;
|
|
137
|
+
}
|
|
138
|
+
/** Complete the leading slash command; an ambiguous draft extends to the shared prefix.
|
|
139
|
+
* @param input - Current composer draft.
|
|
140
|
+
* @returns The completed draft, or undefined when nothing can be completed.
|
|
141
|
+
*/
|
|
142
|
+
export function completeCommand(input) {
|
|
143
|
+
if (!input.startsWith('/') || input.includes(' '))
|
|
144
|
+
return undefined;
|
|
145
|
+
const matches = COMMANDS.filter(command => command.startsWith(input));
|
|
146
|
+
const only = matches.length === 1 ? matches[0] : undefined;
|
|
147
|
+
if (only !== undefined)
|
|
148
|
+
return `${only} `;
|
|
149
|
+
const prefix = commonPrefix(matches);
|
|
150
|
+
return prefix.length > input.length ? prefix : undefined;
|
|
151
|
+
}
|
|
152
|
+
/** Commands matching the current draft, shown under the composer.
|
|
153
|
+
* @param input - Current composer draft.
|
|
154
|
+
* @returns Candidate command names.
|
|
155
|
+
*/
|
|
156
|
+
export function suggestedCommands(input) {
|
|
157
|
+
return COMMANDS.filter(command => input.startsWith('/') && !input.includes(' ') && command.startsWith(input));
|
|
158
|
+
}
|
|
159
|
+
/** The catalog entry whose arguments the draft is currently typing.
|
|
160
|
+
*
|
|
161
|
+
* A usage line is only useful once the name is settled and arguments have begun, so this requires
|
|
162
|
+
* whitespace after a token that names one command: `/model ` hints the model command, while `/co `
|
|
163
|
+
* names none and `/think` is still completing a name. A unique prefix counts, because the same prefix
|
|
164
|
+
* already runs that command.
|
|
165
|
+
* @param input - Current composer draft.
|
|
166
|
+
* @returns The command's hint, or undefined when no arguments are being typed.
|
|
167
|
+
*/
|
|
168
|
+
export function argumentHint(input) {
|
|
169
|
+
if (!input.startsWith('/'))
|
|
170
|
+
return undefined;
|
|
171
|
+
const space = input.search(/\s/);
|
|
172
|
+
if (space <= 0)
|
|
173
|
+
return undefined;
|
|
174
|
+
const token = input.slice(0, space);
|
|
175
|
+
const command = COMMANDS.includes(token) ? token : resolveCommand(token);
|
|
176
|
+
return command === undefined ? undefined : COMMAND_HINTS.find(hint => hint.command === command);
|
|
177
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/** Command values shared by syntax, discovery and admission; no parser dependency. */
|
|
2
|
+
/** One parsed command; every side effect stays with the caller. */
|
|
3
|
+
export type Command = {
|
|
4
|
+
kind: 'ignore';
|
|
5
|
+
} | {
|
|
6
|
+
kind: 'copy';
|
|
7
|
+
} | {
|
|
8
|
+
kind: 'quit';
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'panel';
|
|
11
|
+
panel: 'cost' | 'status' | 'help';
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'remove';
|
|
14
|
+
target: 'workspace' | 'session';
|
|
15
|
+
query: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'navigate';
|
|
18
|
+
target: 'workspace' | 'session';
|
|
19
|
+
query?: string;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'latest';
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'models';
|
|
24
|
+
args: string[];
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'queue';
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'newSession';
|
|
29
|
+
}
|
|
30
|
+
/** Open the saved-prompt picker, or save the following text as a shortcut prompt. */
|
|
31
|
+
| {
|
|
32
|
+
kind: 'prompts';
|
|
33
|
+
} | {
|
|
34
|
+
kind: 'savePrompt';
|
|
35
|
+
text: string;
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'history';
|
|
38
|
+
query: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'sessionSearch';
|
|
41
|
+
command: '/ssearch' | '/wsearch';
|
|
42
|
+
query: string;
|
|
43
|
+
} | {
|
|
44
|
+
kind: 'historySearch';
|
|
45
|
+
query: string;
|
|
46
|
+
} | {
|
|
47
|
+
kind: 'think';
|
|
48
|
+
target: string;
|
|
49
|
+
} | {
|
|
50
|
+
kind: 'older';
|
|
51
|
+
} | {
|
|
52
|
+
kind: 'compact';
|
|
53
|
+
}
|
|
54
|
+
/** Clear the client's own HANDOFF.md, then ask the agent to write a fresh session handoff. */
|
|
55
|
+
| {
|
|
56
|
+
kind: 'handoff';
|
|
57
|
+
}
|
|
58
|
+
/** Start the client-driven scored loop for one `loop.yaml` record. */
|
|
59
|
+
| {
|
|
60
|
+
kind: 'loop';
|
|
61
|
+
name: string;
|
|
62
|
+
options: LoopOptions;
|
|
63
|
+
}
|
|
64
|
+
/** Offer the `loop.yaml` records so one can be chosen instead of typed. */
|
|
65
|
+
| {
|
|
66
|
+
kind: 'loops';
|
|
67
|
+
}
|
|
68
|
+
/** Stop the running loop, running or paused. */
|
|
69
|
+
| {
|
|
70
|
+
kind: 'loopStop';
|
|
71
|
+
}
|
|
72
|
+
/** Answer a paused run, so the current artifact is judged again with the operator's addition. */
|
|
73
|
+
| {
|
|
74
|
+
kind: 'loopAnswer';
|
|
75
|
+
text: string;
|
|
76
|
+
} | {
|
|
77
|
+
kind: 'cancel';
|
|
78
|
+
} | {
|
|
79
|
+
kind: 'approval';
|
|
80
|
+
allowed: boolean;
|
|
81
|
+
} | {
|
|
82
|
+
kind: 'hostCommand';
|
|
83
|
+
line: string;
|
|
84
|
+
}
|
|
85
|
+
/** A local `!` command, run on this machine rather than the host. */
|
|
86
|
+
| {
|
|
87
|
+
kind: 'shell';
|
|
88
|
+
command: string;
|
|
89
|
+
} | {
|
|
90
|
+
kind: 'export';
|
|
91
|
+
destination?: string;
|
|
92
|
+
} | {
|
|
93
|
+
kind: 'exportHtml';
|
|
94
|
+
destination?: string;
|
|
95
|
+
} | {
|
|
96
|
+
kind: 'coredump';
|
|
97
|
+
tag?: string;
|
|
98
|
+
} | {
|
|
99
|
+
kind: 'error';
|
|
100
|
+
message: string;
|
|
101
|
+
} | {
|
|
102
|
+
kind: 'prompt';
|
|
103
|
+
text: string;
|
|
104
|
+
};
|
|
105
|
+
/** Options carried by `/loop`.
|
|
106
|
+
*
|
|
107
|
+
* Only the fields the operator actually typed are present, so the syntax layer validates each value
|
|
108
|
+
* it sees and the application owns the defaults — command line first, then the record's `defaults`,
|
|
109
|
+
* then the global ones.
|
|
110
|
+
*/
|
|
111
|
+
export interface LoopOptions {
|
|
112
|
+
/** First step to run; default 1. */
|
|
113
|
+
from?: number;
|
|
114
|
+
/** Last step to run; default the record's last step. */
|
|
115
|
+
to?: number;
|
|
116
|
+
/** Passing score per step, 0-10 and possibly fractional; default the record's, else 8. */
|
|
117
|
+
score?: number;
|
|
118
|
+
/** Attempts allowed per step; default the record's, else 10. */
|
|
119
|
+
tries?: number;
|
|
120
|
+
/** Values that replace the record's own `vars` for this run, such as the document under review.
|
|
121
|
+
*
|
|
122
|
+
* The interactive form fills this in; the command line has no syntax for it, because the record —
|
|
123
|
+
* not the syntax layer — is what knows which names exist.
|
|
124
|
+
*/
|
|
125
|
+
vars?: Readonly<Record<string, string>>;
|
|
126
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/state.d.ts
CHANGED
|
@@ -1,32 +1,28 @@
|
|
|
1
|
-
/** Application state shared by the controller facade and every domain controller. */
|
|
2
|
-
import { SessionInfo } from './session/info.ts';
|
|
3
1
|
import type { HistorySearch, RemovalTarget } from './session/types.ts';
|
|
2
|
+
import type { SessionState } from './session/state.ts';
|
|
3
|
+
import type { ShellSnapshot } from './shell/index.ts';
|
|
4
4
|
import type { ObjectValue } from './transport/wire.ts';
|
|
5
5
|
/** State shared by the picker and conversation view. */
|
|
6
|
-
export interface State {
|
|
6
|
+
export interface State extends SessionState {
|
|
7
7
|
version: number;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
showAllSessions: boolean;
|
|
16
|
-
workspaceId?: string;
|
|
17
|
-
sessionId?: string;
|
|
18
|
-
pending: ObjectValue[];
|
|
8
|
+
/** The last failure the application recorded, for a diagnostic line and a refusal's reason.
|
|
9
|
+
*
|
|
10
|
+
* Internal on purpose (13.2-D2): a command's failure fact is its `CommandResult.outcome` plus the
|
|
11
|
+
* trace, and this is only what a connection, a session stream or an action envelope left behind.
|
|
12
|
+
* Whether an operation is running is not stored here — that is `queries.foreground`.
|
|
13
|
+
*/
|
|
14
|
+
lastFailure: string;
|
|
19
15
|
controlError?: string;
|
|
20
16
|
modelError?: string;
|
|
21
17
|
presetError?: string;
|
|
22
18
|
presets?: ObjectValue[];
|
|
23
19
|
defaultModel?: ObjectValue;
|
|
24
|
-
/**
|
|
25
|
-
|
|
20
|
+
/** Local `!` runs, published so the UI never reads the shell service object. */
|
|
21
|
+
shell: ShellSnapshot;
|
|
26
22
|
}
|
|
27
|
-
/**
|
|
23
|
+
/** Application state access used by connection orchestration. Feature domains use their own ports. */
|
|
28
24
|
export interface ControllerStore {
|
|
29
|
-
/** Current
|
|
25
|
+
/** Current application snapshot; nested session data remains owned by the session domain. */
|
|
30
26
|
readonly state: State;
|
|
31
27
|
/** Publish a state patch and notify observers. */
|
|
32
28
|
update(patch: Partial<State>): void;
|
|
@@ -34,6 +30,8 @@ export interface ControllerStore {
|
|
|
34
30
|
selection(): number;
|
|
35
31
|
/** Advance the selector generation when the selected workspace or session changes. */
|
|
36
32
|
bumpSelection(): void;
|
|
33
|
+
/** Whether an operation owns the client's foreground slot right now. */
|
|
34
|
+
busy(): boolean;
|
|
37
35
|
}
|
|
38
36
|
/** Build the initial state before any connection exists.
|
|
39
37
|
* @returns A fresh state whose transcript is empty and disconnected.
|
package/dist/state.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
/** Application
|
|
1
|
+
/** Application composition of session state and the other domains' published snapshots. */
|
|
2
2
|
import { SessionInfo } from "./session/info.js";
|
|
3
3
|
/** Build the initial state before any connection exists.
|
|
4
4
|
* @returns A fresh state whose transcript is empty and disconnected.
|
|
5
5
|
*/
|
|
6
6
|
export function initialState() {
|
|
7
|
-
return { version: 0,
|
|
8
|
-
|
|
7
|
+
return { version: 0, lastFailure: '', online: false, screen: 'workspaces',
|
|
8
|
+
status: 'Connecting…', workspaces: [], sessions: [], showAllSessions: false, pending: [],
|
|
9
|
+
shell: { running: false, blocks: [] }, session: new SessionInfo() };
|
|
9
10
|
}
|
package/dist/text.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Terminal text hygiene shared by every layer; it knows nothing about the wire protocol.
|
|
2
|
+
*
|
|
3
|
+
* This module is deliberately dependency-free. It lives at the source root so `session/`, `ui/`
|
|
4
|
+
* and `cli/` can clean remote text without importing the transport domain — the boundary rule that
|
|
5
|
+
* `ui/` must never reach into `transport/`.
|
|
6
|
+
*/
|
|
7
|
+
/** Remove terminal controls from remote text while retaining line breaks and tabs. */
|
|
8
|
+
export declare function safeText(value: string): string;
|
|
9
|
+
/** Return a displayable error without serializing request headers or credentials. */
|
|
10
|
+
export declare function errorText(error: unknown): string;
|
|
11
|
+
/** Reduce text that must appear in a diagnostic log to a fact that is safe to paste elsewhere.
|
|
12
|
+
*
|
|
13
|
+
* A trace or a bug report leaves the machine, so text quoted into one is first stripped of the two
|
|
14
|
+
* things that most often carry content out with it: absolute paths (which name a user, a project or a
|
|
15
|
+
* client) and anything shaped like a credential. Whitespace collapses so a multi-line child's output
|
|
16
|
+
* stays one field, and the result is bounded. This is for *diagnostic* text only: a message the
|
|
17
|
+
* operator is meant to read in full never goes through it.
|
|
18
|
+
* @param text - Text captured for diagnosis.
|
|
19
|
+
* @param limit - Longest result, in characters.
|
|
20
|
+
* @returns One bounded line with paths and credentials replaced.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sanitizeTraceText(text: string, limit?: number): string;
|
|
23
|
+
/** Fit a tool operation to one terminal row without exposing the result body.
|
|
24
|
+
* @param text - Tool name, status icon and optional operation.
|
|
25
|
+
* @param width - Available terminal columns.
|
|
26
|
+
* @returns A single line with an ellipsis when shortened.
|
|
27
|
+
*/
|
|
28
|
+
export declare function toolLine(text: string, width: number): string;
|
package/dist/text.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Terminal text hygiene shared by every layer; it knows nothing about the wire protocol.
|
|
2
|
+
*
|
|
3
|
+
* This module is deliberately dependency-free. It lives at the source root so `session/`, `ui/`
|
|
4
|
+
* and `cli/` can clean remote text without importing the transport domain — the boundary rule that
|
|
5
|
+
* `ui/` must never reach into `transport/`.
|
|
6
|
+
*/
|
|
7
|
+
import sliceAnsi from 'slice-ansi';
|
|
8
|
+
/** Remove terminal controls from remote text while retaining line breaks and tabs. */
|
|
9
|
+
export function safeText(value) {
|
|
10
|
+
return value.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, '');
|
|
11
|
+
}
|
|
12
|
+
/** Return a displayable error without serializing request headers or credentials. */
|
|
13
|
+
export function errorText(error) {
|
|
14
|
+
return safeText(error instanceof Error ? error.message : String(error));
|
|
15
|
+
}
|
|
16
|
+
/** Reduce text that must appear in a diagnostic log to a fact that is safe to paste elsewhere.
|
|
17
|
+
*
|
|
18
|
+
* A trace or a bug report leaves the machine, so text quoted into one is first stripped of the two
|
|
19
|
+
* things that most often carry content out with it: absolute paths (which name a user, a project or a
|
|
20
|
+
* client) and anything shaped like a credential. Whitespace collapses so a multi-line child's output
|
|
21
|
+
* stays one field, and the result is bounded. This is for *diagnostic* text only: a message the
|
|
22
|
+
* operator is meant to read in full never goes through it.
|
|
23
|
+
* @param text - Text captured for diagnosis.
|
|
24
|
+
* @param limit - Longest result, in characters.
|
|
25
|
+
* @returns One bounded line with paths and credentials replaced.
|
|
26
|
+
*/
|
|
27
|
+
export function sanitizeTraceText(text, limit = 200) {
|
|
28
|
+
const flat = safeText(text)
|
|
29
|
+
.replace(/\s+/gu, ' ')
|
|
30
|
+
.trim()
|
|
31
|
+
// A URL keeps its origin — that is the diagnosable part — while its path goes.
|
|
32
|
+
.replace(/\b([a-z][a-z0-9+.-]*:\/\/[^\s/'"]+)(\/[^\s'"]*)?/giu, '$1<path>')
|
|
33
|
+
// A Windows drive path.
|
|
34
|
+
.replace(/\b[A-Za-z]:\\[^\s'"]*/gu, '<path>')
|
|
35
|
+
// A Unix absolute path, only from a boundary and only with a directory part, so an identifier such
|
|
36
|
+
// as `session/agent-busy` is left alone.
|
|
37
|
+
.replace(/(^|[\s'"=(:,])(?:\/(?:[^\s'":,)]+\/)+[^\s'":,)]*)/gu, '$1<path>')
|
|
38
|
+
// Credentials last: a secret can sit inside what the earlier rules replaced.
|
|
39
|
+
.replace(/\b(?:bearer|token|apikey|api[_-]?key|secret|password|passwd|authorization|cookie)\b\s*[:=]\s*\S+/giu, '<redacted>')
|
|
40
|
+
.replace(/\bsk-[A-Za-z0-9_-]{8,}\b/gu, '<redacted>')
|
|
41
|
+
.replace(/\b[A-Fa-f0-9]{32,}\b/gu, '<redacted>');
|
|
42
|
+
return flat.length <= limit ? flat : `${flat.slice(0, limit)}…`;
|
|
43
|
+
}
|
|
44
|
+
/** Fit a tool operation to one terminal row without exposing the result body.
|
|
45
|
+
* @param text - Tool name, status icon and optional operation.
|
|
46
|
+
* @param width - Available terminal columns.
|
|
47
|
+
* @returns A single line with an ellipsis when shortened.
|
|
48
|
+
*/
|
|
49
|
+
export function toolLine(text, width) {
|
|
50
|
+
const clean = safeText(text).replace(/\s+/gu, ' ').trim();
|
|
51
|
+
if (width < 2)
|
|
52
|
+
return width === 1 ? '…' : '';
|
|
53
|
+
const clipped = sliceAnsi(clean, 0, width);
|
|
54
|
+
return clipped.length < clean.length ? sliceAnsi(clean, 0, width - 1) + '…' : clean;
|
|
55
|
+
}
|
|
@@ -18,7 +18,7 @@ interface Listener {
|
|
|
18
18
|
item(value: Json | undefined): void;
|
|
19
19
|
end(error?: Error): void;
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
21
|
+
/** One client lifetime. Peer disconnects allow reconnect; close permanently ends the lifetime. */
|
|
22
22
|
export declare class Client {
|
|
23
23
|
readonly timeoutMs: number;
|
|
24
24
|
readonly base: URL;
|
|
@@ -27,6 +27,7 @@ export declare class Client {
|
|
|
27
27
|
private socket;
|
|
28
28
|
private listeners;
|
|
29
29
|
private lifetime;
|
|
30
|
+
private closeTask?;
|
|
30
31
|
constructor(base: string, timeoutMs?: number);
|
|
31
32
|
/** Exchange a startup token only at GET / and retain the server's cookie expiration. */
|
|
32
33
|
authenticate(token: string): Promise<void>;
|
|
@@ -58,9 +59,9 @@ export declare class Client {
|
|
|
58
59
|
/** Archived session IDs from the latest authoritative workspace baseline. */
|
|
59
60
|
archivedSessionIds: ReadonlySet<string>;
|
|
60
61
|
/** List workspaces by consuming and cancelling the authoritative opening baseline. */
|
|
61
|
-
listWorkspaces(): Promise<ObjectValue[]>;
|
|
62
|
+
listWorkspaces(signal?: AbortSignal): Promise<ObjectValue[]>;
|
|
62
63
|
/** List visible sessions, optionally filtering by the workspace's accounted IDs. */
|
|
63
|
-
listSessions(workspaceId?: string): Promise<ObjectValue[]>;
|
|
64
|
+
listSessions(workspaceId?: string, signal?: AbortSignal): Promise<ObjectValue[]>;
|
|
64
65
|
/** Close all streams, abort in-flight HTTP, and await the physical socket's closure. */
|
|
65
66
|
close(): Promise<void>;
|
|
66
67
|
private signal;
|