@adhdev/daemon-core 0.6.57 → 0.6.58

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 (37) hide show
  1. package/dist/index.d.ts +17 -0
  2. package/dist/index.js +252 -78
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/providers/_builtin/cli/aider-cli/scripts/1.0/parse_output.js +51 -3
  6. package/providers/_builtin/cli/claude-cli/provider.json +18 -6
  7. package/providers/_builtin/cli/claude-cli/scripts/1.0/detect_status.js +68 -16
  8. package/providers/_builtin/cli/claude-cli/scripts/1.0/parse_approval.js +81 -22
  9. package/providers/_builtin/cli/claude-cli/scripts/1.0/parse_output.js +347 -94
  10. package/providers/_builtin/cli/codex-cli/provider.json +2 -0
  11. package/providers/_builtin/cli/codex-cli/scripts/1.0/detect_status.js +44 -10
  12. package/providers/_builtin/cli/codex-cli/scripts/1.0/parse_approval.js +83 -7
  13. package/providers/_builtin/cli/codex-cli/scripts/1.0/parse_output.js +501 -47
  14. package/providers/_builtin/cli/cursor-cli/scripts/1.0/parse_output.js +1 -1
  15. package/providers/_builtin/cli/github-copilot-cli/scripts/1.0/parse_output.js +1 -1
  16. package/providers/_builtin/cli/goose-cli/scripts/1.0/parse_output.js +1 -1
  17. package/providers/_builtin/cli/opencode-cli/scripts/1.0/parse_output.js +1 -1
  18. package/providers/_builtin/ide/vscode/provider.json +5 -1
  19. package/providers/_builtin/ide/vscode/scripts/1.0/focus_editor.js +1 -0
  20. package/providers/_builtin/ide/vscode/scripts/1.0/list_models.js +1 -0
  21. package/providers/_builtin/ide/vscode/scripts/1.0/list_sessions.js +1 -0
  22. package/providers/_builtin/ide/vscode/scripts/1.0/new_session.js +1 -0
  23. package/providers/_builtin/ide/vscode/scripts/1.0/open_panel.js +1 -0
  24. package/providers/_builtin/ide/vscode/scripts/1.0/read_chat.js +1 -0
  25. package/providers/_builtin/ide/vscode/scripts/1.0/resolve_action.js +1 -0
  26. package/providers/_builtin/ide/vscode/scripts/1.0/scripts.js +25 -0
  27. package/providers/_builtin/ide/vscode/scripts/1.0/send_message.js +1 -0
  28. package/providers/_builtin/ide/vscode/scripts/1.0/set_model.js +1 -0
  29. package/providers/_builtin/ide/vscode/scripts/1.0/switch_session.js +1 -0
  30. package/providers/_builtin/registry.json +1 -1
  31. package/src/cli-adapters/provider-cli-adapter.ts +223 -48
  32. package/src/commands/chat-commands.ts +7 -1
  33. package/src/config/chat-history.ts +53 -1
  34. package/src/daemon/dev-server.ts +7 -9
  35. package/src/providers/cli-provider-instance.ts +10 -23
  36. package/src/providers/provider-instance.ts +1 -0
  37. package/src/providers/version-archive.ts +4 -1
