@adhdev/daemon-core 0.8.21 → 0.8.23

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 (36) hide show
  1. package/dist/agent-stream/types.d.ts +3 -0
  2. package/dist/cli-adapter-types.d.ts +3 -0
  3. package/dist/cli-adapters/provider-cli-adapter.d.ts +71 -11
  4. package/dist/commands/stream-commands.d.ts +1 -0
  5. package/dist/config/config.d.ts +6 -0
  6. package/dist/index.js +1163 -314
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +1163 -314
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/providers/cli-provider-instance.d.ts +6 -0
  11. package/dist/providers/contracts.d.ts +59 -1
  12. package/dist/providers/control-effects.d.ts +4 -0
  13. package/dist/providers/extension-provider-instance.d.ts +9 -0
  14. package/dist/providers/ide-provider-instance.d.ts +8 -0
  15. package/dist/shared-types.d.ts +2 -1
  16. package/node_modules/@adhdev/session-host-core/package.json +1 -1
  17. package/package.json +3 -2
  18. package/src/agent-stream/forward.ts +2 -0
  19. package/src/agent-stream/provider-adapter.ts +5 -15
  20. package/src/agent-stream/types.ts +4 -0
  21. package/src/cli-adapter-types.ts +3 -0
  22. package/src/cli-adapters/provider-cli-adapter.ts +399 -49
  23. package/src/commands/chat-commands.ts +33 -12
  24. package/src/commands/handler.ts +1 -0
  25. package/src/commands/stream-commands.ts +99 -8
  26. package/src/config/config.d.ts +1 -0
  27. package/src/config/config.ts +9 -0
  28. package/src/launch.ts +57 -11
  29. package/src/providers/cli-provider-instance.ts +148 -2
  30. package/src/providers/contracts.ts +65 -2
  31. package/src/providers/control-effects.ts +114 -0
  32. package/src/providers/extension-provider-instance.ts +163 -3
  33. package/src/providers/ide-provider-instance.ts +181 -2
  34. package/src/shared-types.d.ts +1 -0
  35. package/src/shared-types.ts +2 -1
  36. package/src/status/snapshot.ts +1 -0
package/dist/index.js CHANGED
@@ -80,7 +80,8 @@ function normalizeConfig(raw) {
80
80
  providerSettings: isPlainObject(parsed.providerSettings) ? parsed.providerSettings : {},
81
81
  ideSettings: isPlainObject(parsed.ideSettings) ? parsed.ideSettings : {},
82
82
  disableUpstream: asBoolean(parsed.disableUpstream, DEFAULT_CONFIG.disableUpstream ?? false),
83
- providerDir: asOptionalString(parsed.providerDir)
83
+ providerDir: asOptionalString(parsed.providerDir),
84
+ terminalSizingMode: parsed.terminalSizingMode === "fit" ? "fit" : "measured"
84
85
  };
85
86
  }
