@adhdev/daemon-standalone 0.8.21 → 0.8.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);
32515
- }
32516
- flushEvents() {
32517
- const events = [...this.events];
32518
- this.events = [];
32519
- return events;
32520
- }
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;
32676
+ return effects;
32677
+ }
32678
+ function normalizeControlValue(value) {
32679
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
32680
+ return value;
32524
32681
  }
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();
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;
32548
32686
  }
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"));
@@ -32797,28 +32936,355 @@ ${data.message || ""}`.trim();
32797
32936
  if (parsed.role !== "system" && parsed.content.trim()) preview = parsed.content.trim();
32798
32937
  }
32799
32938
  }
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
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();
33239
+ }
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 = [];
@@ -34009,6 +34629,24 @@ ${data.message || ""}`.trim();
34009
34629
  recentSendByTarget.set(key, now);
34010
34630
  return false;
34011
34631
  }
34632
+ function parseMaybeJson(value) {
34633
+ if (typeof value !== "string") return value;
34634
+ try {
34635
+ return JSON.parse(value);
34636
+ } catch {
34637
+ return value;
34638
+ }
34639
+ }
34640
+ function didProviderConfirmSend(result) {
34641
+ const parsed = parseMaybeJson(result);
34642
+ if (parsed === true) return true;
34643
+ if (typeof parsed === "string") {
34644
+ const normalized = parsed.trim().toLowerCase();
34645
+ return normalized === "ok" || normalized === "sent" || normalized === "success" || normalized === "true";
34646
+ }
34647
+ if (!parsed || typeof parsed !== "object") return false;
34648
+ return parsed.sent === true || parsed.success === true || parsed.ok === true || parsed.submitted === true || parsed.dispatched === true;
34649
+ }
34012
34650
  async function handleChatHistory(h, args) {
34013
34651
  const { agentType, offset, limit } = args;
34014
34652
  const historySessionId = getHistorySessionId(h, args);
@@ -34191,14 +34829,8 @@ ${data.message || ""}`.trim();
34191
34829
  try {
34192
34830
  const evalResult = await h.evaluateProviderScript("sendMessage", { MESSAGE: text }, 3e4);
34193
34831
  if (evalResult?.result) {
34194
- let parsed = evalResult.result;
34195
- if (typeof parsed === "string") {
34196
- try {
34197
- parsed = JSON.parse(parsed);
34198
- } catch {
34199
- }
34200
- }
34201
- if (parsed?.sent) {
34832
+ const parsed = parseMaybeJson(evalResult.result);
34833
+ if (didProviderConfirmSend(parsed)) {
34202
34834
  _log(`Extension script sent OK`);
34203
34835
  return _logSendSuccess("extension-script");
34204
34836
  }
@@ -34230,14 +34862,8 @@ ${data.message || ""}`.trim();
34230
34862
  if (sendScript) {
34231
34863
  try {
34232
34864
  const result = await targetCdp.evaluate(sendScript, 3e4);
34233
- let parsed = result;
34234
- if (typeof result === "string") {
34235
- try {
34236
- parsed = JSON.parse(result);
34237
- } catch {
34238
- }
34239
- }
34240
- if (parsed?.sent) {
34865
+ const parsed = parseMaybeJson(result);
34866
+ if (didProviderConfirmSend(parsed)) {
34241
34867
  _log(`sendMessage script OK`);
34242
34868
  return _logSendSuccess("script");
34243
34869
  }
@@ -34282,14 +34908,8 @@ ${data.message || ""}`.trim();
34282
34908
  const matchText = provider.webviewMatchText;
34283
34909
  const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
34284
34910
  const wvResult = await targetCdp.evaluateInWebviewFrame(webviewScript, matchFn);
34285
- let wvParsed = wvResult;
34286
- if (typeof wvResult === "string") {
34287
- try {
34288
- wvParsed = JSON.parse(wvResult);
34289
- } catch {
34290
- }
34291
- }
34292
- if (wvParsed?.sent) {
34911
+ const wvParsed = parseMaybeJson(wvResult);
34912
+ if (didProviderConfirmSend(wvParsed)) {
34293
34913
  _log(`webviewSendMessage OK`);
34294
34914
  return _logSendSuccess("webview-script");
34295
34915
  }
@@ -34311,14 +34931,8 @@ ${data.message || ""}`.trim();
34311
34931
  const matchText = provider.webviewMatchText;
34312
34932
  const matchFn = matchText ? (body) => body.includes(matchText) : void 0;
34313
34933
  const wvResult = await targetCdp.evaluateInWebviewFrame(webviewScript, matchFn);
34314
- let wvParsed = wvResult;
34315
- if (typeof wvResult === "string") {
34316
- try {
34317
- wvParsed = JSON.parse(wvResult);
34318
- } catch {
34319
- }
34320
- }
34321
- if (wvParsed?.sent) {
34934
+ const wvParsed = parseMaybeJson(wvResult);
34935
+ if (didProviderConfirmSend(wvParsed)) {
34322
34936
  _log(`webviewSendMessage OK`);
34323
34937
  return _logSendSuccess("webview-script");
34324
34938
  }
@@ -35200,7 +35814,56 @@ ${data.message || ""}`.trim();
35200
35814
  }
35201
35815
  return { success: false, error: `Failed to set ${providerType}.${key} \u2014 invalid key, value, or not a public setting` };
35202
35816
  }
35203
- 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) {
35204
35867
  const { agentType, ideType } = args || {};
35205
35868
  if (!agentType) return { success: false, error: "agentType is required" };
35206
35869
  const loader = h.ctx.providerLoader;
@@ -35213,13 +35876,29 @@ ${data.message || ""}`.trim();
35213
35876
  if (!provider.scripts?.[actualScriptName]) {
35214
35877
  return { success: false, error: `Script '${actualScriptName}' not available for ${agentType}` };
35215
35878
  }
35216
- const scriptFn = provider.scripts[actualScriptName];
35217
- const normalizedArgs = { ...args };
35218
- for (const key of ["mode", "model", "message", "action", "button", "text", "sessionId"]) {
35219
- if (key in normalizedArgs && !(key.toUpperCase() in normalizedArgs)) {
35220
- 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}` };
35221
35899
  }
35222
35900
  }
35901
+ const scriptFn = provider.scripts[actualScriptName];
35223
35902
  const scriptCode = scriptFn(normalizedArgs);
35224
35903
  if (!scriptCode) return { success: false, error: `Script '${actualScriptName}' returned null` };
35225
35904
  const cdpKey = provider.category === "ide" ? h.currentSession?.cdpManagerKey || h.currentManagerKey || agentType : h.currentSession?.cdpManagerKey || h.currentManagerKey || ideType;
@@ -35273,16 +35952,29 @@ ${data.message || ""}`.trim();
35273
35952
  if (typeof result === "string") {
35274
35953
  try {
35275
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
+ }
35276
35959
  return { success: true, ...parsed };
35277
35960
  } catch {
35278
35961
  return { success: true, result };
35279
35962
  }
35280
35963
  }
35964
+ applyProviderPatch(h, args, result);
35281
35965
  return { success: true, result };
35282
35966
  } catch (e) {
35283
35967
  return { success: false, error: `Script execution failed: ${e.message}` };
35284
35968
  }
35285
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
+ }
35286
35978
  function handleGetIdeExtensions(h, args) {
35287
35979
  const { ideType } = args || {};
35288
35980
  const loader = h.ctx.providerLoader;
@@ -35778,6 +36470,8 @@ ${data.message || ""}`.trim();
35778
36470
  case "set_ide_extension":
35779
36471
  return handleSetIdeExtension(this, args);
35780
36472
  // ─── Extension Model / Mode Control (stream-commands.ts) ──────────
36473
+ case "invoke_provider_script":
36474
+ return handleProviderScript(this, args);
35781
36475
  case "list_extension_models":
35782
36476
  return handleExtensionScript(this, args, "listModels");
35783
36477
  case "set_extension_model":
@@ -35950,6 +36644,8 @@ ${data.message || ""}`.trim();
35950
36644
  generatingDebounceTimer = null;
35951
36645
  generatingDebouncePending = null;
35952
36646
  lastApprovalEventAt = 0;
36647
+ controlValues = {};
36648
+ appliedEffectKeys = /* @__PURE__ */ new Set();
35953
36649
  historyWriter;
35954
36650
  runtimeMessages = [];
35955
36651
  instanceId;
@@ -35962,6 +36658,7 @@ ${data.message || ""}`.trim();
35962
36658
  async init(context) {
35963
36659
  this.context = context;
35964
36660
  this.settings = context.settings || {};
36661
+ this.adapter.updateRuntimeSettings?.(this.settings);
35965
36662
  this.monitor.updateConfig({
35966
36663
  approvalAlert: this.settings.approvalAlert !== false,
35967
36664
  longGeneratingAlert: this.settings.longGeneratingAlert !== false,
@@ -35977,6 +36674,21 @@ ${data.message || ""}`.trim();
35977
36674
  this.detectStatusTransition();
35978
36675
  });
35979
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
+ }
35980
36692
  if (this.providerSessionId && this.launchMode === "resume") {
35981
36693
  const resumedAt = Date.now();
35982
36694
  this.historyWriter.appendSystemMarker(
@@ -36057,6 +36769,12 @@ ${data.message || ""}`.trim();
36057
36769
  }
36058
36770
  const runtime = this.adapter.getRuntimeMetadata();
36059
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
+ }
36060
36778
  const mergedMessages = this.mergeConversationMessages(parsedMessages);
36061
36779
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
36062
36780
  if (parsedMessages.length > 0) {
@@ -36077,6 +36795,7 @@ ${data.message || ""}`.trim();
36077
36795
  );
36078
36796
  }
36079
36797
  }
