@deepseek-harness-tui/dsh-tui 0.8.6 → 0.8.8

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 (120) hide show
  1. package/README.md +15 -5
  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/FileSuggestions.d.ts +8 -4
  19. package/lib/types/components/FileSuggestions.d.ts.map +1 -1
  20. package/lib/types/components/FileSuggestions.js +22 -12
  21. package/lib/types/components/HelpMenu.d.ts +8 -1
  22. package/lib/types/components/HelpMenu.d.ts.map +1 -1
  23. package/lib/types/components/HelpMenu.js +15 -4
  24. package/lib/types/components/LogoV2.d.ts +7 -1
  25. package/lib/types/components/LogoV2.d.ts.map +1 -1
  26. package/lib/types/components/LogoV2.js +13 -4
  27. package/lib/types/components/MessageList.d.ts +6 -2
  28. package/lib/types/components/MessageList.d.ts.map +1 -1
  29. package/lib/types/components/MessageList.js +17 -6
  30. package/lib/types/components/ModelPicker.js +2 -2
  31. package/lib/types/components/PromptInput.d.ts +1 -1
  32. package/lib/types/components/PromptInput.d.ts.map +1 -1
  33. package/lib/types/components/PromptInput.js +100 -36
  34. package/lib/types/components/SkillsPicker.js +2 -2
  35. package/lib/types/components/SplitDiffView.d.ts +1 -13
  36. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  37. package/lib/types/components/SplitDiffView.js +6 -105
  38. package/lib/types/components/SubagentCard.d.ts +9 -0
  39. package/lib/types/components/SubagentCard.d.ts.map +1 -0
  40. package/lib/types/components/SubagentCard.js +26 -0
  41. package/lib/types/components/SubagentDashboard.d.ts +13 -0
  42. package/lib/types/components/SubagentDashboard.d.ts.map +1 -0
  43. package/lib/types/components/SubagentDashboard.js +50 -0
  44. package/lib/types/components/SubagentDetailScene.d.ts +16 -0
  45. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -0
  46. package/lib/types/components/SubagentDetailScene.js +141 -0
  47. package/lib/types/components/SyntaxText.d.ts +15 -0
  48. package/lib/types/components/SyntaxText.d.ts.map +1 -0
  49. package/lib/types/components/SyntaxText.js +34 -0
  50. package/lib/types/components/ThinkingToggle.d.ts +3 -10
  51. package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
  52. package/lib/types/components/ThinkingToggle.js +4 -9
  53. package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
  54. package/lib/types/components/design-system/ListItem.js +1 -1
  55. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  56. package/lib/types/components/messages/AssistantThinkingMessage.js +18 -7
  57. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -0
  58. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  59. package/lib/types/components/messages/AssistantToolUseMessage.js +42 -6
  60. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  61. package/lib/types/components/messages/UserPromptMessage.js +15 -2
  62. package/lib/types/dsh-adapter/channel.d.ts +64 -3
  63. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  64. package/lib/types/dsh-adapter/channel.js +387 -37
  65. package/lib/types/dsh-adapter/contract.d.ts +67 -4
  66. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  67. package/lib/types/dsh-adapter/contract.js +149 -15
  68. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  69. package/lib/types/dsh-adapter/index.js +6 -6
  70. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  71. package/lib/types/dsh-adapter/plugin.js +38 -0
  72. package/lib/types/dsh-adapter/shortcuts.js +3 -3
  73. package/lib/types/dsh-adapter/subagents.d.ts +87 -0
  74. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -0
  75. package/lib/types/dsh-adapter/subagents.js +204 -0
  76. package/lib/types/herdr.d.ts +28 -0
  77. package/lib/types/herdr.d.ts.map +1 -0
  78. package/lib/types/herdr.js +133 -0
  79. package/lib/types/i18n.d.ts +233 -32
  80. package/lib/types/i18n.d.ts.map +1 -1
  81. package/lib/types/i18n.js +87 -16
  82. package/lib/types/ink/ink.d.ts +4 -0
  83. package/lib/types/ink/ink.d.ts.map +1 -1
  84. package/lib/types/ink/ink.js +84 -38
  85. package/lib/types/ink/render-node-to-output.d.ts +6 -3
  86. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  87. package/lib/types/ink/render-node-to-output.js +33 -7
  88. package/lib/types/ink/selection.d.ts +29 -4
  89. package/lib/types/ink/selection.d.ts.map +1 -1
  90. package/lib/types/ink/selection.js +41 -5
  91. package/lib/types/minimalMode.d.ts +13 -0
  92. package/lib/types/minimalMode.d.ts.map +1 -0
  93. package/lib/types/minimalMode.js +17 -0
  94. package/lib/types/screens/Chat.d.ts +1 -1
  95. package/lib/types/screens/Chat.d.ts.map +1 -1
  96. package/lib/types/screens/Chat.js +94 -43
  97. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  98. package/lib/types/screens/StatusLine.js +6 -2
  99. package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
  100. package/lib/types/screens/TrajectoryScene.js +4 -2
  101. package/lib/types/theme.d.ts +8 -0
  102. package/lib/types/theme.d.ts.map +1 -1
  103. package/lib/types/theme.js +38 -14
  104. package/lib/types/tips.d.ts.map +1 -1
  105. package/lib/types/tips.js +25 -7
  106. package/lib/types/update.d.ts +18 -0
  107. package/lib/types/update.d.ts.map +1 -1
  108. package/lib/types/update.js +73 -2
  109. package/lib/types/utils/execFileNoThrow.d.ts +3 -20
  110. package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
  111. package/lib/types/utils/execFileNoThrow.js +53 -32
  112. package/lib/types/utils/externalEditor.d.ts +12 -11
  113. package/lib/types/utils/externalEditor.d.ts.map +1 -1
  114. package/lib/types/utils/externalEditor.js +11 -11
  115. package/lib/types/utils/fileSuggestions.d.ts +19 -0
  116. package/lib/types/utils/fileSuggestions.d.ts.map +1 -0
  117. package/lib/types/utils/fileSuggestions.js +40 -0
  118. package/lib/types/utils/mentions.d.ts.map +1 -1
  119. package/lib/types/utils/mentions.js +3 -1
  120. package/package.json +81 -74
@@ -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
+ }