@@ -0,0 +1 @@
1
+ module.exports = async function(args, context) { return { error: "Not implemented" }; };
@@ -0,0 +1 @@
1
+ module.exports = async function(args, context) { return { error: "Not implemented" }; };
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2026.03.28",
2
+ "version": "2026.03.29",
3
3
  "providers": {
4
4
  "agentpool-acp": {
5
5
  "providerVersion": "0.0.0",
@@ -57,6 +57,7 @@ export interface CliSessionStatus {
57
57
  messages: CliChatMessage[];
58
58
  workingDir: string;
59
59
  activeModal: { message: string; buttons: string[] } | null;
60
+ terminalHistory?: string;
60
61
  }
61
62
 
62
63
  /**
@@ -82,10 +83,19 @@ export interface CliScriptInput {
82
83
  rawBuffer: string; // Raw PTY output (with ANSI)
83
84
  recentBuffer: string; // Recent 1000 chars (ANSI-stripped)
84
85
  screenText: string; // Current visible screen snapshot
86
+ terminalHistory?: string; // Rolling append-only terminal transcript
85
87
  messages: CliChatMessage[]; // Previously parsed messages
86
88
  partialResponse: string; // Current partial response being generated
87
89
  }
88
90
 
91
+ interface TurnParseScope {
92
+ prompt: string;
93
+ startedAt: number;
94
+ bufferStart: number;
95
+ rawBufferStart: number;
96
+ terminalHistoryStart: number;
97
+ }
98
+
89
99
  export interface CliProviderModule {
90
100
  type: string;
91
101
  name: string;
@@ -93,6 +103,7 @@ export interface CliProviderModule {
93
103
  binary: string;
94
104
  sendDelayMs?: number;
95
105
  sendKey?: string;
106
+ submitStrategy?: 'wait_for_echo' | 'immediate';
96
107
  spawn: {
97
108
  command: string;
98
109
  args: string[];
@@ -243,6 +254,44 @@ function promptLikelyVisible(screenText: string, promptSnippet: string): boolean
243
254
  return matched >= required;
244
255
  }
245
256
 
257
+ function splitHistoryLines(text: string): string[] {
258
+ return String(text || '')
259
+ .split('\n')
260
+ .map(line => line.replace(/\s+$/, ''));
261
+ }
262
+
263
+ function normalizeHistoryLine(line: string): string {
264
+ return String(line || '').replace(/\s+/g, ' ').trim();
265
+ }
266
+
267
+ function mergeTerminalHistory(existing: string, snapshot: string): string {
268
+ const next = String(snapshot || '').trim();
269
+ if (!next) return existing;
270
+ const prev = String(existing || '').trim();
271
+ if (!prev) return next;
272
+ if (prev === next || prev.endsWith(next)) return prev;
273
+
274
+ const prevLines = splitHistoryLines(prev);
275
+ const nextLines = splitHistoryLines(next);
276
+ const prevNorm = prevLines.map(normalizeHistoryLine);
277
+ const nextNorm = nextLines.map(normalizeHistoryLine);
278
+
279
+ const maxOverlap = Math.min(prevLines.length, nextLines.length);
280
+ for (let overlap = maxOverlap; overlap >= 1; overlap -= 1) {
281
+ const prevTail = prevNorm.slice(prevNorm.length - overlap);
282
+ const nextHead = nextNorm.slice(0, overlap);
283
+ if (prevTail.every((line, index) => line === nextHead[index])) {
284
+ return [...prevLines, ...nextLines.slice(overlap)].join('\n').trim();
285
+ }
286
+ }
287
+
288
+ const compactPrev = prevNorm.join('\n');
289
+ const compactNext = nextNorm.join('\n');
290
+ if (compactPrev.includes(compactNext)) return prev;
291
+
292
+ return `${prev}\n${next}`.trim();
293
+ }
294
+
246
295
  /**
247
296
  * Normalize provider.json for auto-implement approval detection.
248
297
  * Kept for backward compat with dev-server auto-impl pipeline only.
@@ -288,6 +337,7 @@ export class ProviderCliAdapter implements CliAdapter {
288
337
  private provider: CliProviderModule;
289
338
  private ptyProcess: any = null;
290
339
  private messages: CliChatMessage[] = [];
340
+ private committedMessages: CliChatMessage[] = [];
291
341
  private structuredMessages: CliChatMessage[] = [];
292
342
  private currentStatus: CliSessionStatus['status'] = 'starting';
293
343
  private onStatusChange: (() => void) | null = null;
@@ -343,8 +393,47 @@ export class ProviderCliAdapter implements CliAdapter {
343
393
  private accumulatedRawBuffer: string = '';
344
394
  /** Current visible terminal screen snapshot */
345
395
  private terminalScreen = new TerminalScreen(40, 120);
396
+ /** Rolling append-only terminal transcript built from screen snapshots */
397
+ private terminalHistory: string = '';
346
398
  /** Max accumulated buffer size (last 50KB) */
347
399
  private static readonly MAX_ACCUMULATED_BUFFER = 50000;
400
+ private currentTurnScope: TurnParseScope | null = null;
401
+
402
+ private syncMessageViews(): void {
403
+ this.messages = [...this.committedMessages];
404
+ this.structuredMessages = [...this.committedMessages];
405
+ }
406
+
407
+ private sliceFromOffset(text: string, start: number): string {
408
+ if (!text) return '';
409
+ if (!Number.isFinite(start) || start <= 0) return text;
410
+ if (start >= text.length) return '';
411
+ return text.slice(start);
412
+ }
413
+
414
+ private buildParseInput(baseMessages: CliChatMessage[], partialResponse: string, scope?: TurnParseScope | null): CliScriptInput {
415
+ const buffer = scope
416
+ ? (this.sliceFromOffset(this.terminalHistory, scope.terminalHistoryStart)
417
+ || this.sliceFromOffset(this.accumulatedBuffer, scope.bufferStart)
418
+ || this.accumulatedBuffer)
419
+ : this.accumulatedBuffer;
420
+ const rawBuffer = scope
421
+ ? (this.sliceFromOffset(this.accumulatedRawBuffer, scope.rawBufferStart) || this.accumulatedRawBuffer)
422
+ : this.accumulatedRawBuffer;
423
+ const terminalHistory = scope
424
+ ? (this.sliceFromOffset(this.terminalHistory, scope.terminalHistoryStart) || this.terminalHistory)
425
+ : this.terminalHistory;
426
+
427
+ return {
428
+ buffer,
429
+ rawBuffer,
430
+ recentBuffer: buffer.slice(-1000) || this.recentOutputBuffer,
431
+ screenText: this.terminalScreen.getText(),
432
+ terminalHistory,
433
+ messages: [...baseMessages],
434
+ partialResponse,
435
+ };
436
+ }
348
437
 
349
438
  private setStatus(status: CliSessionStatus['status'], trigger?: string): void {
350
439
  const prev = this.currentStatus;
@@ -362,6 +451,7 @@ export class ProviderCliAdapter implements CliAdapter {
362
451
  private readonly approvalKeys: Record<number, string>;
363
452
  private readonly sendDelayMs: number;
364
453
  private readonly sendKey: string;
454
+ private readonly submitStrategy: 'wait_for_echo' | 'immediate';
365
455
 
366
456
  constructor(provider: CliProviderModule, workingDir: string, private extraArgs: string[] = []) {
367
457
  this.provider = provider;
@@ -389,6 +479,7 @@ export class ProviderCliAdapter implements CliAdapter {
389
479
  this.sendKey = typeof (provider as any).sendKey === 'string' && (provider as any).sendKey.length > 0
390
480
  ? (provider as any).sendKey
391
481
  : '\r';
482
+ this.submitStrategy = (provider as any).submitStrategy === 'immediate' ? 'immediate' : 'wait_for_echo';
392
483
 
393
484
  // Scripts are required — loaded by ProviderLoader via compatibility array
394
485
  this.cliScripts = (provider as any).scripts || {};
@@ -514,6 +605,8 @@ export class ProviderCliAdapter implements CliAdapter {
514
605
  this.startupParseGate = true;
515
606
  this.startupBuffer = '';
516
607
  this.terminalScreen.reset(40, 120);
608
+ this.terminalHistory = '';
609
+ this.currentTurnScope = null;
517
610
  this.ready = false;
518
611
  this.setStatus('idle', 'pty_ready');
519
612
  this.onStatusChange?.();
@@ -530,6 +623,7 @@ export class ProviderCliAdapter implements CliAdapter {
530
623
  }
531
624
 
532
625
  this.terminalScreen.write(rawData);
626
+ this.terminalHistory = mergeTerminalHistory(this.terminalHistory, this.terminalScreen.getText());
533
627
  const cleanData = stripAnsi(rawData);
534
628
 
535
629
  if (this.isWaitingForResponse && cleanData) {
@@ -697,6 +791,7 @@ export class ProviderCliAdapter implements CliAdapter {
697
791
  private finishResponse(): void {
698
792
  if (this.submitPendingUntil > Date.now()) return;
699
793
  if (this.responseSettleIgnoreUntil > Date.now()) return;
794
+ this.commitCurrentTranscript();
700
795
  if (this.responseTimeout) { clearTimeout(this.responseTimeout); this.responseTimeout = null; }
701
796
  if (this.idleTimeout) { clearTimeout(this.idleTimeout); this.idleTimeout = null; }
702
797
  if (this.approvalExitTimeout) { clearTimeout(this.approvalExitTimeout); this.approvalExitTimeout = null; }
@@ -707,11 +802,65 @@ export class ProviderCliAdapter implements CliAdapter {
707
802
  this.responseSettleIgnoreUntil = 0;
708
803
  this.submitRetryUsed = false;
709
804
  this.submitRetryPromptSnippet = '';
805
+ this.currentTurnScope = null;
710
806
  this.activeModal = null;
711
807
  this.setStatus('idle', 'response_finished');
712
808
  this.onStatusChange?.();
713
809
  }
714
810
 
811
+ private commitCurrentTranscript(): void {
812
+ const baseMessages = [...this.committedMessages];
813
+ const parsed = this.parseCurrentTranscript(baseMessages, '', this.currentTurnScope);
814
+ if (parsed && Array.isArray(parsed.messages) && parsed.messages.length > 0) {
815
+ const parsedMessages = parsed.messages
816
+ .filter((m: any) => m && (m.role === 'user' || m.role === 'assistant'))
817
+ .map((m: any) => ({
818
+ role: m.role,
819
+ content: typeof m.content === 'string' ? m.content : String(m.content || ''),
820
+ timestamp: m.timestamp,
821
+ }));
822
+ const latestAssistant = [...parsedMessages]
823
+ .reverse()
824
+ .find((m: CliChatMessage) => m.role === 'assistant' && m.content.trim());
825
+ if (latestAssistant) {
826
+ LOG.info('CLI', `[${this.cliType}] commitCurrentTranscript parsed assistant len=${latestAssistant.content.length} scopePrompt=${JSON.stringify(this.currentTurnScope?.prompt || '').slice(0, 120)}`);
827
+ const nextMessages = [...baseMessages];
828
+ const last = nextMessages[nextMessages.length - 1];
829
+ if (last?.role === 'assistant') {
830
+ last.content = latestAssistant.content;
831
+ last.timestamp = latestAssistant.timestamp || last.timestamp;
832
+ } else if (last?.role === 'user') {
833
+ nextMessages.push({
834
+ role: 'assistant',
835
+ content: latestAssistant.content,
836
+ timestamp: latestAssistant.timestamp || Date.now(),
837
+ });
838
+ } else {
839
+ nextMessages.push({
840
+ role: 'assistant',
841
+ content: latestAssistant.content,
842
+ timestamp: latestAssistant.timestamp || Date.now(),
843
+ });
844
+ }
845
+ this.committedMessages = nextMessages;
846
+ this.syncMessageViews();
847
+ return;
848
+ }
849
+ }
850
+
851
+ const fallback = String(this.responseBuffer || '').trim();
852
+ LOG.info('CLI', `[${this.cliType}] commitCurrentTranscript fallback len=${fallback.length} scopePrompt=${JSON.stringify(this.currentTurnScope?.prompt || '').slice(0, 120)}`);
853
+ if (!fallback) return;
854
+ const last = baseMessages[baseMessages.length - 1];
855
+ if (last?.role === 'assistant') {
856
+ last.content = fallback;
857
+ } else {
858
+ baseMessages.push({ role: 'assistant', content: fallback, timestamp: Date.now() });
859
+ }
860
+ this.committedMessages = baseMessages;
861
+ this.syncMessageViews();
862
+ }
863
+
715
864
  // ─── Script Execution ──────────────────────────
716
865
 
717
866
  private runDetectStatus(text: string): string | null {
@@ -745,26 +894,12 @@ export class ProviderCliAdapter implements CliAdapter {
745
894
  // ─── Public API (CliAdapter) ───────────────────
746
895
 
747
896
  getStatus(): CliSessionStatus {
748
- // Use parseOutput script for full result when available
749
- const scriptResult = this.getScriptParsedStatus();
750
- if (scriptResult) {
751
- return {
752
- status: this.currentStatus,
753
- messages: (scriptResult.messages || []).map((m: any) => ({
754
- role: m.role,
755
- content: m.content,
756
- timestamp: m.timestamp,
757
- })),
758
- workingDir: this.workingDir,
759
- activeModal: this.activeModal,
760
- };
761
- }
762
-
763
897
  return {
764
898
  status: this.currentStatus,
765
- messages: [...this.messages],
899
+ messages: [...this.committedMessages],
766
900
  workingDir: this.workingDir,
767
901
  activeModal: this.activeModal,
902
+ terminalHistory: this.terminalHistory,
768
903
  };
769
904
  }
770
905
 
@@ -773,31 +908,33 @@ export class ProviderCliAdapter implements CliAdapter {
773
908
  * Called by command handler / dashboard for rich content rendering.
774
909
  */
775
910
  getScriptParsedStatus(): any {
911
+ const messages = [...this.committedMessages];
912
+ return {
913
+ id: 'cli_session',
914
+ status: this.currentStatus,
915
+ title: this.cliName,
916
+ terminalHistory: this.terminalHistory,
917
+ messages: messages.slice(-50).map((message, index) => ({
918
+ id: `msg_${index}`,
919
+ role: message.role,
920
+ content: message.content,
921
+ timestamp: message.timestamp,
922
+ index,
923
+ kind: 'standard',
924
+ })),
925
+ activeModal: this.activeModal,
926
+ };
927
+ }
928
+
929
+ private parseCurrentTranscript(baseMessages: CliChatMessage[], partialResponse: string, scope?: TurnParseScope | null): any {
776
930
  if (!this.cliScripts?.parseOutput) return null;
777
931
  try {
778
- const input: CliScriptInput = {
779
- buffer: this.accumulatedBuffer,
780
- rawBuffer: this.accumulatedRawBuffer,
781
- recentBuffer: this.recentOutputBuffer,
782
- screenText: this.terminalScreen.getText(),
783
- messages: [...(this.structuredMessages.length > 0 ? this.structuredMessages : this.messages)],
784
- partialResponse: this.responseBuffer,
785
- };
786
- const result = this.cliScripts.parseOutput(input);
787
- if (result && typeof result === 'object') {
788
- if (Array.isArray((result as any).messages)) {
789
- this.structuredMessages = (result as any).messages.map((m: any) => ({
790
- role: m.role,
791
- content: m.content,
792
- timestamp: m.timestamp,
793
- }));
794
- }
795
- return result;
796
- }
932
+ const input = this.buildParseInput(baseMessages, partialResponse, scope);
933
+ return this.cliScripts.parseOutput(input);
797
934
  } catch (e: any) {
798
935
  LOG.warn('CLI', `[${this.cliType}] parseOutput error: ${e.message}`);
936
+ return null;
799
937
  }
800
- return null;
801
938
  }
802
939
 
803
940
  /** Whether this adapter has CLI scripts loaded */
@@ -838,10 +975,18 @@ export class ProviderCliAdapter implements CliAdapter {
838
975
  if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
839
976
  if (this.isWaitingForResponse) return;
840
977
 
841
- this.messages.push({ role: 'user', content: text, timestamp: Date.now() });
842
- this.structuredMessages.push({ role: 'user', content: text, timestamp: Date.now() });
978
+ this.committedMessages.push({ role: 'user', content: text, timestamp: Date.now() });
979
+ this.syncMessageViews();
843
980
  this.isWaitingForResponse = true;
844
981
  this.responseBuffer = '';
982
+ this.currentTurnScope = {
983
+ prompt: text,
984
+ startedAt: Date.now(),
985
+ bufferStart: this.accumulatedBuffer.length,
986
+ rawBufferStart: this.accumulatedRawBuffer.length,
987
+ terminalHistoryStart: this.terminalHistory.length,
988
+ };
989
+ LOG.info('CLI', `[${this.cliType}] sendMessage turn scope buffer=${this.currentTurnScope.bufferStart} raw=${this.currentTurnScope.rawBufferStart} terminal=${this.currentTurnScope.terminalHistoryStart} prompt=${JSON.stringify(text).slice(0, 120)}`);
845
990
  this.submitRetryUsed = false;
846
991
  this.submitRetryPromptSnippet = extractPromptRetrySnippet(text);
847
992
  const normalizedPromptSnippet = normalizePromptText(this.submitRetryPromptSnippet);
@@ -861,10 +1006,12 @@ export class ProviderCliAdapter implements CliAdapter {
861
1006
  this.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
862
1007
  this.setStatus('generating', 'sendMessage');
863
1008
  this.onStatusChange?.();
864
- if (submitDelayMs > 0) {
865
- this.submitPendingUntil = Date.now() + submitDelayMs;
866
- }
867
- this.ptyProcess.write(text);
1009
+ const startResponseTimeout = () => {
1010
+ if (this.responseTimeout) clearTimeout(this.responseTimeout);
1011
+ this.responseTimeout = setTimeout(() => {
1012
+ if (this.isWaitingForResponse) this.finishResponse();
1013
+ }, this.timeouts.maxResponse);
1014
+ };
868
1015
 
869
1016
  const submit = () => {
870
1017
  if (!this.ptyProcess) return;
@@ -888,10 +1035,32 @@ export class ProviderCliAdapter implements CliAdapter {
888
1035
  this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(attempt + 1), retryDelayMs);
889
1036
  };
890
1037
  this.submitRetryTimer = setTimeout(() => retrySubmitIfStuck(1), retryDelayMs);
891
- this.responseTimeout = setTimeout(() => {
892
- if (this.isWaitingForResponse) this.finishResponse();
893
- }, this.timeouts.maxResponse);
1038
+ startResponseTimeout();
894
1039
  };
1040
+
1041
+ if (this.submitStrategy === 'immediate') {
1042
+ this.submitPendingUntil = 0;
1043
+ this.ptyProcess.write(text + this.sendKey);
1044
+ this.submitRetryTimer = setTimeout(() => {
1045
+ this.submitRetryTimer = null;
1046
+ if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
1047
+ if (this.currentStatus !== 'generating') return;
1048
+ if ((this.responseBuffer || '').trim()) return;
1049
+ const screenText = this.terminalScreen.getText();
1050
+ if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
1051
+ LOG.info('CLI', `[${this.cliType}] Retrying submit key for stuck prompt (attempt 1)`);
1052
+ this.responseSettleIgnoreUntil = Date.now() + this.timeouts.outputSettle + 400;
1053
+ this.ptyProcess.write(this.sendKey);
1054
+ this.submitRetryUsed = true;
1055
+ }, retryDelayMs);
1056
+ startResponseTimeout();
1057
+ return;
1058
+ }
1059
+
1060
+ if (submitDelayMs > 0) {
1061
+ this.submitPendingUntil = Date.now() + submitDelayMs;
1062
+ }
1063
+ this.ptyProcess.write(text);
895
1064
  const submitStartedAt = Date.now();
896
1065
  let lastNormalizedScreen = '';
897
1066
  let lastScreenChangeAt = submitStartedAt;
@@ -951,10 +1120,12 @@ export class ProviderCliAdapter implements CliAdapter {
951
1120
  }
952
1121
 
953
1122
  clearHistory(): void {
954
- this.messages = [];
955
- this.structuredMessages = [];
1123
+ this.committedMessages = [];
1124
+ this.syncMessageViews();
956
1125
  this.accumulatedBuffer = '';
957
1126
  this.accumulatedRawBuffer = '';
1127
+ this.terminalHistory = '';
1128
+ this.currentTurnScope = null;
958
1129
  this.submitRetryUsed = false;
959
1130
  this.submitRetryPromptSnippet = '';
960
1131
  this.terminalScreen.reset();
@@ -1008,9 +1179,12 @@ export class ProviderCliAdapter implements CliAdapter {
1008
1179
  spawnAt: this.spawnAt,
1009
1180
  workingDir: this.workingDir,
1010
1181
  messages: this.messages.slice(-20),
1182
+ committedMessages: this.committedMessages.slice(-20),
1011
1183
  structuredMessages: this.structuredMessages.slice(-20),
1012
- messageCount: this.messages.length,
1184
+ messageCount: this.committedMessages.length,
1013
1185
  screenText: this.terminalScreen.getText().slice(-4000),
1186
+ terminalHistory: this.terminalHistory.slice(-8000),
1187
+ currentTurnScope: this.currentTurnScope,
1014
1188
  startupBuffer: this.startupBuffer.slice(-4000),
1015
1189
  recentOutputBuffer: this.recentOutputBuffer.slice(-500),
1016
1190
  settledBuffer: this.settledBuffer.slice(-500),
@@ -1023,6 +1197,7 @@ export class ProviderCliAdapter implements CliAdapter {
1023
1197
  lastApprovalResolvedAt: this.lastApprovalResolvedAt,
1024
1198
  sendDelayMs: this.sendDelayMs,
1025
1199
  sendKey: this.sendKey,
1200
+ submitStrategy: this.submitStrategy,
1026
1201
  submitPendingUntil: this.submitPendingUntil,
1027
1202
  responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
1028
1203
  resizeSuppressUntil: this.resizeSuppressUntil,
@@ -35,7 +35,13 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
35
35
  _log(`${provider.category} adapter: ${(adapter as any).cliType}`);
36
36
  const status = (adapter as any).getStatus?.();
37
37
  if (status) {
38
- return { success: true, messages: status.messages || [], status: status.status, activeModal: status.activeModal };
38
+ return {
39
+ success: true,
40
+ messages: status.messages || [],
41
+ status: status.status,
42
+ activeModal: status.activeModal,
43
+ terminalHistory: status.terminalHistory || '',
44
+ };
39
45
  }
40
46
  }
41
47
  return { success: false, error: `${provider.category} adapter not found` };
@@ -31,6 +31,8 @@ export class ChatHistoryWriter {
31
31
  private lastSeenCounts = new Map<string, number>();
32
32
  /** Last seen message hash per agent (deduplication) */
33
33
  private lastSeenHashes = new Map<string, Set<string>>();
34
+ /** Last seen append-only terminal transcript per agent */
35
+ private lastSeenTerminal = new Map<string, string>();
34
36
  private rotated = false;
35
37
 
36
38
  /**
@@ -107,10 +109,60 @@ export class ChatHistoryWriter {
107
109
  }
108
110
  }
109
111
 
112
+ appendTerminalHistory(
113
+ agentType: string,
114
+ terminalHistory: string,
115
+ sessionTitle?: string,
116
+ instanceId?: string,
117
+ ): void {
118
+ const next = String(terminalHistory || '');
119
+ if (!next.trim()) return;
120
+
121
+ try {
122
+ const dedupKey = instanceId ? `${agentType}:${instanceId}:terminal` : `${agentType}:terminal`;
123
+ const prev = this.lastSeenTerminal.get(dedupKey) || '';
124
+ if (prev === next) return;
125
+
126
+ let delta = '';
127
+ if (!prev) {
128
+ delta = next;
129
+ } else if (next.startsWith(prev)) {
130
+ delta = next.slice(prev.length);
131
+ } else if (prev.includes(next)) {
132
+ this.lastSeenTerminal.set(dedupKey, next);
133
+ return;
134
+ } else {
135
+ delta = `\n\n[terminal snapshot reset ${new Date().toISOString()} | ${sessionTitle || agentType}]\n${next}`;
136
+ }
137
+
138
+ if (!delta) {
139
+ this.lastSeenTerminal.set(dedupKey, next);
140
+ return;
141
+ }
142
+
143
+ const dir = path.join(HISTORY_DIR, this.sanitize(agentType));
144
+ fs.mkdirSync(dir, { recursive: true });
145
+
146
+ const date = new Date().toISOString().slice(0, 10);
147
+ const filePrefix = instanceId ? `${this.sanitize(instanceId)}_` : '';
148
+ const filePath = path.join(dir, `${filePrefix}${date}.terminal.log`);
149
+ fs.appendFileSync(filePath, delta, 'utf-8');
150
+ this.lastSeenTerminal.set(dedupKey, next);
151
+
152
+ if (!this.rotated) {
153
+ this.rotated = true;
154
+ this.rotateOldFiles().catch(() => {});
155
+ }
156
+ } catch {
157
+ // Ignore terminal history save failures
158
+ }
159
+ }
160
+
110
161
  /** Called when agent session is explicitly changed */
111
162
  onSessionChange(agentType: string): void {
112
163
  this.lastSeenHashes.delete(agentType);
113
164
  this.lastSeenCounts.delete(agentType);
165
+ this.lastSeenTerminal.delete(`${agentType}:terminal`);
114
166
  }
115
167
 
116
168
  /** Delete history files older than 30 days */
@@ -125,7 +177,7 @@ export class ChatHistoryWriter {
125
177
  for (const dir of agentDirs) {
126
178
  const dirPath = path.join(HISTORY_DIR, dir.name);
127
179
  const files = fs.readdirSync(dirPath)
128
- .filter(f => f.endsWith('.jsonl'));
180
+ .filter(f => f.endsWith('.jsonl') || f.endsWith('.terminal.log'));
129
181
 
130
182
  for (const file of files) {
131
183
  const filePath = path.join(dirPath, file);
@@ -19,6 +19,7 @@ import * as fs from 'fs';
19
19
  import * as path from 'path';
20
20
  import * as os from 'os';
21
21
  import type { ProviderLoader } from '../providers/provider-loader.js';
22
+ import type { ProviderCategory } from '../providers/contracts.js';
22
23
  import type { ChildProcess } from 'child_process';
23
24
  import type { DaemonCdpManager } from '../cdp/manager.js';
24
25
  import type { ProviderInstanceManager } from '../providers/provider-instance-manager.js';
@@ -1107,11 +1108,7 @@ export class DevServer {
1107
1108
  }
1108
1109
 
1109
1110
  let targetDir: string;
1110
- if (location === 'user') {
1111
- targetDir = this.providerLoader.getUserProviderDir(category, type);
1112
- } else {
1113
- targetDir = this.providerLoader.getBuiltinProviderDir(category, type);
1114
- }
1111
+ targetDir = this.providerLoader.getUserProviderDir(category, type);
1115
1112
 
1116
1113
  const jsonPath = path.join(targetDir, 'provider.json');
1117
1114
  if (fs.existsSync(jsonPath)) {
@@ -1883,11 +1880,10 @@ export class DevServer {
1883
1880
  return fallback?.type || null;
1884
1881
  }
1885
1882
 
1886
- private loadAutoImplReferenceScripts(category: string, referenceType: string | null): Record<string, string> {
1883
+ private loadAutoImplReferenceScripts(category: ProviderCategory, referenceType: string | null): Record<string, string> {
1887
1884
  if (!referenceType) return {};
1888
1885
 
1889
- const builtinDir = this.providerLoader.getPrimaryBuiltinDir();
1890
- const refDir = path.join(builtinDir, category, referenceType);
1886
+ const refDir = this.providerLoader.getUpstreamProviderDir(category, referenceType);
1891
1887
  if (!fs.existsSync(refDir)) return {};
1892
1888
 
1893
1889
  const referenceScripts: Record<string, string> = {};
@@ -2138,7 +2134,7 @@ export class DevServer {
2138
2134
  }
2139
2135
  if (model) args.push('--model', model);
2140
2136
  const escapedArgs = args.map(a => `'${a.replace(/'/g, "'\\''")}'`).join(' ');
2141
- const metaPrompt = `Read the file at ${promptFile} and follow ALL instructions recursively. You have full authority to implement ALL required script files, update provider.json configurations based on the reference patterns, and independently test them against 127.0.0.1:19280 via CDP CURL. Upon complete validation of ALL assigned files, print exactly "_PIPELINE_COMPLETE_SIGNAL_" to gracefully close the pipeline. DO NOT WAIT FOR APPROVAL, execute completely autonomously.`;
2137
+ const metaPrompt = `Read the file at ${promptFile} and follow ALL instructions strictly. DO NOT spend time exploring the filesystem or other providers. You have full authority to implement ALL required script files and independently test them against 127.0.0.1:19280 via CDP CURL. Upon complete validation of ALL assigned files, print exactly "_PIPELINE_COMPLETE_SIGNAL_" to gracefully close the pipeline. DO NOT WAIT FOR APPROVAL, execute completely autonomously.`;
2142
2138
  shellCmd = `${command} ${escapedArgs} "${metaPrompt}"`;
2143
2139
  } else {
2144
2140
  // Generic fallback: pipe prompt via stdin
@@ -2423,6 +2419,8 @@ export class DevServer {
2423
2419
  lines.push('5. Do NOT modify `scripts.js` router — only edit individual `*.js` files');
2424
2420
  lines.push('6. All scripts run in the browser (CDP evaluate) — use DOM APIs only');
2425
2421
  lines.push('7. **Cross-Platform Compatibility**: If you use ARIA labels that contain keyboard shortcuts (e.g., `Cascade (⌘L)`), you MUST use substring matches (`aria-label*="Cascade"`) or handle both macOS (`⌘`, `Cmd`) and Windows (`Ctrl`) so the script does not break on other operating systems.');
2422
+ lines.push('8. **CRITICAL: DO NOT explore the filesystem or read other providers.** The reference implementation pattern is already provided below. Do not run `find`, `rg`, or `cat` on upstream providers. Doing so wastes context tokens and will crash the agent session. Focus entirely on modifying the target files.');
2423
+ lines.push('9. Do NOT delete any files. Implement the logic by replacing the empty stubs.');
2426
2424
  lines.push('');
2427
2425
 
2428
2426
  // ── Output contracts ──
@@ -81,15 +81,7 @@ export class CliProviderInstance implements ProviderInstance {
81
81
  }
82
82
 
83
83
  getState(): ProviderState {
84
- const rawStatus = this.adapter.getStatus();
85
- const parsedStatus = this.adapter.getScriptParsedStatus();
86
-
87
- // Prefer rich script parsed status if available
88
- const adapterStatus = parsedStatus ? {
89
- ...rawStatus,
90
- messages: parsedStatus.messages || rawStatus.messages,
91
- activeModal: parsedStatus.activeModal || rawStatus.activeModal,
92
- } : rawStatus;
84
+ const adapterStatus = this.adapter.getStatus();
93
85
 
94
86
  const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
95
87
 
@@ -102,20 +94,6 @@ export class CliProviderInstance implements ProviderInstance {
102
94
  });
103
95
 
104
96
  // generating during partial response add
105
- const partial = this.adapter.getPartialResponse();
106
- const shouldAppendRawPartial = !parsedStatus;
107
- if (shouldAppendRawPartial && adapterStatus.status === 'generating' && partial) {
108
- const cleaned = partial.trim();
109
- if (cleaned && cleaned !== '(generating...)') {
110
- recentMessages.push({
111
- role: 'assistant',
112
- content: (cleaned.length > 8000 ? cleaned.slice(0, 8000) + '...' : cleaned) + '...',
113
- timestamp: Date.now(),
114
- meta: { streaming: true },
115
- });
116
- }
117
- }
118
-
119
97
  // Save history
120
98
  if (recentMessages.length > 0) {
121
99
  const dirName = this.workingDir.split('/').filter(Boolean).pop() || 'session';
@@ -126,6 +104,14 @@ export class CliProviderInstance implements ProviderInstance {
126
104
  this.instanceId,
127
105
  );
128
106
  }
107
+ if (adapterStatus.terminalHistory?.trim()) {
108
+ this.historyWriter.appendTerminalHistory(
109
+ this.type,
110
+ adapterStatus.terminalHistory,
111
+ `${this.provider.name} · ${dirName}`,
112
+ this.instanceId,
113
+ );
114
+ }
129
115
 
130
116
  return {
131
117
  type: this.type,
@@ -139,6 +125,7 @@ export class CliProviderInstance implements ProviderInstance {
139
125
  status: adapterStatus.status,
140
126
  messages: recentMessages,
141
127
  activeModal: adapterStatus.activeModal,
128
+ terminalHistory: adapterStatus.terminalHistory,
142
129
  inputContent: '',
143
130
  },
144
131
  workspace: this.workingDir,
@@ -22,6 +22,7 @@ export interface ActiveChatData {
22
22
  status: string;
23
23
  messages: ChatMessage[];
24
24
  activeModal: { message: string; buttons: string[] } | null;
25
+ terminalHistory?: string;
25
26
  inputContent?: string;
26
27
  }
27
28
 
@@ -200,7 +200,10 @@ export async function detectAllVersions(
200
200
  detectedAt: new Date().toISOString(),
201
201
  };
202
202
 
203
- const versionCommand = (provider as any).versionCommand;
203
+ const verCmdConfig = (provider as any).versionCommand;
204
+ const versionCommand = typeof verCmdConfig === 'object' && verCmdConfig !== null
205
+ ? verCmdConfig[currentOs]
206
+ : verCmdConfig;
204
207
 
205
208
  if (provider.category === 'ide') {
206
209
  // IDE: check app path + CLI