@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.
Files changed (105) hide show
  1. package/README.md +13 -3
  2. package/bin/dsh-tui.js +269 -218
  3. package/cordis.patch.yml +48 -22
  4. package/lib/types/cc/figures.d.ts.map +1 -1
  5. package/lib/types/cc/figures.js +14 -13
  6. package/lib/types/cc/syntaxRuns.d.ts +16 -0
  7. package/lib/types/cc/syntaxRuns.d.ts.map +1 -0
  8. package/lib/types/cc/syntaxRuns.js +79 -0
  9. package/lib/types/commands.d.ts +14 -0
  10. package/lib/types/commands.d.ts.map +1 -1
  11. package/lib/types/commands.js +20 -1
  12. package/lib/types/components/Chat/SubagentMessage.d.ts +19 -0
  13. package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -0
  14. package/lib/types/components/Chat/SubagentMessage.js +75 -0
  15. package/lib/types/components/EffortInputBorder.d.ts.map +1 -1
  16. package/lib/types/components/EffortInputBorder.js +2 -2
  17. package/lib/types/components/EffortTierBadge.js +1 -1
  18. package/lib/types/components/HelpMenu.d.ts +8 -1
  19. package/lib/types/components/HelpMenu.d.ts.map +1 -1
  20. package/lib/types/components/HelpMenu.js +15 -4
  21. package/lib/types/components/LogoV2.d.ts +3 -1
  22. package/lib/types/components/LogoV2.d.ts.map +1 -1
  23. package/lib/types/components/LogoV2.js +2 -2
  24. package/lib/types/components/MessageList.d.ts +6 -2
  25. package/lib/types/components/MessageList.d.ts.map +1 -1
  26. package/lib/types/components/MessageList.js +17 -6
  27. package/lib/types/components/ModelPicker.js +2 -2
  28. package/lib/types/components/PromptInput.d.ts +1 -1
  29. package/lib/types/components/PromptInput.d.ts.map +1 -1
  30. package/lib/types/components/PromptInput.js +75 -15
  31. package/lib/types/components/SkillsPicker.js +2 -2
  32. package/lib/types/components/SplitDiffView.d.ts +1 -13
  33. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  34. package/lib/types/components/SplitDiffView.js +6 -105
  35. package/lib/types/components/SubagentCard.d.ts +9 -0
  36. package/lib/types/components/SubagentCard.d.ts.map +1 -0
  37. package/lib/types/components/SubagentCard.js +26 -0
  38. package/lib/types/components/SubagentDashboard.d.ts +13 -0
  39. package/lib/types/components/SubagentDashboard.d.ts.map +1 -0
  40. package/lib/types/components/SubagentDashboard.js +50 -0
  41. package/lib/types/components/SubagentDetailScene.d.ts +16 -0
  42. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -0
  43. package/lib/types/components/SubagentDetailScene.js +141 -0
  44. package/lib/types/components/SyntaxText.d.ts +15 -0
  45. package/lib/types/components/SyntaxText.d.ts.map +1 -0
  46. package/lib/types/components/SyntaxText.js +34 -0
  47. package/lib/types/components/ThinkingToggle.d.ts +3 -10
  48. package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
  49. package/lib/types/components/ThinkingToggle.js +4 -9
  50. package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
  51. package/lib/types/components/design-system/ListItem.js +1 -1
  52. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  53. package/lib/types/components/messages/AssistantThinkingMessage.js +18 -7
  54. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -0
  55. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  56. package/lib/types/components/messages/AssistantToolUseMessage.js +42 -6
  57. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  58. package/lib/types/components/messages/UserPromptMessage.js +15 -2
  59. package/lib/types/dsh-adapter/channel.d.ts +53 -2
  60. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  61. package/lib/types/dsh-adapter/channel.js +294 -7
  62. package/lib/types/dsh-adapter/contract.d.ts +27 -4
  63. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  64. package/lib/types/dsh-adapter/contract.js +56 -10
  65. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  66. package/lib/types/dsh-adapter/index.js +9 -3
  67. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  68. package/lib/types/dsh-adapter/plugin.js +38 -0
  69. package/lib/types/dsh-adapter/shortcuts.js +3 -3
  70. package/lib/types/dsh-adapter/subagents.d.ts +87 -0
  71. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -0
  72. package/lib/types/dsh-adapter/subagents.js +204 -0
  73. package/lib/types/herdr.d.ts +28 -0
  74. package/lib/types/herdr.d.ts.map +1 -0
  75. package/lib/types/herdr.js +133 -0
  76. package/lib/types/i18n.d.ts +215 -31
  77. package/lib/types/i18n.d.ts.map +1 -1
  78. package/lib/types/i18n.js +65 -14
  79. package/lib/types/ink/ink.d.ts +4 -0
  80. package/lib/types/ink/ink.d.ts.map +1 -1
  81. package/lib/types/ink/ink.js +46 -15
  82. package/lib/types/minimalMode.d.ts +13 -0
  83. package/lib/types/minimalMode.d.ts.map +1 -0
  84. package/lib/types/minimalMode.js +17 -0
  85. package/lib/types/screens/Chat.d.ts +1 -1
  86. package/lib/types/screens/Chat.d.ts.map +1 -1
  87. package/lib/types/screens/Chat.js +94 -43
  88. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  89. package/lib/types/screens/StatusLine.js +6 -2
  90. package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
  91. package/lib/types/screens/TrajectoryScene.js +4 -2
  92. package/lib/types/theme.d.ts +8 -0
  93. package/lib/types/theme.d.ts.map +1 -1
  94. package/lib/types/theme.js +38 -14
  95. package/lib/types/tips.js +3 -3
  96. package/lib/types/update.d.ts +18 -0
  97. package/lib/types/update.d.ts.map +1 -1
  98. package/lib/types/update.js +73 -2
  99. package/lib/types/utils/execFileNoThrow.d.ts +3 -20
  100. package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
  101. package/lib/types/utils/execFileNoThrow.js +53 -32
  102. package/lib/types/utils/externalEditor.d.ts +12 -11
  103. package/lib/types/utils/externalEditor.d.ts.map +1 -1
  104. package/lib/types/utils/externalEditor.js +11 -11
  105. 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, UPSTREAM_VALIDATED_VERSION } = await import('./contract.js');
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=${UPSTREAM_VALIDATED_VERSION} — the TUI was validated against ` +
71
- `${UPSTREAM_VALIDATED_VERSION}; upgrade the profile when upstream is bumped.`);
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;AAoCnC;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsyBvE;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"}
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
- * (`isMod(key) && input === 'x'` — they never exclude an extra Shift), so
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+x would shadow Ctrl+X on terminals that report both
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+x', // external editor
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
+ }