newmark-agent 0.3.10 → 0.3.11

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.
@@ -0,0 +1,2 @@
1
+ export declare function newmarkHelpText(version: string): string;
2
+ //# sourceMappingURL=cli-help.d.ts.map
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.newmarkHelpText = newmarkHelpText;
4
+ const cli_commands_1 = require("./cli-commands");
5
+ function newmarkHelpText(version) {
6
+ return [
7
+ `Newmark Agent ${version}`,
8
+ '',
9
+ 'Usage:',
10
+ ' Newmark Agent.exe [--gui|--TUI|--cli] [--root <dir>]',
11
+ ' Newmark.exe <command> ...',
12
+ ' Newmark.exe flow <workflow-name> [start-pc] [--input "text"] [--root <dir>]',
13
+ ' Newmark.exe edit <file.txt|.json|.tex|.md>',
14
+ '',
15
+ (0, cli_commands_1.cliCommandUsage)(),
16
+ '',
17
+ 'Flags:',
18
+ ' --help, -h Show this help and exit.',
19
+ ' --version, -v Show the installed version and exit.',
20
+ ].join('\n');
21
+ }
22
+ //# sourceMappingURL=cli-help.js.map
@@ -335769,10 +335769,102 @@ function browserVisualFallback(runtimeKey, observationId) {
335769
335769
  return browserFallbacks.get(key(runtimeKey, observationId)) || null;
335770
335770
  }
335771
335771
 