86
87
  function generateMachineId() {
@@ -228,7 +229,8 @@ var init_config = __esm({
228
229
  registeredMachineId: void 0,
229
230
  providerSettings: {},
230
231
  ideSettings: {},
231
- disableUpstream: false
232
+ disableUpstream: false,
233
+ terminalSizingMode: "measured"
232
234
  };
233
235
  MACHINE_ID_PREFIX = "mach_";
234
236
  }
@@ -810,6 +812,53 @@ function stripTerminalNoise(str) {
810
812
  function sanitizeTerminalText(str) {
811
813
  return stripTerminalNoise(stripAnsi(str));
812
814
  }
815
+ function splitCliScreenLines(text) {
816
+ return String(text || "").replace(/\u0007/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n").map((line) => line.replace(/\s+$/, ""));
817
+ }
818
+ function isPromptLikeCliLine(line) {
819
+ const trimmed = String(line || "").trim();
820
+ if (!trimmed) return false;
821
+ return /^[❯›>]\s*(?:$|\S.*)$/.test(trimmed);
822
+ }
823
+ function buildCliScreenSnapshot(text) {
824
+ const normalizedText = String(text || "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
825
+ const rawLines = splitCliScreenLines(normalizedText);
826
+ const lines = rawLines.map((line, index, arr) => {
827
+ const trimmed = String(line || "").trim();
828
+ return {
829
+ index,
830
+ fromTop: index,
831
+ fromBottom: arr.length - index - 1,
832
+ text: line,
833
+ trimmed,
834
+ isEmpty: trimmed.length === 0
835
+ };
836
+ });
837
+ const nonEmptyLines = lines.filter((line) => !line.isEmpty);
838
+ const firstNonEmptyLine = nonEmptyLines[0] ?? null;
839
+ const lastNonEmptyLine = nonEmptyLines[nonEmptyLines.length - 1] ?? null;
840
+ let promptLineIndex = -1;
841
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
842
+ if (isPromptLikeCliLine(lines[i].text)) {
843
+ promptLineIndex = i;
844
+ break;
845
+ }
846
+ }
847
+ return {
848
+ text: normalizedText,
849
+ lineCount: lines.length,
850
+ lines,
851
+ nonEmptyLines,
852
+ firstNonEmptyLineIndex: firstNonEmptyLine?.index ?? -1,
853
+ lastNonEmptyLineIndex: lastNonEmptyLine?.index ?? -1,
854
+ firstNonEmptyLine,
855
+ lastNonEmptyLine,
856
+ promptLineIndex,
857
+ promptLine: promptLineIndex >= 0 ? lines[promptLineIndex] : null,
858
+ linesAbovePrompt: promptLineIndex >= 0 ? lines.slice(0, promptLineIndex) : [...lines],
859
+ linesBelowPrompt: promptLineIndex >= 0 ? lines.slice(promptLineIndex + 1) : []
860
+ };
861
+ }
813
862
  function computeTerminalQueryTail(buffer) {
814
863
  const prefixes = ["\x1B[6n", "\x1B[?6n"];
815
864
  const maxLength = prefixes.reduce((n, value) => Math.max(n, value.length), 0) - 1;
@@ -1051,7 +1100,9 @@ var init_provider_cli_adapter = __esm({
1051
1100
  ready = false;
1052
1101
  startupBuffer = "";
1053
1102
  startupParseGate = false;
1103
+ startupSettleTimer = null;
1054
1104
  spawnAt = 0;
1105
+ startupFirstOutputAt = 0;
1055
1106
  // PTY I/O
1056
1107
  onPtyDataCallback = null;
1057
1108
  pendingOutputParseBuffer = "";
@@ -1092,6 +1143,7 @@ var init_provider_cli_adapter = __esm({
1092
1143
  statusHistory = [];
1093
1144
  // ─── CLI Scripts (script-based parsing) ───
1094
1145
  cliScripts;
1146
+ runtimeSettings = {};
1095
1147
  /** Full accumulated ANSI-stripped PTY output */
1096
1148
  accumulatedBuffer = "";
1097
1149
  /** Full accumulated raw PTY output (with ANSI) */
@@ -1106,14 +1158,15 @@ var init_provider_cli_adapter = __esm({
1106
1158
  traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
1107
1159
  static MAX_TRACE_ENTRIES = 250;
1108
1160
  providerResolutionMeta;
1109
- static IDLE_FINISH_CONFIRM_MS = 900;
1161
+ static IDLE_FINISH_CONFIRM_MS = 2e3;
1162
+ static STATUS_ACTIVITY_HOLD_MS = 2e3;
1110
1163
  static FINISH_RETRY_DELAY_MS = 300;
1111
1164
  static MAX_FINISH_RETRIES = 2;
1112
1165
  syncMessageViews() {
1113
1166
  this.messages = [...this.committedMessages];
1114
1167
  this.structuredMessages = [...this.committedMessages];
1115
1168
  }
1116
- normalizeParsedMessages(parsedMessages) {
1169
+ hydrateParsedMessages(parsedMessages, scope) {
1117
1170
  const referenceMessages = [...this.committedMessages];
1118
1171
  const usedReferenceIndexes = /* @__PURE__ */ new Set();
1119
1172
  const now = Date.now();
@@ -1146,13 +1199,30 @@ var init_provider_cli_adapter = __esm({
1146
1199
  const content = typeof message.content === "string" ? message.content : String(message.content || "");
1147
1200
  const parsedTimestamp = typeof message.timestamp === "number" && Number.isFinite(message.timestamp) ? message.timestamp : void 0;
1148
1201
  const referenceTimestamp = parsedTimestamp ?? findReferenceTimestamp(role, content, index);
1202
+ const fallbackTimestamp = role === "user" ? scope?.startedAt || now : this.lastOutputAt || scope?.startedAt || now;
1203
+ const timestamp = referenceTimestamp ?? fallbackTimestamp;
1149
1204
  return {
1205
+ ...message,
1150
1206
  role,
1151
1207
  content,
1152
- timestamp: referenceTimestamp ?? now
1208
+ timestamp,
1209
+ receivedAt: typeof message.receivedAt === "number" && Number.isFinite(message.receivedAt) ? message.receivedAt : timestamp
1153
1210
  };
1154
1211
  });
1155
1212
  }
1213
+ normalizeParsedMessages(parsedMessages, scope) {
1214
+ return this.hydrateParsedMessages(parsedMessages, scope).map((message) => ({
1215
+ role: message.role,
1216
+ content: message.content,
1217
+ timestamp: message.timestamp,
1218
+ receivedAt: message.receivedAt,
1219
+ kind: message.kind,
1220
+ id: message.id,
1221
+ index: message.index,
1222
+ meta: message.meta,
1223
+ senderName: message.senderName
1224
+ }));
1225
+ }
1156
1226
  sliceFromOffset(text, start) {
1157
1227
  if (!text) return "";
1158
1228
  if (!Number.isFinite(start) || start <= 0) return text;
@@ -1162,14 +1232,20 @@ var init_provider_cli_adapter = __esm({
1162
1232
  buildParseInput(baseMessages, partialResponse, scope) {
1163
1233
  const buffer = scope ? this.sliceFromOffset(this.accumulatedBuffer, scope.bufferStart) || this.accumulatedBuffer : this.accumulatedBuffer;
1164
1234
  const rawBuffer = scope ? this.sliceFromOffset(this.accumulatedRawBuffer, scope.rawBufferStart) || this.accumulatedRawBuffer : this.accumulatedRawBuffer;
1235
+ const screenText = this.terminalScreen.getText();
1236
+ const recentBuffer = buffer.slice(-1e3) || this.recentOutputBuffer;
1165
1237
  return {
1166
1238
  buffer,
1167
1239
  rawBuffer,
1168
- recentBuffer: buffer.slice(-1e3) || this.recentOutputBuffer,
1169
- screenText: this.terminalScreen.getText(),
1240
+ recentBuffer,
1241
+ screenText,
1242
+ screen: buildCliScreenSnapshot(screenText),
1243
+ bufferScreen: buildCliScreenSnapshot(buffer),
1244
+ recentScreen: buildCliScreenSnapshot(recentBuffer),
1170
1245
  messages: [...baseMessages],
1171
1246
  partialResponse,
1172
- promptText: scope?.prompt || ""
1247
+ promptText: scope?.prompt || "",
1248
+ settings: { ...this.runtimeSettings }
1173
1249
  };
1174
1250
  }
1175
1251
  setStatus(status, trigger) {
@@ -1275,6 +1351,9 @@ var init_provider_cli_adapter = __esm({
1275
1351
  const scriptNames = Object.keys(scripts).filter((k) => typeof scripts[k] === "function");
1276
1352
  LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
1277
1353
  }
1354
+ updateRuntimeSettings(settings) {
1355
+ this.runtimeSettings = { ...settings };
1356
+ }
1278
1357
  // ─── Lifecycle ─────────────────────────────────
1279
1358
  setServerConn(serverConn) {
1280
1359
  this.serverConn = serverConn;
@@ -1311,7 +1390,7 @@ var init_provider_cli_adapter = __esm({
1311
1390
  let shellArgs;
1312
1391
  const useShellUnix = !isWin && (!!spawnConfig.shell || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath) || !looksLikeMachOOrElf(binaryPath));
1313
1392
  const isCmdShim = isWin && /\.(cmd|bat)$/i.test(binaryPath);
1314
- const useShellWin = isCmdShim || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath);
1393
+ const useShellWin = !!spawnConfig.shell || isCmdShim || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath);
1315
1394
  const useShell = isWin ? useShellWin : useShellUnix;
1316
1395
  if (useShell) {
1317
1396
  if (!spawnConfig.shell && !isWin) {
@@ -1409,6 +1488,11 @@ var init_provider_cli_adapter = __esm({
1409
1488
  this.spawnAt = Date.now();
1410
1489
  this.startupParseGate = true;
1411
1490
  this.startupBuffer = "";
1491
+ this.startupFirstOutputAt = 0;
1492
+ if (this.startupSettleTimer) {
1493
+ clearTimeout(this.startupSettleTimer);
1494
+ this.startupSettleTimer = null;
1495
+ }
1412
1496
  this.terminalScreen.reset(24, 80);
1413
1497
  this.pendingTerminalQueryTail = "";
1414
1498
  this.currentTurnScope = null;
@@ -1422,7 +1506,8 @@ var init_provider_cli_adapter = __esm({
1422
1506
  this.recordTrace("ready", {
1423
1507
  runtimeMeta: this.getRuntimeMetadata()
1424
1508
  });
1425
- this.setStatus("idle", "pty_ready");
1509
+ this.setStatus("starting", "pty_ready");
1510
+ this.scheduleStartupSettleCheck();
1426
1511
  this.onStatusChange?.();
1427
1512
  }
1428
1513
  // ─── Output Handling ────────────────────────────
@@ -1437,6 +1522,9 @@ var init_provider_cli_adapter = __esm({
1437
1522
  this.lastScreenSnapshot = normalizedScreenSnapshot;
1438
1523
  this.lastScreenChangeAt = now;
1439
1524
  }
1525
+ if (this.startupParseGate && !this.startupFirstOutputAt && (cleanData.trim() || normalizedScreenSnapshot.trim())) {
1526
+ this.startupFirstOutputAt = now;
1527
+ }
1440
1528
  if (this.idleFinishCandidate && (rawData.length > 0 || cleanData.length > 0)) {
1441
1529
  this.clearIdleFinishCandidate("new_output");
1442
1530
  }
@@ -1447,6 +1535,9 @@ var init_provider_cli_adapter = __esm({
1447
1535
  cleanPreview: this.summarizeTraceText(cleanData, 300),
1448
1536
  screenText: this.summarizeTraceText(this.terminalScreen.getText(), 1200)
1449
1537
  });
1538
+ if (this.startupParseGate) {
1539
+ this.scheduleStartupSettleCheck();
1540
+ }
1450
1541
  if (this.isWaitingForResponse && cleanData) {
1451
1542
  this.responseBuffer = (this.responseBuffer + cleanData).slice(-8e3);
1452
1543
  }
@@ -1460,27 +1551,51 @@ var init_provider_cli_adapter = __esm({
1460
1551
  this.recentOutputBuffer = (this.recentOutputBuffer + cleanData).slice(-1e3);
1461
1552
  this.accumulatedBuffer = (this.accumulatedBuffer + cleanData).slice(-_ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
1462
1553
  this.accumulatedRawBuffer = (this.accumulatedRawBuffer + rawData).slice(-_ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
1463
- if (this.startupParseGate) {
1464
- this.startupBuffer += cleanData;
1465
- const elapsed = Date.now() - this.spawnAt;
1466
- const screenText = this.terminalScreen.getText() || "";
1467
- const startupModal = this.getStartupConfirmationModal(screenText);
1468
- const scriptStatus = startupModal ? "waiting_approval" : this.runDetectStatus(this.startupBuffer);
1469
- const hasInteractivePrompt = this.looksLikeVisibleIdlePrompt(screenText);
1470
- const startupStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
1471
- const isReady = (scriptStatus === "idle" || scriptStatus === "waiting_approval") && hasInteractivePrompt && startupStableMs >= 700 || !!startupModal && startupStableMs >= 700 || elapsed > 8e3 || this.startupBuffer.length > 12e3;
1472
- if (isReady) {
1473
- this.startupParseGate = false;
1474
- this.ready = true;
1475
- LOG.info(
1476
- "CLI",
1477
- `[${this.cliType}] Startup ready (${elapsed}ms, scriptStatus=${scriptStatus}, prompt=${hasInteractivePrompt}, stableMs=${startupStableMs}) providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"}`
1478
- );
1479
- this.onStatusChange?.();
1480
- }
1481
- }
1554
+ this.resolveStartupState("output");
1482
1555
  this.scheduleSettle();
1483
1556
  }
1557
+ resolveStartupState(trigger) {
1558
+ if (!this.startupParseGate) return;
1559
+ const now = Date.now();
1560
+ const screenText = this.terminalScreen.getText() || "";
1561
+ const normalizedScreen = normalizeScreenSnapshot(screenText);
1562
+ const hasStartupOutput = !!this.startupFirstOutputAt || !!normalizedScreen.trim();
1563
+ if (!hasStartupOutput) return;
1564
+ const stableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
1565
+ if (stableMs < 2e3) return;
1566
+ const startupModal = this.getStartupConfirmationModal(screenText);
1567
+ this.startupParseGate = false;
1568
+ if (this.startupSettleTimer) {
1569
+ clearTimeout(this.startupSettleTimer);
1570
+ this.startupSettleTimer = null;
1571
+ }
1572
+ this.ready = true;
1573
+ if (startupModal) {
1574
+ this.activeModal = startupModal;
1575
+ this.setStatus("waiting_approval", `startup_ready:${trigger}`);
1576
+ } else {
1577
+ this.setStatus("idle", `startup_ready:${trigger}`);
1578
+ }
1579
+ LOG.info(
1580
+ "CLI",
1581
+ `[${this.cliType}] Startup settled (${trigger}, stableMs=${stableMs}, modal=${!!startupModal}) providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"}`
1582
+ );
1583
+ this.onStatusChange?.();
1584
+ }
1585
+ scheduleStartupSettleCheck() {
1586
+ if (!this.startupParseGate) return;
1587
+ if (this.startupSettleTimer) clearTimeout(this.startupSettleTimer);
1588
+ const now = Date.now();
1589
+ const stableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
1590
+ const delayMs = Math.max(250, 2050 - stableMs);
1591
+ this.startupSettleTimer = setTimeout(() => {
1592
+ this.startupSettleTimer = null;
1593
+ this.resolveStartupState("startup_timer");
1594
+ if (this.startupParseGate && Date.now() - this.spawnAt < 1e4) {
1595
+ this.scheduleStartupSettleCheck();
1596
+ }
1597
+ }, delayMs);
1598
+ }
1484
1599
  scheduleSettle() {
1485
1600
  if (this.settleTimer) clearTimeout(this.settleTimer);
1486
1601
  const settleEpoch = this.responseEpoch;
@@ -1522,6 +1637,43 @@ var init_provider_cli_adapter = __esm({
1522
1637
  if (!text.trim()) return false;
1523
1638
  return /(^|\n)\s*[❯›>]\s*(?:\n|$)/m.test(text) || /⏎\s+send/i.test(text) || /\?\s*for\s*shortcuts/i.test(text) || /Type your message(?:\s+or\s+@path\/to\/file)?/i.test(text) || /workspace\s*\(\/directory\)/i.test(text) || /for\s*shortcuts/i.test(text);
1524
1639
  }
1640
+ findLastMatchingLineIndex(lines, predicate) {
1641
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
1642
+ if (predicate(lines[index])) return index;
1643
+ }
1644
+ return -1;
1645
+ }
1646
+ looksLikeClaudeGeneratingLine(line) {
1647
+ const trimmed = String(line || "").trim();
1648
+ if (!trimmed) return false;
1649
+ if (/esc to (cancel|interrupt|stop)/i.test(trimmed)) return true;
1650
+ if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+\s+\S+.*\b(?:thinking|thought for \d+s?)\b/i.test(trimmed)) return true;
1651
+ if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+\s+[A-Z][A-Za-z-]{3,}ing\b.*(?:…|\.{3})/u.test(trimmed)) return true;
1652
+ if (/^[⏺•]\s+(?:Reading|Writing|Editing|Searching|Inspecting|Planning|Analyzing|Synthesizing|Drafting|Running|Listing|Scanning|Matching)\b.*(?:…|\.{3})/i.test(trimmed)) {
1653
+ return /ctrl\+o to expand/i.test(trimmed) || /\b\d+\s+(?:file|files|pattern|patterns|director(?:y|ies)|match|matches|result|results)\b/i.test(trimmed);
1654
+ }
1655
+ return false;
1656
+ }
1657
+ detectClaudeGeneratingOverride(screenText, tail) {
1658
+ if (this.cliType !== "claude-cli") return false;
1659
+ const source = sanitizeTerminalText(screenText || tail || "");
1660
+ if (!source.trim()) return false;
1661
+ const allLines = source.split(/\r\n|\n|\r/g).map((line) => line.trim()).filter(Boolean);
1662
+ if (allLines.length === 0) return false;
1663
+ const recentLines = allLines.slice(-12);
1664
+ const promptIndex = this.findLastMatchingLineIndex(recentLines, (line) => /^[❯›>]\s*$/.test(line));
1665
+ const activeRegion = promptIndex >= 0 ? recentLines.slice(Math.max(0, promptIndex - 2), promptIndex) : recentLines;
1666
+ if (activeRegion.length === 0) return false;
1667
+ return activeRegion.some((line) => this.looksLikeClaudeGeneratingLine(line));
1668
+ }
1669
+ refineDetectedStatus(status, tail, screenText) {
1670
+ if (this.startupParseGate) {
1671
+ return this.getStartupConfirmationModal(screenText || "") ? "waiting_approval" : "starting";
1672
+ }
1673
+ if (status === "waiting_approval") return status;
1674
+ if (this.detectClaudeGeneratingOverride(screenText || "", tail)) return "generating";
1675
+ return status;
1676
+ }
1525
1677
  looksLikeVisibleAssistantCandidate(screenText) {
1526
1678
  const lines = sanitizeTerminalText(String(screenText || "")).split(/\r\n|\n|\r/g);
1527
1679
  for (const line of lines) {
@@ -1556,6 +1708,11 @@ var init_provider_cli_adapter = __esm({
1556
1708
  const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
1557
1709
  return quietForMs < 1200 || screenStableMs < 1200 || !commitResult.hasAssistant;
1558
1710
  }
1711
+ hasRecentInteractiveActivity(now) {
1712
+ const quietForMs = this.lastNonEmptyOutputAt ? now - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
1713
+ const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : Number.MAX_SAFE_INTEGER;
1714
+ return quietForMs < _ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS || screenStableMs < _ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS;
1715
+ }
1559
1716
  getStartupConfirmationModal(screenText) {
1560
1717
  const text = sanitizeTerminalText(String(screenText || ""));
1561
1718
  if (!text.trim()) return null;
@@ -1583,13 +1740,14 @@ var init_provider_cli_adapter = __esm({
1583
1740
  const startedAt = Date.now();
1584
1741
  let loggedWait = false;
1585
1742
  while (Date.now() - startedAt < maxWaitMs) {
1743
+ this.resolveStartupState("interactive_wait");
1586
1744
  const screenText = this.terminalScreen.getText() || "";
1587
1745
  const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
1588
1746
  const stableMs = this.lastScreenChangeAt ? Date.now() - this.lastScreenChangeAt : 0;
1589
1747
  const recentlyOutput = this.lastNonEmptyOutputAt ? Date.now() - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
1590
1748
  const status = this.runDetectStatus(this.recentOutputBuffer) || this.currentStatus;
1591
1749
  const startupLikelyActive = /Welcome back|Tips for getting|Recent activity|Claude Code v\d/i.test(screenText);
1592
- const interactiveReady = hasPrompt && stableMs >= 700 && recentlyOutput >= 350 && status !== "starting" && status !== "generating";
1750
+ const interactiveReady = hasPrompt && stableMs >= 700 && recentlyOutput >= 350 && status !== "generating";
1593
1751
  if (interactiveReady) {
1594
1752
  if (loggedWait) {
1595
1753
  LOG.info(
@@ -1628,6 +1786,10 @@ var init_provider_cli_adapter = __esm({
1628
1786
  }
1629
1787
  const tail = this.settledBuffer;
1630
1788
  const screenText = this.terminalScreen.getText() || "";
1789
+ this.resolveStartupState("settled");
1790
+ if (this.startupParseGate) {
1791
+ return;
1792
+ }
1631
1793
  const startupModal = this.getStartupConfirmationModal(screenText);
1632
1794
  const modal = this.runParseApproval(tail) || startupModal;
1633
1795
  const rawScriptStatus = this.runDetectStatus(tail);
@@ -1690,6 +1852,28 @@ var init_provider_cli_adapter = __esm({
1690
1852
  } else {
1691
1853
  clearPendingScriptStatus();
1692
1854
  }
1855
+ const recentInteractiveActivity = this.hasRecentInteractiveActivity(now);
1856
+ const shouldHoldGenerating = scriptStatus === "idle" && this.isWaitingForResponse && !modal && recentInteractiveActivity;
1857
+ if (shouldHoldGenerating) {
1858
+ this.clearIdleFinishCandidate("hold_generating_recent_activity");
1859
+ this.setStatus("generating", "recent_activity_hold");
1860
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
1861
+ this.idleTimeout = setTimeout(() => {
1862
+ if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
1863
+ this.finishResponse();
1864
+ }
1865
+ }, this.timeouts.generatingIdle);
1866
+ this.recordTrace("hold_generating_recent_activity", {
1867
+ scriptStatus,
1868
+ recentInteractiveActivity,
1869
+ lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
1870
+ lastScreenChangeAt: this.lastScreenChangeAt,
1871
+ holdMs: _ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS,
1872
+ ...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
1873
+ });
1874
+ this.onStatusChange?.();
1875
+ return;
1876
+ }
1693
1877
  if (scriptStatus === "waiting_approval") {
1694
1878
  this.clearIdleFinishCandidate("waiting_approval");
1695
1879
  const inCooldown = this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
@@ -1767,8 +1951,8 @@ var init_provider_cli_adapter = __esm({
1767
1951
  const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
1768
1952
  const hasAssistantTurn = !!lastParsedAssistant;
1769
1953
  const assistantLength = lastParsedAssistant?.content?.length || 0;
1770
- const idleQuietThresholdMs = Math.max(220, this.timeouts.outputSettle);
1771
- const idleStableThresholdMs = Math.max(120, Math.min(220, this.timeouts.outputSettle));
1954
+ const idleQuietThresholdMs = Math.max(2e3, this.timeouts.outputSettle);
1955
+ const idleStableThresholdMs = 2e3;
1772
1956
  const idleReady = visibleIdlePrompt && !modal && hasAssistantTurn && quietForMs >= idleQuietThresholdMs && screenStableMs >= idleStableThresholdMs;
1773
1957
  const candidate = this.idleFinishCandidate;
1774
1958
  const candidateQuiet = !!candidate && candidate.responseEpoch === this.responseEpoch && candidate.lastOutputAt === this.lastOutputAt && candidate.lastScreenChangeAt === this.lastScreenChangeAt && assistantLength >= candidate.assistantLength && now - candidate.armedAt >= _ProviderCliAdapter.IDLE_FINISH_CONFIRM_MS;
@@ -1882,7 +2066,7 @@ var init_provider_cli_adapter = __esm({
1882
2066
  this.currentTurnScope
1883
2067
  );
1884
2068
  if (parsed && Array.isArray(parsed.messages)) {
1885
- this.committedMessages = this.normalizeParsedMessages(parsed.messages);
2069
+ this.committedMessages = this.normalizeParsedMessages(parsed.messages, this.currentTurnScope);
1886
2070
  const promptForTrim = this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages);
1887
2071
  if (promptForTrim) {
1888
2072
  const lastAssistantForTrim = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
@@ -1919,11 +2103,15 @@ var init_provider_cli_adapter = __esm({
1919
2103
  runDetectStatus(text) {
1920
2104
  if (!this.cliScripts?.detectStatus) return null;
1921
2105
  try {
1922
- return this.cliScripts.detectStatus({
2106
+ const screenText = this.terminalScreen.getText();
2107
+ const status = this.cliScripts.detectStatus({
1923
2108
  tail: text.slice(-500),
1924
- screenText: this.terminalScreen.getText(),
1925
- rawBuffer: this.accumulatedRawBuffer
2109
+ screenText,
2110
+ rawBuffer: this.accumulatedRawBuffer,
2111
+ screen: buildCliScreenSnapshot(screenText),
2112
+ tailScreen: buildCliScreenSnapshot(text.slice(-500))
1926
2113
  });
2114
+ return this.refineDetectedStatus(status, text, screenText || "");
1927
2115
  } catch (e) {
1928
2116
  LOG.warn("CLI", `[${this.cliType}] detectStatus error: ${e.message}`);
1929
2117
  return null;
@@ -1932,11 +2120,16 @@ var init_provider_cli_adapter = __esm({
1932
2120
  runParseApproval(tail) {
1933
2121
  if (!this.cliScripts?.parseApproval) return null;
1934
2122
  try {
2123
+ const screenText = this.terminalScreen.getText();
2124
+ const buffer = screenText || this.accumulatedBuffer;
1935
2125
  return this.cliScripts.parseApproval({
1936
- buffer: this.terminalScreen.getText() || this.accumulatedBuffer,
1937
- screenText: this.terminalScreen.getText(),
2126
+ buffer,
2127
+ screenText,
1938
2128
  rawBuffer: this.accumulatedRawBuffer,
1939
- tail
2129
+ tail,
2130
+ screen: buildCliScreenSnapshot(screenText),
2131
+ bufferScreen: buildCliScreenSnapshot(buffer),
2132
+ tailScreen: buildCliScreenSnapshot(tail)
1940
2133
  });
1941
2134
  } catch (e) {
1942
2135
  LOG.warn("CLI", `[${this.cliType}] parseApproval error: ${e.message}`);
@@ -1952,6 +2145,21 @@ var init_provider_cli_adapter = __esm({
1952
2145
  activeModal: this.activeModal
1953
2146
  };
1954
2147
  }
2148
+ seedCommittedMessages(messages) {
2149
+ const normalized = (Array.isArray(messages) ? messages : []).filter((message) => message && (message.role === "user" || message.role === "assistant")).map((message) => ({
2150
+ role: message.role,
2151
+ content: typeof message.content === "string" ? message.content : String(message.content || ""),
2152
+ timestamp: typeof message.timestamp === "number" && Number.isFinite(message.timestamp) ? message.timestamp : void 0,
2153
+ receivedAt: typeof message.receivedAt === "number" && Number.isFinite(message.receivedAt) ? message.receivedAt : void 0,
2154
+ kind: typeof message.kind === "string" ? message.kind : void 0,
2155
+ id: typeof message.id === "string" ? message.id : void 0,
2156
+ index: typeof message.index === "number" ? message.index : void 0,
2157
+ meta: message.meta && typeof message.meta === "object" ? { ...message.meta } : void 0,
2158
+ senderName: typeof message.senderName === "string" ? message.senderName : void 0
2159
+ }));
2160
+ this.committedMessages = normalized;
2161
+ this.syncMessageViews();
2162
+ }
1955
2163
  /**
1956
2164
  * Script-based full parse — returns ReadChatResult.
1957
2165
  * Called by command handler / dashboard for rich content rendering.
@@ -1962,12 +2170,20 @@ var init_provider_cli_adapter = __esm({
1962
2170
  this.responseBuffer,
1963
2171
  this.currentTurnScope
1964
2172
  );
2173
+ const shouldPreferCommittedMessages = !this.currentTurnScope && this.currentStatus === "idle" && !this.activeModal;
1965
2174
  if (parsed && Array.isArray(parsed.messages)) {
2175
+ const hydratedMessages = shouldPreferCommittedMessages ? this.committedMessages.map((message, index) => ({
2176
+ ...message,
2177
+ id: message.id || `msg_${index}`,
2178
+ index: typeof message.index === "number" ? message.index : index,
2179
+ kind: message.kind || "standard",
2180
+ receivedAt: typeof message.receivedAt === "number" ? message.receivedAt : message.timestamp
2181
+ })) : this.hydrateParsedMessages(parsed.messages, this.currentTurnScope);
1966
2182
  return {
1967
2183
  id: parsed.id || "cli_session",
1968
2184
  status: parsed.status || this.currentStatus,
1969
2185
  title: parsed.title || this.cliName,
1970
- messages: parsed.messages,
2186
+ messages: hydratedMessages,
1971
2187
  activeModal: parsed.activeModal ?? this.activeModal,
1972
2188
  providerSessionId: typeof parsed.providerSessionId === "string" ? parsed.providerSessionId : void 0
1973
2189
  };
@@ -1988,11 +2204,30 @@ var init_provider_cli_adapter = __esm({
1988
2204
  activeModal: this.activeModal
1989
2205
  };
1990
2206
  }
2207
+ async invokeScript(scriptName, args) {
2208
+ const fn = this.cliScripts?.[scriptName];
2209
+ if (typeof fn !== "function") {
2210
+ throw new Error(`CLI script '${scriptName}' not available`);
2211
+ }
2212
+ const input = this.buildParseInput(
2213
+ this.committedMessages,
2214
+ this.responseBuffer,
2215
+ this.currentTurnScope
2216
+ );
2217
+ return await Promise.resolve(fn({
2218
+ ...input,
2219
+ args: args && typeof args === "object" ? { ...args } : {}
2220
+ }));
2221
+ }
1991
2222
  parseCurrentTranscript(baseMessages, partialResponse, scope) {
1992
2223
  if (!this.cliScripts?.parseOutput) return null;
1993
2224
  try {
1994
2225
  const input = this.buildParseInput(baseMessages, partialResponse, scope);
1995
2226
  const parsed = this.cliScripts.parseOutput(input);
2227
+ const refinedStatus = this.refineDetectedStatus(typeof parsed?.status === "string" ? parsed.status : null, input.recentBuffer, input.screenText);
2228
+ if (parsed && refinedStatus && parsed.status !== refinedStatus) {
2229
+ parsed.status = refinedStatus;
2230
+ }
1996
2231
  const promptForTrim = scope?.prompt || getLastUserPromptText(baseMessages);
1997
2232
  if (parsed && Array.isArray(parsed.messages) && promptForTrim) {
1998
2233
  const lastAssistant = [...parsed.messages].reverse().find((message) => message?.role === "assistant" && typeof message.content === "string");
@@ -2039,12 +2274,23 @@ ${data.message || ""}`.trim();
2039
2274
  if (this.startupParseGate) {
2040
2275
  const deadline = Date.now() + 1e4;
2041
2276
  while (this.startupParseGate && Date.now() < deadline) {
2277
+ this.resolveStartupState("send_wait");
2042
2278
  await new Promise((resolve9) => setTimeout(resolve9, 50));
2043
2279
  }
2044
2280
  }
2281
+ await this.waitForInteractivePrompt();
2282
+ if (!this.ready) {
2283
+ this.resolveStartupState("send_precheck");
2284
+ const screenText = this.terminalScreen.getText() || "";
2285
+ const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
2286
+ if (hasPrompt && this.currentStatus === "idle") {
2287
+ this.ready = true;
2288
+ this.startupParseGate = false;
2289
+ LOG.info("CLI", `[${this.cliType}] sendMessage recovered idle prompt readiness`);
2290
+ }
2291
+ }
2045
2292
  if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
2046
2293
  if (this.isWaitingForResponse) return;
2047
- await this.waitForInteractivePrompt();
2048
2294
  const blockingModal = this.activeModal || this.getStartupConfirmationModal(this.terminalScreen.getText() || "");
2049
2295
  if (blockingModal || this.currentStatus === "waiting_approval") {
2050
2296
  throw new Error(`${this.cliName} is awaiting confirmation before it can accept a prompt`);
@@ -2088,8 +2334,6 @@ ${data.message || ""}`.trim();
2088
2334
  }
2089
2335
  this.responseEpoch += 1;
2090
2336
  this.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
2091
- this.setStatus("generating", "sendMessage");
2092
- this.onStatusChange?.();
2093
2337
  const startResponseTimeout = () => {
2094
2338
  if (this.responseTimeout) clearTimeout(this.responseTimeout);
2095
2339
  this.responseTimeout = setTimeout(() => {
@@ -2108,7 +2352,7 @@ ${data.message || ""}`.trim();
2108
2352
  const retrySubmitIfStuck = (attempt) => {
2109
2353
  this.submitRetryTimer = null;
2110
2354
  if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
2111
- if (this.currentStatus !== "generating") return;
2355
+ if (this.currentStatus === "waiting_approval") return;
2112
2356
  if ((this.responseBuffer || "").trim()) return;
2113
2357
  const screenText = this.terminalScreen.getText();
2114
2358
  if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
@@ -2143,7 +2387,7 @@ ${data.message || ""}`.trim();
2143
2387
  this.submitRetryTimer = setTimeout(() => {
2144
2388
  this.submitRetryTimer = null;
2145
2389
  if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
2146
- if (this.currentStatus !== "generating") return;
2390
+ if (this.currentStatus === "waiting_approval") return;
2147
2391
  if ((this.responseBuffer || "").trim()) return;
2148
2392
  const screenText = this.terminalScreen.getText();
2149
2393
  if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
@@ -4582,199 +4826,94 @@ var StatusMonitor = class {
4582
4826
  }
4583
4827
  };
4584
4828
 
4585
- // src/providers/extension-provider-instance.ts
4586
- var ExtensionProviderInstance = class {
4587
- type;
4588
- category = "extension";
4589
- provider;
4590
- context = null;
4591
- settings = {};
4592
- events = [];
4593
- // status
4594
- currentStatus = "idle";
4595
- agentStreams = [];
4596
- messages = [];
4597
- activeModal = null;
4598
- currentModel = "";
4599
- currentMode = "";
4600
- controlValues = {};
4601
- lastAgentStatus = "idle";
4602
- generatingStartedAt = 0;
4603
- monitor;
4604
- // meta
4605
- instanceId;
4606
- ideType = "";
4607
- chatId = null;
4608
- chatTitle = null;
4609
- agentName = "";
4610
- extensionId = "";
4611
- constructor(provider) {
4612
- this.type = provider.type;
4613
- this.provider = provider;
4614
- this.instanceId = crypto.randomUUID();
4615
- this.monitor = new StatusMonitor();
4616
- }
4617
- // ─── Lifecycle ──────────────────────────────────
4618
- async init(context) {
4619
- this.context = context;
4620
- this.settings = context.settings || {};
4621
- this.monitor.updateConfig({
4622
- approvalAlert: this.settings.approvalAlert !== false,
4623
- longGeneratingAlert: this.settings.longGeneratingAlert !== false,
4624
- longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
4625
- });
4626
- }
4627
- async onTick() {
4628
- if (!this.context?.cdp?.isConnected) return;
4629
- }
4630
- getState() {
4631
- return {
4632
- type: this.type,
4633
- name: this.provider.name,
4634
- category: "extension",
4635
- status: this.currentStatus,
4636
- activeChat: this.messages.length > 0 ? {
4637
- id: this.chatId || this.instanceId,
4638
- title: this.chatTitle || this.agentName || this.provider.name,
4639
- status: this.currentStatus,
4640
- messages: this.messages,
4641
- activeModal: this.activeModal,
4642
- inputContent: ""
4643
- } : null,
4644
- currentModel: this.currentModel || void 0,
4645
- currentPlan: this.currentMode || void 0,
4646
- controlValues: this.controlValues,
4647
- providerControls: this.provider.controls,
4648
- agentStreams: this.agentStreams,
4649
- instanceId: this.instanceId,
4650
- lastUpdated: Date.now(),
4651
- settings: this.settings,
4652
- pendingEvents: this.flushEvents()
4653
- };
4654
- }
4655
- onEvent(event, data) {
4656
- if (event === "stream_update") {
4657
- if (data?.streams) this.agentStreams = data.streams;
4658
- if (data?.messages) this.messages = data.messages;
4659
- if (data?.activeModal !== void 0) this.activeModal = data.activeModal;
4660
- if (data?.model) this.currentModel = data.model;
4661
- if (data?.mode) this.currentMode = data.mode;
4662
- if (data?.controlValues) this.controlValues = data.controlValues;
4663
- if (typeof data?.sessionId === "string" && data.sessionId.trim()) this.chatId = data.sessionId;
4664
- if (typeof data?.title === "string" && data.title.trim()) this.chatTitle = data.title;
4665
- if (typeof data?.agentName === "string" && data.agentName.trim()) this.agentName = data.agentName;
4666
- if (typeof data?.extensionId === "string" && data.extensionId.trim()) this.extensionId = data.extensionId;
4667
- if (data?.status) {
4668
- const newStatus = data.status;
4669
- this.detectTransition(newStatus, data);
4670
- this.currentStatus = newStatus;
4671
- }
4672
- } else if (event === "stream_reset") {
4673
- this.resetStreamState();
4674
- } else if (event === "extension_connected") {
4675
- this.ideType = data?.ideType || "";
4829
+ // src/providers/control-effects.ts
4830
+ function extractProviderControlValues(controls, data) {
4831
+ if (!data || typeof data !== "object") return void 0;
4832
+ const values = {};
4833
+ const explicit = data.controlValues;
4834
+ if (explicit && typeof explicit === "object") {
4835
+ for (const [key, value] of Object.entries(explicit)) {
4836
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4837
+ values[key] = value;
4838
+ }
4839
+ }
4840
+ }
4841
+ for (const ctrl of controls || []) {
4842
+ if (!ctrl.readFrom) continue;
4843
+ const rawValue = data[ctrl.readFrom];
4844
+ if (rawValue === void 0 || rawValue === null) continue;
4845
+ values[ctrl.id] = normalizeControlValue(rawValue);
4846
+ }
4847
+ if (data.model !== void 0 && values.model === void 0) values.model = normalizeControlValue(data.model);
4848
+ if (data.mode !== void 0 && values.mode === void 0) values.mode = normalizeControlValue(data.mode);
4849
+ return Object.keys(values).length > 0 ? values : void 0;
4850
+ }
4851
+ function normalizeProviderEffects(data) {
4852
+ const rawEffects = Array.isArray(data?.effects) ? data.effects : [];
4853
+ const effects = [];
4854
+ for (const raw of rawEffects) {
4855
+ if (!raw || typeof raw !== "object") continue;
4856
+ const type = raw.type;
4857
+ if (type === "message" && raw.message && typeof raw.message === "object") {
4858
+ const content = raw.message.content;
4859
+ if (typeof content !== "string" && !Array.isArray(content)) continue;
4860
+ effects.push({
4861
+ type: "message",
4862
+ id: typeof raw.id === "string" ? raw.id : void 0,
4863
+ when: raw.when === "turn_completed" ? "turn_completed" : "immediate",
4864
+ persist: raw.persist !== false,
4865
+ message: {
4866
+ role: raw.message.role === "assistant" || raw.message.role === "user" ? raw.message.role : "system",
4867
+ content,
4868
+ kind: typeof raw.message.kind === "string" ? raw.message.kind : void 0,
4869
+ senderName: typeof raw.message.senderName === "string" ? raw.message.senderName : void 0
4870
+ }
4871
+ });
4872
+ continue;
4676
4873
  }
4677
- }
4678
- dispose() {
4679
- this.agentStreams = [];
4680
- this.messages = [];
4681
- this.monitor.reset();
4682
- }
4683
- /** Query UUID instanceId */
4684
- getInstanceId() {
4685
- return this.instanceId;
4686
- }
4687
- // ─── status transition detect ──────────────────────────────
4688
- detectTransition(newStatus, data) {
4689
- const now = Date.now();
4690
- const agentStatus = newStatus === "streaming" || newStatus === "generating" ? "generating" : newStatus === "waiting_approval" ? "waiting_approval" : "idle";
4691
- const lastMsg = Array.isArray(data?.messages) && data.messages.length > 0 ? data.messages[data.messages.length - 1] : null;
4692
- const progressFingerprint = agentStatus === "generating" ? `${lastMsg?.role || ""}:${typeof lastMsg?.content === "string" ? lastMsg.content : JSON.stringify(lastMsg?.content || "")}`.slice(-2e3) : void 0;
4693
- if (agentStatus !== this.lastAgentStatus) {
4694
- if (this.lastAgentStatus === "idle" && agentStatus === "generating") {
4695
- this.generatingStartedAt = now;
4696
- this.pushEvent({
4697
- event: "agent:generating_started",
4698
- chatTitle: this.resolveChatTitle(data),
4699
- timestamp: now,
4700
- ideType: this.ideType || this.type,
4701
- agentType: this.type,
4702
- agentName: this.agentName || this.provider.name,
4703
- extensionId: this.extensionId || this.type
4704
- });
4705
- } else if (agentStatus === "waiting_approval") {
4706
- if (!this.generatingStartedAt) this.generatingStartedAt = now;
4707
- this.pushEvent({
4708
- event: "agent:waiting_approval",
4709
- chatTitle: this.resolveChatTitle(data),
4710
- timestamp: now,
4711
- ideType: this.ideType || this.type,
4712
- agentType: this.type,
4713
- agentName: this.agentName || this.provider.name,
4714
- extensionId: this.extensionId || this.type,
4715
- modalMessage: data?.activeModal?.message,
4716
- modalButtons: data?.activeModal?.buttons
4717
- });
4718
- } else if (agentStatus === "idle" && (this.lastAgentStatus === "generating" || this.lastAgentStatus === "waiting_approval")) {
4719
- const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
4720
- this.pushEvent({
4721
- event: "agent:generating_completed",
4722
- chatTitle: this.resolveChatTitle(data),
4723
- duration,
4724
- timestamp: now,
4725
- ideType: this.ideType || this.type,
4726
- agentType: this.type,
4727
- agentName: this.agentName || this.provider.name,
4728
- extensionId: this.extensionId || this.type
4729
- });
4730
- this.generatingStartedAt = 0;
4731
- }
4732
- this.lastAgentStatus = agentStatus;
4874
+ if (type === "toast" && raw.toast && typeof raw.toast.message === "string") {
4875
+ effects.push({
4876
+ type: "toast",
4877
+ id: typeof raw.id === "string" ? raw.id : void 0,
4878
+ when: raw.when === "turn_completed" ? "turn_completed" : "immediate",
4879
+ persist: raw.persist !== false,
4880
+ toast: {
4881
+ level: raw.toast.level === "success" || raw.toast.level === "warning" ? raw.toast.level : "info",
4882
+ message: raw.toast.message
4883
+ }
4884
+ });
4885
+ continue;
4733
4886
  }
4734
- const agentKey = `${this.type}:ext`;
4735
- const monitorEvents = this.monitor.check(agentKey, agentStatus, now, progressFingerprint);
4736
- for (const me of monitorEvents) {
4737
- this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
4887
+ if (type === "notification" && raw.notification && typeof raw.notification.body === "string") {
4888
+ effects.push({
4889
+ type: "notification",
4890
+ id: typeof raw.id === "string" ? raw.id : void 0,
4891
+ when: raw.when === "turn_completed" ? "turn_completed" : "immediate",
4892
+ persist: raw.persist !== false,
4893
+ notification: {
4894
+ title: typeof raw.notification.title === "string" ? raw.notification.title : void 0,
4895
+ body: raw.notification.body,
4896
+ level: raw.notification.level === "success" || raw.notification.level === "warning" ? raw.notification.level : "info",
4897
+ channels: Array.isArray(raw.notification.channels) ? raw.notification.channels.filter((channel) => channel === "bubble" || channel === "toast" || channel === "browser") : void 0,
4898
+ preferenceKey: raw.notification.preferenceKey === "disconnect" || raw.notification.preferenceKey === "completion" || raw.notification.preferenceKey === "approval" || raw.notification.preferenceKey === "browser" ? raw.notification.preferenceKey : void 0,
4899
+ bubbleContent: typeof raw.notification.bubbleContent === "string" || Array.isArray(raw.notification.bubbleContent) ? raw.notification.bubbleContent : void 0
4900
+ }
4901
+ });
4738
4902
  }
4739
4903
  }
4740
- pushEvent(event) {
4741
- this.events.push(event);
4742
- if (this.events.length > 50) this.events = this.events.slice(-50);
4743
- }
4744
- flushEvents() {
4745
- const events = [...this.events];
4746
- this.events = [];
4747
- return events;
4748
- }
4749
- resolveChatTitle(data) {
4750
- const title = typeof data?.title === "string" && data.title.trim() ? data.title.trim() : this.chatTitle;
4751
- return title || this.agentName || this.provider.name;
4904
+ return effects;
4905
+ }
4906
+ function normalizeControlValue(value) {
4907
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4908
+ return value;
4752
4909
  }
4753
- resetStreamState() {
4754
- if (this.currentStatus !== "idle") {
4755
- this.detectTransition("idle", {
4756
- title: this.chatTitle,
4757
- agentName: this.agentName,
4758
- extensionId: this.extensionId,
4759
- messages: this.messages
4760
- });
4761
- }
4762
- this.agentStreams = [];
4763
- this.messages = [];
4764
- this.activeModal = null;
4765
- this.currentModel = "";
4766
- this.currentMode = "";
4767
- this.controlValues = {};
4768
- this.currentStatus = "idle";
4769
- this.chatId = null;
4770
- this.chatTitle = null;
4771
- this.agentName = "";
4772
- this.extensionId = "";
4773
- this.lastAgentStatus = "idle";
4774
- this.generatingStartedAt = 0;
4775
- this.monitor.reset();
4910
+ if (value && typeof value === "object") {
4911
+ if (typeof value.label === "string") return value.label;
4912
+ if (typeof value.name === "string") return value.name;
4913
+ if (typeof value.id === "string") return value.id;
4776
4914
  }
4777
- };
4915
+ return String(value);
4916
+ }
4778
4917
 
4779
4918
  // src/config/chat-history.ts
4780
4919
  var fs3 = __toESM(require("fs"));
@@ -5027,28 +5166,357 @@ function listSavedHistorySessions(agentType, options = {}) {
5027
5166
  if (parsed.role !== "system" && parsed.content.trim()) preview = parsed.content.trim();
5028
5167
  }
5029
5168
  }
5030
- if (messageCount === 0 || !lastMessageAt) continue;
5031
- summaries.push({
5032
- historySessionId,
5033
- sessionTitle: sessionTitle || void 0,
5034
- messageCount,
5035
- firstMessageAt,
5036
- lastMessageAt,
5037
- preview: preview || void 0
5169
+ if (messageCount === 0 || !lastMessageAt) continue;
5170
+ summaries.push({
5171
+ historySessionId,
5172
+ sessionTitle: sessionTitle || void 0,
5173
+ messageCount,
5174
+ firstMessageAt,
5175
+ lastMessageAt,
5176
+ preview: preview || void 0
5177
+ });
5178
+ }
5179
+ summaries.sort((a, b) => b.lastMessageAt - a.lastMessageAt);
5180
+ const offset = Math.max(0, options.offset || 0);
5181
+ const limit = Math.max(1, options.limit || 30);
5182
+ const sliced = summaries.slice(offset, offset + limit);
5183
+ return {
5184
+ sessions: sliced,
5185
+ hasMore: summaries.length > offset + limit
5186
+ };
5187
+ } catch {
5188
+ return { sessions: [], hasMore: false };
5189
+ }
5190
+ }
5191
+
5192
+ // src/providers/extension-provider-instance.ts
5193
+ var ExtensionProviderInstance = class {
5194
+ type;
5195
+ category = "extension";
5196
+ provider;
5197
+ context = null;
5198
+ settings = {};
5199
+ events = [];
5200
+ // status
5201
+ currentStatus = "idle";
5202
+ agentStreams = [];
5203
+ messages = [];
5204
+ activeModal = null;
5205
+ currentModel = "";
5206
+ currentMode = "";
5207
+ controlValues = {};
5208
+ appliedEffectKeys = /* @__PURE__ */ new Set();
5209
+ runtimeMessages = [];
5210
+ lastAgentStatus = "idle";
5211
+ generatingStartedAt = 0;
5212
+ monitor;
5213
+ historyWriter;
5214
+ // meta
5215
+ instanceId;
5216
+ ideType = "";
5217
+ chatId = null;
5218
+ chatTitle = null;
5219
+ agentName = "";
5220
+ extensionId = "";
5221
+ constructor(provider) {
5222
+ this.type = provider.type;
5223
+ this.provider = provider;
5224
+ this.instanceId = crypto.randomUUID();
5225
+ this.monitor = new StatusMonitor();
5226
+ this.historyWriter = new ChatHistoryWriter();
5227
+ }
5228
+ // ─── Lifecycle ──────────────────────────────────
5229
+ async init(context) {
5230
+ this.context = context;
5231
+ this.settings = context.settings || {};
5232
+ this.monitor.updateConfig({
5233
+ approvalAlert: this.settings.approvalAlert !== false,
5234
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
5235
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
5236
+ });
5237
+ }
5238
+ async onTick() {
5239
+ if (!this.context?.cdp?.isConnected) return;
5240
+ }
5241
+ getState() {
5242
+ return {
5243
+ type: this.type,
5244
+ name: this.provider.name,
5245
+ category: "extension",
5246
+ status: this.currentStatus,
5247
+ activeChat: this.messages.length > 0 || this.runtimeMessages.length > 0 ? {
5248
+ id: this.chatId || this.instanceId,
5249
+ title: this.chatTitle || this.agentName || this.provider.name,
5250
+ status: this.currentStatus,
5251
+ messages: this.mergeConversationMessages(this.messages),
5252
+ activeModal: this.activeModal,
5253
+ inputContent: ""
5254
+ } : null,
5255
+ currentModel: this.currentModel || void 0,
5256
+ currentPlan: this.currentMode || void 0,
5257
+ controlValues: this.controlValues,
5258
+ providerControls: this.provider.controls,
5259
+ agentStreams: this.agentStreams,
5260
+ instanceId: this.instanceId,
5261
+ lastUpdated: Date.now(),
5262
+ settings: this.settings,
5263
+ pendingEvents: this.flushEvents()
5264
+ };
5265
+ }
5266
+ onEvent(event, data) {
5267
+ if (event === "stream_update") {
5268
+ if (data?.streams) this.agentStreams = data.streams;
5269
+ if (data?.messages) this.messages = data.messages;
5270
+ if (data?.activeModal !== void 0) this.activeModal = data.activeModal;
5271
+ if (data?.model) this.currentModel = data.model;
5272
+ if (data?.mode) this.currentMode = data.mode;
5273
+ const controlValues = extractProviderControlValues(this.provider.controls, data) || data?.controlValues;
5274
+ if (controlValues) this.controlValues = controlValues;
5275
+ if (typeof data?.sessionId === "string" && data.sessionId.trim()) this.chatId = data.sessionId;
5276
+ if (typeof data?.title === "string" && data.title.trim()) this.chatTitle = data.title;
5277
+ if (typeof data?.agentName === "string" && data.agentName.trim()) this.agentName = data.agentName;
5278
+ if (typeof data?.extensionId === "string" && data.extensionId.trim()) this.extensionId = data.extensionId;
5279
+ if (data?.status) {
5280
+ const newStatus = data.status;
5281
+ this.detectTransition(newStatus, data);
5282
+ this.currentStatus = newStatus;
5283
+ }
5284
+ } else if (event === "stream_reset") {
5285
+ this.resetStreamState();
5286
+ } else if (event === "extension_connected") {
5287
+ this.ideType = data?.ideType || "";
5288
+ } else if (event === "provider_state_patch" && data && typeof data === "object") {
5289
+ this.applyProviderResponse(data, { phase: "immediate" });
5290
+ }
5291
+ }
5292
+ dispose() {
5293
+ this.agentStreams = [];
5294
+ this.messages = [];
5295
+ this.monitor.reset();
5296
+ this.appliedEffectKeys.clear();
5297
+ this.runtimeMessages = [];
5298
+ }
5299
+ updateSettings(newSettings) {
5300
+ this.settings = { ...newSettings };
5301
+ this.monitor.updateConfig({
5302
+ approvalAlert: this.settings.approvalAlert !== false,
5303
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
5304
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
5305
+ });
5306
+ }
5307
+ /** Query UUID instanceId */
5308
+ getInstanceId() {
5309
+ return this.instanceId;
5310
+ }
5311
+ // ─── status transition detect ──────────────────────────────
5312
+ detectTransition(newStatus, data) {
5313
+ const now = Date.now();
5314
+ const agentStatus = newStatus === "streaming" || newStatus === "generating" ? "generating" : newStatus === "waiting_approval" ? "waiting_approval" : "idle";
5315
+ const lastMsg = Array.isArray(data?.messages) && data.messages.length > 0 ? data.messages[data.messages.length - 1] : null;
5316
+ const progressFingerprint = agentStatus === "generating" ? `${lastMsg?.role || ""}:${typeof lastMsg?.content === "string" ? lastMsg.content : JSON.stringify(lastMsg?.content || "")}`.slice(-2e3) : void 0;
5317
+ const previousStatus = this.lastAgentStatus;
5318
+ if (agentStatus !== this.lastAgentStatus) {
5319
+ if (this.lastAgentStatus === "idle" && agentStatus === "generating") {
5320
+ this.generatingStartedAt = now;
5321
+ this.pushEvent({
5322
+ event: "agent:generating_started",
5323
+ chatTitle: this.resolveChatTitle(data),
5324
+ timestamp: now,
5325
+ ideType: this.ideType || this.type,
5326
+ agentType: this.type,
5327
+ agentName: this.agentName || this.provider.name,
5328
+ extensionId: this.extensionId || this.type
5329
+ });
5330
+ } else if (agentStatus === "waiting_approval") {
5331
+ if (!this.generatingStartedAt) this.generatingStartedAt = now;
5332
+ this.pushEvent({
5333
+ event: "agent:waiting_approval",
5334
+ chatTitle: this.resolveChatTitle(data),
5335
+ timestamp: now,
5336
+ ideType: this.ideType || this.type,
5337
+ agentType: this.type,
5338
+ agentName: this.agentName || this.provider.name,
5339
+ extensionId: this.extensionId || this.type,
5340
+ modalMessage: data?.activeModal?.message,
5341
+ modalButtons: data?.activeModal?.buttons
5342
+ });
5343
+ } else if (agentStatus === "idle" && (this.lastAgentStatus === "generating" || this.lastAgentStatus === "waiting_approval")) {
5344
+ const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
5345
+ this.pushEvent({
5346
+ event: "agent:generating_completed",
5347
+ chatTitle: this.resolveChatTitle(data),
5348
+ duration,
5349
+ timestamp: now,
5350
+ ideType: this.ideType || this.type,
5351
+ agentType: this.type,
5352
+ agentName: this.agentName || this.provider.name,
5353
+ extensionId: this.extensionId || this.type
5354
+ });
5355
+ this.generatingStartedAt = 0;
5356
+ }
5357
+ this.lastAgentStatus = agentStatus;
5358
+ }
5359
+ this.applyProviderResponse(data, {
5360
+ phase: agentStatus === "idle" && (previousStatus === "generating" || previousStatus === "waiting_approval") ? "turn_completed" : "immediate"
5361
+ });
5362
+ const agentKey = `${this.type}:ext`;
5363
+ const monitorEvents = this.monitor.check(agentKey, agentStatus, now, progressFingerprint);
5364
+ for (const me of monitorEvents) {
5365
+ this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
5366
+ }
5367
+ }
5368
+ pushEvent(event) {
5369
+ this.events.push(event);
5370
+ if (this.events.length > 50) this.events = this.events.slice(-50);
5371
+ }
5372
+ applyProviderResponse(data, options) {
5373
+ if (!data || typeof data !== "object") return;
5374
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
5375
+ if (controlValues) this.controlValues = { ...this.controlValues, ...controlValues };
5376
+ const effects = normalizeProviderEffects(data);
5377
+ for (const effect of effects) {
5378
+ const effectWhen = effect.when || "immediate";
5379
+ if (effectWhen === "turn_completed" && options.phase !== "turn_completed") continue;
5380
+ if (effectWhen === "immediate" && options.phase === "turn_completed") continue;
5381
+ const effectKey = this.getEffectDedupKey(effect);
5382
+ if (this.appliedEffectKeys.has(effectKey)) continue;
5383
+ this.appliedEffectKeys.add(effectKey);
5384
+ if (effect.persist !== false) {
5385
+ const persisted = this.getPersistedEffectContent(effect);
5386
+ if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
5387
+ }
5388
+ if (effect.type === "message" && effect.message) {
5389
+ this.pushEvent({
5390
+ event: "provider:message",
5391
+ timestamp: Date.now(),
5392
+ content: typeof effect.message.content === "string" ? effect.message.content : JSON.stringify(effect.message.content),
5393
+ role: effect.message.role || "system",
5394
+ kind: effect.message.kind,
5395
+ senderName: effect.message.senderName
5396
+ });
5397
+ } else if (effect.type === "toast" && effect.toast) {
5398
+ this.pushEvent({
5399
+ event: "provider:toast",
5400
+ effectId: effect.id || effectKey,
5401
+ timestamp: Date.now(),
5402
+ message: effect.toast.message,
5403
+ level: effect.toast.level || "info"
5404
+ });
5405
+ } else if (effect.type === "notification" && effect.notification) {
5406
+ this.pushEvent({
5407
+ event: "provider:notification",
5408
+ effectId: effect.id || effectKey,
5409
+ timestamp: Date.now(),
5410
+ title: effect.notification.title,
5411
+ message: effect.notification.body,
5412
+ content: typeof effect.notification.bubbleContent === "string" ? effect.notification.bubbleContent : effect.notification.body,
5413
+ level: effect.notification.level || "info",
5414
+ channels: effect.notification.channels || ["toast"],
5415
+ preferenceKey: effect.notification.preferenceKey
5416
+ });
5417
+ }
5418
+ }
5419
+ }
5420
+ appendRuntimeSystemMessage(content, dedupKey, receivedAt = Date.now()) {
5421
+ const normalizedContent = String(content || "").trim();
5422
+ if (!normalizedContent) return;
5423
+ if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
5424
+ this.runtimeMessages.push({
5425
+ key: dedupKey,
5426
+ message: {
5427
+ role: "system",
5428
+ senderName: "System",
5429
+ content: normalizedContent,
5430
+ receivedAt,
5431
+ timestamp: receivedAt
5432
+ }
5433
+ });
5434
+ if (this.runtimeMessages.length > 50) this.runtimeMessages = this.runtimeMessages.slice(-50);
5435
+ this.historyWriter.appendNewMessages(
5436
+ this.type,
5437
+ [{
5438
+ role: "system",
5439
+ senderName: "System",
5440
+ content: normalizedContent,
5441
+ kind: "system",
5442
+ receivedAt,
5443
+ historyDedupKey: dedupKey
5444
+ }],
5445
+ this.chatTitle || this.agentName || this.provider.name,
5446
+ this.instanceId,
5447
+ this.chatId || this.instanceId
5448
+ );
5449
+ }
5450
+ mergeConversationMessages(messages) {
5451
+ if (this.runtimeMessages.length === 0) return messages;
5452
+ return [...messages, ...this.runtimeMessages.map((entry) => entry.message)].map((message, index) => ({ message, index })).sort((a, b) => {
5453
+ const aTime = a.message.receivedAt || a.message.timestamp || 0;
5454
+ const bTime = b.message.receivedAt || b.message.timestamp || 0;
5455
+ if (aTime !== bTime) return aTime - bTime;
5456
+ return a.index - b.index;
5457
+ }).map((entry) => entry.message);
5458
+ }
5459
+ getPersistedEffectContent(effect) {
5460
+ if (effect.type === "message") {
5461
+ return typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
5462
+ }
5463
+ if (effect.type === "toast") {
5464
+ return effect.toast?.message || null;
5465
+ }
5466
+ if (effect.type === "notification") {
5467
+ if (typeof effect.notification?.bubbleContent === "string") return effect.notification.bubbleContent;
5468
+ if (typeof effect.notification?.title === "string" && effect.notification.title.trim()) {
5469
+ return `${effect.notification.title}
5470
+ ${effect.notification.body || ""}`.trim();
5471
+ }
5472
+ return effect.notification?.body || null;
5473
+ }
5474
+ return null;
5475
+ }
5476
+ getEffectDedupKey(effect) {
5477
+ if (effect.id) return `provider_effect:${effect.id}`;
5478
+ if (effect.type === "message") {
5479
+ return `provider_effect:message:${typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "")}`;
5480
+ }
5481
+ if (effect.type === "notification") {
5482
+ return `provider_effect:notification:${effect.notification?.title || ""}:${effect.notification?.body || ""}`;
5483
+ }
5484
+ return `provider_effect:toast:${effect.toast?.message || ""}`;
5485
+ }
5486
+ flushEvents() {
5487
+ const events = [...this.events];
5488
+ this.events = [];
5489
+ return events;
5490
+ }
5491
+ resolveChatTitle(data) {
5492
+ const title = typeof data?.title === "string" && data.title.trim() ? data.title.trim() : this.chatTitle;
5493
+ return title || this.agentName || this.provider.name;
5494
+ }
5495
+ resetStreamState() {
5496
+ if (this.currentStatus !== "idle") {
5497
+ this.detectTransition("idle", {
5498
+ title: this.chatTitle,
5499
+ agentName: this.agentName,
5500
+ extensionId: this.extensionId,
5501
+ messages: this.messages
5038
5502
  });
5039
5503
  }
5040
- summaries.sort((a, b) => b.lastMessageAt - a.lastMessageAt);
5041
- const offset = Math.max(0, options.offset || 0);
5042
- const limit = Math.max(1, options.limit || 30);
5043
- const sliced = summaries.slice(offset, offset + limit);
5044
- return {
5045
- sessions: sliced,
5046
- hasMore: summaries.length > offset + limit
5047
- };
5048
- } catch {
5049
- return { sessions: [], hasMore: false };
5504
+ this.agentStreams = [];
5505
+ this.messages = [];
5506
+ this.activeModal = null;
5507
+ this.currentModel = "";
5508
+ this.currentMode = "";
5509
+ this.controlValues = {};
5510
+ this.currentStatus = "idle";
5511
+ this.chatId = null;
5512
+ this.chatTitle = null;
5513
+ this.agentName = "";
5514
+ this.extensionId = "";
5515
+ this.lastAgentStatus = "idle";
5516
+ this.generatingStartedAt = 0;
5517
+ this.monitor.reset();
5050
5518
  }
5051
- }
5519
+ };
5052
5520
 
5053
5521
  // src/providers/ide-provider-instance.ts
5054
5522
  init_logger();
@@ -5069,6 +5537,8 @@ var IdeProviderInstance = class {
5069
5537
  monitor;
5070
5538
  historyWriter;
5071
5539
  autoApproveBusy = false;
5540
+ appliedEffectKeys = /* @__PURE__ */ new Set();
5541
+ runtimeMessages = [];
5072
5542
  // IDE meta
5073
5543
  ideVersion = "";
5074
5544
  instanceId;
@@ -5129,7 +5599,7 @@ var IdeProviderInstance = class {
5129
5599
  id: this.cachedChat.id || "active_session",
5130
5600
  title: this.cachedChat.title || this.type,
5131
5601
  status: this.cachedChat.status || this.currentStatus,
5132
- messages: this.cachedChat.messages || [],
5602
+ messages: this.mergeConversationMessages(this.cachedChat.messages || []),
5133
5603
  activeModal: this.cachedChat.activeModal || null,
5134
5604
  inputContent: this.cachedChat.inputContent || ""
5135
5605
  } : null,
@@ -5169,6 +5639,13 @@ var IdeProviderInstance = class {
5169
5639
  for (const ext of this.extensions.values()) {
5170
5640
  ext.onEvent("stream_reset");
5171
5641
  }
5642
+ } else if (event === "provider_state_patch" && data && typeof data === "object") {
5643
+ const extType = typeof data.extensionType === "string" ? data.extensionType : "";
5644
+ if (extType && this.extensions.has(extType)) {
5645
+ this.extensions.get(extType).onEvent("provider_state_patch", data);
5646
+ } else {
5647
+ this.applyProviderResponse(data, { phase: "immediate" });
5648
+ }
5172
5649
  }
5173
5650
  }
5174
5651
  dispose() {
@@ -5176,11 +5653,21 @@ var IdeProviderInstance = class {
5176
5653
  this.lastAgentStatuses.clear();
5177
5654
  this.generatingStartedAt.clear();
5178
5655
  this.monitor.reset();
5656
+ this.appliedEffectKeys.clear();
5657
+ this.runtimeMessages = [];
5179
5658
  for (const ext of this.extensions.values()) {
5180
5659
  ext.dispose();
5181
5660
  }
5182
5661
  this.extensions.clear();
5183
5662
  }
5663
+ updateSettings(newSettings) {
5664
+ this.settings = { ...newSettings };
5665
+ this.monitor.updateConfig({
5666
+ approvalAlert: this.settings.approvalAlert !== false,
5667
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
5668
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
5669
+ });
5670
+ }
5184
5671
  // ─── Extension manage ─────────────────────────────
5185
5672
  /** Extension Instance add */
5186
5673
  async addExtension(provider, settings) {
@@ -5293,6 +5780,8 @@ var IdeProviderInstance = class {
5293
5780
  raw.messages = raw.messages.filter((m) => !hiddenKinds.has(m.kind));
5294
5781
  }
5295
5782
  }
5783
+ const controlValues = extractProviderControlValues(this.provider.controls, raw);
5784
+ if (controlValues) raw.controlValues = controlValues;
5296
5785
  this.cachedChat = { ...raw, activeModal };
5297
5786
  this.detectAgentTransitions(raw, now);
5298
5787
  if (raw.messages?.length > 0) {
@@ -5359,6 +5848,9 @@ var IdeProviderInstance = class {
5359
5848
  }
5360
5849
  this.lastAgentStatuses.set(agentKey, agentStatus);
5361
5850
  }
5851
+ this.applyProviderResponse(chatData, {
5852
+ phase: agentStatus === "idle" && (lastStatus === "generating" || lastStatus === "waiting_approval") ? "turn_completed" : "immediate"
5853
+ });
5362
5854
  if (agentStatus === "waiting_approval" && this.settings.autoApprove && !this.autoApproveBusy) {
5363
5855
  this.autoApproveViaScript(chatData);
5364
5856
  }
@@ -5371,6 +5863,136 @@ var IdeProviderInstance = class {
5371
5863
  this.events.push(event);
5372
5864
  if (this.events.length > 50) this.events = this.events.slice(-50);
5373
5865
  }
5866
+ applyProviderResponse(data, options) {
5867
+ if (!data || typeof data !== "object") return;
5868
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
5869
+ if (controlValues) {
5870
+ this.cachedChat = {
5871
+ ...this.cachedChat || {},
5872
+ ...data,
5873
+ controlValues: { ...this.cachedChat?.controlValues || {}, ...controlValues }
5874
+ };
5875
+ }
5876
+ const effects = normalizeProviderEffects(data);
5877
+ for (const effect of effects) {
5878
+ const effectWhen = effect.when || "immediate";
5879
+ if (effectWhen === "turn_completed" && options.phase !== "turn_completed") continue;
5880
+ if (effectWhen === "immediate" && options.phase === "turn_completed") continue;
5881
+ const effectKey = this.getEffectDedupKey(effect);
5882
+ if (this.appliedEffectKeys.has(effectKey)) continue;
5883
+ this.appliedEffectKeys.add(effectKey);
5884
+ if (effect.persist !== false) {
5885
+ const persisted = this.getPersistedEffectContent(effect);
5886
+ if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
5887
+ }
5888
+ if (effect.type === "message" && effect.message) {
5889
+ this.pushEvent({
5890
+ event: "provider:message",
5891
+ timestamp: Date.now(),
5892
+ content: typeof effect.message.content === "string" ? effect.message.content : JSON.stringify(effect.message.content),
5893
+ role: effect.message.role || "system",
5894
+ kind: effect.message.kind,
5895
+ senderName: effect.message.senderName
5896
+ });
5897
+ } else if (effect.type === "toast" && effect.toast) {
5898
+ this.pushEvent({
5899
+ event: "provider:toast",
5900
+ effectId: effect.id || effectKey,
5901
+ timestamp: Date.now(),
5902
+ message: effect.toast.message,
5903
+ level: effect.toast.level || "info"
5904
+ });
5905
+ } else if (effect.type === "notification" && effect.notification) {
5906
+ this.pushEvent({
5907
+ event: "provider:notification",
5908
+ effectId: effect.id || effectKey,
5909
+ timestamp: Date.now(),
5910
+ title: effect.notification.title,
5911
+ message: effect.notification.body,
5912
+ content: typeof effect.notification.bubbleContent === "string" ? effect.notification.bubbleContent : effect.notification.body,
5913
+ level: effect.notification.level || "info",
5914
+ channels: effect.notification.channels || ["toast"],
5915
+ preferenceKey: effect.notification.preferenceKey
5916
+ });
5917
+ }
5918
+ }
5919
+ }
5920
+ appendRuntimeSystemMessage(content, dedupKey, receivedAt = Date.now()) {
5921
+ const normalizedContent = String(content || "").trim();
5922
+ if (!normalizedContent) return;
5923
+ if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
5924
+ if (!this.cachedChat) {
5925
+ this.cachedChat = {
5926
+ id: "active_session",
5927
+ title: this.provider.name,
5928
+ status: this.currentStatus,
5929
+ messages: [],
5930
+ activeModal: null,
5931
+ inputContent: ""
5932
+ };
5933
+ }
5934
+ this.runtimeMessages.push({
5935
+ key: dedupKey,
5936
+ message: {
5937
+ role: "system",
5938
+ senderName: "System",
5939
+ content: normalizedContent,
5940
+ receivedAt,
5941
+ timestamp: receivedAt
5942
+ }
5943
+ });
5944
+ if (this.runtimeMessages.length > 50) this.runtimeMessages = this.runtimeMessages.slice(-50);
5945
+ this.historyWriter.appendNewMessages(
5946
+ this.type,
5947
+ [{
5948
+ role: "system",
5949
+ senderName: "System",
5950
+ content: normalizedContent,
5951
+ kind: "system",
5952
+ receivedAt,
5953
+ historyDedupKey: dedupKey
5954
+ }],
5955
+ this.cachedChat?.title || this.provider.name,
5956
+ this.instanceId,
5957
+ this.cachedChat?.id || this.instanceId
5958
+ );
5959
+ }
5960
+ mergeConversationMessages(messages) {
5961
+ if (this.runtimeMessages.length === 0) return messages;
5962
+ return [...messages, ...this.runtimeMessages.map((entry) => entry.message)].map((message, index) => ({ message, index })).sort((a, b) => {
5963
+ const aTime = a.message.receivedAt || a.message.timestamp || 0;
5964
+ const bTime = b.message.receivedAt || b.message.timestamp || 0;
5965
+ if (aTime !== bTime) return aTime - bTime;
5966
+ return a.index - b.index;
5967
+ }).map((entry) => entry.message);
5968
+ }
5969
+ getPersistedEffectContent(effect) {
5970
+ if (effect.type === "message") {
5971
+ return typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
5972
+ }
5973
+ if (effect.type === "toast") {
5974
+ return effect.toast?.message || null;
5975
+ }
5976
+ if (effect.type === "notification") {
5977
+ if (typeof effect.notification?.bubbleContent === "string") return effect.notification.bubbleContent;
5978
+ if (typeof effect.notification?.title === "string" && effect.notification.title.trim()) {
5979
+ return `${effect.notification.title}
5980
+ ${effect.notification.body || ""}`.trim();
5981
+ }
5982
+ return effect.notification?.body || null;
5983
+ }
5984
+ return null;
5985
+ }
5986
+ getEffectDedupKey(effect) {
5987
+ if (effect.id) return `provider_effect:${effect.id}`;
5988
+ if (effect.type === "message") {
5989
+ return `provider_effect:message:${typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "")}`;
5990
+ }
5991
+ if (effect.type === "notification") {
5992
+ return `provider_effect:notification:${effect.notification?.title || ""}:${effect.notification?.body || ""}`;
5993
+ }
5994
+ return `provider_effect:toast:${effect.toast?.message || ""}`;
5995
+ }
5374
5996
  flushEvents() {
5375
5997
  const events = [...this.events];
5376
5998
  this.events = [];
@@ -6255,6 +6877,24 @@ function isRecentDuplicateSend(key) {
6255
6877
  recentSendByTarget.set(key, now);
6256
6878
  return false;
6257
6879
  }
6880
+ function parseMaybeJson(value) {
6881
+ if (typeof value !== "string") return value;
6882
+ try {
6883
+ return JSON.parse(value);
6884
+ } catch {
6885
+ return value;
6886
+ }
6887
+ }
6888
+ function didProviderConfirmSend(result) {
6889
+ const parsed = parseMaybeJson(result);
6890
+ if (parsed === true) return true;
6891
+ if (typeof parsed === "string") {
6892
+ const normalized = parsed.trim().toLowerCase();
6893
+ return normalized === "ok" || normalized === "sent" || normalized === "success" || normalized === "true";
6894
+ }
6895
+ if (!parsed || typeof parsed !== "object") return false;
6896
+ return parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true;
6897
+ }
6258
6898
  async function handleChatHistory(h, args) {
6259
6899
  const { agentType, offset, limit } = args;
6260
6900
  const historySessionId = getHistorySessionId(h, args);
@@ -6437,14 +7077,8 @@ async function handleSendChat(h, args) {
6437
7077
  try {
6438
7078
  const evalResult = await h.evaluateProviderScript("sendMessage", { MESSAGE: text }, 3e4);
6439
7079
  if (evalResult?.result) {
6440
- let parsed = evalResult.result;
6441
- if (typeof parsed === "string") {
6442
- try {
6443
- parsed = JSON.parse(parsed);
6444
- } catch {
6445
- }
6446
- }
6447
- if (parsed?.sent) {
7080
+ const parsed = parseMaybeJson(evalResult.result);
7081
+ if (didProviderConfirmSend(parsed)) {
6448
7082
  _log(`Extension script sent OK`);
6449
7083
  return _logSendSuccess("extension-script");
6450
7084
  }
@@ -6476,14 +7110,8 @@ async function handleSendChat(h, args) {
6476
7110
  if (sendScript) {
6477
7111
  try {
6478
7112
  const result = await targetCdp.evaluate(sendScript, 3e4);
6479
- let parsed = result;
6480
- if (typeof result === "string") {
6481
- try {
6482
- parsed = JSON.parse(result);
6483
- } catch {
6484
- }
6485
- }
6486
- if (parsed?.sent) {
7113
+ const parsed = parseMaybeJson(result);
7114
+ if (didProviderConfirmSend(parsed)) {
6487
7115
  _log(`sendMessage script OK`);
6488
7116
  return _logSendSuccess("script");
6489
7117
  }
@@ -6528,14 +7156,8 @@ async function handleSendChat(h, args) {
6528
7156
  const matchText = provider.webviewMatchText;
6529
7157
  const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
6530
7158
  const wvResult = await targetCdp.evaluateInWebviewFrame(webviewScript, matchFn);
6531
- let wvParsed = wvResult;
6532
- if (typeof wvResult === "string") {
6533
- try {
6534
- wvParsed = JSON.parse(wvResult);
6535
- } catch {
6536
- }
6537
- }
6538
- if (wvParsed?.sent) {
7159
+ const wvParsed = parseMaybeJson(wvResult);
7160
+ if (didProviderConfirmSend(wvParsed)) {
6539
7161
  _log(`webviewSendMessage OK`);
6540
7162
  return _logSendSuccess("webview-script");
6541
7163
  }
@@ -6557,14 +7179,8 @@ async function handleSendChat(h, args) {
6557
7179
  const matchText = provider.webviewMatchText;
6558
7180
  const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
6559
7181
  const wvResult = await targetCdp.evaluateInWebviewFrame(webviewScript, matchFn);
6560
- let wvParsed = wvResult;
6561
- if (typeof wvResult === "string") {
6562
- try {
6563
- wvParsed = JSON.parse(wvResult);
6564
- } catch {
6565
- }
6566
- }
6567
- if (wvParsed?.sent) {
7182
+ const wvParsed = parseMaybeJson(wvResult);
7183
+ if (didProviderConfirmSend(wvParsed)) {
6568
7184
  _log(`webviewSendMessage OK`);
6569
7185
  return _logSendSuccess("webview-script");
6570
7186
  }
@@ -7450,7 +8066,56 @@ function handleSetProviderSetting(h, args) {
7450
8066
  }
7451
8067
  return { success: false, error: `Failed to set ${providerType}.${key} \u2014 invalid key, value, or not a public setting` };
7452
8068
  }
7453
- async function handleExtensionScript(h, args, scriptName) {
8069
+ function normalizeProviderScriptArgs(args) {
8070
+ const normalizedArgs = { ...args || {} };
8071
+ for (const key of ["mode", "model", "message", "action", "button", "text", "sessionId", "value"]) {
8072
+ if (key in normalizedArgs && !(key.toUpperCase() in normalizedArgs)) {
8073
+ normalizedArgs[key.toUpperCase()] = normalizedArgs[key];
8074
+ }
8075
+ }
8076
+ return normalizedArgs;
8077
+ }
8078
+ function parseScriptResult(result) {
8079
+ if (typeof result === "string") {
8080
+ try {
8081
+ const parsed = JSON.parse(result);
8082
+ if (parsed && typeof parsed === "object" && parsed.success === false) {
8083
+ return { success: false, payload: parsed };
8084
+ }
8085
+ return { success: true, payload: parsed };
8086
+ } catch {
8087
+ return { success: true, payload: { result } };
8088
+ }
8089
+ }
8090
+ if (result && typeof result === "object" && result.success === false) {
8091
+ return { success: false, payload: result };
8092
+ }
8093
+ return { success: true, payload: result };
8094
+ }
8095
+ function getCliScriptCommand(payload) {
8096
+ if (!payload || typeof payload !== "object") return null;
8097
+ if (typeof payload.sendMessage === "string" && payload.sendMessage.trim()) {
8098
+ return { type: "send_message", text: payload.sendMessage.trim() };
8099
+ }
8100
+ const command = payload.command;
8101
+ if (!command || typeof command !== "object") return null;
8102
+ if (command.type !== "send_message") return null;
8103
+ const text = typeof command.text === "string" ? command.text.trim() : typeof command.message === "string" ? command.message.trim() : "";
8104
+ if (!text) return null;
8105
+ return { type: "send_message", text };
8106
+ }
8107
+ function applyProviderPatch(h, args, payload) {
8108
+ if (!payload || typeof payload !== "object") return;
8109
+ const targetSessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "";
8110
+ const targetSession = targetSessionId ? h.ctx.sessionRegistry?.get(targetSessionId) : void 0;
8111
+ const instanceKey = targetSession?.instanceKey || targetSessionId;
8112
+ if (!instanceKey) return;
8113
+ h.ctx.instanceManager?.sendEvent(instanceKey, "provider_state_patch", {
8114
+ ...payload,
8115
+ extensionType: targetSession?.transport === "cdp-webview" ? targetSession.providerType : void 0
8116
+ });
8117
+ }
8118
+ async function executeProviderScript(h, args, scriptName) {
7454
8119
  const { agentType, ideType } = args || {};
7455
8120
  if (!agentType) return { success: false, error: "agentType is required" };
7456
8121
  const loader = h.ctx.providerLoader;
@@ -7463,13 +8128,29 @@ async function handleExtensionScript(h, args, scriptName) {
7463
8128
  if (!provider.scripts?.[actualScriptName]) {
7464
8129
  return { success: false, error: `Script '${actualScriptName}' not available for ${agentType}` };
7465
8130
  }
7466
- const scriptFn = provider.scripts[actualScriptName];
7467
- const normalizedArgs = { ...args };
7468
- for (const key of ["mode", "model", "message", "action", "button", "text", "sessionId"]) {
7469
- if (key in normalizedArgs && !(key.toUpperCase() in normalizedArgs)) {
7470
- normalizedArgs[key.toUpperCase()] = normalizedArgs[key];
8131
+ const normalizedArgs = normalizeProviderScriptArgs(args);
8132
+ if (provider.category === "cli") {
8133
+ const adapter = h.getCliAdapter(args?.targetSessionId || agentType);
8134
+ if (!adapter?.invokeScript) {
8135
+ return { success: false, error: `CLI adapter does not support script '${actualScriptName}'` };
8136
+ }
8137
+ try {
8138
+ const raw = await adapter.invokeScript(actualScriptName, normalizedArgs);
8139
+ const parsed = parseScriptResult(raw);
8140
+ if (!parsed.success) {
8141
+ return { success: false, ...parsed.payload || {} };
8142
+ }
8143
+ const cliCommand = getCliScriptCommand(parsed.payload);
8144
+ if (cliCommand?.type === "send_message" && cliCommand.text) {
8145
+ await adapter.sendMessage(cliCommand.text);
8146
+ }
8147
+ applyProviderPatch(h, args, parsed.payload);
8148
+ return { success: true, ...parsed.payload && typeof parsed.payload === "object" ? parsed.payload : { result: parsed.payload } };
8149
+ } catch (e) {
8150
+ return { success: false, error: `Script execution failed: ${e.message}` };
7471
8151
  }
7472
8152
  }
8153
+ const scriptFn = provider.scripts[actualScriptName];
7473
8154
  const scriptCode = scriptFn(normalizedArgs);
7474
8155
  if (!scriptCode) return { success: false, error: `Script '${actualScriptName}' returned null` };
7475
8156
  const cdpKey = provider.category === "ide" ? h.currentSession?.cdpManagerKey || h.currentManagerKey || agentType : h.currentSession?.cdpManagerKey || h.currentManagerKey || ideType;
@@ -7523,16 +8204,29 @@ async function handleExtensionScript(h, args, scriptName) {
7523
8204
  if (typeof result === "string") {
7524
8205
  try {
7525
8206
  const parsed = JSON.parse(result);
8207
+ applyProviderPatch(h, args, parsed);
8208
+ if (parsed && typeof parsed === "object" && parsed.success === false) {
8209
+ return { success: false, ...parsed };
8210
+ }
7526
8211
  return { success: true, ...parsed };
7527
8212
  } catch {
7528
8213
  return { success: true, result };
7529
8214
  }
7530
8215
  }
8216
+ applyProviderPatch(h, args, result);
7531
8217
  return { success: true, result };
7532
8218
  } catch (e) {
7533
8219
  return { success: false, error: `Script execution failed: ${e.message}` };
7534
8220
  }
7535
8221
  }
8222
+ async function handleExtensionScript(h, args, scriptName) {
8223
+ return executeProviderScript(h, args, scriptName);
8224
+ }
8225
+ async function handleProviderScript(h, args) {
8226
+ const scriptName = typeof args?.scriptName === "string" ? args.scriptName.trim() : "";
8227
+ if (!scriptName) return { success: false, error: "scriptName is required" };
8228
+ return executeProviderScript(h, args, scriptName);
8229
+ }
7536
8230
  function handleGetIdeExtensions(h, args) {
7537
8231
  const { ideType } = args || {};
7538
8232
  const loader = h.ctx.providerLoader;
@@ -8032,6 +8726,8 @@ var DaemonCommandHandler = class {
8032
8726
  case "set_ide_extension":
8033
8727
  return handleSetIdeExtension(this, args);
8034
8728
  // ─── Extension Model / Mode Control (stream-commands.ts) ──────────
8729
+ case "invoke_provider_script":
8730
+ return handleProviderScript(this, args);
8035
8731
  case "list_extension_models":
8036
8732
  return handleExtensionScript(this, args, "listModels");
8037
8733
  case "set_extension_model":
@@ -8208,6 +8904,8 @@ var CliProviderInstance = class {
8208
8904
  generatingDebounceTimer = null;
8209
8905
  generatingDebouncePending = null;
8210
8906
  lastApprovalEventAt = 0;
8907
+ controlValues = {};
8908
+ appliedEffectKeys = /* @__PURE__ */ new Set();
8211
8909
  historyWriter;
8212
8910
  runtimeMessages = [];
8213
8911
  instanceId;
@@ -8220,6 +8918,7 @@ var CliProviderInstance = class {
8220
8918
  async init(context) {
8221
8919
  this.context = context;
8222
8920
  this.settings = context.settings || {};
8921
+ this.adapter.updateRuntimeSettings?.(this.settings);
8223
8922
  this.monitor.updateConfig({
8224
8923
  approvalAlert: this.settings.approvalAlert !== false,
8225
8924
  longGeneratingAlert: this.settings.longGeneratingAlert !== false,
@@ -8235,6 +8934,21 @@ var CliProviderInstance = class {
8235
8934
  this.detectStatusTransition();
8236
8935
  });
8237
8936
  await this.adapter.spawn();
8937
+ if (this.providerSessionId) {
8938
+ const restoredHistory = readChatHistory(this.type, 0, 200, this.providerSessionId);
8939
+ if (restoredHistory.messages.length > 0) {
8940
+ this.adapter.seedCommittedMessages(
8941
+ restoredHistory.messages.map((message) => ({
8942
+ role: message.role,
8943
+ content: message.content,
8944
+ timestamp: message.receivedAt,
8945
+ receivedAt: message.receivedAt,
8946
+ kind: message.kind,
8947
+ senderName: message.senderName
8948
+ }))
8949
+ );
8950
+ }
8951
+ }
8238
8952
  if (this.providerSessionId && this.launchMode === "resume") {
8239
8953
  const resumedAt = Date.now();
8240
8954
  this.historyWriter.appendSystemMarker(
@@ -8315,6 +9029,12 @@ var CliProviderInstance = class {
8315
9029
  }
8316
9030
  const runtime = this.adapter.getRuntimeMetadata();
8317
9031
  const parsedMessages = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [];
9032
+ const controlValues = extractProviderControlValues(this.provider.controls, parsedStatus);
9033
+ if (controlValues) {
9034
+ this.controlValues = controlValues;
9035
+ } else if (Object.keys(this.controlValues).length > 0) {
9036
+ this.controlValues = {};
9037
+ }
8318
9038
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
8319
9039
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
8320
9040
  if (parsedMessages.length > 0) {
@@ -8335,6 +9055,7 @@ var CliProviderInstance = class {
8335
9055
  );
8336
9056
  }
8337
9057
  }
9058
+ this.applyProviderResponse(parsedStatus, { phase: "immediate" });
8338
9059
  return {
8339
9060
  type: this.type,
8340
9061
  name: this.provider.name,
@@ -8364,8 +9085,7 @@ var CliProviderInstance = class {
8364
9085
  attachedClients: runtime.attachedClients || []
8365
9086
  } : void 0,
8366
9087
  resume: this.provider.resume,
8367
- controlValues: void 0,
8368
- // CLI controls not yet wired from stream
9088
+ controlValues: this.controlValues,
8369
9089
  providerControls: this.provider.controls
8370
9090
  };
8371
9091
  }
@@ -8376,6 +9096,15 @@ var CliProviderInstance = class {
8376
9096
  getPresentationMode() {
8377
9097
  return this.presentationMode;
8378
9098
  }
9099
+ updateSettings(newSettings) {
9100
+ this.settings = { ...newSettings };
9101
+ this.adapter.updateRuntimeSettings?.(this.settings);
9102
+ this.monitor.updateConfig({
9103
+ approvalAlert: this.settings.approvalAlert !== false,
9104
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
9105
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
9106
+ });
9107
+ }
8379
9108
  onEvent(event, data) {
8380
9109
  if (event === "send_message" && data?.text) {
8381
9110
  void this.adapter.sendMessage(data.text).catch((e) => {
@@ -8387,22 +9116,27 @@ var CliProviderInstance = class {
8387
9116
  void this.adapter.resolveAction(data).catch((e) => {
8388
9117
  LOG.warn("CLI", `[${this.type}] resolve_action failed: ${e?.message || e}`);
8389
9118
  });
9119
+ } else if (event === "provider_state_patch" && data && typeof data === "object") {
9120
+ this.applyProviderResponse(data, { phase: "immediate" });
8390
9121
  }
8391
9122
  }
8392
9123
  dispose() {
8393
9124
  this.adapter.shutdown();
8394
9125
  this.monitor.reset();
9126
+ this.appliedEffectKeys.clear();
8395
9127
  }
8396
9128
  completedDebounceTimer = null;
8397
9129
  completedDebouncePending = null;
8398
9130
  detectStatusTransition() {
8399
9131
  const now = Date.now();
8400
9132
  const adapterStatus = this.adapter.getStatus();
9133
+ const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
8401
9134
  const newStatus = adapterStatus.status;
8402
9135
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
8403
9136
  const chatTitle = `${this.provider.name} \xB7 ${dirName}`;
8404
9137
  const partial = this.adapter.getPartialResponse();
8405
9138
  const progressFingerprint = newStatus === "generating" ? `${partial || ""}::${adapterStatus.messages.at(-1)?.content || ""}`.slice(-2e3) : void 0;
9139
+ const previousStatus = this.lastStatus;
8406
9140
  if (newStatus !== this.lastStatus) {
8407
9141
  LOG.info("CLI", `[${this.type}] status: ${this.lastStatus} \u2192 ${newStatus}`);
8408
9142
  if (this.lastStatus === "idle" && newStatus === "generating") {
@@ -8495,6 +9229,9 @@ var CliProviderInstance = class {
8495
9229
  }
8496
9230
  this.lastStatus = newStatus;
8497
9231
  }
9232
+ this.applyProviderResponse(parsedStatus, {
9233
+ phase: newStatus === "idle" && (previousStatus === "generating" || previousStatus === "waiting_approval") ? "turn_completed" : "immediate"
9234
+ });
8498
9235
  const agentKey = `${this.type}:cli`;
8499
9236
  const monitorEvents = this.monitor.check(agentKey, newStatus, now, progressFingerprint);
8500
9237
  for (const me of monitorEvents) {
@@ -8510,6 +9247,88 @@ var CliProviderInstance = class {
8510
9247
  this.events = [];
8511
9248
  return events;
8512
9249
  }
9250
+ applyProviderResponse(data, options) {
9251
+ if (!data || typeof data !== "object") return;
9252
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
9253
+ if (controlValues) {
9254
+ this.controlValues = { ...this.controlValues, ...controlValues };
9255
+ }
9256
+ const effects = normalizeProviderEffects(data);
9257
+ for (const effect of effects) {
9258
+ const effectWhen = effect.when || "immediate";
9259
+ if (effectWhen === "turn_completed" && options.phase !== "turn_completed") continue;
9260
+ if (effectWhen === "immediate" && options.phase === "turn_completed") continue;
9261
+ const effectKey = this.getEffectDedupKey(effect);
9262
+ if (this.appliedEffectKeys.has(effectKey)) continue;
9263
+ this.appliedEffectKeys.add(effectKey);
9264
+ if (effect.persist !== false) {
9265
+ const persisted = this.getPersistedEffectContent(effect);
9266
+ if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
9267
+ }
9268
+ if (effect.type === "message" && effect.message) {
9269
+ const content = typeof effect.message.content === "string" ? effect.message.content : JSON.stringify(effect.message.content);
9270
+ this.pushEvent({
9271
+ event: "provider:message",
9272
+ timestamp: Date.now(),
9273
+ content,
9274
+ role: effect.message.role || "system",
9275
+ kind: effect.message.kind,
9276
+ senderName: effect.message.senderName
9277
+ });
9278
+ } else if (effect.type === "toast" && effect.toast) {
9279
+ this.pushEvent({
9280
+ event: "provider:toast",
9281
+ effectId: effect.id || effectKey,
9282
+ timestamp: Date.now(),
9283
+ message: effect.toast.message,
9284
+ level: effect.toast.level || "info"
9285
+ });
9286
+ } else if (effect.type === "notification" && effect.notification) {
9287
+ this.pushEvent({
9288
+ event: "provider:notification",
9289
+ effectId: effect.id || effectKey,
9290
+ timestamp: Date.now(),
9291
+ title: effect.notification.title,
9292
+ message: effect.notification.body,
9293
+ content: typeof effect.notification.bubbleContent === "string" ? effect.notification.bubbleContent : effect.notification.body,
9294
+ level: effect.notification.level || "info",
9295
+ channels: effect.notification.channels || ["toast"],
9296
+ preferenceKey: effect.notification.preferenceKey
9297
+ });
9298
+ }
9299
+ }
9300
+ if (this.appliedEffectKeys.size > 200) {
9301
+ this.appliedEffectKeys = new Set(Array.from(this.appliedEffectKeys).slice(-100));
9302
+ }
9303
+ }
9304
+ getEffectDedupKey(effect) {
9305
+ if (effect.id) return `provider_effect:${effect.id}`;
9306
+ if (effect.type === "message") {
9307
+ const content = typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
9308
+ return `provider_effect:message:${content}`;
9309
+ }
9310
+ if (effect.type === "notification") {
9311
+ return `provider_effect:notification:${effect.notification?.title || ""}:${effect.notification?.body || ""}`;
9312
+ }
9313
+ return `provider_effect:toast:${effect.toast?.message || ""}`;
9314
+ }
9315
+ getPersistedEffectContent(effect) {
9316
+ if (effect.type === "message") {
9317
+ return typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
9318
+ }
9319
+ if (effect.type === "toast") {
9320
+ return effect.toast?.message || null;
9321
+ }
9322
+ if (effect.type === "notification") {
9323
+ if (typeof effect.notification?.bubbleContent === "string") return effect.notification.bubbleContent;
9324
+ if (typeof effect.notification?.title === "string" && effect.notification.title.trim()) {
9325
+ return `${effect.notification.title}
9326
+ ${effect.notification.body || ""}`.trim();
9327
+ }
9328
+ return effect.notification?.body || null;
9329
+ }
9330
+ return null;
9331
+ }
8513
9332
  // ─── Adapter access (backward compat) ──────────────────
8514
9333
  getAdapter() {
8515
9334
  return this.adapter;
@@ -11282,6 +12101,22 @@ function getMacAppIdentifiers() {
11282
12101
  function getWinProcessNames() {
11283
12102
  return getProviderLoader().getWinProcessNames();
11284
12103
  }
12104
+ function getProviderMeta(ideId) {
12105
+ return getProviderLoader().getMeta(ideId);
12106
+ }
12107
+ function getPreferredLaunchMethod(ideId, platform9) {
12108
+ const prefer = getProviderMeta(ideId)?.launch?.prefer;
12109
+ const value = prefer?.[platform9];
12110
+ return value === "cli" || value === "app" || value === "auto" ? value : "auto";
12111
+ }
12112
+ function getCdpStartupTimeoutMs(ideId) {
12113
+ const value = getProviderMeta(ideId)?.launch?.cdpStartupTimeoutMs;
12114
+ if (typeof value !== "number" || !Number.isFinite(value)) return 15e3;
12115
+ return Math.max(1e3, Math.floor(value));
12116
+ }
12117
+ function escapeForAppleScript(value) {
12118
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
12119
+ }
11285
12120
  async function findFreePort(ports) {
11286
12121
  for (const port2 of ports) {
11287
12122
  const free = await checkPortFree(port2);
@@ -11334,12 +12169,12 @@ async function killIdeProcess(ideId) {
11334
12169
  try {
11335
12170
  if (plat === "darwin" && appName) {
11336
12171
  try {
11337
- (0, import_child_process6.execSync)(`osascript -e 'tell application "${appName}" to quit' 2>/dev/null`, {
12172
+ (0, import_child_process6.execSync)(`osascript -e 'tell application "${escapeForAppleScript(appName)}" to quit' 2>/dev/null`, {
11338
12173
  timeout: 5e3
11339
12174
  });
11340
12175
  } catch {
11341
12176
  try {
11342
- (0, import_child_process6.execSync)(`pkill -f "${appName}" 2>/dev/null`);
12177
+ (0, import_child_process6.execSync)(`pkill -x "${appName}" 2>/dev/null`, { timeout: 5e3 });
11343
12178
  } catch {
11344
12179
  }
11345
12180
  }
@@ -11369,7 +12204,7 @@ async function killIdeProcess(ideId) {
11369
12204
  }
11370
12205
  if (plat === "darwin" && appName) {
11371
12206
  try {
11372
- (0, import_child_process6.execSync)(`pkill -9 -f "${appName}" 2>/dev/null`);
12207
+ (0, import_child_process6.execSync)(`pkill -9 -x "${appName}" 2>/dev/null`, { timeout: 5e3 });
11373
12208
  } catch {
11374
12209
  }
11375
12210
  } else if (plat === "win32" && winProcesses) {
@@ -11392,8 +12227,23 @@ function isIdeRunning(ideId) {
11392
12227
  if (plat === "darwin") {
11393
12228
  const appName = getMacAppIdentifiers()[ideId];
11394
12229
  if (!appName) return false;
11395
- const result = (0, import_child_process6.execSync)(`pgrep -f "${appName}" 2>/dev/null`, { encoding: "utf-8" });
11396
- return result.trim().length > 0;
12230
+ try {
12231
+ const result = (0, import_child_process6.execSync)(`pgrep -x "${appName}" 2>/dev/null`, {
12232
+ encoding: "utf-8",
12233
+ timeout: 3e3
12234
+ });
12235
+ return result.trim().length > 0;
12236
+ } catch {
12237
+ const result = (0, import_child_process6.execSync)(
12238
+ `osascript -e 'tell application "System Events" to count (every process whose name is "${escapeForAppleScript(appName)}")'`,
12239
+ {
12240
+ encoding: "utf-8",
12241
+ timeout: 3e3,
12242
+ stdio: ["pipe", "pipe", "pipe"]
12243
+ }
12244
+ );
12245
+ return Number.parseInt(result.trim() || "0", 10) > 0;
12246
+ }
11397
12247
  } else if (plat === "win32") {
11398
12248
  const winProcesses = getWinProcessNames()[ideId];
11399
12249
  if (!winProcesses) return false;
@@ -11542,7 +12392,8 @@ async function launchWithCdp(options = {}) {
11542
12392
  await launchLinux(targetIde, port, workspace, options.newWindow);
11543
12393
  }
11544
12394
  let cdpReady = false;
11545
- for (let i = 0; i < 30; i++) {
12395
+ const waitDeadline = Date.now() + getCdpStartupTimeoutMs(targetIde.id);
12396
+ while (Date.now() < waitDeadline) {
11546
12397
  await new Promise((r) => setTimeout(r, 500));
11547
12398
  if (await isCdpActive(port)) {
11548
12399
  cdpReady = true;
@@ -11571,14 +12422,18 @@ async function launchWithCdp(options = {}) {
11571
12422
  }
11572
12423
  async function launchMacOS(ide, port, workspace, newWindow) {
11573
12424
  const appName = getMacAppIdentifiers()[ide.id];
12425
+ const preferredMethod = getPreferredLaunchMethod(ide.id, "darwin");
11574
12426
  const args = ["--remote-debugging-port=" + port];
11575
12427
  if (newWindow) args.push("--new-window");
11576
12428
  if (workspace) args.push(workspace);
11577
- if (appName) {
12429
+ const canUseCli = !!ide.cliCommand;
12430
+ const canUseAppLauncher = !!appName;
12431
+ const useAppLauncher = preferredMethod === "app" ? canUseAppLauncher : preferredMethod === "cli" ? false : !canUseCli && canUseAppLauncher;
12432
+ if (!useAppLauncher && ide.cliCommand) {
12433
+ (0, import_child_process6.spawn)(ide.cliCommand, args, { detached: true, stdio: "ignore" }).unref();
12434
+ } else if (appName) {
11578
12435
  const openArgs = ["-a", appName, "--args", ...args];
11579
12436
  (0, import_child_process6.spawn)("open", openArgs, { detached: true, stdio: "ignore" }).unref();
11580
- } else if (ide.cliCommand) {
11581
- (0, import_child_process6.spawn)(ide.cliCommand, args, { detached: true, stdio: "ignore" }).unref();
11582
12437
  } else {
11583
12438
  throw new Error(`No app identifier or CLI for ${ide.displayName}`);
11584
12439
  }
@@ -11889,6 +12744,7 @@ function buildStatusSnapshot(options) {
11889
12744
  workspaces: wsState.workspaces,
11890
12745
  defaultWorkspaceId: wsState.defaultWorkspaceId,
11891
12746
  defaultWorkspacePath: wsState.defaultWorkspacePath,
12747
+ terminalSizingMode: cfg.terminalSizingMode || "measured",
11892
12748
  recentLaunches: buildRecentLaunches(recentActivity),
11893
12749
  terminalBackend,
11894
12750
  availableProviders: buildAvailableProviders(options.providerLoader)
@@ -12736,19 +13592,10 @@ var ProviderStreamAdapter = class {
12736
13592
  mode: data.mode,
12737
13593
  activeModal: data.activeModal
12738
13594
  };
12739
- if (this.provider.controls?.length) {
12740
- const cv = {};
12741
- for (const ctrl of this.provider.controls) {
12742
- if (!ctrl.readFrom) continue;
12743
- const val = data[ctrl.readFrom];
12744
- if (val !== void 0 && val !== null) {
12745
- cv[ctrl.id] = typeof val === "object" ? val.name || val.id || String(val) : val;
12746
- }
12747
- }
12748
- if (data.model && !cv["model"]) cv["model"] = data.model;
12749
- if (data.mode && !cv["mode"]) cv["mode"] = data.mode;
12750
- if (Object.keys(cv).length > 0) state.controlValues = cv;
12751
- }
13595
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
13596
+ if (controlValues) state.controlValues = controlValues;
13597
+ const effects = normalizeProviderEffects(data);
13598
+ if (effects.length > 0) state.effects = effects;
12752
13599
  if (state.messages.length > 0) {
12753
13600
  this.lastSuccessState = state;
12754
13601
  }
@@ -13274,6 +14121,8 @@ function forwardAgentStreamsToIdeInstance(instanceManager, ideType, streams) {
13274
14121
  activeModal: stream.activeModal || null,
13275
14122
  model: stream.model || void 0,
13276
14123
  mode: stream.mode || void 0,
14124
+ controlValues: stream.controlValues || void 0,
14125
+ effects: stream.effects || void 0,
13277
14126
  sessionId: stream.sessionId || stream.instanceId || void 0,
13278
14127
  title: stream.title || stream.agentName || void 0,
13279
14128
  agentType: stream.agentType || void 0,