newmark-agent 0.3.10 → 0.3.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/config.example.json +6 -0
  2. package/dist/cli-commands.d.ts +7 -0
  3. package/dist/cli-commands.js +206 -15
  4. package/dist/cli-discovery.d.ts +15 -0
  5. package/dist/cli-discovery.js +182 -0
  6. package/dist/cli-help.d.ts +4 -0
  7. package/dist/cli-help.js +46 -0
  8. package/dist/conversation-utility-host.bundle.cjs +548 -137
  9. package/dist/core/agent.d.ts +18 -3
  10. package/dist/core/agent.js +236 -34
  11. package/dist/core/agentKernelRunner.js +72 -10
  12. package/dist/core/browserControl.d.ts +8 -0
  13. package/dist/core/browserUsePageAdapter.d.ts +3 -0
  14. package/dist/core/browserUsePageAdapter.js +19 -2
  15. package/dist/core/computerUseSession.d.ts +44 -0
  16. package/dist/core/computerUseSession.js +105 -0
  17. package/dist/core/config.d.ts +7 -2
  18. package/dist/core/config.js +24 -6
  19. package/dist/core/conversationKernel.d.ts +1 -0
  20. package/dist/core/conversationKernel.js +39 -1
  21. package/dist/core/electronBrowserUseHost.js +7 -0
  22. package/dist/core/electronUtilityAgentClient.js +84 -2
  23. package/dist/core/electronUtilityRuntimePool.d.ts +11 -0
  24. package/dist/core/electronUtilityRuntimePool.js +62 -0
  25. package/dist/core/flow-runner.js +1 -1
  26. package/dist/core/modelValidationStore.d.ts +4 -1
  27. package/dist/core/modelValidationStore.js +7 -1
  28. package/dist/core/utilityHostToolRouter.d.ts +7 -0
  29. package/dist/core/utilityHostToolRouter.js +25 -33
  30. package/dist/core/workspace.d.ts +6 -0
  31. package/dist/core/workspace.js +14 -0
  32. package/dist/core/wslAgentRuntimePool.d.ts +4 -0
  33. package/dist/core/wslAgentRuntimePool.js +56 -0
  34. package/dist/launcher.js +51 -10
  35. package/dist/llm/provider.d.ts +8 -5
  36. package/dist/llm/provider.js +85 -33
  37. package/dist/main.js +297 -61
  38. package/dist/preload.js +10 -2
  39. package/dist/providers/chat-completions.adapter.js +1 -5
  40. package/dist/providers/provider-events.d.ts +7 -0
  41. package/dist/providers/provider-events.js +44 -0
  42. package/dist/providers/responses.adapter.js +1 -3
  43. package/dist/tools/index.js +36 -49
  44. package/dist/tui/src/adapters/core-runtime-adapter.js +40 -3
  45. package/dist/tui/src/app.js +47 -13
  46. package/dist/tui/src/render.js +23 -7
  47. package/dist/tui/src/state.js +61 -9
  48. package/dist/ui/index.html +545 -122
  49. package/dist/wsl-agent-host.bundle.cjs +548 -137
  50. package/package.json +14 -5
@@ -326959,7 +326959,12 @@ async function runFlowBuild(agent, prompt, options) {
326959
326959
  if (!options.signal?.aborted && typeof agent.emitWorkEvent === "function") {
326960
326960
  agent.emitWorkEvent({ type: "error", content: reportedError.message, runId });
326961
326961
  }
326962
- agent.finishConversationWorkRun(runId, options.signal?.aborted ? "interrupted" : "error");
326962
+ agent.finishConversationWorkRun(
326963
+ runId,
326964
+ options.signal?.aborted ? "interrupted" : "error",
326965
+ void 0,
326966
+ options.signal?.aborted ? "" : reportedError.message
326967
+ );
326963
326968
  agent.flushWorkspaceConversationState();
326964
326969
  }
326965
326970
  throw reportedError;