335772
+ // src/core/computerUseSession.ts
335773
+ var COMPUTER_USE_OCCUPIED_MARKER = "computerUse occupied";
335774
+ var COMPUTER_USE_LOCK_TTL_MS = 10 * 60 * 1e3;
335775
+ var ComputerUseSessionRegistry = class {
335776
+ constructor(ttlMs = COMPUTER_USE_LOCK_TTL_MS) {
335777
+ this.ttlMs = ttlMs;
335778
+ }
335779
+ ttlMs;
335780
+ enabledByRuntime = /* @__PURE__ */ new Map();
335781
+ activeLease = null;
335782
+ authorize(action, scope, dryRun = false) {
335783
+ const normalizedAction = String(action || "").trim().toLowerCase();
335784
+ const runtimeKey = String(scope.runtimeKey || "").trim() || "conversation:default";
335785
+ const now2 = Date.now();
335786
+ this.clearExpired(now2);
335787
+ if (this.activeLease && this.activeLease.runtimeKey !== runtimeKey) {
335788
+ return this.occupiedError(normalizedAction, scope.ownerLabel, this.activeLease.ownerLabel);
335789
+ }
335790
+ if (normalizedAction === "takeover_stop") return null;
335791
+ const enabled = this.enabledByRuntime.get(runtimeKey) !== false;
335792
+ const readOnly = normalizedAction === "observe" || normalizedAction === "app_list" || normalizedAction === "app_observe" || normalizedAction === "wait";
335793
+ if (!enabled && !readOnly && !dryRun && normalizedAction !== "takeover_start") {
335794
+ return JSON.stringify({
335795
+ ok: false,
335796
+ action: normalizedAction,
335797
+ error: `ComputerUse is disabled for ${scope.ownerLabel}. Enable ComputerUse for this conversation before sending desktop operations.`,
335798
+ computer_use_enabled: false,
335799
+ requested_owner: scope.ownerLabel
335800
+ }, null, 2);
335801
+ }
335802
+ if (normalizedAction === "takeover_start") this.enabledByRuntime.set(runtimeKey, true);
335803
+ if (!this.activeLease) {
335804
+ this.activeLease = { ...scope, runtimeKey, updatedAt: now2 };
335805
+ } else {
335806
+ this.activeLease.updatedAt = now2;
335807
+ }
335808
+ return null;
335809
+ }
335810
+ complete(action, scope) {
335811
+ const normalizedAction = String(action || "").trim().toLowerCase();
335812
+ const runtimeKey = String(scope.runtimeKey || "").trim() || "conversation:default";
335813
+ if (normalizedAction === "takeover_stop") {
335814
+ if (!this.activeLease || this.activeLease.runtimeKey === runtimeKey) this.activeLease = null;
335815
+ this.enabledByRuntime.set(runtimeKey, false);
335816
+ return;
335817
+ }
335818
+ if (this.activeLease?.runtimeKey === runtimeKey) this.activeLease.updatedAt = Date.now();
335819
+ }
335820
+ setEnabled(scope, enabled) {
335821
+ const runtimeKey = String(scope.runtimeKey || "").trim() || "conversation:default";
335822
+ this.clearExpired();
335823
+ if (this.activeLease && this.activeLease.runtimeKey !== runtimeKey) {
335824
+ return { ok: false, error: this.occupiedError("toggle", scope.ownerLabel, this.activeLease.ownerLabel), state: this.state(runtimeKey) };
335825
+ }
335826
+ this.enabledByRuntime.set(runtimeKey, enabled !== false);
335827
+ if (enabled === false && this.activeLease?.runtimeKey === runtimeKey) this.activeLease = null;
335828
+ return { ok: true, state: this.state(runtimeKey) };
335829
+ }
335830
+ state(runtimeKey) {
335831
+ const key3 = String(runtimeKey || "").trim() || "conversation:default";
335832
+ this.clearExpired();
335833
+ const lease = this.activeLease;
335834
+ return {
335835
+ runtimeKey: key3,
335836
+ enabled: this.enabledByRuntime.get(key3) !== false,
335837
+ occupied: !!lease,
335838
+ ...lease ? { ownerLabel: lease.ownerLabel, updatedAt: lease.updatedAt } : {}
335839
+ };
335840
+ }
335841
+ cancelTarget(runtimeKey) {
335842
+ const key3 = String(runtimeKey || "").trim();
335843
+ if (!key3) return false;
335844
+ const hadActiveLease = this.activeLease?.runtimeKey === key3;
335845
+ if (hadActiveLease) this.activeLease = null;
335846
+ this.enabledByRuntime.set(key3, false);
335847
+ return hadActiveLease;
335848
+ }
335849
+ clearExpired(now2 = Date.now()) {
335850
+ if (this.activeLease && now2 - this.activeLease.updatedAt > this.ttlMs) {
335851
+ this.activeLease = null;
335852
+ }
335853
+ }
335854
+ occupiedError(action, requestedOwner, activeOwner) {
335855
+ return JSON.stringify({
335856
+ ok: false,
335857
+ action,
335858
+ 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.`,
335859
+ lock_owner: activeOwner,
335860
+ requested_owner: requestedOwner
335861
+ }, null, 2);
335862
+ }
335863
+ };
335864
+ var defaultComputerUseSessionRegistry = new ComputerUseSessionRegistry();
335865
+
335772
335866
  // src/tools/index.ts
335773
335867
  var globSync = require_index_min().sync;
335774
- var computerUseLock = null;
335775
- var COMPUTER_USE_LOCK_TTL_MS = 10 * 60 * 1e3;
335776
335868
  function normalizeComputerUseAction(action) {
335777
335869
  return String(action || "").trim().toLowerCase();
335778
335870
  }
@@ -335857,49 +335949,24 @@ async function abortableToolDelay(durationMs, signal) {
335857
335949
  if (signal?.aborted) abort();
335858
335950
  });
335859
335951
  }
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
335952
+ function computerUseSessionScope(context, wsPath, owner) {
335953
+ return {
335954
+ runtimeKey: browserUseScope(context, wsPath).runtimeKey,
335955
+ ownerLabel: owner,
335956
+ workspacePath: path15.resolve(wsPath || process.cwd())
335885
335957
  };
335886
- return null;
335887
335958
  }
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;
335959
+ function acquireComputerUseLock(action, owner, wsPath, context = {}, dryRun = false) {
335960
+ return defaultComputerUseSessionRegistry.authorize(action, computerUseSessionScope(context, wsPath, owner), dryRun);
335895
335961
  }
335896
- function assertComputerUseLockOwner(action, owner) {
335897
- clearStaleComputerUseLock();
335898
- if (computerUseLock && computerUseLock.owner !== owner) {
335899
- return computerUseLockError(action, owner);
335900
- }
335962
+ function releaseComputerUseLock(action, owner, context = {}, wsPath = context.workspacePath || "") {
335963
+ const scope = computerUseSessionScope(context, wsPath, owner);
335964
+ defaultComputerUseSessionRegistry.complete(action, scope);
335901
335965
  return null;
335902
335966
  }
335967
+ function assertComputerUseLockOwner(action, owner, context = {}, wsPath = context.workspacePath || "") {
335968
+ return defaultComputerUseSessionRegistry.authorize(action, computerUseSessionScope(context, wsPath, owner));
335969
+ }
335903
335970
  var ToolExecutor = class {
335904
335971
  constructor(root2, config, ssh, workspace) {
335905
335972
  this.config = config;
@@ -336450,7 +336517,7 @@ var ToolExecutor = class {
336450
336517
  case "computer_use": {
336451
336518
  const action = normalizeComputerUseAction(g2("action"));
336452
336519
  const owner = `${computerUseOwner(context, wsPath)}:${String(context.actorId || "root")}`;
336453
- const lockGuard = action === "takeover_stop" ? assertComputerUseLockOwner(action, owner) : acquireComputerUseLock(action, owner, wsPath);
336520
+ const lockGuard = action === "takeover_stop" ? assertComputerUseLockOwner(action, owner, context, wsPath) : acquireComputerUseLock(action, owner, wsPath, context, args.dry_run === true);
336454
336521
  if (lockGuard) return lockGuard;
336455
336522
  if (process.env.NEWMARK_WSL_DISTRO) {
336456
336523
  try {
@@ -336464,7 +336531,7 @@ var ToolExecutor = class {
336464
336531
  }, 12e4, context.signal);
336465
336532
  return typeof result === "string" ? result : JSON.stringify(result);
336466
336533
  } finally {
336467
- if (action === "takeover_stop") releaseComputerUseLock(action, owner);
336534
+ if (action === "takeover_stop") releaseComputerUseLock(action, owner, context, wsPath);
336468
336535
  }
336469
336536
  }
336470
336537
  if (process.env.NEWMARK_ISOLATED_RUNTIME === "1") {
@@ -336481,7 +336548,7 @@ var ToolExecutor = class {
336481
336548
  const result = await requestUtilityHostTool("computer_use", args, trustedComputerUseContext, 12e4, context.signal);
336482
336549
  return typeof result === "string" ? result : JSON.stringify(result);
336483
336550
  } finally {
336484
- if (action === "takeover_stop") releaseComputerUseLock(action, owner);
336551
+ if (action === "takeover_stop") releaseComputerUseLock(action, owner, context, wsPath);
336485
336552
  }
336486
336553
  }
336487
336554
  const output = await runComputerUse({
@@ -336522,7 +336589,7 @@ var ToolExecutor = class {
336522
336589
  durationMs: Number(step.duration_ms || 0)
336523
336590
  })) : void 0
336524
336591
  });
336525
- if (action === "takeover_stop") releaseComputerUseLock(action, owner);
336592
+ if (action === "takeover_stop") releaseComputerUseLock(action, owner, context, wsPath);
336526
336593
  return output;
336527
336594
  }
336528
336595
  case "terminal_takeover": {
@@ -337003,9 +337070,17 @@ ${snippet ? clean(snippet[1]) : ""}`.trim());
337003
337070
  }
337004
337071
  }
337005
337072
  async browserRun(request, signal, context = {}, workspacePath = this.root) {
337073
+ const scope = browserUseScope(context, workspacePath);
337074
+ const scopedRequest = {
337075
+ ...request,
337076
+ target: {
337077
+ workspaceId: context.workspaceId || terminalTakeoverWorkspaceId(workspacePath),
337078
+ conversationId: context.conversationId || "default",
337079
+ runtimeKey: scope.runtimeKey
337080
+ }
337081
+ };
337006
337082
  if (process.env.NEWMARK_WSL_DISTRO) {
337007
- const scope = browserUseScope(context, workspacePath);
337008
- const result2 = await requestWindowsHostTool("browser_control", request, {
337083
+ const result2 = await requestWindowsHostTool("browser_control", scopedRequest, {
337009
337084
  conversationId: context.conversationId || process.env.NEWMARK_CONVERSATION_ID || "default",
337010
337085
  workspaceId: process.env.NEWMARK_WORKSPACE_ID || context.workspaceId || terminalTakeoverWorkspaceId(workspacePath),
337011
337086
  actorId: context.actorId || ROOT_TERMINAL_ACTOR_ID,
@@ -337015,10 +337090,10 @@ ${snippet ? clean(snippet[1]) : ""}`.trim());
337015
337090
  return this.formatBrowserResult(result2);
337016
337091
  }
337017
337092
  if (process.env.NEWMARK_ISOLATED_RUNTIME === "1") {
337018
- const result2 = await requestUtilityHostTool("browser_control", request, void 0, 3e4, signal);
337093
+ const result2 = await requestUtilityHostTool("browser_control", scopedRequest, void 0, 3e4, signal);
337019
337094
  return this.formatBrowserResult(result2);
337020
337095
  }
337021
- const result = await BrowserControl.run(request, signal);
337096
+ const result = await BrowserControl.run(scopedRequest, signal);
337022
337097
  return this.formatBrowserResult(result);
337023
337098
  }
337024
337099
  formatBrowserResult(result) {
@@ -339896,6 +339971,10 @@ var ProviderRunError = class extends Error {
339896
339971
  function kernelTurnFailed(agent, turn) {
339897
339972
  return turn.stopReason === "error" || agent.isLlmErrorText(turn.text);
339898
339973
  }
339974
+ function providerTurnIsEmpty(turn) {
339975
+ return /provider returned an empty response/i.test(`${turn.errorMessage}
339976
+ ${turn.text}`);
339977
+ }
339899
339978
  function removeTrailingFailedAssistant(agent, messages) {
339900
339979
  const last = messages[messages.length - 1];
339901
339980
  if (last?.role !== "assistant") return;
@@ -340007,10 +340086,13 @@ async function runAgentKernel(agent) {
340007
340086
  }
340008
340087
  await kernel2.prompt(promptMessages);
340009
340088
  const assistant = lastAssistant;
340089
+ const text = assistant ? KernelMessageText(assistant) : "";
340090
+ const hasToolCall = !!assistant?.content?.some((content) => content.type === "toolCall");
340091
+ const emptyResponse = !assistant || !text.trim() && !hasToolCall && String(assistant?.stopReason || "") !== "aborted";
340010
340092
  return {
340011
- text: assistant ? KernelMessageText(assistant) : "",
340093
+ text: emptyResponse ? "[Error] Provider returned an empty response." : text,
340012
340094
  stopReason: String(assistant?.stopReason || ""),
340013
- errorMessage: String(assistant?.errorMessage || "")
340095
+ errorMessage: String(assistant?.errorMessage || (emptyResponse ? "Provider returned an empty response." : ""))
340014
340096
  };
340015
340097
  } finally {
340016
340098
  unsubscribe();
@@ -340045,6 +340127,16 @@ async function runAgentKernel(agent) {
340045
340127
  if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some((t3) => t3.text?.includes("[Model fallback]"))) {
340046
340128
  tokens.unshift({ type: "text", text: `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.` });
340047
340129
  }
340130
+ let emptyResponseRetries = 0;
340131
+ while (providerTurnIsEmpty(lastTurn) && emptyResponseRetries < 2) {
340132
+ removeTrailingFailedAssistant(agent, kernel2.state.messages);
340133
+ emptyResponseRetries += 1;
340134
+ const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${emptyResponseRetries}/2).`;
340135
+ tokens.push({ type: "text", text: notice });
340136
+ agent.recordWorkStatus(notice);
340137
+ await agent.waitForPlannedRouteRetry();
340138
+ lastTurn = await runWithCompressionResume([], false);
340139
+ }
340048
340140
  let routeRetries = 0;
340049
340141
  while (kernelTurnFailed(agent, lastTurn) && routeRetries < 2) {
340050
340142
  const previous = agent.switchToFallbackModel(lastTurn.errorMessage || lastTurn.text);
@@ -351075,8 +351167,26 @@ var GoalStateImpl = class {
351075
351167
  return s3;
351076
351168
  }
351077
351169
  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");
351170
+ const lines = String(response || "").replace(/\r\n?/g, "\n").split("\n");
351171
+ const completionMarkers = "(?:goal\\s+complete|objective\\s+achieved|task\\s+finished|all\\s+done|goal\\s+accomplished)";
351172
+ const explicitLinePatterns = [
351173
+ new RegExp(`^\\s*(?:[*#_~\\-]+\\s*)*(?:\\[\\s*)?${completionMarkers}(?:\\s*\\])?(?=\\s|[!.,:;]|$)`, "i"),
351174
+ /^\s*(?:[*#_~\-]+\s*)*(?:i|we)\s+(?:have\s+)?(?:now\s+)?(?:fully\s+)?(?:completed|finished|achieved|accomplished)\s+(?:the\s+)?(?:goal|objective|task)\b/i,
351175
+ /^\s*(?:[*#_~\-]+\s*)*(?:the\s+)?(?:goal|objective|task)\s+(?:is|was)\s+(?:now\s+)?(?:complete|achieved|finished|accomplished)\b/i
351176
+ ];
351177
+ const completionContext = new RegExp(completionMarkers, "i");
351178
+ 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");
351179
+ const negatedBefore = new RegExp(`\\b(?:not|isn't|is not|never|don't|do not|won't|will not)\\b.{0,180}${completionMarkers}`, "i");
351180
+ 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");
351181
+ 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");
351182
+ for (let index = 0; index < lines.length; index += 1) {
351183
+ const line = lines[index];
351184
+ if (!explicitLinePatterns.some((pattern) => pattern.test(line))) continue;
351185
+ const context = lines.slice(Math.max(0, index - 2), Math.min(lines.length, index + 3)).join(" ");
351186
+ if (completionContext.test(context) && (deferredBefore.test(context) || negatedBefore.test(context) || pendingBefore.test(context) || deferredAfter.test(context))) continue;
351187
+ return true;
351188
+ }
351189
+ return false;
351080
351190
  }
351081
351191
  };
351082
351192
 
@@ -351273,6 +351383,7 @@ var ConversationKernel = class {
351273
351383
  runtime.runner.recordGuideReceipt(deferred2);
351274
351384
  this.emitQueueUpdate(runtime);
351275
351385
  this.activateAcceptedGoal(runtime, envelope.goalObjective);
351386
+ this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
351276
351387
  return deferred2;
351277
351388
  }
351278
351389
  if (runtime.guideAcceptanceClosedRunId === runtime.runId) {
@@ -351304,6 +351415,7 @@ var ConversationKernel = class {
351304
351415
  createdAt: deferred2.createdAt
351305
351416
  }]);
351306
351417
  this.activateAcceptedGoal(runtime, envelope.goalObjective);
351418
+ this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
351307
351419
  return deferred2;
351308
351420
  }
351309
351421
  const queued = runtime.runner.queueActiveKernelMessage(safeEnvelope.text, "steer", clientMessageId, runtime.runId, safeEnvelope.images);
@@ -351334,6 +351446,7 @@ var ConversationKernel = class {
351334
351446
  createdAt: deferred.createdAt
351335
351447
  }]);
351336
351448
  this.activateAcceptedGoal(runtime, envelope.goalObjective);
351449
+ this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
351337
351450
  return deferred;
351338
351451
  }
351339
351452
  checkpoint(target) {
@@ -351541,6 +351654,8 @@ var ConversationKernel = class {
351541
351654
  if (runtime.stopRequestedRunId === runId) {
351542
351655
  stopped = true;
351543
351656
  this.settleCooperativeStop(runtime, runId);
351657
+ } else if (runtime.pendingNextTurn.length > 0) {
351658
+ this.schedulePendingRuntimeContinuation(runtime, runId);
351544
351659
  }
351545
351660
  }
351546
351661
  }
@@ -351675,7 +351790,8 @@ Review this persisted peer result and summarize or continue the parent task as n
351675
351790
  guideAcceptanceClosedRunId: "",
351676
351791
  guideReceipts: /* @__PURE__ */ new Map(),
351677
351792
  guideEnvelopes: /* @__PURE__ */ new Map(),
351678
- goalContinuationTimer: void 0
351793
+ goalContinuationTimer: void 0,
351794
+ pendingContinuationRunId: void 0
351679
351795
  };
351680
351796
  runner.setGoalContinuationGate(() => {
351681
351797
  this.queueState(runtime);
@@ -351747,6 +351863,28 @@ Review this persisted peer result and summarize or continue the parent task as n
351747
351863
  });
351748
351864
  }, 250);
351749
351865
  }
