@adhdev/daemon-standalone 0.8.22 → 0.8.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -27855,7 +27855,8 @@ var require_dist2 = __commonJS({
27855
27855
  providerSettings: isPlainObject2(parsed.providerSettings) ? parsed.providerSettings : {},
27856
27856
  ideSettings: isPlainObject2(parsed.ideSettings) ? parsed.ideSettings : {},
27857
27857
  disableUpstream: asBoolean(parsed.disableUpstream, DEFAULT_CONFIG.disableUpstream ?? false),
27858
- providerDir: asOptionalString(parsed.providerDir)
27858
+ providerDir: asOptionalString(parsed.providerDir),
27859
+ terminalSizingMode: parsed.terminalSizingMode === "fit" ? "fit" : "measured"
27859
27860
  };
27860
27861
  }
27861
27862
  function generateMachineId() {
@@ -28008,7 +28009,8 @@ var require_dist2 = __commonJS({
28008
28009
  registeredMachineId: void 0,
28009
28010
  providerSettings: {},
28010
28011
  ideSettings: {},
28011
- disableUpstream: false
28012
+ disableUpstream: false,
28013
+ terminalSizingMode: "measured"
28012
28014
  };
28013
28015
  MACHINE_ID_PREFIX = "mach_";
28014
28016
  }
@@ -28604,6 +28606,53 @@ var require_dist2 = __commonJS({
28604
28606
  function sanitizeTerminalText(str) {
28605
28607
  return stripTerminalNoise(stripAnsi(str));
28606
28608
  }
28609
+ function splitCliScreenLines(text) {
28610
+ return String(text || "").replace(/\u0007/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n").map((line) => line.replace(/\s+$/, ""));
28611
+ }
28612
+ function isPromptLikeCliLine(line) {
28613
+ const trimmed = String(line || "").trim();
28614
+ if (!trimmed) return false;
28615
+ return /^[❯›>]\s*(?:$|\S.*)$/.test(trimmed);
28616
+ }
28617
+ function buildCliScreenSnapshot(text) {
28618
+ const normalizedText = String(text || "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
28619
+ const rawLines = splitCliScreenLines(normalizedText);
28620
+ const lines = rawLines.map((line, index, arr) => {
28621
+ const trimmed = String(line || "").trim();
28622
+ return {
28623
+ index,
28624
+ fromTop: index,
28625
+ fromBottom: arr.length - index - 1,
28626
+ text: line,
28627
+ trimmed,
28628
+ isEmpty: trimmed.length === 0
28629
+ };
28630
+ });
28631
+ const nonEmptyLines = lines.filter((line) => !line.isEmpty);
28632
+ const firstNonEmptyLine = nonEmptyLines[0] ?? null;
28633
+ const lastNonEmptyLine = nonEmptyLines[nonEmptyLines.length - 1] ?? null;
28634
+ let promptLineIndex = -1;
28635
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
28636
+ if (isPromptLikeCliLine(lines[i].text)) {
28637
+ promptLineIndex = i;
28638
+ break;
28639
+ }
28640
+ }
28641
+ return {
28642
+ text: normalizedText,
28643
+ lineCount: lines.length,
28644
+ lines,
28645
+ nonEmptyLines,
28646
+ firstNonEmptyLineIndex: firstNonEmptyLine?.index ?? -1,
28647
+ lastNonEmptyLineIndex: lastNonEmptyLine?.index ?? -1,
28648
+ firstNonEmptyLine,
28649
+ lastNonEmptyLine,
28650
+ promptLineIndex,
28651
+ promptLine: promptLineIndex >= 0 ? lines[promptLineIndex] : null,
28652
+ linesAbovePrompt: promptLineIndex >= 0 ? lines.slice(0, promptLineIndex) : [...lines],
28653
+ linesBelowPrompt: promptLineIndex >= 0 ? lines.slice(promptLineIndex + 1) : []
28654
+ };
28655
+ }
28607
28656
  function computeTerminalQueryTail(buffer) {
28608
28657
  const prefixes = ["\x1B[6n", "\x1B[?6n"];
28609
28658
  const maxLength = prefixes.reduce((n, value) => Math.max(n, value.length), 0) - 1;
@@ -28849,7 +28898,9 @@ var require_dist2 = __commonJS({
28849
28898
  ready = false;
28850
28899
  startupBuffer = "";
28851
28900
  startupParseGate = false;
28901
+ startupSettleTimer = null;
28852
28902
  spawnAt = 0;
28903
+ startupFirstOutputAt = 0;
28853
28904
  // PTY I/O
28854
28905
  onPtyDataCallback = null;
28855
28906
  pendingOutputParseBuffer = "";
@@ -28890,6 +28941,7 @@ var require_dist2 = __commonJS({
28890
28941
  statusHistory = [];
28891
28942
  // ─── CLI Scripts (script-based parsing) ───
28892
28943
  cliScripts;
28944
+ runtimeSettings = {};
28893
28945
  /** Full accumulated ANSI-stripped PTY output */
28894
28946
  accumulatedBuffer = "";
28895
28947
  /** Full accumulated raw PTY output (with ANSI) */
@@ -28904,14 +28956,15 @@ var require_dist2 = __commonJS({
28904
28956
  traceSessionId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
28905
28957
  static MAX_TRACE_ENTRIES = 250;
28906
28958
  providerResolutionMeta;
28907
- static IDLE_FINISH_CONFIRM_MS = 900;
28959
+ static IDLE_FINISH_CONFIRM_MS = 2e3;
28960
+ static STATUS_ACTIVITY_HOLD_MS = 2e3;
28908
28961
  static FINISH_RETRY_DELAY_MS = 300;
28909
28962
  static MAX_FINISH_RETRIES = 2;
28910
28963
  syncMessageViews() {
28911
28964
  this.messages = [...this.committedMessages];
28912
28965
  this.structuredMessages = [...this.committedMessages];
28913
28966
  }
28914
- normalizeParsedMessages(parsedMessages) {
28967
+ hydrateParsedMessages(parsedMessages, scope) {
28915
28968
  const referenceMessages = [...this.committedMessages];
28916
28969
  const usedReferenceIndexes = /* @__PURE__ */ new Set();
28917
28970
  const now = Date.now();
@@ -28944,13 +28997,30 @@ var require_dist2 = __commonJS({
28944
28997
  const content = typeof message.content === "string" ? message.content : String(message.content || "");
28945
28998
  const parsedTimestamp = typeof message.timestamp === "number" && Number.isFinite(message.timestamp) ? message.timestamp : void 0;
28946
28999
  const referenceTimestamp = parsedTimestamp ?? findReferenceTimestamp(role, content, index);
29000
+ const fallbackTimestamp = role === "user" ? scope?.startedAt || now : this.lastOutputAt || scope?.startedAt || now;
29001
+ const timestamp = referenceTimestamp ?? fallbackTimestamp;
28947
29002
  return {
29003
+ ...message,
28948
29004
  role,
28949
29005
  content,
28950
- timestamp: referenceTimestamp ?? now
29006
+ timestamp,
29007
+ receivedAt: typeof message.receivedAt === "number" && Number.isFinite(message.receivedAt) ? message.receivedAt : timestamp
28951
29008
  };
28952
29009
  });
28953
29010
  }
29011
+ normalizeParsedMessages(parsedMessages, scope) {
29012
+ return this.hydrateParsedMessages(parsedMessages, scope).map((message) => ({
29013
+ role: message.role,
29014
+ content: message.content,
29015
+ timestamp: message.timestamp,
29016
+ receivedAt: message.receivedAt,
29017
+ kind: message.kind,
29018
+ id: message.id,
29019
+ index: message.index,
29020
+ meta: message.meta,
29021
+ senderName: message.senderName
29022
+ }));
29023
+ }
28954
29024
  sliceFromOffset(text, start) {
28955
29025
  if (!text) return "";
28956
29026
  if (!Number.isFinite(start) || start <= 0) return text;
@@ -28960,14 +29030,20 @@ var require_dist2 = __commonJS({
28960
29030
  buildParseInput(baseMessages, partialResponse, scope) {
28961
29031
  const buffer = scope ? this.sliceFromOffset(this.accumulatedBuffer, scope.bufferStart) || this.accumulatedBuffer : this.accumulatedBuffer;
28962
29032
  const rawBuffer = scope ? this.sliceFromOffset(this.accumulatedRawBuffer, scope.rawBufferStart) || this.accumulatedRawBuffer : this.accumulatedRawBuffer;
29033
+ const screenText = this.terminalScreen.getText();
29034
+ const recentBuffer = buffer.slice(-1e3) || this.recentOutputBuffer;
28963
29035
  return {
28964
29036
  buffer,
28965
29037
  rawBuffer,
28966
- recentBuffer: buffer.slice(-1e3) || this.recentOutputBuffer,
28967
- screenText: this.terminalScreen.getText(),
29038
+ recentBuffer,
29039
+ screenText,
29040
+ screen: buildCliScreenSnapshot(screenText),
29041
+ bufferScreen: buildCliScreenSnapshot(buffer),
29042
+ recentScreen: buildCliScreenSnapshot(recentBuffer),
28968
29043
  messages: [...baseMessages],
28969
29044
  partialResponse,
28970
- promptText: scope?.prompt || ""
29045
+ promptText: scope?.prompt || "",
29046
+ settings: { ...this.runtimeSettings }
28971
29047
  };
28972
29048
  }
28973
29049
  setStatus(status, trigger) {
@@ -29073,6 +29149,9 @@ var require_dist2 = __commonJS({
29073
29149
  const scriptNames = Object.keys(scripts).filter((k) => typeof scripts[k] === "function");
29074
29150
  LOG2.info("CLI", `[${this.cliType}] CLI scripts injected: [${scriptNames.join(", ")}]`);
29075
29151
  }
29152
+ updateRuntimeSettings(settings) {
29153
+ this.runtimeSettings = { ...settings };
29154
+ }
29076
29155
  // ─── Lifecycle ─────────────────────────────────
29077
29156
  setServerConn(serverConn) {
29078
29157
  this.serverConn = serverConn;
@@ -29109,7 +29188,7 @@ var require_dist2 = __commonJS({
29109
29188
  let shellArgs;
29110
29189
  const useShellUnix = !isWin && (!!spawnConfig.shell || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath) || !looksLikeMachOOrElf(binaryPath));
29111
29190
  const isCmdShim = isWin && /\.(cmd|bat)$/i.test(binaryPath);
29112
- const useShellWin = isCmdShim || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath);
29191
+ const useShellWin = !!spawnConfig.shell || isCmdShim || !path7.isAbsolute(binaryPath) || isScriptBinary(binaryPath);
29113
29192
  const useShell = isWin ? useShellWin : useShellUnix;
29114
29193
  if (useShell) {
29115
29194
  if (!spawnConfig.shell && !isWin) {
@@ -29207,6 +29286,11 @@ var require_dist2 = __commonJS({
29207
29286
  this.spawnAt = Date.now();
29208
29287
  this.startupParseGate = true;
29209
29288
  this.startupBuffer = "";
29289
+ this.startupFirstOutputAt = 0;
29290
+ if (this.startupSettleTimer) {
29291
+ clearTimeout(this.startupSettleTimer);
29292
+ this.startupSettleTimer = null;
29293
+ }
29210
29294
  this.terminalScreen.reset(24, 80);
29211
29295
  this.pendingTerminalQueryTail = "";
29212
29296
  this.currentTurnScope = null;
@@ -29220,7 +29304,8 @@ var require_dist2 = __commonJS({
29220
29304
  this.recordTrace("ready", {
29221
29305
  runtimeMeta: this.getRuntimeMetadata()
29222
29306
  });
29223
- this.setStatus("idle", "pty_ready");
29307
+ this.setStatus("starting", "pty_ready");
29308
+ this.scheduleStartupSettleCheck();
29224
29309
  this.onStatusChange?.();
29225
29310
  }
29226
29311
  // ─── Output Handling ────────────────────────────
@@ -29235,6 +29320,9 @@ var require_dist2 = __commonJS({
29235
29320
  this.lastScreenSnapshot = normalizedScreenSnapshot;
29236
29321
  this.lastScreenChangeAt = now;
29237
29322
  }
29323
+ if (this.startupParseGate && !this.startupFirstOutputAt && (cleanData.trim() || normalizedScreenSnapshot.trim())) {
29324
+ this.startupFirstOutputAt = now;
29325
+ }
29238
29326
  if (this.idleFinishCandidate && (rawData.length > 0 || cleanData.length > 0)) {
29239
29327
  this.clearIdleFinishCandidate("new_output");
29240
29328
  }
@@ -29245,6 +29333,9 @@ var require_dist2 = __commonJS({
29245
29333
  cleanPreview: this.summarizeTraceText(cleanData, 300),
29246
29334
  screenText: this.summarizeTraceText(this.terminalScreen.getText(), 1200)
29247
29335
  });
29336
+ if (this.startupParseGate) {
29337
+ this.scheduleStartupSettleCheck();
29338
+ }
29248
29339
  if (this.isWaitingForResponse && cleanData) {
29249
29340
  this.responseBuffer = (this.responseBuffer + cleanData).slice(-8e3);
29250
29341
  }
@@ -29258,27 +29349,51 @@ var require_dist2 = __commonJS({
29258
29349
  this.recentOutputBuffer = (this.recentOutputBuffer + cleanData).slice(-1e3);
29259
29350
  this.accumulatedBuffer = (this.accumulatedBuffer + cleanData).slice(-_ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
29260
29351
  this.accumulatedRawBuffer = (this.accumulatedRawBuffer + rawData).slice(-_ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
29261
- if (this.startupParseGate) {
29262
- this.startupBuffer += cleanData;
29263
- const elapsed = Date.now() - this.spawnAt;
29264
- const screenText = this.terminalScreen.getText() || "";
29265
- const startupModal = this.getStartupConfirmationModal(screenText);
29266
- const scriptStatus = startupModal ? "waiting_approval" : this.runDetectStatus(this.startupBuffer);
29267
- const hasInteractivePrompt = this.looksLikeVisibleIdlePrompt(screenText);
29268
- const startupStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
29269
- const isReady = (scriptStatus === "idle" || scriptStatus === "waiting_approval") && hasInteractivePrompt && startupStableMs >= 700 || !!startupModal && startupStableMs >= 700 || elapsed > 8e3 || this.startupBuffer.length > 12e3;
29270
- if (isReady) {
29271
- this.startupParseGate = false;
29272
- this.ready = true;
29273
- LOG2.info(
29274
- "CLI",
29275
- `[${this.cliType}] Startup ready (${elapsed}ms, scriptStatus=${scriptStatus}, prompt=${hasInteractivePrompt}, stableMs=${startupStableMs}) providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"}`
29276
- );
29277
- this.onStatusChange?.();
29278
- }
29279
- }
29352
+ this.resolveStartupState("output");
29280
29353
  this.scheduleSettle();
29281
29354
  }
29355
+ resolveStartupState(trigger) {
29356
+ if (!this.startupParseGate) return;
29357
+ const now = Date.now();
29358
+ const screenText = this.terminalScreen.getText() || "";
29359
+ const normalizedScreen = normalizeScreenSnapshot(screenText);
29360
+ const hasStartupOutput = !!this.startupFirstOutputAt || !!normalizedScreen.trim();
29361
+ if (!hasStartupOutput) return;
29362
+ const stableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
29363
+ if (stableMs < 2e3) return;
29364
+ const startupModal = this.getStartupConfirmationModal(screenText);
29365
+ this.startupParseGate = false;
29366
+ if (this.startupSettleTimer) {
29367
+ clearTimeout(this.startupSettleTimer);
29368
+ this.startupSettleTimer = null;
29369
+ }
29370
+ this.ready = true;
29371
+ if (startupModal) {
29372
+ this.activeModal = startupModal;
29373
+ this.setStatus("waiting_approval", `startup_ready:${trigger}`);
29374
+ } else {
29375
+ this.setStatus("idle", `startup_ready:${trigger}`);
29376
+ }
29377
+ LOG2.info(
29378
+ "CLI",
29379
+ `[${this.cliType}] Startup settled (${trigger}, stableMs=${stableMs}, modal=${!!startupModal}) providerDir=${this.providerResolutionMeta.providerDir || "-"} scriptDir=${this.providerResolutionMeta.scriptDir || "-"} scriptsPath=${this.providerResolutionMeta.scriptsPath || "-"}`
29380
+ );
29381
+ this.onStatusChange?.();
29382
+ }
29383
+ scheduleStartupSettleCheck() {
29384
+ if (!this.startupParseGate) return;
29385
+ if (this.startupSettleTimer) clearTimeout(this.startupSettleTimer);
29386
+ const now = Date.now();
29387
+ const stableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
29388
+ const delayMs = Math.max(250, 2050 - stableMs);
29389
+ this.startupSettleTimer = setTimeout(() => {
29390
+ this.startupSettleTimer = null;
29391
+ this.resolveStartupState("startup_timer");
29392
+ if (this.startupParseGate && Date.now() - this.spawnAt < 1e4) {
29393
+ this.scheduleStartupSettleCheck();
29394
+ }
29395
+ }, delayMs);
29396
+ }
29282
29397
  scheduleSettle() {
29283
29398
  if (this.settleTimer) clearTimeout(this.settleTimer);
29284
29399
  const settleEpoch = this.responseEpoch;
@@ -29320,6 +29435,43 @@ var require_dist2 = __commonJS({
29320
29435
  if (!text.trim()) return false;
29321
29436
  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);
29322
29437
  }
29438
+ findLastMatchingLineIndex(lines, predicate) {
29439
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
29440
+ if (predicate(lines[index])) return index;
29441
+ }
29442
+ return -1;
29443
+ }
29444
+ looksLikeClaudeGeneratingLine(line) {
29445
+ const trimmed = String(line || "").trim();
29446
+ if (!trimmed) return false;
29447
+ if (/esc to (cancel|interrupt|stop)/i.test(trimmed)) return true;
29448
+ if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+\s+\S+.*\b(?:thinking|thought for \d+s?)\b/i.test(trimmed)) return true;
29449
+ if (/^[✻✶✳✢✽⠂⠐⠒⠓⠦⠴⠶⠷⠿]+\s+[A-Z][A-Za-z-]{3,}ing\b.*(?:…|\.{3})/u.test(trimmed)) return true;
29450
+ if (/^[⏺•]\s+(?:Reading|Writing|Editing|Searching|Inspecting|Planning|Analyzing|Synthesizing|Drafting|Running|Listing|Scanning|Matching)\b.*(?:…|\.{3})/i.test(trimmed)) {
29451
+ 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);
29452
+ }
29453
+ return false;
29454
+ }
29455
+ detectClaudeGeneratingOverride(screenText, tail) {
29456
+ if (this.cliType !== "claude-cli") return false;
29457
+ const source = sanitizeTerminalText(screenText || tail || "");
29458
+ if (!source.trim()) return false;
29459
+ const allLines = source.split(/\r\n|\n|\r/g).map((line) => line.trim()).filter(Boolean);
29460
+ if (allLines.length === 0) return false;
29461
+ const recentLines = allLines.slice(-12);
29462
+ const promptIndex = this.findLastMatchingLineIndex(recentLines, (line) => /^[❯›>]\s*$/.test(line));
29463
+ const activeRegion = promptIndex >= 0 ? recentLines.slice(Math.max(0, promptIndex - 2), promptIndex) : recentLines;
29464
+ if (activeRegion.length === 0) return false;
29465
+ return activeRegion.some((line) => this.looksLikeClaudeGeneratingLine(line));
29466
+ }
29467
+ refineDetectedStatus(status, tail, screenText) {
29468
+ if (this.startupParseGate) {
29469
+ return this.getStartupConfirmationModal(screenText || "") ? "waiting_approval" : "starting";
29470
+ }
29471
+ if (status === "waiting_approval") return status;
29472
+ if (this.detectClaudeGeneratingOverride(screenText || "", tail)) return "generating";
29473
+ return status;
29474
+ }
29323
29475
  looksLikeVisibleAssistantCandidate(screenText) {
29324
29476
  const lines = sanitizeTerminalText(String(screenText || "")).split(/\r\n|\n|\r/g);
29325
29477
  for (const line of lines) {
@@ -29354,6 +29506,11 @@ var require_dist2 = __commonJS({
29354
29506
  const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
29355
29507
  return quietForMs < 1200 || screenStableMs < 1200 || !commitResult.hasAssistant;
29356
29508
  }
29509
+ hasRecentInteractiveActivity(now) {
29510
+ const quietForMs = this.lastNonEmptyOutputAt ? now - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
29511
+ const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : Number.MAX_SAFE_INTEGER;
29512
+ return quietForMs < _ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS || screenStableMs < _ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS;
29513
+ }
29357
29514
  getStartupConfirmationModal(screenText) {
29358
29515
  const text = sanitizeTerminalText(String(screenText || ""));
29359
29516
  if (!text.trim()) return null;
@@ -29381,13 +29538,14 @@ var require_dist2 = __commonJS({
29381
29538
  const startedAt = Date.now();
29382
29539
  let loggedWait = false;
29383
29540
  while (Date.now() - startedAt < maxWaitMs) {
29541
+ this.resolveStartupState("interactive_wait");
29384
29542
  const screenText = this.terminalScreen.getText() || "";
29385
29543
  const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
29386
29544
  const stableMs = this.lastScreenChangeAt ? Date.now() - this.lastScreenChangeAt : 0;
29387
29545
  const recentlyOutput = this.lastNonEmptyOutputAt ? Date.now() - this.lastNonEmptyOutputAt : Number.MAX_SAFE_INTEGER;
29388
29546
  const status = this.runDetectStatus(this.recentOutputBuffer) || this.currentStatus;
29389
29547
  const startupLikelyActive = /Welcome back|Tips for getting|Recent activity|Claude Code v\d/i.test(screenText);
29390
- const interactiveReady = hasPrompt && stableMs >= 700 && recentlyOutput >= 350 && status !== "starting" && status !== "generating";
29548
+ const interactiveReady = hasPrompt && stableMs >= 700 && recentlyOutput >= 350 && status !== "generating";
29391
29549
  if (interactiveReady) {
29392
29550
  if (loggedWait) {
29393
29551
  LOG2.info(
@@ -29426,6 +29584,10 @@ var require_dist2 = __commonJS({
29426
29584
  }
29427
29585
  const tail = this.settledBuffer;
29428
29586
  const screenText = this.terminalScreen.getText() || "";
29587
+ this.resolveStartupState("settled");
29588
+ if (this.startupParseGate) {
29589
+ return;
29590
+ }
29429
29591
  const startupModal = this.getStartupConfirmationModal(screenText);
29430
29592
  const modal = this.runParseApproval(tail) || startupModal;
29431
29593
  const rawScriptStatus = this.runDetectStatus(tail);
@@ -29488,6 +29650,28 @@ var require_dist2 = __commonJS({
29488
29650
  } else {
29489
29651
  clearPendingScriptStatus();
29490
29652
  }
29653
+ const recentInteractiveActivity = this.hasRecentInteractiveActivity(now);
29654
+ const shouldHoldGenerating = scriptStatus === "idle" && this.isWaitingForResponse && !modal && recentInteractiveActivity;
29655
+ if (shouldHoldGenerating) {
29656
+ this.clearIdleFinishCandidate("hold_generating_recent_activity");
29657
+ this.setStatus("generating", "recent_activity_hold");
29658
+ if (this.idleTimeout) clearTimeout(this.idleTimeout);
29659
+ this.idleTimeout = setTimeout(() => {
29660
+ if (this.isWaitingForResponse && this.currentStatus !== "waiting_approval") {
29661
+ this.finishResponse();
29662
+ }
29663
+ }, this.timeouts.generatingIdle);
29664
+ this.recordTrace("hold_generating_recent_activity", {
29665
+ scriptStatus,
29666
+ recentInteractiveActivity,
29667
+ lastNonEmptyOutputAt: this.lastNonEmptyOutputAt,
29668
+ lastScreenChangeAt: this.lastScreenChangeAt,
29669
+ holdMs: _ProviderCliAdapter.STATUS_ACTIVITY_HOLD_MS,
29670
+ ...this.buildTraceParseSnapshot(this.currentTurnScope, this.responseBuffer)
29671
+ });
29672
+ this.onStatusChange?.();
29673
+ return;
29674
+ }
29491
29675
  if (scriptStatus === "waiting_approval") {
29492
29676
  this.clearIdleFinishCandidate("waiting_approval");
29493
29677
  const inCooldown = this.lastApprovalResolvedAt && Date.now() - this.lastApprovalResolvedAt < this.timeouts.approvalCooldown;
@@ -29565,8 +29749,8 @@ var require_dist2 = __commonJS({
29565
29749
  const screenStableMs = this.lastScreenChangeAt ? now - this.lastScreenChangeAt : 0;
29566
29750
  const hasAssistantTurn = !!lastParsedAssistant;
29567
29751
  const assistantLength = lastParsedAssistant?.content?.length || 0;
29568
- const idleQuietThresholdMs = Math.max(220, this.timeouts.outputSettle);
29569
- const idleStableThresholdMs = Math.max(120, Math.min(220, this.timeouts.outputSettle));
29752
+ const idleQuietThresholdMs = Math.max(2e3, this.timeouts.outputSettle);
29753
+ const idleStableThresholdMs = 2e3;
29570
29754
  const idleReady = visibleIdlePrompt && !modal && hasAssistantTurn && quietForMs >= idleQuietThresholdMs && screenStableMs >= idleStableThresholdMs;
29571
29755
  const candidate = this.idleFinishCandidate;
29572
29756
  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;
@@ -29680,7 +29864,7 @@ var require_dist2 = __commonJS({
29680
29864
  this.currentTurnScope
29681
29865
  );
29682
29866
  if (parsed && Array.isArray(parsed.messages)) {
29683
- this.committedMessages = this.normalizeParsedMessages(parsed.messages);
29867
+ this.committedMessages = this.normalizeParsedMessages(parsed.messages, this.currentTurnScope);
29684
29868
  const promptForTrim = this.currentTurnScope?.prompt || getLastUserPromptText(this.committedMessages);
29685
29869
  if (promptForTrim) {
29686
29870
  const lastAssistantForTrim = [...this.committedMessages].reverse().find((message) => message.role === "assistant");
@@ -29717,11 +29901,15 @@ var require_dist2 = __commonJS({
29717
29901
  runDetectStatus(text) {
29718
29902
  if (!this.cliScripts?.detectStatus) return null;
29719
29903
  try {
29720
- return this.cliScripts.detectStatus({
29904
+ const screenText = this.terminalScreen.getText();
29905
+ const status = this.cliScripts.detectStatus({
29721
29906
  tail: text.slice(-500),
29722
- screenText: this.terminalScreen.getText(),
29723
- rawBuffer: this.accumulatedRawBuffer
29907
+ screenText,
29908
+ rawBuffer: this.accumulatedRawBuffer,
29909
+ screen: buildCliScreenSnapshot(screenText),
29910
+ tailScreen: buildCliScreenSnapshot(text.slice(-500))
29724
29911
  });
29912
+ return this.refineDetectedStatus(status, text, screenText || "");
29725
29913
  } catch (e) {
29726
29914
  LOG2.warn("CLI", `[${this.cliType}] detectStatus error: ${e.message}`);
29727
29915
  return null;
@@ -29730,11 +29918,16 @@ var require_dist2 = __commonJS({
29730
29918
  runParseApproval(tail) {
29731
29919
  if (!this.cliScripts?.parseApproval) return null;
29732
29920
  try {
29921
+ const screenText = this.terminalScreen.getText();
29922
+ const buffer = screenText || this.accumulatedBuffer;
29733
29923
  return this.cliScripts.parseApproval({
29734
- buffer: this.terminalScreen.getText() || this.accumulatedBuffer,
29735
- screenText: this.terminalScreen.getText(),
29924
+ buffer,
29925
+ screenText,
29736
29926
  rawBuffer: this.accumulatedRawBuffer,
29737
- tail
29927
+ tail,
29928
+ screen: buildCliScreenSnapshot(screenText),
29929
+ bufferScreen: buildCliScreenSnapshot(buffer),
29930
+ tailScreen: buildCliScreenSnapshot(tail)
29738
29931
  });
29739
29932
  } catch (e) {
29740
29933
  LOG2.warn("CLI", `[${this.cliType}] parseApproval error: ${e.message}`);
@@ -29750,6 +29943,21 @@ var require_dist2 = __commonJS({
29750
29943
  activeModal: this.activeModal
29751
29944
  };
29752
29945
  }
29946
+ seedCommittedMessages(messages) {
29947
+ const normalized = (Array.isArray(messages) ? messages : []).filter((message) => message && (message.role === "user" || message.role === "assistant")).map((message) => ({
29948
+ role: message.role,
29949
+ content: typeof message.content === "string" ? message.content : String(message.content || ""),
29950
+ timestamp: typeof message.timestamp === "number" && Number.isFinite(message.timestamp) ? message.timestamp : void 0,
29951
+ receivedAt: typeof message.receivedAt === "number" && Number.isFinite(message.receivedAt) ? message.receivedAt : void 0,
29952
+ kind: typeof message.kind === "string" ? message.kind : void 0,
29953
+ id: typeof message.id === "string" ? message.id : void 0,
29954
+ index: typeof message.index === "number" ? message.index : void 0,
29955
+ meta: message.meta && typeof message.meta === "object" ? { ...message.meta } : void 0,
29956
+ senderName: typeof message.senderName === "string" ? message.senderName : void 0
29957
+ }));
29958
+ this.committedMessages = normalized;
29959
+ this.syncMessageViews();
29960
+ }
29753
29961
  /**
29754
29962
  * Script-based full parse — returns ReadChatResult.
29755
29963
  * Called by command handler / dashboard for rich content rendering.
@@ -29760,12 +29968,20 @@ var require_dist2 = __commonJS({
29760
29968
  this.responseBuffer,
29761
29969
  this.currentTurnScope
29762
29970
  );
29971
+ const shouldPreferCommittedMessages = !this.currentTurnScope && this.currentStatus === "idle" && !this.activeModal;
29763
29972
  if (parsed && Array.isArray(parsed.messages)) {
29973
+ const hydratedMessages = shouldPreferCommittedMessages ? this.committedMessages.map((message, index) => ({
29974
+ ...message,
29975
+ id: message.id || `msg_${index}`,
29976
+ index: typeof message.index === "number" ? message.index : index,
29977
+ kind: message.kind || "standard",
29978
+ receivedAt: typeof message.receivedAt === "number" ? message.receivedAt : message.timestamp
29979
+ })) : this.hydrateParsedMessages(parsed.messages, this.currentTurnScope);
29764
29980
  return {
29765
29981
  id: parsed.id || "cli_session",
29766
29982
  status: parsed.status || this.currentStatus,
29767
29983
  title: parsed.title || this.cliName,
29768
- messages: parsed.messages,
29984
+ messages: hydratedMessages,
29769
29985
  activeModal: parsed.activeModal ?? this.activeModal,
29770
29986
  providerSessionId: typeof parsed.providerSessionId === "string" ? parsed.providerSessionId : void 0
29771
29987
  };
@@ -29786,11 +30002,30 @@ var require_dist2 = __commonJS({
29786
30002
  activeModal: this.activeModal
29787
30003
  };
29788
30004
  }
30005
+ async invokeScript(scriptName, args) {
30006
+ const fn2 = this.cliScripts?.[scriptName];
30007
+ if (typeof fn2 !== "function") {
30008
+ throw new Error(`CLI script '${scriptName}' not available`);
30009
+ }
30010
+ const input = this.buildParseInput(
30011
+ this.committedMessages,
30012
+ this.responseBuffer,
30013
+ this.currentTurnScope
30014
+ );
30015
+ return await Promise.resolve(fn2({
30016
+ ...input,
30017
+ args: args && typeof args === "object" ? { ...args } : {}
30018
+ }));
30019
+ }
29789
30020
  parseCurrentTranscript(baseMessages, partialResponse, scope) {
29790
30021
  if (!this.cliScripts?.parseOutput) return null;
29791
30022
  try {
29792
30023
  const input = this.buildParseInput(baseMessages, partialResponse, scope);
29793
30024
  const parsed = this.cliScripts.parseOutput(input);
30025
+ const refinedStatus = this.refineDetectedStatus(typeof parsed?.status === "string" ? parsed.status : null, input.recentBuffer, input.screenText);
30026
+ if (parsed && refinedStatus && parsed.status !== refinedStatus) {
30027
+ parsed.status = refinedStatus;
30028
+ }
29794
30029
  const promptForTrim = scope?.prompt || getLastUserPromptText(baseMessages);
29795
30030
  if (parsed && Array.isArray(parsed.messages) && promptForTrim) {
29796
30031
  const lastAssistant = [...parsed.messages].reverse().find((message) => message?.role === "assistant" && typeof message.content === "string");
@@ -29837,12 +30072,23 @@ ${data.message || ""}`.trim();
29837
30072
  if (this.startupParseGate) {
29838
30073
  const deadline = Date.now() + 1e4;
29839
30074
  while (this.startupParseGate && Date.now() < deadline) {
30075
+ this.resolveStartupState("send_wait");
29840
30076
  await new Promise((resolve9) => setTimeout(resolve9, 50));
29841
30077
  }
29842
30078
  }
30079
+ await this.waitForInteractivePrompt();
30080
+ if (!this.ready) {
30081
+ this.resolveStartupState("send_precheck");
30082
+ const screenText = this.terminalScreen.getText() || "";
30083
+ const hasPrompt = this.looksLikeVisibleIdlePrompt(screenText);
30084
+ if (hasPrompt && this.currentStatus === "idle") {
30085
+ this.ready = true;
30086
+ this.startupParseGate = false;
30087
+ LOG2.info("CLI", `[${this.cliType}] sendMessage recovered idle prompt readiness`);
30088
+ }
30089
+ }
29843
30090
  if (!this.ready) throw new Error(`${this.cliName} not ready (status: ${this.currentStatus})`);
29844
30091
  if (this.isWaitingForResponse) return;
29845
- await this.waitForInteractivePrompt();
29846
30092
  const blockingModal = this.activeModal || this.getStartupConfirmationModal(this.terminalScreen.getText() || "");
29847
30093
  if (blockingModal || this.currentStatus === "waiting_approval") {
29848
30094
  throw new Error(`${this.cliName} is awaiting confirmation before it can accept a prompt`);
@@ -29886,8 +30132,6 @@ ${data.message || ""}`.trim();
29886
30132
  }
29887
30133
  this.responseEpoch += 1;
29888
30134
  this.responseSettleIgnoreUntil = Date.now() + submitDelayMs + this.timeouts.outputSettle + 250;
29889
- this.setStatus("generating", "sendMessage");
29890
- this.onStatusChange?.();
29891
30135
  const startResponseTimeout = () => {
29892
30136
  if (this.responseTimeout) clearTimeout(this.responseTimeout);
29893
30137
  this.responseTimeout = setTimeout(() => {
@@ -29906,7 +30150,7 @@ ${data.message || ""}`.trim();
29906
30150
  const retrySubmitIfStuck = (attempt) => {
29907
30151
  this.submitRetryTimer = null;
29908
30152
  if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
29909
- if (this.currentStatus !== "generating") return;
30153
+ if (this.currentStatus === "waiting_approval") return;
29910
30154
  if ((this.responseBuffer || "").trim()) return;
29911
30155
  const screenText = this.terminalScreen.getText();
29912
30156
  if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
@@ -29941,7 +30185,7 @@ ${data.message || ""}`.trim();
29941
30185
  this.submitRetryTimer = setTimeout(() => {
29942
30186
  this.submitRetryTimer = null;
29943
30187
  if (!this.ptyProcess || !this.isWaitingForResponse || this.submitRetryUsed) return;
29944
- if (this.currentStatus !== "generating") return;
30188
+ if (this.currentStatus === "waiting_approval") return;
29945
30189
  if ((this.responseBuffer || "").trim()) return;
29946
30190
  const screenText = this.terminalScreen.getText();
29947
30191
  if (!promptLikelyVisible(screenText, normalizedPromptSnippet)) return;
@@ -32355,198 +32599,93 @@ ${data.message || ""}`.trim();
32355
32599
  }
32356
32600
  }
32357
32601
  };
32358
- var ExtensionProviderInstance = class {
32359
- type;
32360
- category = "extension";
32361
- provider;
32362
- context = null;
32363
- settings = {};
32364
- events = [];
32365
- // status
32366
- currentStatus = "idle";
32367
- agentStreams = [];
32368
- messages = [];
32369
- activeModal = null;
32370
- currentModel = "";
32371
- currentMode = "";
32372
- controlValues = {};
32373
- lastAgentStatus = "idle";
32374
- generatingStartedAt = 0;
32375
- monitor;
32376
- // meta
32377
- instanceId;
32378
- ideType = "";
32379
- chatId = null;
32380
- chatTitle = null;
32381
- agentName = "";
32382
- extensionId = "";
32383
- constructor(provider) {
32384
- this.type = provider.type;
32385
- this.provider = provider;
32386
- this.instanceId = crypto.randomUUID();
32387
- this.monitor = new StatusMonitor();
32388
- }
32389
- // ─── Lifecycle ──────────────────────────────────
32390
- async init(context) {
32391
- this.context = context;
32392
- this.settings = context.settings || {};
32393
- this.monitor.updateConfig({
32394
- approvalAlert: this.settings.approvalAlert !== false,
32395
- longGeneratingAlert: this.settings.longGeneratingAlert !== false,
32396
- longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
32397
- });
32398
- }
32399
- async onTick() {
32400
- if (!this.context?.cdp?.isConnected) return;
32401
- }
32402
- getState() {
32403
- return {
32404
- type: this.type,
32405
- name: this.provider.name,
32406
- category: "extension",
32407
- status: this.currentStatus,
32408
- activeChat: this.messages.length > 0 ? {
32409
- id: this.chatId || this.instanceId,
32410
- title: this.chatTitle || this.agentName || this.provider.name,
32411
- status: this.currentStatus,
32412
- messages: this.messages,
32413
- activeModal: this.activeModal,
32414
- inputContent: ""
32415
- } : null,
32416
- currentModel: this.currentModel || void 0,
32417
- currentPlan: this.currentMode || void 0,
32418
- controlValues: this.controlValues,
32419
- providerControls: this.provider.controls,
32420
- agentStreams: this.agentStreams,
32421
- instanceId: this.instanceId,
32422
- lastUpdated: Date.now(),
32423
- settings: this.settings,
32424
- pendingEvents: this.flushEvents()
32425
- };
32426
- }
32427
- onEvent(event, data) {
32428
- if (event === "stream_update") {
32429
- if (data?.streams) this.agentStreams = data.streams;
32430
- if (data?.messages) this.messages = data.messages;
32431
- if (data?.activeModal !== void 0) this.activeModal = data.activeModal;
32432
- if (data?.model) this.currentModel = data.model;
32433
- if (data?.mode) this.currentMode = data.mode;
32434
- if (data?.controlValues) this.controlValues = data.controlValues;
32435
- if (typeof data?.sessionId === "string" && data.sessionId.trim()) this.chatId = data.sessionId;
32436
- if (typeof data?.title === "string" && data.title.trim()) this.chatTitle = data.title;
32437
- if (typeof data?.agentName === "string" && data.agentName.trim()) this.agentName = data.agentName;
32438
- if (typeof data?.extensionId === "string" && data.extensionId.trim()) this.extensionId = data.extensionId;
32439
- if (data?.status) {
32440
- const newStatus = data.status;
32441
- this.detectTransition(newStatus, data);
32442
- this.currentStatus = newStatus;
32443
- }
32444
- } else if (event === "stream_reset") {
32445
- this.resetStreamState();
32446
- } else if (event === "extension_connected") {
32447
- this.ideType = data?.ideType || "";
32602
+ function extractProviderControlValues(controls, data) {
32603
+ if (!data || typeof data !== "object") return void 0;
32604
+ const values = {};
32605
+ const explicit = data.controlValues;
32606
+ if (explicit && typeof explicit === "object") {
32607
+ for (const [key, value] of Object.entries(explicit)) {
32608
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
32609
+ values[key] = value;
32610
+ }
32611
+ }
32612
+ }
32613
+ for (const ctrl of controls || []) {
32614
+ if (!ctrl.readFrom) continue;
32615
+ const rawValue = data[ctrl.readFrom];
32616
+ if (rawValue === void 0 || rawValue === null) continue;
32617
+ values[ctrl.id] = normalizeControlValue(rawValue);
32618
+ }
32619
+ if (data.model !== void 0 && values.model === void 0) values.model = normalizeControlValue(data.model);
32620
+ if (data.mode !== void 0 && values.mode === void 0) values.mode = normalizeControlValue(data.mode);
32621
+ return Object.keys(values).length > 0 ? values : void 0;
32622
+ }
32623
+ function normalizeProviderEffects(data) {
32624
+ const rawEffects = Array.isArray(data?.effects) ? data.effects : [];
32625
+ const effects = [];
32626
+ for (const raw of rawEffects) {
32627
+ if (!raw || typeof raw !== "object") continue;
32628
+ const type = raw.type;
32629
+ if (type === "message" && raw.message && typeof raw.message === "object") {
32630
+ const content = raw.message.content;
32631
+ if (typeof content !== "string" && !Array.isArray(content)) continue;
32632
+ effects.push({
32633
+ type: "message",
32634
+ id: typeof raw.id === "string" ? raw.id : void 0,
32635
+ when: raw.when === "turn_completed" ? "turn_completed" : "immediate",
32636
+ persist: raw.persist !== false,
32637
+ message: {
32638
+ role: raw.message.role === "assistant" || raw.message.role === "user" ? raw.message.role : "system",
32639
+ content,
32640
+ kind: typeof raw.message.kind === "string" ? raw.message.kind : void 0,
32641
+ senderName: typeof raw.message.senderName === "string" ? raw.message.senderName : void 0
32642
+ }
32643
+ });
32644
+ continue;
32448
32645
  }
32449
- }
32450
- dispose() {
32451
- this.agentStreams = [];
32452
- this.messages = [];
32453
- this.monitor.reset();
32454
- }
32455
- /** Query UUID instanceId */
32456
- getInstanceId() {
32457
- return this.instanceId;
32458
- }
32459
- // ─── status transition detect ──────────────────────────────
32460
- detectTransition(newStatus, data) {
32461
- const now = Date.now();
32462
- const agentStatus = newStatus === "streaming" || newStatus === "generating" ? "generating" : newStatus === "waiting_approval" ? "waiting_approval" : "idle";
32463
- const lastMsg = Array.isArray(data?.messages) && data.messages.length > 0 ? data.messages[data.messages.length - 1] : null;
32464
- const progressFingerprint = agentStatus === "generating" ? `${lastMsg?.role || ""}:${typeof lastMsg?.content === "string" ? lastMsg.content : JSON.stringify(lastMsg?.content || "")}`.slice(-2e3) : void 0;
32465
- if (agentStatus !== this.lastAgentStatus) {
32466
- if (this.lastAgentStatus === "idle" && agentStatus === "generating") {
32467
- this.generatingStartedAt = now;
32468
- this.pushEvent({
32469
- event: "agent:generating_started",
32470
- chatTitle: this.resolveChatTitle(data),
32471
- timestamp: now,
32472
- ideType: this.ideType || this.type,
32473
- agentType: this.type,
32474
- agentName: this.agentName || this.provider.name,
32475
- extensionId: this.extensionId || this.type
32476
- });
32477
- } else if (agentStatus === "waiting_approval") {
32478
- if (!this.generatingStartedAt) this.generatingStartedAt = now;
32479
- this.pushEvent({
32480
- event: "agent:waiting_approval",
32481
- chatTitle: this.resolveChatTitle(data),
32482
- timestamp: now,
32483
- ideType: this.ideType || this.type,
32484
- agentType: this.type,
32485
- agentName: this.agentName || this.provider.name,
32486
- extensionId: this.extensionId || this.type,
32487
- modalMessage: data?.activeModal?.message,
32488
- modalButtons: data?.activeModal?.buttons
32489
- });
32490
- } else if (agentStatus === "idle" && (this.lastAgentStatus === "generating" || this.lastAgentStatus === "waiting_approval")) {
32491
- const duration3 = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
32492
- this.pushEvent({
32493
- event: "agent:generating_completed",
32494
- chatTitle: this.resolveChatTitle(data),
32495
- duration: duration3,
32496
- timestamp: now,
32497
- ideType: this.ideType || this.type,
32498
- agentType: this.type,
32499
- agentName: this.agentName || this.provider.name,
32500
- extensionId: this.extensionId || this.type
32501
- });
32502
- this.generatingStartedAt = 0;
32503
- }
32504
- this.lastAgentStatus = agentStatus;
32646
+ if (type === "toast" && raw.toast && typeof raw.toast.message === "string") {
32647
+ effects.push({
32648
+ type: "toast",
32649
+ id: typeof raw.id === "string" ? raw.id : void 0,
32650
+ when: raw.when === "turn_completed" ? "turn_completed" : "immediate",
32651
+ persist: raw.persist !== false,
32652
+ toast: {
32653
+ level: raw.toast.level === "success" || raw.toast.level === "warning" ? raw.toast.level : "info",
32654
+ message: raw.toast.message
32655
+ }
32656
+ });
32657
+ continue;
32505
32658
  }
32506
- const agentKey = `${this.type}:ext`;
32507
- const monitorEvents = this.monitor.check(agentKey, agentStatus, now, progressFingerprint);
32508
- for (const me of monitorEvents) {
32509
- this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
32659
+ if (type === "notification" && raw.notification && typeof raw.notification.body === "string") {
32660
+ effects.push({
32661
+ type: "notification",
32662
+ id: typeof raw.id === "string" ? raw.id : void 0,
32663
+ when: raw.when === "turn_completed" ? "turn_completed" : "immediate",
32664
+ persist: raw.persist !== false,
32665
+ notification: {
32666
+ title: typeof raw.notification.title === "string" ? raw.notification.title : void 0,
32667
+ body: raw.notification.body,
32668
+ level: raw.notification.level === "success" || raw.notification.level === "warning" ? raw.notification.level : "info",
32669
+ channels: Array.isArray(raw.notification.channels) ? raw.notification.channels.filter((channel) => channel === "bubble" || channel === "toast" || channel === "browser") : void 0,
32670
+ preferenceKey: raw.notification.preferenceKey === "disconnect" || raw.notification.preferenceKey === "completion" || raw.notification.preferenceKey === "approval" || raw.notification.preferenceKey === "browser" ? raw.notification.preferenceKey : void 0,
32671
+ bubbleContent: typeof raw.notification.bubbleContent === "string" || Array.isArray(raw.notification.bubbleContent) ? raw.notification.bubbleContent : void 0
32672
+ }
32673
+ });
32510
32674
  }
32511
32675
  }
32512
- pushEvent(event) {
32513
- this.events.push(event);
32514
- if (this.events.length > 50) this.events = this.events.slice(-50);
32676
+ return effects;
32677
+ }
32678
+ function normalizeControlValue(value) {
32679
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
32680
+ return value;
32515
32681
  }
32516
- flushEvents() {
32517
- const events = [...this.events];
32518
- this.events = [];
32519
- return events;
32682
+ if (value && typeof value === "object") {
32683
+ if (typeof value.label === "string") return value.label;
32684
+ if (typeof value.name === "string") return value.name;
32685
+ if (typeof value.id === "string") return value.id;
32520
32686
  }
32521
- resolveChatTitle(data) {
32522
- const title = typeof data?.title === "string" && data.title.trim() ? data.title.trim() : this.chatTitle;
32523
- return title || this.agentName || this.provider.name;
32524
- }
32525
- resetStreamState() {
32526
- if (this.currentStatus !== "idle") {
32527
- this.detectTransition("idle", {
32528
- title: this.chatTitle,
32529
- agentName: this.agentName,
32530
- extensionId: this.extensionId,
32531
- messages: this.messages
32532
- });
32533
- }
32534
- this.agentStreams = [];
32535
- this.messages = [];
32536
- this.activeModal = null;
32537
- this.currentModel = "";
32538
- this.currentMode = "";
32539
- this.controlValues = {};
32540
- this.currentStatus = "idle";
32541
- this.chatId = null;
32542
- this.chatTitle = null;
32543
- this.agentName = "";
32544
- this.extensionId = "";
32545
- this.lastAgentStatus = "idle";
32546
- this.generatingStartedAt = 0;
32547
- this.monitor.reset();
32548
- }
32549
- };
32687
+ return String(value);
32688
+ }
32550
32689
  var fs32 = __toESM2(require("fs"));
32551
32690
  var path52 = __toESM2(require("path"));
32552
32691
  var os52 = __toESM2(require("os"));
@@ -32771,54 +32910,381 @@ ${data.message || ""}`.trim();
32771
32910
  files.push(file2);
32772
32911
  groupedFiles.set(historySessionId, files);
32773
32912
  }
32774
- const summaries = [];
32775
- for (const [historySessionId, files] of groupedFiles.entries()) {
32776
- let messageCount = 0;
32777
- let firstMessageAt = 0;
32778
- let lastMessageAt = 0;
32779
- let sessionTitle = "";
32780
- let preview = "";
32781
- for (const file2 of files.sort()) {
32782
- const filePath = path52.join(dir, file2);
32783
- const content = fs32.readFileSync(filePath, "utf-8");
32784
- const lines = content.split("\n").filter(Boolean);
32785
- for (const line of lines) {
32786
- let parsed = null;
32787
- try {
32788
- parsed = JSON.parse(line);
32789
- } catch {
32790
- parsed = null;
32791
- }
32792
- if (!parsed || parsed.historySessionId !== historySessionId) continue;
32793
- messageCount += 1;
32794
- if (!firstMessageAt || parsed.receivedAt < firstMessageAt) firstMessageAt = parsed.receivedAt;
32795
- if (!lastMessageAt || parsed.receivedAt > lastMessageAt) lastMessageAt = parsed.receivedAt;
32796
- if (parsed.sessionTitle) sessionTitle = parsed.sessionTitle;
32797
- if (parsed.role !== "system" && parsed.content.trim()) preview = parsed.content.trim();
32798
- }
32913
+ const summaries = [];
32914
+ for (const [historySessionId, files] of groupedFiles.entries()) {
32915
+ let messageCount = 0;
32916
+ let firstMessageAt = 0;
32917
+ let lastMessageAt = 0;
32918
+ let sessionTitle = "";
32919
+ let preview = "";
32920
+ for (const file2 of files.sort()) {
32921
+ const filePath = path52.join(dir, file2);
32922
+ const content = fs32.readFileSync(filePath, "utf-8");
32923
+ const lines = content.split("\n").filter(Boolean);
32924
+ for (const line of lines) {
32925
+ let parsed = null;
32926
+ try {
32927
+ parsed = JSON.parse(line);
32928
+ } catch {
32929
+ parsed = null;
32930
+ }
32931
+ if (!parsed || parsed.historySessionId !== historySessionId) continue;
32932
+ messageCount += 1;
32933
+ if (!firstMessageAt || parsed.receivedAt < firstMessageAt) firstMessageAt = parsed.receivedAt;
32934
+ if (!lastMessageAt || parsed.receivedAt > lastMessageAt) lastMessageAt = parsed.receivedAt;
32935
+ if (parsed.sessionTitle) sessionTitle = parsed.sessionTitle;
32936
+ if (parsed.role !== "system" && parsed.content.trim()) preview = parsed.content.trim();
32937
+ }
32938
+ }
32939
+ if (messageCount === 0 || !lastMessageAt) continue;
32940
+ summaries.push({
32941
+ historySessionId,
32942
+ sessionTitle: sessionTitle || void 0,
32943
+ messageCount,
32944
+ firstMessageAt,
32945
+ lastMessageAt,
32946
+ preview: preview || void 0
32947
+ });
32948
+ }
32949
+ summaries.sort((a, b2) => b2.lastMessageAt - a.lastMessageAt);
32950
+ const offset = Math.max(0, options.offset || 0);
32951
+ const limit = Math.max(1, options.limit || 30);
32952
+ const sliced = summaries.slice(offset, offset + limit);
32953
+ return {
32954
+ sessions: sliced,
32955
+ hasMore: summaries.length > offset + limit
32956
+ };
32957
+ } catch {
32958
+ return { sessions: [], hasMore: false };
32959
+ }
32960
+ }
32961
+ var ExtensionProviderInstance = class {
32962
+ type;
32963
+ category = "extension";
32964
+ provider;
32965
+ context = null;
32966
+ settings = {};
32967
+ events = [];
32968
+ // status
32969
+ currentStatus = "idle";
32970
+ agentStreams = [];
32971
+ messages = [];
32972
+ activeModal = null;
32973
+ currentModel = "";
32974
+ currentMode = "";
32975
+ controlValues = {};
32976
+ appliedEffectKeys = /* @__PURE__ */ new Set();
32977
+ runtimeMessages = [];
32978
+ lastAgentStatus = "idle";
32979
+ generatingStartedAt = 0;
32980
+ monitor;
32981
+ historyWriter;
32982
+ // meta
32983
+ instanceId;
32984
+ ideType = "";
32985
+ chatId = null;
32986
+ chatTitle = null;
32987
+ agentName = "";
32988
+ extensionId = "";
32989
+ constructor(provider) {
32990
+ this.type = provider.type;
32991
+ this.provider = provider;
32992
+ this.instanceId = crypto.randomUUID();
32993
+ this.monitor = new StatusMonitor();
32994
+ this.historyWriter = new ChatHistoryWriter();
32995
+ }
32996
+ // ─── Lifecycle ──────────────────────────────────
32997
+ async init(context) {
32998
+ this.context = context;
32999
+ this.settings = context.settings || {};
33000
+ this.monitor.updateConfig({
33001
+ approvalAlert: this.settings.approvalAlert !== false,
33002
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
33003
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
33004
+ });
33005
+ }
33006
+ async onTick() {
33007
+ if (!this.context?.cdp?.isConnected) return;
33008
+ }
33009
+ getState() {
33010
+ return {
33011
+ type: this.type,
33012
+ name: this.provider.name,
33013
+ category: "extension",
33014
+ status: this.currentStatus,
33015
+ activeChat: this.messages.length > 0 || this.runtimeMessages.length > 0 ? {
33016
+ id: this.chatId || this.instanceId,
33017
+ title: this.chatTitle || this.agentName || this.provider.name,
33018
+ status: this.currentStatus,
33019
+ messages: this.mergeConversationMessages(this.messages),
33020
+ activeModal: this.activeModal,
33021
+ inputContent: ""
33022
+ } : null,
33023
+ currentModel: this.currentModel || void 0,
33024
+ currentPlan: this.currentMode || void 0,
33025
+ controlValues: this.controlValues,
33026
+ providerControls: this.provider.controls,
33027
+ agentStreams: this.agentStreams,
33028
+ instanceId: this.instanceId,
33029
+ lastUpdated: Date.now(),
33030
+ settings: this.settings,
33031
+ pendingEvents: this.flushEvents()
33032
+ };
33033
+ }
33034
+ onEvent(event, data) {
33035
+ if (event === "stream_update") {
33036
+ if (data?.streams) this.agentStreams = data.streams;
33037
+ if (data?.messages) this.messages = data.messages;
33038
+ if (data?.activeModal !== void 0) this.activeModal = data.activeModal;
33039
+ if (data?.model) this.currentModel = data.model;
33040
+ if (data?.mode) this.currentMode = data.mode;
33041
+ const controlValues = extractProviderControlValues(this.provider.controls, data) || data?.controlValues;
33042
+ if (controlValues) this.controlValues = controlValues;
33043
+ if (typeof data?.sessionId === "string" && data.sessionId.trim()) this.chatId = data.sessionId;
33044
+ if (typeof data?.title === "string" && data.title.trim()) this.chatTitle = data.title;
33045
+ if (typeof data?.agentName === "string" && data.agentName.trim()) this.agentName = data.agentName;
33046
+ if (typeof data?.extensionId === "string" && data.extensionId.trim()) this.extensionId = data.extensionId;
33047
+ if (data?.status) {
33048
+ const newStatus = data.status;
33049
+ this.detectTransition(newStatus, data);
33050
+ this.currentStatus = newStatus;
33051
+ }
33052
+ } else if (event === "stream_reset") {
33053
+ this.resetStreamState();
33054
+ } else if (event === "extension_connected") {
33055
+ this.ideType = data?.ideType || "";
33056
+ } else if (event === "provider_state_patch" && data && typeof data === "object") {
33057
+ this.applyProviderResponse(data, { phase: "immediate" });
33058
+ }
33059
+ }
33060
+ dispose() {
33061
+ this.agentStreams = [];
33062
+ this.messages = [];
33063
+ this.monitor.reset();
33064
+ this.appliedEffectKeys.clear();
33065
+ this.runtimeMessages = [];
33066
+ }
33067
+ updateSettings(newSettings) {
33068
+ this.settings = { ...newSettings };
33069
+ this.monitor.updateConfig({
33070
+ approvalAlert: this.settings.approvalAlert !== false,
33071
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
33072
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
33073
+ });
33074
+ }
33075
+ /** Query UUID instanceId */
33076
+ getInstanceId() {
33077
+ return this.instanceId;
33078
+ }
33079
+ // ─── status transition detect ──────────────────────────────
33080
+ detectTransition(newStatus, data) {
33081
+ const now = Date.now();
33082
+ const agentStatus = newStatus === "streaming" || newStatus === "generating" ? "generating" : newStatus === "waiting_approval" ? "waiting_approval" : "idle";
33083
+ const lastMsg = Array.isArray(data?.messages) && data.messages.length > 0 ? data.messages[data.messages.length - 1] : null;
33084
+ const progressFingerprint = agentStatus === "generating" ? `${lastMsg?.role || ""}:${typeof lastMsg?.content === "string" ? lastMsg.content : JSON.stringify(lastMsg?.content || "")}`.slice(-2e3) : void 0;
33085
+ const previousStatus = this.lastAgentStatus;
33086
+ if (agentStatus !== this.lastAgentStatus) {
33087
+ if (this.lastAgentStatus === "idle" && agentStatus === "generating") {
33088
+ this.generatingStartedAt = now;
33089
+ this.pushEvent({
33090
+ event: "agent:generating_started",
33091
+ chatTitle: this.resolveChatTitle(data),
33092
+ timestamp: now,
33093
+ ideType: this.ideType || this.type,
33094
+ agentType: this.type,
33095
+ agentName: this.agentName || this.provider.name,
33096
+ extensionId: this.extensionId || this.type
33097
+ });
33098
+ } else if (agentStatus === "waiting_approval") {
33099
+ if (!this.generatingStartedAt) this.generatingStartedAt = now;
33100
+ this.pushEvent({
33101
+ event: "agent:waiting_approval",
33102
+ chatTitle: this.resolveChatTitle(data),
33103
+ timestamp: now,
33104
+ ideType: this.ideType || this.type,
33105
+ agentType: this.type,
33106
+ agentName: this.agentName || this.provider.name,
33107
+ extensionId: this.extensionId || this.type,
33108
+ modalMessage: data?.activeModal?.message,
33109
+ modalButtons: data?.activeModal?.buttons
33110
+ });
33111
+ } else if (agentStatus === "idle" && (this.lastAgentStatus === "generating" || this.lastAgentStatus === "waiting_approval")) {
33112
+ const duration3 = this.generatingStartedAt ? Math.round((now - this.generatingStartedAt) / 1e3) : 0;
33113
+ this.pushEvent({
33114
+ event: "agent:generating_completed",
33115
+ chatTitle: this.resolveChatTitle(data),
33116
+ duration: duration3,
33117
+ timestamp: now,
33118
+ ideType: this.ideType || this.type,
33119
+ agentType: this.type,
33120
+ agentName: this.agentName || this.provider.name,
33121
+ extensionId: this.extensionId || this.type
33122
+ });
33123
+ this.generatingStartedAt = 0;
33124
+ }
33125
+ this.lastAgentStatus = agentStatus;
33126
+ }
33127
+ this.applyProviderResponse(data, {
33128
+ phase: agentStatus === "idle" && (previousStatus === "generating" || previousStatus === "waiting_approval") ? "turn_completed" : "immediate"
33129
+ });
33130
+ const agentKey = `${this.type}:ext`;
33131
+ const monitorEvents = this.monitor.check(agentKey, agentStatus, now, progressFingerprint);
33132
+ for (const me of monitorEvents) {
33133
+ this.pushEvent({ event: me.type, agentKey: me.agentKey, message: me.message, elapsedSec: me.elapsedSec, timestamp: me.timestamp });
33134
+ }
33135
+ }
33136
+ pushEvent(event) {
33137
+ this.events.push(event);
33138
+ if (this.events.length > 50) this.events = this.events.slice(-50);
33139
+ }
33140
+ applyProviderResponse(data, options) {
33141
+ if (!data || typeof data !== "object") return;
33142
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
33143
+ if (controlValues) this.controlValues = { ...this.controlValues, ...controlValues };
33144
+ const effects = normalizeProviderEffects(data);
33145
+ for (const effect of effects) {
33146
+ const effectWhen = effect.when || "immediate";
33147
+ if (effectWhen === "turn_completed" && options.phase !== "turn_completed") continue;
33148
+ if (effectWhen === "immediate" && options.phase === "turn_completed") continue;
33149
+ const effectKey = this.getEffectDedupKey(effect);
33150
+ if (this.appliedEffectKeys.has(effectKey)) continue;
33151
+ this.appliedEffectKeys.add(effectKey);
33152
+ if (effect.persist !== false) {
33153
+ const persisted = this.getPersistedEffectContent(effect);
33154
+ if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
33155
+ }
33156
+ if (effect.type === "message" && effect.message) {
33157
+ this.pushEvent({
33158
+ event: "provider:message",
33159
+ timestamp: Date.now(),
33160
+ content: typeof effect.message.content === "string" ? effect.message.content : JSON.stringify(effect.message.content),
33161
+ role: effect.message.role || "system",
33162
+ kind: effect.message.kind,
33163
+ senderName: effect.message.senderName
33164
+ });
33165
+ } else if (effect.type === "toast" && effect.toast) {
33166
+ this.pushEvent({
33167
+ event: "provider:toast",
33168
+ effectId: effect.id || effectKey,
33169
+ timestamp: Date.now(),
33170
+ message: effect.toast.message,
33171
+ level: effect.toast.level || "info"
33172
+ });
33173
+ } else if (effect.type === "notification" && effect.notification) {
33174
+ this.pushEvent({
33175
+ event: "provider:notification",
33176
+ effectId: effect.id || effectKey,
33177
+ timestamp: Date.now(),
33178
+ title: effect.notification.title,
33179
+ message: effect.notification.body,
33180
+ content: typeof effect.notification.bubbleContent === "string" ? effect.notification.bubbleContent : effect.notification.body,
33181
+ level: effect.notification.level || "info",
33182
+ channels: effect.notification.channels || ["toast"],
33183
+ preferenceKey: effect.notification.preferenceKey
33184
+ });
33185
+ }
33186
+ }
33187
+ }
33188
+ appendRuntimeSystemMessage(content, dedupKey, receivedAt = Date.now()) {
33189
+ const normalizedContent = String(content || "").trim();
33190
+ if (!normalizedContent) return;
33191
+ if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
33192
+ this.runtimeMessages.push({
33193
+ key: dedupKey,
33194
+ message: {
33195
+ role: "system",
33196
+ senderName: "System",
33197
+ content: normalizedContent,
33198
+ receivedAt,
33199
+ timestamp: receivedAt
33200
+ }
33201
+ });
33202
+ if (this.runtimeMessages.length > 50) this.runtimeMessages = this.runtimeMessages.slice(-50);
33203
+ this.historyWriter.appendNewMessages(
33204
+ this.type,
33205
+ [{
33206
+ role: "system",
33207
+ senderName: "System",
33208
+ content: normalizedContent,
33209
+ kind: "system",
33210
+ receivedAt,
33211
+ historyDedupKey: dedupKey
33212
+ }],
33213
+ this.chatTitle || this.agentName || this.provider.name,
33214
+ this.instanceId,
33215
+ this.chatId || this.instanceId
33216
+ );
33217
+ }
33218
+ mergeConversationMessages(messages) {
33219
+ if (this.runtimeMessages.length === 0) return messages;
33220
+ return [...messages, ...this.runtimeMessages.map((entry) => entry.message)].map((message, index) => ({ message, index })).sort((a, b2) => {
33221
+ const aTime = a.message.receivedAt || a.message.timestamp || 0;
33222
+ const bTime = b2.message.receivedAt || b2.message.timestamp || 0;
33223
+ if (aTime !== bTime) return aTime - bTime;
33224
+ return a.index - b2.index;
33225
+ }).map((entry) => entry.message);
33226
+ }
33227
+ getPersistedEffectContent(effect) {
33228
+ if (effect.type === "message") {
33229
+ return typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
33230
+ }
33231
+ if (effect.type === "toast") {
33232
+ return effect.toast?.message || null;
33233
+ }
33234
+ if (effect.type === "notification") {
33235
+ if (typeof effect.notification?.bubbleContent === "string") return effect.notification.bubbleContent;
33236
+ if (typeof effect.notification?.title === "string" && effect.notification.title.trim()) {
33237
+ return `${effect.notification.title}
33238
+ ${effect.notification.body || ""}`.trim();
32799
33239
  }
32800
- if (messageCount === 0 || !lastMessageAt) continue;
32801
- summaries.push({
32802
- historySessionId,
32803
- sessionTitle: sessionTitle || void 0,
32804
- messageCount,
32805
- firstMessageAt,
32806
- lastMessageAt,
32807
- preview: preview || void 0
33240
+ return effect.notification?.body || null;
33241
+ }
33242
+ return null;
33243
+ }
33244
+ getEffectDedupKey(effect) {
33245
+ if (effect.id) return `provider_effect:${effect.id}`;
33246
+ if (effect.type === "message") {
33247
+ return `provider_effect:message:${typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "")}`;
33248
+ }
33249
+ if (effect.type === "notification") {
33250
+ return `provider_effect:notification:${effect.notification?.title || ""}:${effect.notification?.body || ""}`;
33251
+ }
33252
+ return `provider_effect:toast:${effect.toast?.message || ""}`;
33253
+ }
33254
+ flushEvents() {
33255
+ const events = [...this.events];
33256
+ this.events = [];
33257
+ return events;
33258
+ }
33259
+ resolveChatTitle(data) {
33260
+ const title = typeof data?.title === "string" && data.title.trim() ? data.title.trim() : this.chatTitle;
33261
+ return title || this.agentName || this.provider.name;
33262
+ }
33263
+ resetStreamState() {
33264
+ if (this.currentStatus !== "idle") {
33265
+ this.detectTransition("idle", {
33266
+ title: this.chatTitle,
33267
+ agentName: this.agentName,
33268
+ extensionId: this.extensionId,
33269
+ messages: this.messages
32808
33270
  });
32809
33271
  }
32810
- summaries.sort((a, b2) => b2.lastMessageAt - a.lastMessageAt);
32811
- const offset = Math.max(0, options.offset || 0);
32812
- const limit = Math.max(1, options.limit || 30);
32813
- const sliced = summaries.slice(offset, offset + limit);
32814
- return {
32815
- sessions: sliced,
32816
- hasMore: summaries.length > offset + limit
32817
- };
32818
- } catch {
32819
- return { sessions: [], hasMore: false };
33272
+ this.agentStreams = [];
33273
+ this.messages = [];
33274
+ this.activeModal = null;
33275
+ this.currentModel = "";
33276
+ this.currentMode = "";
33277
+ this.controlValues = {};
33278
+ this.currentStatus = "idle";
33279
+ this.chatId = null;
33280
+ this.chatTitle = null;
33281
+ this.agentName = "";
33282
+ this.extensionId = "";
33283
+ this.lastAgentStatus = "idle";
33284
+ this.generatingStartedAt = 0;
33285
+ this.monitor.reset();
32820
33286
  }
32821
- }
33287
+ };
32822
33288
  init_logger();
32823
33289
  var IdeProviderInstance = class {
32824
33290
  type;
@@ -32837,6 +33303,8 @@ ${data.message || ""}`.trim();
32837
33303
  monitor;
32838
33304
  historyWriter;
32839
33305
  autoApproveBusy = false;
33306
+ appliedEffectKeys = /* @__PURE__ */ new Set();
33307
+ runtimeMessages = [];
32840
33308
  // IDE meta
32841
33309
  ideVersion = "";
32842
33310
  instanceId;
@@ -32897,7 +33365,7 @@ ${data.message || ""}`.trim();
32897
33365
  id: this.cachedChat.id || "active_session",
32898
33366
  title: this.cachedChat.title || this.type,
32899
33367
  status: this.cachedChat.status || this.currentStatus,
32900
- messages: this.cachedChat.messages || [],
33368
+ messages: this.mergeConversationMessages(this.cachedChat.messages || []),
32901
33369
  activeModal: this.cachedChat.activeModal || null,
32902
33370
  inputContent: this.cachedChat.inputContent || ""
32903
33371
  } : null,
@@ -32937,6 +33405,13 @@ ${data.message || ""}`.trim();
32937
33405
  for (const ext of this.extensions.values()) {
32938
33406
  ext.onEvent("stream_reset");
32939
33407
  }
33408
+ } else if (event === "provider_state_patch" && data && typeof data === "object") {
33409
+ const extType = typeof data.extensionType === "string" ? data.extensionType : "";
33410
+ if (extType && this.extensions.has(extType)) {
33411
+ this.extensions.get(extType).onEvent("provider_state_patch", data);
33412
+ } else {
33413
+ this.applyProviderResponse(data, { phase: "immediate" });
33414
+ }
32940
33415
  }
32941
33416
  }
32942
33417
  dispose() {
@@ -32944,11 +33419,21 @@ ${data.message || ""}`.trim();
32944
33419
  this.lastAgentStatuses.clear();
32945
33420
  this.generatingStartedAt.clear();
32946
33421
  this.monitor.reset();
33422
+ this.appliedEffectKeys.clear();
33423
+ this.runtimeMessages = [];
32947
33424
  for (const ext of this.extensions.values()) {
32948
33425
  ext.dispose();
32949
33426
  }
32950
33427
  this.extensions.clear();
32951
33428
  }
33429
+ updateSettings(newSettings) {
33430
+ this.settings = { ...newSettings };
33431
+ this.monitor.updateConfig({
33432
+ approvalAlert: this.settings.approvalAlert !== false,
33433
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
33434
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
33435
+ });
33436
+ }
32952
33437
  // ─── Extension manage ─────────────────────────────
32953
33438
  /** Extension Instance add */
32954
33439
  async addExtension(provider, settings) {
@@ -33061,6 +33546,8 @@ ${data.message || ""}`.trim();
33061
33546
  raw.messages = raw.messages.filter((m) => !hiddenKinds.has(m.kind));
33062
33547
  }
33063
33548
  }
33549
+ const controlValues = extractProviderControlValues(this.provider.controls, raw);
33550
+ if (controlValues) raw.controlValues = controlValues;
33064
33551
  this.cachedChat = { ...raw, activeModal };
33065
33552
  this.detectAgentTransitions(raw, now);
33066
33553
  if (raw.messages?.length > 0) {
@@ -33127,6 +33614,9 @@ ${data.message || ""}`.trim();
33127
33614
  }
33128
33615
  this.lastAgentStatuses.set(agentKey, agentStatus);
33129
33616
  }
33617
+ this.applyProviderResponse(chatData, {
33618
+ phase: agentStatus === "idle" && (lastStatus === "generating" || lastStatus === "waiting_approval") ? "turn_completed" : "immediate"
33619
+ });
33130
33620
  if (agentStatus === "waiting_approval" && this.settings.autoApprove && !this.autoApproveBusy) {
33131
33621
  this.autoApproveViaScript(chatData);
33132
33622
  }
@@ -33139,6 +33629,136 @@ ${data.message || ""}`.trim();
33139
33629
  this.events.push(event);
33140
33630
  if (this.events.length > 50) this.events = this.events.slice(-50);
33141
33631
  }
33632
+ applyProviderResponse(data, options) {
33633
+ if (!data || typeof data !== "object") return;
33634
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
33635
+ if (controlValues) {
33636
+ this.cachedChat = {
33637
+ ...this.cachedChat || {},
33638
+ ...data,
33639
+ controlValues: { ...this.cachedChat?.controlValues || {}, ...controlValues }
33640
+ };
33641
+ }
33642
+ const effects = normalizeProviderEffects(data);
33643
+ for (const effect of effects) {
33644
+ const effectWhen = effect.when || "immediate";
33645
+ if (effectWhen === "turn_completed" && options.phase !== "turn_completed") continue;
33646
+ if (effectWhen === "immediate" && options.phase === "turn_completed") continue;
33647
+ const effectKey = this.getEffectDedupKey(effect);
33648
+ if (this.appliedEffectKeys.has(effectKey)) continue;
33649
+ this.appliedEffectKeys.add(effectKey);
33650
+ if (effect.persist !== false) {
33651
+ const persisted = this.getPersistedEffectContent(effect);
33652
+ if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
33653
+ }
33654
+ if (effect.type === "message" && effect.message) {
33655
+ this.pushEvent({
33656
+ event: "provider:message",
33657
+ timestamp: Date.now(),
33658
+ content: typeof effect.message.content === "string" ? effect.message.content : JSON.stringify(effect.message.content),
33659
+ role: effect.message.role || "system",
33660
+ kind: effect.message.kind,
33661
+ senderName: effect.message.senderName
33662
+ });
33663
+ } else if (effect.type === "toast" && effect.toast) {
33664
+ this.pushEvent({
33665
+ event: "provider:toast",
33666
+ effectId: effect.id || effectKey,
33667
+ timestamp: Date.now(),
33668
+ message: effect.toast.message,
33669
+ level: effect.toast.level || "info"
33670
+ });
33671
+ } else if (effect.type === "notification" && effect.notification) {
33672
+ this.pushEvent({
33673
+ event: "provider:notification",
33674
+ effectId: effect.id || effectKey,
33675
+ timestamp: Date.now(),
33676
+ title: effect.notification.title,
33677
+ message: effect.notification.body,
33678
+ content: typeof effect.notification.bubbleContent === "string" ? effect.notification.bubbleContent : effect.notification.body,
33679
+ level: effect.notification.level || "info",
33680
+ channels: effect.notification.channels || ["toast"],
33681
+ preferenceKey: effect.notification.preferenceKey
33682
+ });
33683
+ }
33684
+ }
33685
+ }
33686
+ appendRuntimeSystemMessage(content, dedupKey, receivedAt = Date.now()) {
33687
+ const normalizedContent = String(content || "").trim();
33688
+ if (!normalizedContent) return;
33689
+ if (this.runtimeMessages.some((entry) => entry.key === dedupKey)) return;
33690
+ if (!this.cachedChat) {
33691
+ this.cachedChat = {
33692
+ id: "active_session",
33693
+ title: this.provider.name,
33694
+ status: this.currentStatus,
33695
+ messages: [],
33696
+ activeModal: null,
33697
+ inputContent: ""
33698
+ };
33699
+ }
33700
+ this.runtimeMessages.push({
33701
+ key: dedupKey,
33702
+ message: {
33703
+ role: "system",
33704
+ senderName: "System",
33705
+ content: normalizedContent,
33706
+ receivedAt,
33707
+ timestamp: receivedAt
33708
+ }
33709
+ });
33710
+ if (this.runtimeMessages.length > 50) this.runtimeMessages = this.runtimeMessages.slice(-50);
33711
+ this.historyWriter.appendNewMessages(
33712
+ this.type,
33713
+ [{
33714
+ role: "system",
33715
+ senderName: "System",
33716
+ content: normalizedContent,
33717
+ kind: "system",
33718
+ receivedAt,
33719
+ historyDedupKey: dedupKey
33720
+ }],
33721
+ this.cachedChat?.title || this.provider.name,
33722
+ this.instanceId,
33723
+ this.cachedChat?.id || this.instanceId
33724
+ );
33725
+ }
33726
+ mergeConversationMessages(messages) {
33727
+ if (this.runtimeMessages.length === 0) return messages;
33728
+ return [...messages, ...this.runtimeMessages.map((entry) => entry.message)].map((message, index) => ({ message, index })).sort((a, b2) => {
33729
+ const aTime = a.message.receivedAt || a.message.timestamp || 0;
33730
+ const bTime = b2.message.receivedAt || b2.message.timestamp || 0;
33731
+ if (aTime !== bTime) return aTime - bTime;
33732
+ return a.index - b2.index;
33733
+ }).map((entry) => entry.message);
33734
+ }
33735
+ getPersistedEffectContent(effect) {
33736
+ if (effect.type === "message") {
33737
+ return typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
33738
+ }
33739
+ if (effect.type === "toast") {
33740
+ return effect.toast?.message || null;
33741
+ }
33742
+ if (effect.type === "notification") {
33743
+ if (typeof effect.notification?.bubbleContent === "string") return effect.notification.bubbleContent;
33744
+ if (typeof effect.notification?.title === "string" && effect.notification.title.trim()) {
33745
+ return `${effect.notification.title}
33746
+ ${effect.notification.body || ""}`.trim();
33747
+ }
33748
+ return effect.notification?.body || null;
33749
+ }
33750
+ return null;
33751
+ }
33752
+ getEffectDedupKey(effect) {
33753
+ if (effect.id) return `provider_effect:${effect.id}`;
33754
+ if (effect.type === "message") {
33755
+ return `provider_effect:message:${typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "")}`;
33756
+ }
33757
+ if (effect.type === "notification") {
33758
+ return `provider_effect:notification:${effect.notification?.title || ""}:${effect.notification?.body || ""}`;
33759
+ }
33760
+ return `provider_effect:toast:${effect.toast?.message || ""}`;
33761
+ }
33142
33762
  flushEvents() {
33143
33763
  const events = [...this.events];
33144
33764
  this.events = [];
@@ -35194,7 +35814,56 @@ ${data.message || ""}`.trim();
35194
35814
  }
35195
35815
  return { success: false, error: `Failed to set ${providerType}.${key} \u2014 invalid key, value, or not a public setting` };
35196
35816
  }
35197
- async function handleExtensionScript(h, args, scriptName) {
35817
+ function normalizeProviderScriptArgs(args) {
35818
+ const normalizedArgs = { ...args || {} };
35819
+ for (const key of ["mode", "model", "message", "action", "button", "text", "sessionId", "value"]) {
35820
+ if (key in normalizedArgs && !(key.toUpperCase() in normalizedArgs)) {
35821
+ normalizedArgs[key.toUpperCase()] = normalizedArgs[key];
35822
+ }
35823
+ }
35824
+ return normalizedArgs;
35825
+ }
35826
+ function parseScriptResult(result) {
35827
+ if (typeof result === "string") {
35828
+ try {
35829
+ const parsed = JSON.parse(result);
35830
+ if (parsed && typeof parsed === "object" && parsed.success === false) {
35831
+ return { success: false, payload: parsed };
35832
+ }
35833
+ return { success: true, payload: parsed };
35834
+ } catch {
35835
+ return { success: true, payload: { result } };
35836
+ }
35837
+ }
35838
+ if (result && typeof result === "object" && result.success === false) {
35839
+ return { success: false, payload: result };
35840
+ }
35841
+ return { success: true, payload: result };
35842
+ }
35843
+ function getCliScriptCommand(payload) {
35844
+ if (!payload || typeof payload !== "object") return null;
35845
+ if (typeof payload.sendMessage === "string" && payload.sendMessage.trim()) {
35846
+ return { type: "send_message", text: payload.sendMessage.trim() };
35847
+ }
35848
+ const command = payload.command;
35849
+ if (!command || typeof command !== "object") return null;
35850
+ if (command.type !== "send_message") return null;
35851
+ const text = typeof command.text === "string" ? command.text.trim() : typeof command.message === "string" ? command.message.trim() : "";
35852
+ if (!text) return null;
35853
+ return { type: "send_message", text };
35854
+ }
35855
+ function applyProviderPatch(h, args, payload) {
35856
+ if (!payload || typeof payload !== "object") return;
35857
+ const targetSessionId = typeof args?.targetSessionId === "string" ? args.targetSessionId.trim() : "";
35858
+ const targetSession = targetSessionId ? h.ctx.sessionRegistry?.get(targetSessionId) : void 0;
35859
+ const instanceKey = targetSession?.instanceKey || targetSessionId;
35860
+ if (!instanceKey) return;
35861
+ h.ctx.instanceManager?.sendEvent(instanceKey, "provider_state_patch", {
35862
+ ...payload,
35863
+ extensionType: targetSession?.transport === "cdp-webview" ? targetSession.providerType : void 0
35864
+ });
35865
+ }
35866
+ async function executeProviderScript(h, args, scriptName) {
35198
35867
  const { agentType, ideType } = args || {};
35199
35868
  if (!agentType) return { success: false, error: "agentType is required" };
35200
35869
  const loader = h.ctx.providerLoader;
@@ -35207,13 +35876,29 @@ ${data.message || ""}`.trim();
35207
35876
  if (!provider.scripts?.[actualScriptName]) {
35208
35877
  return { success: false, error: `Script '${actualScriptName}' not available for ${agentType}` };
35209
35878
  }
35210
- const scriptFn = provider.scripts[actualScriptName];
35211
- const normalizedArgs = { ...args };
35212
- for (const key of ["mode", "model", "message", "action", "button", "text", "sessionId"]) {
35213
- if (key in normalizedArgs && !(key.toUpperCase() in normalizedArgs)) {
35214
- normalizedArgs[key.toUpperCase()] = normalizedArgs[key];
35879
+ const normalizedArgs = normalizeProviderScriptArgs(args);
35880
+ if (provider.category === "cli") {
35881
+ const adapter = h.getCliAdapter(args?.targetSessionId || agentType);
35882
+ if (!adapter?.invokeScript) {
35883
+ return { success: false, error: `CLI adapter does not support script '${actualScriptName}'` };
35884
+ }
35885
+ try {
35886
+ const raw = await adapter.invokeScript(actualScriptName, normalizedArgs);
35887
+ const parsed = parseScriptResult(raw);
35888
+ if (!parsed.success) {
35889
+ return { success: false, ...parsed.payload || {} };
35890
+ }
35891
+ const cliCommand = getCliScriptCommand(parsed.payload);
35892
+ if (cliCommand?.type === "send_message" && cliCommand.text) {
35893
+ await adapter.sendMessage(cliCommand.text);
35894
+ }
35895
+ applyProviderPatch(h, args, parsed.payload);
35896
+ return { success: true, ...parsed.payload && typeof parsed.payload === "object" ? parsed.payload : { result: parsed.payload } };
35897
+ } catch (e) {
35898
+ return { success: false, error: `Script execution failed: ${e.message}` };
35215
35899
  }
35216
35900
  }
35901
+ const scriptFn = provider.scripts[actualScriptName];
35217
35902
  const scriptCode = scriptFn(normalizedArgs);
35218
35903
  if (!scriptCode) return { success: false, error: `Script '${actualScriptName}' returned null` };
35219
35904
  const cdpKey = provider.category === "ide" ? h.currentSession?.cdpManagerKey || h.currentManagerKey || agentType : h.currentSession?.cdpManagerKey || h.currentManagerKey || ideType;
@@ -35267,16 +35952,29 @@ ${data.message || ""}`.trim();
35267
35952
  if (typeof result === "string") {
35268
35953
  try {
35269
35954
  const parsed = JSON.parse(result);
35955
+ applyProviderPatch(h, args, parsed);
35956
+ if (parsed && typeof parsed === "object" && parsed.success === false) {
35957
+ return { success: false, ...parsed };
35958
+ }
35270
35959
  return { success: true, ...parsed };
35271
35960
  } catch {
35272
35961
  return { success: true, result };
35273
35962
  }
35274
35963
  }
35964
+ applyProviderPatch(h, args, result);
35275
35965
  return { success: true, result };
35276
35966
  } catch (e) {
35277
35967
  return { success: false, error: `Script execution failed: ${e.message}` };
35278
35968
  }
35279
35969
  }
35970
+ async function handleExtensionScript(h, args, scriptName) {
35971
+ return executeProviderScript(h, args, scriptName);
35972
+ }
35973
+ async function handleProviderScript(h, args) {
35974
+ const scriptName = typeof args?.scriptName === "string" ? args.scriptName.trim() : "";
35975
+ if (!scriptName) return { success: false, error: "scriptName is required" };
35976
+ return executeProviderScript(h, args, scriptName);
35977
+ }
35280
35978
  function handleGetIdeExtensions(h, args) {
35281
35979
  const { ideType } = args || {};
35282
35980
  const loader = h.ctx.providerLoader;
@@ -35772,6 +36470,8 @@ ${data.message || ""}`.trim();
35772
36470
  case "set_ide_extension":
35773
36471
  return handleSetIdeExtension(this, args);
35774
36472
  // ─── Extension Model / Mode Control (stream-commands.ts) ──────────
36473
+ case "invoke_provider_script":
36474
+ return handleProviderScript(this, args);
35775
36475
  case "list_extension_models":
35776
36476
  return handleExtensionScript(this, args, "listModels");
35777
36477
  case "set_extension_model":
@@ -35944,6 +36644,8 @@ ${data.message || ""}`.trim();
35944
36644
  generatingDebounceTimer = null;
35945
36645
  generatingDebouncePending = null;
35946
36646
  lastApprovalEventAt = 0;
36647
+ controlValues = {};
36648
+ appliedEffectKeys = /* @__PURE__ */ new Set();
35947
36649
  historyWriter;
35948
36650
  runtimeMessages = [];
35949
36651
  instanceId;
@@ -35956,6 +36658,7 @@ ${data.message || ""}`.trim();
35956
36658
  async init(context) {
35957
36659
  this.context = context;
35958
36660
  this.settings = context.settings || {};
36661
+ this.adapter.updateRuntimeSettings?.(this.settings);
35959
36662
  this.monitor.updateConfig({
35960
36663
  approvalAlert: this.settings.approvalAlert !== false,
35961
36664
  longGeneratingAlert: this.settings.longGeneratingAlert !== false,
@@ -35971,6 +36674,21 @@ ${data.message || ""}`.trim();
35971
36674
  this.detectStatusTransition();
35972
36675
  });
35973
36676
  await this.adapter.spawn();
36677
+ if (this.providerSessionId) {
36678
+ const restoredHistory = readChatHistory(this.type, 0, 200, this.providerSessionId);
36679
+ if (restoredHistory.messages.length > 0) {
36680
+ this.adapter.seedCommittedMessages(
36681
+ restoredHistory.messages.map((message) => ({
36682
+ role: message.role,
36683
+ content: message.content,
36684
+ timestamp: message.receivedAt,
36685
+ receivedAt: message.receivedAt,
36686
+ kind: message.kind,
36687
+ senderName: message.senderName
36688
+ }))
36689
+ );
36690
+ }
36691
+ }
35974
36692
  if (this.providerSessionId && this.launchMode === "resume") {
35975
36693
  const resumedAt = Date.now();
35976
36694
  this.historyWriter.appendSystemMarker(
@@ -36051,6 +36769,12 @@ ${data.message || ""}`.trim();
36051
36769
  }
36052
36770
  const runtime = this.adapter.getRuntimeMetadata();
36053
36771
  const parsedMessages = Array.isArray(parsedStatus?.messages) ? parsedStatus.messages : [];
36772
+ const controlValues = extractProviderControlValues(this.provider.controls, parsedStatus);
36773
+ if (controlValues) {
36774
+ this.controlValues = controlValues;
36775
+ } else if (Object.keys(this.controlValues).length > 0) {
36776
+ this.controlValues = {};
36777
+ }
36054
36778
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
36055
36779
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
36056
36780
  if (parsedMessages.length > 0) {
@@ -36071,6 +36795,7 @@ ${data.message || ""}`.trim();
36071
36795
  );
36072
36796
  }
36073
36797
  }
36798
+ this.applyProviderResponse(parsedStatus, { phase: "immediate" });
36074
36799
  return {
36075
36800
  type: this.type,
36076
36801
  name: this.provider.name,
@@ -36100,8 +36825,7 @@ ${data.message || ""}`.trim();
36100
36825
  attachedClients: runtime.attachedClients || []
36101
36826
  } : void 0,
36102
36827
  resume: this.provider.resume,
36103
- controlValues: void 0,
36104
- // CLI controls not yet wired from stream
36828
+ controlValues: this.controlValues,
36105
36829
  providerControls: this.provider.controls
36106
36830
  };
36107
36831
  }
@@ -36112,6 +36836,15 @@ ${data.message || ""}`.trim();
36112
36836
  getPresentationMode() {
36113
36837
  return this.presentationMode;
36114
36838
  }
36839
+ updateSettings(newSettings) {
36840
+ this.settings = { ...newSettings };
36841
+ this.adapter.updateRuntimeSettings?.(this.settings);
36842
+ this.monitor.updateConfig({
36843
+ approvalAlert: this.settings.approvalAlert !== false,
36844
+ longGeneratingAlert: this.settings.longGeneratingAlert !== false,
36845
+ longGeneratingThresholdSec: this.settings.longGeneratingThresholdSec || 180
36846
+ });
36847
+ }
36115
36848
  onEvent(event, data) {
36116
36849
  if (event === "send_message" && data?.text) {
36117
36850
  void this.adapter.sendMessage(data.text).catch((e) => {
@@ -36123,22 +36856,27 @@ ${data.message || ""}`.trim();
36123
36856
  void this.adapter.resolveAction(data).catch((e) => {
36124
36857
  LOG2.warn("CLI", `[${this.type}] resolve_action failed: ${e?.message || e}`);
36125
36858
  });
36859
+ } else if (event === "provider_state_patch" && data && typeof data === "object") {
36860
+ this.applyProviderResponse(data, { phase: "immediate" });
36126
36861
  }
36127
36862
  }
36128
36863
  dispose() {
36129
36864
  this.adapter.shutdown();
36130
36865
  this.monitor.reset();
36866
+ this.appliedEffectKeys.clear();
36131
36867
  }
36132
36868
  completedDebounceTimer = null;
36133
36869
  completedDebouncePending = null;
36134
36870
  detectStatusTransition() {
36135
36871
  const now = Date.now();
36136
36872
  const adapterStatus = this.adapter.getStatus();
36873
+ const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
36137
36874
  const newStatus = adapterStatus.status;
36138
36875
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
36139
36876
  const chatTitle = `${this.provider.name} \xB7 ${dirName}`;
36140
36877
  const partial2 = this.adapter.getPartialResponse();
36141
36878
  const progressFingerprint = newStatus === "generating" ? `${partial2 || ""}::${adapterStatus.messages.at(-1)?.content || ""}`.slice(-2e3) : void 0;
36879
+ const previousStatus = this.lastStatus;
36142
36880
  if (newStatus !== this.lastStatus) {
36143
36881
  LOG2.info("CLI", `[${this.type}] status: ${this.lastStatus} \u2192 ${newStatus}`);
36144
36882
  if (this.lastStatus === "idle" && newStatus === "generating") {
@@ -36231,6 +36969,9 @@ ${data.message || ""}`.trim();
36231
36969
  }
36232
36970
  this.lastStatus = newStatus;
36233
36971
  }
36972
+ this.applyProviderResponse(parsedStatus, {
36973
+ phase: newStatus === "idle" && (previousStatus === "generating" || previousStatus === "waiting_approval") ? "turn_completed" : "immediate"
36974
+ });
36234
36975
  const agentKey = `${this.type}:cli`;
36235
36976
  const monitorEvents = this.monitor.check(agentKey, newStatus, now, progressFingerprint);
36236
36977
  for (const me of monitorEvents) {
@@ -36246,6 +36987,88 @@ ${data.message || ""}`.trim();
36246
36987
  this.events = [];
36247
36988
  return events;
36248
36989
  }
36990
+ applyProviderResponse(data, options) {
36991
+ if (!data || typeof data !== "object") return;
36992
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
36993
+ if (controlValues) {
36994
+ this.controlValues = { ...this.controlValues, ...controlValues };
36995
+ }
36996
+ const effects = normalizeProviderEffects(data);
36997
+ for (const effect of effects) {
36998
+ const effectWhen = effect.when || "immediate";
36999
+ if (effectWhen === "turn_completed" && options.phase !== "turn_completed") continue;
37000
+ if (effectWhen === "immediate" && options.phase === "turn_completed") continue;
37001
+ const effectKey = this.getEffectDedupKey(effect);
37002
+ if (this.appliedEffectKeys.has(effectKey)) continue;
37003
+ this.appliedEffectKeys.add(effectKey);
37004
+ if (effect.persist !== false) {
37005
+ const persisted = this.getPersistedEffectContent(effect);
37006
+ if (persisted) this.appendRuntimeSystemMessage(persisted, effectKey);
37007
+ }
37008
+ if (effect.type === "message" && effect.message) {
37009
+ const content = typeof effect.message.content === "string" ? effect.message.content : JSON.stringify(effect.message.content);
37010
+ this.pushEvent({
37011
+ event: "provider:message",
37012
+ timestamp: Date.now(),
37013
+ content,
37014
+ role: effect.message.role || "system",
37015
+ kind: effect.message.kind,
37016
+ senderName: effect.message.senderName
37017
+ });
37018
+ } else if (effect.type === "toast" && effect.toast) {
37019
+ this.pushEvent({
37020
+ event: "provider:toast",
37021
+ effectId: effect.id || effectKey,
37022
+ timestamp: Date.now(),
37023
+ message: effect.toast.message,
37024
+ level: effect.toast.level || "info"
37025
+ });
37026
+ } else if (effect.type === "notification" && effect.notification) {
37027
+ this.pushEvent({
37028
+ event: "provider:notification",
37029
+ effectId: effect.id || effectKey,
37030
+ timestamp: Date.now(),
37031
+ title: effect.notification.title,
37032
+ message: effect.notification.body,
37033
+ content: typeof effect.notification.bubbleContent === "string" ? effect.notification.bubbleContent : effect.notification.body,
37034
+ level: effect.notification.level || "info",
37035
+ channels: effect.notification.channels || ["toast"],
37036
+ preferenceKey: effect.notification.preferenceKey
37037
+ });
37038
+ }
37039
+ }
37040
+ if (this.appliedEffectKeys.size > 200) {
37041
+ this.appliedEffectKeys = new Set(Array.from(this.appliedEffectKeys).slice(-100));
37042
+ }
37043
+ }
37044
+ getEffectDedupKey(effect) {
37045
+ if (effect.id) return `provider_effect:${effect.id}`;
37046
+ if (effect.type === "message") {
37047
+ const content = typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
37048
+ return `provider_effect:message:${content}`;
37049
+ }
37050
+ if (effect.type === "notification") {
37051
+ return `provider_effect:notification:${effect.notification?.title || ""}:${effect.notification?.body || ""}`;
37052
+ }
37053
+ return `provider_effect:toast:${effect.toast?.message || ""}`;
37054
+ }
37055
+ getPersistedEffectContent(effect) {
37056
+ if (effect.type === "message") {
37057
+ return typeof effect.message?.content === "string" ? effect.message.content : JSON.stringify(effect.message?.content || "");
37058
+ }
37059
+ if (effect.type === "toast") {
37060
+ return effect.toast?.message || null;
37061
+ }
37062
+ if (effect.type === "notification") {
37063
+ if (typeof effect.notification?.bubbleContent === "string") return effect.notification.bubbleContent;
37064
+ if (typeof effect.notification?.title === "string" && effect.notification.title.trim()) {
37065
+ return `${effect.notification.title}
37066
+ ${effect.notification.body || ""}`.trim();
37067
+ }
37068
+ return effect.notification?.body || null;
37069
+ }
37070
+ return null;
37071
+ }
36249
37072
  // ─── Adapter access (backward compat) ──────────────────
36250
37073
  getAdapter() {
36251
37074
  return this.adapter;
@@ -39004,6 +39827,22 @@ Run 'adhdev doctor' for detailed diagnostics.`
39004
39827
  function getWinProcessNames() {
39005
39828
  return getProviderLoader().getWinProcessNames();
39006
39829
  }
39830
+ function getProviderMeta(ideId) {
39831
+ return getProviderLoader().getMeta(ideId);
39832
+ }
39833
+ function getPreferredLaunchMethod(ideId, platform9) {
39834
+ const prefer = getProviderMeta(ideId)?.launch?.prefer;
39835
+ const value = prefer?.[platform9];
39836
+ return value === "cli" || value === "app" || value === "auto" ? value : "auto";
39837
+ }
39838
+ function getCdpStartupTimeoutMs(ideId) {
39839
+ const value = getProviderMeta(ideId)?.launch?.cdpStartupTimeoutMs;
39840
+ if (typeof value !== "number" || !Number.isFinite(value)) return 15e3;
39841
+ return Math.max(1e3, Math.floor(value));
39842
+ }
39843
+ function escapeForAppleScript(value) {
39844
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
39845
+ }
39007
39846
  async function findFreePort(ports) {
39008
39847
  for (const port2 of ports) {
39009
39848
  const free = await checkPortFree(port2);
@@ -39056,12 +39895,12 @@ Run 'adhdev doctor' for detailed diagnostics.`
39056
39895
  try {
39057
39896
  if (plat === "darwin" && appName) {
39058
39897
  try {
39059
- (0, import_child_process6.execSync)(`osascript -e 'tell application "${appName}" to quit' 2>/dev/null`, {
39898
+ (0, import_child_process6.execSync)(`osascript -e 'tell application "${escapeForAppleScript(appName)}" to quit' 2>/dev/null`, {
39060
39899
  timeout: 5e3
39061
39900
  });
39062
39901
  } catch {
39063
39902
  try {
39064
- (0, import_child_process6.execSync)(`pkill -f "${appName}" 2>/dev/null`);
39903
+ (0, import_child_process6.execSync)(`pkill -x "${appName}" 2>/dev/null`, { timeout: 5e3 });
39065
39904
  } catch {
39066
39905
  }
39067
39906
  }
@@ -39091,7 +39930,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
39091
39930
  }
39092
39931
  if (plat === "darwin" && appName) {
39093
39932
  try {
39094
- (0, import_child_process6.execSync)(`pkill -9 -f "${appName}" 2>/dev/null`);
39933
+ (0, import_child_process6.execSync)(`pkill -9 -x "${appName}" 2>/dev/null`, { timeout: 5e3 });
39095
39934
  } catch {
39096
39935
  }
39097
39936
  } else if (plat === "win32" && winProcesses) {
@@ -39114,8 +39953,23 @@ Run 'adhdev doctor' for detailed diagnostics.`
39114
39953
  if (plat === "darwin") {
39115
39954
  const appName = getMacAppIdentifiers()[ideId];
39116
39955
  if (!appName) return false;
39117
- const result = (0, import_child_process6.execSync)(`pgrep -f "${appName}" 2>/dev/null`, { encoding: "utf-8" });
39118
- return result.trim().length > 0;
39956
+ try {
39957
+ const result = (0, import_child_process6.execSync)(`pgrep -x "${appName}" 2>/dev/null`, {
39958
+ encoding: "utf-8",
39959
+ timeout: 3e3
39960
+ });
39961
+ return result.trim().length > 0;
39962
+ } catch {
39963
+ const result = (0, import_child_process6.execSync)(
39964
+ `osascript -e 'tell application "System Events" to count (every process whose name is "${escapeForAppleScript(appName)}")'`,
39965
+ {
39966
+ encoding: "utf-8",
39967
+ timeout: 3e3,
39968
+ stdio: ["pipe", "pipe", "pipe"]
39969
+ }
39970
+ );
39971
+ return Number.parseInt(result.trim() || "0", 10) > 0;
39972
+ }
39119
39973
  } else if (plat === "win32") {
39120
39974
  const winProcesses = getWinProcessNames()[ideId];
39121
39975
  if (!winProcesses) return false;
@@ -39264,7 +40118,8 @@ Run 'adhdev doctor' for detailed diagnostics.`
39264
40118
  await launchLinux(targetIde, port, workspace, options.newWindow);
39265
40119
  }
39266
40120
  let cdpReady = false;
39267
- for (let i = 0; i < 30; i++) {
40121
+ const waitDeadline = Date.now() + getCdpStartupTimeoutMs(targetIde.id);
40122
+ while (Date.now() < waitDeadline) {
39268
40123
  await new Promise((r) => setTimeout(r, 500));
39269
40124
  if (await isCdpActive(port)) {
39270
40125
  cdpReady = true;
@@ -39293,14 +40148,18 @@ Run 'adhdev doctor' for detailed diagnostics.`
39293
40148
  }
39294
40149
  async function launchMacOS(ide, port, workspace, newWindow) {
39295
40150
  const appName = getMacAppIdentifiers()[ide.id];
40151
+ const preferredMethod = getPreferredLaunchMethod(ide.id, "darwin");
39296
40152
  const args = ["--remote-debugging-port=" + port];
39297
40153
  if (newWindow) args.push("--new-window");
39298
40154
  if (workspace) args.push(workspace);
39299
- if (appName) {
40155
+ const canUseCli = !!ide.cliCommand;
40156
+ const canUseAppLauncher = !!appName;
40157
+ const useAppLauncher = preferredMethod === "app" ? canUseAppLauncher : preferredMethod === "cli" ? false : !canUseCli && canUseAppLauncher;
40158
+ if (!useAppLauncher && ide.cliCommand) {
40159
+ (0, import_child_process6.spawn)(ide.cliCommand, args, { detached: true, stdio: "ignore" }).unref();
40160
+ } else if (appName) {
39300
40161
  const openArgs = ["-a", appName, "--args", ...args];
39301
40162
  (0, import_child_process6.spawn)("open", openArgs, { detached: true, stdio: "ignore" }).unref();
39302
- } else if (ide.cliCommand) {
39303
- (0, import_child_process6.spawn)(ide.cliCommand, args, { detached: true, stdio: "ignore" }).unref();
39304
40163
  } else {
39305
40164
  throw new Error(`No app identifier or CLI for ${ide.displayName}`);
39306
40165
  }
@@ -39603,6 +40462,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
39603
40462
  workspaces: wsState.workspaces,
39604
40463
  defaultWorkspaceId: wsState.defaultWorkspaceId,
39605
40464
  defaultWorkspacePath: wsState.defaultWorkspacePath,
40465
+ terminalSizingMode: cfg.terminalSizingMode || "measured",
39606
40466
  recentLaunches: buildRecentLaunches(recentActivity),
39607
40467
  terminalBackend,
39608
40468
  availableProviders: buildAvailableProviders(options.providerLoader)
@@ -40438,19 +41298,10 @@ Run 'adhdev doctor' for detailed diagnostics.`
40438
41298
  mode: data.mode,
40439
41299
  activeModal: data.activeModal
40440
41300
  };
40441
- if (this.provider.controls?.length) {
40442
- const cv = {};
40443
- for (const ctrl of this.provider.controls) {
40444
- if (!ctrl.readFrom) continue;
40445
- const val = data[ctrl.readFrom];
40446
- if (val !== void 0 && val !== null) {
40447
- cv[ctrl.id] = typeof val === "object" ? val.name || val.id || String(val) : val;
40448
- }
40449
- }
40450
- if (data.model && !cv["model"]) cv["model"] = data.model;
40451
- if (data.mode && !cv["mode"]) cv["mode"] = data.mode;
40452
- if (Object.keys(cv).length > 0) state.controlValues = cv;
40453
- }
41301
+ const controlValues = extractProviderControlValues(this.provider.controls, data);
41302
+ if (controlValues) state.controlValues = controlValues;
41303
+ const effects = normalizeProviderEffects(data);
41304
+ if (effects.length > 0) state.effects = effects;
40454
41305
  if (state.messages.length > 0) {
40455
41306
  this.lastSuccessState = state;
40456
41307
  }
@@ -40970,6 +41821,8 @@ Run 'adhdev doctor' for detailed diagnostics.`
40970
41821
  activeModal: stream.activeModal || null,
40971
41822
  model: stream.model || void 0,
40972
41823
  mode: stream.mode || void 0,
41824
+ controlValues: stream.controlValues || void 0,
41825
+ effects: stream.effects || void 0,
40973
41826
  sessionId: stream.sessionId || stream.instanceId || void 0,
40974
41827
  title: stream.title || stream.agentName || void 0,
40975
41828
  agentType: stream.agentType || void 0,