@adhdev/daemon-core 0.8.22 → 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 (35) 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 +1162 -307
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +1162 -307
  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/handler.ts +1 -0
  24. package/src/commands/stream-commands.ts +99 -8
  25. package/src/config/config.d.ts +1 -0
  26. package/src/config/config.ts +9 -0
  27. package/src/launch.ts +57 -11
  28. package/src/providers/cli-provider-instance.ts +148 -2
  29. package/src/providers/contracts.ts +65 -2
  30. package/src/providers/control-effects.ts +114 -0
  31. package/src/providers/extension-provider-instance.ts +163 -3
  32. package/src/providers/ide-provider-instance.ts +181 -2
  33. package/src/shared-types.d.ts +1 -0
  34. package/src/shared-types.ts +2 -1
  35. 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);
4904
+ return effects;
4905
+ }
4906
+ function normalizeControlValue(value) {
4907
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4908
+ return value;
4743
4909
  }
4744
- flushEvents() {
4745
- const events = [...this.events];
4746
- this.events = [];
4747
- return events;
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;
4748
4914
  }
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;
4752
- }
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();
4776
- }
4777
- };
4915
+ return String(value);
4916
+ }
4778
4917
 
4779
4918
  // src/config/chat-history.ts
4780
4919
  var fs3 = __toESM(require("fs"));
@@ -5001,54 +5140,383 @@ function listSavedHistorySessions(agentType, options = {}) {
5001
5140
  files.push(file);
5002
5141
  groupedFiles.set(historySessionId, files);
5003
5142
  }