351866
+ schedulePendingRuntimeContinuation(runtime, runId) {
351867
+ if (runtime.pendingContinuationRunId === runId) return;
351868
+ runtime.pendingContinuationRunId = runId;
351869
+ const active = runtime.activePromise;
351870
+ if (active) {
351871
+ const continueAfterSettlement = () => {
351872
+ runtime.pendingContinuationRunId = void 0;
351873
+ this.schedulePendingRuntimeContinuation(runtime, runId);
351874
+ };
351875
+ void active.then(continueAfterSettlement, continueAfterSettlement);
351876
+ return;
351877
+ }
351878
+ setImmediate(() => {
351879
+ runtime.pendingContinuationRunId = void 0;
351880
+ if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId) return;
351881
+ const next = runtime.pendingNextTurn.shift();
351882
+ if (!next) return;
351883
+ const message = typeof next.message === "string" ? { text: next.message, runId } : { ...next.message, runId: next.message.runId || runId };
351884
+ void this.prompt(message, runtime.target, runtime.options, next.queueMode).catch(() => {
351885
+ });
351886
+ });
351887
+ }
351750
351888
  startGoalDrivenBuild(runtime) {
351751
351889
  if (runtime.goalContinuationTimer) {
351752
351890
  clearTimeout(runtime.goalContinuationTimer);
@@ -8045,10 +8045,28 @@ class GoalStateImpl {
8045
8045
  return s;
8046
8046
  }
8047
8047
  checkComplete(response) {
8048
- const lower = response.toLowerCase();
8049
- return lower.includes('goal complete') || lower.includes('objective achieved')
8050
- || lower.includes('task finished') || lower.includes('all done')
8051
- || lower.includes('goal accomplished');
8048
+ const lines = String(response || '').replace(/\r\n?/g, '\n').split('\n');
8049
+ const completionMarkers = '(?:goal\\s+complete|objective\\s+achieved|task\\s+finished|all\\s+done|goal\\s+accomplished)';
8050
+ const explicitLinePatterns = [
8051
+ new RegExp(`^\\s*(?:[*#_~\\-]+\\s*)*(?:\\[\\s*)?${completionMarkers}(?:\\s*\\])?(?=\\s|[!.,:;]|$)`, 'i'),
8052
+ /^\s*(?:[*#_~\-]+\s*)*(?:i|we)\s+(?:have\s+)?(?:now\s+)?(?:fully\s+)?(?:completed|finished|achieved|accomplished)\s+(?:the\s+)?(?:goal|objective|task)\b/i,
8053
+ /^\s*(?:[*#_~\-]+\s*)*(?:the\s+)?(?:goal|objective|task)\s+(?:is|was)\s+(?:now\s+)?(?:complete|achieved|finished|accomplished)\b/i,
8054
+ ];
8055
+ const completionContext = new RegExp(completionMarkers, 'i');
8056
+ 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');
8057
+ const negatedBefore = new RegExp(`\\b(?:not|isn't|is not|never|don't|do not|won't|will not)\\b.{0,180}${completionMarkers}`, 'i');
8058
+ 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');
8059
+ 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');
8060
+ for (let index = 0; index < lines.length; index += 1) {
8061
+ const line = lines[index];
8062
+ if (!explicitLinePatterns.some(pattern => pattern.test(line)))
8063
+ continue;
8064
+ const context = lines.slice(Math.max(0, index - 2), Math.min(lines.length, index + 3)).join(' ');
8065
+ if (completionContext.test(context) && (deferredBefore.test(context) || negatedBefore.test(context) || pendingBefore.test(context) || deferredAfter.test(context)))
8066
+ continue;
8067
+ return true;
8068
+ }
8069
+ return false;
8052
8070
  }
8053
8071
  }
8054
8072
  //# sourceMappingURL=agent.js.map
@@ -205,6 +205,9 @@ class ProviderRunError extends Error {
205
205
  function kernelTurnFailed(agent, turn) {
206
206
  return turn.stopReason === 'error' || agent.isLlmErrorText(turn.text);
207
207
  }
208
+ function providerTurnIsEmpty(turn) {
209
+ return /provider returned an empty response/i.test(`${turn.errorMessage}\n${turn.text}`);
210
+ }
208
211
  function removeTrailingFailedAssistant(agent, messages) {
209
212
  const last = messages[messages.length - 1];
210
213
  if (last?.role !== 'assistant')
@@ -338,10 +341,14 @@ async function runAgentKernel(agent) {
338
341
  }
339
342
  await kernel.prompt(promptMessages);
340
343
  const assistant = lastAssistant;
344
+ const text = assistant ? KernelMessageText(assistant) : '';
345
+ const hasToolCall = !!assistant?.content?.some(content => content.type === 'toolCall');
346
+ const emptyResponse = !assistant
347
+ || (!text.trim() && !hasToolCall && String(assistant?.stopReason || '') !== 'aborted');
341
348
  return {
342
- text: assistant ? KernelMessageText(assistant) : '',
349
+ text: emptyResponse ? '[Error] Provider returned an empty response.' : text,
343
350
  stopReason: String(assistant?.stopReason || ''),
344
- errorMessage: String(assistant?.errorMessage || ''),
351
+ errorMessage: String(assistant?.errorMessage || (emptyResponse ? 'Provider returned an empty response.' : '')),
345
352
  };
346
353
  }
347
354
  finally {
@@ -380,6 +387,16 @@ async function runAgentKernel(agent) {
380
387
  if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some(t => t.text?.includes('[Model fallback]'))) {
381
388
  tokens.unshift({ type: 'text', text: `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.` });
382
389
  }
390
+ let emptyResponseRetries = 0;
391
+ while (providerTurnIsEmpty(lastTurn) && emptyResponseRetries < 2) {
392
+ removeTrailingFailedAssistant(agent, kernel.state.messages);
393
+ emptyResponseRetries += 1;
394
+ const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${emptyResponseRetries}/2).`;
395
+ tokens.push({ type: 'text', text: notice });
396
+ agent.recordWorkStatus(notice);
397
+ await agent.waitForPlannedRouteRetry();
398
+ lastTurn = await runWithCompressionResume([], false);
399
+ }
383
400
  let routeRetries = 0;
384
401
  while (kernelTurnFailed(agent, lastTurn) && routeRetries < 2) {
385
402
  const previous = agent.switchToFallbackModel(lastTurn.errorMessage || lastTurn.text);
@@ -1,6 +1,14 @@
1
1
  export type BrowserControlAction = 'open' | 'snapshot' | 'click' | 'type' | 'eval' | 'back' | 'forward' | 'reload' | 'cdp' | 'use';
2
+ /** Renderer/host routing identity. Browser controls must never fall back to the
3
+ * currently focused conversation when a caller has a concrete target. */
4
+ export interface BrowserControlTarget {
5
+ workspaceId?: string;
6
+ conversationId?: string;
7
+ runtimeKey?: string;
8
+ }
2
9
  export interface BrowserControlRequest {
3
10
  action: BrowserControlAction;
11
+ target?: BrowserControlTarget;
4
12
  url?: string;
5
13
  selector?: string;
6
14
  text?: string;
@@ -22,6 +22,8 @@ export interface BrowserUseHostPage {
22
22
  }>;
23
23
  evaluateFixed<T>(script: string, signal?: AbortSignal): Promise<T>;
24
24
  clickAt(x: number, y: number, signal?: AbortSignal): Promise<void>;
25
+ /** Optional deterministic DOM click for embedded guests where native input can be dropped while the host tab is settling. */
26
+ clickElement?(token: string, signal?: AbortSignal): Promise<void>;
25
27
  replaceFocusedText(text: string, signal?: AbortSignal): Promise<void>;
26
28
  pressKey(key: string, signal?: AbortSignal): Promise<void>;
27
29
  navigate(url: string, signal?: AbortSignal): Promise<void>;
@@ -46,6 +48,7 @@ export type BrowserUseHostPageResolver = (scope: BrowserUseScope) => Promise<Bro
46
48
  export declare function browserUseObservationScript(maxChars: number, maxRefs: number): string;
47
49
  export declare function browserUseProbeScript(token: string, scrollIntoView?: boolean): string;
48
50
  export declare function browserUseFocusScript(token: string): string;
51
+ export declare function browserUseClickScript(token: string): string;
49
52
  export declare function browserUseSelectScript(token: string, value: string): string;
50
53
  export declare function browserUseScrollScript(token: string | undefined, deltaX: number, deltaY: number): string;
51
54
  export declare function browserUseExtractScript(token: string | undefined, attribute: string | undefined, maxChars: number): string;
@@ -4,6 +4,7 @@ exports.NativeBrowserUsePageAdapter = void 0;
4
4
  exports.browserUseObservationScript = browserUseObservationScript;
5
5
  exports.browserUseProbeScript = browserUseProbeScript;
6
6
  exports.browserUseFocusScript = browserUseFocusScript;
7
+ exports.browserUseClickScript = browserUseClickScript;
7
8
  exports.browserUseSelectScript = browserUseSelectScript;
8
9
  exports.browserUseScrollScript = browserUseScrollScript;
9
10
  exports.browserUseExtractScript = browserUseExtractScript;
@@ -186,6 +187,17 @@ function browserUseProbeScript(token, scrollIntoView = false) {
186
187
  function browserUseFocusScript(token) {
187
188
  return `(() => { const el = document.querySelector(${json(token)}); if (!el) return false; if (typeof el.scrollIntoView === 'function') el.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'auto' }); el.focus({ preventScroll: true }); return document.activeElement === el || el.contains(document.activeElement); })()`;
188
189
  }
190
+ function browserUseClickScript(token) {
191
+ return `(() => {
192
+ const el = document.querySelector(${json(token)});
193
+ if (!el) return { clicked: false, error: 'ref_not_found' };
194
+ if (typeof el.scrollIntoView === 'function') el.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'auto' });
195
+ if (typeof el.focus === 'function') el.focus({ preventScroll: true });
196
+ if (typeof el.click !== 'function') return { clicked: false, error: 'not_clickable' };
197
+ el.click();
198
+ return { clicked: true, tag: String(el.localName || 'element').toLowerCase() };
199
+ })()`;
200
+ }
189
201
  function browserUseSelectScript(token, value) {
190
202
  return `(() => {
191
203
  const el = document.querySelector(${json(token)});
@@ -299,8 +311,13 @@ class NativeBrowserUsePageAdapter {
299
311
  throw error;
300
312
  }
301
313
  if (request.action === 'click') {
302
- const rect = probe.rect;
303
- await page.clickAt(rect.x + rect.width / 2, rect.y + rect.height / 2, signal);
314
+ if (page.clickElement) {
315
+ await page.clickElement(request.element.token, signal);
316
+ }
317
+ else {
318
+ const rect = probe.rect;
319
+ await page.clickAt(rect.x + rect.width / 2, rect.y + rect.height / 2, signal);
320
+ }
304
321
  await page.waitForReady(signal);
305
322
  return { clicked: true };
306
323
  }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Process-wide Computer-Use session state.
3
+ *
4
+ * The state is keyed by conversation runtime, not by Build/run id. A Build may finish
5
+ * or be interrupted/replaced while the conversation keeps its
6
+ * Computer-Use switch and lease. Only an explicit stop/toggle-off or runtime
7
+ * teardown releases it.
8
+ */
9
+ export interface ComputerUseSessionScope {
10
+ runtimeKey: string;
11
+ ownerLabel: string;
12
+ workspacePath?: string;
13
+ }
14
+ export interface ComputerUseSessionState {
15
+ runtimeKey: string;
16
+ enabled: boolean;
17
+ occupied: boolean;
18
+ ownerLabel?: string;
19
+ updatedAt?: number;
20
+ }
21
+ export declare const COMPUTER_USE_OCCUPIED_MARKER = "computerUse occupied";
22
+ export declare const COMPUTER_USE_LOCK_TTL_MS: number;
23
+ export declare class ComputerUseSessionRegistry {
24
+ private readonly ttlMs;
25
+ private readonly enabledByRuntime;
26
+ private activeLease;
27
+ constructor(ttlMs?: number);
28
+ authorize(action: string, scope: ComputerUseSessionScope, dryRun?: boolean): string | null;
29
+ complete(action: string, scope: ComputerUseSessionScope): void;
30
+ setEnabled(scope: ComputerUseSessionScope, enabled: boolean): {
31
+ ok: true;
32
+ state: ComputerUseSessionState;
33
+ } | {
34
+ ok: false;
35
+ error: string;
36
+ state: ComputerUseSessionState;
37
+ };
38
+ state(runtimeKey: string): ComputerUseSessionState;
39
+ cancelTarget(runtimeKey: string): boolean;
40
+ private clearExpired;
41
+ private occupiedError;
42
+ }
43
+ export declare const defaultComputerUseSessionRegistry: ComputerUseSessionRegistry;
44
+ //# sourceMappingURL=computerUseSession.d.ts.map