newmark-agent 0.4.6 → 0.4.8

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 (43) hide show
  1. package/assets/app-icon-dark.svg +6 -0
  2. package/dist/assets/app-icon-dark.svg +6 -0
  3. package/dist/cli-commands.js +2 -1
  4. package/dist/cli-discovery.js +2 -0
  5. package/dist/conversation-utility-host.bundle.cjs +577 -166
  6. package/dist/conversation-utility-host.js +3 -0
  7. package/dist/core/agent.d.ts +40 -4
  8. package/dist/core/agent.js +251 -49
  9. package/dist/core/agentKernelRunner.d.ts +3 -0
  10. package/dist/core/agentKernelRunner.js +74 -91
  11. package/dist/core/config.js +1 -1
  12. package/dist/core/conversationKernel.d.ts +40 -0
  13. package/dist/core/conversationKernel.js +219 -8
  14. package/dist/core/electronUtilityAgentClient.d.ts +8 -0
  15. package/dist/core/electronUtilityAgentClient.js +5 -1
  16. package/dist/core/electronUtilityRuntimePool.d.ts +15 -0
  17. package/dist/core/electronUtilityRuntimePool.js +11 -0
  18. package/dist/core/installUpdate.js +11 -8
  19. package/dist/core/mobilePairing.d.ts +1 -0
  20. package/dist/core/mobilePairing.js +15 -1
  21. package/dist/core/subagent.d.ts +10 -3
  22. package/dist/core/subagent.js +23 -8
  23. package/dist/core/toolPolicy.js +14 -3
  24. package/dist/core/utilityAgentProtocol.d.ts +29 -1
  25. package/dist/core/utilityHostToolRouter.js +18 -0
  26. package/dist/core/wslAgentClient.d.ts +8 -0
  27. package/dist/core/wslAgentClient.js +5 -1
  28. package/dist/core/wslAgentProtocol.d.ts +18 -1
  29. package/dist/core/wslAgentRuntimePool.d.ts +15 -0
  30. package/dist/core/wslAgentRuntimePool.js +11 -0
  31. package/dist/launcher.js +14 -11
  32. package/dist/main.js +207 -9
  33. package/dist/providers/chat-completions.adapter.js +6 -2
  34. package/dist/providers/responses.adapter.js +1 -0
  35. package/dist/server.d.ts +33 -1
  36. package/dist/server.js +696 -48
  37. package/dist/toolchain/registry-seeder.js +3 -1
  38. package/dist/tools/index.js +57 -6
  39. package/dist/tools/nativeTools.js +2 -1
  40. package/dist/ui/index.html +88 -101
  41. package/dist/wsl-agent-host.bundle.cjs +577 -166
  42. package/dist/wsl-agent-host.js +3 -0
  43. package/package.json +4 -2
