@adhdev/daemon-core 0.8.21 → 0.8.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/agent-stream/types.d.ts +3 -0
  2. package/dist/cli-adapter-types.d.ts +3 -0
  3. package/dist/cli-adapters/provider-cli-adapter.d.ts +71 -11
  4. package/dist/commands/stream-commands.d.ts +1 -0
  5. package/dist/config/config.d.ts +6 -0
  6. package/dist/index.js +1163 -314
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +1163 -314
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/providers/cli-provider-instance.d.ts +6 -0
  11. package/dist/providers/contracts.d.ts +59 -1
  12. package/dist/providers/control-effects.d.ts +4 -0
  13. package/dist/providers/extension-provider-instance.d.ts +9 -0
  14. package/dist/providers/ide-provider-instance.d.ts +8 -0
  15. package/dist/shared-types.d.ts +2 -1
  16. package/node_modules/@adhdev/session-host-core/package.json +1 -1
  17. package/package.json +3 -2
  18. package/src/agent-stream/forward.ts +2 -0
  19. package/src/agent-stream/provider-adapter.ts +5 -15
  20. package/src/agent-stream/types.ts +4 -0
  21. package/src/cli-adapter-types.ts +3 -0
  22. package/src/cli-adapters/provider-cli-adapter.ts +399 -49
  23. package/src/commands/chat-commands.ts +33 -12
  24. package/src/commands/handler.ts +1 -0
  25. package/src/commands/stream-commands.ts +99 -8
  26. package/src/config/config.d.ts +1 -0
  27. package/src/config/config.ts +9 -0
  28. package/src/launch.ts +57 -11
  29. package/src/providers/cli-provider-instance.ts +148 -2
  30. package/src/providers/contracts.ts +65 -2
  31. package/src/providers/control-effects.ts +114 -0
  32. package/src/providers/extension-provider-instance.ts +163 -3
  33. package/src/providers/ide-provider-instance.ts +181 -2
  34. package/src/shared-types.d.ts +1 -0
  35. package/src/shared-types.ts +2 -1
  36. package/src/status/snapshot.ts +1 -0
package/dist/index.mjs CHANGED
@@ -79,7 +79,8 @@ function normalizeConfig(raw) {
79
79
  providerSettings: isPlainObject(parsed.providerSettings) ? parsed.providerSettings : {},
80
80
  ideSettings: isPlainObject(parsed.ideSettings) ? parsed.ideSettings : {},
81
81
  disableUpstream: asBoolean(parsed.disableUpstream, DEFAULT_CONFIG.disableUpstream ?? false),
82
- providerDir: asOptionalString(parsed.providerDir)
82
+ providerDir: asOptionalString(parsed.providerDir),
83
+ terminalSizingMode: parsed.terminalSizingMode === "fit" ? "fit" : "measured"
83
84
  };
84
85
  }
