@deepseek-harness-tui/dsh-tui 0.8.6 → 0.8.7
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.md +13 -3
- package/bin/dsh-tui.js +269 -218
- package/cordis.patch.yml +48 -22
- package/lib/types/cc/figures.d.ts.map +1 -1
- package/lib/types/cc/figures.js +14 -13
- package/lib/types/cc/syntaxRuns.d.ts +16 -0
- package/lib/types/cc/syntaxRuns.d.ts.map +1 -0
- package/lib/types/cc/syntaxRuns.js +79 -0
- package/lib/types/commands.d.ts +14 -0
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +20 -1
- package/lib/types/components/Chat/SubagentMessage.d.ts +19 -0
- package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -0
- package/lib/types/components/Chat/SubagentMessage.js +75 -0
- package/lib/types/components/EffortInputBorder.d.ts.map +1 -1
- package/lib/types/components/EffortInputBorder.js +2 -2
- package/lib/types/components/EffortTierBadge.js +1 -1
- package/lib/types/components/HelpMenu.d.ts +8 -1
- package/lib/types/components/HelpMenu.d.ts.map +1 -1
- package/lib/types/components/HelpMenu.js +15 -4
- package/lib/types/components/LogoV2.d.ts +3 -1
- package/lib/types/components/LogoV2.d.ts.map +1 -1
- package/lib/types/components/LogoV2.js +2 -2
- package/lib/types/components/MessageList.d.ts +6 -2
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +17 -6
- package/lib/types/components/ModelPicker.js +2 -2
- package/lib/types/components/PromptInput.d.ts +1 -1
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +75 -15
- package/lib/types/components/SkillsPicker.js +2 -2
- package/lib/types/components/SplitDiffView.d.ts +1 -13
- package/lib/types/components/SplitDiffView.d.ts.map +1 -1
- package/lib/types/components/SplitDiffView.js +6 -105
- package/lib/types/components/SubagentCard.d.ts +9 -0
- package/lib/types/components/SubagentCard.d.ts.map +1 -0
- package/lib/types/components/SubagentCard.js +26 -0
- package/lib/types/components/SubagentDashboard.d.ts +13 -0
- package/lib/types/components/SubagentDashboard.d.ts.map +1 -0
- package/lib/types/components/SubagentDashboard.js +50 -0
- package/lib/types/components/SubagentDetailScene.d.ts +16 -0
- package/lib/types/components/SubagentDetailScene.d.ts.map +1 -0
- package/lib/types/components/SubagentDetailScene.js +141 -0
- package/lib/types/components/SyntaxText.d.ts +15 -0
- package/lib/types/components/SyntaxText.d.ts.map +1 -0
- package/lib/types/components/SyntaxText.js +34 -0
- package/lib/types/components/ThinkingToggle.d.ts +3 -10
- package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
- package/lib/types/components/ThinkingToggle.js +4 -9
- package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
- package/lib/types/components/design-system/ListItem.js +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantThinkingMessage.js +18 -7
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -0
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +42 -6
- package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
- package/lib/types/components/messages/UserPromptMessage.js +15 -2
- package/lib/types/dsh-adapter/channel.d.ts +53 -2
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +294 -7
- package/lib/types/dsh-adapter/contract.d.ts +27 -4
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +56 -10
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +9 -3
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +38 -0
- package/lib/types/dsh-adapter/shortcuts.js +3 -3
- package/lib/types/dsh-adapter/subagents.d.ts +87 -0
- package/lib/types/dsh-adapter/subagents.d.ts.map +1 -0
- package/lib/types/dsh-adapter/subagents.js +204 -0
- package/lib/types/herdr.d.ts +28 -0
- package/lib/types/herdr.d.ts.map +1 -0
- package/lib/types/herdr.js +133 -0
- package/lib/types/i18n.d.ts +215 -31
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +65 -14
- package/lib/types/ink/ink.d.ts +4 -0
- package/lib/types/ink/ink.d.ts.map +1 -1
- package/lib/types/ink/ink.js +46 -15
- package/lib/types/minimalMode.d.ts +13 -0
- package/lib/types/minimalMode.d.ts.map +1 -0
- package/lib/types/minimalMode.js +17 -0
- package/lib/types/screens/Chat.d.ts +1 -1
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +94 -43
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +6 -2
- package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
- package/lib/types/screens/TrajectoryScene.js +4 -2
- package/lib/types/theme.d.ts +8 -0
- package/lib/types/theme.d.ts.map +1 -1
- package/lib/types/theme.js +38 -14
- package/lib/types/tips.js +3 -3
- package/lib/types/update.d.ts +18 -0
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +73 -2
- package/lib/types/utils/execFileNoThrow.d.ts +3 -20
- package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
- package/lib/types/utils/execFileNoThrow.js +53 -32
- package/lib/types/utils/externalEditor.d.ts +12 -11
- package/lib/types/utils/externalEditor.d.ts.map +1 -1
- package/lib/types/utils/externalEditor.js +11 -11
- package/package.json +64 -58
|
@@ -64,11 +64,17 @@ export const Config = Schema.object({
|
|
|
64
64
|
* @returns a promise settling when the TUI teardown completes.
|
|
65
65
|
*/
|
|
66
66
|
export async function apply(ctx, config) {
|
|
67
|
-
const { upstreamDrift,
|
|
67
|
+
const { upstreamDrift, installedUpstreamLines, UPSTREAM_VALIDATED_LABEL } = await import('./contract.js');
|
|
68
68
|
for (const entry of upstreamDrift()) {
|
|
69
69
|
console.warn(`[dsh-tui] upstream drift: ${entry.package} installed=${entry.installed ?? 'missing'} ` +
|
|
70
|
-
`validated=${
|
|
71
|
-
|
|
70
|
+
`validated=${entry.validated} — the TUI is validated against ${UPSTREAM_VALIDATED_LABEL}; ` +
|
|
71
|
+
`upgrade or downgrade the profile when the upstream line changes.`);
|
|
72
|
+
}
|
|
73
|
+
const installedLines = installedUpstreamLines();
|
|
74
|
+
if (installedLines.length > 1) {
|
|
75
|
+
console.warn(`[dsh-tui] mixed upstream lines detected (${installedLines.map(n => `rc.${n}`).join(', ')}) — ` +
|
|
76
|
+
`blessed packages are on different rc lines; the TUI is validated against ${UPSTREAM_VALIDATED_LABEL} ` +
|
|
77
|
+
`and a coherent install is strongly recommended.`);
|
|
72
78
|
}
|
|
73
79
|
const { apply: ccTuiApply } = await import('./plugin.js');
|
|
74
80
|
return ccTuiApply(ctx, config);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAe,MAAM,wBAAwB,CAAA;AAGhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAGlD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAe,MAAM,wBAAwB,CAAA;AAGhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAGlD,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAqCnC;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA60BvE;AA6GD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IAAE,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,GAAG;IACjF,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,YAAY,EAAE,MAAM,IAAI,CAAA;CACzB,CAcA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,KAAK,GAAG,SAAS,CAAA;IAC5B,YAAY,EAAE,KAAK,CAAA;CACpB,GAAG,OAAO,CAQV"}
|
|
@@ -25,6 +25,7 @@ import { checkForTuiUpdate, installedTuiVersion, isBootDeadlockTarget, isVersion
|
|
|
25
25
|
import { getLang, isLang, resolveStartupLang, setLang, t, writeLangPref } from '../i18n.js';
|
|
26
26
|
import { DEFAULT_STATUS_BAR, normalizeStatusBar, normalizeToolBackground } from '../tuiDisplayPrefs.js';
|
|
27
27
|
import { detectLegacyEnv, migrateLegacyDataDir, RENAMED_ENV } from '../utils/paths.js';
|
|
28
|
+
import { attachHerdrIntegration } from '../herdr.js';
|
|
28
29
|
import { Chat } from '../screens/Chat.js';
|
|
29
30
|
import { getHostDialogStore } from './dialogs.js';
|
|
30
31
|
import { getHostStatusStore } from './status.js';
|
|
@@ -357,6 +358,11 @@ export async function apply(ctx, config) {
|
|
|
357
358
|
trajectory: Schema.boolean().default(DEFAULT_STATUS_BAR.trajectory),
|
|
358
359
|
shortcutHint: Schema.boolean().default(DEFAULT_STATUS_BAR.shortcutHint),
|
|
359
360
|
}).default({ ...DEFAULT_STATUS_BAR }),
|
|
361
|
+
// Header pixel whale art; on unless settings.yaml says otherwise.
|
|
362
|
+
whale: Schema.boolean().default(true),
|
|
363
|
+
// Minimal mode: strips the header splash, emoji glyphs, and
|
|
364
|
+
// decorative colors; code highlight and tool colors stay.
|
|
365
|
+
minimal: Schema.boolean().default(false),
|
|
360
366
|
// No default on purpose: an unset `lang` keeps the field showing
|
|
361
367
|
// the effective language (see the section's format below) and lets
|
|
362
368
|
// cordis.yml / lang.json keep their precedence.
|
|
@@ -365,6 +371,12 @@ export async function apply(ctx, config) {
|
|
|
365
371
|
const applyLayout = (value) => {
|
|
366
372
|
channel.setDiffLayout(value.diffLayout ?? config.diffLayout ?? 'auto');
|
|
367
373
|
};
|
|
374
|
+
const applyWhale = (value) => {
|
|
375
|
+
channel.setWhale(value.whale ?? true);
|
|
376
|
+
};
|
|
377
|
+
const applyMinimal = (value) => {
|
|
378
|
+
channel.setMinimal(value.minimal ?? false);
|
|
379
|
+
};
|
|
368
380
|
// The /settings language field writes `lang` through the settings
|
|
369
381
|
// service (user layer): apply it live and mirror it to lang.json so
|
|
370
382
|
// the /lang command and next-boot resolution agree. DSH_TUI_LANG
|
|
@@ -385,6 +397,8 @@ export async function apply(ctx, config) {
|
|
|
385
397
|
};
|
|
386
398
|
const apply = (next) => {
|
|
387
399
|
applyLayout(next);
|
|
400
|
+
applyWhale(next);
|
|
401
|
+
applyMinimal(next);
|
|
388
402
|
applyLang(next);
|
|
389
403
|
applyDisplay(next);
|
|
390
404
|
};
|
|
@@ -595,6 +609,22 @@ export async function apply(ctx, config) {
|
|
|
595
609
|
group: 'status-bar',
|
|
596
610
|
kind: 'boolean',
|
|
597
611
|
},
|
|
612
|
+
{
|
|
613
|
+
path: ['whale'],
|
|
614
|
+
label: 'Whale art',
|
|
615
|
+
descriptions: { zh: '鲸鱼娘' },
|
|
616
|
+
hint: 'Show the pixel whale in the header splash.',
|
|
617
|
+
hintDescriptions: { zh: '开屏头部显示像素鲸鱼娘。' },
|
|
618
|
+
kind: 'boolean',
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
path: ['minimal'],
|
|
622
|
+
label: 'Minimal mode',
|
|
623
|
+
descriptions: { zh: '极简模式' },
|
|
624
|
+
hint: 'Hide the header splash, emoji glyphs, and decorative colors; code highlight and tool colors stay. Trims the status bar to model + cwd.',
|
|
625
|
+
hintDescriptions: { zh: '隐藏开屏头部、emoji 状态符与装饰性配色;代码高亮与工具配色保留,底栏只留模型与目录。' },
|
|
626
|
+
kind: 'boolean',
|
|
627
|
+
},
|
|
598
628
|
],
|
|
599
629
|
});
|
|
600
630
|
ctx.effect(() => unregister);
|
|
@@ -612,6 +642,14 @@ export async function apply(ctx, config) {
|
|
|
612
642
|
ctx.on('approval/request', (req, next) => String(req.agent.id) === channel.agentId ? approvalStore.park(req) : next());
|
|
613
643
|
ctx.effect(() => () => approvalStore.settleAll('cancelled'));
|
|
614
644
|
}
|
|
645
|
+
const herdr = attachHerdrIntegration({
|
|
646
|
+
channel,
|
|
647
|
+
questions: questionStore,
|
|
648
|
+
approvals: approvalStore,
|
|
649
|
+
});
|
|
650
|
+
if (herdr !== undefined) {
|
|
651
|
+
ctx.effect(() => () => herdr.dispose());
|
|
652
|
+
}
|
|
615
653
|
// Positional command-line arguments are the initial prompt (issue #53):
|
|
616
654
|
// `dsh-tui "run the tests"` forwards positionals through the dsh CLI,
|
|
617
655
|
// which mounts them as ctx.cmdlineArgs. Submit once the channel exists —
|
|
@@ -43,9 +43,9 @@ const NAMED_KEYS = {
|
|
|
43
43
|
* Combos the TUI owns, globally or inside the prompt editor (the editor's
|
|
44
44
|
* bindings are checked AFTER plugin shortcuts, so refusing them here is
|
|
45
45
|
* what keeps "locals win" true). Built-in handlers match a MODIFIER SUBSET
|
|
46
|
-
* (`
|
|
46
|
+
* (`key.ctrl && input === 'g'` — they never exclude an extra Shift), so
|
|
47
47
|
* the reserved check below refuses a plugin combo whose SHIFTLESS form is
|
|
48
|
-
* reserved: ctrl+shift+
|
|
48
|
+
* reserved: ctrl+shift+g would shadow Ctrl+G on terminals that report both
|
|
49
49
|
* as the same keypress (ConPTY does), and would be dead weight on terminals
|
|
50
50
|
* that don't.
|
|
51
51
|
*/
|
|
@@ -54,7 +54,7 @@ const RESERVED_COMBOS = new Set([
|
|
|
54
54
|
'ctrl+d', // exit on empty input
|
|
55
55
|
'ctrl+t', // startup context panel
|
|
56
56
|
'ctrl+r', // history search
|
|
57
|
-
'ctrl+
|
|
57
|
+
'ctrl+g', // external editor
|
|
58
58
|
'ctrl+o', // transcript mode toggle
|
|
59
59
|
'ctrl+l', // terminal redraw
|
|
60
60
|
'ctrl+e', // show all messages / line end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** Unified subagent activity domain model used by the adapter and every view. */
|
|
2
|
+
export type SubagentStatus = 'starting' | 'running' | 'completed' | 'failed' | 'cancelled' | 'unknown';
|
|
3
|
+
export type SubagentOutputKind = 'text' | 'thinking' | 'tool' | 'error' | 'system';
|
|
4
|
+
export interface SubagentOutputLine {
|
|
5
|
+
kind: SubagentOutputKind;
|
|
6
|
+
text: string;
|
|
7
|
+
at: number;
|
|
8
|
+
/** False while the line is still absorbing streaming deltas. */
|
|
9
|
+
settled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface SubagentToolCall {
|
|
12
|
+
id?: string;
|
|
13
|
+
name: string;
|
|
14
|
+
status: 'running' | 'completed' | 'failed';
|
|
15
|
+
startedAt: number;
|
|
16
|
+
endedAt?: number;
|
|
17
|
+
argsPreview?: string;
|
|
18
|
+
resultPreview?: string;
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface SubagentTokenUsage {
|
|
22
|
+
input?: number;
|
|
23
|
+
output?: number;
|
|
24
|
+
total?: number;
|
|
25
|
+
context?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface SubagentState {
|
|
28
|
+
agentId: string;
|
|
29
|
+
runId?: string;
|
|
30
|
+
description: string;
|
|
31
|
+
provider?: string;
|
|
32
|
+
model?: string;
|
|
33
|
+
effort?: string;
|
|
34
|
+
status: SubagentStatus;
|
|
35
|
+
startedAt: number;
|
|
36
|
+
completedAt?: number;
|
|
37
|
+
endedAt?: number;
|
|
38
|
+
local?: boolean;
|
|
39
|
+
parentSessionId?: string;
|
|
40
|
+
sessionId?: string;
|
|
41
|
+
stopReason?: string;
|
|
42
|
+
error?: string;
|
|
43
|
+
/** Compatibility projection for older consumers. */
|
|
44
|
+
output: string[];
|
|
45
|
+
outputEvents: SubagentOutputLine[];
|
|
46
|
+
toolCalls: SubagentToolCall[];
|
|
47
|
+
tokens?: SubagentTokenUsage;
|
|
48
|
+
summary?: string;
|
|
49
|
+
}
|
|
50
|
+
export declare class SubagentActivityStore {
|
|
51
|
+
private states;
|
|
52
|
+
private sessionToAgent;
|
|
53
|
+
private listeners;
|
|
54
|
+
private commitLine;
|
|
55
|
+
private pushLine;
|
|
56
|
+
onSpawned(agentId: string, provider?: string, model?: string, info?: Partial<SubagentState>): void;
|
|
57
|
+
linkSession(agentId: string, session: unknown): void;
|
|
58
|
+
getSubagentIdBySession(session: unknown): string | undefined;
|
|
59
|
+
appendOutput(agentId: string, text: string, kind?: SubagentOutputKind): void;
|
|
60
|
+
/** Mark the streaming tail line complete (run settlement). */
|
|
61
|
+
flushOutput(agentId: string): void;
|
|
62
|
+
onSessionEvent(agentId: string, event: unknown): void;
|
|
63
|
+
/** Merge partial updates (model discovery, session id) into one subagent. */
|
|
64
|
+
patch(agentId: string, partial: Partial<SubagentState>): void;
|
|
65
|
+
/** One-line preview of a tool-result content block (text or item list). */
|
|
66
|
+
private previewOf;
|
|
67
|
+
recordTool(agentId: string, input: Partial<SubagentToolCall> & {
|
|
68
|
+
name?: string;
|
|
69
|
+
}): void;
|
|
70
|
+
setTokens(agentId: string, usage: {
|
|
71
|
+
inputTokens?: number;
|
|
72
|
+
outputTokens?: number;
|
|
73
|
+
input?: number;
|
|
74
|
+
output?: number;
|
|
75
|
+
total?: number;
|
|
76
|
+
context?: number;
|
|
77
|
+
}): void;
|
|
78
|
+
onCompleted(agentId: string, summary?: string, stopReason?: string): void;
|
|
79
|
+
onFailed(agentId: string, error: string): void;
|
|
80
|
+
onCancelled(agentId: string, reason?: string, summary?: string): void;
|
|
81
|
+
private finish;
|
|
82
|
+
snapshot(): SubagentState[];
|
|
83
|
+
get(agentId: string): SubagentState | undefined;
|
|
84
|
+
subscribe(listener: () => void): () => void;
|
|
85
|
+
private notify;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=subagents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagents.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/subagents.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/** Unified subagent activity domain model used by the adapter and every view. */
|
|
2
|
+
const MAX_OUTPUT_EVENTS = 160;
|
|
3
|
+
const MAX_OUTPUT_LINES = 160;
|
|
4
|
+
export class SubagentActivityStore {
|
|
5
|
+
states = new Map();
|
|
6
|
+
sessionToAgent = new Map();
|
|
7
|
+
listeners = new Set();
|
|
8
|
+
commitLine(agentId, kind, text) {
|
|
9
|
+
const state = this.states.get(agentId);
|
|
10
|
+
if (!state)
|
|
11
|
+
return;
|
|
12
|
+
this.pushLine(state, { kind, text, at: Date.now(), settled: true });
|
|
13
|
+
}
|
|
14
|
+
pushLine(state, line) {
|
|
15
|
+
state.outputEvents.push(line);
|
|
16
|
+
if (state.outputEvents.length > MAX_OUTPUT_EVENTS)
|
|
17
|
+
state.outputEvents.splice(0, state.outputEvents.length - MAX_OUTPUT_EVENTS);
|
|
18
|
+
state.output = state.outputEvents.map(entry => entry.text);
|
|
19
|
+
if (state.output.length > MAX_OUTPUT_LINES)
|
|
20
|
+
state.output.splice(0, state.output.length - MAX_OUTPUT_LINES);
|
|
21
|
+
}
|
|
22
|
+
onSpawned(agentId, provider = 'subagent', model, info = {}) {
|
|
23
|
+
const existing = this.states.get(agentId);
|
|
24
|
+
const state = existing ?? {
|
|
25
|
+
agentId,
|
|
26
|
+
runId: info.runId ?? agentId,
|
|
27
|
+
description: info.description ?? 'Subagent task',
|
|
28
|
+
provider,
|
|
29
|
+
model: model ?? info.model ?? provider,
|
|
30
|
+
effort: info.effort,
|
|
31
|
+
status: 'running',
|
|
32
|
+
startedAt: info.startedAt ?? Date.now(),
|
|
33
|
+
local: info.local,
|
|
34
|
+
parentSessionId: info.parentSessionId,
|
|
35
|
+
sessionId: info.sessionId,
|
|
36
|
+
output: [],
|
|
37
|
+
outputEvents: [],
|
|
38
|
+
toolCalls: [],
|
|
39
|
+
};
|
|
40
|
+
if (existing) {
|
|
41
|
+
Object.assign(existing, info, { provider, model: model ?? existing.model, status: existing.status === 'completed' ? existing.status : 'running' });
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
this.states.set(agentId, state);
|
|
45
|
+
}
|
|
46
|
+
this.notify();
|
|
47
|
+
}
|
|
48
|
+
linkSession(agentId, session) {
|
|
49
|
+
if (session !== undefined && session !== null)
|
|
50
|
+
this.sessionToAgent.set(session, agentId);
|
|
51
|
+
const state = this.states.get(agentId);
|
|
52
|
+
if (state && typeof session === 'string')
|
|
53
|
+
state.sessionId = session;
|
|
54
|
+
}
|
|
55
|
+
getSubagentIdBySession(session) { return this.sessionToAgent.get(session); }
|
|
56
|
+
appendOutput(agentId, text, kind = 'text') {
|
|
57
|
+
const state = this.states.get(agentId);
|
|
58
|
+
if (!state || !text)
|
|
59
|
+
return;
|
|
60
|
+
const last = state.outputEvents[state.outputEvents.length - 1];
|
|
61
|
+
if (last === undefined || last.settled || last.kind !== kind) {
|
|
62
|
+
if (last !== undefined && !last.settled)
|
|
63
|
+
last.settled = true;
|
|
64
|
+
this.pushLine(state, { kind, text, at: Date.now(), settled: false });
|
|
65
|
+
this.notify();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
last.text += text;
|
|
69
|
+
const parts = last.text.split('\n');
|
|
70
|
+
if (parts.length > 1) {
|
|
71
|
+
last.text = parts[0];
|
|
72
|
+
last.settled = true;
|
|
73
|
+
for (const middle of parts.slice(1, -1)) {
|
|
74
|
+
this.pushLine(state, { kind, text: middle, at: Date.now(), settled: true });
|
|
75
|
+
}
|
|
76
|
+
const tail = parts[parts.length - 1] ?? '';
|
|
77
|
+
if (tail)
|
|
78
|
+
this.pushLine(state, { kind, text: tail, at: Date.now(), settled: false });
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
state.output = state.outputEvents.map(entry => entry.text);
|
|
82
|
+
}
|
|
83
|
+
this.notify();
|
|
84
|
+
}
|
|
85
|
+
/** Mark the streaming tail line complete (run settlement). */
|
|
86
|
+
flushOutput(agentId) {
|
|
87
|
+
const state = this.states.get(agentId);
|
|
88
|
+
const last = state?.outputEvents[state.outputEvents.length - 1];
|
|
89
|
+
if (last !== undefined && !last.settled) {
|
|
90
|
+
last.settled = true;
|
|
91
|
+
this.notify();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
onSessionEvent(agentId, event) {
|
|
95
|
+
if (!event || typeof event !== 'object')
|
|
96
|
+
return;
|
|
97
|
+
const ev = event;
|
|
98
|
+
const data = ev.data ?? {};
|
|
99
|
+
switch (ev.type) {
|
|
100
|
+
case 'assistant/chunk': {
|
|
101
|
+
const chunk = data.chunk ?? {};
|
|
102
|
+
if (chunk.type === 'text-delta' && chunk.text)
|
|
103
|
+
this.appendOutput(agentId, chunk.text, 'text');
|
|
104
|
+
else if (chunk.type === 'reasoning-delta' && chunk.text)
|
|
105
|
+
this.appendOutput(agentId, chunk.text, 'thinking');
|
|
106
|
+
else if (chunk.type === 'usage' && chunk.usage)
|
|
107
|
+
this.setTokens(agentId, chunk.usage);
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case 'assistant/message': {
|
|
111
|
+
if (data.usage)
|
|
112
|
+
this.setTokens(agentId, data.usage);
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case 'tool/call': {
|
|
116
|
+
this.recordTool(agentId, { id: data.callId, name: data.name, status: 'running', startedAt: Date.now(), argsPreview: data.arguments });
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case 'tool/result': {
|
|
120
|
+
const callId = data?.message?.source?.callId;
|
|
121
|
+
const state = this.states.get(agentId);
|
|
122
|
+
const tool = callId !== undefined ? state?.toolCalls.find(entry => entry.id === callId) : undefined;
|
|
123
|
+
if (tool) {
|
|
124
|
+
tool.status = data.error !== undefined ? 'failed' : 'completed';
|
|
125
|
+
tool.endedAt = Date.now();
|
|
126
|
+
if (data.error !== undefined)
|
|
127
|
+
tool.error = String(data.error);
|
|
128
|
+
else {
|
|
129
|
+
const block = data.message?.content?.[0];
|
|
130
|
+
tool.resultPreview = block !== undefined && block.type === 'tool-result'
|
|
131
|
+
? this.previewOf(block.content)
|
|
132
|
+
: undefined;
|
|
133
|
+
}
|
|
134
|
+
this.notify();
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
default:
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Merge partial updates (model discovery, session id) into one subagent. */
|
|
143
|
+
patch(agentId, partial) {
|
|
144
|
+
const state = this.states.get(agentId);
|
|
145
|
+
if (!state)
|
|
146
|
+
return;
|
|
147
|
+
Object.assign(state, partial);
|
|
148
|
+
this.notify();
|
|
149
|
+
}
|
|
150
|
+
/** One-line preview of a tool-result content block (text or item list). */
|
|
151
|
+
previewOf(content) {
|
|
152
|
+
const text = typeof content === 'string'
|
|
153
|
+
? content
|
|
154
|
+
: Array.isArray(content)
|
|
155
|
+
? content.map(item => (typeof item === 'object' && item !== null && 'text' in item ? String(item.text ?? '') : '')).join(' ')
|
|
156
|
+
: '';
|
|
157
|
+
const flat = text.replace(/\s+/g, ' ').trim();
|
|
158
|
+
return flat ? (flat.length > 80 ? `${flat.slice(0, 80)}…` : flat) : undefined;
|
|
159
|
+
}
|
|
160
|
+
recordTool(agentId, input) {
|
|
161
|
+
const state = this.states.get(agentId);
|
|
162
|
+
if (!state || !input.name)
|
|
163
|
+
return;
|
|
164
|
+
const current = input.id ? state.toolCalls.find(tool => tool.id === input.id) : undefined;
|
|
165
|
+
if (current)
|
|
166
|
+
Object.assign(current, input);
|
|
167
|
+
else
|
|
168
|
+
state.toolCalls.push({ name: input.name, status: input.status ?? 'completed', startedAt: input.startedAt ?? Date.now(), ...input });
|
|
169
|
+
this.notify();
|
|
170
|
+
}
|
|
171
|
+
setTokens(agentId, usage) {
|
|
172
|
+
const state = this.states.get(agentId);
|
|
173
|
+
if (!state)
|
|
174
|
+
return;
|
|
175
|
+
const input = usage.input ?? usage.inputTokens;
|
|
176
|
+
const output = usage.output ?? usage.outputTokens;
|
|
177
|
+
if (input === undefined && output === undefined && usage.total === undefined)
|
|
178
|
+
return;
|
|
179
|
+
state.tokens = { ...state.tokens, input, output, total: usage.total ?? ((input ?? 0) + (output ?? 0) || undefined) };
|
|
180
|
+
this.notify();
|
|
181
|
+
}
|
|
182
|
+
onCompleted(agentId, summary, stopReason = 'completed') { this.finish(agentId, 'completed', stopReason, summary); }
|
|
183
|
+
onFailed(agentId, error) { this.finish(agentId, 'failed', error, undefined); }
|
|
184
|
+
onCancelled(agentId, reason = 'cancelled', summary) { this.finish(agentId, 'cancelled', reason, summary); }
|
|
185
|
+
finish(agentId, status, reason, summary) {
|
|
186
|
+
const state = this.states.get(agentId);
|
|
187
|
+
if (!state || (state.status !== 'running' && state.status !== 'starting'))
|
|
188
|
+
return;
|
|
189
|
+
state.status = status;
|
|
190
|
+
state.completedAt = Date.now();
|
|
191
|
+
state.endedAt = state.completedAt;
|
|
192
|
+
state.stopReason = reason;
|
|
193
|
+
if (summary)
|
|
194
|
+
state.summary = summary;
|
|
195
|
+
if (status === 'failed')
|
|
196
|
+
state.error = reason;
|
|
197
|
+
this.notify();
|
|
198
|
+
}
|
|
199
|
+
snapshot() { return Array.from(this.states.values()).map(state => ({ ...state, output: [...state.output], outputEvents: [...state.outputEvents], toolCalls: state.toolCalls.map(tool => ({ ...tool })), tokens: state.tokens ? { ...state.tokens } : undefined })); }
|
|
200
|
+
get(agentId) { return this.snapshot().find(state => state.agentId === agentId); }
|
|
201
|
+
subscribe(listener) { this.listeners.add(listener); return () => this.listeners.delete(listener); }
|
|
202
|
+
notify() { for (const listener of this.listeners)
|
|
203
|
+
listener(); }
|
|
204
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ExecFileNoThrowResult } from './utils/execFileNoThrow.js';
|
|
2
|
+
interface HerdrChannel {
|
|
3
|
+
readonly working: boolean;
|
|
4
|
+
subscribe(listener: () => void): () => void;
|
|
5
|
+
}
|
|
6
|
+
interface BlockingStore {
|
|
7
|
+
subscribe(listener: () => void): () => void;
|
|
8
|
+
getSnapshot(): unknown | null;
|
|
9
|
+
}
|
|
10
|
+
type RunCommand = (file: string, args: readonly string[]) => Promise<ExecFileNoThrowResult>;
|
|
11
|
+
export interface HerdrIntegration {
|
|
12
|
+
settled(): Promise<void>;
|
|
13
|
+
dispose(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export interface HerdrIntegrationOptions {
|
|
16
|
+
readonly channel: HerdrChannel;
|
|
17
|
+
readonly questions: BlockingStore;
|
|
18
|
+
readonly approvals: BlockingStore;
|
|
19
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
20
|
+
readonly run?: RunCommand;
|
|
21
|
+
readonly reportTimeoutMs?: number;
|
|
22
|
+
readonly releaseTimeoutMs?: number;
|
|
23
|
+
readonly retryDelaysMs?: readonly number[];
|
|
24
|
+
}
|
|
25
|
+
/** Report this TUI's lifecycle to the owning Herdr pane when Herdr launches it. */
|
|
26
|
+
export declare function attachHerdrIntegration(options: HerdrIntegrationOptions): HerdrIntegration | undefined;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=herdr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"herdr.d.ts","sourceRoot":"","sources":["../../src/herdr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AAQxF,UAAU,YAAY;IACpB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;CAC5C;AAED,UAAU,aAAa;IACrB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IAC3C,WAAW,IAAI,OAAO,GAAG,IAAI,CAAA;CAC9B;AAED,KAAK,UAAU,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;AAE3F,MAAM,WAAW,gBAAgB;IAC/B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACxB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;IAC9B,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAA;IACjC,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAA;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IAChC,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAClC,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC3C;AAkBD,mFAAmF;AACnF,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,gBAAgB,GAAG,SAAS,CAgH9B"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { execFileNoThrow } from './utils/execFileNoThrow.js';
|
|
2
|
+
const REPORT_TIMEOUT_MS = 2000;
|
|
3
|
+
const RELEASE_TIMEOUT_MS = 500;
|
|
4
|
+
const RETRY_DELAYS_MS = [100, 250, 500, 1000];
|
|
5
|
+
function withTimeout(promise, timeoutMs) {
|
|
6
|
+
return new Promise(resolve => {
|
|
7
|
+
const timer = setTimeout(() => resolve(undefined), timeoutMs);
|
|
8
|
+
void promise.then(value => {
|
|
9
|
+
clearTimeout(timer);
|
|
10
|
+
resolve(value);
|
|
11
|
+
}, () => {
|
|
12
|
+
clearTimeout(timer);
|
|
13
|
+
resolve(undefined);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** Report this TUI's lifecycle to the owning Herdr pane when Herdr launches it. */
|
|
18
|
+
export function attachHerdrIntegration(options) {
|
|
19
|
+
const env = options.env ?? process.env;
|
|
20
|
+
const executable = env.HERDR_BIN_PATH?.trim();
|
|
21
|
+
const paneId = env.HERDR_PANE_ID?.trim();
|
|
22
|
+
if (env.HERDR_ENV !== '1' || !executable || !paneId)
|
|
23
|
+
return undefined;
|
|
24
|
+
const reportTimeoutMs = options.reportTimeoutMs ?? REPORT_TIMEOUT_MS;
|
|
25
|
+
const releaseTimeoutMs = options.releaseTimeoutMs ?? RELEASE_TIMEOUT_MS;
|
|
26
|
+
const retryDelays = options.retryDelaysMs ?? RETRY_DELAYS_MS;
|
|
27
|
+
const run = options.run ?? ((file, args) => execFileNoThrow(file, args, { timeout: reportTimeoutMs }));
|
|
28
|
+
const runSafely = (args) => {
|
|
29
|
+
try {
|
|
30
|
+
return Promise.resolve(run(executable, args));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return Promise.reject(new Error('Herdr command could not be started'));
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
let sequence = 0;
|
|
37
|
+
let lastConfirmedReport = '';
|
|
38
|
+
let disposed = false;
|
|
39
|
+
let running = false;
|
|
40
|
+
let wakeDelay;
|
|
41
|
+
let settledPromise = Promise.resolve();
|
|
42
|
+
const computeState = () => {
|
|
43
|
+
const blocked = options.questions.getSnapshot() !== null || options.approvals.getSnapshot() !== null;
|
|
44
|
+
return { state: blocked ? 'blocked' : options.channel.working ? 'working' : 'idle', blocked };
|
|
45
|
+
};
|
|
46
|
+
const waitForRetry = (delayMs) => new Promise(resolve => {
|
|
47
|
+
const timer = setTimeout(() => {
|
|
48
|
+
wakeDelay = undefined;
|
|
49
|
+
resolve();
|
|
50
|
+
}, delayMs);
|
|
51
|
+
wakeDelay = () => {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
wakeDelay = undefined;
|
|
54
|
+
resolve();
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
const processQueue = async () => {
|
|
58
|
+
let failedState = '';
|
|
59
|
+
let attempts = 0;
|
|
60
|
+
try {
|
|
61
|
+
while (!disposed) {
|
|
62
|
+
const { state, blocked } = computeState();
|
|
63
|
+
if (state === lastConfirmedReport)
|
|
64
|
+
break;
|
|
65
|
+
if (state !== failedState) {
|
|
66
|
+
failedState = state;
|
|
67
|
+
attempts = 0;
|
|
68
|
+
}
|
|
69
|
+
attempts += 1;
|
|
70
|
+
const result = await withTimeout(runSafely([
|
|
71
|
+
'pane', 'report-agent', paneId,
|
|
72
|
+
'--source', 'custom:dsh-tui',
|
|
73
|
+
'--agent', 'dsh-tui',
|
|
74
|
+
'--state', state,
|
|
75
|
+
...(blocked ? ['--message', 'Waiting for user input'] : []),
|
|
76
|
+
'--seq', String(++sequence),
|
|
77
|
+
]), reportTimeoutMs);
|
|
78
|
+
if (disposed)
|
|
79
|
+
break;
|
|
80
|
+
if (result?.code === 0) {
|
|
81
|
+
lastConfirmedReport = state;
|
|
82
|
+
failedState = '';
|
|
83
|
+
attempts = 0;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (attempts > retryDelays.length)
|
|
87
|
+
break;
|
|
88
|
+
await waitForRetry(retryDelays[attempts - 1] ?? 0);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
running = false;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const report = () => {
|
|
96
|
+
if (disposed)
|
|
97
|
+
return;
|
|
98
|
+
if (running) {
|
|
99
|
+
wakeDelay?.();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (computeState().state === lastConfirmedReport)
|
|
103
|
+
return;
|
|
104
|
+
running = true;
|
|
105
|
+
settledPromise = processQueue();
|
|
106
|
+
};
|
|
107
|
+
const unsubscribes = [
|
|
108
|
+
options.channel.subscribe(report),
|
|
109
|
+
options.questions.subscribe(report),
|
|
110
|
+
options.approvals.subscribe(report),
|
|
111
|
+
];
|
|
112
|
+
report();
|
|
113
|
+
let disposePromise;
|
|
114
|
+
return {
|
|
115
|
+
settled: () => settledPromise,
|
|
116
|
+
dispose: () => {
|
|
117
|
+
if (disposePromise !== undefined)
|
|
118
|
+
return disposePromise;
|
|
119
|
+
disposed = true;
|
|
120
|
+
wakeDelay?.();
|
|
121
|
+
for (const unsubscribe of unsubscribes)
|
|
122
|
+
unsubscribe();
|
|
123
|
+
const release = runSafely([
|
|
124
|
+
'pane', 'release-agent', paneId,
|
|
125
|
+
'--source', 'custom:dsh-tui',
|
|
126
|
+
'--agent', 'dsh-tui',
|
|
127
|
+
'--seq', String(++sequence),
|
|
128
|
+
]);
|
|
129
|
+
disposePromise = withTimeout(release, releaseTimeoutMs).then(() => undefined);
|
|
130
|
+
return disposePromise;
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|