@@ -327560,6 +327560,7 @@ var NATIVE_TOOL_CATALOG = [
327560
327560
  { name: "browser_reload", label: "Browser reload", description: "Reload the built-in browser.", category: "browser", defaultEnabled: true },
327561
327561
  { name: "browser_cdp", label: "Browser CDP", description: "Run an advanced Chrome DevTools Protocol command.", category: "browser", defaultEnabled: true },
327562
327562
  { name: "browser_use", label: "Browser Use", description: "Observe and control the built-in browser through scoped opaque element refs and structured receipts.", category: "browser", defaultEnabled: true },
327563
+ { name: "screen_capture", label: "Active screenshot", description: "Capture the Windows desktop or one visible application without starting the full Computer Use control surface.", category: "desktop", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327563
327564
  { name: "computer_use", label: "Computer Use", description: "Observe and control Windows desktop UI with screenshots and semantic objects.", category: "desktop", defaultEnabled: true },
327564
327565
  { name: "image_inspect", label: "Image inspect", description: "Crop and magnify images submitted in the current conversation for closer visual inspection.", category: "core", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327565
327566
  { name: "image_display", label: "Image display", description: "Present a workspace PNG/JPEG inside the current Build Block.", category: "core", defaultEnabled: true, protected: true, availability: "mode-scoped" },
@@ -327567,7 +327568,7 @@ var NATIVE_TOOL_CATALOG = [
327567
327568
  { name: "pdf_read", label: "PDF read", description: "Read PDF text and render scanned pages through vision before local OCR fallback.", category: "core", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327568
327569
  { name: "terminal_takeover", label: "Terminal takeover", description: "Maintain a persistent Agent-controlled shell session.", category: "desktop", defaultEnabled: true },
327569
327570
  { name: "ssh_workspace", label: "OpenSSH workspace", description: "Manage native OpenSSH connections and link remote workspaces by PC_Hash.", category: "ssh", defaultEnabled: true },
327570
- { name: "task", label: "Subagent task", description: "Create a same-conversation peer agent.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327571
+ { name: "SubAgent", label: "SubAgent", description: "Create one real same-conversation peer SubAgent.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327571
327572
  { name: "subagent_list", label: "Subagent list", description: "List same-conversation peer agents.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327572
327573
  { name: "subagent_read", label: "Subagent read", description: "Read bounded status, feedback, result, queue, and mailbox summaries for a same-conversation peer.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327573
327574
  { name: "subagent_send", label: "Subagent send", description: "Persist a message to a peer mailbox.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
@@ -328319,7 +328320,7 @@ function defaultConfig() {
328319
328320
  auto_archive_on_close: { _description: "Auto archive on close", _type: "boolean", value: true }
328320
328321
  },
328321
328322
  remote: {
328322
- touch_enabled: { _description: "Allow mobile devices to reach this Newmark instance over Tailscale", _type: "boolean", value: true }
328323
+ touch_enabled: { _description: "Allow mobile devices to reach this Newmark instance on the same LAN / Tailscale network", _type: "boolean", value: true }
328323
328324
  },
328324
328325
  models: {
328325
328326
  providers: { _description: "LLM providers", _type: "array", value: [] },
@@ -328867,14 +328868,18 @@ var ChatCompletionsAdapter = class {
328867
328868
  ...request.systemPrompt ? [{ role: CHAT_SYSTEM_ROLE, content: request.systemPrompt }] : [],
328868
328869
  ...openAIChatMessages(request.messages)
328869
328870
  ];
328871
+ const tools = this.serializeTools(request.tools);
328870
328872
  const body = {
328871
328873
  model: request.model,
328872
328874
  messages,
328873
328875
  temperature: request.temperature,
328874
- max_tokens: request.maxOutputTokens,
328875
- tools: this.serializeTools(request.tools),
328876
- tool_choice: "auto"
328876
+ max_tokens: request.maxOutputTokens
328877
328877
  };
328878
+ if (tools.length) {
328879
+ body.tools = tools;
328880
+ body.tool_choice = "auto";
328881
+ body.parallel_tool_calls = true;
328882
+ }
328878
328883
  if (request.reasoningEffort) body.reasoning_effort = request.reasoningEffort;
328879
328884
  if (request.sessionId) body.session_id = request.sessionId;
328880
328885
  const base2 = request.baseUrl.replace(/\/+$/, "");
@@ -329128,6 +329133,7 @@ var ResponsesAdapter = class {
329128
329133
  if (tools.length) {
329129
329134
  body.tools = tools;
329130
329135
  body.tool_choice = "auto";
329136
+ body.parallel_tool_calls = true;
329131
329137
  }
329132
329138
  const base2 = request.baseUrl.replace(/\/+$/, "");
329133
329139
  return {
@@ -335749,6 +335755,7 @@ var import_crypto6 = require("crypto");
335749
335755
  // src/core/toolPolicy.ts
335750
335756
  var REQUIRED_TOOLS = /* @__PURE__ */ new Set(["pwd", "read", "glob", "grep"]);
335751
335757
  var MODE_SCOPED_TOOLS = /* @__PURE__ */ new Set([
335758
+ "screen_capture",
335752
335759
  "image_inspect",
335753
335760
  "image_display",
335754
335761
  "ocr_read",
@@ -335765,6 +335772,10 @@ var MODE_SCOPED_TOOLS = /* @__PURE__ */ new Set([
335765
335772
  "task_read",
335766
335773
  "task_create",
335767
335774
  "question",
335775
+ "SubAgent",
335776
+ "subagent_create",
335777
+ // Legacy runtime alias. It is no longer published to models because its
335778
+ // generic name collides with the persistent task checklist.
335768
335779
  "task",
335769
335780
  "subagent_list",
335770
335781
  "subagent_read",
@@ -335777,6 +335788,7 @@ var MODE_SCOPED_TOOLS = /* @__PURE__ */ new Set([
335777
335788
  "branch_create"
335778
335789
  ]);
335779
335790
  var PLAN_READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
335791
+ "screen_capture",
335780
335792
  "task_read",
335781
335793
  "pwd",
335782
335794
  "read",
@@ -335798,6 +335810,8 @@ var PLAN_READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
335798
335810
  "skill",
335799
335811
  "linked_plan",
335800
335812
  "build_history_query",
335813
+ "SubAgent",
335814
+ "subagent_create",
335801
335815
  "task",
335802
335816
  "subagent_list",
335803
335817
  "subagent_read",
@@ -335821,7 +335835,9 @@ var CONCURRENCY_SAFE_TOOLS = /* @__PURE__ */ new Set([
335821
335835
  "web_fetch",
335822
335836
  "git_status",
335823
335837
  "file_audit",
335824
- "repo_security_audit"
335838
+ "repo_security_audit",
335839
+ "screen_capture",
335840
+ "SubAgent"
335825
335841
  ]);
335826
335842
  function isConcurrencySafeTool(name50, riskLevel) {
335827
335843
  const toolName = String(name50 || "").trim();
@@ -336900,6 +336916,13 @@ var ToolExecutor = class {
336900
336916
  max_refs: { type: "number" },
336901
336917
  attribute: { type: "string" }
336902
336918
  }, ["action"]),
336919
+ t3("screen_capture", "Read-only active Windows screenshot available without provisioning or starting full Computer Use. Capture the whole desktop or one visible application. The image is a one-use model input and is deleted immediately; semantic Windows UI objects are returned alongside it.", {
336920
+ target: { type: "string", enum: ["desktop", "application"], description: "Capture the whole desktop or one visible application. Defaults to desktop." },
336921
+ app_target: { type: "string", description: "For target=application: application title, process name, process id, or window handle." },
336922
+ capture_max_width: { type: "number", minimum: 320, maximum: 2048, description: "Maximum ephemeral screenshot width; defaults to 1280. Aspect ratio is preserved and the source is never enlarged." },
336923
+ capture_max_height: { type: "number", minimum: 240, maximum: 2048, description: "Maximum ephemeral screenshot height; defaults to 960. Aspect ratio is preserved and the source is never enlarged." },
336924
+ max_chars: { type: "number", minimum: 1e3, maximum: 1e5, description: "Maximum returned semantic UI text characters." }
336925
+ }, []),
336903
336926
  t3("computer_use", "Native Windows desktop control with persistent observation/action helpers. Use observe/app_observe before acting. sequence may perform up to three stable low-risk move/click/scroll/wait/app_activate steps and stops when focus, window, menu, dialog, scene, or risk changes. Vision screenshots remain one-time inputs and are deleted immediately.", {
336904
336927
  action: { type: "string", enum: ["observe", "app_list", "app_observe", "sequence", "takeover_start", "takeover_stop", "move", "click", "scroll", "type", "key", "wait", "app_activate", "app_click", "app_scroll", "app_type", "app_key"] },
336905
336928
  capture_max_width: { type: "number", minimum: 320, maximum: 2048, description: "For observe/app_observe only. Maximum ephemeral screenshot width; defaults to 1280. Aspect ratio is preserved and the source is never enlarged." },
@@ -336985,7 +337008,7 @@ var ToolExecutor = class {
336985
337008
  remote_root: { type: "string" },
336986
337009
  remote_path: { type: "string" }
336987
337010
  }, ["action"]),
336988
- t3("task", "Create a same-conversation peer agent and return immediately. The peer has a stable human-readable name, a short id, and a canonical UUID-qualified identity. The peer name is decoupled from its id: pass name for readable references and id for exact targeting. Pass model to select an exact configured model deployment (deployment:providerId:modelId or an unambiguous provider/model name). When model is omitted, the peer inherits the parent Agent's currently resolved model deployment. Plan mode peers are forced to Plan.", { nature: { type: "string" }, name: { type: "string", description: "Legacy alias for nature." }, prompt: { type: "string" }, preset: { type: "string" }, agent: { type: "string" }, model: { type: "string", description: "Optional exact model deployment. Omit to inherit the parent Agent resolved model." }, mode: { type: "string" }, input_mode: { type: "string" }, flow: { type: "string" } }, ["prompt"]),
337011
+ t3("SubAgent", "Create one real same-conversation peer SubAgent and return immediately. This tool is only for delegation; never use it to create or update the conversation task checklist (use task_create for that). Creation is bound to the currently running Build Block: non-Ultra intelligence has a hard ceiling of 4 active peers for that Build, Ultra has 16, and excess calls terminate without creating or queueing a record. The peer has a stable human-readable name plus a separate exact id. Pass model to select an exact configured model deployment (deployment:providerId:modelId or an unambiguous provider/model name). When model is omitted, the peer inherits the parent Agent's currently resolved model deployment. Plan mode peers are forced to Plan.", { name: { type: "string", description: "Human-readable SubAgent name shown in the monitoring sidebar. Omit only when a named preset supplies it." }, nature: { type: "string", description: "Legacy alias for name." }, prompt: { type: "string", description: "Work delegated to this SubAgent; this is not a checklist item." }, preset: { type: "string" }, agent: { type: "string" }, model: { type: "string", description: "Optional exact model deployment. Omit to inherit the parent Agent resolved model." }, mode: { type: "string" }, input_mode: { type: "string" }, flow: { type: "string" } }, ["prompt"]),
336989
337012
  t3("subagent_list", "List flat same-conversation peer agents, optionally filtered by status. Each entry exposes both the stable name and the exact id; use the id for any subsequent targeting.", { status: { type: "string", enum: ["idle", "queued", "working", "completed", "error", "closed"] } }, []),
336990
337013
  t3("subagent_read", "Read one same-conversation peer status, queue/mailbox summary, latest bounded feedback, and result. Available for running, queued, completed, error, and closed peers. Pass the exact id returned by subagent_list, or a name for convenience lookup.", { id: { type: "string", description: "Exact peer id from subagent_list." }, name: { type: "string", description: "Convenience peer name; ambiguous names resolve to the first match." }, max_chars: { type: "number", description: "Bounded result size from 2000 to 32000 characters." } }, []),
336991
337014
  t3("subagent_send", "Persist a mailbox message to a same-conversation peer agent. Target by exact id (preferred) or name.", { id: { type: "string", description: "Exact peer id from subagent_list." }, name: { type: "string", description: "Convenience peer name." }, message: { type: "string" }, prompt: { type: "string", description: "Legacy alias for message." }, kind: { type: "string", enum: ["directive", "question", "result", "handoff"] }, reply_to: { type: "string" }, correlation_id: { type: "string" } }, []),
@@ -337162,7 +337185,8 @@ var ToolExecutor = class {
337162
337185
  return normalizeToolResult(output, { tool, workspacePath: wsPath, mode: context.mode || "" });
337163
337186
  }
337164
337187
  validateInvocation(tool, argsStr, _mode = "", inputSchema) {
337165
- if (inputSchema === void 0 && !isNativeToolEnabled(tool, this.config.nativeToolEnabled())) {
337188
+ const schemaTool = tool === "task" || tool === "subagent_create" ? "SubAgent" : tool;
337189
+ if (inputSchema === void 0 && !isNativeToolEnabled(schemaTool, this.config.nativeToolEnabled())) {
337166
337190
  return { ok: false, error: `[tool disabled] ${tool} is disabled in Settings > Tools.` };
337167
337191
  }
337168
337192
  let parsed;
@@ -337184,11 +337208,11 @@ var ToolExecutor = class {
337184
337208
  }, args);
337185
337209
  return validation2.ok ? { ok: false, error: "[permission] Question is disabled by fully_autonomous option feedback." } : { ok: false, error: `[tool schema error] ${validation2.error}` };
337186
337210
  }
337187
- const definition = inputSchema === void 0 ? this.definitions().find((candidate) => candidate.function?.name === tool) : { function: { name: tool, parameters: inputSchema } };
337211
+ const definition = inputSchema === void 0 ? this.definitions().find((candidate) => candidate.function?.name === schemaTool) : { function: { name: tool, parameters: inputSchema } };
337188
337212
  if (!definition) {
337189
337213
  return { ok: false, error: `[tool unsupported] ${tool || "(missing tool)"} is not available for the ${this.hostProfile.kind} host on ${this.hostProfile.platform}.` };
337190
337214
  }
337191
- const validation = this.argumentValidators.validate(tool, definition.function.parameters, args);
337215
+ const validation = this.argumentValidators.validate(schemaTool, definition.function.parameters, args);
337192
337216
  return validation.ok ? { ok: true, args } : { ok: false, error: `[tool schema error] ${validation.error}` };
337193
337217
  }
337194
337218
  async execute(tool, argsStr, wsPath, context = {}) {
@@ -337389,6 +337413,44 @@ var ToolExecutor = class {
337389
337413
  result: parsed
337390
337414
  }, null, 2);
337391
337415
  }
337416
+ case "screen_capture": {
337417
+ const target = g2("target").toLowerCase() === "application" ? "application" : "desktop";
337418
+ if (process.env.NEWMARK_WSL_DISTRO) {
337419
+ const result = await requestWindowsHostTool("screen_capture", args, {
337420
+ conversationId: context.conversationId || "default",
337421
+ workspaceId: context.workspaceId || terminalTakeoverWorkspaceId(context.workspacePath || wsPath),
337422
+ actorId: context.actorId || ROOT_TERMINAL_ACTOR_ID,
337423
+ runtimeKey: browserUseScope(context, wsPath).runtimeKey,
337424
+ allowEphemeralVisionImage: context.allowEphemeralVisionImage === true,
337425
+ mode: context.mode || "build"
337426
+ }, 12e4, context.signal);
337427
+ return typeof result === "string" ? result : JSON.stringify(result);
337428
+ }
337429
+ if (process.env.NEWMARK_ISOLATED_RUNTIME === "1") {
337430
+ const result = await requestUtilityHostTool("screen_capture", args, {
337431
+ conversationId: context.conversationId || "default",
337432
+ workspaceId: context.workspaceId || terminalTakeoverWorkspaceId(context.workspacePath || wsPath),
337433
+ actorId: context.actorId || ROOT_TERMINAL_ACTOR_ID,
337434
+ workspacePath: context.workspacePath || wsPath,
337435
+ backend: "utility",
337436
+ mode: context.mode || "build",
337437
+ allowEphemeralVisionImage: context.allowEphemeralVisionImage === true
337438
+ }, 12e4, context.signal);
337439
+ return typeof result === "string" ? result : JSON.stringify(result);
337440
+ }
337441
+ return await runComputerUse({
337442
+ action: target === "application" ? "app_observe" : "observe",
337443
+ appTarget: g2("app_target"),
337444
+ windowHandle: target === "application" ? g2("app_target") : "",
337445
+ maxChars: Number(args.max_chars || 3e4),
337446
+ workspacePath: wsPath,
337447
+ allowEphemeralVisionImage: context.allowEphemeralVisionImage === true,
337448
+ captureMaxWidth: Number(args.capture_max_width),
337449
+ captureMaxHeight: Number(args.capture_max_height),
337450
+ invocation: context.invocation,
337451
+ ownerId: `screen-capture:${computerUseOwner(context, wsPath)}:${String(context.actorId || "root")}`
337452
+ });
337453
+ }
337392
337454
  case "computer_use": {
337393
337455
  const action = normalizeComputerUseAction(g2("action"));
337394
337456
  const owner = `${computerUseOwner(context, wsPath)}:${String(context.actorId || "root")}`;
@@ -337510,7 +337572,9 @@ var ToolExecutor = class {
337510
337572
  case "ssh_workspace":
337511
337573
  return await this.sshWorkspace(args, wsPath, context.signal);
337512
337574
  case "task":
337513
- return `[task] Subagent request accepted: ${g2("name")}`;
337575
+ case "subagent_create":
337576
+ case "SubAgent":
337577
+ return `[SubAgent] SubAgent request accepted: ${g2("name")}`;
337514
337578
  case "subagent_send":
337515
337579
  return `[subagent_send] Routed to Agent runtime: ${g2("name")}`;
337516
337580
  case "subagent_read":
@@ -337593,7 +337657,7 @@ var ToolExecutor = class {
337593
337657
  }
337594
337658
  hostSupportsTool(name50) {
337595
337659
  if (name50.startsWith("browser_") && !this.hostProfile.electronBrowser) return false;
337596
- if (name50 === "computer_use" && !this.hostProfile.windowsComputerUse) return false;
337660
+ if ((name50 === "computer_use" || name50 === "screen_capture") && !this.hostProfile.windowsComputerUse) return false;
337597
337661
  return true;
337598
337662
  }
337599
337663
  async decorateBrowserUseReceipt(input2, context, runtimeKey) {
@@ -338683,6 +338747,7 @@ var SubagentManager = class {
338683
338747
  queueMicrotask(() => this.pump());
338684
338748
  }
338685
338749
  bind(options) {
338750
+ if (options.concurrency !== void 0) this.setConcurrencyLimit(options.concurrency);
338686
338751
  if (options.executor) this.executor = options.executor;
338687
338752
  if (options.onChange) this.onChange = options.onChange;
338688
338753
  if (options.persist) this.persist = options.persist;
@@ -338699,12 +338764,13 @@ var SubagentManager = class {
338699
338764
  removeRootInboxListener(listener) {
338700
338765
  this.rootInboxListeners.delete(listener);
338701
338766
  }
338702
- create(name50, prompt, model, inputMode, agentMode = "build", createdByAgentId = this.rootAgentId, flowName = "", goalObjective = "", flowPc = 0) {
338767
+ create(name50, prompt, model, inputMode, agentMode = "build", createdByAgentId = this.rootAgentId, flowName = "", goalObjective = "", flowPc = 0, buildRunId = "", intelligenceTier = "") {
338703
338768
  const id = (0, import_crypto8.randomUUID)();
338704
338769
  const shortId = id.replace(/-/g, "").slice(0, 8);
338705
338770
  const slug = natureSlug(name50);
338706
- const displayName = `${slug}-${shortId}`;
338707
- const qualifiedName = `${displayName}--${id}`;
338771
+ const createdName = String(name50 || "SubAgent").replace(/\s+/g, " ").trim().slice(0, 160) || "SubAgent";
338772
+ const displayName = createdName;
338773
+ const qualifiedName = `${slug}--${id}`;
338708
338774
  const stamp = now();
338709
338775
  const record = {
338710
338776
  id,
@@ -338712,9 +338778,11 @@ var SubagentManager = class {
338712
338778
  natureSlug: slug,
338713
338779
  displayName,
338714
338780
  qualifiedName,
338715
- name: slug,
338781
+ name: createdName,
338716
338782
  conversationId: this.conversationId,
338717
338783
  createdByAgentId,
338784
+ buildRunId: String(buildRunId || "").trim() || void 0,
338785
+ intelligenceTier: String(intelligenceTier || "").trim() || void 0,
338718
338786
  prompt,
338719
338787
  model: model || "default",
338720
338788
  inputMode: inputMode || "guide",
@@ -339037,6 +339105,20 @@ var SubagentManager = class {
339037
339105
  listAll() {
339038
339106
  return [...this.subs.values()].map(cloneRecord);
339039
339107
  }
339108
+ activeCountForBuild(buildRunId) {
339109
+ const target = String(buildRunId || "").trim();
339110
+ if (!target) return 0;
339111
+ return [...this.subs.values()].filter(
339112
+ (record) => record.buildRunId === target && (record.status === "queued" || record.status === "working")
339113
+ ).length;
339114
+ }
339115
+ setConcurrencyLimit(value) {
339116
+ this.concurrency = Math.max(1, Math.min(16, Math.floor(Number(value) || 4)));
339117
+ this.pump();
339118
+ }
339119
+ concurrencyLimit() {
339120
+ return this.concurrency;
339121
+ }
339040
339122
  pauseScheduling() {
339041
339123
  if (this.schedulingPaused) return;
339042
339124
  this.schedulingPaused = true;
@@ -340202,7 +340284,8 @@ function inferDomain(name50) {
340202
340284
  }
340203
340285
  if (name50 === "question") return "interaction";
340204
340286
  if (name50 === "skill") return "skills";
340205
- if (name50 === "task") return "subagent";
340287
+ if (name50 === "task" || name50 === "subagent_create" || name50 === "SubAgent") return "subagent";
340288
+ if (/^task_(read|create)$/.test(name50)) return "plan";
340206
340289
  if (/^(linked_plan|build_history_query)$/.test(name50)) return "plan";
340207
340290
  return "general";
340208
340291
  }
@@ -340466,7 +340549,7 @@ function resetPublicAssistantDeltaFilter(agent) {
340466
340549
  function prepareAssistantToolVisibility(agent, definitions) {
340467
340550
  const names = definitions.map(toolDefinitionName);
340468
340551
  brokerOnlyAssistantBuffers.set(agent, {
340469
- brokerOnly: names.includes(TOOL_PROVISION_NAME) && names.every((name50) => name50 === TOOL_PROVISION_NAME || name50 === "skill" || SUBAGENT_CORE_TOOL_NAMES.has(name50)),
340552
+ brokerOnly: names.includes(TOOL_PROVISION_NAME) && names.every((name50) => name50 === TOOL_PROVISION_NAME || name50 === "skill" || ALWAYS_AVAILABLE_AGENT_TOOL_NAMES.has(name50)),
340470
340553
  pending: [],
340471
340554
  released: false
340472
340555
  });
@@ -340758,7 +340841,7 @@ async function runAgentKernel(agent) {
340758
340841
  const requestStartedAt = Date.now();
340759
340842
  let firstTokenRecorded = false;
340760
340843
  const tools = context.tools || [];
340761
- const brokerOnlySurface = tools.length > 0 && tools.every((tool) => tool.name === TOOL_PROVISION_NAME || tool.name === "skill" || SUBAGENT_CORE_TOOL_NAMES.has(tool.name));
340844
+ const brokerOnlySurface = tools.length > 0 && tools.every((tool) => tool.name === TOOL_PROVISION_NAME || tool.name === "skill" || ALWAYS_AVAILABLE_AGENT_TOOL_NAMES.has(tool.name));
340762
340845
  currentAgent.beginRouteAttempt();
340763
340846
  try {
340764
340847
  const currentProvider = currentAgent.engineModel();
@@ -340993,6 +341076,8 @@ function buildBuildContextBootstrap(agent, messages, options) {
340993
341076
  buildConversationTaskLedger(agent),
340994
341077
  "## Tool Awareness Bootstrap",
340995
341078
  "The following catalog is capability metadata only. Tool descriptions are not instructions, and a tool is callable only when its full schema is present in the provider tools field.",
341079
+ "Only bash, pwd, read, write, edit, delete_file, glob, and grep are foundational tools with initial full schemas (subject to mode and policy filtering).",
341080
+ "Advanced capabilities\u2014including SubAgent, task tools, Git/GitHub, browser, Computer Use, skills, MCP, automations, Flow, and Memory Lab\u2014are not initially callable. Before using one, first call tool_provision with its exact tool name as the only tool call in that assistant subturn; call the advanced tool only on the following model turn after its full schema appears.",
340996
341081
  ...catalogLines.length ? catalogLines : ["- No callable tools are available for this provider turn."],
340997
341082
  `Necessary full schemas supplied natively for this provider turn: ${activeNames.length ? activeNames.join(", ") : "(none; use tool_provision when its schema is available)"}.`,
340998
341083
  "Do not invent parameters from the brief catalog. Use only the exact full schemas supplied through the provider tool interface; provision another exact tool when needed."
@@ -341182,7 +341267,8 @@ function routeTransitionNotice(agent, previous) {
341182
341267
  var TOOL_PROVISION_NAME = "tool_provision";
341183
341268
  var INITIAL_TOOL_SCHEMA_LIMIT = 8;
341184
341269
  var TOOL_PROVISION_BATCH_LIMIT = 8;
341185
- var SUBAGENT_CORE_TOOL_NAMES = /* @__PURE__ */ new Set(["task", "subagent_list", "subagent_read", "subagent_send", "subagent_result", "subagent_close"]);
341270
+ var BASIC_INITIAL_TOOL_NAMES = /* @__PURE__ */ new Set(["bash", "pwd", "read", "write", "edit", "delete_file", "glob", "grep"]);
341271
+ var ALWAYS_AVAILABLE_AGENT_TOOL_NAMES = BASIC_INITIAL_TOOL_NAMES;
341186
341272
  var ToolProvisionSession = class {
341187
341273
  definitionsByName = /* @__PURE__ */ new Map();
341188
341274
  initialNames = /* @__PURE__ */ new Set();
@@ -341206,7 +341292,7 @@ var ToolProvisionSession = class {
341206
341292
  const name50 = toolDefinitionName(definition);
341207
341293
  if (this.definitionsByName.has(name50)) this.initialNames.add(name50);
341208
341294
  }
341209
- for (const name50 of SUBAGENT_CORE_TOOL_NAMES) {
341295
+ for (const name50 of ALWAYS_AVAILABLE_AGENT_TOOL_NAMES) {
341210
341296
  if (this.definitionsByName.has(name50)) this.initialNames.add(name50);
341211
341297
  }
341212
341298
  for (const name50 of this.provisionedNames) {
@@ -341364,29 +341450,6 @@ function toolSurfaceIdentityForAgent(agent) {
341364
341450
  optionFeedback: agent.config.getStr("agent", "option_feedback")
341365
341451
  });
341366
341452
  }
341367
- var CAPABILITY_TO_DOMAIN = {
341368
- "vcs.inspect": ["git"],
341369
- "code.search": ["core"],
341370
- "test.run": ["core"],
341371
- "web.search": ["web"],
341372
- "automation.manage": ["automation"],
341373
- "flow.manage": ["flow"],
341374
- "memory.manage": ["memory"],
341375
- "computer.manage": ["computer"],
341376
- "browser.manage": ["browser"],
341377
- "terminal.manage": ["general", "core"],
341378
- "github.manage": ["general"],
341379
- "ssh.manage": ["general"],
341380
- "skill.manage": ["skills", "general"],
341381
- "plan.manage": ["plan"],
341382
- "history.query": ["plan"],
341383
- "media.display": ["media"],
341384
- "interaction.manage": ["interaction"]
341385
- };
341386
- var CAPABILITY_TOOL_HINTS = {
341387
- "skill.manage": ["skill", "skill_download"],
341388
- "memory.manage": ["memory_lab_read", "memory_lab_query", "memory_lab_update", "memory_lab_reindex"]
341389
- };
341390
341453
  function routeToolSurfaceV2(agent, definitions, toolchain, task) {
341391
341454
  if (!agent.shouldExposeToolInterface()) {
341392
341455
  return {
@@ -341398,67 +341461,34 @@ function routeToolSurfaceV2(agent, definitions, toolchain, task) {
341398
341461
  ].join("\n")
341399
341462
  };
341400
341463
  }
341401
- if (!toolchain) return { definitions, systemPromptNotice: "" };
341402
- const planner = new ToolExposurePlanner(toolchain.registry, toolchain.catalog);
341403
- const plan = planner.plan({
341404
- agentRunId: agent.runtimeActorId,
341405
- buildBlockId: agent.activeConversationId || "build",
341406
- userInput: task,
341407
- objective: "",
341408
- previousToolCalls: [],
341409
- toolUsageFrequency: /* @__PURE__ */ new Map(),
341410
- permissionScope: ["workspace"],
341411
- tokenBudget: 2e4,
341412
- providerToolLimit: 0
341413
- });
341414
- const domains = /* @__PURE__ */ new Set();
341415
- const toolHints = /* @__PURE__ */ new Set();
341416
- for (const capabilityId of plan.suggestedCapabilityIds) {
341417
- for (const domain of CAPABILITY_TO_DOMAIN[capabilityId] || []) domains.add(domain);
341418
- for (const toolName of CAPABILITY_TOOL_HINTS[capabilityId] || []) toolHints.add(toolName);
341419
- }
341420
- const names = definitions.map(toolDefinitionName);
341421
- for (const name50 of names) {
341422
- if (name50 && new RegExp(`(?:^|[^A-Za-z0-9_])${name50.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:$|[^A-Za-z0-9_])`, "i").test(task)) {
341423
- toolHints.add(name50);
341424
- }
341425
- }
341426
- const selected = definitions.filter((definition) => {
341427
- const name50 = toolDefinitionName(definition);
341428
- const descriptor = toolchain.registry.get(name50);
341429
- if (!descriptor || descriptor.riskLevel === "destructive") return false;
341430
- if (toolHints.has(name50)) return true;
341431
- if (!domains.has(descriptor.capabilityId.slice(4))) return false;
341432
- return true;
341433
- }).sort((a3, b2) => {
341434
- const aHint = toolHints.has(toolDefinitionName(a3)) ? 0 : 1;
341435
- const bHint = toolHints.has(toolDefinitionName(b2)) ? 0 : 1;
341436
- return aHint - bHint || names.indexOf(toolDefinitionName(a3)) - names.indexOf(toolDefinitionName(b2));
341437
- }).slice(0, INITIAL_TOOL_SCHEMA_LIMIT);
341438
- const selectedNames = new Set(selected.map(toolDefinitionName));
341439
- const core = definitions.filter((definition) => {
341440
- const name50 = toolDefinitionName(definition);
341441
- return SUBAGENT_CORE_TOOL_NAMES.has(name50) && !selectedNames.has(name50);
341442
- });
341443
- const surface = core.length ? selected.concat(core) : selected;
341444
- if (surface.length === definitions.length) return { definitions, systemPromptNotice: "" };
341445
- if (!selected.length) {
341446
- return {
341447
- definitions: surface,
341448
- systemPromptNotice: [
341449
- "## Tool Interface Availability",
341450
- "This turn was classified as conversational, so no task-specific tool schema was preloaded.",
341451
- `The ${TOOL_PROVISION_NAME} interface still exposes the complete compact capability catalog and can provision an original tool schema when the task requires it.`
341452
- ].join("\n")
341453
- };
341464
+ const surface = definitions.filter((definition) => BASIC_INITIAL_TOOL_NAMES.has(toolDefinitionName(definition)));
341465
+ const advancedCount = Math.max(0, definitions.length - surface.length);
341466
+ let planFingerprint = "";
341467
+ if (toolchain) {
341468
+ const planner = new ToolExposurePlanner(toolchain.registry, toolchain.catalog);
341469
+ const plan = planner.plan({
341470
+ agentRunId: agent.runtimeActorId,
341471
+ buildBlockId: agent.activeConversationId || "build",
341472
+ userInput: task,
341473
+ objective: "",
341474
+ previousToolCalls: [],
341475
+ toolUsageFrequency: /* @__PURE__ */ new Map(),
341476
+ permissionScope: ["workspace"],
341477
+ tokenBudget: 2e4,
341478
+ providerToolLimit: 0
341479
+ });
341480
+ planFingerprint = plan.plan.stableToolsetHash.slice(0, 8);
341454
341481
  }
341455
341482
  return {
341456
341483
  definitions: surface,
341457
341484
  systemPromptNotice: [
341458
341485
  "## Tool Interface Availability",
341459
- `At most ${INITIAL_TOOL_SCHEMA_LIMIT} deterministic task-relevant schemas are preloaded for this turn; the always-available subagent orchestration tools are appended separately.`,
341460
- `Use ${TOOL_PROVISION_NAME} to provision any catalogued tool that is not yet listed; the original tool name and schema become available on the next model turn.`,
341461
- `Adaptive exposure plan ${plan.plan.stableToolsetHash.slice(0, 8)}: ${plan.activeToolIds.length} planned tools, ${plan.plan.suggestedCapabilityIds.join(",")}.`
341486
+ `The initial full-schema surface is restricted to foundational workspace tools: ${surface.map(toolDefinitionName).join(", ") || "(none allowed in this mode)"}.`,
341487
+ `${advancedCount} advanced tools are advertised by capability in the compact ${TOOL_PROVISION_NAME} catalog without loading their schemas.`,
341488
+ "Advanced tools include SubAgent, task tools, Git/GitHub, browser, Computer Use, skills, MCP, automations, Flow, and Memory Lab.",
341489
+ `Call ${TOOL_PROVISION_NAME} as the only tool in a subturn to load any advanced tool by exact name; its original schema becomes available on the next model turn.`,
341490
+ "Do not call an advanced tool directly from the initial catalog: capability presence is not callability until provisioning has completed.",
341491
+ planFingerprint ? `Capability routing fingerprint: ${planFingerprint}.` : "Capability registry unavailable; the compact catalog remains authoritative."
341462
341492
  ].join("\n")
341463
341493
  };
341464
341494
  }
@@ -341527,10 +341557,12 @@ function toKernelTools(agent, definitions, provisioning) {
341527
341557
  throw new Error(rawText);
341528
341558
  }
341529
341559
  const visionImage = visualFallbackImageInput(agent, name50, rawText);
341560
+ const capturedInput = name50 === "screen_capture" ? agent.registerCapturedImageInput(visionImage.image || "", "active-screenshot.jpg") : null;
341530
341561
  const directImage = imageInspectDataUrl(name50, rawText);
341531
- const text = spillOversizedToolResult(agent, name50, sanitizeVisualToolText(name50, rawText));
341562
+ const text = spillOversizedToolResult(agent, name50, capturedImageToolText(name50, sanitizeVisualToolText(name50, rawText), capturedInput?.id));
341532
341563
  const content = [{ type: "text", text }];
341533
- if (visionImage.imagePath) content.push({ type: "image", imagePath: visionImage.imagePath, mimeType: imageMimeForPath(visionImage.imagePath) });
341564
+ if (name50 === "screen_capture" && capturedInput?.dataUrl) content.push({ type: "image", image: capturedInput.dataUrl, mimeType: capturedInput.mimeType });
341565
+ else if (visionImage.imagePath) content.push({ type: "image", imagePath: visionImage.imagePath, mimeType: imageMimeForPath(visionImage.imagePath) });
341534
341566
  else if (visionImage.image) content.push({ type: "image", image: visionImage.image, mimeType: visionImage.mimeType });
341535
341567
  if (directImage) content.push({ type: "image", image: directImage, mimeType: "image/png" });
341536
341568
  const terminate = shouldTerminateAfterToolResult(name50) && (name50 !== "question" || agent.pendingOptions.length > 0);
@@ -341543,7 +341575,7 @@ function toKernelTools(agent, definitions, provisioning) {
341543
341575
  } catch {
341544
341576
  }
341545
341577
  }
341546
- return { content, details: { tool: name50, ok: true, terminate, ...launchReceipt ? { launchReceipt } : {}, visionImagePath: visionImage.imagePath || void 0, ephemeralVisionImage: !!visionImage.image, displayImage }, terminate };
341578
+ return { content, details: { tool: name50, ok: true, terminate, ...launchReceipt ? { launchReceipt } : {}, visionImagePath: visionImage.imagePath || void 0, ephemeralVisionImage: !!visionImage.image, capturedAttachmentId: capturedInput?.id, displayImage }, terminate };
341547
341579
  }
341548
341580
  };
341549
341581
  }).filter((tool) => !!tool.name);
@@ -341567,7 +341599,7 @@ var INLINE_TOOL_RESULT_MAX_CHARS = 24e3;
341567
341599
  function spillOversizedToolResult(agent, name50, text) {
341568
341600
  const value = String(text || "");
341569
341601
  if (value.length <= INLINE_TOOL_RESULT_MAX_CHARS) return value;
341570
- if (["computer_use", "browser_use", "pdf_read", "image_inspect", "image_display", "task", "subagent_send", "subagent_result", "subagent_read", "linked_plan", "question"].includes(name50)) {
341602
+ if (["screen_capture", "computer_use", "browser_use", "pdf_read", "image_inspect", "image_display", "task", "subagent_create", "SubAgent", "subagent_send", "subagent_result", "subagent_read", "linked_plan", "question"].includes(name50)) {
341571
341603
  return value;
341572
341604
  }
341573
341605
  const artifactId = agent.storeToolResultArtifact(name50, value);
@@ -341582,10 +341614,10 @@ function spillOversizedToolResult(agent, name50, text) {
341582
341614
  ].join("\n");
341583
341615
  }
341584
341616
  function sanitizeVisualToolText(name50, text) {
341585
- if (name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read" && name50 !== "image_inspect") return text;
341617
+ if (name50 !== "screen_capture" && name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read" && name50 !== "image_inspect") return text;
341586
341618
  try {
341587
341619
  const parsed = JSON.parse(text);
341588
- if (name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read") {
341620
+ if (name50 === "screen_capture" || name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read") {
341589
341621
  delete parsed.vision_image_path;
341590
341622
  delete parsed.vision_image_data_url;
341591
341623
  if (name50 === "pdf_read" && parsed.result && typeof parsed.result === "object") {
@@ -341601,7 +341633,7 @@ function sanitizeVisualToolText(name50, text) {
341601
341633
  }
341602
341634
  }
341603
341635
  function discardComputerUseVisionImage(name50, text) {
341604
- if (name50 !== "computer_use") return;
341636
+ if (name50 !== "screen_capture" && name50 !== "computer_use") return;
341605
341637
  try {
341606
341638
  const parsed = JSON.parse(text);
341607
341639
  const screenshotPath = String(parsed.vision_image_path || "");
@@ -341619,8 +341651,20 @@ function imageInspectDataUrl(name50, text) {
341619
341651
  return "";
341620
341652
  }
341621
341653
  }
341654
+ function capturedImageToolText(name50, text, attachmentId) {
341655
+ if (name50 !== "screen_capture" || !attachmentId) return text;
341656
+ try {
341657
+ const parsed = JSON.parse(text);
341658
+ parsed.attachment_id = attachmentId;
341659
+ parsed.image_input_channel = "user-image";
341660
+ parsed.inspect_next = { tool: "image_inspect", actions: ["source_info", "crop"], max_scale: 4 };
341661
+ return JSON.stringify(parsed, null, 2);
341662
+ } catch {
341663
+ return text;
341664
+ }
341665
+ }
341622
341666
  function visualFallbackImageInput(agent, name50, text) {
341623
- if (name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read") return {};
341667
+ if (name50 !== "screen_capture" && name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read") return {};
341624
341668
  const model = agent.activeModelConfig();
341625
341669
  if (!model?.vision) return {};
341626
341670
  try {
@@ -341668,7 +341712,7 @@ async function executeNewmarkTool(agent, name50, args, inputSchema, signal) {
341668
341712
  if (signal?.aborted) throw abortError4();
341669
341713
  return result2;
341670
341714
  };
341671
- if (name50 === "task") return (await agent.handleSubagentEnvelope(args)).output;
341715
+ if (name50 === "task" || name50 === "subagent_create" || name50 === "SubAgent") return (await agent.handleSubagentEnvelope(args, true)).output;
341672
341716
  if (name50 === "subagent_send") return (await agent.handleSubagentContinueEnvelope(args)).output;
341673
341717
  if (name50 === "subagent_list") return agent.handleSubagentListEnvelope(args).output;
341674
341718
  if (name50 === "subagent_read") return agent.handleSubagentReadEnvelope(args).output;
@@ -341746,7 +341790,7 @@ async function executeNewmarkTool(agent, name50, args, inputSchema, signal) {
341746
341790
  actorId: agent.runtimeActorId,
341747
341791
  workspaceId: terminalTakeoverWorkspaceId(wsDir),
341748
341792
  backend: process.env.NEWMARK_WSL_DISTRO ? "wsl" : process.platform === "win32" ? "windows" : process.platform,
341749
- allowEphemeralVisionImage: (name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read" || name50 === "ocr_read") && !!agent.activeModelConfig()?.vision,
341793
+ allowEphemeralVisionImage: (name50 === "screen_capture" || name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read" || name50 === "ocr_read") && !!agent.activeModelConfig()?.vision,
341750
341794
  signal
341751
341795
  });
341752
341796
  if (signal?.aborted) throw abortError4();
@@ -344820,7 +344864,7 @@ var Agent4 = class _Agent {
344820
344864
  this.tools = new ToolExecutor(rootPath, this.config, this.ssh, this.workspace);
344821
344865
  this.skills = new SkillsManager(rootPath);
344822
344866
  this.memoryLab = new MemoryLabManager(rootPath, this.config.getStr("general", "language"));
344823
- this.subagents = new SubagentManager({ rootAgentId: this.runtimeActorId });
344867
+ this.subagents = new SubagentManager({ rootAgentId: this.runtimeActorId, concurrency: this.subagentConcurrencyLimit() });
344824
344868
  if (this.mode === "goal" && !this.goal) {
344825
344869
  this.goal = new GoalStateImpl("Set your objective");
344826
344870
  }
@@ -344889,6 +344933,7 @@ var Agent4 = class _Agent {
344889
344933
  memoryLabRebuildState = "idle";
344890
344934
  memoryLabRebuildError = "";
344891
344935
  displayImageDescriptionCache = /* @__PURE__ */ new Map();
344936
+ latestCapturedImageInputs = [];
344892
344937
  activeProcessAbortController = null;
344893
344938
  automationManager = null;
344894
344939
  activeAgentKernelRuntime = null;
@@ -345419,6 +345464,7 @@ var Agent4 = class _Agent {
345419
345464
  }
345420
345465
  setIntelligence(tier, persist = false) {
345421
345466
  this.intelligence = normalizeIntelligenceTier(tier);
345467
+ this.subagents?.setConcurrencyLimit(this.subagentConcurrencyLimit());
345422
345468
  if (persist) {
345423
345469
  this.config.set("models", "default_intelligence", this.intelligence);
345424
345470
  this.config.save();
@@ -345623,12 +345669,17 @@ var Agent4 = class _Agent {
345623
345669
  return path28.join(ws.path, "conversations", "state.json");
345624
345670
  }
345625
345671
  workspaceConversationStateKey(conversationId = this.activeConversationId) {
345626
- const prefix = this.workspaceConversationPrefix();
345672
+ return this.workspaceConversationStateKeyFor(conversationId, this.workspace.current);
345673
+ }
345674
+ workspaceConversationStateKeyFor(conversationId, ws) {
345675
+ const prefix = this.workspaceConversationPrefixFor(ws);
345627
345676
  if (!prefix) return null;
345628
345677
  return `${prefix}-${this.safeConversationId(conversationId)}`;
345629
345678
  }
345630
345679
  workspaceConversationPrefix() {
345631
- const ws = this.workspace.current;
345680
+ return this.workspaceConversationPrefixFor(this.workspace.current);
345681
+ }
345682
+ workspaceConversationPrefixFor(ws) {
345632
345683
  if (!ws) return null;
345633
345684
  const supplied = String(ws.conversationStatePrefix || "").trim();
345634
345685
  if (/^(?:internal|external)-[a-f0-9]{16}$/i.test(supplied)) return supplied.toLowerCase();
@@ -347078,8 +347129,56 @@ ${String(event.toolArgs || "")}`;
347078
347129
  }
347079
347130
  }
347080
347131
  listConversationStates() {
347081
- const stored = this.readStoredConversationState();
347082
- const prefix = this.workspaceConversationPrefix() || "";
347132
+ return this.listWorkspaceConversationStates(this.workspace.current);
347133
+ }
347134
+ registerCapturedImageInput(dataUrl, name50 = "active-screenshot.jpg") {
347135
+ if (!String(dataUrl || "").startsWith("data:image/")) return null;
347136
+ const prepared = this.prepareSubmittedConversationImages([{ dataUrl, name: name50, type: dataUrl.slice(5, dataUrl.indexOf(";")) }]);
347137
+ const attachment = prepared.attachments[0] || null;
347138
+ this.latestCapturedImageInputs = attachment ? [attachment] : [];
347139
+ return attachment;
347140
+ }
347141
+ subagentConcurrencyLimit() {
347142
+ return this.intelligence === "ultra" ? 16 : 4;
347143
+ }
347144
+ /** 当前工作区使用内存中的前台选择;后台工作区从各自持久化状态读取 active id。 */
347145
+ activeConversationIdForWorkspace(ws) {
347146
+ const targetWs = ws || this.workspace.current;
347147
+ if (!targetWs) return "default";
347148
+ const currentWs = this.workspace.current;
347149
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347150
+ if (isCurrent) return this.safeConversationId(this.activeConversationId || "default");
347151
+ const stored = this.readStoredConversationState(targetWs);
347152
+ return this.safeConversationId(stored.activeConversationId || "default");
347153
+ }
347154
+ /**
347155
+ * 持久化的完整 work run 记录(含 interrupted/force_interrupted)。
347156
+ * 不依赖运行时内存(run 结束后内存清空,state 端点曾因此丢失被中断的构建记录),
347157
+ * 供 mobile 端点稳定透出完整对话信息;移动端按同一格式解析。
347158
+ */
347159
+ getPersistedConversationWorkRuns(conversationId, ws = null) {
347160
+ const targetWs = ws || this.workspace.current;
347161
+ const clean = this.safeConversationId(conversationId || "default");
347162
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, targetWs);
347163
+ const stored = this.readStoredConversationState(targetWs);
347164
+ const persisted = stateKey2 && stored.conversations ? stored.conversations[stateKey2] : void 0;
347165
+ const tree = persisted ? this.normalizeConversationTree(persisted) : null;
347166
+ const runtimeNodeId = String(tree?.activeNodeId || "");
347167
+ const viewedNodeId = tree ? this.resolveConversationTreePath(tree, this.storedConversationTreePath(tree, persisted?.viewedBranchNodePath, runtimeNodeId)) : "";
347168
+ const viewedNode = tree?.nodes[viewedNodeId];
347169
+ return this.normalizeWorkRuns(viewedNode?.workRuns || persisted?.workRuns);
347170
+ }
347171
+ /** Exact membership check against the raw persisted state key, before UI content deduplication. */
347172
+ hasConversationInWorkspace(conversationId, ws) {
347173
+ const targetWs = ws || this.workspace.current;
347174
+ const stateKey2 = this.workspaceConversationStateKeyFor(this.safeConversationId(conversationId || "default"), targetWs);
347175
+ if (!stateKey2) return false;
347176
+ return Object.prototype.hasOwnProperty.call(this.readStoredConversationState(targetWs).conversations || {}, stateKey2);
347177
+ }
347178
+ /** 按工作区列对话(任意 ws,key 前缀 = kind-sha256(path).slice(0,16));供 mobile API 透出工作区从属对话 */
347179
+ listWorkspaceConversationStates(ws) {
347180
+ const stored = this.readStoredConversationState(ws);
347181
+ const prefix = this.workspaceConversationPrefixFor(ws) || "";
347083
347182
  const scopedEntries = Object.entries(stored.conversations || {}).filter(([key3]) => !prefix || key3.startsWith(prefix));
347084
347183
  if (scopedEntries.some(([, value]) => !Number.isFinite(value.order))) {
347085
347184
  const legacyOrder = [...scopedEntries].sort(([, a3], [, b2]) => {
@@ -347090,7 +347189,7 @@ ${String(event.toolArgs || "")}`;
347090
347189
  legacyOrder.forEach(([, value], index) => {
347091
347190
  value.order = index;
347092
347191
  });
347093
- this.writeStoredConversationState(stored);
347192
+ this.writeStoredConversationState(stored, ws);
347094
347193
  }
347095
347194
  const rows = [];
347096
347195
  for (const [key3, value] of Object.entries(stored.conversations || {})) {
@@ -347144,6 +347243,7 @@ ${String(event.toolArgs || "")}`;
347144
347243
  this.subagents = sharedSubagentManager(this.subagentManagerKey(clean), {
347145
347244
  conversationId: clean,
347146
347245
  rootAgentId: state?.rootAgentId || this.runtimeActorId,
347246
+ concurrency: this.subagentConcurrencyLimit(),
347147
347247
  state,
347148
347248
  executor: (job) => this.runSubagentJob(job.record.id, job.prompt, job.flowName, job.reason),
347149
347249
  persist: (subagentState) => this.persistSubagentState(clean, subagentState),
@@ -347213,15 +347313,14 @@ Review this persisted peer result and summarize or continue the parent task as n
347213
347313
  }
347214
347314
  getConversationSnapshot(conversationId = this.activeConversationId, options = {}) {
347215
347315
  const clean = this.safeConversationId(conversationId || "default");
347216
- const isActiveConversation = clean === this.safeConversationId(this.activeConversationId || "default");
347217
- const stateKey2 = this.workspaceConversationStateKey(clean);
347218
- const memoryKey = (() => {
347219
- const ws = this.workspace.current;
347220
- if (!ws) return null;
347221
- return `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
347222
- })();
347316
+ const ws = options.workspace || this.workspace.current;
347317
+ const currentWs = this.workspace.current;
347318
+ const isActiveWorkspace = !ws && !currentWs || !!ws && !!currentWs && path28.resolve(ws.path) === path28.resolve(currentWs.path);
347319
+ const isActiveConversation = isActiveWorkspace && clean === this.safeConversationId(this.activeConversationId || "default");
347320
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, ws);
347321
+ const memoryKey = ws ? `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}` : null;
347223
347322
  const memory = memoryKey ? this.workspaceConversations.get(memoryKey) : void 0;
347224
- const stored = this.readStoredConversationState();
347323
+ const stored = this.readStoredConversationState(ws);
347225
347324
  const persisted = stateKey2 && stored.conversations ? stored.conversations[stateKey2] : void 0;
347226
347325
  const tree = persisted ? this.normalizeConversationTree(persisted) : null;
347227
347326
  const runtimeNodeId = String(tree?.activeNodeId || "");
@@ -347241,7 +347340,7 @@ Review this persisted peer result and summarize or continue the parent task as n
347241
347340
  const continuations = this.normalizeContinuations(isActiveConversation && viewingRuntimeNode ? this.continuations : viewedNode?.continuations || persisted?.continuations || memory?.continuations);
347242
347341
  return {
347243
347342
  conversationId: clean,
347244
- conversations: this.listConversationStates(),
347343
+ conversations: this.listWorkspaceConversationStates(ws),
347245
347344
  conversationPlan: this.normalizeConversationPlan(isActiveConversation ? this.conversationPlan : persisted?.plan || memory?.plan),
347246
347345
  linkedPlan: this.normalizeLinkedPlan(isActiveConversation ? this.linkedPlan : persisted?.linkedPlan || memory?.linkedPlan),
347247
347346
  subagents: this.recordsForState(isActiveConversation ? this.subagents.serialize() : persisted?.subagentState || memory?.subagentState),
@@ -347251,9 +347350,9 @@ Review this persisted peer result and summarize or continue the parent task as n
347251
347350
  historyMessages: history.length,
347252
347351
  workRuns,
347253
347352
  continuations,
347254
- modelSelection: isActiveConversation ? this.currentConversationModelSelection() : persisted?.modelSelection || memory?.modelSelection || this.currentConversationModelSelection(),
347353
+ modelSelection: isActiveConversation ? this.currentConversationModelSelection() : persisted?.modelSelection || memory?.modelSelection || { kind: "auto" },
347255
347354
  flowSelection: isActiveConversation ? this.currentConversationFlowSelection() : persisted?.flowSelection || memory?.flowSelection || null,
347256
- inputMode: this.inputMode,
347355
+ inputMode: isActiveConversation ? this.inputMode : persisted?.inputMode || memory?.inputMode || "guide",
347257
347356
  mode: isActiveConversation ? this.mode : persisted?.mode || memory?.mode || "build",
347258
347357
  goal: isActiveConversation ? this.serializeGoal() : persisted?.goal || memory?.goal || null,
347259
347358
  branches: this.branchGroupMetadata(tree),
@@ -347540,12 +347639,16 @@ Review this persisted peer result and summarize or continue the parent task as n
347540
347639
  if (clean === this.safeConversationId(this.activeConversationId)) this.setConversationFromStorage(clean);
347541
347640
  return this.getConversationSnapshot(clean);
347542
347641
  }
347543
- setConversationPinned(id, pinned) {
347642
+ setConversationPinned(id, pinned, ws = this.workspace.current) {
347643
+ const targetWs = ws || this.workspace.current;
347644
+ if (!targetWs) return false;
347544
347645
  const clean = this.safeConversationId(id || "default");
347545
- this.saveWorkspaceConversationState();
347546
- const stateKey2 = this.workspaceConversationStateKey(clean);
347646
+ const currentWs = this.workspace.current;
347647
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347648
+ if (isCurrent) this.saveWorkspaceConversationState();
347649
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, targetWs);
347547
347650
  if (!stateKey2) return false;
347548
- const stored = this.readStoredConversationState();
347651
+ const stored = this.readStoredConversationState(targetWs);
347549
347652
  stored.conversations = stored.conversations || {};
347550
347653
  const existing = stored.conversations[stateKey2];
347551
347654
  if (!existing) return false;
@@ -347554,23 +347657,65 @@ Review this persisted peer result and summarize or continue the parent task as n
347554
347657
  const siblingOrders = Object.entries(stored.conversations).filter(([key3, value]) => key3 !== stateKey2 && !!value.pinned === existing.pinned && Number.isFinite(value.order)).map(([, value]) => Number(value.order));
347555
347658
  existing.order = siblingOrders.length ? Math.min(...siblingOrders) - 1 : 0;
347556
347659
  existing.updatedAt = existing.updatedAt || (/* @__PURE__ */ new Date()).toISOString();
347557
- this.writeStoredConversationState(stored);
347660
+ this.writeStoredConversationState(stored, targetWs);
347558
347661
  return true;
347559
347662
  }
347560
- renameConversation(id, title) {
347663
+ renameConversation(id, title, ws = this.workspace.current) {
347664
+ const targetWs = ws || this.workspace.current;
347665
+ if (!targetWs) return false;
347561
347666
  const clean = this.safeConversationId(id || "default");
347562
347667
  const nextTitle = String(title || "").replace(/\s+/g, " ").trim().slice(0, 80);
347563
347668
  if (!nextTitle) return false;
347564
- this.saveWorkspaceConversationState();
347565
- const stateKey2 = this.workspaceConversationStateKey(clean);
347669
+ const currentWs = this.workspace.current;
347670
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347671
+ if (isCurrent) this.saveWorkspaceConversationState();
347672
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, targetWs);
347566
347673
  if (!stateKey2) return false;
347567
- const stored = this.readStoredConversationState();
347674
+ const stored = this.readStoredConversationState(targetWs);
347568
347675
  const existing = stored.conversations?.[stateKey2];
347569
347676
  if (!existing) return false;
347570
347677
  existing.title = nextTitle;
347571
- this.writeStoredConversationState(stored);
347678
+ this.writeStoredConversationState(stored, targetWs);
347572
347679
  return true;
347573
347680
  }
347681
+ /** 为指定工作区创建空白对话,不临时切换全局前台工作区。 */
347682
+ createConversationInWorkspace(ws, title = "") {
347683
+ const existing = this.listWorkspaceConversationStates(ws);
347684
+ const id = this.safeConversationId(`conv-${Date.now()}-${crypto14.randomUUID().slice(0, 8)}`);
347685
+ const resolvedTitle = String(title || "").replace(/\s+/g, " ").trim().slice(0, 80) || `New chat ${existing.length + 1}`;
347686
+ const stateKey2 = this.workspaceConversationStateKeyFor(id, ws);
347687
+ if (!stateKey2) throw new Error("Conversation workspace is unavailable.");
347688
+ const unpinnedOrders = existing.filter((item) => !item.pinned).map((item) => Number(item.order || 0));
347689
+ const order = unpinnedOrders.length ? Math.min(...unpinnedOrders) - 1 : 0;
347690
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
347691
+ this.mutateStoredConversationState(ws, (latest) => {
347692
+ latest.version = 3;
347693
+ latest.activeConversationId = id;
347694
+ latest.conversations = latest.conversations || {};
347695
+ latest.conversations[stateKey2] = {
347696
+ title: resolvedTitle,
347697
+ chatMessages: [],
347698
+ history: [],
347699
+ plan: { items: [] },
347700
+ linkedPlan: { markdown: "", revision: 0 },
347701
+ workRuns: [],
347702
+ continuations: [],
347703
+ inputMode: this.defaultInputMode(),
347704
+ mode: "build",
347705
+ updatedAt: now2,
347706
+ pinned: false,
347707
+ pinnedAt: "",
347708
+ order,
347709
+ branchCommunication: false
347710
+ };
347711
+ return latest;
347712
+ });
347713
+ const currentWs = this.workspace.current;
347714
+ if (currentWs && path28.resolve(currentWs.path) === path28.resolve(ws.path)) {
347715
+ this.setConversationFromStorage(id);
347716
+ }
347717
+ return { id, title: resolvedTitle };
347718
+ }
347574
347719
  /**
347575
347720
  * 首 Build 命名判定:仅当 (1) 当前对话尚无历史 Build(排除当前 run)且
347576
347721
  * (2) 其持久化 title 仍是自动生成(含为空)时返回 true。dev-0.4.3 起不再
@@ -347681,6 +347826,34 @@ Conversation title (a few words):`;
347681
347826
  this.writeStoredConversationState(stored);
347682
347827
  return true;
347683
347828
  }
347829
+ /** Reorder one pinned group inside an explicit workspace without changing membership. */
347830
+ reorderWorkspaceConversationGroup(ids, ws) {
347831
+ const targetWs = ws || this.workspace.current;
347832
+ if (!targetWs || !Array.isArray(ids) || ids.length < 2) return false;
347833
+ const normalized = ids.map((id) => this.safeConversationId(String(id || "")));
347834
+ if (normalized.some((id) => !id) || new Set(normalized).size !== normalized.length) return false;
347835
+ const currentWs = this.workspace.current;
347836
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347837
+ if (isCurrent) this.saveWorkspaceConversationState();
347838
+ let accepted = false;
347839
+ this.mutateStoredConversationState(targetWs, (latest) => {
347840
+ const prefix = this.workspaceConversationPrefixFor(targetWs) || "";
347841
+ const entries = Object.entries(latest.conversations || {}).filter(([key3]) => !prefix || key3.startsWith(prefix));
347842
+ const entryById = new Map(entries.map(([key3, value]) => [key3.slice(prefix.length + 1) || key3, value]));
347843
+ const requested = normalized.map((id) => entryById.get(id));
347844
+ if (requested.some((entry) => !entry)) return latest;
347845
+ if (new Set(requested.map((entry) => !!entry.pinned)).size !== 1) return latest;
347846
+ const orderSlots = requested.map((entry) => Number(entry.order));
347847
+ if (orderSlots.some((order) => !Number.isFinite(order))) return latest;
347848
+ orderSlots.sort((a3, b2) => a3 - b2);
347849
+ normalized.forEach((id, index) => {
347850
+ entryById.get(id).order = orderSlots[index];
347851
+ });
347852
+ accepted = true;
347853
+ return latest;
347854
+ });
347855
+ return accepted;
347856
+ }
347684
347857
  flushConversationState() {
347685
347858
  this.saveWorkspaceConversationState();
347686
347859
  }
@@ -349629,21 +349802,21 @@ ${summary}`, segment, "local-summarize", true);
349629
349802
  this.saveWorkspaceConversationState(true);
349630
349803
  return { text, hiddenUserInput: true, goalContinuation: true };
349631
349804
  }
349632
- buildSessionArchive(messages, mode, model, archiveDir) {
349805
+ buildSessionArchive(messages, context, archiveDir) {
349633
349806
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").replace("T", "_").replace("Z", "");
349634
349807
  const filename = `session_${stamp}_${crypto14.randomUUID().slice(0, 8)}.md`;
349635
349808
  let markdown = `# Newmark Session \u2014 ${stamp}
349636
349809
 
349637
349810
  `;
349638
- markdown += `**Mode**: ${mode}
349639
- **Model**: ${model}
349811
+ markdown += `**Mode**: ${context.mode}
349812
+ **Model**: ${context.model}
349640
349813
  `;
349641
349814
  markdown += `**Messages**: ${messages.length}
349642
349815
 
349643
349816
  ---
349644
349817
 
349645
349818
  `;
349646
- if (this.goal) markdown += `**Goal**: ${this.goal.objective}
349819
+ if (context.goal?.objective) markdown += `**Goal**: ${context.goal.objective}
349647
349820
 
349648
349821
  `;
349649
349822
  for (const msg of messages) {
@@ -349666,12 +349839,12 @@ ${msg.content}
349666
349839
  writeSessionArchive(messages, mode, model) {
349667
349840
  const archiveDir = this.archiveDir();
349668
349841
  fs25.mkdirSync(archiveDir, { recursive: true });
349669
- const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
349842
+ const archive = this.buildSessionArchive(messages, { mode, model, goal: this.serializeGoal() }, archiveDir);
349670
349843
  fs25.writeFileSync(path28.join(archiveDir, archive.filename), archive.markdown, "utf-8");
349671
349844
  return archive.filename;
349672
349845
  }
349673
- async writeSessionArchiveAsync(messages, mode, model, archiveDir = this.archiveDir()) {
349674
- const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
349846
+ async writeSessionArchiveAsync(messages, context, archiveDir = this.archiveDir()) {
349847
+ const archive = this.buildSessionArchive(messages, context, archiveDir);
349675
349848
  await fs25.promises.mkdir(archiveDir, { recursive: true });
349676
349849
  const outPath = path28.join(archiveDir, archive.filename);
349677
349850
  const tempPath = `${outPath}.${process.pid}.${crypto14.randomUUID()}.tmp`;
@@ -349758,30 +349931,36 @@ ${msg.content}
349758
349931
  * large markdown payload and manifest use promise-based filesystem I/O so
349759
349932
  * independent workspaces can archive in parallel without freezing Electron.
349760
349933
  */
349761
- async archiveConversationAsync(conversationId) {
349762
- return await this.archiveConversationAsyncUnlocked(conversationId);
349934
+ async archiveConversationAsync(conversationId, ws = this.workspace.current) {
349935
+ return await this.archiveConversationAsyncUnlocked(conversationId, ws);
349763
349936
  }
349764
- async archiveConversationAsyncUnlocked(conversationId) {
349765
- const ws = this.workspace.current;
349937
+ async archiveConversationAsyncUnlocked(conversationId, targetWorkspace = this.workspace.current) {
349938
+ const ws = targetWorkspace || this.workspace.current;
349766
349939
  if (!ws) return null;
349767
349940
  const clean = this.safeConversationId(conversationId || "default");
349768
- const stateKey2 = this.workspaceConversationStateKey(clean);
349941
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, ws);
349769
349942
  if (!stateKey2) return null;
349770
349943
  const memoryKey = `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
349771
349944
  const archiveDir = path28.join(ws.path, "archive");
349772
- const workspacePrefix = this.workspaceConversationPrefix() || "";
349773
- const archiveMode = this.modeName();
349774
- const archiveModel = this.model;
349945
+ const workspacePrefix = this.workspaceConversationPrefixFor(ws) || "";
349775
349946
  const cachedStored = this.readStoredConversationState(ws);
349776
349947
  const stored = JSON.parse(JSON.stringify(cachedStored || {}));
349777
349948
  const persisted = stored.conversations?.[stateKey2];
349778
349949
  if (persisted) this.normalizeConversationTree(persisted);
349779
349950
  const memory = this.workspaceConversations.get(memoryKey);
349951
+ const archiveMode = persisted?.mode || memory?.mode || "build";
349952
+ const archiveSelection = persisted?.modelSelection || memory?.modelSelection;
349953
+ const archiveModel = archiveSelection?.kind === "deployment" ? archiveSelection.modelId : archiveSelection?.kind === "auto" ? "auto" : "auto";
349954
+ const archiveGoal = persisted?.goal || memory?.goal || null;
349780
349955
  const persistedMessagesAvailable = persisted?.chatMessages !== void 0;
349781
349956
  const sourceMessages = persisted?.chatMessages ?? memory?.chatMessages ?? [];
349782
349957
  const sourceHistory = persistedMessagesAvailable ? persisted?.history ?? [] : memory?.history ?? persisted?.history ?? [];
349783
349958
  const messages = this.normalizeConversationChatMessages(sourceMessages, sourceHistory);
349784
- const filename = await this.writeSessionArchiveAsync(messages, archiveMode, archiveModel, archiveDir);
349959
+ const filename = await this.writeSessionArchiveAsync(messages, {
349960
+ mode: archiveMode,
349961
+ model: archiveModel,
349962
+ goal: archiveGoal
349963
+ }, archiveDir);
349785
349964
  const archiveEntry = persisted ? JSON.parse(JSON.stringify(persisted)) : {
349786
349965
  title: this.titleFromMessages(messages, clean),
349787
349966
  chatMessages: messages,
@@ -349834,7 +350013,9 @@ ${msg.content}
349834
350013
  this.workspaceConversations.delete(memoryKey);
349835
350014
  const duplicateMemoryKey = `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
349836
350015
  this.workspaceConversations.delete(duplicateMemoryKey);
349837
- if (clean === this.safeConversationId(this.activeConversationId || "default")) {
350016
+ const currentWs = this.workspace.current;
350017
+ const isCurrentWorkspace = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(ws.path);
350018
+ if (isCurrentWorkspace && clean === this.safeConversationId(this.activeConversationId || "default")) {
349838
350019
  this.activeConversationId = nextActiveId || "default";
349839
350020
  this.loadWorkspaceConversationState();
349840
350021
  }
@@ -351079,7 +351260,7 @@ ${persisted.map((guide, index) => `Guide ${index + 1}: ${guide.text}`).join("\n"
351079
351260
  return `${accepted.output}
351080
351261
  ${settled?.result || settled?.error || ""}`.trim();
351081
351262
  }
351082
- async handleSubagentEnvelope(args) {
351263
+ async handleSubagentEnvelope(args, requireRunningBuild = false) {
351083
351264
  try {
351084
351265
  const params = JSON.parse(args);
351085
351266
  const preset = this.resolveSubagentPreset(params);
@@ -351094,6 +351275,36 @@ ${settled?.result || settled?.error || ""}`.trim();
351094
351275
  const requestedModel = this.normalizeSubagentModelSelection(params.model || preset?.model || inheritedModel);
351095
351276
  const activeDeployment = this.activeDeployment();
351096
351277
  const peerModel = requestedModel !== "auto" && !parseDeploymentSelectionValue2(requestedModel) && activeDeployment && requestedModel === activeDeployment.modelId ? `deployment:${encodeURIComponent(activeDeployment.providerId)}:${encodeURIComponent(activeDeployment.modelId)}` : requestedModel;
351278
+ const buildRunId = this.currentWorkRunId();
351279
+ const runningBuild = buildRunId ? this.workRuns.find((run) => run.runId === buildRunId && run.status === "running") : void 0;
351280
+ const limit = this.subagentConcurrencyLimit();
351281
+ if (requireRunningBuild && !runningBuild) {
351282
+ return {
351283
+ ok: false,
351284
+ output: "[SubAgent terminated] No running Build Block owns this call; no SubAgent was created.",
351285
+ error: "SubAgent tool calls require a running Build Block.",
351286
+ metadata: { kind: "subagent", buildRunId: buildRunId || "", limit, terminated: true }
351287
+ };
351288
+ }
351289
+ if (buildRunId && !runningBuild) {
351290
+ return {
351291
+ ok: false,
351292
+ output: `[SubAgent terminated] Build Block ${buildRunId} is not running; no SubAgent was created.`,
351293
+ error: "SubAgent creation requires a running Build Block.",
351294
+ metadata: { kind: "subagent", buildRunId, limit, terminated: true }
351295
+ };
351296
+ }
351297
+ if (runningBuild) {
351298
+ const activeForBuild = this.subagents.activeCountForBuild(buildRunId);
351299
+ if (activeForBuild >= limit) {
351300
+ return {
351301
+ ok: false,
351302
+ output: `[SubAgent terminated] Build Block ${buildRunId} reached the ${this.intelligence === "ultra" ? "Ultra" : "non-Ultra"} hard limit (${limit}); no SubAgent was created or queued.`,
351303
+ error: `SubAgent hard limit reached for Build Block ${buildRunId}: ${activeForBuild}/${limit}.`,
351304
+ metadata: { kind: "subagent", buildRunId, intelligence: this.intelligence, activeForBuild, limit, terminated: true }
351305
+ };
351306
+ }
351307
+ }
351097
351308
  const id = this.subagents.create(
351098
351309
  name50,
351099
351310
  prompt,
@@ -351103,7 +351314,9 @@ ${settled?.result || settled?.error || ""}`.trim();
351103
351314
  this.runtimeActorId,
351104
351315
  peerFlow,
351105
351316
  peerGoal,
351106
- Number(params.flow_pc ?? params.flowPc ?? (peerMode === "flow" ? this.flowPc : 0))
351317
+ Number(params.flow_pc ?? params.flowPc ?? (peerMode === "flow" ? this.flowPc : 0)),
351318
+ runningBuild?.runId || "",
351319
+ this.intelligence
351107
351320
  );
351108
351321
  const sa = this.subagents.get(id);
351109
351322
  if (sa && preset) {
@@ -351842,6 +352055,13 @@ ${items.map((item) => this.formatAutomation(item)).join("\n")}`;
351842
352055
  }
351843
352056
  latestSubmittedImages(attachmentId = "") {
351844
352057
  const normalizedId = String(attachmentId || "").trim();
352058
+ const captured = hydrateConversationImageAttachments(this.rootPath, this.latestCapturedImageInputs).flatMap((attachment) => attachment.dataUrl ? [{ id: attachment.id, dataUrl: attachment.dataUrl }] : []);
352059
+ if (normalizedId) {
352060
+ const capturedMatch = captured.find((item) => item.id === normalizedId);
352061
+ if (capturedMatch) return [capturedMatch];
352062
+ } else if (captured.length) {
352063
+ return captured;
352064
+ }
351845
352065
  for (let index = this.chatMessages.length - 1; index >= 0; index -= 1) {
351846
352066
  const message = this.chatMessages[index];
351847
352067
  if (message?.role !== "user") continue;
@@ -352411,7 +352631,7 @@ ${custom}`);
352411
352631
  if (this.intelligence === "ultra") {
352412
352632
  parts.push([
352413
352633
  "[Ultra Intelligence \u2013 Orchestrator Role]",
352414
- "You are the lead orchestrator. Actively decompose complex tasks into parallel sub-tasks. Use the `task` tool to create specialized SubAgents for each distinct sub-task. Coordinate the SubAgent team: assign clear responsibilities, merge results, resolve conflicts, and produce a unified final output. SubAgents are your team; delegate aggressively and manage them as a manager, not just a tool caller.",
352634
+ "You are the lead orchestrator. Actively decompose complex tasks into parallel sub-tasks. Use the `SubAgent` tool to create specialized SubAgents for each distinct sub-task. Use `task_create` only for the conversation checklist; it never creates a SubAgent. Coordinate the SubAgent team: assign clear responsibilities, merge results, resolve conflicts, and produce a unified final output. SubAgents are your team; delegate aggressively and manage them as a manager, not just a tool caller.",
352415
352635
  "Do not attempt to do all the work yourself. Use SubAgents for parallel investigation, verification, implementation, and review."
352416
352636
  ].join("\n"));
352417
352637
  }
@@ -352517,7 +352737,7 @@ ${custom}`);
352517
352737
  "- Build history disclosure is two-layered. The request prompt contains only each historical Build Block user input, final summary, and completion status. When the current task continues, fixes, verifies, or depends on earlier Build Blocks, proactively call build_history_query to read the concrete tool activity and results of the relevant block, and reuse that information instead of re-investigating (re-running commands or re-reading files) from scratch. Querying history is read-only and never authorizes resuming that work; do not query merely to answer completion status already shown in the prompt.",
352518
352738
  "- Linked plan disclosure: a durable conversation-linked Markdown plan exists and can be inspected or updated with linked_plan when explicitly needed or required by Plan mode. Its full Markdown and revision are not injected into every model request.",
352519
352739
  "- A memory_lab_update, memory_lab_delete, or memory_lab_reindex call is unfinished until its awaited tool result contains rebuildReceipt.completed=true. The completion receipt is represented by the tool activity inside the current Build block and should not be repeated as a separate completion message.",
352520
- `- Skills and subagents: skill searches enabled metadata and loads one SKILL.md body on demand; skill_download installs offline skill folders; task creates constrained subagents tracked in agent state.`,
352740
+ `- Skills and subagents: skill searches enabled metadata and loads one SKILL.md body on demand; skill_download installs offline skill folders; SubAgent creates constrained peer agents tracked in agent state. task_create is only for the conversation checklist and never creates a SubAgent.`,
352521
352741
  `- Visible output contract: assistant replies are sanitized before display to remove hidden-reasoning markers. ${visibleOutputContract}`,
352522
352742
  "- During Build work, before the first tool call and between materially different tool phases, emit a concise public progress explanation of what you are checking or changing and why. This is visible commentary, not hidden chain-of-thought. Do not wait until the final answer to explain the work."
352523
352743
  ].join("\n");
@@ -353026,6 +353246,155 @@ var ConversationKernel = class {
353026
353246
  followUp: queued?.followUp.slice() || []
353027
353247
  };
353028
353248
  }
353249
+ queueItems(target) {
353250
+ const runtime = this.findRuntime(target);
353251
+ if (!runtime) return [];
353252
+ return runtime.pendingNextTurn.flatMap((item) => {
353253
+ if (item.queueMode !== "followUp") return [];
353254
+ if (typeof item.message === "string") return [];
353255
+ const id = String(item.message.clientMessageId || "");
353256
+ if (!id) return [];
353257
+ return [{
353258
+ id,
353259
+ text: String(item.message.visibleUserInput || item.message.text || "").replace(/^\[Next queued while current turn is running\]\n/, ""),
353260
+ queueMode: item.queueMode,
353261
+ requestedMode: item.message.visibleMode,
353262
+ goalObjective: item.message.goalObjective,
353263
+ runId: item.message.runId,
353264
+ createdAt: String(item.message.createdAt || "")
353265
+ }];
353266
+ });
353267
+ }
353268
+ enqueueNext(target, input2) {
353269
+ const runtime = this.findRuntime(target);
353270
+ if (!runtime?.activePromise || !runtime.runId) throw new Error("Target conversation is not running");
353271
+ const id = String(input2.id || "").trim().slice(0, 200);
353272
+ const text = String(input2.text || "").trim();
353273
+ if (!id || !text) throw new Error("Queue item id and text are required");
353274
+ const existing = this.queueItems(runtime.target).find((item) => item.id === id);
353275
+ if (existing) return existing;
353276
+ const prompt = `[Next queued while current turn is running]
353277
+ ${text}`;
353278
+ const createdAt = String(input2.createdAt || (/* @__PURE__ */ new Date()).toISOString());
353279
+ runtime.pendingNextTurn.push({
353280
+ message: {
353281
+ text: prompt,
353282
+ visibleUserInput: text,
353283
+ visibleMode: String(input2.requestedMode || "build"),
353284
+ goalObjective: String(input2.goalObjective || "") || void 0,
353285
+ clientMessageId: id,
353286
+ runId: runtime.runId,
353287
+ createdAt
353288
+ },
353289
+ queueMode: "followUp"
353290
+ });
353291
+ runtime.runner.retainConversationContinuations([{
353292
+ content: prompt,
353293
+ queueMode: "followUp",
353294
+ clientMessageId: id,
353295
+ runId: runtime.runId,
353296
+ createdAt
353297
+ }]);
353298
+ this.trackQueuedMessage(runtime, prompt, "followUp");
353299
+ this.emitQueueUpdate(runtime);
353300
+ return this.queueItems(runtime.target).find((item) => item.id === id);
353301
+ }
353302
+ updateQueueItem(target, idInput, textInput) {
353303
+ const runtime = this.findRuntime(target);
353304
+ if (!runtime) throw new Error("Target conversation runtime is unavailable");
353305
+ const id = String(idInput || "").trim();
353306
+ const text = String(textInput || "").trim();
353307
+ if (!id || !text) throw new Error("Queue item id and text are required");
353308
+ const pending3 = runtime.pendingNextTurn.find((item) => typeof item.message !== "string" && item.message.clientMessageId === id);
353309
+ if (!pending3 || typeof pending3.message === "string") throw new Error("Queue item is no longer editable");
353310
+ const oldPrompt = pending3.message.text;
353311
+ pending3.message.text = `[Next queued while current turn is running]
353312
+ ${text}`;
353313
+ pending3.message.visibleUserInput = text;
353314
+ runtime.runner.consumeConversationContinuation({ content: oldPrompt, queueMode: pending3.queueMode, clientMessageId: id });
353315
+ runtime.runner.retainConversationContinuations([{
353316
+ content: pending3.message.text,
353317
+ queueMode: pending3.queueMode,
353318
+ clientMessageId: id,
353319
+ runId: pending3.message.runId
353320
+ }]);
353321
+ this.replaceTrackedQueuedMessage(runtime, oldPrompt, pending3.message.text, pending3.queueMode);
353322
+ this.emitQueueUpdate(runtime);
353323
+ return this.queueItems(runtime.target).find((item) => item.id === id);
353324
+ }
353325
+ deleteQueueItem(target, idInput) {
353326
+ const runtime = this.findRuntime(target);
353327
+ if (!runtime) return false;
353328
+ const id = String(idInput || "").trim();
353329
+ const index = runtime.pendingNextTurn.findIndex((item) => typeof item.message !== "string" && item.message.clientMessageId === id);
353330
+ if (index < 0) return false;
353331
+ const [removed] = runtime.pendingNextTurn.splice(index, 1);
353332
+ if (typeof removed.message !== "string") {
353333
+ runtime.runner.consumeConversationContinuation({ content: removed.message.text, queueMode: removed.queueMode, clientMessageId: id });
353334
+ this.consumeQueuedMessage(runtime, removed.message.text);
353335
+ }
353336
+ this.emitQueueUpdate(runtime);
353337
+ return true;
353338
+ }
353339
+ setQueuePaused(target, paused) {
353340
+ const runtime = this.findRuntime(target);
353341
+ if (!runtime) throw new Error("Target conversation runtime is unavailable");
353342
+ runtime.queuePaused = paused;
353343
+ this.emitQueueUpdate(runtime);
353344
+ if (!paused && !runtime.activePromise && runtime.pendingNextTurn.length) {
353345
+ this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
353346
+ }
353347
+ return runtime.queuePaused;
353348
+ }
353349
+ queueAction(target, action, input2 = {}) {
353350
+ const runtime = this.findRuntime(target);
353351
+ if (!runtime) throw new Error("Target conversation runtime is unavailable");
353352
+ let receipt;
353353
+ if (action === "enqueue") {
353354
+ this.enqueueNext(runtime.target, {
353355
+ id: String(input2.id || ""),
353356
+ text: String(input2.text || ""),
353357
+ requestedMode: input2.requestedMode,
353358
+ goalObjective: input2.goalObjective,
353359
+ createdAt: input2.createdAt
353360
+ });
353361
+ } else if (action === "update") {
353362
+ this.updateQueueItem(runtime.target, String(input2.id || ""), String(input2.text || ""));
353363
+ } else if (action === "delete") {
353364
+ if (!this.deleteQueueItem(runtime.target, String(input2.id || ""))) throw new Error("Queue item was not found");
353365
+ } else if (action === "toggle_pause") {
353366
+ this.setQueuePaused(runtime.target, !runtime.queuePaused);
353367
+ } else if (action === "guide") {
353368
+ const item = this.queueItems(runtime.target).find((entry) => entry.id === String(input2.id || ""));
353369
+ if (!item) throw new Error("Queue item was not found");
353370
+ receipt = this.enqueueGuide({
353371
+ clientMessageId: item.id,
353372
+ guideId: (0, import_crypto16.randomUUID)(),
353373
+ target: runtime.target,
353374
+ runId: runtime.runId,
353375
+ deliveryMode: "steer",
353376
+ text: item.goalObjective ? `Goal for the current Build:
353377
+ ${item.goalObjective}` : item.text,
353378
+ goalObjective: item.goalObjective,
353379
+ createdAt: item.createdAt || (/* @__PURE__ */ new Date()).toISOString()
353380
+ });
353381
+ if (receipt.status === "rejected") return {
353382
+ ok: false,
353383
+ queueItems: this.queueItems(runtime.target),
353384
+ queuePaused: runtime.queuePaused,
353385
+ queued: this.queued(runtime.target),
353386
+ receipt
353387
+ };
353388
+ this.deleteQueueItem(runtime.target, item.id);
353389
+ }
353390
+ return {
353391
+ ok: true,
353392
+ queueItems: this.queueItems(runtime.target),
353393
+ queuePaused: runtime.queuePaused,
353394
+ queued: this.queued(runtime.target),
353395
+ receipt
353396
+ };
353397
+ }
353029
353398
  events(target) {
353030
353399
  return this.findRuntime(target)?.events.slice() || [];
353031
353400
  }
@@ -353054,6 +353423,8 @@ var ConversationKernel = class {
353054
353423
  workRuns: this.bindWorkRunsToRuntimeTarget(conversationSnapshot.workRuns, normalized),
353055
353424
  target: normalized,
353056
353425
  queued: this.queued(normalized),
353426
+ queueItems: this.queueItems(normalized),
353427
+ queuePaused: runtime?.queuePaused === true,
353057
353428
  workEvents: this.events(normalized),
353058
353429
  runtime: this.runtimeState(normalized),
353059
353430
  mode: runner.mode,
@@ -353489,7 +353860,7 @@ var ConversationKernel = class {
353489
353860
  if (runtime.stopRequestedRunId === runId) {
353490
353861
  stopped = true;
353491
353862
  this.settleCooperativeStop(runtime, runId);
353492
- } else if (runtime.pendingNextTurn.length > 0) {
353863
+ } else if (!runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
353493
353864
  this.schedulePendingRuntimeContinuation(runtime, runId);
353494
353865
  }
353495
353866
  }
@@ -353524,7 +353895,7 @@ var ConversationKernel = class {
353524
353895
  return this.result(runtime, lastTokens);
353525
353896
  }
353526
353897
  for (; ; ) {
353527
- while (runtime.pendingNextTurn.length > 0) {
353898
+ while (!runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
353528
353899
  if (runtime.stopRequestedRunId === runtime.runId) return this.result(runtime, lastTokens);
353529
353900
  const next = runtime.pendingNextTurn.shift();
353530
353901
  if (next.queueMode === "steer" && typeof next.message !== "string" && !!next.message.clientMessageId) {
@@ -353566,11 +353937,11 @@ ${batchText}`,
353566
353937
  this.mirrorHostIfTargetActive(runtime);
353567
353938
  return this.result(runtime, lastTokens);
353568
353939
  }
353569
- this.clearQueued(runtime);
353940
+ if (!runtime.pendingNextTurn.length) this.clearQueued(runtime);
353570
353941
  const completedRunId = runtime.runId;
353571
353942
  runtime.runner.finishConversationWorkRun(completedRunId, "completed");
353572
353943
  await new Promise((resolve16) => setImmediate(resolve16));
353573
- if (runtime.runId === completedRunId && runtime.stopRequestedRunId !== completedRunId && runtime.pendingNextTurn.length > 0) {
353944
+ if (runtime.runId === completedRunId && runtime.stopRequestedRunId !== completedRunId && !runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
353574
353945
  runtime.guideAcceptanceClosedRunId = "";
353575
353946
  if (!runtime.runner.resumeConversationWorkRun(completedRunId)) {
353576
353947
  throw new Error(`Unable to resume deferred Guide work run ${completedRunId}`);
@@ -353661,6 +354032,7 @@ Review this persisted peer result and summarize or continue the parent task as n
353661
354032
  events: [],
353662
354033
  pendingNextTurn: [],
353663
354034
  queued: { steering: [], followUp: [] },
354035
+ queuePaused: false,
353664
354036
  runId: "",
353665
354037
  generation: this.generations.get(target.runtimeKey) || 0,
353666
354038
  stopRequestedRunId: "",
@@ -353756,7 +354128,7 @@ Review this persisted peer result and summarize or continue the parent task as n
353756
354128
  }
353757
354129
  setImmediate(() => {
353758
354130
  runtime.pendingContinuationRunId = void 0;
353759
- if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId) return;
354131
+ if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId || runtime.queuePaused) return;
353760
354132
  const next = runtime.pendingNextTurn.shift();
353761
354133
  if (!next) return;
353762
354134
  const message = typeof next.message === "string" ? { text: next.message, runId } : { ...next.message, runId: next.message.runId || runId };
@@ -353907,7 +354279,36 @@ Review this persisted peer result and summarize or continue the parent task as n
353907
354279
  const text = typeof message === "string" ? message : message.text;
353908
354280
  const prompt = isSteer ? text : `[Next queued while current turn is running]
353909
354281
  ${text}`;
353910
- if (!isSteer) this.trackQueuedMessage(runtime, prompt, queueMode);
354282
+ if (!isSteer) {
354283
+ const structured = typeof message === "string" ? null : message;
354284
+ const clientMessageId = String(structured?.clientMessageId || (0, import_crypto16.randomUUID)());
354285
+ const queuedMessage = {
354286
+ ...structured || {},
354287
+ text: prompt,
354288
+ visibleUserInput: structured?.visibleUserInput || text,
354289
+ visibleMode: structured?.visibleMode || runtime.options.mode,
354290
+ clientMessageId,
354291
+ runId: structured?.runId || runtime.runId,
354292
+ createdAt: String(structured?.createdAt || (/* @__PURE__ */ new Date()).toISOString())
354293
+ };
354294
+ if (!runtime.pendingNextTurn.some((item) => typeof item.message !== "string" && item.message.clientMessageId === clientMessageId)) {
354295
+ runtime.pendingNextTurn.push({ message: queuedMessage, queueMode: "followUp" });
354296
+ }
354297
+ runtime.runner.retainConversationContinuations([{
354298
+ content: prompt,
354299
+ queueMode: "followUp",
354300
+ clientMessageId,
354301
+ runId: queuedMessage.runId,
354302
+ images: queuedMessage.images?.map((image) => ({ ...image })),
354303
+ attachments: queuedMessage.attachments?.map((attachment) => ({ ...attachment })),
354304
+ createdAt: queuedMessage.createdAt
354305
+ }]);
354306
+ this.trackQueuedMessage(runtime, prompt, "followUp");
354307
+ runtime.runner.recordWorkStatus(runtime.stopRequestedRunId === runtime.runId ? "Next message retained while stopping." : "Next message queued.");
354308
+ if (runtime.stopRequestedRunId === runtime.runId) runtime.forceStopArmedRunId = "";
354309
+ this.emitQueueUpdate(runtime);
354310
+ return;
354311
+ }
353911
354312
  if (runtime.stopRequestedRunId === runtime.runId) {
353912
354313
  runtime.forceStopArmedRunId = "";
353913
354314
  const structured = typeof message === "string" ? void 0 : message;
@@ -353953,6 +354354,12 @@ ${text}`;
353953
354354
  }
353954
354355
  if (changed) this.emitQueueUpdate(runtime);
353955
354356
  }
354357
+ replaceTrackedQueuedMessage(runtime, oldMessage, nextMessage, queueMode) {
354358
+ this.queueState(runtime);
354359
+ const list = queueMode === "steer" ? runtime.queued.steering : runtime.queued.followUp;
354360
+ const index = list.indexOf(oldMessage);
354361
+ if (index >= 0) list[index] = nextMessage;
354362
+ }
353956
354363
  emitQueueUpdate(runtime) {
353957
354364
  this.queueState(runtime);
353958
354365
  runtime.runner.emitWorkEvent({
@@ -354074,9 +354481,10 @@ ${text}`;
354074
354481
  const runId = runtime.runId;
354075
354482
  await Promise.resolve();
354076
354483
  if (runtime.runId !== runId || runtime.stopRequestedRunId === runId) return true;
354077
- if (runtime.pendingNextTurn.length > 0 || runtime.runner.subagents.readRootInbox().length > 0) return false;
354484
+ const hasSteering = runtime.pendingNextTurn.some((item) => item.queueMode === "steer");
354485
+ if (hasSteering || runtime.runner.subagents.readRootInbox().length > 0) return false;
354078
354486
  runtime.guideAcceptanceClosedRunId = runId;
354079
- if (runtime.pendingNextTurn.length > 0 || runtime.runner.subagents.readRootInbox().length > 0) {
354487
+ if (runtime.pendingNextTurn.some((item) => item.queueMode === "steer") || runtime.runner.subagents.readRootInbox().length > 0) {
354080
354488
  runtime.guideAcceptanceClosedRunId = "";
354081
354489
  return false;
354082
354490
  }
@@ -354287,6 +354695,9 @@ async function handle(request) {
354287
354695
  target
354288
354696
  });
354289
354697
  }
354698
+ if (request.method === "queue_action") {
354699
+ return kernel.queueAction(requestTarget(request.params), request.params.action, request.params.input);
354700
+ }
354290
354701
  if (request.method === "checkpoint") return kernel.checkpoint(requestTarget(request.params));
354291
354702
  if (request.method === "context_compress") {
354292
354703
  return kernel.compressContext(requestTarget(request.params), request.params.options);