85
86
  function generateMachineId() {
@@ -223,7 +224,8 @@ var init_config = __esm({
223
224
  registeredMachineId: void 0,
224
225
  providerSettings: {},
225
226
  ideSettings: {},
226
- disableUpstream: false
227
+ disableUpstream: false,
228
+ terminalSizingMode: "measured"
227
229
  };
228
230
  MACHINE_ID_PREFIX = "mach_";
229
231
  }
@@ -811,6 +813,53 @@ function stripTerminalNoise(str) {
811
813
  function sanitizeTerminalText(str) {
812
814
  return stripTerminalNoise(stripAnsi(str));
813
815
  }
816
+ function splitCliScreenLines(text) {
817
+ return String(text || "").replace(/\u0007/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n").map((line) => line.replace(/\s+$/, ""));
818
+ }
819
+ function isPromptLikeCliLine(line) {
820
+ const trimmed = String(line || "").trim();
821
+ if (!trimmed) return false;
822
+ return /^[❯›>]\s*(?:$|\S.*)$/.test(trimmed);
823
+ }
824
+ function buildCliScreenSnapshot(text) {
825
+ const normalizedText = String(text || "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
826
+ const rawLines = splitCliScreenLines(normalizedText);
827
+ const lines = rawLines.map((line, index, arr) => {
828
+ const trimmed = String(line || "").trim();
829
+ return {
830
+ index,
831
+ fromTop: index,
832
+ fromBottom: arr.length - index - 1,
833
+ text: line,
834
+ trimmed,
835
+ isEmpty: trimmed.length === 0
836
+ };
837
+ });
838
+ const nonEmptyLines = lines.filter((line) => !line.isEmpty);
839
+ const firstNonEmptyLine = nonEmptyLines[0] ?? null;
840
+ const lastNonEmptyLine = nonEmptyLines[nonEmptyLines.length - 1] ?? null;
841
+ let promptLineIndex = -1;
842
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
843
+ if (isPromptLikeCliLine(lines[i].text)) {
844
+ promptLineIndex = i;
845
+ break;
846
+ }
847
+ }
848
+ return {
849
+ text: normalizedText,
850
+ lineCount: lines.length,
851
+ lines,
852
+ nonEmptyLines,
853
+ firstNonEmptyLineIndex: firstNonEmptyLine?.index ?? -1,
854
+ lastNonEmptyLineIndex: lastNonEmptyLine?.index ?? -1,
855
+ firstNonEmptyLine,
856
+ lastNonEmptyLine,
857
+ promptLineIndex,
858
+ promptLine: promptLineIndex >= 0 ? lines[promptLineIndex] : null,
859
+ linesAbovePrompt: promptLineIndex >= 0 ? lines.slice(0, promptLineIndex) : [...lines],
860
+ linesBelowPrompt: promptLineIndex >= 0 ? lines.slice(promptLineIndex + 1) : []
861
+ };
862
+ }
814
863
  function computeTerminalQueryTail(buffer) {
815
864
  const prefixes = ["\x1B[6n", "\x1B[?6n"];
816
865
  const maxLength = prefixes.reduce((n, value) => Math.max(n, value.length), 0) - 1;
@@ -1049,7 +1098,9 @@ var init_provider_cli_adapter = __esm({
1049
1098
  ready = false;
1050
1099
  startupBuffer = "";
1051
1100
  startupParseGate = false;
1101
+ startupSettleTimer = null;
1052
1102
  spawnAt = 0;
1103
+ startupFirstOutputAt = 0;
1053
1104
  // PTY I/O
1054
1105
  onPtyDataCallback = null;
1055
1106
  pendingOutputParseBuffer = "";
@@ -1090,6 +1141,7 @@ var init_provider_cli_adapter = __esm({
1090
1141
  statusHistory = [];
1091
1142
  // ─── CLI Scripts (script-based parsing) ───
1092
1143
  cliScripts;
1144
+ runtimeSettings = {};
1093
1145
  /** Full accumulated ANSI-stripped PTY output */
1094
1146
  accumulatedBuffer = "";
1095
1147
  /** Full accumulated raw PTY output (with ANSI) */
@@ -1104,14 +1156,15 @@ var init_provider_cli_adapter = __esm({
1104
1156
  traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
1105
1157
  static MAX_TRACE_ENTRIES = 250;
1106
1158
  providerResolutionMeta;
1107
- static IDLE_FINISH_CONFIRM_MS = 900;
1159
+ static IDLE_FINISH_CONFIRM_MS = 2e3;
1160
+ static STATUS_ACTIVITY_HOLD_MS = 2e3;
1108
1161
  static FINISH_RETRY_DELAY_MS = 300;
1109
1162
  static MAX_FINISH_RETRIES = 2;
1110
1163
  syncMessageViews() {
1111
1164
  this.messages = [...this.committedMessages];
1112
1165
  this.structuredMessages = [...this.committedMessages];
1113
1166
  }
1114
- normalizeParsedMessages(parsedMessages) {
1167
+ hydrateParsedMessages(parsedMessages, scope) {
1115
1168
  const referenceMessages = [...this.committedMessages];
1116
1169
  const usedReferenceIndexes = /* @__PURE__ */ new Set();
1117
1170
  const now = Date.now();
@@ -1144,13 +1197,30 @@ var init_provider_cli_adapter = __esm({
1144
1197
  const content = typeof message.content === "string" ? message.content : String(message.content || "");
1145
1198
  const parsedTimestamp = typeof message.timestamp === "number" && Number.isFinite(message.timestamp) ? message.timestamp : void 0;
1146
1199
  const referenceTimestamp = parsedTimestamp ?? findReferenceTimestamp(role, content, index);
1200
+ const fallbackTimestamp = role === "user" ? scope?.startedAt || now : this.lastOutputAt || scope?.startedAt || now;
1201
+ const timestamp = referenceTimestamp ?? fallbackTimestamp;
1147
1202
  return {
1203
+ ...message,
1148
1204
  role,
1149
1205
  content,
1150
- timestamp: referenceTimestamp ?? now
1206
+ timestamp,
1207
+ receivedAt: typeof message.receivedAt === "number" && Number.isFinite(message.receivedAt) ? message.receivedAt : timestamp
1151
1208
  };
1152
1209
  });
1153
1210
  }
1211
+ normalizeParsedMessages(parsedMessages, scope) {
1212
+ return this.hydrateParsedMessages(parsedMessages, scope).map((message) => ({
1213
+ role: message.role,
1214
+ content: message.content,
1215
+ timestamp: message.timestamp,
1216
+ receivedAt: message.receivedAt,
1217
+ kind: message.kind,
1218
+ id: message.id,
1219
+ index: message.index,
1220
+ meta: message.meta,
1221
+ senderName: message.senderName
1222
+ }));
1223
+ }
1154
1224
  sliceFromOffset(text, start) {
1155
1225
  if (!text) return "";
1156
1226
  if (!Number.isFinite(start) || start <= 0) return text;
@@ -1160,14 +1230,20 @@ var init_provider_cli_adapter = __esm({
1160
1230
  buildParseInput(baseMessages, partialResponse, scope) {
1161
1231
  const buffer = scope ? this.sliceFromOffset(this.accumulatedBuffer, scope.bufferStart) || this.accumulatedBuffer : this.accumulatedBuffer;
1162
1232
  const rawBuffer = scope ? this.sliceFromOffset(this.accumulatedRawBuffer, scope.rawBufferStart) || this.accumulatedRawBuffer : this.accumulatedRawBuffer;
1233
+ const screenText = this.terminalScreen.getText();
1234
+ const recentBuffer = buffer.slice(-1e3) || this.recentOutputBuffer;
1163
1235
  return {
1164
1236
  buffer,
1165
1237
  rawBuffer,
1166
- recentBuffer: buffer.slice(-1e3) || this.recentOutputBuffer,
1167
- screenText: this.terminalScreen.getText(),
1238
+ recentBuffer,
1239
+ screenText,
1240
+ screen: buildCliScreenSnapshot(screenText),
1241
+ bufferScreen: buildCliScreenSnapshot(buffer),
1242
+ recentScreen: buildCliScreenSnapshot(recentBuffer),
1168
1243
  messages: [...baseMessages],
1169
1244
  partialResponse,
1170
- promptText: scope?.prompt || ""
1245
+ promptText: scope?.prompt || "",
1246
+ settings: { ...this.runtimeSettings }
1171
1247
  };
1172
1248
  }
1173
1249
  setStatus(status, trigger) {
@@ -1273,6 +1349,9 @@ var init_provider_cli_adapter = __esm({
1273
1349
  const scriptNames = Object.keys(scripts).filter((k) => typeof scripts[k] === "function");
1274
1350
  LOG.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
1275
1351
  }
1352
+ updateRuntimeSettings(settings) {
1353
+ this.runtimeSettings = { ...settings };
1354
+ }
1276
1355
  // ─── Lifecycle ─────────────────────────────────
1277
1356
  setServerConn(serverConn) {
1278
1357
  this.serverConn = serverConn;
@@ -1309,7 +1388,7 @@ var init_provider_cli_adapter = __esm({
1309
1388
  let shellArgs;
1310
1389
  const useShellUnix = !isWin && (!!spawnConfig.shell || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath) || !looksLikeMachOOrElf(binaryPath));
1311
1390
  const isCmdShim = isWin && /\.(cmd|bat)$/i.test(binaryPath);
1312
- const useShellWin = isCmdShim || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath);
1391
+ const useShellWin = !!spawnConfig.shell || isCmdShim || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath);
1313
1392
  const useShell = isWin ? useShellWin : useShellUnix;
1314
1393
  if (useShell) {
1315
1394
  if (!spawnConfig.shell && !isWin) {
@@ -1407,6 +1486,11 @@ var init_provider_cli_adapter = __esm({
1407
1486
  this.spawnAt = Date.now();
1408
1487
  this.startupParseGate = true;
1409
1488
  this.startupBuffer = "";
1489
+ this.startupFirstOutputAt = 0;
1490
+ if (this.startupSettleTimer) {
1491
+ clearTimeout(this.startupSettleTimer);
1492
+ this.startupSettleTimer = null;
1493
+ }
1410
1494
  this.terminalScreen.reset(24, 80);
1411
1495
  this.pendingTerminalQueryTail = "";
1412
1496
  this.currentTurnScope = null;
@@ -1420,7 +1504,8 @@ var init_provider_cli_adapter = __esm({
1420
1504
  this.recordTrace("ready", {
1421
1505
  runtimeMeta: this.getRuntimeMetadata()
1422
1506
  });
1423
- this.setStatus("idle", "pty_ready");
1507
+ this.setStatus("starting", "pty_ready");
1508
+ this.scheduleStartupSettleCheck();
1424
1509
  this.onStatusChange?.();
1425
1510
  }
1426
1511
  // ─── Output Handling ────────────────────────────
@@ -1435,6 +1520,9 @@ var init_provider_cli_adapter = __esm({
1435
1520
  this.lastScreenSnapshot = normalizedScreenSnapshot;
1436
1521
  this.lastScreenChangeAt = now;
1437
1522
  }
1523
+ if (this.startupParseGate && !this.startupFirstOutputAt && (cleanData.trim() || normalizedScreenSnapshot.trim())) {
1524
+ this.startupFirstOutputAt = now;
1525
+ }
1438
1526
  if (this.idleFinishCandidate && (rawData.length > 0 || cleanData.length > 0)) {
1439
1527
  this.clearIdleFinishCandidate("new_output");
1440
1528
  }
@@ -1445,6 +1533,9 @@ var init_provider_cli_adapter = __esm({
1445
1533
  cleanPreview: this.summarizeTraceText(cleanData, 300),
1446
1534
  screenText: this.summarizeTraceText(this.terminalScreen.getText(), 1200)
1447
1535
  });
1536
+ if (this.startupParseGate) {
1537
+ this.scheduleStartupSettleCheck();
1538
+ }
1448
1539
  if (this.isWaitingForResponse && cleanData) {
1449
1540
  this.responseBuffer = (this.responseBuffer + cleanData).slice(-8e3);
1450
1541
  }
@@ -1458,27 +1549,51 @@ var init_provider_cli_adapter = __esm({
1458
1549
  this.recentOutputBuffer = (this.recentOutputBuffer + cleanData).slice(-1e3);
1459
1550
  this.accumulatedBuffer = (this.accumulatedBuffer + cleanData).slice(-_ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
1460
1551
  this.accumulatedRawBuffer = (this.accumulatedRawBuffer + rawData).slice(-_ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
1461
- if (this.startupParseGate) {
1462
- this.startupBuffer += cleanData;
1463
- const elapsed = Date.now() - this.spawnAt;
1464
- const screenText = this.terminalScreen.getText() || "";
1465
- const startupModal = this.getStartupConfirmationModal(screenText);
1466
- const scriptStatus = startupModal ? "waiting_approval" : this.runDetectStatus(this.startupBuffer);
1467
- const hasInteractivePrompt = this.looksLikeVisibleIdlePrompt(screenText);
1468
- const startupStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
1469
- const isReady = (scriptStatus === "idle" || scriptStatus === "waiting_approval") && hasInteractivePrompt && startupStableMs >= 700 || !!startupModal && startupStableMs >= 700 || elapsed > 8e3 || this.startupBuffer.length > 12e3;
1470
- if (isReady) {
1471
- this.startupParseGate = false;
1472
- this.ready = true;
1473
- LOG.info(
1474
- "CLI",
1475
- `[${this.cliType}] Startup ready (${elapsed}ms, scriptStatus=${scriptStatus}, prompt=${hasInteractivePrompt}, stableMs=${startupStableMs}) providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"}`
1476
- );
1477
- this.onStatusChange?.();
1478
- }
1479
- }
1552
+ this.resolveStartupState("output");
1480
1553
  this.scheduleSettle();
1481
1554
  }
1555
+ resolveStartupState(trigger) {
1556
+ if (!this.startupParseGate) return;
1557
+ const now = Date.now();
1558
+ const screenText = this.terminalScreen.getText() || "";
1559
+ const normalizedScreen = normalizeScreenSnapshot(screenText);
1560
+ const hasStartupOutput = !!this.startupFirstOutputAt || !!normalizedScreen.trim();
1561
+ if (!hasStartupOutput) return;
1562
+ const stableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
1563
+ if (stableMs < 2e3) return;
1564
+ const startupModal = this.getStartupConfirmationModal(screenText);
1565
+ this.startupParseGate = false;
1566
+ if (this.startupSettleTimer) {
1567
+ clearTimeout(this.startupSettleTimer);
1568
+ this.startupSettleTimer = null;
1569
+ }
1570
+ this.ready = true;
1571
+ if (startupModal) {
1572
+ this.activeModal = startupModal;
1573
+ this.setStatus("waiting_approval", `startup_ready:${trigger}`);
1574
+ } else {
1575
+ this.setStatus("idle", `startup_ready:${trigger}`);
1576
+ }
1577
+ LOG.info(
1578
+ "CLI",
1579
+ `[${this.cliType}] Startup settled (${trigger}, stableMs=${stableMs}, modal=${!!startupModal}) providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"}`
1580
+ );
1581
+ this.onStatusChange?.();
1582
+ }
1583
+ scheduleStartupSettleCheck() {
1584
+ if (!this.startupParseGate) return;
1585
+ if (this.startupSettleTimer) clearTimeout(this.startupSettleTimer);
1586
+ const now = Date.now();
1587
+ const stableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
1588
+ const delayMs = Math.max(250, 2050 - stableMs);
1589
+ this.startupSettleTimer = setTimeout(() => {
1590
+ this.startupSettleTimer = null;
1591
+ this.resolveStartupState("startup_timer");
1592
+ if (this.startupParseGate && Date.now() - this.spawnAt < 1e4) {
1593
+ this.scheduleStartupSettleCheck();
1594
+ }
1595
+ }, delayMs);
1596
+ }
1482
1597
  scheduleSettle() {
1483
1598
  if (this.settleTimer) clearTimeout(this.settleTimer);
1484
1599
  const settleEpoch = this.responseEpoch;
@@ -1520,6 +1635,43 @@ var init_provider_cli_adapter = __esm({
1520
1635
  if (!text.trim()) return false;
1521
1636
  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);
1522
1637
  }
1638
+ findLastMatchingLineIndex(lines, predicate) {
1639
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
1640
+ if (predicate(lines[index])) return index;
1641
+ }
1642
+ return -1;
1643
+ }
1644
+ looksLikeClaudeGeneratingLine(line) {
1645
+ const trimmed = String(line || "").trim();
1646
+ if (!trimmed) return false;
1647
+ if (/esc to (cancel|interrupt|stop)/i.test(trimmed)) return true;
1648
+ if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+\s+\S+.*\b(?:thinking|thought for \d+s?)\b/i.test(trimmed)) return true;
1649
+ if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+\s+[A-Z][A-Za-z-]{3,}ing\b.*(?:…|\.{3})/u.test(trimmed)) return true;
1650
+ if (/^[⏺•]\s+(?:Reading|Writing|Editing|Searching|Inspecting|Planning|Analyzing|Synthesizing|Drafting|Running|Listing|Scanning|Matching)\b.*(?:…|\.{3})/i.test(trimmed)) {
1651
+ 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);
1652
+ }
1653
+ return false;
1654
+ }
1655
+ detectClaudeGeneratingOverride(screenText, tail) {
1656
+ if (this.cliType !== "claude-cli") return false;
1657
+ const source = sanitizeTerminalText(screenText || tail || "");
1658
+ if (!source.trim()) return false;
1659
+ const allLines = source.split(/\r\n|\n|\r/g).map((line) => line.trim()).filter(Boolean);
1660
+ if (allLines.length === 0) return false;
1661
+ const recentLines = allLines.slice(-12);
1662
+ const promptIndex = this.findLastMatchingLineIndex(recentLines, (line) => /^[❯›>]\s*$/.test(line));
1663
+ const activeRegion = promptIndex >= 0 ? recentLines.slice(Math.max(0, promptIndex - 2), promptIndex) : recentLines;
1664
+ if (activeRegion.length === 0) return false;
1665
+ return activeRegion.some((line) => this.looksLikeClaudeGeneratingLine(line));
1666
+ }
1667
+ refineDetectedStatus(status, tail, screenText) {
1668
+ if (this.startupParseGate) {
1669
+ return this.getStartupConfirmationModal(screenText || "") ? "waiting_approval" : "starting";
1670
+ }
1671
+ if (status === "waiting_approval") return status;
1672
+ if (this.detectClaudeGeneratingOverride(screenText || "", tail)) return "generating";
1673
+ return status;
1674
+ }
1523
1675
  looksLikeVisibleAssistantCandidate(screenText) {
1524
1676
  const lines = sanitizeTerminalText(String(screenText || "")).split(/\r\n|\n|\r/g);
1525
1677
  for (const line of lines) {
@@ -1554,6 +1706,11 @@ var init_provider_cli_adapter = __esm({
1554
1706
  const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
1555
1707
  return quietForMs < 1200 || screenStableMs < 1200 || !commitResult.hasAssistant;
1556
1708
  }
1709
+ hasRecentInteractiveActivity(now) {
1710
+ const quietForMs = this.lastNonEmptyOutputAt ? now - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
1711
+ const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : Number.MAX_SAFE_INTEGER;
1712
+ return quietForMs < _ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS || screenStableMs < _ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS;
1713
+ }
1557
1714
  getStartupConfirmationModal(screenText) {
1558
1715
  const text = sanitizeTerminalText(String(screenText || ""));
1559
1716
  if (!text.trim()) return null;
@@ -1581,13 +1738,14 @@ var init_provider_cli_adapter = __esm({
1581
1738
  const startedAt = Date.now();
1582
1739
  let loggedWait = false;
1583
1740
  while (Date.now() - startedAt < maxWaitMs) {
1741
+ this.resolveStartupState("interactive_wait");
1584
1742
  const screenText = this.terminalScreen.getText() || "";
1585
1743
  const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
1586
1744
  const stableMs = this.lastScreenChangeAt ? Date.now() - this.lastScreenChangeAt : 0;
1587
1745
  const recentlyOutput = this.lastNonEmptyOutputAt ? Date.now() - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
1588
1746
  const status = this.runDetectStatus(this.recentOutputBuffer) || this.currentStatus;
1589
1747
  const startupLikelyActive = /Welcome back|Tips for getting|Recent activity|Claude Code v\d/i.test(screenText);
1590
- const interactiveReady = hasPrompt && stableMs >= 700 && recentlyOutput >= 350 && status !== "starting" && status !== "generating";
1748
+ const interactiveReady = hasPrompt && stableMs >= 700 && recentlyOutput >= 350 && status !== "generating";
1591
1749
  if (interactiveReady) {
1592
1750
  if (loggedWait) {
1593
1751
  LOG.info(
@@ -1626,6 +1784,10 @@ var init_provider_cli_adapter = __esm({
1626
1784
  }
1627
1785
  const tail = this.settledBuffer;
1628
1786
  const screenText = this.terminalScreen.getText() || "";
1787
+ this.resolveStartupState("settled");
1788
+ if (this.startupParseGate) {
1789
+ return;
1790
+ }
1629
1791
  const startupModal = this.getStartupConfirmationModal(screenText);
1630
1792
  const modal = this.runParseApproval(tail) || startupModal;
1631
1793
  const rawScriptStatus = this.runDetectStatus(tail);
@@ -1688,6 +1850,28 @@ var init_provider_cli_adapter = __esm({
1688
1850
  } else {
1689
1851
  clearPendingScriptStatus();
1690
1852
  }
1853
+ const recentInteractiveActivity = this.hasRecentInteractiveActivity(now);
1854
+ const shouldHoldGenerating = scriptStatus === "idle" && this.isWaitingForResponse && !modal && recentInteractiveActivity;
1855
+ if (shouldHoldGenerating) {
1856
+ this.clearIdleFinishCandidate("hold_generating_recent_activity");
1857
+ this.setStatus("generating", "recent_activity_hold");
1858
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
1859
+ this.idleTimeout = setTimeout(() => {
1860
+ if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
1861
+ this.finishResponse();
1862
+ }
1863
+ }, this.timeouts.generatingIdle);
1864
+ this.recordTrace("hold_generating_recent_activity", {
1865
+ scriptStatus,
1866
+ recentInteractiveActivity,
1867
+ lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
1868
+ lastScreenChangeAt: this.lastScreenChangeAt,
1869
+ holdMs: _ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS,
1870
+ ...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
1871
+ });
1872
+ this.onStatusChange?.();
1873
+ return;
1874
+ }
1691
1875
  if (scriptStatus === "waiting_approval") {
1692
1876
  this.clearIdleFinishCandidate("waiting_approval");
1693
1877
  const inCooldown = this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
@@ -1765,8 +1949,8 @@ var init_provider_cli_adapter = __esm({
1765
1949
  const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
1766
1950
  const hasAssistantTurn = !!lastParsedAssistant;
1767
1951
  const assistantLength = lastParsedAssistant?.content?.length || 0;
1768
- const idleQuietThresholdMs = Math.max(220, this.timeouts.outputSettle);
1769
- const idleStableThresholdMs = Math.max(120, Math.min(220, this.timeouts.outputSettle));
1952
+ const idleQuietThresholdMs = Math.max(2e3, this.timeouts.outputSettle);
1953
+ const idleStableThresholdMs = 2e3;
1770
1954
  const idleReady = visibleIdlePrompt && !modal && hasAssistantTurn && quietForMs >= idleQuietThresholdMs && screenStableMs >= idleStableThresholdMs;
1771
1955
  const candidate = this.idleFinishCandidate;
1772
1956
  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;
@@ -1880,7 +2064,7 @@ var init_provider_cli_adapter = __esm({
1880
2064
  this.currentTurnScope
1881
2065
  );
1882
2066
  if (parsed && Array.isArray(parsed.messages)) {
1883
- this.committedMessages = this.normalizeParsedMessages(parsed.messages);
2067
+ this.committedMessages = this.normalizeParsedMessages(parsed.messages, this.currentTurnScope);
1884
2068
  const promptForTrim = this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages);
1885
2069
  if (promptForTrim) {
1886
2070
  const lastAssistantForTrim = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
@@ -1917,11 +2101,15 @@ var init_provider_cli_adapter = __esm({
1917
2101
  runDetectStatus(text) {
1918
2102
  if (!this.cliScripts?.detectStatus) return null;
1919
2103
  try {
1920
- return this.cliScripts.detectStatus({
2104
+ const screenText = this.terminalScreen.getText();
2105
+ const status = this.cliScripts.detectStatus({
1921
2106
  tail: text.slice(-500),
1922
- screenText: this.terminalScreen.getText(),
1923
- rawBuffer: this.accumulatedRawBuffer
2107
+ screenText,
2108
+ rawBuffer: this.accumulatedRawBuffer,
2109
+ screen: buildCliScreenSnapshot(screenText),
2110
+ tailScreen: buildCliScreenSnapshot(text.slice(-500))
1924
2111
  });
2112
+ return this.refineDetectedStatus(status, text, screenText || "");
1925
2113
  } catch (e) {
1926
2114
  LOG.warn("CLI", `[${this.cliType}] detectStatus error: ${e.message}`);
1927
2115
  return null;
@@ -1930,11 +2118,16 @@ var init_provider_cli_adapter = __esm({
1930
2118
  runParseApproval(tail) {
1931
2119
  if (!this.cliScripts?.parseApproval) return null;
1932
2120
  try {
2121
+ const screenText = this.terminalScreen.getText();
2122
+ const buffer = screenText || this.accumulatedBuffer;
1933
2123
  return this.cliScripts.parseApproval({
1934
- buffer: this.terminalScreen.getText() || this.accumulatedBuffer,
1935
- screenText: this.terminalScreen.getText(),
2124
+ buffer,
2125
+ screenText,
1936
2126
  rawBuffer: this.accumulatedRawBuffer,
1937
- tail
2127
+ tail,
2128
+ screen: buildCliScreenSnapshot(screenText),
2129
+ bufferScreen: buildCliScreenSnapshot(buffer),
2130
+ tailScreen: buildCliScreenSnapshot(tail)
1938
2131
  });
1939
2132
  } catch (e) {
1940
2133
  LOG.warn("CLI", `[${this.cliType}] parseApproval error: ${e.message}`);
@@ -1950,6 +2143,21 @@ var init_provider_cli_adapter = __esm({
1950
2143
  activeModal: this.activeModal
1951
2144
  };
1952
2145
  }
2146
+ seedCommittedMessages(messages) {
2147
+ const normalized = (Array.isArray(messages) ? messages : []).filter((message) => message && (message.role === "user" || message.role === "assistant")).map((message) => ({
2148
+ role: message.role,
2149
+ content: typeof message.content === "string" ? message.content : String(message.content || ""),
2150
+ timestamp: typeof message.timestamp === "number" && Number.isFinite(message.timestamp) ? message.timestamp : void 0,
2151
+ receivedAt: typeof message.receivedAt === "number" && Number.isFinite(message.receivedAt) ? message.receivedAt : void 0,
2152
+ kind: typeof message.kind === "string" ? message.kind : void 0,
2153
+ id: typeof message.id === "string" ? message.id : void 0,
2154
+ index: typeof message.index === "number" ? message.index : void 0,
2155
+ meta: message.meta && typeof message.meta === "object" ? { ...message.meta } : void 0,
2156
+ senderName: typeof message.senderName === "string" ? message.senderName : void 0
2157
+ }));
2158
+ this.committedMessages = normalized;
2159
+ this.syncMessageViews();
2160
+ }
1953
2161
  /**
1954
2162
  * Script-based full parse — returns ReadChatResult.
1955
2163
  * Called by command handler / dashboard for rich content rendering.
@@ -1960,12 +2168,20 @@ var init_provider_cli_adapter = __esm({
1960
2168
  this.responseBuffer,
1961
2169
  this.currentTurnScope
1962
2170
  );
2171
+ const shouldPreferCommittedMessages = !this.currentTurnScope && this.currentStatus === "idle" && !this.activeModal;
1963
2172
  if (parsed && Array.isArray(parsed.messages)) {
2173
+ const hydratedMessages = shouldPreferCommittedMessages ? this.committedMessages.map((message, index) => ({
2174
+ ...message,
2175
+ id: message.id || `msg_${index}`,
2176
+ index: typeof message.index === "number" ? message.index : index,
2177
+ kind: message.kind || "standard",
2178
+ receivedAt: typeof message.receivedAt === "number" ? message.receivedAt : message.timestamp
2179
+ })) : this.hydrateParsedMessages(parsed.messages, this.currentTurnScope);
1964
2180
  return {
1965
2181
  id: parsed.id || "cli_session",
1966
2182
  status: parsed.status || this.currentStatus,
1967
2183
  title: parsed.title || this.cliName,
1968
- messages: parsed.messages,
2184
+ messages: hydratedMessages,
1969
2185
  activeModal: parsed.activeModal ?? this.activeModal,
1970
2186
  providerSessionId: typeof parsed.providerSessionId === "string" ? parsed.providerSessionId : void 0
1971
2187
  };
@@ -1986,11 +2202,30 @@ var init_provider_cli_adapter = __esm({
1986
2202
  activeModal: this.activeModal
1987
2203
  };
1988
2204
  }
2205
+ async invokeScript(scriptName, args) {
2206
+ const fn = this.cliScripts?.[scriptName];
2207
+ if (typeof fn !== "function") {
2208
+ throw new Error(`CLI script '${scriptName}' not available`);
2209
+ }
2210
+ const input = this.buildParseInput(
2211
+ this.committedMessages,
2212
+ this.responseBuffer,
2213
+ this.currentTurnScope
2214
+ );
2215
+ return await Promise.resolve(fn({
2216
+ ...input,
2217
+ args: args && typeof args === "object" ? { ...args } : {}
2218
+ }));
2219
+ }
1989
2220
  parseCurrentTranscript(baseMessages, partialResponse, scope) {
1990
2221
  if (!this.cliScripts?.parseOutput) return null;
1991
2222
  try {
1992
2223
  const input = this.buildParseInput(baseMessages, partialResponse, scope);
1993
2224
  const parsed = this.cliScripts.parseOutput(input);
2225
+ const refinedStatus = this.refineDetectedStatus(typeof parsed?.status === "string" ? parsed.status : null, input.recentBuffer, input.screenText);
2226
+ if (parsed && refinedStatus && parsed.status !== refinedStatus) {
2227
+ parsed.status = refinedStatus;
2228
+ }
1994
2229
  const promptForTrim = scope?.prompt || getLastUserPromptText(baseMessages);
1995
2230
  if (parsed && Array.isArray(parsed.messages) && promptForTrim) {
1996
2231
  const lastAssistant = [...parsed.messages].reverse().find((message) => message?.role === "assistant" && typeof message.content === "string");
@@ -2037,12 +2272,23 @@ ${data.message || ""}`.trim();
2037
2272
  if (this.startupParseGate) {
2038
2273
  const deadline = Date.now() + 1e4;
2039
2274
  while (this.startupParseGate && Date.now() < deadline) {
2275
+ this.resolveStartupState("send_wait");
2040
2276
  await new Promise((resolve9) => setTimeout(resolve9, 50));
2041
2277
  }
2042
2278
  }
2279
+ await this.waitForInteractivePrompt();
2280
+ if (!this.ready) {
2281
+ this.resolveStartupState("send_precheck");
2282
+ const screenText = this.terminalScreen.getText() || "";
2283
+ const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
2284
+ if (hasPrompt && this.currentStatus === "idle") {
2285
+ this.ready = true;
2286
+ this.startupParseGate = false;
2287
+ LOG.info("CLI", `[${this.cliType}] sendMessage recovered idle prompt readiness`);
2288
+ }
2289
+ }
2043
2290
  if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
2044
2291
  if (this.isWaitingForResponse) return;
2045
- await this.waitForInteractivePrompt();
2046
2292
  const blockingModal = this.activeModal || this.getStartupConfirmationModal(this.terminalScreen.getText() || "");
2047
2293
  if (blockingModal || this.currentStatus === "waiting_approval") {
2048
2294
  throw new Error(`${this.cliName} is awaiting confirmation before it can accept a prompt`);
@@ -2086,8 +2332,6 @@ ${data.message || ""}`.trim();
2086
2332
  }
2087
2333
  this.responseEpoch += 1;
2088
2334
  this.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
2089
- this.setStatus("generating", "sendMessage");
2090
- this.onStatusChange?.();
2091
2335
  const startResponseTimeout = () => {
2092
2336
  if (this.responseTimeout) clearTimeout(this.responseTimeout);
2093
2337
  this.responseTimeout = setTimeout(() => {
@@ -2106,7 +2350,7 @@ ${data.message || ""}`.trim();
2106
2350
  const retrySubmitIfStuck = (attempt) => {
2107
2351
  this.submitRetryTimer = null;
2108
2352
  if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
2109
- if (this.currentStatus !== "generating") return;
2353
+ if (this.currentStatus === "waiting_approval") return;
2110
2354
  if ((this.responseBuffer || "").trim()) return;
2111
2355
  const screenText = this.terminalScreen.getText();
2112
2356
  if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
@@ -2141,7 +2385,7 @@ ${data.message || ""}`.trim();
2141
2385
  this.submitRetryTimer = setTimeout(() => {
2142
2386
  this.submitRetryTimer = null;
2143
2387
  if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
2144
- if (this.currentStatus !== "generating") return;
2388
+ if (this.currentStatus === "waiting_approval") return;
2145
2389
  if ((this.responseBuffer || "").trim()) return;
2146
2390
  const screenText = this.terminalScreen.getText();
2147
2391
  if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
@@ -4497,199 +4741,94 @@ var StatusMonitor = class {
4497
4741
  }
4498
4742
  };
4499
4743
 
4500
- // src/providers/extension-provider-instance.ts
4501
- var ExtensionProviderInstance = class {
4502
- type;
4503
- category = "extension";
4504
- provider;
4505
- context = null;
4506
- settings = {};
4507
- events = [];
4508
- // status
4509
- currentStatus = "idle";
4510
- agentStreams = [];
4511
- messages = [];
4512
- activeModal = null;
4513
- currentModel = "";
4514
- currentMode = "";
4515
- controlValues = {};
4516
- lastAgentStatus = "idle";
4517
- generatingStartedAt = 0;
4518
- monitor;
4519
- // meta
4520
- instanceId;
4521
- ideType = "";
4522
- chatId = null;
4523
- chatTitle = null;
4524
- agentName = "";
4525
- extensionId = "";
4526
- constructor(provider) {
4527
- this.type = provider.type;
4528
- this.provider = provider;
4529
- this.instanceId = crypto.randomUUID();
4530
- this.monitor = new StatusMonitor();
4531
- }
4532
- // ─── Lifecycle ──────────────────────────────────
4533
- async init(context) {
4534
- this.context = context;
4535
- this.settings = context.settings || {};
4536
- this.monitor.updateConfig({
4537
- approvalAlert: this.settings.approvalAlert !== false,
4538
- longGeneratingAlert: this.settings.longGeneratingAlert !== false,
4539
- longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
4540
- });
4541
- }
4542
- async onTick() {
4543
- if (!this.context?.cdp?.isConnected) return;
4544
- }
4545
- getState() {
4546
- return {
4547
- type: this.type,
4548
- name: this.provider.name,
4549
- category: "extension",
4550
- status: this.currentStatus,
4551
- activeChat: this.messages.length > 0 ? {
4552
- id: this.chatId || this.instanceId,
4553
- title: this.chatTitle || this.agentName || this.provider.name,
4554
- status: this.currentStatus,
4555
- messages: this.messages,
4556
- activeModal: this.activeModal,
4557
- inputContent: ""
4558
- } : null,
4559
- currentModel: this.currentModel || void 0,
4560
- currentPlan: this.currentMode || void 0,
4561
- controlValues: this.controlValues,
4562
- providerControls: this.provider.controls,
4563
- agentStreams: this.agentStreams,
4564
- instanceId: this.instanceId,
4565
- lastUpdated: Date.now(),
4566
- settings: this.settings,
4567
- pendingEvents: this.flushEvents()
4568
- };
4569
- }
4570
- onEvent(event, data) {
4571
- if (event === "stream_update") {
4572
- if (data?.streams) this.agentStreams = data.streams;
4573
- if (data?.messages) this.messages = data.messages;
4574
- if (data?.activeModal !== void 0) this.activeModal = data.activeModal;
4575
- if (data?.model) this.currentModel = data.model;
4576
- if (data?.mode) this.currentMode = data.mode;
4577
- if (data?.controlValues) this.controlValues = data.controlValues;
4578
- if (typeof data?.sessionId === "string" && data.sessionId.trim()) this.chatId = data.sessionId;
4579
- if (typeof data?.title === "string" && data.title.trim()) this.chatTitle = data.title;
4580
- if (typeof data?.agentName === "string" && data.agentName.trim()) this.agentName = data.agentName;
4581
- if (typeof data?.extensionId === "string" && data.extensionId.trim()) this.extensionId = data.extensionId;
4582
- if (data?.status) {
4583
- const newStatus = data.status;
4584
- this.detectTransition(newStatus, data);
4585
- this.currentStatus = newStatus;
4586
- }
4587
- } else if (event === "stream_reset") {
4588
- this.resetStreamState();
4589
- } else if (event === "extension_connected") {
4590
- this.ideType = data?.ideType || "";
4744
+ // src/providers/control-effects.ts
4745
+ function extractProviderControlValues(controls, data) {
4746
+ if (!data || typeof data !== "object") return void 0;
4747
+ const values = {};
4748
+ const explicit = data.controlValues;
4749
+ if (explicit && typeof explicit === "object") {
4750
+ for (const [key, value] of Object.entries(explicit)) {
4751
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4752
+ values[key] = value;
4753
+ }
4754
+ }
4755
+ }
4756
+ for (const ctrl of controls || []) {
4757
+ if (!ctrl.readFrom) continue;
4758
+ const rawValue = data[ctrl.readFrom];
4759
+ if (rawValue === void 0 || rawValue === null) continue;
4760
+ values[ctrl.id] = normalizeControlValue(rawValue);
4761
+ }
4762
+ if (data.model !== void 0 && values.model === void 0) values.model = normalizeControlValue(data.model);
4763
+ if (data.mode !== void 0 && values.mode === void 0) values.mode = normalizeControlValue(data.mode);
4764
+ return Object.keys(values).length > 0 ? values : void 0;
4765
+ }
4766
+ function normalizeProviderEffects(data) {
4767
+ const rawEffects = Array.isArray(data?.effects) ? data.effects : [];
4768
+ const effects = [];
4769
+ for (const raw of rawEffects) {
4770
+ if (!raw || typeof raw !== "object") continue;
4771
+ const type = raw.type;
4772
+ if (type === "message" && raw.message && typeof raw.message === "object") {
4773
+ const content = raw.message.content;
4774
+ if (typeof content !== "string" && !Array.isArray(content)) continue;
4775
+ effects.push({
4776
+ type: "message",
4777
+ id: typeof raw.id === "string" ? raw.id : void 0,
4778
+ when: raw.when === "turn_completed" ? "turn_completed" : "immediate",
4779
+ persist: raw.persist !== false,
4780
+ message: {
4781
+ role: raw.message.role === "assistant" || raw.message.role === "user" ? raw.message.role : "system",
4782
+ content,
4783
+ kind: typeof raw.message.kind === "string" ? raw.message.kind : void 0,
4784
+ senderName: typeof raw.message.senderName === "string" ? raw.message.senderName : void 0
4785
+ }
4786
+ });
4787
+ continue;
4591
4788
  }
4592
- }
4593
- dispose() {
4594
- this.agentStreams = [];
4595
- this.messages = [];
4596
- this.monitor.reset();
4597
- }
4598
- /** Query UUID instanceId */
4599
- getInstanceId() {
4600
- return this.instanceId;
4601
- }
4602
- // ─── status transition detect ──────────────────────────────
4603
- detectTransition(newStatus, data) {
4604
- const now = Date.now();
4605
- const agentStatus = newStatus === "streaming" || newStatus === "generating" ? "generating" : newStatus === "waiting_approval" ? "waiting_approval" : "idle";
4606
- const lastMsg = Array.isArray(data?.messages) && data.messages.length > 0 ? data.messages[data.messages.length - 1] : null;
4607
- const progressFingerprint = agentStatus === "generating" ? `${lastMsg?.role || ""}:${typeof lastMsg?.content === "string" ? lastMsg.content : JSON.stringify(lastMsg?.content || "")}`.slice(-2e3) : void 0;
4608
- if (agentStatus !== this.lastAgentStatus) {
4609
- if (this.lastAgentStatus === "idle" && agentStatus === "generating") {
4610
- this.generatingStartedAt = now;
4611
- this.pushEvent({
4612
- event: "agent:generating_started",
4613
- chatTitle: this.resolveChatTitle(data),
4614
- timestamp: now,
4615
- ideType: this.ideType || this.type,
4616
- agentType: this.type,
4617
- agentName: this.agentName || this.provider.name,
4618
- extensionId: this.extensionId || this.type
4619
- });
4620
- } else if (agentStatus === "waiting_approval") {
4621
- if (!this.generatingStartedAt) this.generatingStartedAt = now;
4622
- this.pushEvent({
4623
- event: "agent:waiting_approval",
4624
- chatTitle: this.resolveChatTitle(data),
4625
- timestamp: now,
4626
- ideType: this.ideType || this.type,
4627
- agentType: this.type,
4628
- agentName: this.agentName || this.provider.name,
4629
- extensionId: this.extensionId || this.type,
4630
- modalMessage: data?.activeModal?.message,
4631
- modalButtons: data?.activeModal?.buttons
4632
- });
4633
- } else if (agentStatus === "idle" && (this.lastAgentStatus === "generating" || this.lastAgentStatus === "waiting_approval")) {
4634
- const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
4635
- this.pushEvent({
4636
- event: "agent:generating_completed",
4637
- chatTitle: this.resolveChatTitle(data),
4638
- duration,
4639
- timestamp: now,
4640
- ideType: this.ideType || this.type,
4641
- agentType: this.type,
4642
- agentName: this.agentName || this.provider.name,
4643
- extensionId: this.extensionId || this.type
4644
- });
4645
- this.generatingStartedAt = 0;
4646
- }
4647
- this.lastAgentStatus = agentStatus;
4789
+ if (type === "toast" && raw.toast && typeof raw.toast.message === "string") {
4790
+ effects.push({
4791
+ type: "toast",
4792
+ id: typeof raw.id === "string" ? raw.id : void 0,
4793
+ when: raw.when === "turn_completed" ? "turn_completed" : "immediate",
4794
+ persist: raw.persist !== false,
4795
+ toast: {
4796
+ level: raw.toast.level === "success" || raw.toast.level === "warning" ? raw.toast.level : "info",
4797
+ message: raw.toast.message
4798
+ }
4799
+ });
4800
+ continue;
4648
4801
  }
4649
- const agentKey = `${this.type}:ext`;
4650
- const monitorEvents = this.monitor.check(agentKey, agentStatus, now, progressFingerprint);
4651
- for (const me of monitorEvents) {
4652
- this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
4802
+ if (type === "notification" && raw.notification && typeof raw.notification.body === "string") {
4803
+ effects.push({
4804
+ type: "notification",
4805
+ id: typeof raw.id === "string" ? raw.id : void 0,
4806
+ when: raw.when === "turn_completed" ? "turn_completed" : "immediate",
4807
+ persist: raw.persist !== false,
4808
+ notification: {
4809
+ title: typeof raw.notification.title === "string" ? raw.notification.title : void 0,
4810
+ body: raw.notification.body,
4811
+ level: raw.notification.level === "success" || raw.notification.level === "warning" ? raw.notification.level : "info",
4812
+ channels: Array.isArray(raw.notification.channels) ? raw.notification.channels.filter((channel) => channel === "bubble" || channel === "toast" || channel === "browser") : void 0,
4813
+ preferenceKey: raw.notification.preferenceKey === "disconnect" || raw.notification.preferenceKey === "completion" || raw.notification.preferenceKey === "approval" || raw.notification.preferenceKey === "browser" ? raw.notification.preferenceKey : void 0,
4814
+ bubbleContent: typeof raw.notification.bubbleContent === "string" || Array.isArray(raw.notification.bubbleContent) ? raw.notification.bubbleContent : void 0
4815
+ }
4816
+ });
4653
4817
  }
4654
4818
  }
4655
- pushEvent(event) {
4656
- this.events.push(event);
4657
- if (this.events.length > 50) this.events = this.events.slice(-50);
4658
- }
4659
- flushEvents() {
4660
- const events = [...this.events];
4661
- this.events = [];
4662
- return events;
4663
- }
4664
- resolveChatTitle(data) {
4665
- const title = typeof data?.title === "string" && data.title.trim() ? data.title.trim() : this.chatTitle;
4666
- return title || this.agentName || this.provider.name;
4819
+ return effects;
4820
+ }
4821
+ function normalizeControlValue(value) {
4822
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4823
+ return value;
4667
4824
  }
4668
- resetStreamState() {
4669
- if (this.currentStatus !== "idle") {
4670
- this.detectTransition("idle", {
4671
- title: this.chatTitle,
4672
- agentName: this.agentName,
4673
- extensionId: this.extensionId,
4674
- messages: this.messages
4675
- });
4676
- }
4677
- this.agentStreams = [];
4678
- this.messages = [];
4679
- this.activeModal = null;
4680
- this.currentModel = "";
4681
- this.currentMode = "";
4682
- this.controlValues = {};
4683
- this.currentStatus = "idle";
4684
- this.chatId = null;
4685
- this.chatTitle = null;
4686
- this.agentName = "";
4687
- this.extensionId = "";
4688
- this.lastAgentStatus = "idle";
4689
- this.generatingStartedAt = 0;
4690
- this.monitor.reset();
4825
+ if (value && typeof value === "object") {
4826
+ if (typeof value.label === "string") return value.label;
4827
+ if (typeof value.name === "string") return value.name;
4828
+ if (typeof value.id === "string") return value.id;
4691
4829
  }
4692
- };
4830
+ return String(value);
4831
+ }
4693
4832
 
4694
4833
  // src/config/chat-history.ts
4695
4834
  import * as fs3 from "fs";
@@ -4942,28 +5081,357 @@ function listSavedHistorySessions(agentType, options = {}) {
4942
5081
  if (parsed.role !== "system" && parsed.content.trim()) preview = parsed.content.trim();
4943
5082
  }
4944
5083
  }
4945
- if (messageCount === 0 || !lastMessageAt) continue;
4946
- summaries.push({
4947
- historySessionId,
4948
- sessionTitle: sessionTitle || void 0,
4949
- messageCount,
4950
- firstMessageAt,
4951
- lastMessageAt,
4952
- preview: preview || void 0
5084
+ if (messageCount === 0 || !lastMessageAt) continue;
5085
+ summaries.push({
5086
+ historySessionId,
5087
+ sessionTitle: sessionTitle || void 0,
5088
+ messageCount,
5089
+ firstMessageAt,
5090
+ lastMessageAt,
5091
+ preview: preview || void 0
5092
+ });
5093
+ }
5094
+ summaries.sort((a, b) => b.lastMessageAt - a.lastMessageAt);
5095
+ const offset = Math.max(0, options.offset || 0);
5096
+ const limit = Math.max(1, options.limit || 30);
5097
+ const sliced = summaries.slice(offset, offset + limit);
5098
+ return {
5099
+ sessions: sliced,
5100
+ hasMore: summaries.length > offset + limit
5101
+ };
5102
+ } catch {
5103
+ return { sessions: [], hasMore: false };
5104
+ }
5105
+ }
5106
+
5107
+ // src/providers/extension-provider-instance.ts
5108
+ var ExtensionProviderInstance = class {
5109
+ type;
5110
+ category = "extension";
5111
+ provider;
5112
+ context = null;
5113
+ settings = {};
5114
+ events = [];
5115
+ // status
5116
+ currentStatus = "idle";
5117
+ agentStreams = [];
5118
+ messages = [];
5119
+ activeModal = null;
5120
+ currentModel = "";
5121
+ currentMode = "";
5122
+ controlValues = {};
5123
+ appliedEffectKeys = /* @__PURE__ */ new Set();
5124
+ runtimeMessages = [];
5125
+ lastAgentStatus = "idle";
5126
+ generatingStartedAt = 0;
5127
+ monitor;
5128
+ historyWriter;
5129
+ // meta
5130
+ instanceId;
5131
+ ideType = "";
5132
+ chatId = null;
5133
+ chatTitle = null;
5134
+ agentName = "";
5135
+ extensionId = "";
5136
+ constructor(provider) {
5137
+ this.type = provider.type;
5138
+ this.provider = provider;
5139
+ this.instanceId = crypto.randomUUID();
5140
+ this.monitor = new StatusMonitor();
5141
+ this.historyWriter = new ChatHistoryWriter();
5142
+ }
5143
+ // ─── Lifecycle ──────────────────────────────────
5144
+ async init(context) {
5145
+ this.context = context;
5146
+ this.settings = context.settings || {};
5147
+ this.monitor.updateConfig({
5148
+ approvalAlert: this.settings.approvalAlert !== false,
5149
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
5150
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
5151
+ });
5152
+ }
5153
+ async onTick() {
5154
+ if (!this.context?.cdp?.isConnected) return;
5155
+ }
5156
+ getState() {
5157
+ return {
5158
+ type: this.type,
5159
+ name: this.provider.name,
5160
+ category: "extension",
5161
+ status: this.currentStatus,
5162
+ activeChat: this.messages.length > 0 || this.runtimeMessages.length > 0 ? {
5163
+ id: this.chatId || this.instanceId,
5164
+ title: this.chatTitle || this.agentName || this.provider.name,
5165
+ status: this.currentStatus,
5166
+ messages: this.mergeConversationMessages(this.messages),
5167
+ activeModal: this.activeModal,
5168
+ inputContent: ""
5169
+ } : null,
5170
+ currentModel: this.currentModel || void 0,
5171
+ currentPlan: this.currentMode || void 0,
5172
+ controlValues: this.controlValues,
5173
+ providerControls: this.provider.controls,
5174
+ agentStreams: this.agentStreams,
5175
+ instanceId: this.instanceId,
5176
+ lastUpdated: Date.now(),
5177
+ settings: this.settings,
5178
+ pendingEvents: this.flushEvents()
5179
+ };
5180
+ }
5181
+ onEvent(event, data) {
5182
+ if (event === "stream_update") {
5183
+ if (data?.streams) this.agentStreams = data.streams;
5184
+ if (data?.messages) this.messages = data.messages;
5185
+ if (data?.activeModal !== void 0) this.activeModal = data.activeModal;
5186
+ if (data?.model) this.currentModel = data.model;
5187
+ if (data?.mode) this.currentMode = data.mode;
5188
+ const controlValues = extractProviderControlValues(this.provider.controls, data) || data?.controlValues;
5189
+ if (controlValues) this.controlValues = controlValues;
5190
+ if (typeof data?.sessionId === "string" && data.sessionId.trim()) this.chatId = data.sessionId;
5191
+ if (typeof data?.title === "string" && data.title.trim()) this.chatTitle = data.title;
5192
+ if (typeof data?.agentName === "string" && data.agentName.trim()) this.agentName = data.agentName;
5193
+ if (typeof data?.extensionId === "string" && data.extensionId.trim()) this.extensionId = data.extensionId;
5194
+ if (data?.status) {
5195
+ const newStatus = data.status;
5196
+ this.detectTransition(newStatus, data);
5197
+ this.currentStatus = newStatus;
5198
+ }
5199
+ } else if (event === "stream_reset") {
5200
+ this.resetStreamState();
5201
+ } else if (event === "extension_connected") {
5202
+ this.ideType = data?.ideType || "";
5203
+ } else if (event === "provider_state_patch" && data && typeof data === "object") {
5204
+ this.applyProviderResponse(data, { phase: "immediate" });
5205
+ }
5206
+ }
5207
+ dispose() {
5208
+ this.agentStreams = [];
5209
+ this.messages = [];
5210
+ this.monitor.reset();
5211
+ this.appliedEffectKeys.clear();
5212
+ this.runtimeMessages = [];
5213
+ }
5214
+ updateSettings(newSettings) {
5215
+ this.settings = { ...newSettings };
5216
+ this.monitor.updateConfig({
5217
+ approvalAlert: this.settings.approvalAlert !== false,
5218
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
5219
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
5220
+ });
5221
+ }
5222
+ /** Query UUID instanceId */
5223
+ getInstanceId() {
5224
+ return this.instanceId;
5225
+ }
5226
+ // ─── status transition detect ──────────────────────────────
5227
+ detectTransition(newStatus, data) {
5228
+ const now = Date.now();
5229
+ const agentStatus = newStatus === "streaming" || newStatus === "generating" ? "generating" : newStatus === "waiting_approval" ? "waiting_approval" : "idle";
5230
+ const lastMsg = Array.isArray(data?.messages) && data.messages.length > 0 ? data.messages[data.messages.length - 1] : null;
5231
+ const progressFingerprint = agentStatus === "generating" ? `${lastMsg?.role || ""}:${typeof lastMsg?.content === "string" ? lastMsg.content : JSON.stringify(lastMsg?.content || "")}`.slice(-2e3) : void 0;
5232
+ const previousStatus = this.lastAgentStatus;
5233
+ if (agentStatus !== this.lastAgentStatus) {
5234
+ if (this.lastAgentStatus === "idle" && agentStatus === "generating") {
5235
+ this.generatingStartedAt = now;
5236
+ this.pushEvent({
5237
+ event: "agent:generating_started",
5238
+ chatTitle: this.resolveChatTitle(data),
5239
+ timestamp: now,
5240
+ ideType: this.ideType || this.type,
5241
+ agentType: this.type,
5242
+ agentName: this.agentName || this.provider.name,
5243
+ extensionId: this.extensionId || this.type
5244
+ });
5245
+ } else if (agentStatus === "waiting_approval") {
5246
+ if (!this.generatingStartedAt) this.generatingStartedAt = now;
5247
+ this.pushEvent({
5248
+ event: "agent:waiting_approval",
5249
+ chatTitle: this.resolveChatTitle(data),
5250
+ timestamp: now,
5251
+ ideType: this.ideType || this.type,
5252
+ agentType: this.type,
5253
+ agentName: this.agentName || this.provider.name,
5254
+ extensionId: this.extensionId || this.type,
5255
+ modalMessage: data?.activeModal?.message,
5256
+ modalButtons: data?.activeModal?.buttons
5257
+ });
5258
+ } else if (agentStatus === "idle" && (this.lastAgentStatus === "generating" || this.lastAgentStatus === "waiting_approval")) {
5259
+ const duration = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
5260
+ this.pushEvent({
5261
+ event: "agent:generating_completed",
5262
+ chatTitle: this.resolveChatTitle(data),
5263
+ duration,
5264
+ timestamp: now,
5265
+ ideType: this.ideType || this.type,
5266
+ agentType: this.type,
5267
+ agentName: this.agentName || this.provider.name,
5268
+ extensionId: this.extensionId || this.type
5269
+ });
5270
+ this.generatingStartedAt = 0;
5271
+ }
5272
+ this.lastAgentStatus = agentStatus;
5273
+ }
5274
+ this.applyProviderResponse(data, {
5275
+ phase: agentStatus === "idle" && (previousStatus === "generating" || previousStatus === "waiting_approval") ? "turn_completed" : "immediate"
5276
+ });
5277
+ const agentKey = `${this.type}:ext`;
5278
+ const monitorEvents = this.monitor.check(agentKey, agentStatus, now, progressFingerprint);
5279
+ for (const me of monitorEvents) {
5280
+ this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
5281
+ }
5282
+ }
5283
+ pushEvent(event) {
5284
+ this.events.push(event);
5285
+ if (this.events.length > 50) this.events = this.events.slice(-50);
5286
+ }
5287
+ applyProviderResponse(data, options) {
5288
+ if (!data || typeof data !== "object") return;
5289
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
5290
+ if (controlValues) this.controlValues = { ...this.controlValues, ...controlValues };
5291
+ const effects = normalizeProviderEffects(data);
5292
+ for (const effect of effects) {
5293
+ const effectWhen = effect.when || "immediate";
5294
+ if (effectWhen === "turn_completed" && options.phase !== "turn_completed") continue;
5295
+ if (effectWhen === "immediate" && options.phase === "turn_completed") continue;
5296
+ const effectKey = this.getEffectDedupKey(effect);
5297
+ if (this.appliedEffectKeys.has(effectKey)) continue;
5298
+ this.appliedEffectKeys.add(effectKey);
5299
+ if (effect.persist !== false) {
5300
+ const persisted = this.getPersistedEffectContent(effect);
5301
+ if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
5302
+ }
5303
+ if (effect.type === "message" && effect.message) {
5304
+ this.pushEvent({
5305
+ event: "provider:message",
5306
+ timestamp: Date.now(),
5307
+ content: typeof effect.message.content === "string" ? effect.message.content : JSON.stringify(effect.message.content),
5308
+ role: effect.message.role || "system",
5309
+ kind: effect.message.kind,
5310
+ senderName: effect.message.senderName
5311
+ });
5312
+ } else if (effect.type === "toast" && effect.toast) {
5313
+ this.pushEvent({
5314
+ event: "provider:toast",
5315
+ effectId: effect.id || effectKey,
5316
+ timestamp: Date.now(),
5317
+ message: effect.toast.message,
5318
+ level: effect.toast.level || "info"
5319
+ });
5320
+ } else if (effect.type === "notification" && effect.notification) {
5321
+ this.pushEvent({
5322
+ event: "provider:notification",
5323
+ effectId: effect.id || effectKey,
5324
+ timestamp: Date.now(),
5325
+ title: effect.notification.title,
5326
+ message: effect.notification.body,
5327
+ content: typeof effect.notification.bubbleContent === "string" ? effect.notification.bubbleContent : effect.notification.body,
5328
+ level: effect.notification.level || "info",
5329
+ channels: effect.notification.channels || ["toast"],
5330
+ preferenceKey: effect.notification.preferenceKey
5331
+ });
5332
+ }
5333
+ }
5334
+ }
5335
+ appendRuntimeSystemMessage(content, dedupKey, receivedAt = Date.now()) {
5336
+ const normalizedContent = String(content || "").trim();
5337
+ if (!normalizedContent) return;
5338
+ if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
5339
+ this.runtimeMessages.push({
5340
+ key: dedupKey,
5341
+ message: {
5342
+ role: "system",
5343
+ senderName: "System",
5344
+ content: normalizedContent,
5345
+ receivedAt,
5346
+ timestamp: receivedAt
5347
+ }
5348
+ });
5349
+ if (this.runtimeMessages.length > 50) this.runtimeMessages = this.runtimeMessages.slice(-50);
5350
+ this.historyWriter.appendNewMessages(
5351
+ this.type,
5352
+ [{
5353
+ role: "system",
5354
+ senderName: "System",
5355
+ content: normalizedContent,
5356
+ kind: "system",
5357
+ receivedAt,
5358
+ historyDedupKey: dedupKey
5359
+ }],
5360
+ this.chatTitle || this.agentName || this.provider.name,
5361
+ this.instanceId,
5362
+ this.chatId || this.instanceId
5363
+ );
5364
+ }
5365
+ mergeConversationMessages(messages) {
5366
+ if (this.runtimeMessages.length === 0) return messages;
5367
+ return [...messages, ...this.runtimeMessages.map((entry) => entry.message)].map((message, index) => ({ message, index })).sort((a, b) => {
5368
+ const aTime = a.message.receivedAt || a.message.timestamp || 0;
5369
+ const bTime = b.message.receivedAt || b.message.timestamp || 0;
5370
+ if (aTime !== bTime) return aTime - bTime;
5371
+ return a.index - b.index;
5372
+ }).map((entry) => entry.message);
5373
+ }
5374
+ getPersistedEffectContent(effect) {
5375
+ if (effect.type === "message") {
5376
+ return typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
5377
+ }
5378
+ if (effect.type === "toast") {
5379
+ return effect.toast?.message || null;
5380
+ }
5381
+ if (effect.type === "notification") {
5382
+ if (typeof effect.notification?.bubbleContent === "string") return effect.notification.bubbleContent;
5383
+ if (typeof effect.notification?.title === "string" && effect.notification.title.trim()) {
5384
+ return `${effect.notification.title}
5385
+ ${effect.notification.body || ""}`.trim();
5386
+ }
5387
+ return effect.notification?.body || null;
5388
+ }
5389
+ return null;
5390
+ }
5391
+ getEffectDedupKey(effect) {
5392
+ if (effect.id) return `provider_effect:${effect.id}`;
5393
+ if (effect.type === "message") {
5394
+ return `provider_effect:message:${typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "")}`;
5395
+ }
5396
+ if (effect.type === "notification") {
5397
+ return `provider_effect:notification:${effect.notification?.title || ""}:${effect.notification?.body || ""}`;
5398
+ }
5399
+ return `provider_effect:toast:${effect.toast?.message || ""}`;
5400
+ }
5401
+ flushEvents() {
5402
+ const events = [...this.events];
5403
+ this.events = [];
5404
+ return events;
5405
+ }
5406
+ resolveChatTitle(data) {
5407
+ const title = typeof data?.title === "string" && data.title.trim() ? data.title.trim() : this.chatTitle;
5408
+ return title || this.agentName || this.provider.name;
5409
+ }
5410
+ resetStreamState() {
5411
+ if (this.currentStatus !== "idle") {
5412
+ this.detectTransition("idle", {
5413
+ title: this.chatTitle,
5414
+ agentName: this.agentName,
5415
+ extensionId: this.extensionId,
5416
+ messages: this.messages
4953
5417
  });
4954
5418
  }
4955
- summaries.sort((a, b) => b.lastMessageAt - a.lastMessageAt);
4956
- const offset = Math.max(0, options.offset || 0);
4957
- const limit = Math.max(1, options.limit || 30);
4958
- const sliced = summaries.slice(offset, offset + limit);
4959
- return {
4960
- sessions: sliced,
4961
- hasMore: summaries.length > offset + limit
4962
- };
4963
- } catch {
4964
- return { sessions: [], hasMore: false };
5419
+ this.agentStreams = [];
5420
+ this.messages = [];
5421
+ this.activeModal = null;
5422
+ this.currentModel = "";
5423
+ this.currentMode = "";
5424
+ this.controlValues = {};
5425
+ this.currentStatus = "idle";
5426
+ this.chatId = null;
5427
+ this.chatTitle = null;
5428
+ this.agentName = "";
5429
+ this.extensionId = "";
5430
+ this.lastAgentStatus = "idle";
5431
+ this.generatingStartedAt = 0;
5432
+ this.monitor.reset();
4965
5433
  }
4966
- }
5434
+ };
4967
5435
 
4968
5436
  // src/providers/ide-provider-instance.ts
4969
5437
  init_logger();
@@ -4984,6 +5452,8 @@ var IdeProviderInstance = class {
4984
5452
  monitor;
4985
5453
  historyWriter;
4986
5454
  autoApproveBusy = false;
5455
+ appliedEffectKeys = /* @__PURE__ */ new Set();
5456
+ runtimeMessages = [];
4987
5457
  // IDE meta
4988
5458
  ideVersion = "";
4989
5459
  instanceId;
@@ -5044,7 +5514,7 @@ var IdeProviderInstance = class {
5044
5514
  id: this.cachedChat.id || "active_session",
5045
5515
  title: this.cachedChat.title || this.type,
5046
5516
  status: this.cachedChat.status || this.currentStatus,
5047
- messages: this.cachedChat.messages || [],
5517
+ messages: this.mergeConversationMessages(this.cachedChat.messages || []),
5048
5518
  activeModal: this.cachedChat.activeModal || null,
5049
5519
  inputContent: this.cachedChat.inputContent || ""
5050
5520
  } : null,
@@ -5084,6 +5554,13 @@ var IdeProviderInstance = class {
5084
5554
  for (const ext of this.extensions.values()) {
5085
5555
  ext.onEvent("stream_reset");
5086
5556
  }
5557
+ } else if (event === "provider_state_patch" && data && typeof data === "object") {
5558
+ const extType = typeof data.extensionType === "string" ? data.extensionType : "";
5559
+ if (extType && this.extensions.has(extType)) {
5560
+ this.extensions.get(extType).onEvent("provider_state_patch", data);
5561
+ } else {
5562
+ this.applyProviderResponse(data, { phase: "immediate" });
5563
+ }
5087
5564
  }
5088
5565
  }
5089
5566
  dispose() {
@@ -5091,11 +5568,21 @@ var IdeProviderInstance = class {
5091
5568
  this.lastAgentStatuses.clear();
5092
5569
  this.generatingStartedAt.clear();
5093
5570
  this.monitor.reset();
5571
+ this.appliedEffectKeys.clear();
5572
+ this.runtimeMessages = [];
5094
5573
  for (const ext of this.extensions.values()) {
5095
5574
  ext.dispose();
5096
5575
  }
5097
5576
  this.extensions.clear();
5098
5577
  }
5578
+ updateSettings(newSettings) {
5579
+ this.settings = { ...newSettings };
5580
+ this.monitor.updateConfig({
5581
+ approvalAlert: this.settings.approvalAlert !== false,
5582
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
5583
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
5584
+ });
5585
+ }
5099
5586
  // ─── Extension manage ─────────────────────────────
5100
5587
  /** Extension Instance add */
5101
5588
  async addExtension(provider, settings) {
@@ -5208,6 +5695,8 @@ var IdeProviderInstance = class {
5208
5695
  raw.messages = raw.messages.filter((m) => !hiddenKinds.has(m.kind));
5209
5696
  }
5210
5697
  }
5698
+ const controlValues = extractProviderControlValues(this.provider.controls, raw);
5699
+ if (controlValues) raw.controlValues = controlValues;
5211
5700
  this.cachedChat = { ...raw, activeModal };
5212
5701
  this.detectAgentTransitions(raw, now);
5213
5702
  if (raw.messages?.length > 0) {
@@ -5274,6 +5763,9 @@ var IdeProviderInstance = class {
5274
5763
  }
5275
5764
  this.lastAgentStatuses.set(agentKey, agentStatus);
5276
5765
  }
5766
+ this.applyProviderResponse(chatData, {
5767
+ phase: agentStatus === "idle" && (lastStatus === "generating" || lastStatus === "waiting_approval") ? "turn_completed" : "immediate"
5768
+ });
5277
5769
  if (agentStatus === "waiting_approval" && this.settings.autoApprove && !this.autoApproveBusy) {
5278
5770
  this.autoApproveViaScript(chatData);
5279
5771
  }
@@ -5286,6 +5778,136 @@ var IdeProviderInstance = class {
5286
5778
  this.events.push(event);
5287
5779
  if (this.events.length > 50) this.events = this.events.slice(-50);
5288
5780
  }
5781
+ applyProviderResponse(data, options) {
5782
+ if (!data || typeof data !== "object") return;
5783
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
5784
+ if (controlValues) {
5785
+ this.cachedChat = {
5786
+ ...this.cachedChat || {},
5787
+ ...data,
5788
+ controlValues: { ...this.cachedChat?.controlValues || {}, ...controlValues }
5789
+ };
5790
+ }
5791
+ const effects = normalizeProviderEffects(data);
5792
+ for (const effect of effects) {
5793
+ const effectWhen = effect.when || "immediate";
5794
+ if (effectWhen === "turn_completed" && options.phase !== "turn_completed") continue;
5795
+ if (effectWhen === "immediate" && options.phase === "turn_completed") continue;
5796
+ const effectKey = this.getEffectDedupKey(effect);
5797
+ if (this.appliedEffectKeys.has(effectKey)) continue;
5798
+ this.appliedEffectKeys.add(effectKey);
5799
+ if (effect.persist !== false) {
5800
+ const persisted = this.getPersistedEffectContent(effect);
5801
+ if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
5802
+ }
5803
+ if (effect.type === "message" && effect.message) {
5804
+ this.pushEvent({
5805
+ event: "provider:message",
5806
+ timestamp: Date.now(),
5807
+ content: typeof effect.message.content === "string" ? effect.message.content : JSON.stringify(effect.message.content),
5808
+ role: effect.message.role || "system",
5809
+ kind: effect.message.kind,
5810
+ senderName: effect.message.senderName
5811
+ });
5812
+ } else if (effect.type === "toast" && effect.toast) {
5813
+ this.pushEvent({
5814
+ event: "provider:toast",
5815
+ effectId: effect.id || effectKey,
5816
+ timestamp: Date.now(),
5817
+ message: effect.toast.message,
5818
+ level: effect.toast.level || "info"
5819
+ });
5820
+ } else if (effect.type === "notification" && effect.notification) {
5821
+ this.pushEvent({
5822
+ event: "provider:notification",
5823
+ effectId: effect.id || effectKey,
5824
+ timestamp: Date.now(),
5825
+ title: effect.notification.title,
5826
+ message: effect.notification.body,
5827
+ content: typeof effect.notification.bubbleContent === "string" ? effect.notification.bubbleContent : effect.notification.body,
5828
+ level: effect.notification.level || "info",
5829
+ channels: effect.notification.channels || ["toast"],
5830
+ preferenceKey: effect.notification.preferenceKey
5831
+ });
5832
+ }
5833
+ }
5834
+ }
5835
+ appendRuntimeSystemMessage(content, dedupKey, receivedAt = Date.now()) {
5836
+ const normalizedContent = String(content || "").trim();
5837
+ if (!normalizedContent) return;
5838
+ if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
5839
+ if (!this.cachedChat) {
5840
+ this.cachedChat = {
5841
+ id: "active_session",
5842
+ title: this.provider.name,
5843
+ status: this.currentStatus,
5844
+ messages: [],
5845
+ activeModal: null,
5846
+ inputContent: ""
5847
+ };
5848
+ }
5849
+ this.runtimeMessages.push({
5850
+ key: dedupKey,
5851
+ message: {
5852
+ role: "system",
5853
+ senderName: "System",
5854
+ content: normalizedContent,
5855
+ receivedAt,
5856
+ timestamp: receivedAt
5857
+ }
5858
+ });
5859
+ if (this.runtimeMessages.length > 50) this.runtimeMessages = this.runtimeMessages.slice(-50);
5860
+ this.historyWriter.appendNewMessages(
5861
+ this.type,
5862
+ [{
5863
+ role: "system",
5864
+ senderName: "System",
5865
+ content: normalizedContent,
5866
+ kind: "system",
5867
+ receivedAt,
5868
+ historyDedupKey: dedupKey
5869
+ }],
5870
+ this.cachedChat?.title || this.provider.name,
5871
+ this.instanceId,
5872
+ this.cachedChat?.id || this.instanceId
5873
+ );
5874
+ }
5875
+ mergeConversationMessages(messages) {
5876
+ if (this.runtimeMessages.length === 0) return messages;
5877
+ return [...messages, ...this.runtimeMessages.map((entry) => entry.message)].map((message, index) => ({ message, index })).sort((a, b) => {
5878
+ const aTime = a.message.receivedAt || a.message.timestamp || 0;
5879
+ const bTime = b.message.receivedAt || b.message.timestamp || 0;
5880
+ if (aTime !== bTime) return aTime - bTime;
5881
+ return a.index - b.index;
5882
+ }).map((entry) => entry.message);
5883
+ }
5884
+ getPersistedEffectContent(effect) {
5885
+ if (effect.type === "message") {
5886
+ return typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
5887
+ }
5888
+ if (effect.type === "toast") {
5889
+ return effect.toast?.message || null;
5890
+ }
5891
+ if (effect.type === "notification") {
5892
+ if (typeof effect.notification?.bubbleContent === "string") return effect.notification.bubbleContent;
5893
+ if (typeof effect.notification?.title === "string" && effect.notification.title.trim()) {
5894
+ return `${effect.notification.title}
5895
+ ${effect.notification.body || ""}`.trim();
5896
+ }
5897
+ return effect.notification?.body || null;
5898
+ }
5899
+ return null;
5900
+ }
5901
+ getEffectDedupKey(effect) {
5902
+ if (effect.id) return `provider_effect:${effect.id}`;
5903
+ if (effect.type === "message") {
5904
+ return `provider_effect:message:${typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "")}`;
5905
+ }
5906
+ if (effect.type === "notification") {
5907
+ return `provider_effect:notification:${effect.notification?.title || ""}:${effect.notification?.body || ""}`;
5908
+ }
5909
+ return `provider_effect:toast:${effect.toast?.message || ""}`;
5910
+ }
5289
5911
  flushEvents() {
5290
5912
  const events = [...this.events];
5291
5913
  this.events = [];
@@ -6170,6 +6792,24 @@ function isRecentDuplicateSend(key) {
6170
6792
  recentSendByTarget.set(key, now);
6171
6793
  return false;
6172
6794
  }
6795
+ function parseMaybeJson(value) {
6796
+ if (typeof value !== "string") return value;
6797
+ try {
6798
+ return JSON.parse(value);
6799
+ } catch {
6800
+ return value;
6801
+ }
6802
+ }
6803
+ function didProviderConfirmSend(result) {
6804
+ const parsed = parseMaybeJson(result);
6805
+ if (parsed === true) return true;
6806
+ if (typeof parsed === "string") {
6807
+ const normalized = parsed.trim().toLowerCase();
6808
+ return normalized === "ok" || normalized === "sent" || normalized === "success" || normalized === "true";
6809
+ }
6810
+ if (!parsed || typeof parsed !== "object") return false;
6811
+ return parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true;
6812
+ }
6173
6813
  async function handleChatHistory(h, args) {
6174
6814
  const { agentType, offset, limit } = args;
6175
6815
  const historySessionId = getHistorySessionId(h, args);
@@ -6352,14 +6992,8 @@ async function handleSendChat(h, args) {
6352
6992
  try {
6353
6993
  const evalResult = await h.evaluateProviderScript("sendMessage", { MESSAGE: text }, 3e4);
6354
6994
  if (evalResult?.result) {
6355
- let parsed = evalResult.result;
6356
- if (typeof parsed === "string") {
6357
- try {
6358
- parsed = JSON.parse(parsed);
6359
- } catch {
6360
- }
6361
- }
6362
- if (parsed?.sent) {
6995
+ const parsed = parseMaybeJson(evalResult.result);
6996
+ if (didProviderConfirmSend(parsed)) {
6363
6997
  _log(`Extension script sent OK`);
6364
6998
  return _logSendSuccess("extension-script");
6365
6999
  }
@@ -6391,14 +7025,8 @@ async function handleSendChat(h, args) {
6391
7025
  if (sendScript) {
6392
7026
  try {
6393
7027
  const result = await targetCdp.evaluate(sendScript, 3e4);
6394
- let parsed = result;
6395
- if (typeof result === "string") {
6396
- try {
6397
- parsed = JSON.parse(result);
6398
- } catch {
6399
- }
6400
- }
6401
- if (parsed?.sent) {
7028
+ const parsed = parseMaybeJson(result);
7029
+ if (didProviderConfirmSend(parsed)) {
6402
7030
  _log(`sendMessage script OK`);
6403
7031
  return _logSendSuccess("script");
6404
7032
  }
@@ -6443,14 +7071,8 @@ async function handleSendChat(h, args) {
6443
7071
  const matchText = provider.webviewMatchText;
6444
7072
  const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
6445
7073
  const wvResult = await targetCdp.evaluateInWebviewFrame(webviewScript, matchFn);
6446
- let wvParsed = wvResult;
6447
- if (typeof wvResult === "string") {
6448
- try {
6449
- wvParsed = JSON.parse(wvResult);
6450
- } catch {
6451
- }
6452
- }
6453
- if (wvParsed?.sent) {
7074
+ const wvParsed = parseMaybeJson(wvResult);
7075
+ if (didProviderConfirmSend(wvParsed)) {
6454
7076
  _log(`webviewSendMessage OK`);
6455
7077
  return _logSendSuccess("webview-script");
6456
7078
  }
@@ -6472,14 +7094,8 @@ async function handleSendChat(h, args) {
6472
7094
  const matchText = provider.webviewMatchText;
6473
7095
  const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
6474
7096
  const wvResult = await targetCdp.evaluateInWebviewFrame(webviewScript, matchFn);
6475
- let wvParsed = wvResult;
6476
- if (typeof wvResult === "string") {
6477
- try {
6478
- wvParsed = JSON.parse(wvResult);
6479
- } catch {
6480
- }
6481
- }
6482
- if (wvParsed?.sent) {
7097
+ const wvParsed = parseMaybeJson(wvResult);
7098
+ if (didProviderConfirmSend(wvParsed)) {
6483
7099
  _log(`webviewSendMessage OK`);
6484
7100
  return _logSendSuccess("webview-script");
6485
7101
  }
@@ -7365,7 +7981,56 @@ function handleSetProviderSetting(h, args) {
7365
7981
  }
7366
7982
  return { success: false, error: `Failed to set ${providerType}.${key} \u2014 invalid key, value, or not a public setting` };
7367
7983
  }
7368
- async function handleExtensionScript(h, args, scriptName) {
7984
+ function normalizeProviderScriptArgs(args) {
7985
+ const normalizedArgs = { ...args || {} };
7986
+ for (const key of ["mode", "model", "message", "action", "button", "text", "sessionId", "value"]) {
7987
+ if (key in normalizedArgs && !(key.toUpperCase() in normalizedArgs)) {
7988
+ normalizedArgs[key.toUpperCase()] = normalizedArgs[key];
7989
+ }
7990
+ }
7991
+ return normalizedArgs;
7992
+ }
7993
+ function parseScriptResult(result) {
7994
+ if (typeof result === "string") {
7995
+ try {
7996
+ const parsed = JSON.parse(result);
7997
+ if (parsed && typeof parsed === "object" && parsed.success === false) {
7998
+ return { success: false, payload: parsed };
7999
+ }
8000
+ return { success: true, payload: parsed };
8001
+ } catch {
8002
+ return { success: true, payload: { result } };
8003
+ }
8004
+ }
8005
+ if (result && typeof result === "object" && result.success === false) {
8006
+ return { success: false, payload: result };
8007
+ }
8008
+ return { success: true, payload: result };
8009
+ }
8010
+ function getCliScriptCommand(payload) {
8011
+ if (!payload || typeof payload !== "object") return null;
8012
+ if (typeof payload.sendMessage === "string" && payload.sendMessage.trim()) {
8013
+ return { type: "send_message", text: payload.sendMessage.trim() };
8014
+ }
8015
+ const command = payload.command;
8016
+ if (!command || typeof command !== "object") return null;
8017
+ if (command.type !== "send_message") return null;
8018
+ const text = typeof command.text === "string" ? command.text.trim() : typeof command.message === "string" ? command.message.trim() : "";
8019
+ if (!text) return null;
8020
+ return { type: "send_message", text };
8021
+ }
8022
+ function applyProviderPatch(h, args, payload) {
8023
+ if (!payload || typeof payload !== "object") return;
8024
+ const targetSessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "";
8025
+ const targetSession = targetSessionId ? h.ctx.sessionRegistry?.get(targetSessionId) : void 0;
8026
+ const instanceKey = targetSession?.instanceKey || targetSessionId;
8027
+ if (!instanceKey) return;
8028
+ h.ctx.instanceManager?.sendEvent(instanceKey, "provider_state_patch", {
8029
+ ...payload,
8030
+ extensionType: targetSession?.transport === "cdp-webview" ? targetSession.providerType : void 0
8031
+ });
8032
+ }
8033
+ async function executeProviderScript(h, args, scriptName) {
7369
8034
  const { agentType, ideType } = args || {};
7370
8035
  if (!agentType) return { success: false, error: "agentType is required" };
7371
8036
  const loader = h.ctx.providerLoader;
@@ -7378,13 +8043,29 @@ async function handleExtensionScript(h, args, scriptName) {
7378
8043
  if (!provider.scripts?.[actualScriptName]) {
7379
8044
  return { success: false, error: `Script '${actualScriptName}' not available for ${agentType}` };
7380
8045
  }
7381
- const scriptFn = provider.scripts[actualScriptName];
7382
- const normalizedArgs = { ...args };
7383
- for (const key of ["mode", "model", "message", "action", "button", "text", "sessionId"]) {
7384
- if (key in normalizedArgs && !(key.toUpperCase() in normalizedArgs)) {
7385
- normalizedArgs[key.toUpperCase()] = normalizedArgs[key];
8046
+ const normalizedArgs = normalizeProviderScriptArgs(args);
8047
+ if (provider.category === "cli") {
8048
+ const adapter = h.getCliAdapter(args?.targetSessionId || agentType);
8049
+ if (!adapter?.invokeScript) {
8050
+ return { success: false, error: `CLI adapter does not support script '${actualScriptName}'` };
8051
+ }
8052
+ try {
8053
+ const raw = await adapter.invokeScript(actualScriptName, normalizedArgs);
8054
+ const parsed = parseScriptResult(raw);
8055
+ if (!parsed.success) {
8056
+ return { success: false, ...parsed.payload || {} };
8057
+ }
8058
+ const cliCommand = getCliScriptCommand(parsed.payload);
8059
+ if (cliCommand?.type === "send_message" && cliCommand.text) {
8060
+ await adapter.sendMessage(cliCommand.text);
8061
+ }
8062
+ applyProviderPatch(h, args, parsed.payload);
8063
+ return { success: true, ...parsed.payload && typeof parsed.payload === "object" ? parsed.payload : { result: parsed.payload } };
8064
+ } catch (e) {
8065
+ return { success: false, error: `Script execution failed: ${e.message}` };
7386
8066
  }
7387
8067
  }
8068
+ const scriptFn = provider.scripts[actualScriptName];
7388
8069
  const scriptCode = scriptFn(normalizedArgs);
7389
8070
  if (!scriptCode) return { success: false, error: `Script '${actualScriptName}' returned null` };
7390
8071
  const cdpKey = provider.category === "ide" ? h.currentSession?.cdpManagerKey || h.currentManagerKey || agentType : h.currentSession?.cdpManagerKey || h.currentManagerKey || ideType;
@@ -7438,16 +8119,29 @@ async function handleExtensionScript(h, args, scriptName) {
7438
8119
  if (typeof result === "string") {
7439
8120
  try {
7440
8121
  const parsed = JSON.parse(result);
8122
+ applyProviderPatch(h, args, parsed);
8123
+ if (parsed && typeof parsed === "object" && parsed.success === false) {
8124
+ return { success: false, ...parsed };
8125
+ }
7441
8126
  return { success: true, ...parsed };
7442
8127
  } catch {
7443
8128
  return { success: true, result };
7444
8129
  }
7445
8130
  }
8131
+ applyProviderPatch(h, args, result);
7446
8132
  return { success: true, result };
7447
8133
  } catch (e) {
7448
8134
  return { success: false, error: `Script execution failed: ${e.message}` };
7449
8135
  }
7450
8136
  }
8137
+ async function handleExtensionScript(h, args, scriptName) {
8138
+ return executeProviderScript(h, args, scriptName);
8139
+ }
8140
+ async function handleProviderScript(h, args) {
8141
+ const scriptName = typeof args?.scriptName === "string" ? args.scriptName.trim() : "";
8142
+ if (!scriptName) return { success: false, error: "scriptName is required" };
8143
+ return executeProviderScript(h, args, scriptName);
8144
+ }
7451
8145
  function handleGetIdeExtensions(h, args) {
7452
8146
  const { ideType } = args || {};
7453
8147
  const loader = h.ctx.providerLoader;
@@ -7947,6 +8641,8 @@ var DaemonCommandHandler = class {
7947
8641
  case "set_ide_extension":
7948
8642
  return handleSetIdeExtension(this, args);
7949
8643
  // ─── Extension Model / Mode Control (stream-commands.ts) ──────────
8644
+ case "invoke_provider_script":
8645
+ return handleProviderScript(this, args);
7950
8646
  case "list_extension_models":
7951
8647
  return handleExtensionScript(this, args, "listModels");
7952
8648
  case "set_extension_model":
@@ -8123,6 +8819,8 @@ var CliProviderInstance = class {
8123
8819
  generatingDebounceTimer = null;
8124
8820
  generatingDebouncePending = null;
8125
8821
  lastApprovalEventAt = 0;
8822
+ controlValues = {};
8823
+ appliedEffectKeys = /* @__PURE__ */ new Set();
8126
8824
  historyWriter;
8127
8825
  runtimeMessages = [];
8128
8826
  instanceId;
@@ -8135,6 +8833,7 @@ var CliProviderInstance = class {
8135
8833
  async init(context) {
8136
8834
  this.context = context;
8137
8835
  this.settings = context.settings || {};
8836
+ this.adapter.updateRuntimeSettings?.(this.settings);
8138
8837
  this.monitor.updateConfig({
8139
8838
  approvalAlert: this.settings.approvalAlert !== false,
8140
8839
  longGeneratingAlert: this.settings.longGeneratingAlert !== false,
@@ -8150,6 +8849,21 @@ var CliProviderInstance = class {
8150
8849
  this.detectStatusTransition();
8151
8850
  });
8152
8851
  await this.adapter.spawn();
8852
+ if (this.providerSessionId) {
8853
+ const restoredHistory = readChatHistory(this.type, 0, 200, this.providerSessionId);
8854
+ if (restoredHistory.messages.length > 0) {
8855
+ this.adapter.seedCommittedMessages(
8856
+ restoredHistory.messages.map((message) => ({
8857
+ role: message.role,
8858
+ content: message.content,
8859
+ timestamp: message.receivedAt,
8860
+ receivedAt: message.receivedAt,
8861
+ kind: message.kind,
8862
+ senderName: message.senderName
8863
+ }))
8864
+ );
8865
+ }
8866
+ }
8153
8867
  if (this.providerSessionId && this.launchMode === "resume") {
8154
8868
  const resumedAt = Date.now();
8155
8869
  this.historyWriter.appendSystemMarker(
@@ -8230,6 +8944,12 @@ var CliProviderInstance = class {
8230
8944
  }
8231
8945
  const runtime = this.adapter.getRuntimeMetadata();
8232
8946
  const parsedMessages = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [];
8947
+ const controlValues = extractProviderControlValues(this.provider.controls, parsedStatus);
8948
+ if (controlValues) {
8949
+ this.controlValues = controlValues;
8950
+ } else if (Object.keys(this.controlValues).length > 0) {
8951
+ this.controlValues = {};
8952
+ }
8233
8953
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
8234
8954
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
8235
8955
  if (parsedMessages.length > 0) {
@@ -8250,6 +8970,7 @@ var CliProviderInstance = class {
8250
8970
  );
8251
8971
  }
8252
8972
  }
8973
+ this.applyProviderResponse(parsedStatus, { phase: "immediate" });
8253
8974
  return {
8254
8975
  type: this.type,
8255
8976
  name: this.provider.name,
@@ -8279,8 +9000,7 @@ var CliProviderInstance = class {
8279
9000
  attachedClients: runtime.attachedClients || []
8280
9001
  } : void 0,
8281
9002
  resume: this.provider.resume,
8282
- controlValues: void 0,
8283
- // CLI controls not yet wired from stream
9003
+ controlValues: this.controlValues,
8284
9004
  providerControls: this.provider.controls
8285
9005
  };
8286
9006
  }
@@ -8291,6 +9011,15 @@ var CliProviderInstance = class {
8291
9011
  getPresentationMode() {
8292
9012
  return this.presentationMode;
8293
9013
  }
9014
+ updateSettings(newSettings) {
9015
+ this.settings = { ...newSettings };
9016
+ this.adapter.updateRuntimeSettings?.(this.settings);
9017
+ this.monitor.updateConfig({
9018
+ approvalAlert: this.settings.approvalAlert !== false,
9019
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
9020
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
9021
+ });
9022
+ }
8294
9023
  onEvent(event, data) {
8295
9024
  if (event === "send_message" && data?.text) {
8296
9025
  void this.adapter.sendMessage(data.text).catch((e) => {
@@ -8302,22 +9031,27 @@ var CliProviderInstance = class {
8302
9031
  void this.adapter.resolveAction(data).catch((e) => {
8303
9032
  LOG.warn("CLI", `[${this.type}] resolve_action failed: ${e?.message || e}`);
8304
9033
  });
9034
+ } else if (event === "provider_state_patch" && data && typeof data === "object") {
9035
+ this.applyProviderResponse(data, { phase: "immediate" });
8305
9036
  }
8306
9037
  }
8307
9038
  dispose() {
8308
9039
  this.adapter.shutdown();
8309
9040
  this.monitor.reset();
9041
+ this.appliedEffectKeys.clear();
8310
9042
  }
8311
9043
  completedDebounceTimer = null;
8312
9044
  completedDebouncePending = null;
8313
9045
  detectStatusTransition() {
8314
9046
  const now = Date.now();
8315
9047
  const adapterStatus = this.adapter.getStatus();
9048
+ const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
8316
9049
  const newStatus = adapterStatus.status;
8317
9050
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
8318
9051
  const chatTitle = `${this.provider.name} \xB7 ${dirName}`;
8319
9052
  const partial = this.adapter.getPartialResponse();
8320
9053
  const progressFingerprint = newStatus === "generating" ? `${partial || ""}::${adapterStatus.messages.at(-1)?.content || ""}`.slice(-2e3) : void 0;
9054
+ const previousStatus = this.lastStatus;
8321
9055
  if (newStatus !== this.lastStatus) {
8322
9056
  LOG.info("CLI", `[${this.type}] status: ${this.lastStatus} \u2192 ${newStatus}`);
8323
9057
  if (this.lastStatus === "idle" && newStatus === "generating") {
@@ -8410,6 +9144,9 @@ var CliProviderInstance = class {
8410
9144
  }
8411
9145
  this.lastStatus = newStatus;
8412
9146
  }
9147
+ this.applyProviderResponse(parsedStatus, {
9148
+ phase: newStatus === "idle" && (previousStatus === "generating" || previousStatus === "waiting_approval") ? "turn_completed" : "immediate"
9149
+ });
8413
9150
  const agentKey = `${this.type}:cli`;
8414
9151
  const monitorEvents = this.monitor.check(agentKey, newStatus, now, progressFingerprint);
8415
9152
  for (const me of monitorEvents) {
@@ -8425,6 +9162,88 @@ var CliProviderInstance = class {
8425
9162
  this.events = [];
8426
9163
  return events;
8427
9164
  }
9165
+ applyProviderResponse(data, options) {
9166
+ if (!data || typeof data !== "object") return;
9167
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
9168
+ if (controlValues) {
9169
+ this.controlValues = { ...this.controlValues, ...controlValues };
9170
+ }
9171
+ const effects = normalizeProviderEffects(data);
9172
+ for (const effect of effects) {
9173
+ const effectWhen = effect.when || "immediate";
9174
+ if (effectWhen === "turn_completed" && options.phase !== "turn_completed") continue;
9175
+ if (effectWhen === "immediate" && options.phase === "turn_completed") continue;
9176
+ const effectKey = this.getEffectDedupKey(effect);
9177
+ if (this.appliedEffectKeys.has(effectKey)) continue;
9178
+ this.appliedEffectKeys.add(effectKey);
9179
+ if (effect.persist !== false) {
9180
+ const persisted = this.getPersistedEffectContent(effect);
9181
+ if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
9182
+ }
9183
+ if (effect.type === "message" && effect.message) {
9184
+ const content = typeof effect.message.content === "string" ? effect.message.content : JSON.stringify(effect.message.content);
9185
+ this.pushEvent({
9186
+ event: "provider:message",
9187
+ timestamp: Date.now(),
9188
+ content,
9189
+ role: effect.message.role || "system",
9190
+ kind: effect.message.kind,
9191
+ senderName: effect.message.senderName
9192
+ });
9193
+ } else if (effect.type === "toast" && effect.toast) {
9194
+ this.pushEvent({
9195
+ event: "provider:toast",
9196
+ effectId: effect.id || effectKey,
9197
+ timestamp: Date.now(),
9198
+ message: effect.toast.message,
9199
+ level: effect.toast.level || "info"
9200
+ });
9201
+ } else if (effect.type === "notification" && effect.notification) {
9202
+ this.pushEvent({
9203
+ event: "provider:notification",
9204
+ effectId: effect.id || effectKey,
9205
+ timestamp: Date.now(),
9206
+ title: effect.notification.title,
9207
+ message: effect.notification.body,
9208
+ content: typeof effect.notification.bubbleContent === "string" ? effect.notification.bubbleContent : effect.notification.body,
9209
+ level: effect.notification.level || "info",
9210
+ channels: effect.notification.channels || ["toast"],
9211
+ preferenceKey: effect.notification.preferenceKey
9212
+ });
9213
+ }
9214
+ }
9215
+ if (this.appliedEffectKeys.size > 200) {
9216
+ this.appliedEffectKeys = new Set(Array.from(this.appliedEffectKeys).slice(-100));
9217
+ }
9218
+ }
9219
+ getEffectDedupKey(effect) {
9220
+ if (effect.id) return `provider_effect:${effect.id}`;
9221
+ if (effect.type === "message") {
9222
+ const content = typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
9223
+ return `provider_effect:message:${content}`;
9224
+ }
9225
+ if (effect.type === "notification") {
9226
+ return `provider_effect:notification:${effect.notification?.title || ""}:${effect.notification?.body || ""}`;
9227
+ }
9228
+ return `provider_effect:toast:${effect.toast?.message || ""}`;
9229
+ }
9230
+ getPersistedEffectContent(effect) {
9231
+ if (effect.type === "message") {
9232
+ return typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
9233
+ }
9234
+ if (effect.type === "toast") {
9235
+ return effect.toast?.message || null;
9236
+ }
9237
+ if (effect.type === "notification") {
9238
+ if (typeof effect.notification?.bubbleContent === "string") return effect.notification.bubbleContent;
9239
+ if (typeof effect.notification?.title === "string" && effect.notification.title.trim()) {
9240
+ return `${effect.notification.title}
9241
+ ${effect.notification.body || ""}`.trim();
9242
+ }
9243
+ return effect.notification?.body || null;
9244
+ }
9245
+ return null;
9246
+ }
8428
9247
  // ─── Adapter access (backward compat) ──────────────────
8429
9248
  getAdapter() {
8430
9249
  return this.adapter;
@@ -11202,6 +12021,22 @@ function getMacAppIdentifiers() {
11202
12021
  function getWinProcessNames() {
11203
12022
  return getProviderLoader().getWinProcessNames();
11204
12023
  }
12024
+ function getProviderMeta(ideId) {
12025
+ return getProviderLoader().getMeta(ideId);
12026
+ }
12027
+ function getPreferredLaunchMethod(ideId, platform9) {
12028
+ const prefer = getProviderMeta(ideId)?.launch?.prefer;
12029
+ const value = prefer?.[platform9];
12030
+ return value === "cli" || value === "app" || value === "auto" ? value : "auto";
12031
+ }
12032
+ function getCdpStartupTimeoutMs(ideId) {
12033
+ const value = getProviderMeta(ideId)?.launch?.cdpStartupTimeoutMs;
12034
+ if (typeof value !== "number" || !Number.isFinite(value)) return 15e3;
12035
+ return Math.max(1e3, Math.floor(value));
12036
+ }
12037
+ function escapeForAppleScript(value) {
12038
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
12039
+ }
11205
12040
  async function findFreePort(ports) {
11206
12041
  for (const port2 of ports) {
11207
12042
  const free = await checkPortFree(port2);
@@ -11254,12 +12089,12 @@ async function killIdeProcess(ideId) {
11254
12089
  try {
11255
12090
  if (plat === "darwin" && appName) {
11256
12091
  try {
11257
- execSync4(`osascript -e 'tell application "${appName}" to quit' 2>/dev/null`, {
12092
+ execSync4(`osascript -e 'tell application "${escapeForAppleScript(appName)}" to quit' 2>/dev/null`, {
11258
12093
  timeout: 5e3
11259
12094
  });
11260
12095
  } catch {
11261
12096
  try {
11262
- execSync4(`pkill -f "${appName}" 2>/dev/null`);
12097
+ execSync4(`pkill -x "${appName}" 2>/dev/null`, { timeout: 5e3 });
11263
12098
  } catch {
11264
12099
  }
11265
12100
  }
@@ -11289,7 +12124,7 @@ async function killIdeProcess(ideId) {
11289
12124
  }
11290
12125
  if (plat === "darwin" && appName) {
11291
12126
  try {
11292
- execSync4(`pkill -9 -f "${appName}" 2>/dev/null`);
12127
+ execSync4(`pkill -9 -x "${appName}" 2>/dev/null`, { timeout: 5e3 });
11293
12128
  } catch {
11294
12129
  }
11295
12130
  } else if (plat === "win32" && winProcesses) {
@@ -11312,8 +12147,23 @@ function isIdeRunning(ideId) {
11312
12147
  if (plat === "darwin") {
11313
12148
  const appName = getMacAppIdentifiers()[ideId];
11314
12149
  if (!appName) return false;
11315
- const result = execSync4(`pgrep -f "${appName}" 2>/dev/null`, { encoding: "utf-8" });
11316
- return result.trim().length > 0;
12150
+ try {
12151
+ const result = execSync4(`pgrep -x "${appName}" 2>/dev/null`, {
12152
+ encoding: "utf-8",
12153
+ timeout: 3e3
12154
+ });
12155
+ return result.trim().length > 0;
12156
+ } catch {
12157
+ const result = execSync4(
12158
+ `osascript -e 'tell application "System Events" to count (every process whose name is "${escapeForAppleScript(appName)}")'`,
12159
+ {
12160
+ encoding: "utf-8",
12161
+ timeout: 3e3,
12162
+ stdio: ["pipe", "pipe", "pipe"]
12163
+ }
12164
+ );
12165
+ return Number.parseInt(result.trim() || "0", 10) > 0;
12166
+ }
11317
12167
  } else if (plat === "win32") {
11318
12168
  const winProcesses = getWinProcessNames()[ideId];
11319
12169
  if (!winProcesses) return false;
@@ -11462,7 +12312,8 @@ async function launchWithCdp(options = {}) {
11462
12312
  await launchLinux(targetIde, port, workspace, options.newWindow);
11463
12313
  }
11464
12314
  let cdpReady = false;
11465
- for (let i = 0; i < 30; i++) {
12315
+ const waitDeadline = Date.now() + getCdpStartupTimeoutMs(targetIde.id);
12316
+ while (Date.now() < waitDeadline) {
11466
12317
  await new Promise((r) => setTimeout(r, 500));
11467
12318
  if (await isCdpActive(port)) {
11468
12319
  cdpReady = true;
@@ -11491,14 +12342,18 @@ async function launchWithCdp(options = {}) {
11491
12342
  }
11492
12343
  async function launchMacOS(ide, port, workspace, newWindow) {
11493
12344
  const appName = getMacAppIdentifiers()[ide.id];
12345
+ const preferredMethod = getPreferredLaunchMethod(ide.id, "darwin");
11494
12346
  const args = ["--remote-debugging-port=" + port];
11495
12347
  if (newWindow) args.push("--new-window");
11496
12348
  if (workspace) args.push(workspace);
11497
- if (appName) {
12349
+ const canUseCli = !!ide.cliCommand;
12350
+ const canUseAppLauncher = !!appName;
12351
+ const useAppLauncher = preferredMethod === "app" ? canUseAppLauncher : preferredMethod === "cli" ? false : !canUseCli && canUseAppLauncher;
12352
+ if (!useAppLauncher && ide.cliCommand) {
12353
+ spawn2(ide.cliCommand, args, { detached: true, stdio: "ignore" }).unref();
12354
+ } else if (appName) {
11498
12355
  const openArgs = ["-a", appName, "--args", ...args];
11499
12356
  spawn2("open", openArgs, { detached: true, stdio: "ignore" }).unref();
11500
- } else if (ide.cliCommand) {
11501
- spawn2(ide.cliCommand, args, { detached: true, stdio: "ignore" }).unref();
11502
12357
  } else {
11503
12358
  throw new Error(`No app identifier or CLI for ${ide.displayName}`);
11504
12359
  }
@@ -11809,6 +12664,7 @@ function buildStatusSnapshot(options) {
11809
12664
  workspaces: wsState.workspaces,
11810
12665
  defaultWorkspaceId: wsState.defaultWorkspaceId,
11811
12666
  defaultWorkspacePath: wsState.defaultWorkspacePath,
12667
+ terminalSizingMode: cfg.terminalSizingMode || "measured",
11812
12668
  recentLaunches: buildRecentLaunches(recentActivity),
11813
12669
  terminalBackend,
11814
12670
  availableProviders: buildAvailableProviders(options.providerLoader)
@@ -12656,19 +13512,10 @@ var ProviderStreamAdapter = class {
12656
13512
  mode: data.mode,
12657
13513
  activeModal: data.activeModal
12658
13514
  };
12659
- if (this.provider.controls?.length) {
12660
- const cv = {};
12661
- for (const ctrl of this.provider.controls) {
12662
- if (!ctrl.readFrom) continue;
12663
- const val = data[ctrl.readFrom];
12664
- if (val !== void 0 && val !== null) {
12665
- cv[ctrl.id] = typeof val === "object" ? val.name || val.id || String(val) : val;
12666
- }
12667
- }
12668
- if (data.model && !cv["model"]) cv["model"] = data.model;
12669
- if (data.mode && !cv["mode"]) cv["mode"] = data.mode;
12670
- if (Object.keys(cv).length > 0) state.controlValues = cv;
12671
- }
13515
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
13516
+ if (controlValues) state.controlValues = controlValues;
13517
+ const effects = normalizeProviderEffects(data);
13518
+ if (effects.length > 0) state.effects = effects;
12672
13519
  if (state.messages.length > 0) {
12673
13520
  this.lastSuccessState = state;
12674
13521
  }
@@ -13194,6 +14041,8 @@ function forwardAgentStreamsToIdeInstance(instanceManager, ideType, streams) {
13194
14041
  activeModal: stream.activeModal || null,
13195
14042
  model: stream.model || void 0,
13196
14043
  mode: stream.mode || void 0,
14044
+ controlValues: stream.controlValues || void 0,
14045
+ effects: stream.effects || void 0,
13197
14046
  sessionId: stream.sessionId || stream.instanceId || void 0,
13198
14047
  title: stream.title || stream.agentName || void 0,
13199
14048
  agentType: stream.agentType || void 0,