@@ -327619,8 +327624,10 @@ var ConfigManager = class {
327619
327624
  rootPath;
327620
327625
  config;
327621
327626
  workspaceOverrides;
327622
- constructor(rootPath) {
327627
+ readOnly;
327628
+ constructor(rootPath, options = {}) {
327623
327629
  this.rootPath = rootPath;
327630
+ this.readOnly = options.readOnly === true;
327624
327631
  this.workspaceOverrides = /* @__PURE__ */ new Map();
327625
327632
  this.config = this.load();
327626
327633
  }
@@ -327635,17 +327642,19 @@ var ConfigManager = class {
327635
327642
  const raw = JSON.parse(readJsonText(cp));
327636
327643
  const normalized = normalizeConfigShape(raw, true);
327637
327644
  if (isCorruptConfig(raw, normalized)) {
327645
+ if (this.readOnly) return defaultConfig();
327638
327646
  this.backupConfig(cp, "invalid-shape");
327639
327647
  return this.writeRecoveredConfig(cp);
327640
327648
  }
327641
327649
  if (migrateProviderIdsInConfig(normalized)) {
327642
327650
  try {
327643
- fs3.writeFileSync(cp, JSON.stringify(normalized, null, 2), "utf-8");
327651
+ if (!this.readOnly) fs3.writeFileSync(cp, JSON.stringify(normalized, null, 2), "utf-8");
327644
327652
  } catch {
327645
327653
  }
327646
327654
  }
327647
327655
  return normalized;
327648
327656
  } catch {
327657
+ if (this.readOnly) return defaultConfig();
327649
327658
  this.backupConfig(cp, "invalid-json");
327650
327659
  return this.writeRecoveredConfig(cp);
327651
327660
  }
@@ -327691,10 +327700,12 @@ var ConfigManager = class {
327691
327700
  this.config[section][key3] = { value: normalizedValue };
327692
327701
  }
327693
327702
  save() {
327703
+ if (this.readOnly) return;
327694
327704
  const j2 = JSON.stringify(this.config, null, 2);
327695
327705
  fs3.writeFileSync(path3.join(this.rootPath, "config.json"), j2, "utf-8");
327696
327706
  }
327697
327707
  saveTo(targetPath) {
327708
+ if (this.readOnly) return;
327698
327709
  const j2 = JSON.stringify(this.config, null, 2);
327699
327710
  fs3.writeFileSync(targetPath, j2, "utf-8");
327700
327711
  }
@@ -327919,12 +327930,14 @@ var ConfigManager = class {
327919
327930
  return providers;
327920
327931
  }
327921
327932
  writeRecoveredConfig(configPath) {
327933
+ if (this.readOnly) return defaultConfig();
327922
327934
  const config = loadExampleConfig();
327923
327935
  fs3.mkdirSync(path3.dirname(configPath), { recursive: true });
327924
327936
  fs3.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
327925
327937
  return config;
327926
327938
  }
327927
327939
  backupConfig(configPath, reason) {
327940
+ if (this.readOnly) return;
327928
327941
  try {
327929
327942
  if (!fs3.existsSync(configPath)) return;
327930
327943
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -328269,7 +328282,10 @@ function defaultConfig() {
328269
328282
  general: {
328270
328283
  tone: { _description: "Conversation style", _type: "choice", _values: ["strict_simple", "casual_friendly"], value: "strict_simple" },
328271
328284
  language: { _description: "Default language", _type: "choice", _values: ["en", "zh", "auto"], value: "auto" },
328272
- close_behavior: { _description: "Close behavior", _type: "choice", _values: ["minimize", "exit"], value: "minimize" },
328285
+ // A first-run desktop window must have a deterministic close/exit
328286
+ // contract. Users who explicitly choose minimize-to-tray keep that
328287
+ // choice, but a fresh install must not hide the process on OS close.
328288
+ close_behavior: { _description: "Close behavior", _type: "choice", _values: ["minimize", "exit"], value: "exit" },
328273
328289
  default_input: { _description: "Default input mode", _type: "choice", _values: ["guide", "next"], value: "guide" },
328274
328290
  auto_archive_on_close: { _description: "Auto archive on close", _type: "boolean", value: true }
328275
328291
  },
@@ -328608,6 +328624,38 @@ function providerAbortError(signal) {
328608
328624
  if (!error.name || error.name === "Error") error.name = "AbortError";
328609
328625
  return error;
328610
328626
  }
328627
+ function providerStreamTimeoutError(timeoutMs) {
328628
+ const error = new Error("Stream read timeout");
328629
+ error.name = "TimeoutError";
328630
+ error.message = `Stream read timeout after ${timeoutMs}ms`;
328631
+ return error;
328632
+ }
328633
+ async function readProviderStreamChunk(reader, signal, timeoutMs = 3e4) {
328634
+ if (signal.aborted) throw providerAbortError(signal);
328635
+ let timer;
328636
+ let onAbort;
328637
+ const abortPromise = new Promise((_3, reject) => {
328638
+ onAbort = () => reject(providerAbortError(signal));
328639
+ signal.addEventListener("abort", onAbort, { once: true });
328640
+ });
328641
+ const timeoutPromise = new Promise((_3, reject) => {
328642
+ timer = setTimeout(() => reject(providerStreamTimeoutError(timeoutMs)), timeoutMs);
328643
+ });
328644
+ try {
328645
+ return await Promise.race([reader.read(), abortPromise, timeoutPromise]);
328646
+ } catch (error) {
328647
+ if (signal.aborted || error instanceof Error && error.name === "TimeoutError") {
328648
+ try {
328649
+ await reader.cancel(error);
328650
+ } catch {
328651
+ }
328652
+ }
328653
+ throw error;
328654
+ } finally {
328655
+ if (timer) clearTimeout(timer);
328656
+ if (onAbort) signal.removeEventListener("abort", onAbort);
328657
+ }
328658
+ }
328611
328659
  function parseProviderSse(raw) {
328612
328660
  const events = [];
328613
328661
  for (const block of String(raw || "").replace(/\r\n/g, "\n").split(/\n\n+/)) {
@@ -328843,12 +328891,7 @@ var ChatCompletionsAdapter = class {
328843
328891
  let emittedTool = false;
328844
328892
  try {
328845
328893
  while (true) {
328846
- if (signal.aborted) throw providerAbortError(signal);
328847
- const readPromise = reader.read();
328848
- const timeoutPromise = new Promise(
328849
- (_3, reject) => setTimeout(() => reject(new Error("Stream read timeout")), 3e4)
328850
- );
328851
- const { done, value } = await Promise.race([readPromise, timeoutPromise]);
328894
+ const { done, value } = await readProviderStreamChunk(reader, signal);
328852
328895
  if (done) break;
328853
328896
  buffer += decoder.decode(value, { stream: true });
328854
328897
  const lines = buffer.split("\n");
@@ -329086,8 +329129,7 @@ var ResponsesAdapter = class {
329086
329129
  let streamError = "";
329087
329130
  try {
329088
329131
  while (true) {
329089
- if (signal.aborted) throw providerAbortError(signal);
329090
- const { done, value } = await reader.read();
329132
+ const { done, value } = await readProviderStreamChunk(reader, signal);
329091
329133
  if (done) break;
329092
329134
  buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
329093
329135
  const blocks = buffer.split(/\n\n+/);
@@ -329297,6 +329339,16 @@ function createProviderAdapter(providerId, apiMode) {
329297
329339
  }
329298
329340
 
329299
329341
  // src/llm/provider.ts
329342
+ var DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS = 9e4;
329343
+ var MIN_PROVIDER_REQUEST_TIMEOUT_MS = 50;
329344
+ function providerTimeoutError(timeoutMs) {
329345
+ const error = new Error(`Provider request timed out after ${timeoutMs}ms`);
329346
+ error.name = "TimeoutError";
329347
+ return error;
329348
+ }
329349
+ function isProviderTimeoutError(error) {
329350
+ return error instanceof Error && error.name === "TimeoutError";
329351
+ }
329300
329352
  function abortFailure(signal) {
329301
329353
  const reason = signal?.reason;
329302
329354
  const error = reason instanceof Error ? reason : new Error(reason ? String(reason) : "LLM request aborted");
@@ -329326,13 +329378,14 @@ function parseProviderSse2(raw) {
329326
329378
  return events;
329327
329379
  }
329328
329380
  var LLMProvider = class _LLMProvider {
329329
- constructor(name50, baseUrl, apiKey, explicitProtocol, openAIMode = "chat_stream", useProviderAdaptersV2 = false) {
329381
+ constructor(name50, baseUrl, apiKey, explicitProtocol, openAIMode = "chat_stream", useProviderAdaptersV2 = false, requestTimeoutMs = DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS) {
329330
329382
  this.name = name50;
329331
329383
  this.baseUrl = baseUrl;
329332
329384
  this.apiKey = apiKey;
329333
329385
  this.explicitProtocol = explicitProtocol;
329334
329386
  this.openAIMode = openAIMode;
329335
329387
  this.useProviderAdaptersV2 = useProviderAdaptersV2;
329388
+ this.requestTimeoutMs = requestTimeoutMs;
329336
329389
  }
329337
329390
  name;
329338
329391
  baseUrl;
@@ -329340,8 +329393,25 @@ var LLMProvider = class _LLMProvider {
329340
329393
  explicitProtocol;
329341
329394
  openAIMode;
329342
329395
  useProviderAdaptersV2;
329396
+ requestTimeoutMs;
329343
329397
  static nodeHttpTransport = null;
329344
329398
  static powershellTransport = null;
329399
+ effectiveRequestTimeout(timeoutMs) {
329400
+ const requested = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS;
329401
+ const configured = Number.isFinite(this.requestTimeoutMs) && this.requestTimeoutMs > 0 ? this.requestTimeoutMs : DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS;
329402
+ return Math.max(MIN_PROVIDER_REQUEST_TIMEOUT_MS, Math.min(requested, configured));
329403
+ }
329404
+ async withRequestTimeout(promise, timeoutMs, signal) {
329405
+ let timer;
329406
+ const timeoutPromise = new Promise((_3, reject) => {
329407
+ timer = setTimeout(() => reject(providerTimeoutError(timeoutMs)), timeoutMs);
329408
+ });
329409
+ try {
329410
+ return await abortable(Promise.race([promise, timeoutPromise]), signal);
329411
+ } finally {
329412
+ if (timer) clearTimeout(timer);
329413
+ }
329414
+ }
329345
329415
  intelligenceConfig(tier) {
329346
329416
  switch (tier) {
329347
329417
  case "low":
@@ -329443,6 +329513,7 @@ var LLMProvider = class _LLMProvider {
329443
329513
  };
329444
329514
  }
329445
329515
  async postJsonWithFetchFallback(url, headers, body, timeoutMs = 12e4, signal) {
329516
+ const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
329446
329517
  if (this.isPlainHttpLoopback(url)) {
329447
329518
  const pathname = (() => {
329448
329519
  try {
@@ -329452,7 +329523,7 @@ var LLMProvider = class _LLMProvider {
329452
329523
  }
329453
329524
  })();
329454
329525
  this.transportDiagnostic("loopback:start", pathname);
329455
- const local = await this.nodeHttpJson("POST", url, headers, JSON.stringify(body), signal);
329526
+ const local = await this.nodeHttpJson("POST", url, headers, JSON.stringify(body), signal, effectiveTimeout);
329456
329527
  this.transportDiagnostic("loopback:complete", `status=${local.status} bytes=${Buffer.byteLength(local.body || "")}`);
329457
329528
  return {
329458
329529
  ok: local.status >= 200 && local.status < 300,
@@ -329466,7 +329537,7 @@ var LLMProvider = class _LLMProvider {
329466
329537
  const forwardAbort = () => abort.abort(signal?.reason);
329467
329538
  if (signal?.aborted) forwardAbort();
329468
329539
  else signal?.addEventListener("abort", forwardAbort, { once: true });
329469
- const timer = setTimeout(() => abort.abort(), timeoutMs);
329540
+ const timer = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
329470
329541
  try {
329471
329542
  const response = await fetch(url, {
329472
329543
  method: "POST",
@@ -329477,8 +329548,9 @@ var LLMProvider = class _LLMProvider {
329477
329548
  return response;
329478
329549
  } catch (e3) {
329479
329550
  if (signal?.aborted) throw abortFailure(signal);
329551
+ if (abort.signal.aborted) throw abortFailure(abort.signal);
329480
329552
  if (!this.shouldUseNodeHttpFallback(e3)) throw e3;
329481
- const fallback = await this.nodeHttpJson("POST", url, headers, JSON.stringify(body), signal);
329553
+ const fallback = await this.nodeHttpJson("POST", url, headers, JSON.stringify(body), signal, effectiveTimeout);
329482
329554
  return {
329483
329555
  ok: fallback.status >= 200 && fallback.status < 300,
329484
329556
  status: fallback.status,
@@ -329492,14 +329564,16 @@ var LLMProvider = class _LLMProvider {
329492
329564
  }
329493
329565
  }
329494
329566
  async getJsonWithFetchFallback(url, headers, timeoutMs = 3e4) {
329567
+ const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
329495
329568
  const abort = new AbortController();
329496
- const timer = setTimeout(() => abort.abort(), timeoutMs);
329569
+ const timer = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
329497
329570
  try {
329498
329571
  const response = await fetch(url, { method: "GET", headers, signal: abort.signal });
329499
329572
  return response;
329500
329573
  } catch (e3) {
329574
+ if (abort.signal.aborted) throw abortFailure(abort.signal);
329501
329575
  if (!this.shouldUseNodeHttpFallback(e3)) throw e3;
329502
- const fallback = await this.nodeHttpJson("GET", url, headers);
329576
+ const fallback = await this.nodeHttpJson("GET", url, headers, "", void 0, effectiveTimeout);
329503
329577
  return {
329504
329578
  ok: fallback.status >= 200 && fallback.status < 300,
329505
329579
  status: fallback.status,
@@ -329512,14 +329586,16 @@ var LLMProvider = class _LLMProvider {
329512
329586
  }
329513
329587
  }
329514
329588
  shouldUseNodeHttpFallback(error) {
329515
- return error instanceof TypeError && /fetch failed/i.test(error.message) || error instanceof Error && /abort/i.test(error.name || error.message);
329589
+ return error instanceof TypeError && /fetch failed/i.test(error.message);
329516
329590
  }
329517
- nodeHttpJson(method, urlValue, headers, body = "", signal) {
329591
+ nodeHttpJson(method, urlValue, headers, body = "", signal, timeoutMs = DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS) {
329592
+ const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
329518
329593
  if (_LLMProvider.nodeHttpTransport) {
329519
- return abortable(_LLMProvider.nodeHttpTransport(method, urlValue, headers, body), signal).catch((error) => {
329594
+ return this.withRequestTimeout(_LLMProvider.nodeHttpTransport(method, urlValue, headers, body), effectiveTimeout, signal).catch((error) => {
329520
329595
  if (signal?.aborted) throw abortFailure(signal);
329596
+ if (isProviderTimeoutError(error)) throw error;
329521
329597
  if (process.platform === "win32") {
329522
- return this.powershellJson(method, urlValue, headers, body, signal);
329598
+ return this.powershellJson(method, urlValue, headers, body, signal, effectiveTimeout);
329523
329599
  }
329524
329600
  throw error;
329525
329601
  });
@@ -329564,8 +329640,8 @@ var LLMProvider = class _LLMProvider {
329564
329640
  else fail(new Error("Node HTTP response closed before completion"));
329565
329641
  });
329566
329642
  });
329567
- req.setTimeout(12e4, () => {
329568
- req.destroy(new Error("Node HTTP fallback timeout"));
329643
+ req.setTimeout(effectiveTimeout, () => {
329644
+ req.destroy(providerTimeoutError(effectiveTimeout));
329569
329645
  });
329570
329646
  req.on("error", reject);
329571
329647
  const onAbort = () => req.destroy(abortFailure(signal));
@@ -329576,15 +329652,17 @@ var LLMProvider = class _LLMProvider {
329576
329652
  req.end();
329577
329653
  }).catch((error) => {
329578
329654
  if (signal?.aborted) throw abortFailure(signal);
329655
+ if (isProviderTimeoutError(error)) throw error;
329579
329656
  if (process.platform === "win32") {
329580
- return this.powershellJson(method, urlValue, headers, body, signal);
329657
+ return this.powershellJson(method, urlValue, headers, body, signal, effectiveTimeout);
329581
329658
  }
329582
329659
  throw error;
329583
329660
  });
329584
329661
  }
329585
- powershellJson(method, urlValue, headers, body = "", signal) {
329662
+ powershellJson(method, urlValue, headers, body = "", signal, timeoutMs = DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS) {
329663
+ const effectiveTimeout = this.effectiveRequestTimeout(timeoutMs);
329586
329664
  if (_LLMProvider.powershellTransport) {
329587
- return _LLMProvider.powershellTransport(method, urlValue, headers, body);
329665
+ return this.withRequestTimeout(_LLMProvider.powershellTransport(method, urlValue, headers, body), effectiveTimeout, signal);
329588
329666
  }
329589
329667
  return new Promise((resolve16, reject) => {
329590
329668
  const headerJson = JSON.stringify(headers);
@@ -329613,7 +329691,7 @@ var LLMProvider = class _LLMProvider {
329613
329691
  " $raw = $headerJson | ConvertFrom-Json",
329614
329692
  " foreach ($p in $raw.PSObject.Properties) { $headers[$p.Name] = [string]$p.Value }",
329615
329693
  "}",
329616
- "$params = @{ Uri = $uri; Method = $method; Headers = $headers; UseBasicParsing = $true; TimeoutSec = 120 }",
329694
+ `'$params = @{ Uri = $uri; Method = $method; Headers = $headers; UseBasicParsing = $true; TimeoutSec = ${Math.max(1, Math.ceil(effectiveTimeout / 1e3))} }`,
329617
329695
  'if ($method -eq "POST") { $params["Body"] = $bodyJson }',
329618
329696
  'if ($method -eq "POST") { $params["ContentType"] = "application/json; charset=utf-8" }',
329619
329697
  "$resp = Invoke-WebRequest @params",
@@ -329645,8 +329723,8 @@ var LLMProvider = class _LLMProvider {
329645
329723
  const timer = setTimeout(() => {
329646
329724
  child.kill();
329647
329725
  cleanup();
329648
- reject(new Error("PowerShell HTTP fallback timeout"));
329649
- }, 13e4);
329726
+ reject(providerTimeoutError(effectiveTimeout));
329727
+ }, effectiveTimeout + 5e3);
329650
329728
  child.stdout.setEncoding("utf8");
329651
329729
  child.stderr.setEncoding("utf8");
329652
329730
  child.stdout.on("data", (chunk) => {
@@ -330094,10 +330172,10 @@ ${responsePath}
330094
330172
  return this.shouldUseResponsesFallback(Number(match[1]), errorText);
330095
330173
  }
330096
330174
  /**
330097
- * Loopback-aware transport injected into adapter `execute`. Mirrors the
330098
- * legacy orchestration exactly: streaming requests go through fetch with a
330099
- * 120s timeout and degrade to a non-streaming node-http request on fetch
330100
- * failure; non-streaming requests reuse postJsonWithFetchFallback.
330175
+ * Loopback-aware transport injected into adapter `execute`. Streaming
330176
+ * requests retain the fetch-to-node fallback for transport failures, while
330177
+ * a local deadline is returned directly so one request cannot become a
330178
+ * second Windows fallback request.
330101
330179
  */
330102
330180
  buildProviderAdapterTransport() {
330103
330181
  return async (request, signal) => {
@@ -330106,7 +330184,8 @@ ${responsePath}
330106
330184
  const forwardAbort = () => abort.abort(signal?.reason);
330107
330185
  if (signal?.aborted) forwardAbort();
330108
330186
  else signal?.addEventListener("abort", forwardAbort, { once: true });
330109
- const timer = setTimeout(() => abort.abort(), 12e4);
330187
+ const effectiveTimeout = this.effectiveRequestTimeout(12e4);
330188
+ const timer = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
330110
330189
  try {
330111
330190
  try {
330112
330191
  return await fetch(request.url, {
@@ -330117,6 +330196,7 @@ ${responsePath}
330117
330196
  });
330118
330197
  } catch (error) {
330119
330198
  if (signal?.aborted) throw abortFailure(signal);
330199
+ if (abort.signal.aborted) throw abortFailure(abort.signal);
330120
330200
  if (!this.shouldUseNodeHttpFallback(error)) throw error;
330121
330201
  const fallbackHeaders = { ...request.headers };
330122
330202
  delete fallbackHeaders["Accept"];
@@ -330124,7 +330204,7 @@ ${responsePath}
330124
330204
  request.url,
330125
330205
  fallbackHeaders,
330126
330206
  { ...request.body, stream: false },
330127
- 12e4,
330207
+ effectiveTimeout,
330128
330208
  signal
330129
330209
  );
330130
330210
  return this.toTransportResponse(fallback);
@@ -330234,7 +330314,8 @@ ${responsePath}
330234
330314
  const forwardAbort = () => abort.abort(signal?.reason);
330235
330315
  if (signal?.aborted) forwardAbort();
330236
330316
  else signal?.addEventListener("abort", forwardAbort, { once: true });
330237
- const timeout = setTimeout(() => abort.abort(), 12e4);
330317
+ const effectiveTimeout = this.effectiveRequestTimeout(12e4);
330318
+ const timeout = setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout);
330238
330319
  let reader = null;
330239
330320
  try {
330240
330321
  let response;
@@ -330246,9 +330327,10 @@ ${responsePath}
330246
330327
  signal: abort.signal
330247
330328
  });
330248
330329
  } catch (e3) {
330330
+ if (signal?.aborted) throw abortFailure(signal);
330331
+ if (abort.signal.aborted) throw abortFailure(abort.signal);
330249
330332
  if (!this.shouldUseNodeHttpFallback(e3)) throw e3;
330250
330333
  clearTimeout(timeout);
330251
- if (signal?.aborted) throw abortFailure(signal);
330252
330334
  yield* this.githubModelsChatNonStreaming(url, body, signal);
330253
330335
  return;
330254
330336
  }
@@ -330268,13 +330350,9 @@ ${responsePath}
330268
330350
  let currentReasoningContent = "";
330269
330351
  let contentPolicyBlocked = false;
330270
330352
  let emittedContent = false;
330353
+ const streamSignal = signal || new AbortController().signal;
330271
330354
  while (true) {
330272
- if (signal?.aborted) throw abortFailure(signal);
330273
- const readPromise = reader.read();
330274
- const timeoutPromise = new Promise(
330275
- (_3, reject) => setTimeout(() => reject(new Error("Stream read timeout")), 3e4)
330276
- );
330277
- const { done, value } = await Promise.race([readPromise, timeoutPromise]);
330355
+ const { done, value } = await readProviderStreamChunk(reader, streamSignal);
330278
330356
  if (done) break;
330279
330357
  buffer += decoder.decode(value, { stream: true });
330280
330358
  const lines = buffer.split("\n");
@@ -335769,10 +335847,102 @@ function browserVisualFallback(runtimeKey, observationId) {
335769
335847
  return browserFallbacks.get(key(runtimeKey, observationId)) || null;
335770
335848
  }
335771
335849
 
335850
+ // src/core/computerUseSession.ts
335851
+ var COMPUTER_USE_OCCUPIED_MARKER = "computerUse occupied";
335852
+ var COMPUTER_USE_LOCK_TTL_MS = 10 * 60 * 1e3;
335853
+ var ComputerUseSessionRegistry = class {
335854
+ constructor(ttlMs = COMPUTER_USE_LOCK_TTL_MS) {
335855
+ this.ttlMs = ttlMs;
335856
+ }
335857
+ ttlMs;
335858
+ enabledByRuntime = /* @__PURE__ */ new Map();
335859
+ activeLease = null;
335860
+ authorize(action, scope, dryRun = false) {
335861
+ const normalizedAction = String(action || "").trim().toLowerCase();
335862
+ const runtimeKey = String(scope.runtimeKey || "").trim() || "conversation:default";
335863
+ const now2 = Date.now();
335864
+ this.clearExpired(now2);
335865
+ if (this.activeLease && this.activeLease.runtimeKey !== runtimeKey) {
335866
+ return this.occupiedError(normalizedAction, scope.ownerLabel, this.activeLease.ownerLabel);
335867
+ }
335868
+ if (normalizedAction === "takeover_stop") return null;
335869
+ const enabled = this.enabledByRuntime.get(runtimeKey) !== false;
335870
+ const readOnly = normalizedAction === "observe" || normalizedAction === "app_list" || normalizedAction === "app_observe" || normalizedAction === "wait";
335871
+ if (!enabled && !readOnly && !dryRun && normalizedAction !== "takeover_start") {
335872
+ return JSON.stringify({
335873
+ ok: false,
335874
+ action: normalizedAction,
335875
+ error: `ComputerUse is disabled for ${scope.ownerLabel}. Enable ComputerUse for this conversation before sending desktop operations.`,
335876
+ computer_use_enabled: false,
335877
+ requested_owner: scope.ownerLabel
335878
+ }, null, 2);
335879
+ }
335880
+ if (normalizedAction === "takeover_start") this.enabledByRuntime.set(runtimeKey, true);
335881
+ if (!this.activeLease) {
335882
+ this.activeLease = { ...scope, runtimeKey, updatedAt: now2 };
335883
+ } else {
335884
+ this.activeLease.updatedAt = now2;
335885
+ }
335886
+ return null;
335887
+ }
335888
+ complete(action, scope) {
335889
+ const normalizedAction = String(action || "").trim().toLowerCase();
335890
+ const runtimeKey = String(scope.runtimeKey || "").trim() || "conversation:default";
335891
+ if (normalizedAction === "takeover_stop") {
335892
+ if (!this.activeLease || this.activeLease.runtimeKey === runtimeKey) this.activeLease = null;
335893
+ this.enabledByRuntime.set(runtimeKey, false);
335894
+ return;
335895
+ }
335896
+ if (this.activeLease?.runtimeKey === runtimeKey) this.activeLease.updatedAt = Date.now();
335897
+ }
335898
+ setEnabled(scope, enabled) {
335899
+ const runtimeKey = String(scope.runtimeKey || "").trim() || "conversation:default";
335900
+ this.clearExpired();
335901
+ if (this.activeLease && this.activeLease.runtimeKey !== runtimeKey) {
335902
+ return { ok: false, error: this.occupiedError("toggle", scope.ownerLabel, this.activeLease.ownerLabel), state: this.state(runtimeKey) };
335903
+ }
335904
+ this.enabledByRuntime.set(runtimeKey, enabled !== false);
335905
+ if (enabled === false && this.activeLease?.runtimeKey === runtimeKey) this.activeLease = null;
335906
+ return { ok: true, state: this.state(runtimeKey) };
335907
+ }
335908
+ state(runtimeKey) {
335909
+ const key3 = String(runtimeKey || "").trim() || "conversation:default";
335910
+ this.clearExpired();
335911
+ const lease = this.activeLease;
335912
+ return {
335913
+ runtimeKey: key3,
335914
+ enabled: this.enabledByRuntime.get(key3) !== false,
335915
+ occupied: !!lease,
335916
+ ...lease ? { ownerLabel: lease.ownerLabel, updatedAt: lease.updatedAt } : {}
335917
+ };
335918
+ }
335919
+ cancelTarget(runtimeKey) {
335920
+ const key3 = String(runtimeKey || "").trim();
335921
+ if (!key3) return false;
335922
+ const hadActiveLease = this.activeLease?.runtimeKey === key3;
335923
+ if (hadActiveLease) this.activeLease = null;
335924
+ this.enabledByRuntime.set(key3, false);
335925
+ return hadActiveLease;
335926
+ }
335927
+ clearExpired(now2 = Date.now()) {
335928
+ if (this.activeLease && now2 - this.activeLease.updatedAt > this.ttlMs) {
335929
+ this.activeLease = null;
335930
+ }
335931
+ }
335932
+ occupiedError(action, requestedOwner, activeOwner) {
335933
+ return JSON.stringify({
335934
+ ok: false,
335935
+ action,
335936
+ error: `${COMPUTER_USE_OCCUPIED_MARKER}: ComputerUse is already active in ${activeOwner}. Stop it with computer_use takeover_stop or wait before another conversation takes control.`,
335937
+ lock_owner: activeOwner,
335938
+ requested_owner: requestedOwner
335939
+ }, null, 2);
335940
+ }
335941
+ };
335942
+ var defaultComputerUseSessionRegistry = new ComputerUseSessionRegistry();
335943
+
335772
335944
  // src/tools/index.ts
335773
335945
  var globSync = require_index_min().sync;
335774
- var computerUseLock = null;
335775
- var COMPUTER_USE_LOCK_TTL_MS = 10 * 60 * 1e3;
335776
335946
  function normalizeComputerUseAction(action) {
335777
335947
  return String(action || "").trim().toLowerCase();
335778
335948
  }
@@ -335857,49 +336027,24 @@ async function abortableToolDelay(durationMs, signal) {
335857
336027
  if (signal?.aborted) abort();
335858
336028
  });
335859
336029
  }
335860
- function clearStaleComputerUseLock(now2 = Date.now()) {
335861
- if (computerUseLock && now2 - computerUseLock.updatedAt > COMPUTER_USE_LOCK_TTL_MS) {
335862
- computerUseLock = null;
335863
- }
335864
- }
335865
- function computerUseLockError(action, owner) {
335866
- return JSON.stringify({
335867
- ok: false,
335868
- action,
335869
- error: `ComputerUse is already active in ${computerUseLock?.owner || "another conversation"}. Stop it with computer_use takeover_stop or wait before using ComputerUse from another conversation.`,
335870
- lock_owner: computerUseLock?.owner || "",
335871
- requested_owner: owner
335872
- }, null, 2);
335873
- }
335874
- function acquireComputerUseLock(action, owner, wsPath) {
335875
- const now2 = Date.now();
335876
- clearStaleComputerUseLock(now2);
335877
- if (computerUseLock && computerUseLock.owner !== owner) {
335878
- return computerUseLockError(action, owner);
335879
- }
335880
- computerUseLock = {
335881
- owner,
335882
- workspacePath: path15.resolve(wsPath || process.cwd()),
335883
- acquiredAt: computerUseLock?.owner === owner ? computerUseLock.acquiredAt : now2,
335884
- updatedAt: now2
336030
+ function computerUseSessionScope(context, wsPath, owner) {
336031
+ return {
336032
+ runtimeKey: browserUseScope(context, wsPath).runtimeKey,
336033
+ ownerLabel: owner,
336034
+ workspacePath: path15.resolve(wsPath || process.cwd())
335885
336035
  };
335886
- return null;
335887
336036
  }
335888
- function releaseComputerUseLock(action, owner) {
335889
- clearStaleComputerUseLock();
335890
- if (computerUseLock && computerUseLock.owner !== owner) {
335891
- return computerUseLockError(action, owner);
335892
- }
335893
- if (computerUseLock?.owner === owner) computerUseLock = null;
335894
- return null;
336037
+ function acquireComputerUseLock(action, owner, wsPath, context = {}, dryRun = false) {
336038
+ return defaultComputerUseSessionRegistry.authorize(action, computerUseSessionScope(context, wsPath, owner), dryRun);
335895
336039
  }
335896
- function assertComputerUseLockOwner(action, owner) {
335897
- clearStaleComputerUseLock();
335898
- if (computerUseLock && computerUseLock.owner !== owner) {
335899
- return computerUseLockError(action, owner);
335900
- }
336040
+ function releaseComputerUseLock(action, owner, context = {}, wsPath = context.workspacePath || "") {
336041
+ const scope = computerUseSessionScope(context, wsPath, owner);
336042
+ defaultComputerUseSessionRegistry.complete(action, scope);
335901
336043
  return null;
335902
336044
  }
336045
+ function assertComputerUseLockOwner(action, owner, context = {}, wsPath = context.workspacePath || "") {
336046
+ return defaultComputerUseSessionRegistry.authorize(action, computerUseSessionScope(context, wsPath, owner));
336047
+ }
335903
336048
  var ToolExecutor = class {
335904
336049
  constructor(root2, config, ssh, workspace) {
335905
336050
  this.config = config;
@@ -336450,7 +336595,7 @@ var ToolExecutor = class {
336450
336595
  case "computer_use": {
336451
336596
  const action = normalizeComputerUseAction(g2("action"));
336452
336597
  const owner = `${computerUseOwner(context, wsPath)}:${String(context.actorId || "root")}`;
336453
- const lockGuard = action === "takeover_stop" ? assertComputerUseLockOwner(action, owner) : acquireComputerUseLock(action, owner, wsPath);
336598
+ const lockGuard = action === "takeover_stop" ? assertComputerUseLockOwner(action, owner, context, wsPath) : acquireComputerUseLock(action, owner, wsPath, context, args.dry_run === true);
336454
336599
  if (lockGuard) return lockGuard;
336455
336600
  if (process.env.NEWMARK_WSL_DISTRO) {
336456
336601
  try {
@@ -336464,7 +336609,7 @@ var ToolExecutor = class {
336464
336609
  }, 12e4, context.signal);
336465
336610
  return typeof result === "string" ? result : JSON.stringify(result);
336466
336611
  } finally {
336467
- if (action === "takeover_stop") releaseComputerUseLock(action, owner);
336612
+ if (action === "takeover_stop") releaseComputerUseLock(action, owner, context, wsPath);
336468
336613
  }
336469
336614
  }
336470
336615
  if (process.env.NEWMARK_ISOLATED_RUNTIME === "1") {
@@ -336481,7 +336626,7 @@ var ToolExecutor = class {
336481
336626
  const result = await requestUtilityHostTool("computer_use", args, trustedComputerUseContext, 12e4, context.signal);
336482
336627
  return typeof result === "string" ? result : JSON.stringify(result);
336483
336628
  } finally {
336484
- if (action === "takeover_stop") releaseComputerUseLock(action, owner);
336629
+ if (action === "takeover_stop") releaseComputerUseLock(action, owner, context, wsPath);
336485
336630
  }
336486
336631
  }
336487
336632
  const output = await runComputerUse({
@@ -336522,7 +336667,7 @@ var ToolExecutor = class {
336522
336667
  durationMs: Number(step.duration_ms || 0)
336523
336668
  })) : void 0
336524
336669
  });
336525
- if (action === "takeover_stop") releaseComputerUseLock(action, owner);
336670
+ if (action === "takeover_stop") releaseComputerUseLock(action, owner, context, wsPath);
336526
336671
  return output;
336527
336672
  }
336528
336673
  case "terminal_takeover": {
@@ -337003,9 +337148,17 @@ ${snippet ? clean(snippet[1]) : ""}`.trim());
337003
337148
  }
337004
337149
  }
337005
337150
  async browserRun(request, signal, context = {}, workspacePath = this.root) {
337151
+ const scope = browserUseScope(context, workspacePath);
337152
+ const scopedRequest = {
337153
+ ...request,
337154
+ target: {
337155
+ workspaceId: context.workspaceId || terminalTakeoverWorkspaceId(workspacePath),
337156
+ conversationId: context.conversationId || "default",
337157
+ runtimeKey: scope.runtimeKey
337158
+ }
337159
+ };
337006
337160
  if (process.env.NEWMARK_WSL_DISTRO) {
337007
- const scope = browserUseScope(context, workspacePath);
337008
- const result2 = await requestWindowsHostTool("browser_control", request, {
337161
+ const result2 = await requestWindowsHostTool("browser_control", scopedRequest, {
337009
337162
  conversationId: context.conversationId || process.env.NEWMARK_CONVERSATION_ID || "default",
337010
337163
  workspaceId: process.env.NEWMARK_WORKSPACE_ID || context.workspaceId || terminalTakeoverWorkspaceId(workspacePath),
337011
337164
  actorId: context.actorId || ROOT_TERMINAL_ACTOR_ID,
@@ -337015,10 +337168,10 @@ ${snippet ? clean(snippet[1]) : ""}`.trim());
337015
337168
  return this.formatBrowserResult(result2);
337016
337169
  }
337017
337170
  if (process.env.NEWMARK_ISOLATED_RUNTIME === "1") {
337018
- const result2 = await requestUtilityHostTool("browser_control", request, void 0, 3e4, signal);
337171
+ const result2 = await requestUtilityHostTool("browser_control", scopedRequest, void 0, 3e4, signal);
337019
337172
  return this.formatBrowserResult(result2);
337020
337173
  }
337021
- const result = await BrowserControl.run(request, signal);
337174
+ const result = await BrowserControl.run(scopedRequest, signal);
337022
337175
  return this.formatBrowserResult(result);
337023
337176
  }
337024
337177
  formatBrowserResult(result) {
@@ -337845,6 +337998,19 @@ var WorkspaceManager = class {
337845
337998
  this.saveState();
337846
337999
  }
337847
338000
  }
338001
+ /**
338002
+ * Re-read the registry and persisted current-workspace pointer after another
338003
+ * Newmark entrypoint updates Work/*.json. This intentionally does not create
338004
+ * a workspace: a refresh must reflect the shared on-disk state exactly.
338005
+ */
338006
+ reloadFromStorage() {
338007
+ if (this.detached) return this.current;
338008
+ this.scan();
338009
+ this.validate();
338010
+ this.current = null;
338011
+ this.restoreCurrent();
338012
+ return this.current;
338013
+ }
337848
338014
  saveInternal() {
337849
338015
  if (this.detached) return;
337850
338016
  const p = path16.join(this.rootPath, "Work", "Local.json");
@@ -339896,6 +340062,10 @@ var ProviderRunError = class extends Error {
339896
340062
  function kernelTurnFailed(agent, turn) {
339897
340063
  return turn.stopReason === "error" || agent.isLlmErrorText(turn.text);
339898
340064
  }
340065
+ function providerTurnIsEmpty(turn) {
340066
+ return /provider returned an empty response/i.test(`${turn.errorMessage}
340067
+ ${turn.text}`);
340068
+ }
339899
340069
  function removeTrailingFailedAssistant(agent, messages) {
339900
340070
  const last = messages[messages.length - 1];
339901
340071
  if (last?.role !== "assistant") return;
@@ -339931,17 +340101,35 @@ function normalizePublicProviderError(error, secrets = []) {
339931
340101
  }
339932
340102
  return raw.slice(0, 1200);
339933
340103
  }
340104
+ function throwIfKernelAborted(signal) {
340105
+ if (!signal?.aborted) return;
340106
+ const reason = signal.reason;
340107
+ if (reason instanceof Error) {
340108
+ reason.name = "AbortError";
340109
+ throw reason;
340110
+ }
340111
+ const error = new Error(reason ? String(reason) : "Agent run aborted");
340112
+ error.name = "AbortError";
340113
+ throw error;
340114
+ }
339934
340115
  async function runAgentKernel(agent) {
339935
340116
  const stopContextTimer = performanceTimer("context_prepare", { conversationId: agent.activeConversationId });
340117
+ const processSignal = agent.activeProcessSignal();
340118
+ if (processSignal?.aborted) {
340119
+ stopContextTimer();
340120
+ throwIfKernelAborted(processSignal);
340121
+ }
339936
340122
  if (!agent.engineModel()) {
340123
+ const message = "No LLM configured. Add provider in Settings > Models.";
339937
340124
  agent.status = "error";
339938
340125
  agent.saveWorkspaceConversationState();
339939
- return [{ type: "text", text: "[Error] No LLM configured. Add provider in Settings > Models." }];
340126
+ throw new Error(message);
339940
340127
  }
339941
340128
  const [{ Agent: NativeAgent }, KernelStreamCompat] = await Promise.all([
339942
340129
  Promise.resolve().then(() => (init_agentKernel(), agentKernel_exports)),
339943
340130
  Promise.resolve().then(() => (init_stream_types(), stream_types_exports))
339944
340131
  ]);
340132
+ throwIfKernelAborted(processSignal);
339945
340133
  const toolProvisioning = new ToolProvisionSession([], []);
339946
340134
  let activeToolSurfaceIdentity = "";
339947
340135
  let activeToolSurfaceNotice = "";
@@ -339967,6 +340155,7 @@ async function runAgentKernel(agent) {
339967
340155
  const initialToolSurface = refreshToolSurface(true);
339968
340156
  const assembledContext = agent.assembleContextV2(initialToolSurface.systemPromptNotice);
339969
340157
  const systemPrompt = assembledContext.text;
340158
+ throwIfKernelAborted(processSignal);
339970
340159
  let providerRequestCount = 0;
339971
340160
  let bootstrappedCompressionAt = agent.lastCompression?.at || "";
339972
340161
  stopContextTimer();
@@ -339987,6 +340176,24 @@ async function runAgentKernel(agent) {
339987
340176
  kernel2.state.tools = toKernelTools(agent, initialToolSurface.definitions, toolProvisioning);
339988
340177
  kernel2.state.messages = toKernelMessages(agent);
339989
340178
  agent.attachAgentKernelRuntime(kernel2);
340179
+ let detachProcessAbort = () => {
340180
+ };
340181
+ if (processSignal) {
340182
+ const abortKernel = () => kernel2.abort();
340183
+ if (processSignal.aborted) {
340184
+ kernel2.abort();
340185
+ } else {
340186
+ processSignal.addEventListener("abort", abortKernel, { once: true });
340187
+ detachProcessAbort = () => processSignal.removeEventListener("abort", abortKernel);
340188
+ }
340189
+ }
340190
+ try {
340191
+ throwIfKernelAborted(processSignal);
340192
+ } catch (error) {
340193
+ detachProcessAbort();
340194
+ agent.attachAgentKernelRuntime(null);
340195
+ throw error;
340196
+ }
339990
340197
  const tokens = [];
339991
340198
  const runOnce = async (promptMessages, appendPromptToAgentHistory) => {
339992
340199
  let lastAssistant = null;
@@ -340007,10 +340214,13 @@ async function runAgentKernel(agent) {
340007
340214
  }
340008
340215
  await kernel2.prompt(promptMessages);
340009
340216
  const assistant = lastAssistant;
340217
+ const text = assistant ? KernelMessageText(assistant) : "";
340218
+ const hasToolCall = !!assistant?.content?.some((content) => content.type === "toolCall");
340219
+ const emptyResponse = !assistant || !text.trim() && !hasToolCall && String(assistant?.stopReason || "") !== "aborted";
340010
340220
  return {
340011
- text: assistant ? KernelMessageText(assistant) : "",
340221
+ text: emptyResponse ? "[Error] Provider returned an empty response." : text,
340012
340222
  stopReason: String(assistant?.stopReason || ""),
340013
- errorMessage: String(assistant?.errorMessage || "")
340223
+ errorMessage: String(assistant?.errorMessage || (emptyResponse ? "Provider returned an empty response." : ""))
340014
340224
  };
340015
340225
  } finally {
340016
340226
  unsubscribe();
@@ -340045,6 +340255,16 @@ async function runAgentKernel(agent) {
340045
340255
  if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some((t3) => t3.text?.includes("[Model fallback]"))) {
340046
340256
  tokens.unshift({ type: "text", text: `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.` });
340047
340257
  }
340258
+ let emptyResponseRetries = 0;
340259
+ while (providerTurnIsEmpty(lastTurn) && emptyResponseRetries < 2) {
340260
+ removeTrailingFailedAssistant(agent, kernel2.state.messages);
340261
+ emptyResponseRetries += 1;
340262
+ const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${emptyResponseRetries}/2).`;
340263
+ tokens.push({ type: "text", text: notice });
340264
+ agent.recordWorkStatus(notice);
340265
+ await agent.waitForPlannedRouteRetry();
340266
+ lastTurn = await runWithCompressionResume([], false);
340267
+ }
340048
340268
  let routeRetries = 0;
340049
340269
  while (kernelTurnFailed(agent, lastTurn) && routeRetries < 2) {
340050
340270
  const previous = agent.switchToFallbackModel(lastTurn.errorMessage || lastTurn.text);
@@ -340077,6 +340297,7 @@ async function runAgentKernel(agent) {
340077
340297
  else agent.pendingOptions = agent.pendingOptions.filter((question) => !isPlanExecutionQuestion(question));
340078
340298
  }
340079
340299
  } finally {
340300
+ detachProcessAbort();
340080
340301
  agent.attachAgentKernelRuntime(null);
340081
340302
  }
340082
340303
  agent.status = "idle";
@@ -340243,20 +340464,18 @@ async function transformContext(agent, messages, signal) {
340243
340464
  const provider = agent.engineModel();
340244
340465
  if (!provider || !compressionModel) return messages;
340245
340466
  const newmarkMessages = publicHistoryFromKernelMessages(messages);
340246
- const beforeCompression = JSON.stringify(newmarkMessages);
340247
340467
  const compressionAt = agent.lastCompression?.at || "";
340248
- await agent.maybeCompress(newmarkMessages, provider, processSignal, compressionModel);
340468
+ let compressed = await agent.maybeCompress(newmarkMessages, provider, processSignal, compressionModel);
340249
340469
  if (processSignal?.aborted) return messages;
340250
- const primaryCompressed = JSON.stringify(newmarkMessages) !== beforeCompression;
340251
- if (primaryCompressed && agent.estimateContextTokens(newmarkMessages) >= Math.floor(agent.contextWindow(compressionModel).maxTokens * 0.82)) {
340252
- await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true);
340470
+ if (compressed && agent.estimateContextTokens(newmarkMessages) >= Math.floor(agent.contextWindow(compressionModel).maxTokens * 0.82)) {
340471
+ compressed = await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true) || compressed;
340253
340472
  }
340254
340473
  const windowMax = agent.contextWindow(compressionModel).maxTokens;
340255
340474
  const conservativeTokens = agent.estimateContextTokens(newmarkMessages);
340256
340475
  if (conservativeTokens >= Math.floor(windowMax * 0.9) && !processSignal?.aborted) {
340257
- await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true);
340476
+ compressed = await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true) || compressed;
340258
340477
  }
340259
- if (JSON.stringify(newmarkMessages) === beforeCompression) return messages;
340478
+ if (!compressed) return messages;
340260
340479
  const durableMessages = toKernelMessagesFromHistory(newmarkMessages, agent);
340261
340480
  if (agent.lastCompression?.at && agent.lastCompression.at !== compressionAt) {
340262
340481
  agent.recordContextCompressionStep();
@@ -342147,9 +342366,11 @@ function key2(model) {
342147
342366
  }
342148
342367
  var FileModelValidationCache = class {
342149
342368
  filePath;
342369
+ readOnly;
342150
342370
  records = /* @__PURE__ */ new Map();
342151
- constructor(rootPath) {
342371
+ constructor(rootPath, options = {}) {
342152
342372
  this.filePath = path20.join(rootPath, "model-validation", "records.json");
342373
+ this.readOnly = options.readOnly === true;
342153
342374
  this.load();
342154
342375
  }
342155
342376
  get(modelKey2) {
@@ -342158,10 +342379,12 @@ var FileModelValidationCache = class {
342158
342379
  }
342159
342380
  set(record) {
342160
342381
  this.records.set(record.modelKey || key2(record.model), JSON.parse(JSON.stringify(record)));
342382
+ if (this.readOnly) return;
342161
342383
  this.save();
342162
342384
  }
342163
342385
  delete(modelKey2) {
342164
342386
  if (!this.records.delete(modelKey2)) return;
342387
+ if (this.readOnly) return;
342165
342388
  this.save();
342166
342389
  }
342167
342390
  load() {
@@ -344037,7 +344260,7 @@ var Agent4 = class _Agent {
344037
344260
  this.subagentName = options.subagentName || "";
344038
344261
  this.subagentPrompt = options.subagentPrompt || "";
344039
344262
  this.linkedPlanAccess = options.linkedPlanAccess;
344040
- this.config = new ConfigManager(rootPath);
344263
+ this.config = new ConfigManager(rootPath, { readOnly: options.readOnlyConfig === true });
344041
344264
  this.compressionHistoryArchive = new CompressionHistoryArchive(rootPath);
344042
344265
  this.contextV2 = new AgentContextManager(rootPath, this.config);
344043
344266
  this.agentRunService = this.config.contextFlag("agent_runtime_v2") ? new AgentRunService(path28.join(rootPath, ".newmark-context-v2")) : null;
@@ -344568,7 +344791,10 @@ var Agent4 = class _Agent {
344568
344791
  }
344569
344792
  const previousAuto = this.model === "auto" ? this.resolvedDeployment : null;
344570
344793
  const qualified = parseDeploymentSelectionValue2(requested);
344571
- const current = qualified ? this.config.findDeployment(qualified) : requested ? this.config.findModel(requested) : void 0;
344794
+ const legacyQualified = requested.includes("/") ? this.config.allModels().filter(
344795
+ (model2) => `${model2.provider_id}/${model2.name}` === requested || `${model2.provider}/${model2.name}` === requested
344796
+ ) : [];
344797
+ const current = qualified ? this.config.findDeployment(qualified) : legacyQualified.length === 1 ? legacyQualified[0] : requested ? this.config.findModel(requested) : void 0;
344572
344798
  this.model = current?.name || requested;
344573
344799
  this.fixedDeployment = current ? this.deploymentRef(current) : qualified;
344574
344800
  this.resolvedDeployment = null;
@@ -345685,7 +345911,7 @@ ${String(event.toolArgs || "")}`;
345685
345911
  this.saveWorkspaceConversationState(true);
345686
345912
  return true;
345687
345913
  }
345688
- finishConversationWorkRun(runId, status, endedAt = this.nowIso()) {
345914
+ finishConversationWorkRun(runId, status, endedAt = this.nowIso(), errorMessage = "") {
345689
345915
  const run = this.workRuns.find((item) => item.runId === String(runId || ""));
345690
345916
  if (!run) return false;
345691
345917
  this.syncAgentRunTerminal(run.runId, status, endedAt);
@@ -345729,7 +345955,7 @@ ${String(event.toolArgs || "")}`;
345729
345955
  this.enforceGoalTerminalInvariant(status, goalAudit);
345730
345956
  this.emitWorkEvent({
345731
345957
  type: status === "completed" ? "done" : status === "error" ? "error" : "status",
345732
- content: status === "force_interrupted" ? "Force interrupted." : status === "interrupted" ? "Interrupted." : "Response complete.",
345958
+ content: status === "error" ? String(errorMessage || "").trim() || "Agent run failed." : status === "force_interrupted" ? "Force interrupted." : status === "interrupted" ? "Interrupted." : "Response complete.",
345733
345959
  status,
345734
345960
  runId: run.runId,
345735
345961
  conversationId: run.target.conversationId,
@@ -345874,6 +346100,7 @@ ${String(event.toolArgs || "")}`;
345874
346100
  this.activeAgentKernelRuntime = runtime;
345875
346101
  this.awaitingAgentKernelRuntime = false;
345876
346102
  if (!runtime) return;
346103
+ if (this.activeProcessAbortController?.signal.aborted) runtime.abort?.();
345877
346104
  const queued = this.pendingAgentKernelQueue.splice(0);
345878
346105
  for (const item of queued) {
345879
346106
  const accepted = this.forwardAgentKernelQueueMessage(item.content, item.queueMode, item.clientMessageId, item.runId, item.images, item.hiddenUserInput);
@@ -347035,6 +347262,25 @@ Review this persisted peer result and summarize or continue the parent task as n
347035
347262
  this.loadWorkspaceConversationState();
347036
347263
  return selected;
347037
347264
  }
347265
+ refreshWorkspaceRegistryFromStorage() {
347266
+ const before = JSON.stringify({
347267
+ internal: this.workspace.internal,
347268
+ external: this.workspace.external,
347269
+ current: this.workspace.current
347270
+ });
347271
+ const selected = this.workspace.reloadFromStorage();
347272
+ const after = JSON.stringify({
347273
+ internal: this.workspace.internal,
347274
+ external: this.workspace.external,
347275
+ current: this.workspace.current
347276
+ });
347277
+ if (before === after) return selected;
347278
+ if (selected) this.config.loadWorkspaceConfig(selected.path);
347279
+ else this.config.clearWorkspaceOverrides();
347280
+ this.workspaceConversations.clear();
347281
+ this.loadWorkspaceConversationState();
347282
+ return selected;
347283
+ }
347038
347284
  setConversation(id) {
347039
347285
  const clean = this.safeConversationId(id || "default");
347040
347286
  if (this.workspaceConversationKey() === this.loadedWorkspaceConversationKey) {
@@ -348028,28 +348274,25 @@ ${summary}`, segment, "local-summarize", true);
348028
348274
  this.saveWorkspaceConversationState(true);
348029
348275
  return { text, hiddenUserInput: true, goalContinuation: true };
348030
348276
  }
348031
- writeSessionArchive(messages, mode, model) {
348277
+ buildSessionArchive(messages, mode, model, archiveDir) {
348032
348278
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").replace("T", "_").replace("Z", "");
348033
- const archiveDir = this.archiveDir();
348034
- fs25.mkdirSync(archiveDir, { recursive: true });
348035
- const filename = `session_${stamp}.md`;
348036
- const outPath = path28.join(archiveDir, filename);
348037
- let md = `# Newmark Session \u2014 ${stamp}
348279
+ const filename = `session_${stamp}_${crypto14.randomUUID().slice(0, 8)}.md`;
348280
+ let markdown = `# Newmark Session \u2014 ${stamp}
348038
348281
 
348039
348282
  `;
348040
- md += `**Mode**: ${mode}
348283
+ markdown += `**Mode**: ${mode}
348041
348284
  **Model**: ${model}
348042
348285
  `;
348043
- md += `**Messages**: ${messages.length}
348286
+ markdown += `**Messages**: ${messages.length}
348044
348287
 
348045
348288
  ---
348046
348289
 
348047
348290
  `;
348048
- if (this.goal) md += `**Goal**: ${this.goal.objective}
348291
+ if (this.goal) markdown += `**Goal**: ${this.goal.objective}
348049
348292
 
348050
348293
  `;
348051
348294
  for (const msg of messages) {
348052
- md += `**[${msg.role}] ${msg.timestamp}**
348295
+ markdown += `**[${msg.role}] ${msg.timestamp}**
348053
348296
 
348054
348297
  ${msg.content}
348055
348298
 
@@ -348058,13 +348301,35 @@ ${msg.content}
348058
348301
  const archived = archiveConversationImageAttachment(this.rootPath, archiveDir, attachment);
348059
348302
  if (!archived) continue;
348060
348303
  const alt = archived.name.replace(/[\]\r\n]/g, " ").trim() || "Submitted image";
348061
- md += `![${alt}](${archived.relativePath})
348304
+ markdown += `![${alt}](${archived.relativePath})
348062
348305
 
348063
348306
  `;
348064
348307
  }
348065
348308
  }
348066
- fs25.writeFileSync(outPath, md, "utf-8");
348067
- return filename;
348309
+ return { filename, markdown };
348310
+ }
348311
+ writeSessionArchive(messages, mode, model) {
348312
+ const archiveDir = this.archiveDir();
348313
+ fs25.mkdirSync(archiveDir, { recursive: true });
348314
+ const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
348315
+ fs25.writeFileSync(path28.join(archiveDir, archive.filename), archive.markdown, "utf-8");
348316
+ return archive.filename;
348317
+ }
348318
+ async writeSessionArchiveAsync(messages, mode, model, archiveDir = this.archiveDir()) {
348319
+ const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
348320
+ await fs25.promises.mkdir(archiveDir, { recursive: true });
348321
+ const outPath = path28.join(archiveDir, archive.filename);
348322
+ const tempPath = `${outPath}.${process.pid}.${crypto14.randomUUID()}.tmp`;
348323
+ try {
348324
+ await fs25.promises.writeFile(tempPath, archive.markdown, "utf-8");
348325
+ await fs25.promises.rename(tempPath, outPath);
348326
+ } finally {
348327
+ try {
348328
+ await fs25.promises.unlink(tempPath);
348329
+ } catch {
348330
+ }
348331
+ }
348332
+ return archive.filename;
348068
348333
  }
348069
348334
  archiveSession() {
348070
348335
  return this.writeSessionArchive(this.chatMessages, this.modeName(), this.model);
@@ -348132,6 +348397,93 @@ ${msg.content}
348132
348397
  }
348133
348398
  return filename;
348134
348399
  }
348400
+ /**
348401
+ * Non-blocking archive writer used by the desktop IPC path. The conversation
348402
+ * state merge remains synchronous and lock-protected, but the potentially
348403
+ * large markdown payload and manifest use promise-based filesystem I/O so
348404
+ * independent workspaces can archive in parallel without freezing Electron.
348405
+ */
348406
+ async archiveConversationAsync(conversationId) {
348407
+ return await this.archiveConversationAsyncUnlocked(conversationId);
348408
+ }
348409
+ async archiveConversationAsyncUnlocked(conversationId) {
348410
+ const ws = this.workspace.current;
348411
+ if (!ws) return null;
348412
+ const clean = this.safeConversationId(conversationId || "default");
348413
+ const stateKey2 = this.workspaceConversationStateKey(clean);
348414
+ if (!stateKey2) return null;
348415
+ const memoryKey = `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
348416
+ const archiveDir = path28.join(ws.path, "archive");
348417
+ const workspacePrefix = this.workspaceConversationPrefix() || "";
348418
+ const archiveMode = this.modeName();
348419
+ const archiveModel = this.model;
348420
+ const cachedStored = this.readStoredConversationState(ws);
348421
+ const stored = JSON.parse(JSON.stringify(cachedStored || {}));
348422
+ const persisted = stored.conversations?.[stateKey2];
348423
+ if (persisted) this.normalizeConversationTree(persisted);
348424
+ const memory = this.workspaceConversations.get(memoryKey);
348425
+ const persistedMessagesAvailable = persisted?.chatMessages !== void 0;
348426
+ const sourceMessages = persisted?.chatMessages ?? memory?.chatMessages ?? [];
348427
+ const sourceHistory = persistedMessagesAvailable ? persisted?.history ?? [] : memory?.history ?? persisted?.history ?? [];
348428
+ const messages = this.normalizeConversationChatMessages(sourceMessages, sourceHistory);
348429
+ const filename = await this.writeSessionArchiveAsync(messages, archiveMode, archiveModel, archiveDir);
348430
+ const archiveEntry = persisted ? JSON.parse(JSON.stringify(persisted)) : {
348431
+ title: this.titleFromMessages(messages, clean),
348432
+ chatMessages: messages,
348433
+ history: sourceHistory,
348434
+ plan: memory?.plan,
348435
+ linkedPlan: memory?.linkedPlan,
348436
+ subagentState: memory?.subagentState,
348437
+ workRuns: memory?.workRuns,
348438
+ continuations: memory?.continuations,
348439
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
348440
+ };
348441
+ const manifest = {
348442
+ version: 2,
348443
+ kind: "newmark-conversation-archive",
348444
+ archivedAt: (/* @__PURE__ */ new Date()).toISOString(),
348445
+ conversationId: clean,
348446
+ workspaceId: ws.id,
348447
+ workspaceName: ws.name,
348448
+ workspacePath: ws.path,
348449
+ workspaceInternal: ws.isInternal,
348450
+ statePrefix: workspacePrefix,
348451
+ entry: this.conversationEntryForDisk(archiveEntry)
348452
+ };
348453
+ const manifestPath = this.archiveManifestPath(path28.join(archiveDir, filename));
348454
+ const manifestTempPath = `${manifestPath}.${process.pid}.${crypto14.randomUUID()}.tmp`;
348455
+ try {
348456
+ await fs25.promises.writeFile(manifestTempPath, JSON.stringify(manifest, null, 2), "utf-8");
348457
+ await fs25.promises.rename(manifestTempPath, manifestPath);
348458
+ } finally {
348459
+ try {
348460
+ await fs25.promises.unlink(manifestTempPath);
348461
+ } catch {
348462
+ }
348463
+ }
348464
+ this.finalizeAsyncConversationArchive(clean, stateKey2, memoryKey, ws);
348465
+ return filename;
348466
+ }
348467
+ finalizeAsyncConversationArchive(clean, stateKey2, memoryKey, ws) {
348468
+ let nextActiveId = "";
348469
+ this.mutateStoredConversationState(ws, (latest) => {
348470
+ latest.conversations = latest.conversations || {};
348471
+ delete latest.conversations[stateKey2];
348472
+ const prefix = stateKey2.slice(0, Math.max(0, stateKey2.length - clean.length - 1)) + "-";
348473
+ const remaining = Object.keys(latest.conversations).filter((key3) => !prefix || key3.startsWith(prefix)).map((key3) => key3.slice(prefix.length)).filter(Boolean);
348474
+ const currentActiveId = this.safeConversationId(latest.activeConversationId || this.activeConversationId || "default");
348475
+ if (clean === currentActiveId) latest.activeConversationId = remaining[0] || "default";
348476
+ nextActiveId = latest.activeConversationId || remaining[0] || "default";
348477
+ return latest;
348478
+ });
348479
+ this.workspaceConversations.delete(memoryKey);
348480
+ const duplicateMemoryKey = `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
348481
+ this.workspaceConversations.delete(duplicateMemoryKey);
348482
+ if (clean === this.safeConversationId(this.activeConversationId || "default")) {
348483
+ this.activeConversationId = nextActiveId || "default";
348484
+ this.loadWorkspaceConversationState();
348485
+ }
348486
+ }
348135
348487
  listStoredConversationIds(stored) {
348136
348488
  const prefix = `${this.workspaceConversationPrefix() || ""}-`;
348137
348489
  return Object.keys(stored.conversations || {}).filter((key3) => !prefix || key3.startsWith(prefix)).map((key3) => key3.slice(prefix.length)).filter(Boolean);
@@ -348657,9 +349009,9 @@ ${msg.content}
348657
349009
  const provider = this.config.findProvider(providerId);
348658
349010
  return all.filter((m2) => m2.provider_id === (provider?.id || providerId));
348659
349011
  }
348660
- async validateModels(selectedNames) {
349012
+ async validateModels(selectedNames, options = {}) {
348661
349013
  if (this.modelValidationPromise) return this.modelValidationPromise;
348662
- const validation = this.runModelValidation(selectedNames);
349014
+ const validation = this.runModelValidation(selectedNames, options.persist !== false);
348663
349015
  this.modelValidationPromise = validation;
348664
349016
  try {
348665
349017
  return await validation;
@@ -348677,7 +349029,7 @@ ${msg.content}
348677
349029
  recentChecks: this.modelValidationProgress.recentChecks.map((item) => ({ ...item }))
348678
349030
  };
348679
349031
  }
348680
- async runModelValidation(selectedNames) {
349032
+ async runModelValidation(selectedNames, persist = true) {
348681
349033
  const selectedModels = this.config.modelsForSelections(selectedNames);
348682
349034
  if (!selectedModels.length) {
348683
349035
  this.modelValidationProgress = {
@@ -348695,7 +349047,7 @@ ${msg.content}
348695
349047
  }
348696
349048
  const results = [];
348697
349049
  const catalogByProvider = /* @__PURE__ */ new Map();
348698
- const cache = new FileModelValidationCache(this.rootPath);
349050
+ const cache = new FileModelValidationCache(this.rootPath, { readOnly: !persist });
348699
349051
  const checksPerModel = 11;
348700
349052
  let currentModel = "";
348701
349053
  let currentModelChecks = 0;
@@ -348827,7 +349179,7 @@ ${msg.content}
348827
349179
  completedModels: this.modelValidationProgress.completedModels + 1
348828
349180
  };
348829
349181
  }
348830
- this.config.save();
349182
+ if (persist) this.config.save();
348831
349183
  this.modelValidationProgress = {
348832
349184
  ...this.modelValidationProgress,
348833
349185
  running: false,
@@ -349079,7 +349431,8 @@ ${String(input.content || "").slice(0, 18e3)}`;
349079
349431
  const text = typeof input === "string" ? input : String(input.text || "");
349080
349432
  const inputEnvelope = typeof input === "string" ? null : input;
349081
349433
  const hiddenUserInput = inputEnvelope?.hiddenUserInput === true;
349082
- this.ensureUsableModelSelection();
349434
+ const explicitFixedModel = this.model !== "" && this.model !== "auto";
349435
+ if (!explicitFixedModel) this.ensureUsableModelSelection();
349083
349436
  const clientMessageId = String(inputEnvelope?.clientMessageId || "").trim();
349084
349437
  const inputRunId = String(inputEnvelope?.runId || this.activeWorkRunId || "").trim();
349085
349438
  const rawImages = typeof input === "string" ? [] : Array.isArray(input.images) ? input.images : [];
@@ -349167,7 +349520,13 @@ ${String(input.content || "").slice(0, 18e3)}`;
349167
349520
  await this.evaluateAndSwitch(displayText, inputEnvelope?.routePolicy);
349168
349521
  }
349169
349522
  if (this.model && this.modelIsUnavailable(this.model)) {
349523
+ const requestedModel = this.model;
349170
349524
  this.switchToFallbackModel();
349525
+ if (this.modelIsUnavailable(this.model)) {
349526
+ const message = `[Error] Model '${requestedModel || "unknown"}' is unavailable or not configured. Select a configured model or enable a valid provider before sending.`;
349527
+ this.status = "error";
349528
+ throw new Error(message);
349529
+ }
349171
349530
  }
349172
349531
  if (this.engine === "opencode") {
349173
349532
  if (images.length) return [{ type: "text", text: "[Vision unavailable] The OpenCode engine does not accept Newmark image attachments." }];
@@ -350160,11 +350519,11 @@ Falling back to built-in engine.` }];
350160
350519
  }
350161
350520
  }
350162
350521
  async maybeCompress(msgs, provider, signal, compressionModel, force = false) {
350163
- if (signal?.aborted) return;
350164
- if (!this.config.getBool("context", "auto_compress")) return;
350522
+ if (signal?.aborted) return false;
350523
+ if (!this.config.getBool("context", "auto_compress")) return false;
350165
350524
  const total = msgs.reduce((sum, m2) => sum + (typeof m2.content === "string" ? m2.content.length : JSON.stringify(m2.content || "").length), 0);
350166
350525
  const budget = this.compressionBudget(msgs);
350167
- if (budget.estimatedTokens < budget.triggerTokens && !force) return;
350526
+ if (budget.estimatedTokens < budget.triggerTokens && !force) return false;
350168
350527
  if (!force && this.lastCompression && String(msgs[0]?.content || "").includes(this.lastCompression.summary)) {
350169
350528
  const baselineChars = Math.max(0, Number(this.lastCompression.compressedChars || 0));
350170
350529
  const baselineTokens = Math.max(0, Number(this.lastCompression.compressedTokens || 0));
@@ -350172,16 +350531,16 @@ Falling back to built-in engine.` }];
350172
350531
  const tokenGrowth = baselineTokens ? Math.max(0, budget.estimatedTokens - baselineTokens) : Number.POSITIVE_INFINITY;
350173
350532
  const minCharGrowth = Math.max(12e3, Math.floor(baselineChars * 0.25));
350174
350533
  const minTokenGrowth = Math.max(1024, Math.floor(budget.triggerTokens * 0.2));
350175
- if (charGrowth < minCharGrowth && tokenGrowth < minTokenGrowth) return;
350534
+ if (charGrowth < minCharGrowth && tokenGrowth < minTokenGrowth) return false;
350176
350535
  }
350177
350536
  const originalMessageCount = msgs.length;
350178
350537
  const configuredKeepLast = this.config.getNum("context", "keep_recent_messages") || 10;
350179
- if (msgs.length <= 1) return;
350538
+ if (msgs.length <= 1) return false;
350180
350539
  const continuationAnchorTokens = this.estimateContextTokens([this.postCompressionContinuationMessage()]);
350181
350540
  const recentBudget = Math.max(64, budget.targetTokens - budget.summaryTokens - continuationAnchorTokens);
350182
350541
  const recent = this.recentContextSuffix(msgs, configuredKeepLast, recentBudget);
350183
350542
  const recentStart = Math.max(0, msgs.length - recent.length);
350184
- if (recentStart <= 0) return;
350543
+ if (recentStart <= 0) return false;
350185
350544
  const middle = msgs.slice(0, recentStart);
350186
350545
  const currentInstruction = this.latestUserHistoryText(recent);
350187
350546
  const compression = await this.buildCompressionSummary(
@@ -350193,7 +350552,7 @@ Falling back to built-in engine.` }];
350193
350552
  compressionModel || this.activeModelName(),
350194
350553
  currentInstruction
350195
350554
  );
350196
- if (signal?.aborted) return;
350555
+ if (signal?.aborted) return false;
350197
350556
  const compressed = [{
350198
350557
  role: "system",
350199
350558
  content: compression.summary
@@ -350218,6 +350577,7 @@ Falling back to built-in engine.` }];
350218
350577
  };
350219
350578
  this.pushCompressionCacheEntry(compression.summary, middle, compression.model, compression.fallback);
350220
350579
  this.persistCompressedHistory(compression.summary, recent.length, msgs);
350580
+ return true;
350221
350581
  }
350222
350582
  async buildCompressionSummary(middle, totalChars, budget, provider, signal, compressionModel, currentInstruction = "") {
350223
350583
  const workspacePath = this.workspace.current?.path || this.rootPath;
@@ -351075,8 +351435,26 @@ var GoalStateImpl = class {
351075
351435
  return s3;
351076
351436
  }
351077
351437
  checkComplete(response) {
351078
- const lower = response.toLowerCase();
351079
- return lower.includes("goal complete") || lower.includes("objective achieved") || lower.includes("task finished") || lower.includes("all done") || lower.includes("goal accomplished");
351438
+ const lines = String(response || "").replace(/\r\n?/g, "\n").split("\n");
351439
+ const completionMarkers = "(?:goal\\s+complete|objective\\s+achieved|task\\s+finished|all\\s+done|goal\\s+accomplished)";
351440
+ const explicitLinePatterns = [
351441
+ new RegExp(`^\\s*(?:[*#_~\\-]+\\s*)*(?:\\[\\s*)?${completionMarkers}(?:\\s*\\])?(?=\\s|[!.,:;]|$)`, "i"),
351442
+ /^\s*(?:[*#_~\-]+\s*)*(?:i|we)\s+(?:have\s+)?(?:now\s+)?(?:fully\s+)?(?:completed|finished|achieved|accomplished)\s+(?:the\s+)?(?:goal|objective|task)\b/i,
351443
+ /^\s*(?:[*#_~\-]+\s*)*(?:the\s+)?(?:goal|objective|task)\s+(?:is|was)\s+(?:now\s+)?(?:complete|achieved|finished|accomplished)\b/i
351444
+ ];
351445
+ const completionContext = new RegExp(completionMarkers, "i");
351446
+ const deferredBefore = new RegExp(`\\b(?:must|will|should|would|can|could)\\s+(?:still\\s+)?(?:contain(?:s|ed)?|include(?:s|d)?|say|state|use|write|appear)\\b.{0,180}${completionMarkers}`, "i");
351447
+ const negatedBefore = new RegExp(`\\b(?:not|isn't|is not|never|don't|do not|won't|will not)\\b.{0,180}${completionMarkers}`, "i");
351448
+ const pendingBefore = new RegExp(`\\b(?:one\\s+step\\s+remains|still\\s+required|one\\s+more\\s+(?:step|call|turn)|not\\s+(?:the\\s+)?final)\\b.{0,180}${completionMarkers}`, "i");
351449
+ const deferredAfter = new RegExp(`${completionMarkers}.{0,180}\\b(?:not\\s+(?:the\\s+)?final|not\\s+yet|one\\s+more\\s+model\\s+call|next\\s+(?:call|step)|not\\s+done)\\b`, "i");
351450
+ for (let index = 0; index < lines.length; index += 1) {
351451
+ const line = lines[index];
351452
+ if (!explicitLinePatterns.some((pattern) => pattern.test(line))) continue;
351453
+ const context = lines.slice(Math.max(0, index - 2), Math.min(lines.length, index + 3)).join(" ");
351454
+ if (completionContext.test(context) && (deferredBefore.test(context) || negatedBefore.test(context) || pendingBefore.test(context) || deferredAfter.test(context))) continue;
351455
+ return true;
351456
+ }
351457
+ return false;
351080
351458
  }
351081
351459
  };
351082
351460
 
@@ -351273,6 +351651,7 @@ var ConversationKernel = class {
351273
351651
  runtime.runner.recordGuideReceipt(deferred2);
351274
351652
  this.emitQueueUpdate(runtime);
351275
351653
  this.activateAcceptedGoal(runtime, envelope.goalObjective);
351654
+ this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
351276
351655
  return deferred2;
351277
351656
  }
351278
351657
  if (runtime.guideAcceptanceClosedRunId === runtime.runId) {
@@ -351304,6 +351683,7 @@ var ConversationKernel = class {
351304
351683
  createdAt: deferred2.createdAt
351305
351684
  }]);
351306
351685
  this.activateAcceptedGoal(runtime, envelope.goalObjective);
351686
+ this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
351307
351687
  return deferred2;
351308
351688
  }
351309
351689
  const queued = runtime.runner.queueActiveKernelMessage(safeEnvelope.text, "steer", clientMessageId, runtime.runId, safeEnvelope.images);
@@ -351334,6 +351714,7 @@ var ConversationKernel = class {
351334
351714
  createdAt: deferred.createdAt
351335
351715
  }]);
351336
351716
  this.activateAcceptedGoal(runtime, envelope.goalObjective);
351717
+ this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
351337
351718
  return deferred;
351338
351719
  }
351339
351720
  checkpoint(target) {
@@ -351532,7 +351913,12 @@ var ConversationKernel = class {
351532
351913
  if (runtime.runId === runId && runtime.stopRequestedRunId === runId) {
351533
351914
  stopped = true;
351534
351915
  } else {
351535
- runtime.runner.finishConversationWorkRun(runId, "error");
351916
+ runtime.runner.finishConversationWorkRun(
351917
+ runId,
351918
+ "error",
351919
+ void 0,
351920
+ error instanceof Error ? error.message : String(error)
351921
+ );
351536
351922
  throw error;
351537
351923
  }
351538
351924
  } finally {
@@ -351541,6 +351927,8 @@ var ConversationKernel = class {
351541
351927
  if (runtime.stopRequestedRunId === runId) {
351542
351928
  stopped = true;
351543
351929
  this.settleCooperativeStop(runtime, runId);
351930
+ } else if (runtime.pendingNextTurn.length > 0) {
351931
+ this.schedulePendingRuntimeContinuation(runtime, runId);
351544
351932
  }
351545
351933
  }
351546
351934
  }
@@ -351675,7 +352063,8 @@ Review this persisted peer result and summarize or continue the parent task as n
351675
352063
  guideAcceptanceClosedRunId: "",
351676
352064
  guideReceipts: /* @__PURE__ */ new Map(),
351677
352065
  guideEnvelopes: /* @__PURE__ */ new Map(),
351678
- goalContinuationTimer: void 0
352066
+ goalContinuationTimer: void 0,
352067
+ pendingContinuationRunId: void 0
351679
352068
  };
351680
352069
  runner.setGoalContinuationGate(() => {
351681
352070
  this.queueState(runtime);
@@ -351747,6 +352136,28 @@ Review this persisted peer result and summarize or continue the parent task as n
351747
352136
  });
351748
352137
  }, 250);
351749
352138
  }
352139
+ schedulePendingRuntimeContinuation(runtime, runId) {
352140
+ if (runtime.pendingContinuationRunId === runId) return;
352141
+ runtime.pendingContinuationRunId = runId;
352142
+ const active = runtime.activePromise;
352143
+ if (active) {
352144
+ const continueAfterSettlement = () => {
352145
+ runtime.pendingContinuationRunId = void 0;
352146
+ this.schedulePendingRuntimeContinuation(runtime, runId);
352147
+ };
352148
+ void active.then(continueAfterSettlement, continueAfterSettlement);
352149
+ return;
352150
+ }
352151
+ setImmediate(() => {
352152
+ runtime.pendingContinuationRunId = void 0;
352153
+ if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId) return;
352154
+ const next = runtime.pendingNextTurn.shift();
352155
+ if (!next) return;
352156
+ const message = typeof next.message === "string" ? { text: next.message, runId } : { ...next.message, runId: next.message.runId || runId };
352157
+ void this.prompt(message, runtime.target, runtime.options, next.queueMode).catch(() => {
352158
+ });
352159
+ });
352160
+ }
351750
352161
  startGoalDrivenBuild(runtime) {
351751
352162
  if (runtime.goalContinuationTimer) {
351752
352163
  clearTimeout(runtime.goalContinuationTimer);