5004
- const summaries = [];
5005
- for (const [historySessionId, files] of groupedFiles.entries()) {
5006
- let messageCount = 0;
5007
- let firstMessageAt = 0;
5008
- let lastMessageAt = 0;
5009
- let sessionTitle = "";
5010
- let preview = "";
5011
- for (const file of files.sort()) {
5012
- const filePath = path5.join(dir, file);
5013
- const content = fs3.readFileSync(filePath, "utf-8");
5014
- const lines = content.split("\n").filter(Boolean);
5015
- for (const line of lines) {
5016
- let parsed = null;
5017
- try {
5018
- parsed = JSON.parse(line);
5019
- } catch {
5020
- parsed = null;
5021
- }
5022
- if (!parsed || parsed.historySessionId !== historySessionId) continue;
5023
- messageCount += 1;
5024
- if (!firstMessageAt || parsed.receivedAt < firstMessageAt) firstMessageAt = parsed.receivedAt;
5025
- if (!lastMessageAt || parsed.receivedAt > lastMessageAt) lastMessageAt = parsed.receivedAt;
5026
- if (parsed.sessionTitle) sessionTitle = parsed.sessionTitle;
5027
- if (parsed.role !== "system" && parsed.content.trim()) preview = parsed.content.trim();
5028
- }
5143
+ const summaries = [];
5144
+ for (const [historySessionId, files] of groupedFiles.entries()) {
5145
+ let messageCount = 0;
5146
+ let firstMessageAt = 0;
5147
+ let lastMessageAt = 0;
5148
+ let sessionTitle = "";
5149
+ let preview = "";
5150
+ for (const file of files.sort()) {
5151
+ const filePath = path5.join(dir, file);
5152
+ const content = fs3.readFileSync(filePath, "utf-8");
5153
+ const lines = content.split("\n").filter(Boolean);
5154
+ for (const line of lines) {
5155
+ let parsed = null;
5156
+ try {
5157
+ parsed = JSON.parse(line);
5158
+ } catch {
5159
+ parsed = null;
5160
+ }
5161
+ if (!parsed || parsed.historySessionId !== historySessionId) continue;
5162
+ messageCount += 1;
5163
+ if (!firstMessageAt || parsed.receivedAt < firstMessageAt) firstMessageAt = parsed.receivedAt;
5164
+ if (!lastMessageAt || parsed.receivedAt > lastMessageAt) lastMessageAt = parsed.receivedAt;
5165
+ if (parsed.sessionTitle) sessionTitle = parsed.sessionTitle;
5166
+ if (parsed.role !== "system" && parsed.content.trim()) preview = parsed.content.trim();
5167
+ }
5168
+ }
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();
5029
5471
  }
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
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 = [];
@@ -7444,7 +8066,56 @@ function handleSetProviderSetting(h, args) {
7444
8066
  }
7445
8067
  return { success: false, error: `Failed to set ${providerType}.${key} \u2014 invalid key, value, or not a public setting` };
7446
8068
  }
7447
- 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) {
7448
8119
  const { agentType, ideType } = args || {};
7449
8120
  if (!agentType) return { success: false, error: "agentType is required" };
7450
8121
  const loader = h.ctx.providerLoader;
@@ -7457,13 +8128,29 @@ async function handleExtensionScript(h, args, scriptName) {
7457
8128
  if (!provider.scripts?.[actualScriptName]) {
7458
8129
  return { success: false, error: `Script '${actualScriptName}' not available for ${agentType}` };
7459
8130
  }
7460
- const scriptFn = provider.scripts[actualScriptName];
7461
- const normalizedArgs = { ...args };
7462
- for (const key of ["mode", "model", "message", "action", "button", "text", "sessionId"]) {
7463
- if (key in normalizedArgs && !(key.toUpperCase() in normalizedArgs)) {
7464
- 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}` };
7465
8151
  }
7466
8152
  }
8153
+ const scriptFn = provider.scripts[actualScriptName];
7467
8154
  const scriptCode = scriptFn(normalizedArgs);
7468
8155
  if (!scriptCode) return { success: false, error: `Script '${actualScriptName}' returned null` };
7469
8156
  const cdpKey = provider.category === "ide" ? h.currentSession?.cdpManagerKey || h.currentManagerKey || agentType : h.currentSession?.cdpManagerKey || h.currentManagerKey || ideType;
@@ -7517,16 +8204,29 @@ async function handleExtensionScript(h, args, scriptName) {
7517
8204
  if (typeof result === "string") {
7518
8205
  try {
7519
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
+ }
7520
8211
  return { success: true, ...parsed };
7521
8212
  } catch {
7522
8213
  return { success: true, result };
7523
8214
  }
7524
8215
  }
8216
+ applyProviderPatch(h, args, result);
7525
8217
  return { success: true, result };
7526
8218
  } catch (e) {
7527
8219
  return { success: false, error: `Script execution failed: ${e.message}` };
7528
8220
  }
7529
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
+ }
7530
8230
  function handleGetIdeExtensions(h, args) {
7531
8231
  const { ideType } = args || {};
7532
8232
  const loader = h.ctx.providerLoader;
@@ -8026,6 +8726,8 @@ var DaemonCommandHandler = class {
8026
8726
  case "set_ide_extension":
8027
8727
  return handleSetIdeExtension(this, args);
8028
8728
  // ─── Extension Model / Mode Control (stream-commands.ts) ──────────
8729
+ case "invoke_provider_script":
8730
+ return handleProviderScript(this, args);
8029
8731
  case "list_extension_models":
8030
8732
  return handleExtensionScript(this, args, "listModels");
8031
8733
  case "set_extension_model":
@@ -8202,6 +8904,8 @@ var CliProviderInstance = class {
8202
8904
  generatingDebounceTimer = null;
8203
8905
  generatingDebouncePending = null;
8204
8906
  lastApprovalEventAt = 0;
8907
+ controlValues = {};
8908
+ appliedEffectKeys = /* @__PURE__ */ new Set();
8205
8909
  historyWriter;
8206
8910
  runtimeMessages = [];
8207
8911
  instanceId;
@@ -8214,6 +8918,7 @@ var CliProviderInstance = class {
8214
8918
  async init(context) {
8215
8919
  this.context = context;
8216
8920
  this.settings = context.settings || {};
8921
+ this.adapter.updateRuntimeSettings?.(this.settings);
8217
8922
  this.monitor.updateConfig({
8218
8923
  approvalAlert: this.settings.approvalAlert !== false,
8219
8924
  longGeneratingAlert: this.settings.longGeneratingAlert !== false,
@@ -8229,6 +8934,21 @@ var CliProviderInstance = class {
8229
8934
  this.detectStatusTransition();
8230
8935
  });
8231
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
+ }
8232
8952
  if (this.providerSessionId && this.launchMode === "resume") {
8233
8953
  const resumedAt = Date.now();
8234
8954
  this.historyWriter.appendSystemMarker(
@@ -8309,6 +9029,12 @@ var CliProviderInstance = class {
8309
9029
  }
8310
9030
  const runtime = this.adapter.getRuntimeMetadata();
8311
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
+ }
8312
9038
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
8313
9039
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
8314
9040
  if (parsedMessages.length > 0) {
@@ -8329,6 +9055,7 @@ var CliProviderInstance = class {
8329
9055
  );
8330
9056
  }
8331
9057
  }
9058
+ this.applyProviderResponse(parsedStatus, { phase: "immediate" });
8332
9059
  return {
8333
9060
  type: this.type,
8334
9061
  name: this.provider.name,
@@ -8358,8 +9085,7 @@ var CliProviderInstance = class {
8358
9085
  attachedClients: runtime.attachedClients || []
8359
9086
  } : void 0,
8360
9087
  resume: this.provider.resume,
8361
- controlValues: void 0,
8362
- // CLI controls not yet wired from stream
9088
+ controlValues: this.controlValues,
8363
9089
  providerControls: this.provider.controls
8364
9090
  };
8365
9091
  }
@@ -8370,6 +9096,15 @@ var CliProviderInstance = class {
8370
9096
  getPresentationMode() {
8371
9097
  return this.presentationMode;
8372
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
+ }
8373
9108
  onEvent(event, data) {
8374
9109
  if (event === "send_message" && data?.text) {
8375
9110
  void this.adapter.sendMessage(data.text).catch((e) => {
@@ -8381,22 +9116,27 @@ var CliProviderInstance = class {
8381
9116
  void this.adapter.resolveAction(data).catch((e) => {
8382
9117
  LOG.warn("CLI", `[${this.type}] resolve_action failed: ${e?.message || e}`);
8383
9118
  });
9119
+ } else if (event === "provider_state_patch" && data && typeof data === "object") {
9120
+ this.applyProviderResponse(data, { phase: "immediate" });
8384
9121
  }
8385
9122
  }
8386
9123
  dispose() {
8387
9124
  this.adapter.shutdown();
8388
9125
  this.monitor.reset();
9126
+ this.appliedEffectKeys.clear();
8389
9127
  }
8390
9128
  completedDebounceTimer = null;
8391
9129
  completedDebouncePending = null;
8392
9130
  detectStatusTransition() {
8393
9131
  const now = Date.now();
8394
9132
  const adapterStatus = this.adapter.getStatus();
9133
+ const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
8395
9134
  const newStatus = adapterStatus.status;
8396
9135
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
8397
9136
  const chatTitle = `${this.provider.name} \xB7 ${dirName}`;
8398
9137
  const partial = this.adapter.getPartialResponse();
8399
9138
  const progressFingerprint = newStatus === "generating" ? `${partial || ""}::${adapterStatus.messages.at(-1)?.content || ""}`.slice(-2e3) : void 0;
9139
+ const previousStatus = this.lastStatus;
8400
9140
  if (newStatus !== this.lastStatus) {
8401
9141
  LOG.info("CLI", `[${this.type}] status: ${this.lastStatus} \u2192 ${newStatus}`);
8402
9142
  if (this.lastStatus === "idle" && newStatus === "generating") {
@@ -8489,6 +9229,9 @@ var CliProviderInstance = class {
8489
9229
  }
8490
9230
  this.lastStatus = newStatus;
8491
9231
  }
9232
+ this.applyProviderResponse(parsedStatus, {
9233
+ phase: newStatus === "idle" && (previousStatus === "generating" || previousStatus === "waiting_approval") ? "turn_completed" : "immediate"
9234
+ });
8492
9235
  const agentKey = `${this.type}:cli`;
8493
9236
  const monitorEvents = this.monitor.check(agentKey, newStatus, now, progressFingerprint);
8494
9237
  for (const me of monitorEvents) {
@@ -8504,6 +9247,88 @@ var CliProviderInstance = class {
8504
9247
  this.events = [];
8505
9248
  return events;
8506
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
+ }
8507
9332
  // ─── Adapter access (backward compat) ──────────────────
8508
9333
  getAdapter() {
8509
9334
  return this.adapter;
@@ -11276,6 +12101,22 @@ function getMacAppIdentifiers() {
11276
12101
  function getWinProcessNames() {
11277
12102
  return getProviderLoader().getWinProcessNames();
11278
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
+ }
11279
12120
  async function findFreePort(ports) {
11280
12121
  for (const port2 of ports) {
11281
12122
  const free = await checkPortFree(port2);
@@ -11328,12 +12169,12 @@ async function killIdeProcess(ideId) {
11328
12169
  try {
11329
12170
  if (plat === "darwin" && appName) {
11330
12171
  try {
11331
- (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`, {
11332
12173
  timeout: 5e3
11333
12174
  });
11334
12175
  } catch {
11335
12176
  try {
11336
- (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 });
11337
12178
  } catch {
11338
12179
  }
11339
12180
  }
