@adhdev/daemon-core 0.9.76-rc.9 → 0.9.76
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/dist/cli-adapters/provider-cli-adapter.d.ts +5 -2
- package/dist/cli-adapters/provider-cli-runtime.d.ts +1 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +24 -0
- package/dist/commands/chat-commands.d.ts +2 -0
- package/dist/commands/cli-manager.d.ts +17 -4
- package/dist/commands/mesh-coordinator.d.ts +2 -0
- package/dist/commands/router.d.ts +11 -0
- package/dist/config/mesh-config.d.ts +3 -0
- package/dist/git/git-types.d.ts +1 -1
- package/dist/git/git-worktree.d.ts +64 -0
- package/dist/git/index.d.ts +2 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2427 -561
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2432 -584
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/coordinator-prompt.d.ts +1 -0
- package/dist/mesh/mesh-events.d.ts +18 -0
- package/dist/providers/chat-message-normalization.d.ts +40 -0
- package/dist/providers/cli-provider-instance.d.ts +7 -1
- package/dist/providers/contracts.d.ts +20 -1
- package/dist/providers/io-contracts.d.ts +17 -1
- package/dist/providers/provider-input-support.d.ts +18 -2
- package/dist/providers/provider-instance-manager.d.ts +1 -0
- package/dist/providers/provider-instance.d.ts +4 -0
- package/dist/repo-mesh-types.d.ts +34 -0
- package/dist/session-host/runtime-support.d.ts +2 -1
- package/dist/shared-types.d.ts +8 -0
- package/dist/types.d.ts +9 -0
- package/package.json +4 -5
- package/src/chat/subscription-updates.ts +3 -1
- package/src/cli-adapters/provider-cli-adapter.ts +44 -11
- package/src/cli-adapters/provider-cli-runtime.ts +3 -2
- package/src/cli-adapters/provider-cli-shared.ts +201 -15
- package/src/commands/chat-commands.ts +166 -16
- package/src/commands/cli-manager.ts +78 -5
- package/src/commands/handler.ts +13 -4
- package/src/commands/mesh-coordinator.ts +155 -5
- package/src/commands/router.d.ts +1 -0
- package/src/commands/router.ts +606 -32
- package/src/config/mesh-config.ts +27 -2
- package/src/git/git-commands.ts +5 -1
- package/src/git/git-types.ts +1 -0
- package/src/git/git-worktree.ts +214 -0
- package/src/git/index.ts +14 -0
- package/src/index.ts +20 -1
- package/src/mesh/coordinator-prompt.ts +36 -14
- package/src/mesh/mesh-events.ts +173 -42
- package/src/providers/acp-provider-instance.ts +118 -30
- package/src/providers/chat-message-normalization.ts +241 -0
- package/src/providers/cli-provider-instance.d.ts +2 -0
- package/src/providers/cli-provider-instance.ts +219 -13
- package/src/providers/contracts.ts +25 -1
- package/src/providers/io-contracts.ts +63 -5
- package/src/providers/provider-input-support.ts +125 -1
- package/src/providers/provider-instance-manager.ts +20 -1
- package/src/providers/provider-instance.ts +4 -0
- package/src/providers/provider-schema.ts +38 -8
- package/src/providers/read-chat-contract.ts +8 -0
- package/src/repo-mesh-types.ts +38 -0
- package/src/session-host/runtime-support.ts +55 -7
- package/src/shared-types.ts +8 -0
- package/src/status/builders.ts +5 -3
- package/src/status/reporter.ts +6 -0
- package/src/types.ts +9 -0
|
@@ -173,32 +173,218 @@ export interface CliProviderModule {
|
|
|
173
173
|
function stripAnsi(str: string): string {
|
|
174
174
|
// eslint-disable-next-line no-control-regex
|
|
175
175
|
return str
|
|
176
|
-
.replace(/\x1B\][^\x07]
|
|
177
|
-
.replace(/\x1B\][\s\S]*?\x1B\\/g, '')
|
|
176
|
+
.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, '')
|
|
178
177
|
.replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, '')
|
|
179
|
-
.replace(/\x1B
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
type SavedCursor = { row: number; col: number };
|
|
182
|
+
|
|
183
|
+
function parseCount(params: string, fallback = 1): number {
|
|
184
|
+
const first = Number(String(params || '').split(';')[0] || fallback);
|
|
185
|
+
return Math.max(1, Number.isFinite(first) ? first : fallback);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function isCombiningMark(ch: string): boolean {
|
|
189
|
+
return /[\u0300-\u036F\u1AB0-\u1AFF\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/.test(ch);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function isWideCodePoint(ch: string): boolean {
|
|
193
|
+
const cp = ch.codePointAt(0) || 0;
|
|
194
|
+
return cp >= 0x1100 && (
|
|
195
|
+
cp <= 0x115F || cp === 0x2329 || cp === 0x232A ||
|
|
196
|
+
(cp >= 0x2E80 && cp <= 0xA4CF && cp !== 0x303F) ||
|
|
197
|
+
(cp >= 0xAC00 && cp <= 0xD7A3) ||
|
|
198
|
+
(cp >= 0xF900 && cp <= 0xFAFF) ||
|
|
199
|
+
(cp >= 0xFE10 && cp <= 0xFE19) ||
|
|
200
|
+
(cp >= 0xFE30 && cp <= 0xFE6F) ||
|
|
201
|
+
(cp >= 0xFF00 && cp <= 0xFF60) ||
|
|
202
|
+
(cp >= 0xFFE0 && cp <= 0xFFE6) ||
|
|
203
|
+
(cp >= 0x1F300 && cp <= 0x1FAFF)
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Stateful, transcript-oriented terminal cell accumulator.
|
|
209
|
+
*
|
|
210
|
+
* CLI transcript parsing must not consume raw PTY append text for user-visible
|
|
211
|
+
* readback: CLIs rewrite prompts/status/tool lines with CR, BS, CSI cursor
|
|
212
|
+
* motion and erase-line. This accumulator preserves parser state across chunks
|
|
213
|
+
* and mutates rendered cells before exposing plain transcript text. It is a
|
|
214
|
+
* deliberately small terminal model for readback buffers; live UI rendering still
|
|
215
|
+
* uses TerminalScreen's ghostty/xterm backend.
|
|
216
|
+
*/
|
|
217
|
+
export class TerminalTranscriptAccumulator {
|
|
218
|
+
private lines: string[][] = [[]];
|
|
219
|
+
private row = 0;
|
|
220
|
+
private col = 0;
|
|
221
|
+
private savedCursor: SavedCursor | null = null;
|
|
222
|
+
private pendingEscape = '';
|
|
223
|
+
|
|
224
|
+
append(data: string): string {
|
|
225
|
+
const input = this.pendingEscape + String(data || '');
|
|
226
|
+
this.pendingEscape = '';
|
|
227
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
228
|
+
let ch = input[i];
|
|
229
|
+
if (ch === '\x1B') {
|
|
230
|
+
const consumed = this.consumeEscape(input.slice(i));
|
|
231
|
+
if (consumed === 0) {
|
|
232
|
+
this.pendingEscape = input.slice(i);
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
i += consumed - 1;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const cp = input.codePointAt(i);
|
|
239
|
+
if (cp && cp > 0xFFFF) {
|
|
240
|
+
ch = String.fromCodePoint(cp);
|
|
241
|
+
i += 1;
|
|
242
|
+
}
|
|
243
|
+
this.writeControlOrChar(ch);
|
|
244
|
+
}
|
|
245
|
+
return this.getText();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
reset(): void {
|
|
249
|
+
this.lines = [[]];
|
|
250
|
+
this.row = 0;
|
|
251
|
+
this.col = 0;
|
|
252
|
+
this.savedCursor = null;
|
|
253
|
+
this.pendingEscape = '';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
getText(): string {
|
|
257
|
+
return this.lines.map(line => line.join('').replace(/[ \t]+$/g, '')).join('\n');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private ensureRow(row = this.row): void {
|
|
261
|
+
while (this.lines.length <= row) this.lines.push([]);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private writeControlOrChar(ch: string): void {
|
|
265
|
+
if (ch === '\r') {
|
|
266
|
+
this.col = 0;
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (ch === '\n') {
|
|
270
|
+
this.row += 1;
|
|
271
|
+
this.col = 0;
|
|
272
|
+
this.ensureRow();
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (ch === '\b') {
|
|
276
|
+
this.col = Math.max(0, this.col - 1);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (ch < ' ' || ch === '\x7F') return;
|
|
280
|
+
|
|
281
|
+
this.ensureRow();
|
|
282
|
+
const line = this.lines[this.row];
|
|
283
|
+
if (isCombiningMark(ch) && this.col > 0) {
|
|
284
|
+
line[this.col - 1] = `${line[this.col - 1] || ''}${ch}`;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
while (line.length < this.col) line.push(' ');
|
|
288
|
+
const wide = isWideCodePoint(ch);
|
|
289
|
+
line[this.col] = ch;
|
|
290
|
+
if (wide) line[this.col + 1] = '';
|
|
291
|
+
this.col += wide ? 2 : 1;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private consumeEscape(seq: string): number {
|
|
295
|
+
if (seq.length < 2) return 0;
|
|
296
|
+
const next = seq[1];
|
|
297
|
+
if (next === '7') {
|
|
298
|
+
this.savedCursor = { row: this.row, col: this.col };
|
|
299
|
+
return 2;
|
|
300
|
+
}
|
|
301
|
+
if (next === '8') {
|
|
302
|
+
if (this.savedCursor) {
|
|
303
|
+
this.row = this.savedCursor.row;
|
|
304
|
+
this.col = this.savedCursor.col;
|
|
305
|
+
this.ensureRow();
|
|
306
|
+
}
|
|
307
|
+
return 2;
|
|
308
|
+
}
|
|
309
|
+
if (next === ']') {
|
|
310
|
+
const bel = seq.indexOf('\x07', 2);
|
|
311
|
+
const st = seq.indexOf('\x1B\\', 2);
|
|
312
|
+
const end = bel >= 0 && (st < 0 || bel < st) ? bel + 1 : st >= 0 ? st + 2 : 0;
|
|
313
|
+
return end;
|
|
314
|
+
}
|
|
315
|
+
if (next === '[') {
|
|
316
|
+
const match = seq.match(/^\x1B\[([0-?]*)([ -/]*)([@-~])/);
|
|
317
|
+
if (!match) return seq.length < 32 ? 0 : 1;
|
|
318
|
+
this.applyCsi(match[1] || '', match[3]);
|
|
319
|
+
return match[0].length;
|
|
320
|
+
}
|
|
321
|
+
if (/[P^_X]/.test(next)) {
|
|
322
|
+
const bel = seq.indexOf('\x07', 2);
|
|
323
|
+
const st = seq.indexOf('\x1B\\', 2);
|
|
324
|
+
const end = bel >= 0 && (st < 0 || bel < st) ? bel + 1 : st >= 0 ? st + 2 : 0;
|
|
325
|
+
return end;
|
|
326
|
+
}
|
|
327
|
+
return 2;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private applyCsi(params: string, final: string): void {
|
|
331
|
+
const count = parseCount(params);
|
|
332
|
+
this.ensureRow();
|
|
333
|
+
if (final === 'A') this.row = Math.max(0, this.row - count);
|
|
334
|
+
else if (final === 'B') this.row += count;
|
|
335
|
+
else if (final === 'C') this.col += count;
|
|
336
|
+
else if (final === 'D') this.col = Math.max(0, this.col - count);
|
|
337
|
+
else if (final === 'G') this.col = Math.max(0, count - 1);
|
|
338
|
+
else if (final === 'H' || final === 'f') {
|
|
339
|
+
const parts = String(params || '').split(';');
|
|
340
|
+
this.row = Math.max(0, (Number(parts[0] || 1) || 1) - 1);
|
|
341
|
+
this.col = Math.max(0, (Number(parts[1] || 1) || 1) - 1);
|
|
342
|
+
} else if (final === 'J') {
|
|
343
|
+
const mode = Number(params || 0) || 0;
|
|
344
|
+
if (mode === 2 || mode === 3) {
|
|
345
|
+
this.lines = [[]];
|
|
346
|
+
this.row = 0;
|
|
347
|
+
this.col = 0;
|
|
348
|
+
} else if (mode === 0) {
|
|
349
|
+
this.lines[this.row] = this.lines[this.row].slice(0, this.col);
|
|
350
|
+
this.lines.splice(this.row + 1);
|
|
351
|
+
} else if (mode === 1) {
|
|
352
|
+
for (let r = 0; r < this.row; r += 1) this.lines[r] = [];
|
|
353
|
+
const line = this.lines[this.row];
|
|
354
|
+
for (let c = 0; c <= Math.min(this.col, line.length - 1); c += 1) line[c] = ' ';
|
|
355
|
+
}
|
|
356
|
+
} else if (final === 'K') {
|
|
357
|
+
const mode = Number(params || 0) || 0;
|
|
358
|
+
const line = this.lines[this.row];
|
|
359
|
+
if (mode === 2) this.lines[this.row] = [];
|
|
360
|
+
else if (mode === 1) {
|
|
361
|
+
for (let c = 0; c <= Math.min(this.col, line.length - 1); c += 1) line[c] = ' ';
|
|
362
|
+
} else {
|
|
363
|
+
this.lines[this.row] = line.slice(0, this.col);
|
|
364
|
+
}
|
|
365
|
+
} else if (final === 's') {
|
|
366
|
+
this.savedCursor = { row: this.row, col: this.col };
|
|
367
|
+
} else if (final === 'u') {
|
|
368
|
+
if (this.savedCursor) {
|
|
369
|
+
this.row = this.savedCursor.row;
|
|
370
|
+
this.col = this.savedCursor.col;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
this.ensureRow();
|
|
374
|
+
}
|
|
182
375
|
}
|
|
183
376
|
|
|
184
377
|
function stripTerminalNoise(str: string): string {
|
|
185
378
|
return String(str || '')
|
|
186
379
|
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, '')
|
|
187
|
-
.replace(/(^|[\s([])(?:\??\d{1,4}(?:;\d{1,4})*[A-Za-z])(?=$|[\s)\]])/g, '$1')
|
|
188
|
-
.replace(/(^|[\s([])(?:\[\??\d{1,4}(?:;\d{1,4})*[A-Za-z])(?=$|[\s)\]])/g, '$1')
|
|
189
|
-
.replace(/(^|[\s([])(?:\d{1,4};\?)(?=$|[\s)\]])/g, '$1')
|
|
190
|
-
.replace(/(^|[\s([])(?:\d+\$r[0-9;\" ]*[A-Za-z]?)(?=$|[\s)\]])/g, '$1')
|
|
191
|
-
.replace(/(^|[\s([])(?:>\|[A-Za-z0-9_.:-]+(?:\([^)]*\))?)(?=$|[\s)\]])/g, '$1')
|
|
192
|
-
.replace(/(^|[\s([])(?:[A-Z]\d(?:\s+[A-Z]\d)+)(?=$|[\s)\]])/g, '$1')
|
|
193
|
-
.replace(/(^|[\s([])(?:\d+;[^\s)\]]+)(?=$|[\s)\]])/g, '$1')
|
|
194
380
|
.replace(/\r+/g, '\n')
|
|
195
381
|
.replace(/[ \t]+\n/g, '\n')
|
|
196
|
-
.replace(/\n{
|
|
197
|
-
.replace(/ {2,}/g, ' ');
|
|
382
|
+
.replace(/\n{4,}/g, '\n\n\n');
|
|
198
383
|
}
|
|
199
384
|
|
|
200
385
|
export function sanitizeTerminalText(str: string): string {
|
|
201
|
-
|
|
386
|
+
const accumulator = new TerminalTranscriptAccumulator();
|
|
387
|
+
return stripTerminalNoise(stripAnsi(accumulator.append(str)));
|
|
202
388
|
}
|
|
203
389
|
|
|
204
390
|
export function listCliScriptNames(scripts: CliScripts | undefined): string[] {
|
|
@@ -16,18 +16,24 @@ import type { ProviderInstance } from '../providers/provider-instance.js';
|
|
|
16
16
|
import { readProviderChatHistory } from '../config/chat-history.js';
|
|
17
17
|
import { LOG, getRecentLogs } from '../logging/logger.js';
|
|
18
18
|
import { getRecentDebugTrace, recordDebugTrace } from '../logging/debug-trace.js';
|
|
19
|
-
import { buildChatMessageSignature } from '../chat/chat-signatures.js';
|
|
19
|
+
import { buildChatMessageSignature, hashSignatureParts } from '../chat/chat-signatures.js';
|
|
20
20
|
import type { ChatMessage } from '../types.js';
|
|
21
21
|
import type { SessionTransport } from '../shared-types.js';
|
|
22
|
+
import { filterUserFacingChatMessages, normalizeChatMessages } from '../providers/chat-message-normalization.js';
|
|
22
23
|
|
|
23
24
|
const RECENT_SEND_WINDOW_MS = 1200;
|
|
24
25
|
export const READ_CHAT_PROVIDER_EVAL_TIMEOUT_MS = 25_000;
|
|
26
|
+
const HERMES_CLI_STARTING_SEND_SETTLE_MS = 2_000;
|
|
25
27
|
const recentSendByTarget = new Map<string, number>();
|
|
26
28
|
|
|
27
29
|
interface ApprovalSelectableInstance extends ProviderInstance {
|
|
28
30
|
recordApprovalSelection?(buttonText: string): void;
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
interface RuntimeChatMessageMerger extends ProviderInstance {
|
|
34
|
+
mergeRuntimeChatMessages?(messages: ChatMessage[]): ChatMessage[];
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
type LegacyStringScript = (params?: Record<string, unknown> | string) => string;
|
|
32
38
|
|
|
33
39
|
function getCurrentProviderType(h: CommandHelpers, fallback = ''): string {
|
|
@@ -87,16 +93,51 @@ function buildRecentSendKey(h: CommandHelpers, args: any, provider: ProviderModu
|
|
|
87
93
|
return `${transport}:${target}:${signature.trim()}`;
|
|
88
94
|
}
|
|
89
95
|
|
|
90
|
-
function
|
|
96
|
+
function summarizeSendInputPart(part: any): string {
|
|
97
|
+
if (!part || typeof part !== 'object') return String(part ?? '');
|
|
98
|
+
if (part.type === 'text') return `text:${String(part.text || '').trim()}`;
|
|
99
|
+
const fields = [
|
|
100
|
+
`type=${String(part.type || '')}`,
|
|
101
|
+
`mime=${String(part.mimeType || '')}`,
|
|
102
|
+
`uri=${String(part.uri || '')}`,
|
|
103
|
+
`name=${String(part.name || '')}`,
|
|
104
|
+
];
|
|
105
|
+
const data = typeof part.data === 'string'
|
|
106
|
+
? part.data
|
|
107
|
+
: typeof part.resource?.blob === 'string'
|
|
108
|
+
? part.resource.blob
|
|
109
|
+
: '';
|
|
110
|
+
if (data) fields.push(`dataLen=${data.length}`, `dataHash=${hashSignatureParts([data]).slice(0, 12)}`);
|
|
111
|
+
const textish = [part.alt, part.transcript, part.description, part.title, part.resource?.uri]
|
|
112
|
+
.filter((value) => typeof value === 'string' && value.trim())
|
|
113
|
+
.join('\u001f');
|
|
114
|
+
if (textish) fields.push(`meta=${hashSignatureParts([textish]).slice(0, 12)}`);
|
|
115
|
+
return fields.join(';');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function buildSendInputSignature(input: InputEnvelope): string {
|
|
91
119
|
const text = typeof input.textFallback === 'string' ? input.textFallback.trim() : '';
|
|
92
|
-
|
|
93
|
-
return
|
|
120
|
+
const partSummaries = (input.parts || []).map(summarizeSendInputPart);
|
|
121
|
+
return hashSignatureParts([text, ...partSummaries]);
|
|
94
122
|
}
|
|
95
123
|
|
|
96
124
|
function getSendChatInputEnvelope(args: any): InputEnvelope {
|
|
97
125
|
return normalizeInputEnvelope(args?.input ? { input: args.input } : args);
|
|
98
126
|
}
|
|
99
127
|
|
|
128
|
+
function sleep(ms: number): Promise<void> {
|
|
129
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function waitOnceForFreshHermesCliStart(adapter: CliAdapter, log: (msg: string) => void): Promise<void> {
|
|
133
|
+
if (adapter.cliType !== 'hermes-cli') return;
|
|
134
|
+
const status = typeof adapter.getStatus === 'function' ? adapter.getStatus()?.status : undefined;
|
|
135
|
+
if (status !== 'starting') return;
|
|
136
|
+
|
|
137
|
+
log(`Hermes CLI is still starting; waiting ${HERMES_CLI_STARTING_SEND_SETTLE_MS}ms before first send`);
|
|
138
|
+
await sleep(HERMES_CLI_STARTING_SEND_SETTLE_MS);
|
|
139
|
+
}
|
|
140
|
+
|
|
100
141
|
function getHistorySessionId(h: CommandHelpers, args: any): string | undefined {
|
|
101
142
|
const explicit = typeof args?.historySessionId === 'string' ? args.historySessionId.trim() : '';
|
|
102
143
|
if (explicit) return explicit;
|
|
@@ -177,7 +218,7 @@ function normalizeReadChatTailLimit(args: any): number {
|
|
|
177
218
|
|
|
178
219
|
function normalizeReadChatMessages(payload: Record<string, any>): ChatMessage[] {
|
|
179
220
|
const messages = Array.isArray(payload.messages) ? payload.messages as ChatMessage[] : [];
|
|
180
|
-
return messages;
|
|
221
|
+
return normalizeChatMessages(messages);
|
|
181
222
|
}
|
|
182
223
|
|
|
183
224
|
|
|
@@ -250,6 +291,40 @@ function normalizeReadChatCommandStatus(status: unknown, activeModal: unknown):
|
|
|
250
291
|
}
|
|
251
292
|
}
|
|
252
293
|
|
|
294
|
+
function isGeneratingLikeStatus(status: unknown): boolean {
|
|
295
|
+
return status === 'generating' || status === 'streaming' || status === 'long_generating' || status === 'starting';
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function shouldTrustCliAdapterTerminalStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): boolean {
|
|
299
|
+
if (!isGeneratingLikeStatus(parsedStatus)) return false;
|
|
300
|
+
if (hasNonEmptyModalButtons(activeModal)) return false;
|
|
301
|
+
const adapterRawStatus = typeof adapterStatus?.status === 'string' ? adapterStatus.status.trim() : '';
|
|
302
|
+
if (adapterRawStatus !== 'idle') return false;
|
|
303
|
+
if (typeof adapter.isProcessing === 'function' && adapter.isProcessing()) return false;
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function normalizeCliReadChatStatus(parsedStatus: unknown, activeModal: unknown, adapter: CliAdapter, adapterStatus: any): string {
|
|
308
|
+
if (shouldTrustCliAdapterTerminalStatus(parsedStatus, activeModal, adapter, adapterStatus)) return 'idle';
|
|
309
|
+
return typeof parsedStatus === 'string' && parsedStatus.trim() ? parsedStatus : 'idle';
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function finalizeStreamingMessagesWhenIdle(messages: ChatMessage[], status: string): ChatMessage[] {
|
|
313
|
+
if (status !== 'idle') return messages;
|
|
314
|
+
return messages.map((message) => {
|
|
315
|
+
const meta = message.meta && typeof message.meta === 'object'
|
|
316
|
+
? message.meta as Record<string, unknown>
|
|
317
|
+
: undefined;
|
|
318
|
+
const hasStreamingMeta = meta?.streaming === true;
|
|
319
|
+
if (message.bubbleState !== 'streaming' && !hasStreamingMeta) return message;
|
|
320
|
+
return {
|
|
321
|
+
...message,
|
|
322
|
+
...(message.bubbleState === 'streaming' ? { bubbleState: 'final' as const } : {}),
|
|
323
|
+
...(hasStreamingMeta ? { meta: { ...meta, streaming: false } } : {}),
|
|
324
|
+
};
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
253
328
|
function buildReadChatCommandResult(payload: Record<string, any>, args: any): CommandResult {
|
|
254
329
|
let validatedPayload: Record<string, any>;
|
|
255
330
|
const debugReadChat = payload?.debugReadChat && typeof payload.debugReadChat === 'object'
|
|
@@ -264,13 +339,26 @@ function buildReadChatCommandResult(payload: Record<string, any>, args: any): Co
|
|
|
264
339
|
return { success: false, error: error?.message || String(error) };
|
|
265
340
|
}
|
|
266
341
|
const messages = normalizeReadChatMessages(validatedPayload);
|
|
267
|
-
const
|
|
342
|
+
const visibleMessages = filterUserFacingChatMessages(messages);
|
|
343
|
+
const sync = buildFullTail(visibleMessages, normalizeReadChatTailLimit(args));
|
|
344
|
+
const hiddenMsgCount = Math.max(0, messages.length - visibleMessages.length);
|
|
345
|
+
const returnedDebugReadChat = debugReadChat
|
|
346
|
+
? {
|
|
347
|
+
...debugReadChat,
|
|
348
|
+
fullMsgCount: typeof debugReadChat.fullMsgCount === 'number'
|
|
349
|
+
? debugReadChat.fullMsgCount
|
|
350
|
+
: messages.length,
|
|
351
|
+
visibleMsgCount: visibleMessages.length,
|
|
352
|
+
hiddenMsgCount,
|
|
353
|
+
returnedMsgCount: sync.messages.length,
|
|
354
|
+
}
|
|
355
|
+
: undefined;
|
|
268
356
|
return {
|
|
269
357
|
success: true,
|
|
270
358
|
...validatedPayload,
|
|
271
359
|
messages: sync.messages,
|
|
272
360
|
totalMessages: sync.totalMessages,
|
|
273
|
-
...(
|
|
361
|
+
...(returnedDebugReadChat ? { debugReadChat: returnedDebugReadChat } : {}),
|
|
274
362
|
};
|
|
275
363
|
}
|
|
276
364
|
|
|
@@ -464,6 +552,18 @@ function buildChatDebugBundleSummary(bundle: Record<string, unknown>): Record<st
|
|
|
464
552
|
const readChat = bundle.readChat && typeof bundle.readChat === 'object' ? bundle.readChat as Record<string, unknown> : {};
|
|
465
553
|
const cli = bundle.cli && typeof bundle.cli === 'object' ? bundle.cli as Record<string, unknown> : null;
|
|
466
554
|
const frontend = bundle.frontend && typeof bundle.frontend === 'object' ? bundle.frontend as Record<string, unknown> : null;
|
|
555
|
+
const debugReadChat = readChat.debugReadChat && typeof readChat.debugReadChat === 'object'
|
|
556
|
+
? readChat.debugReadChat as Record<string, unknown>
|
|
557
|
+
: {};
|
|
558
|
+
const parsedStatus = cli?.parsedStatus && typeof cli.parsedStatus === 'object'
|
|
559
|
+
? cli.parsedStatus as Record<string, unknown>
|
|
560
|
+
: null;
|
|
561
|
+
const cliParsedMessageCount = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages.length : undefined;
|
|
562
|
+
const readChatReturnedMessages = Array.isArray(readChat.messagesTail) ? readChat.messagesTail.length : undefined;
|
|
563
|
+
const cliPartialResponse = typeof cli?.partialResponse === 'string' ? cli.partialResponse : '';
|
|
564
|
+
const readChatStatus = typeof readChat.status === 'string' ? readChat.status : '';
|
|
565
|
+
const cliStatus = typeof cli?.status === 'string' ? cli.status : '';
|
|
566
|
+
const cliParsedStatus = typeof parsedStatus?.status === 'string' ? parsedStatus.status : '';
|
|
467
567
|
return {
|
|
468
568
|
createdAt: bundle.createdAt,
|
|
469
569
|
targetSessionId: target.targetSessionId,
|
|
@@ -472,8 +572,22 @@ function buildChatDebugBundleSummary(bundle: Record<string, unknown>): Record<st
|
|
|
472
572
|
readChatSuccess: readChat.success,
|
|
473
573
|
readChatStatus: readChat.status,
|
|
474
574
|
readChatTotalMessages: readChat.totalMessages,
|
|
575
|
+
readChatReturnedMessages,
|
|
475
576
|
cliStatus: cli?.status,
|
|
577
|
+
cliParsedStatus: cliParsedStatus || undefined,
|
|
476
578
|
cliMessageCount: cli?.messageCount,
|
|
579
|
+
cliParsedMessageCount,
|
|
580
|
+
cliPartialResponseChars: cliPartialResponse.length,
|
|
581
|
+
parserAdapterStatusMismatch: Boolean(cliStatus && cliParsedStatus && cliStatus !== cliParsedStatus),
|
|
582
|
+
parserReadChatStatusMismatch: Boolean(readChatStatus && cliParsedStatus && readChatStatus !== cliParsedStatus),
|
|
583
|
+
readChatDebug: Object.keys(debugReadChat).length ? {
|
|
584
|
+
adapterStatus: debugReadChat.adapterStatus,
|
|
585
|
+
parsedStatus: debugReadChat.parsedStatus,
|
|
586
|
+
returnedStatus: debugReadChat.returnedStatus,
|
|
587
|
+
parsedMsgCount: debugReadChat.parsedMsgCount,
|
|
588
|
+
returnedMsgCount: debugReadChat.returnedMsgCount,
|
|
589
|
+
shouldPreferAdapterMessages: debugReadChat.shouldPreferAdapterMessages,
|
|
590
|
+
} : undefined,
|
|
477
591
|
hasFrontendSnapshot: !!frontend,
|
|
478
592
|
};
|
|
479
593
|
}
|
|
@@ -720,7 +834,7 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
|
|
|
720
834
|
}
|
|
721
835
|
|
|
722
836
|
export async function handleReadChat(h: CommandHelpers, args: any): Promise<CommandResult> {
|
|
723
|
-
const provider = h.getProvider(args?.agentType);
|
|
837
|
+
const provider = h.getProvider(args?.agentType || args?.providerType);
|
|
724
838
|
const transport = getTargetTransport(h, provider);
|
|
725
839
|
const historySessionId = getHistorySessionId(h, args);
|
|
726
840
|
|
|
@@ -760,10 +874,17 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
760
874
|
? parsedRecord.coverage
|
|
761
875
|
: undefined;
|
|
762
876
|
const activeModal = parsedRecord.activeModal ?? parsedRecord.modal ?? null;
|
|
763
|
-
const returnedStatus = parsedRecord.status
|
|
764
|
-
|
|
877
|
+
const returnedStatus = normalizeCliReadChatStatus(parsedRecord.status, activeModal, adapter, adapterStatus);
|
|
878
|
+
const runtimeMessageMerger = getTargetInstance(h, args) as RuntimeChatMessageMerger | null;
|
|
879
|
+
const parsedMessages = finalizeStreamingMessagesWhenIdle(parsedRecord.messages as ChatMessage[], returnedStatus);
|
|
880
|
+
const returnedMessages = runtimeMessageMerger?.category === 'cli'
|
|
881
|
+
&& runtimeMessageMerger.type === adapter.cliType
|
|
882
|
+
&& typeof runtimeMessageMerger.mergeRuntimeChatMessages === 'function'
|
|
883
|
+
? runtimeMessageMerger.mergeRuntimeChatMessages(parsedMessages)
|
|
884
|
+
: parsedMessages;
|
|
885
|
+
LOG.debug('Command', `[read_chat] cli-like parsed provider=${adapter.cliType} target=${String(args?.targetSessionId || '')} adapterStatus=${String(adapterStatus.status || '')} parsedStatus=${String(parsedRecord.status || '')} parsedMsgCount=${parsedRecord.messages.length} returnedMsgCount=${returnedMessages.length}`);
|
|
765
886
|
return buildReadChatCommandResult({
|
|
766
|
-
messages:
|
|
887
|
+
messages: returnedMessages,
|
|
767
888
|
status: returnedStatus,
|
|
768
889
|
activeModal,
|
|
769
890
|
debugReadChat: {
|
|
@@ -774,7 +895,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
774
895
|
returnedStatus: String(returnedStatus || ''),
|
|
775
896
|
shouldPreferAdapterMessages: false,
|
|
776
897
|
parsedMsgCount: parsedRecord.messages.length,
|
|
777
|
-
returnedMsgCount:
|
|
898
|
+
returnedMsgCount: returnedMessages.length,
|
|
778
899
|
},
|
|
779
900
|
...(title ? { title } : {}),
|
|
780
901
|
...(providerSessionId ? { providerSessionId } : {}),
|
|
@@ -1041,14 +1162,28 @@ export async function handleSendChat(h: CommandHelpers, args: any): Promise<Comm
|
|
|
1041
1162
|
}
|
|
1042
1163
|
}
|
|
1043
1164
|
|
|
1044
|
-
// PTY transport:
|
|
1165
|
+
// PTY transport: route structured input through the provider instance so
|
|
1166
|
+
// provider-specific CLI attachment strategies (for example Hermes file-path
|
|
1167
|
+
// image prompts) are applied instead of collapsing everything to text.
|
|
1045
1168
|
if (transport === 'pty') {
|
|
1046
1169
|
const adapter = getTargetedCliAdapter(h, args, provider?.type);
|
|
1047
1170
|
if (adapter) {
|
|
1048
1171
|
_log(`${transport} adapter: ${adapter.cliType}`);
|
|
1049
1172
|
try {
|
|
1173
|
+
const hasStructuredParts = input.parts.some((part) => part.type !== 'text');
|
|
1174
|
+
if (hasStructuredParts) {
|
|
1175
|
+
const target = getTargetInstance(h, args);
|
|
1176
|
+
if (!target || target.category !== 'cli') {
|
|
1177
|
+
return { success: false, error: `CLI instance not found for ${provider?.type || args?.agentType || 'unknown'}` };
|
|
1178
|
+
}
|
|
1179
|
+
assertProviderSupportsDeclaredInput(provider, input);
|
|
1180
|
+
await waitOnceForFreshHermesCliStart(adapter, _log);
|
|
1181
|
+
target.onEvent('send_message', { input });
|
|
1182
|
+
return _logSendSuccess(`${transport}-instance`, target.type);
|
|
1183
|
+
}
|
|
1050
1184
|
assertTextOnlyInput(provider, input);
|
|
1051
1185
|
if (!text) return { success: false, error: 'text required for PTY send' };
|
|
1186
|
+
await waitOnceForFreshHermesCliStart(adapter, _log);
|
|
1052
1187
|
await adapter.sendMessage(text);
|
|
1053
1188
|
return _logSendSuccess(`${transport}-adapter`, adapter.cliType);
|
|
1054
1189
|
} catch (e: any) {
|
|
@@ -1571,11 +1706,26 @@ export async function handleResolveAction(h: CommandHelpers, args: any): Promise
|
|
|
1571
1706
|
&& status.activeModal.buttons.some((candidate) => typeof candidate === 'string' && candidate.trim())
|
|
1572
1707
|
? status.activeModal
|
|
1573
1708
|
: null;
|
|
1574
|
-
const
|
|
1575
|
-
|
|
1709
|
+
const parsedStatus = !statusModal && !surfacedModal && typeof adapter.getScriptParsedStatus === 'function'
|
|
1710
|
+
? (() => {
|
|
1711
|
+
try {
|
|
1712
|
+
return parseMaybeJson(adapter.getScriptParsedStatus());
|
|
1713
|
+
} catch {
|
|
1714
|
+
return null;
|
|
1715
|
+
}
|
|
1716
|
+
})()
|
|
1717
|
+
: null;
|
|
1718
|
+
const parsedModal = parsedStatus?.status === 'waiting_approval'
|
|
1719
|
+
&& parsedStatus?.activeModal
|
|
1720
|
+
&& Array.isArray(parsedStatus.activeModal.buttons)
|
|
1721
|
+
&& parsedStatus.activeModal.buttons.some((candidate: unknown) => typeof candidate === 'string' && candidate.trim())
|
|
1722
|
+
? parsedStatus.activeModal
|
|
1723
|
+
: null;
|
|
1724
|
+
const effectiveModal = statusModal || surfacedModal || parsedModal;
|
|
1725
|
+
const effectiveStatus = status?.status === 'waiting_approval' || targetState?.activeChat?.status === 'waiting_approval' || parsedStatus?.status === 'waiting_approval'
|
|
1576
1726
|
? 'waiting_approval'
|
|
1577
1727
|
: status?.status;
|
|
1578
|
-
LOG.info('Command', `[resolveAction] CLI PTY gate target=${String(args?.targetSessionId || '')} rawStatus=${String(status?.status || '')} effectiveStatus=${String(effectiveStatus || '')} statusModal=${statusModal ? 'yes' : 'no'} surfacedModal=${surfacedModal ? 'yes' : 'no'} instance=${targetInstance ? 'yes' : 'no'}`);
|
|
1728
|
+
LOG.info('Command', `[resolveAction] CLI PTY gate target=${String(args?.targetSessionId || '')} rawStatus=${String(status?.status || '')} effectiveStatus=${String(effectiveStatus || '')} statusModal=${statusModal ? 'yes' : 'no'} surfacedModal=${surfacedModal ? 'yes' : 'no'} parsedModal=${parsedModal ? 'yes' : 'no'} instance=${targetInstance ? 'yes' : 'no'}`);
|
|
1579
1729
|
if (!effectiveModal) {
|
|
1580
1730
|
return { success: false, error: 'Not in approval state' };
|
|
1581
1731
|
}
|