36798
+ this.applyProviderResponse(parsedStatus, { phase: "immediate" });
36080
36799
  return {
36081
36800
  type: this.type,
36082
36801
  name: this.provider.name,
@@ -36106,8 +36825,7 @@ ${data.message || ""}`.trim();
36106
36825
  attachedClients: runtime.attachedClients || []
36107
36826
  } : void 0,
36108
36827
  resume: this.provider.resume,
36109
- controlValues: void 0,
36110
- // CLI controls not yet wired from stream
36828
+ controlValues: this.controlValues,
36111
36829
  providerControls: this.provider.controls
36112
36830
  };
36113
36831
  }
@@ -36118,6 +36836,15 @@ ${data.message || ""}`.trim();
36118
36836
  getPresentationMode() {
36119
36837
  return this.presentationMode;
36120
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
+ }
36121
36848
  onEvent(event, data) {
36122
36849
  if (event === "send_message" && data?.text) {
36123
36850
  void this.adapter.sendMessage(data.text).catch((e) => {
@@ -36129,22 +36856,27 @@ ${data.message || ""}`.trim();
36129
36856
  void this.adapter.resolveAction(data).catch((e) => {
36130
36857
  LOG2.warn("CLI", `[${this.type}] resolve_action failed: ${e?.message || e}`);
36131
36858
  });
36859
+ } else if (event === "provider_state_patch" && data && typeof data === "object") {
36860
+ this.applyProviderResponse(data, { phase: "immediate" });
36132
36861
  }
36133
36862
  }
36134
36863
  dispose() {
36135
36864
  this.adapter.shutdown();
36136
36865
  this.monitor.reset();
36866
+ this.appliedEffectKeys.clear();
36137
36867
  }
36138
36868
  completedDebounceTimer = null;
36139
36869
  completedDebouncePending = null;
36140
36870
  detectStatusTransition() {
36141
36871
  const now = Date.now();
36142
36872
  const adapterStatus = this.adapter.getStatus();
36873
+ const parsedStatus = this.adapter.getScriptParsedStatus?.() || null;
36143
36874
  const newStatus = adapterStatus.status;
36144
36875
  const dirName = this.workingDir.split("/").filter(Boolean).pop() || "session";
36145
36876
  const chatTitle = `${this.provider.name} \xB7 ${dirName}`;
36146
36877
  const partial2 = this.adapter.getPartialResponse();
36147
36878
  const progressFingerprint = newStatus === "generating" ? `${partial2 || ""}::${adapterStatus.messages.at(-1)?.content || ""}`.slice(-2e3) : void 0;
36879
+ const previousStatus = this.lastStatus;
36148
36880
  if (newStatus !== this.lastStatus) {
36149
36881
  LOG2.info("CLI", `[${this.type}] status: ${this.lastStatus} \u2192 ${newStatus}`);
36150
36882
  if (this.lastStatus === "idle" && newStatus === "generating") {
@@ -36237,6 +36969,9 @@ ${data.message || ""}`.trim();
36237
36969
  }
36238
36970
  this.lastStatus = newStatus;
36239
36971
  }
36972
+ this.applyProviderResponse(parsedStatus, {
36973
+ phase: newStatus === "idle" && (previousStatus === "generating" || previousStatus === "waiting_approval") ? "turn_completed" : "immediate"
36974
+ });
36240
36975
  const agentKey = `${this.type}:cli`;
36241
36976
  const monitorEvents = this.monitor.check(agentKey, newStatus, now, progressFingerprint);
36242
36977
  for (const me of monitorEvents) {
@@ -36252,6 +36987,88 @@ ${data.message || ""}`.trim();
36252
36987
  this.events = [];
36253
36988
  return events;
36254
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
+ }
36255
37072
  // ─── Adapter access (backward compat) ──────────────────
36256
37073
  getAdapter() {
36257
37074
  return this.adapter;
@@ -39010,6 +39827,22 @@ Run 'adhdev doctor' for detailed diagnostics.`
39010
39827
  function getWinProcessNames() {
39011
39828
  return getProviderLoader().getWinProcessNames();
39012
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
+ }
39013
39846
  async function findFreePort(ports) {
39014
39847
  for (const port2 of ports) {
39015
39848
  const free = await checkPortFree(port2);
@@ -39062,12 +39895,12 @@ Run 'adhdev doctor' for detailed diagnostics.`
39062
39895
  try {
39063
39896
  if (plat === "darwin" && appName) {
39064
39897
  try {
39065
- (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`, {
39066
39899
  timeout: 5e3
39067
39900
  });
39068
39901
  } catch {
39069
39902
  try {
39070
- (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 });
39071
39904
  } catch {
39072
39905
  }
39073
39906
  }
@@ -39097,7 +39930,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
39097
39930
  }
39098
39931
  if (plat === "darwin" && appName) {
39099
39932
  try {
39100
- (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 });
39101
39934
  } catch {
39102
39935
  }
39103
39936
  } else if (plat === "win32" && winProcesses) {
@@ -39120,8 +39953,23 @@ Run 'adhdev doctor' for detailed diagnostics.`
39120
39953
  if (plat === "darwin") {
39121
39954
  const appName = getMacAppIdentifiers()[ideId];
39122
39955
  if (!appName) return false;
39123
- const result = (0, import_child_process6.execSync)(`pgrep -f "${appName}" 2>/dev/null`, { encoding: "utf-8" });
39124
- 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
+ }
39125
39973
  } else if (plat === "win32") {
39126
39974
  const winProcesses = getWinProcessNames()[ideId];
39127
39975
  if (!winProcesses) return false;
@@ -39270,7 +40118,8 @@ Run 'adhdev doctor' for detailed diagnostics.`
39270
40118
  await launchLinux(targetIde, port, workspace, options.newWindow);
39271
40119
  }
39272
40120
  let cdpReady = false;
39273
- for (let i = 0; i < 30; i++) {
40121
+ const waitDeadline = Date.now() + getCdpStartupTimeoutMs(targetIde.id);
40122
+ while (Date.now() < waitDeadline) {
39274
40123
  await new Promise((r) => setTimeout(r, 500));
39275
40124
  if (await isCdpActive(port)) {
39276
40125
  cdpReady = true;
@@ -39299,14 +40148,18 @@ Run 'adhdev doctor' for detailed diagnostics.`
39299
40148
  }
39300
40149
  async function launchMacOS(ide, port, workspace, newWindow) {
39301
40150
  const appName = getMacAppIdentifiers()[ide.id];
40151
+ const preferredMethod = getPreferredLaunchMethod(ide.id, "darwin");
39302
40152
  const args = ["--remote-debugging-port=" + port];
39303
40153
  if (newWindow) args.push("--new-window");
39304
40154
  if (workspace) args.push(workspace);
39305
- 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) {
39306
40161
  const openArgs = ["-a", appName, "--args", ...args];
39307
40162
  (0, import_child_process6.spawn)("open", openArgs, { detached: true, stdio: "ignore" }).unref();
39308
- } else if (ide.cliCommand) {
39309
- (0, import_child_process6.spawn)(ide.cliCommand, args, { detached: true, stdio: "ignore" }).unref();
39310
40163
  } else {
39311
40164
  throw new Error(`No app identifier or CLI for ${ide.displayName}`);
39312
40165
  }
@@ -39609,6 +40462,7 @@ Run 'adhdev doctor' for detailed diagnostics.`
39609
40462
  workspaces: wsState.workspaces,
39610
40463
  defaultWorkspaceId: wsState.defaultWorkspaceId,
39611
40464
  defaultWorkspacePath: wsState.defaultWorkspacePath,
40465
+ terminalSizingMode: cfg.terminalSizingMode || "measured",
39612
40466
  recentLaunches: buildRecentLaunches(recentActivity),
39613
40467
  terminalBackend,
39614
40468
  availableProviders: buildAvailableProviders(options.providerLoader)
@@ -40444,19 +41298,10 @@ Run 'adhdev doctor' for detailed diagnostics.`
40444
41298
  mode: data.mode,
40445
41299
  activeModal: data.activeModal
40446
41300
  };
40447
- if (this.provider.controls?.length) {
40448
- const cv = {};
40449
- for (const ctrl of this.provider.controls) {
40450
- if (!ctrl.readFrom) continue;
40451
- const val = data[ctrl.readFrom];
40452
- if (val !== void 0 && val !== null) {
40453
- cv[ctrl.id] = typeof val === "object" ? val.name || val.id || String(val) : val;
40454
- }
40455
- }
40456
- if (data.model && !cv["model"]) cv["model"] = data.model;
40457
- if (data.mode && !cv["mode"]) cv["mode"] = data.mode;
40458
- if (Object.keys(cv).length > 0) state.controlValues = cv;
40459
- }
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;
40460
41305
  if (state.messages.length > 0) {
40461
41306
  this.lastSuccessState = state;
40462
41307
  }
@@ -40976,6 +41821,8 @@ Run 'adhdev doctor' for detailed diagnostics.`
40976
41821
  activeModal: stream.activeModal || null,
40977
41822
  model: stream.model || void 0,
40978
41823
  mode: stream.mode || void 0,
41824
+ controlValues: stream.controlValues || void 0,
41825
+ effects: stream.effects || void 0,
40979
41826
  sessionId: stream.sessionId || stream.instanceId || void 0,
40980
41827
  title: stream.title || stream.agentName || void 0,
40981
41828
  agentType: stream.agentType || void 0,