@@ -11363,7 +12204,7 @@ async function killIdeProcess(ideId) {
11363
12204
  }
11364
12205
  if (plat === "darwin" && appName) {
11365
12206
  try {
11366
- (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 });
11367
12208
  } catch {
11368
12209
  }
11369
12210
  } else if (plat === "win32" && winProcesses) {
@@ -11386,8 +12227,23 @@ function isIdeRunning(ideId) {
11386
12227
  if (plat === "darwin") {
11387
12228
  const appName = getMacAppIdentifiers()[ideId];
11388
12229
  if (!appName) return false;
11389
- const result = (0, import_child_process6.execSync)(`pgrep -f "${appName}" 2>/dev/null`, { encoding: "utf-8" });
11390
- 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
+ }
11391
12247
  } else if (plat === "win32") {
11392
12248
  const winProcesses = getWinProcessNames()[ideId];
11393
12249
  if (!winProcesses) return false;
@@ -11536,7 +12392,8 @@ async function launchWithCdp(options = {}) {
11536
12392
  await launchLinux(targetIde, port, workspace, options.newWindow);
11537
12393
  }
11538
12394
  let cdpReady = false;
11539
- for (let i = 0; i < 30; i++) {
12395
+ const waitDeadline = Date.now() + getCdpStartupTimeoutMs(targetIde.id);
12396
+ while (Date.now() < waitDeadline) {
11540
12397
  await new Promise((r) => setTimeout(r, 500));
11541
12398
  if (await isCdpActive(port)) {
11542
12399
  cdpReady = true;
@@ -11565,14 +12422,18 @@ async function launchWithCdp(options = {}) {
11565
12422
  }
11566
12423
  async function launchMacOS(ide, port, workspace, newWindow) {
11567
12424
  const appName = getMacAppIdentifiers()[ide.id];
12425
+ const preferredMethod = getPreferredLaunchMethod(ide.id, "darwin");
11568
12426
  const args = ["--remote-debugging-port=" + port];
11569
12427
  if (newWindow) args.push("--new-window");
11570
12428
  if (workspace) args.push(workspace);
11571
- 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) {
11572
12435
  const openArgs = ["-a", appName, "--args", ...args];
11573
12436
  (0, import_child_process6.spawn)("open", openArgs, { detached: true, stdio: "ignore" }).unref();
11574
- } else if (ide.cliCommand) {
11575
- (0, import_child_process6.spawn)(ide.cliCommand, args, { detached: true, stdio: "ignore" }).unref();
11576
12437
  } else {
11577
12438
  throw new Error(`No app identifier or CLI for ${ide.displayName}`);
11578
12439
  }
@@ -11883,6 +12744,7 @@ function buildStatusSnapshot(options) {
11883
12744
  workspaces: wsState.workspaces,
11884
12745
  defaultWorkspaceId: wsState.defaultWorkspaceId,
11885
12746
  defaultWorkspacePath: wsState.defaultWorkspacePath,
12747
+ terminalSizingMode: cfg.terminalSizingMode || "measured",
11886
12748
  recentLaunches: buildRecentLaunches(recentActivity),
11887
12749
  terminalBackend,
11888
12750
  availableProviders: buildAvailableProviders(options.providerLoader)
@@ -12730,19 +13592,10 @@ var ProviderStreamAdapter = class {
12730
13592
  mode: data.mode,
12731
13593
  activeModal: data.activeModal
12732
13594
  };
12733
- if (this.provider.controls?.length) {
12734
- const cv = {};
12735
- for (const ctrl of this.provider.controls) {
12736
- if (!ctrl.readFrom) continue;
12737
- const val = data[ctrl.readFrom];
12738
- if (val !== void 0 && val !== null) {
12739
- cv[ctrl.id] = typeof val === "object" ? val.name || val.id || String(val) : val;
12740
- }
12741
- }
12742
- if (data.model && !cv["model"]) cv["model"] = data.model;
12743
- if (data.mode && !cv["mode"]) cv["mode"] = data.mode;
12744
- if (Object.keys(cv).length > 0) state.controlValues = cv;
12745
- }
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;
12746
13599
  if (state.messages.length > 0) {
12747
13600
  this.lastSuccessState = state;
12748
13601
  }
@@ -13268,6 +14121,8 @@ function forwardAgentStreamsToIdeInstance(instanceManager, ideType, streams) {
13268
14121
  activeModal: stream.activeModal || null,
13269
14122
  model: stream.model || void 0,
13270
14123
  mode: stream.mode || void 0,
14124
+ controlValues: stream.controlValues || void 0,
14125
+ effects: stream.effects || void 0,
13271
14126
  sessionId: stream.sessionId || stream.instanceId || void 0,
13272
14127
  title: stream.title || stream.agentName || void 0,
13273
14128
  agentType: stream.agentType || void 0,