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
@@ -327556,6 +327556,7 @@ var NATIVE_TOOL_CATALOG = [
327556
327556
  { name: "browser_reload", label: "Browser reload", description: "Reload the built-in browser.", category: "browser", defaultEnabled: true },
327557
327557
  { name: "browser_cdp", label: "Browser CDP", description: "Run an advanced Chrome DevTools Protocol command.", category: "browser", defaultEnabled: true },
327558
327558
  { 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 },
327559
+ { 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" },
327559
327560
  { name: "computer_use", label: "Computer Use", description: "Observe and control Windows desktop UI with screenshots and semantic objects.", category: "desktop", defaultEnabled: true },
327560
327561
  { 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" },
327561
327562
  { 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" },
@@ -327563,7 +327564,7 @@ var NATIVE_TOOL_CATALOG = [
327563
327564
  { 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" },
327564
327565
  { name: "terminal_takeover", label: "Terminal takeover", description: "Maintain a persistent Agent-controlled shell session.", category: "desktop", defaultEnabled: true },
327565
327566
  { name: "ssh_workspace", label: "OpenSSH workspace", description: "Manage native OpenSSH connections and link remote workspaces by PC_Hash.", category: "ssh", defaultEnabled: true },
327566
- { name: "task", label: "Subagent task", description: "Create a same-conversation peer agent.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327567
+ { name: "SubAgent", label: "SubAgent", description: "Create one real same-conversation peer SubAgent.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327567
327568
  { name: "subagent_list", label: "Subagent list", description: "List same-conversation peer agents.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327568
327569
  { 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" },
327569
327570
  { name: "subagent_send", label: "Subagent send", description: "Persist a message to a peer mailbox.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
@@ -328315,7 +328316,7 @@ function defaultConfig() {
328315
328316
  auto_archive_on_close: { _description: "Auto archive on close", _type: "boolean", value: true }
328316
328317
  },
328317
328318
  remote: {
328318
- touch_enabled: { _description: "Allow mobile devices to reach this Newmark instance over Tailscale", _type: "boolean", value: true }
328319
+ touch_enabled: { _description: "Allow mobile devices to reach this Newmark instance on the same LAN / Tailscale network", _type: "boolean", value: true }
328319
328320
  },
328320
328321
  models: {
328321
328322
  providers: { _description: "LLM providers", _type: "array", value: [] },
@@ -328863,14 +328864,18 @@ var ChatCompletionsAdapter = class {
328863
328864
  ...request.systemPrompt ? [{ role: CHAT_SYSTEM_ROLE, content: request.systemPrompt }] : [],
328864
328865
  ...openAIChatMessages(request.messages)
328865
328866
  ];
328867
+ const tools = this.serializeTools(request.tools);
328866
328868
  const body = {
328867
328869
  model: request.model,
328868
328870
  messages,
328869
328871
  temperature: request.temperature,
328870
- max_tokens: request.maxOutputTokens,
328871
- tools: this.serializeTools(request.tools),
328872
- tool_choice: "auto"
328872
+ max_tokens: request.maxOutputTokens
328873
328873
  };
328874
+ if (tools.length) {
328875
+ body.tools = tools;
328876
+ body.tool_choice = "auto";
328877
+ body.parallel_tool_calls = true;
328878
+ }
328874
328879
  if (request.reasoningEffort) body.reasoning_effort = request.reasoningEffort;
328875
328880
  if (request.sessionId) body.session_id = request.sessionId;
328876
328881
  const base2 = request.baseUrl.replace(/\/+$/, "");
@@ -329124,6 +329129,7 @@ var ResponsesAdapter = class {
329124
329129
  if (tools.length) {
329125
329130
  body.tools = tools;
329126
329131
  body.tool_choice = "auto";
329132
+ body.parallel_tool_calls = true;
329127
329133
  }
329128
329134
  const base2 = request.baseUrl.replace(/\/+$/, "");
329129
329135
  return {
@@ -335741,6 +335747,7 @@ var import_crypto6 = require("crypto");
335741
335747
  // src/core/toolPolicy.ts
335742
335748
  var REQUIRED_TOOLS = /* @__PURE__ */ new Set(["pwd", "read", "glob", "grep"]);
335743
335749
  var MODE_SCOPED_TOOLS = /* @__PURE__ */ new Set([
335750
+ "screen_capture",
335744
335751
  "image_inspect",
335745
335752
  "image_display",
335746
335753
  "ocr_read",
@@ -335757,6 +335764,10 @@ var MODE_SCOPED_TOOLS = /* @__PURE__ */ new Set([
335757
335764
  "task_read",
335758
335765
  "task_create",
335759
335766
  "question",
335767
+ "SubAgent",
335768
+ "subagent_create",
335769
+ // Legacy runtime alias. It is no longer published to models because its
335770
+ // generic name collides with the persistent task checklist.
335760
335771
  "task",
335761
335772
  "subagent_list",
335762
335773
  "subagent_read",
@@ -335769,6 +335780,7 @@ var MODE_SCOPED_TOOLS = /* @__PURE__ */ new Set([
335769
335780
  "branch_create"
335770
335781
  ]);
335771
335782
  var PLAN_READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
335783
+ "screen_capture",
335772
335784
  "task_read",
335773
335785
  "pwd",
335774
335786
  "read",
@@ -335790,6 +335802,8 @@ var PLAN_READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
335790
335802
  "skill",
335791
335803
  "linked_plan",
335792
335804
  "build_history_query",
335805
+ "SubAgent",
335806
+ "subagent_create",
335793
335807
  "task",
335794
335808
  "subagent_list",
335795
335809
  "subagent_read",
@@ -335813,7 +335827,9 @@ var CONCURRENCY_SAFE_TOOLS = /* @__PURE__ */ new Set([
335813
335827
  "web_fetch",
335814
335828
  "git_status",
335815
335829
  "file_audit",
335816
- "repo_security_audit"
335830
+ "repo_security_audit",
335831
+ "screen_capture",
335832
+ "SubAgent"
335817
335833
  ]);
335818
335834
  function isConcurrencySafeTool(name50, riskLevel) {
335819
335835
  const toolName = String(name50 || "").trim();
@@ -336896,6 +336912,13 @@ var ToolExecutor = class {
336896
336912
  max_refs: { type: "number" },
336897
336913
  attribute: { type: "string" }
336898
336914
  }, ["action"]),
336915
+ 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.", {
336916
+ target: { type: "string", enum: ["desktop", "application"], description: "Capture the whole desktop or one visible application. Defaults to desktop." },
336917
+ app_target: { type: "string", description: "For target=application: application title, process name, process id, or window handle." },
336918
+ 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." },
336919
+ 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." },
336920
+ max_chars: { type: "number", minimum: 1e3, maximum: 1e5, description: "Maximum returned semantic UI text characters." }
336921
+ }, []),
336899
336922
  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.", {
336900
336923
  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"] },
336901
336924
  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." },
@@ -336981,7 +337004,7 @@ var ToolExecutor = class {
336981
337004
  remote_root: { type: "string" },
336982
337005
  remote_path: { type: "string" }
336983
337006
  }, ["action"]),
336984
- 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"]),
337007
+ 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"]),
336985
337008
  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"] } }, []),
336986
337009
  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." } }, []),
336987
337010
  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" } }, []),
@@ -337158,7 +337181,8 @@ var ToolExecutor = class {
337158
337181
  return normalizeToolResult(output, { tool, workspacePath: wsPath, mode: context.mode || "" });
337159
337182
  }
337160
337183
  validateInvocation(tool, argsStr, _mode = "", inputSchema) {
337161
- if (inputSchema === void 0 && !isNativeToolEnabled(tool, this.config.nativeToolEnabled())) {
337184
+ const schemaTool = tool === "task" || tool === "subagent_create" ? "SubAgent" : tool;
337185
+ if (inputSchema === void 0 && !isNativeToolEnabled(schemaTool, this.config.nativeToolEnabled())) {
337162
337186
  return { ok: false, error: `[tool disabled] ${tool} is disabled in Settings > Tools.` };
337163
337187
  }
337164
337188
  let parsed;
@@ -337180,11 +337204,11 @@ var ToolExecutor = class {
337180
337204
  }, args);
337181
337205
  return validation2.ok ? { ok: false, error: "[permission] Question is disabled by fully_autonomous option feedback." } : { ok: false, error: `[tool schema error] ${validation2.error}` };
337182
337206
  }
337183
- const definition = inputSchema === void 0 ? this.definitions().find((candidate) => candidate.function?.name === tool) : { function: { name: tool, parameters: inputSchema } };
337207
+ const definition = inputSchema === void 0 ? this.definitions().find((candidate) => candidate.function?.name === schemaTool) : { function: { name: tool, parameters: inputSchema } };
337184
337208
  if (!definition) {
337185
337209
  return { ok: false, error: `[tool unsupported] ${tool || "(missing tool)"} is not available for the ${this.hostProfile.kind} host on ${this.hostProfile.platform}.` };
337186
337210
  }
337187
- const validation = this.argumentValidators.validate(tool, definition.function.parameters, args);
337211
+ const validation = this.argumentValidators.validate(schemaTool, definition.function.parameters, args);
337188
337212
  return validation.ok ? { ok: true, args } : { ok: false, error: `[tool schema error] ${validation.error}` };
337189
337213
  }
337190
337214
  async execute(tool, argsStr, wsPath, context = {}) {
@@ -337385,6 +337409,44 @@ var ToolExecutor = class {
337385
337409
  result: parsed
337386
337410
  }, null, 2);
337387
337411
  }
337412
+ case "screen_capture": {
337413
+ const target = g2("target").toLowerCase() === "application" ? "application" : "desktop";
337414
+ if (process.env.NEWMARK_WSL_DISTRO) {
337415
+ const result = await requestWindowsHostTool("screen_capture", args, {
337416
+ conversationId: context.conversationId || "default",
337417
+ workspaceId: context.workspaceId || terminalTakeoverWorkspaceId(context.workspacePath || wsPath),
337418
+ actorId: context.actorId || ROOT_TERMINAL_ACTOR_ID,
337419
+ runtimeKey: browserUseScope(context, wsPath).runtimeKey,
337420
+ allowEphemeralVisionImage: context.allowEphemeralVisionImage === true,
337421
+ mode: context.mode || "build"
337422
+ }, 12e4, context.signal);
337423
+ return typeof result === "string" ? result : JSON.stringify(result);
337424
+ }
337425
+ if (process.env.NEWMARK_ISOLATED_RUNTIME === "1") {
337426
+ const result = await requestUtilityHostTool("screen_capture", args, {
337427
+ conversationId: context.conversationId || "default",
337428
+ workspaceId: context.workspaceId || terminalTakeoverWorkspaceId(context.workspacePath || wsPath),
337429
+ actorId: context.actorId || ROOT_TERMINAL_ACTOR_ID,
337430
+ workspacePath: context.workspacePath || wsPath,
337431
+ backend: "utility",
337432
+ mode: context.mode || "build",
337433
+ allowEphemeralVisionImage: context.allowEphemeralVisionImage === true
337434
+ }, 12e4, context.signal);
337435
+ return typeof result === "string" ? result : JSON.stringify(result);
337436
+ }
337437
+ return await runComputerUse({
337438
+ action: target === "application" ? "app_observe" : "observe",
337439
+ appTarget: g2("app_target"),
337440
+ windowHandle: target === "application" ? g2("app_target") : "",
337441
+ maxChars: Number(args.max_chars || 3e4),
337442
+ workspacePath: wsPath,
337443
+ allowEphemeralVisionImage: context.allowEphemeralVisionImage === true,
337444
+ captureMaxWidth: Number(args.capture_max_width),
337445
+ captureMaxHeight: Number(args.capture_max_height),
337446
+ invocation: context.invocation,
337447
+ ownerId: `screen-capture:${computerUseOwner(context, wsPath)}:${String(context.actorId || "root")}`
337448
+ });
337449
+ }
337388
337450
  case "computer_use": {
337389
337451
  const action = normalizeComputerUseAction(g2("action"));
337390
337452
  const owner = `${computerUseOwner(context, wsPath)}:${String(context.actorId || "root")}`;
@@ -337506,7 +337568,9 @@ var ToolExecutor = class {
337506
337568
  case "ssh_workspace":
337507
337569
  return await this.sshWorkspace(args, wsPath, context.signal);
337508
337570
  case "task":
337509
- return `[task] Subagent request accepted: ${g2("name")}`;
337571
+ case "subagent_create":
337572
+ case "SubAgent":
337573
+ return `[SubAgent] SubAgent request accepted: ${g2("name")}`;
337510
337574
  case "subagent_send":
337511
337575
  return `[subagent_send] Routed to Agent runtime: ${g2("name")}`;
337512
337576
  case "subagent_read":
@@ -337589,7 +337653,7 @@ var ToolExecutor = class {
337589
337653
  }
337590
337654
  hostSupportsTool(name50) {
337591
337655
  if (name50.startsWith("browser_") && !this.hostProfile.electronBrowser) return false;
337592
- if (name50 === "computer_use" && !this.hostProfile.windowsComputerUse) return false;
337656
+ if ((name50 === "computer_use" || name50 === "screen_capture") && !this.hostProfile.windowsComputerUse) return false;
337593
337657
  return true;
337594
337658
  }
337595
337659
  async decorateBrowserUseReceipt(input, context, runtimeKey) {
@@ -338679,6 +338743,7 @@ var SubagentManager = class {
338679
338743
  queueMicrotask(() => this.pump());
338680
338744
  }
338681
338745
  bind(options) {
338746
+ if (options.concurrency !== void 0) this.setConcurrencyLimit(options.concurrency);
338682
338747
  if (options.executor) this.executor = options.executor;
338683
338748
  if (options.onChange) this.onChange = options.onChange;
338684
338749
  if (options.persist) this.persist = options.persist;
@@ -338695,12 +338760,13 @@ var SubagentManager = class {
338695
338760
  removeRootInboxListener(listener) {
338696
338761
  this.rootInboxListeners.delete(listener);
338697
338762
  }
338698
- create(name50, prompt, model, inputMode, agentMode = "build", createdByAgentId = this.rootAgentId, flowName = "", goalObjective = "", flowPc = 0) {
338763
+ create(name50, prompt, model, inputMode, agentMode = "build", createdByAgentId = this.rootAgentId, flowName = "", goalObjective = "", flowPc = 0, buildRunId = "", intelligenceTier = "") {
338699
338764
  const id = (0, import_crypto8.randomUUID)();
338700
338765
  const shortId = id.replace(/-/g, "").slice(0, 8);
338701
338766
  const slug = natureSlug(name50);
338702
- const displayName = `${slug}-${shortId}`;
338703
- const qualifiedName = `${displayName}--${id}`;
338767
+ const createdName = String(name50 || "SubAgent").replace(/\s+/g, " ").trim().slice(0, 160) || "SubAgent";
338768
+ const displayName = createdName;
338769
+ const qualifiedName = `${slug}--${id}`;
338704
338770
  const stamp = now();
338705
338771
  const record = {
338706
338772
  id,
@@ -338708,9 +338774,11 @@ var SubagentManager = class {
338708
338774
  natureSlug: slug,
338709
338775
  displayName,
338710
338776
  qualifiedName,
338711
- name: slug,
338777
+ name: createdName,
338712
338778
  conversationId: this.conversationId,
338713
338779
  createdByAgentId,
338780
+ buildRunId: String(buildRunId || "").trim() || void 0,
338781
+ intelligenceTier: String(intelligenceTier || "").trim() || void 0,
338714
338782
  prompt,
338715
338783
  model: model || "default",
338716
338784
  inputMode: inputMode || "guide",
@@ -339033,6 +339101,20 @@ var SubagentManager = class {
339033
339101
  listAll() {
339034
339102
  return [...this.subs.values()].map(cloneRecord);
339035
339103
  }
339104
+ activeCountForBuild(buildRunId) {
339105
+ const target = String(buildRunId || "").trim();
339106
+ if (!target) return 0;
339107
+ return [...this.subs.values()].filter(
339108
+ (record) => record.buildRunId === target && (record.status === "queued" || record.status === "working")
339109
+ ).length;
339110
+ }
339111
+ setConcurrencyLimit(value) {
339112
+ this.concurrency = Math.max(1, Math.min(16, Math.floor(Number(value) || 4)));
339113
+ this.pump();
339114
+ }
339115
+ concurrencyLimit() {
339116
+ return this.concurrency;
339117
+ }
339036
339118
  pauseScheduling() {
339037
339119
  if (this.schedulingPaused) return;
339038
339120
  this.schedulingPaused = true;
@@ -340198,7 +340280,8 @@ function inferDomain(name50) {
340198
340280
  }
340199
340281
  if (name50 === "question") return "interaction";
340200
340282
  if (name50 === "skill") return "skills";
340201
- if (name50 === "task") return "subagent";
340283
+ if (name50 === "task" || name50 === "subagent_create" || name50 === "SubAgent") return "subagent";
340284
+ if (/^task_(read|create)$/.test(name50)) return "plan";
340202
340285
  if (/^(linked_plan|build_history_query)$/.test(name50)) return "plan";
340203
340286
  return "general";
340204
340287
  }
@@ -340462,7 +340545,7 @@ function resetPublicAssistantDeltaFilter(agent) {
340462
340545
  function prepareAssistantToolVisibility(agent, definitions) {
340463
340546
  const names = definitions.map(toolDefinitionName);
340464
340547
  brokerOnlyAssistantBuffers.set(agent, {
340465
- brokerOnly: names.includes(TOOL_PROVISION_NAME) && names.every((name50) => name50 === TOOL_PROVISION_NAME || name50 === "skill" || SUBAGENT_CORE_TOOL_NAMES.has(name50)),
340548
+ brokerOnly: names.includes(TOOL_PROVISION_NAME) && names.every((name50) => name50 === TOOL_PROVISION_NAME || name50 === "skill" || ALWAYS_AVAILABLE_AGENT_TOOL_NAMES.has(name50)),
340466
340549
  pending: [],
340467
340550
  released: false
340468
340551
  });
@@ -340754,7 +340837,7 @@ async function runAgentKernel(agent) {
340754
340837
  const requestStartedAt = Date.now();
340755
340838
  let firstTokenRecorded = false;
340756
340839
  const tools = context.tools || [];
340757
- const brokerOnlySurface = tools.length > 0 && tools.every((tool) => tool.name === TOOL_PROVISION_NAME || tool.name === "skill" || SUBAGENT_CORE_TOOL_NAMES.has(tool.name));
340840
+ const brokerOnlySurface = tools.length > 0 && tools.every((tool) => tool.name === TOOL_PROVISION_NAME || tool.name === "skill" || ALWAYS_AVAILABLE_AGENT_TOOL_NAMES.has(tool.name));
340758
340841
  currentAgent.beginRouteAttempt();
340759
340842
  try {
340760
340843
  const currentProvider = currentAgent.engineModel();
@@ -340989,6 +341072,8 @@ function buildBuildContextBootstrap(agent, messages, options) {
340989
341072
  buildConversationTaskLedger(agent),
340990
341073
  "## Tool Awareness Bootstrap",
340991
341074
  "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.",
341075
+ "Only bash, pwd, read, write, edit, delete_file, glob, and grep are foundational tools with initial full schemas (subject to mode and policy filtering).",
341076
+ "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.",
340992
341077
  ...catalogLines.length ? catalogLines : ["- No callable tools are available for this provider turn."],
340993
341078
  `Necessary full schemas supplied natively for this provider turn: ${activeNames.length ? activeNames.join(", ") : "(none; use tool_provision when its schema is available)"}.`,
340994
341079
  "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."
@@ -341178,7 +341263,8 @@ function routeTransitionNotice(agent, previous) {
341178
341263
  var TOOL_PROVISION_NAME = "tool_provision";
341179
341264
  var INITIAL_TOOL_SCHEMA_LIMIT = 8;
341180
341265
  var TOOL_PROVISION_BATCH_LIMIT = 8;
341181
- var SUBAGENT_CORE_TOOL_NAMES = /* @__PURE__ */ new Set(["task", "subagent_list", "subagent_read", "subagent_send", "subagent_result", "subagent_close"]);
341266
+ var BASIC_INITIAL_TOOL_NAMES = /* @__PURE__ */ new Set(["bash", "pwd", "read", "write", "edit", "delete_file", "glob", "grep"]);
341267
+ var ALWAYS_AVAILABLE_AGENT_TOOL_NAMES = BASIC_INITIAL_TOOL_NAMES;
341182
341268
  var ToolProvisionSession = class {
341183
341269
  definitionsByName = /* @__PURE__ */ new Map();
341184
341270
  initialNames = /* @__PURE__ */ new Set();
@@ -341202,7 +341288,7 @@ var ToolProvisionSession = class {
341202
341288
  const name50 = toolDefinitionName(definition);
341203
341289
  if (this.definitionsByName.has(name50)) this.initialNames.add(name50);
341204
341290
  }
341205
- for (const name50 of SUBAGENT_CORE_TOOL_NAMES) {
341291
+ for (const name50 of ALWAYS_AVAILABLE_AGENT_TOOL_NAMES) {
341206
341292
  if (this.definitionsByName.has(name50)) this.initialNames.add(name50);
341207
341293
  }
341208
341294
  for (const name50 of this.provisionedNames) {
@@ -341360,29 +341446,6 @@ function toolSurfaceIdentityForAgent(agent) {
341360
341446
  optionFeedback: agent.config.getStr("agent", "option_feedback")
341361
341447
  });
341362
341448
  }
341363
- var CAPABILITY_TO_DOMAIN = {
341364
- "vcs.inspect": ["git"],
341365
- "code.search": ["core"],
341366
- "test.run": ["core"],
341367
- "web.search": ["web"],
341368
- "automation.manage": ["automation"],
341369
- "flow.manage": ["flow"],
341370
- "memory.manage": ["memory"],
341371
- "computer.manage": ["computer"],
341372
- "browser.manage": ["browser"],
341373
- "terminal.manage": ["general", "core"],
341374
- "github.manage": ["general"],
341375
- "ssh.manage": ["general"],
341376
- "skill.manage": ["skills", "general"],
341377
- "plan.manage": ["plan"],
341378
- "history.query": ["plan"],
341379
- "media.display": ["media"],
341380
- "interaction.manage": ["interaction"]
341381
- };
341382
- var CAPABILITY_TOOL_HINTS = {
341383
- "skill.manage": ["skill", "skill_download"],
341384
- "memory.manage": ["memory_lab_read", "memory_lab_query", "memory_lab_update", "memory_lab_reindex"]
341385
- };
341386
341449
  function routeToolSurfaceV2(agent, definitions, toolchain, task) {
341387
341450
  if (!agent.shouldExposeToolInterface()) {
341388
341451
  return {
@@ -341394,67 +341457,34 @@ function routeToolSurfaceV2(agent, definitions, toolchain, task) {
341394
341457
  ].join("\n")
341395
341458
  };
341396
341459
  }
341397
- if (!toolchain) return { definitions, systemPromptNotice: "" };
341398
- const planner = new ToolExposurePlanner(toolchain.registry, toolchain.catalog);
341399
- const plan = planner.plan({
341400
- agentRunId: agent.runtimeActorId,
341401
- buildBlockId: agent.activeConversationId || "build",
341402
- userInput: task,
341403
- objective: "",
341404
- previousToolCalls: [],
341405
- toolUsageFrequency: /* @__PURE__ */ new Map(),
341406
- permissionScope: ["workspace"],
341407
- tokenBudget: 2e4,
341408
- providerToolLimit: 0
341409
- });
341410
- const domains = /* @__PURE__ */ new Set();
341411
- const toolHints = /* @__PURE__ */ new Set();
341412
- for (const capabilityId of plan.suggestedCapabilityIds) {
341413
- for (const domain of CAPABILITY_TO_DOMAIN[capabilityId] || []) domains.add(domain);
341414
- for (const toolName of CAPABILITY_TOOL_HINTS[capabilityId] || []) toolHints.add(toolName);
341415
- }
341416
- const names = definitions.map(toolDefinitionName);
341417
- for (const name50 of names) {
341418
- if (name50 && new RegExp(`(?:^|[^A-Za-z0-9_])${name50.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:$|[^A-Za-z0-9_])`, "i").test(task)) {
341419
- toolHints.add(name50);
341420
- }
341421
- }
341422
- const selected = definitions.filter((definition) => {
341423
- const name50 = toolDefinitionName(definition);
341424
- const descriptor = toolchain.registry.get(name50);
341425
- if (!descriptor || descriptor.riskLevel === "destructive") return false;
341426
- if (toolHints.has(name50)) return true;
341427
- if (!domains.has(descriptor.capabilityId.slice(4))) return false;
341428
- return true;
341429
- }).sort((a3, b2) => {
341430
- const aHint = toolHints.has(toolDefinitionName(a3)) ? 0 : 1;
341431
- const bHint = toolHints.has(toolDefinitionName(b2)) ? 0 : 1;
341432
- return aHint - bHint || names.indexOf(toolDefinitionName(a3)) - names.indexOf(toolDefinitionName(b2));
341433
- }).slice(0, INITIAL_TOOL_SCHEMA_LIMIT);
341434
- const selectedNames = new Set(selected.map(toolDefinitionName));
341435
- const core = definitions.filter((definition) => {
341436
- const name50 = toolDefinitionName(definition);
341437
- return SUBAGENT_CORE_TOOL_NAMES.has(name50) && !selectedNames.has(name50);
341438
- });
341439
- const surface = core.length ? selected.concat(core) : selected;
341440
- if (surface.length === definitions.length) return { definitions, systemPromptNotice: "" };
341441
- if (!selected.length) {
341442
- return {
341443
- definitions: surface,
341444
- systemPromptNotice: [
341445
- "## Tool Interface Availability",
341446
- "This turn was classified as conversational, so no task-specific tool schema was preloaded.",
341447
- `The ${TOOL_PROVISION_NAME} interface still exposes the complete compact capability catalog and can provision an original tool schema when the task requires it.`
341448
- ].join("\n")
341449
- };
341460
+ const surface = definitions.filter((definition) => BASIC_INITIAL_TOOL_NAMES.has(toolDefinitionName(definition)));
341461
+ const advancedCount = Math.max(0, definitions.length - surface.length);
341462
+ let planFingerprint = "";
341463
+ if (toolchain) {
341464
+ const planner = new ToolExposurePlanner(toolchain.registry, toolchain.catalog);
341465
+ const plan = planner.plan({
341466
+ agentRunId: agent.runtimeActorId,
341467
+ buildBlockId: agent.activeConversationId || "build",
341468
+ userInput: task,
341469
+ objective: "",
341470
+ previousToolCalls: [],
341471
+ toolUsageFrequency: /* @__PURE__ */ new Map(),
341472
+ permissionScope: ["workspace"],
341473
+ tokenBudget: 2e4,
341474
+ providerToolLimit: 0
341475
+ });
341476
+ planFingerprint = plan.plan.stableToolsetHash.slice(0, 8);
341450
341477
  }
341451
341478
  return {
341452
341479
  definitions: surface,
341453
341480
  systemPromptNotice: [
341454
341481
  "## Tool Interface Availability",
341455
- `At most ${INITIAL_TOOL_SCHEMA_LIMIT} deterministic task-relevant schemas are preloaded for this turn; the always-available subagent orchestration tools are appended separately.`,
341456
- `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.`,
341457
- `Adaptive exposure plan ${plan.plan.stableToolsetHash.slice(0, 8)}: ${plan.activeToolIds.length} planned tools, ${plan.plan.suggestedCapabilityIds.join(",")}.`
341482
+ `The initial full-schema surface is restricted to foundational workspace tools: ${surface.map(toolDefinitionName).join(", ") || "(none allowed in this mode)"}.`,
341483
+ `${advancedCount} advanced tools are advertised by capability in the compact ${TOOL_PROVISION_NAME} catalog without loading their schemas.`,
341484
+ "Advanced tools include SubAgent, task tools, Git/GitHub, browser, Computer Use, skills, MCP, automations, Flow, and Memory Lab.",
341485
+ `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.`,
341486
+ "Do not call an advanced tool directly from the initial catalog: capability presence is not callability until provisioning has completed.",
341487
+ planFingerprint ? `Capability routing fingerprint: ${planFingerprint}.` : "Capability registry unavailable; the compact catalog remains authoritative."
341458
341488
  ].join("\n")
341459
341489
  };
341460
341490
  }
@@ -341523,10 +341553,12 @@ function toKernelTools(agent, definitions, provisioning) {
341523
341553
  throw new Error(rawText);
341524
341554
  }
341525
341555
  const visionImage = visualFallbackImageInput(agent, name50, rawText);
341556
+ const capturedInput = name50 === "screen_capture" ? agent.registerCapturedImageInput(visionImage.image || "", "active-screenshot.jpg") : null;
341526
341557
  const directImage = imageInspectDataUrl(name50, rawText);
341527
- const text = spillOversizedToolResult(agent, name50, sanitizeVisualToolText(name50, rawText));
341558
+ const text = spillOversizedToolResult(agent, name50, capturedImageToolText(name50, sanitizeVisualToolText(name50, rawText), capturedInput?.id));
341528
341559
  const content = [{ type: "text", text }];
341529
- if (visionImage.imagePath) content.push({ type: "image", imagePath: visionImage.imagePath, mimeType: imageMimeForPath(visionImage.imagePath) });
341560
+ if (name50 === "screen_capture" && capturedInput?.dataUrl) content.push({ type: "image", image: capturedInput.dataUrl, mimeType: capturedInput.mimeType });
341561
+ else if (visionImage.imagePath) content.push({ type: "image", imagePath: visionImage.imagePath, mimeType: imageMimeForPath(visionImage.imagePath) });
341530
341562
  else if (visionImage.image) content.push({ type: "image", image: visionImage.image, mimeType: visionImage.mimeType });
341531
341563
  if (directImage) content.push({ type: "image", image: directImage, mimeType: "image/png" });
341532
341564
  const terminate = shouldTerminateAfterToolResult(name50) && (name50 !== "question" || agent.pendingOptions.length > 0);
@@ -341539,7 +341571,7 @@ function toKernelTools(agent, definitions, provisioning) {
341539
341571
  } catch {
341540
341572
  }
341541
341573
  }
341542
- return { content, details: { tool: name50, ok: true, terminate, ...launchReceipt ? { launchReceipt } : {}, visionImagePath: visionImage.imagePath || void 0, ephemeralVisionImage: !!visionImage.image, displayImage }, terminate };
341574
+ return { content, details: { tool: name50, ok: true, terminate, ...launchReceipt ? { launchReceipt } : {}, visionImagePath: visionImage.imagePath || void 0, ephemeralVisionImage: !!visionImage.image, capturedAttachmentId: capturedInput?.id, displayImage }, terminate };
341543
341575
  }
341544
341576
  };
341545
341577
  }).filter((tool) => !!tool.name);
@@ -341563,7 +341595,7 @@ var INLINE_TOOL_RESULT_MAX_CHARS = 24e3;
341563
341595
  function spillOversizedToolResult(agent, name50, text) {
341564
341596
  const value = String(text || "");
341565
341597
  if (value.length <= INLINE_TOOL_RESULT_MAX_CHARS) return value;
341566
- if (["computer_use", "browser_use", "pdf_read", "image_inspect", "image_display", "task", "subagent_send", "subagent_result", "subagent_read", "linked_plan", "question"].includes(name50)) {
341598
+ 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)) {
341567
341599
  return value;
341568
341600
  }
341569
341601
  const artifactId = agent.storeToolResultArtifact(name50, value);
@@ -341578,10 +341610,10 @@ function spillOversizedToolResult(agent, name50, text) {
341578
341610
  ].join("\n");
341579
341611
  }
341580
341612
  function sanitizeVisualToolText(name50, text) {
341581
- if (name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read" && name50 !== "image_inspect") return text;
341613
+ if (name50 !== "screen_capture" && name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read" && name50 !== "image_inspect") return text;
341582
341614
  try {
341583
341615
  const parsed = JSON.parse(text);
341584
- if (name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read") {
341616
+ if (name50 === "screen_capture" || name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read") {
341585
341617
  delete parsed.vision_image_path;
341586
341618
  delete parsed.vision_image_data_url;
341587
341619
  if (name50 === "pdf_read" && parsed.result && typeof parsed.result === "object") {
@@ -341597,7 +341629,7 @@ function sanitizeVisualToolText(name50, text) {
341597
341629
  }
341598
341630
  }
341599
341631
  function discardComputerUseVisionImage(name50, text) {
341600
- if (name50 !== "computer_use") return;
341632
+ if (name50 !== "screen_capture" && name50 !== "computer_use") return;
341601
341633
  try {
341602
341634
  const parsed = JSON.parse(text);
341603
341635
  const screenshotPath = String(parsed.vision_image_path || "");
@@ -341615,8 +341647,20 @@ function imageInspectDataUrl(name50, text) {
341615
341647
  return "";
341616
341648
  }
341617
341649
  }
341650
+ function capturedImageToolText(name50, text, attachmentId) {
341651
+ if (name50 !== "screen_capture" || !attachmentId) return text;
341652
+ try {
341653
+ const parsed = JSON.parse(text);
341654
+ parsed.attachment_id = attachmentId;
341655
+ parsed.image_input_channel = "user-image";
341656
+ parsed.inspect_next = { tool: "image_inspect", actions: ["source_info", "crop"], max_scale: 4 };
341657
+ return JSON.stringify(parsed, null, 2);
341658
+ } catch {
341659
+ return text;
341660
+ }
341661
+ }
341618
341662
  function visualFallbackImageInput(agent, name50, text) {
341619
- if (name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read") return {};
341663
+ if (name50 !== "screen_capture" && name50 !== "computer_use" && name50 !== "browser_use" && name50 !== "pdf_read") return {};
341620
341664
  const model = agent.activeModelConfig();
341621
341665
  if (!model?.vision) return {};
341622
341666
  try {
@@ -341664,7 +341708,7 @@ async function executeNewmarkTool(agent, name50, args, inputSchema, signal) {
341664
341708
  if (signal?.aborted) throw abortError4();
341665
341709
  return result2;
341666
341710
  };
341667
- if (name50 === "task") return (await agent.handleSubagentEnvelope(args)).output;
341711
+ if (name50 === "task" || name50 === "subagent_create" || name50 === "SubAgent") return (await agent.handleSubagentEnvelope(args, true)).output;
341668
341712
  if (name50 === "subagent_send") return (await agent.handleSubagentContinueEnvelope(args)).output;
341669
341713
  if (name50 === "subagent_list") return agent.handleSubagentListEnvelope(args).output;
341670
341714
  if (name50 === "subagent_read") return agent.handleSubagentReadEnvelope(args).output;
@@ -341742,7 +341786,7 @@ async function executeNewmarkTool(agent, name50, args, inputSchema, signal) {
341742
341786
  actorId: agent.runtimeActorId,
341743
341787
  workspaceId: terminalTakeoverWorkspaceId(wsDir),
341744
341788
  backend: process.env.NEWMARK_WSL_DISTRO ? "wsl" : process.platform === "win32" ? "windows" : process.platform,
341745
- allowEphemeralVisionImage: (name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read" || name50 === "ocr_read") && !!agent.activeModelConfig()?.vision,
341789
+ allowEphemeralVisionImage: (name50 === "screen_capture" || name50 === "computer_use" || name50 === "browser_use" || name50 === "pdf_read" || name50 === "ocr_read") && !!agent.activeModelConfig()?.vision,
341746
341790
  signal
341747
341791
  });
341748
341792
  if (signal?.aborted) throw abortError4();
@@ -344816,7 +344860,7 @@ var Agent4 = class _Agent {
344816
344860
  this.tools = new ToolExecutor(rootPath, this.config, this.ssh, this.workspace);
344817
344861
  this.skills = new SkillsManager(rootPath);
344818
344862
  this.memoryLab = new MemoryLabManager(rootPath, this.config.getStr("general", "language"));
344819
- this.subagents = new SubagentManager({ rootAgentId: this.runtimeActorId });
344863
+ this.subagents = new SubagentManager({ rootAgentId: this.runtimeActorId, concurrency: this.subagentConcurrencyLimit() });
344820
344864
  if (this.mode === "goal" && !this.goal) {
344821
344865
  this.goal = new GoalStateImpl("Set your objective");
344822
344866
  }
@@ -344885,6 +344929,7 @@ var Agent4 = class _Agent {
344885
344929
  memoryLabRebuildState = "idle";
344886
344930
  memoryLabRebuildError = "";
344887
344931
  displayImageDescriptionCache = /* @__PURE__ */ new Map();
344932
+ latestCapturedImageInputs = [];
344888
344933
  activeProcessAbortController = null;
344889
344934
  automationManager = null;
344890
344935
  activeAgentKernelRuntime = null;
@@ -345415,6 +345460,7 @@ var Agent4 = class _Agent {
345415
345460
  }
345416
345461
  setIntelligence(tier, persist = false) {
345417
345462
  this.intelligence = normalizeIntelligenceTier(tier);
345463
+ this.subagents?.setConcurrencyLimit(this.subagentConcurrencyLimit());
345418
345464
  if (persist) {
345419
345465
  this.config.set("models", "default_intelligence", this.intelligence);
345420
345466
  this.config.save();
@@ -345619,12 +345665,17 @@ var Agent4 = class _Agent {
345619
345665
  return path28.join(ws.path, "conversations", "state.json");
345620
345666
  }
345621
345667
  workspaceConversationStateKey(conversationId = this.activeConversationId) {
345622
- const prefix = this.workspaceConversationPrefix();
345668
+ return this.workspaceConversationStateKeyFor(conversationId, this.workspace.current);
345669
+ }
345670
+ workspaceConversationStateKeyFor(conversationId, ws) {
345671
+ const prefix = this.workspaceConversationPrefixFor(ws);
345623
345672
  if (!prefix) return null;
345624
345673
  return `${prefix}-${this.safeConversationId(conversationId)}`;
345625
345674
  }
345626
345675
  workspaceConversationPrefix() {
345627
- const ws = this.workspace.current;
345676
+ return this.workspaceConversationPrefixFor(this.workspace.current);
345677
+ }
345678
+ workspaceConversationPrefixFor(ws) {
345628
345679
  if (!ws) return null;
345629
345680
  const supplied = String(ws.conversationStatePrefix || "").trim();
345630
345681
  if (/^(?:internal|external)-[a-f0-9]{16}$/i.test(supplied)) return supplied.toLowerCase();
@@ -347074,8 +347125,56 @@ ${String(event.toolArgs || "")}`;
347074
347125
  }
347075
347126
  }
347076
347127
  listConversationStates() {
347077
- const stored = this.readStoredConversationState();
347078
- const prefix = this.workspaceConversationPrefix() || "";
347128
+ return this.listWorkspaceConversationStates(this.workspace.current);
347129
+ }
347130
+ registerCapturedImageInput(dataUrl, name50 = "active-screenshot.jpg") {
347131
+ if (!String(dataUrl || "").startsWith("data:image/")) return null;
347132
+ const prepared = this.prepareSubmittedConversationImages([{ dataUrl, name: name50, type: dataUrl.slice(5, dataUrl.indexOf(";")) }]);
347133
+ const attachment = prepared.attachments[0] || null;
347134
+ this.latestCapturedImageInputs = attachment ? [attachment] : [];
347135
+ return attachment;
347136
+ }
347137
+ subagentConcurrencyLimit() {
347138
+ return this.intelligence === "ultra" ? 16 : 4;
347139
+ }
347140
+ /** 当前工作区使用内存中的前台选择;后台工作区从各自持久化状态读取 active id。 */
347141
+ activeConversationIdForWorkspace(ws) {
347142
+ const targetWs = ws || this.workspace.current;
347143
+ if (!targetWs) return "default";
347144
+ const currentWs = this.workspace.current;
347145
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347146
+ if (isCurrent) return this.safeConversationId(this.activeConversationId || "default");
347147
+ const stored = this.readStoredConversationState(targetWs);
347148
+ return this.safeConversationId(stored.activeConversationId || "default");
347149
+ }
347150
+ /**
347151
+ * 持久化的完整 work run 记录(含 interrupted/force_interrupted)。
347152
+ * 不依赖运行时内存(run 结束后内存清空,state 端点曾因此丢失被中断的构建记录),
347153
+ * 供 mobile 端点稳定透出完整对话信息;移动端按同一格式解析。
347154
+ */
347155
+ getPersistedConversationWorkRuns(conversationId, ws = null) {
347156
+ const targetWs = ws || this.workspace.current;
347157
+ const clean = this.safeConversationId(conversationId || "default");
347158
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, targetWs);
347159
+ const stored = this.readStoredConversationState(targetWs);
347160
+ const persisted = stateKey2 && stored.conversations ? stored.conversations[stateKey2] : void 0;
347161
+ const tree = persisted ? this.normalizeConversationTree(persisted) : null;
347162
+ const runtimeNodeId = String(tree?.activeNodeId || "");
347163
+ const viewedNodeId = tree ? this.resolveConversationTreePath(tree, this.storedConversationTreePath(tree, persisted?.viewedBranchNodePath, runtimeNodeId)) : "";
347164
+ const viewedNode = tree?.nodes[viewedNodeId];
347165
+ return this.normalizeWorkRuns(viewedNode?.workRuns || persisted?.workRuns);
347166
+ }
347167
+ /** Exact membership check against the raw persisted state key, before UI content deduplication. */
347168
+ hasConversationInWorkspace(conversationId, ws) {
347169
+ const targetWs = ws || this.workspace.current;
347170
+ const stateKey2 = this.workspaceConversationStateKeyFor(this.safeConversationId(conversationId || "default"), targetWs);
347171
+ if (!stateKey2) return false;
347172
+ return Object.prototype.hasOwnProperty.call(this.readStoredConversationState(targetWs).conversations || {}, stateKey2);
347173
+ }
347174
+ /** 按工作区列对话(任意 ws,key 前缀 = kind-sha256(path).slice(0,16));供 mobile API 透出工作区从属对话 */
347175
+ listWorkspaceConversationStates(ws) {
347176
+ const stored = this.readStoredConversationState(ws);
347177
+ const prefix = this.workspaceConversationPrefixFor(ws) || "";
347079
347178
  const scopedEntries = Object.entries(stored.conversations || {}).filter(([key3]) => !prefix || key3.startsWith(prefix));
347080
347179
  if (scopedEntries.some(([, value]) => !Number.isFinite(value.order))) {
347081
347180
  const legacyOrder = [...scopedEntries].sort(([, a3], [, b2]) => {
@@ -347086,7 +347185,7 @@ ${String(event.toolArgs || "")}`;
347086
347185
  legacyOrder.forEach(([, value], index) => {
347087
347186
  value.order = index;
347088
347187
  });
347089
- this.writeStoredConversationState(stored);
347188
+ this.writeStoredConversationState(stored, ws);
347090
347189
  }
347091
347190
  const rows = [];
347092
347191
  for (const [key3, value] of Object.entries(stored.conversations || {})) {
@@ -347140,6 +347239,7 @@ ${String(event.toolArgs || "")}`;
347140
347239
  this.subagents = sharedSubagentManager(this.subagentManagerKey(clean), {
347141
347240
  conversationId: clean,
347142
347241
  rootAgentId: state?.rootAgentId || this.runtimeActorId,
347242
+ concurrency: this.subagentConcurrencyLimit(),
347143
347243
  state,
347144
347244
  executor: (job) => this.runSubagentJob(job.record.id, job.prompt, job.flowName, job.reason),
347145
347245
  persist: (subagentState) => this.persistSubagentState(clean, subagentState),
@@ -347209,15 +347309,14 @@ Review this persisted peer result and summarize or continue the parent task as n
347209
347309
  }
347210
347310
  getConversationSnapshot(conversationId = this.activeConversationId, options = {}) {
347211
347311
  const clean = this.safeConversationId(conversationId || "default");
347212
- const isActiveConversation = clean === this.safeConversationId(this.activeConversationId || "default");
347213
- const stateKey2 = this.workspaceConversationStateKey(clean);
347214
- const memoryKey = (() => {
347215
- const ws = this.workspace.current;
347216
- if (!ws) return null;
347217
- return `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
347218
- })();
347312
+ const ws = options.workspace || this.workspace.current;
347313
+ const currentWs = this.workspace.current;
347314
+ const isActiveWorkspace = !ws && !currentWs || !!ws && !!currentWs && path28.resolve(ws.path) === path28.resolve(currentWs.path);
347315
+ const isActiveConversation = isActiveWorkspace && clean === this.safeConversationId(this.activeConversationId || "default");
347316
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, ws);
347317
+ const memoryKey = ws ? `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}` : null;
347219
347318
  const memory = memoryKey ? this.workspaceConversations.get(memoryKey) : void 0;
347220
- const stored = this.readStoredConversationState();
347319
+ const stored = this.readStoredConversationState(ws);
347221
347320
  const persisted = stateKey2 && stored.conversations ? stored.conversations[stateKey2] : void 0;
347222
347321
  const tree = persisted ? this.normalizeConversationTree(persisted) : null;
347223
347322
  const runtimeNodeId = String(tree?.activeNodeId || "");
@@ -347237,7 +347336,7 @@ Review this persisted peer result and summarize or continue the parent task as n
347237
347336
  const continuations = this.normalizeContinuations(isActiveConversation && viewingRuntimeNode ? this.continuations : viewedNode?.continuations || persisted?.continuations || memory?.continuations);
347238
347337
  return {
347239
347338
  conversationId: clean,
347240
- conversations: this.listConversationStates(),
347339
+ conversations: this.listWorkspaceConversationStates(ws),
347241
347340
  conversationPlan: this.normalizeConversationPlan(isActiveConversation ? this.conversationPlan : persisted?.plan || memory?.plan),
347242
347341
  linkedPlan: this.normalizeLinkedPlan(isActiveConversation ? this.linkedPlan : persisted?.linkedPlan || memory?.linkedPlan),
347243
347342
  subagents: this.recordsForState(isActiveConversation ? this.subagents.serialize() : persisted?.subagentState || memory?.subagentState),
@@ -347247,9 +347346,9 @@ Review this persisted peer result and summarize or continue the parent task as n
347247
347346
  historyMessages: history.length,
347248
347347
  workRuns,
347249
347348
  continuations,
347250
- modelSelection: isActiveConversation ? this.currentConversationModelSelection() : persisted?.modelSelection || memory?.modelSelection || this.currentConversationModelSelection(),
347349
+ modelSelection: isActiveConversation ? this.currentConversationModelSelection() : persisted?.modelSelection || memory?.modelSelection || { kind: "auto" },
347251
347350
  flowSelection: isActiveConversation ? this.currentConversationFlowSelection() : persisted?.flowSelection || memory?.flowSelection || null,
347252
- inputMode: this.inputMode,
347351
+ inputMode: isActiveConversation ? this.inputMode : persisted?.inputMode || memory?.inputMode || "guide",
347253
347352
  mode: isActiveConversation ? this.mode : persisted?.mode || memory?.mode || "build",
347254
347353
  goal: isActiveConversation ? this.serializeGoal() : persisted?.goal || memory?.goal || null,
347255
347354
  branches: this.branchGroupMetadata(tree),
@@ -347536,12 +347635,16 @@ Review this persisted peer result and summarize or continue the parent task as n
347536
347635
  if (clean === this.safeConversationId(this.activeConversationId)) this.setConversationFromStorage(clean);
347537
347636
  return this.getConversationSnapshot(clean);
347538
347637
  }
347539
- setConversationPinned(id, pinned) {
347638
+ setConversationPinned(id, pinned, ws = this.workspace.current) {
347639
+ const targetWs = ws || this.workspace.current;
347640
+ if (!targetWs) return false;
347540
347641
  const clean = this.safeConversationId(id || "default");
347541
- this.saveWorkspaceConversationState();
347542
- const stateKey2 = this.workspaceConversationStateKey(clean);
347642
+ const currentWs = this.workspace.current;
347643
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347644
+ if (isCurrent) this.saveWorkspaceConversationState();
347645
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, targetWs);
347543
347646
  if (!stateKey2) return false;
347544
- const stored = this.readStoredConversationState();
347647
+ const stored = this.readStoredConversationState(targetWs);
347545
347648
  stored.conversations = stored.conversations || {};
347546
347649
  const existing = stored.conversations[stateKey2];
347547
347650
  if (!existing) return false;
@@ -347550,23 +347653,65 @@ Review this persisted peer result and summarize or continue the parent task as n
347550
347653
  const siblingOrders = Object.entries(stored.conversations).filter(([key3, value]) => key3 !== stateKey2 && !!value.pinned === existing.pinned && Number.isFinite(value.order)).map(([, value]) => Number(value.order));
347551
347654
  existing.order = siblingOrders.length ? Math.min(...siblingOrders) - 1 : 0;
347552
347655
  existing.updatedAt = existing.updatedAt || (/* @__PURE__ */ new Date()).toISOString();
347553
- this.writeStoredConversationState(stored);
347656
+ this.writeStoredConversationState(stored, targetWs);
347554
347657
  return true;
347555
347658
  }
347556
- renameConversation(id, title) {
347659
+ renameConversation(id, title, ws = this.workspace.current) {
347660
+ const targetWs = ws || this.workspace.current;
347661
+ if (!targetWs) return false;
347557
347662
  const clean = this.safeConversationId(id || "default");
347558
347663
  const nextTitle = String(title || "").replace(/\s+/g, " ").trim().slice(0, 80);
347559
347664
  if (!nextTitle) return false;
347560
- this.saveWorkspaceConversationState();
347561
- const stateKey2 = this.workspaceConversationStateKey(clean);
347665
+ const currentWs = this.workspace.current;
347666
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347667
+ if (isCurrent) this.saveWorkspaceConversationState();
347668
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, targetWs);
347562
347669
  if (!stateKey2) return false;
347563
- const stored = this.readStoredConversationState();
347670
+ const stored = this.readStoredConversationState(targetWs);
347564
347671
  const existing = stored.conversations?.[stateKey2];
347565
347672
  if (!existing) return false;
347566
347673
  existing.title = nextTitle;
347567
- this.writeStoredConversationState(stored);
347674
+ this.writeStoredConversationState(stored, targetWs);
347568
347675
  return true;
347569
347676
  }
347677
+ /** 为指定工作区创建空白对话,不临时切换全局前台工作区。 */
347678
+ createConversationInWorkspace(ws, title = "") {
347679
+ const existing = this.listWorkspaceConversationStates(ws);
347680
+ const id = this.safeConversationId(`conv-${Date.now()}-${crypto14.randomUUID().slice(0, 8)}`);
347681
+ const resolvedTitle = String(title || "").replace(/\s+/g, " ").trim().slice(0, 80) || `New chat ${existing.length + 1}`;
347682
+ const stateKey2 = this.workspaceConversationStateKeyFor(id, ws);
347683
+ if (!stateKey2) throw new Error("Conversation workspace is unavailable.");
347684
+ const unpinnedOrders = existing.filter((item) => !item.pinned).map((item) => Number(item.order || 0));
347685
+ const order = unpinnedOrders.length ? Math.min(...unpinnedOrders) - 1 : 0;
347686
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
347687
+ this.mutateStoredConversationState(ws, (latest) => {
347688
+ latest.version = 3;
347689
+ latest.activeConversationId = id;
347690
+ latest.conversations = latest.conversations || {};
347691
+ latest.conversations[stateKey2] = {
347692
+ title: resolvedTitle,
347693
+ chatMessages: [],
347694
+ history: [],
347695
+ plan: { items: [] },
347696
+ linkedPlan: { markdown: "", revision: 0 },
347697
+ workRuns: [],
347698
+ continuations: [],
347699
+ inputMode: this.defaultInputMode(),
347700
+ mode: "build",
347701
+ updatedAt: now2,
347702
+ pinned: false,
347703
+ pinnedAt: "",
347704
+ order,
347705
+ branchCommunication: false
347706
+ };
347707
+ return latest;
347708
+ });
347709
+ const currentWs = this.workspace.current;
347710
+ if (currentWs && path28.resolve(currentWs.path) === path28.resolve(ws.path)) {
347711
+ this.setConversationFromStorage(id);
347712
+ }
347713
+ return { id, title: resolvedTitle };
347714
+ }
347570
347715
  /**
347571
347716
  * 首 Build 命名判定:仅当 (1) 当前对话尚无历史 Build(排除当前 run)且
347572
347717
  * (2) 其持久化 title 仍是自动生成(含为空)时返回 true。dev-0.4.3 起不再
@@ -347677,6 +347822,34 @@ Conversation title (a few words):`;
347677
347822
  this.writeStoredConversationState(stored);
347678
347823
  return true;
347679
347824
  }
347825
+ /** Reorder one pinned group inside an explicit workspace without changing membership. */
347826
+ reorderWorkspaceConversationGroup(ids, ws) {
347827
+ const targetWs = ws || this.workspace.current;
347828
+ if (!targetWs || !Array.isArray(ids) || ids.length < 2) return false;
347829
+ const normalized = ids.map((id) => this.safeConversationId(String(id || "")));
347830
+ if (normalized.some((id) => !id) || new Set(normalized).size !== normalized.length) return false;
347831
+ const currentWs = this.workspace.current;
347832
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347833
+ if (isCurrent) this.saveWorkspaceConversationState();
347834
+ let accepted = false;
347835
+ this.mutateStoredConversationState(targetWs, (latest) => {
347836
+ const prefix = this.workspaceConversationPrefixFor(targetWs) || "";
347837
+ const entries = Object.entries(latest.conversations || {}).filter(([key3]) => !prefix || key3.startsWith(prefix));
347838
+ const entryById = new Map(entries.map(([key3, value]) => [key3.slice(prefix.length + 1) || key3, value]));
347839
+ const requested = normalized.map((id) => entryById.get(id));
347840
+ if (requested.some((entry) => !entry)) return latest;
347841
+ if (new Set(requested.map((entry) => !!entry.pinned)).size !== 1) return latest;
347842
+ const orderSlots = requested.map((entry) => Number(entry.order));
347843
+ if (orderSlots.some((order) => !Number.isFinite(order))) return latest;
347844
+ orderSlots.sort((a3, b2) => a3 - b2);
347845
+ normalized.forEach((id, index) => {
347846
+ entryById.get(id).order = orderSlots[index];
347847
+ });
347848
+ accepted = true;
347849
+ return latest;
347850
+ });
347851
+ return accepted;
347852
+ }
347680
347853
  flushConversationState() {
347681
347854
  this.saveWorkspaceConversationState();
347682
347855
  }
@@ -349625,21 +349798,21 @@ ${summary}`, segment, "local-summarize", true);
349625
349798
  this.saveWorkspaceConversationState(true);
349626
349799
  return { text, hiddenUserInput: true, goalContinuation: true };
349627
349800
  }
349628
- buildSessionArchive(messages, mode, model, archiveDir) {
349801
+ buildSessionArchive(messages, context, archiveDir) {
349629
349802
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").replace("T", "_").replace("Z", "");
349630
349803
  const filename = `session_${stamp}_${crypto14.randomUUID().slice(0, 8)}.md`;
349631
349804
  let markdown = `# Newmark Session \u2014 ${stamp}
349632
349805
 
349633
349806
  `;
349634
- markdown += `**Mode**: ${mode}
349635
- **Model**: ${model}
349807
+ markdown += `**Mode**: ${context.mode}
349808
+ **Model**: ${context.model}
349636
349809
  `;
349637
349810
  markdown += `**Messages**: ${messages.length}
349638
349811
 
349639
349812
  ---
349640
349813
 
349641
349814
  `;
349642
- if (this.goal) markdown += `**Goal**: ${this.goal.objective}
349815
+ if (context.goal?.objective) markdown += `**Goal**: ${context.goal.objective}
349643
349816
 
349644
349817
  `;
349645
349818
  for (const msg of messages) {
@@ -349662,12 +349835,12 @@ ${msg.content}
349662
349835
  writeSessionArchive(messages, mode, model) {
349663
349836
  const archiveDir = this.archiveDir();
349664
349837
  fs25.mkdirSync(archiveDir, { recursive: true });
349665
- const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
349838
+ const archive = this.buildSessionArchive(messages, { mode, model, goal: this.serializeGoal() }, archiveDir);
349666
349839
  fs25.writeFileSync(path28.join(archiveDir, archive.filename), archive.markdown, "utf-8");
349667
349840
  return archive.filename;
349668
349841
  }
349669
- async writeSessionArchiveAsync(messages, mode, model, archiveDir = this.archiveDir()) {
349670
- const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
349842
+ async writeSessionArchiveAsync(messages, context, archiveDir = this.archiveDir()) {
349843
+ const archive = this.buildSessionArchive(messages, context, archiveDir);
349671
349844
  await fs25.promises.mkdir(archiveDir, { recursive: true });
349672
349845
  const outPath = path28.join(archiveDir, archive.filename);
349673
349846
  const tempPath = `${outPath}.${process.pid}.${crypto14.randomUUID()}.tmp`;
@@ -349754,30 +349927,36 @@ ${msg.content}
349754
349927
  * large markdown payload and manifest use promise-based filesystem I/O so
349755
349928
  * independent workspaces can archive in parallel without freezing Electron.
349756
349929
  */
349757
- async archiveConversationAsync(conversationId) {
349758
- return await this.archiveConversationAsyncUnlocked(conversationId);
349930
+ async archiveConversationAsync(conversationId, ws = this.workspace.current) {
349931
+ return await this.archiveConversationAsyncUnlocked(conversationId, ws);
349759
349932
  }
349760
- async archiveConversationAsyncUnlocked(conversationId) {
349761
- const ws = this.workspace.current;
349933
+ async archiveConversationAsyncUnlocked(conversationId, targetWorkspace = this.workspace.current) {
349934
+ const ws = targetWorkspace || this.workspace.current;
349762
349935
  if (!ws) return null;
349763
349936
  const clean = this.safeConversationId(conversationId || "default");
349764
- const stateKey2 = this.workspaceConversationStateKey(clean);
349937
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, ws);
349765
349938
  if (!stateKey2) return null;
349766
349939
  const memoryKey = `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
349767
349940
  const archiveDir = path28.join(ws.path, "archive");
349768
- const workspacePrefix = this.workspaceConversationPrefix() || "";
349769
- const archiveMode = this.modeName();
349770
- const archiveModel = this.model;
349941
+ const workspacePrefix = this.workspaceConversationPrefixFor(ws) || "";
349771
349942
  const cachedStored = this.readStoredConversationState(ws);
349772
349943
  const stored = JSON.parse(JSON.stringify(cachedStored || {}));
349773
349944
  const persisted = stored.conversations?.[stateKey2];
349774
349945
  if (persisted) this.normalizeConversationTree(persisted);
349775
349946
  const memory = this.workspaceConversations.get(memoryKey);
349947
+ const archiveMode = persisted?.mode || memory?.mode || "build";
349948
+ const archiveSelection = persisted?.modelSelection || memory?.modelSelection;
349949
+ const archiveModel = archiveSelection?.kind === "deployment" ? archiveSelection.modelId : archiveSelection?.kind === "auto" ? "auto" : "auto";
349950
+ const archiveGoal = persisted?.goal || memory?.goal || null;
349776
349951
  const persistedMessagesAvailable = persisted?.chatMessages !== void 0;
349777
349952
  const sourceMessages = persisted?.chatMessages ?? memory?.chatMessages ?? [];
349778
349953
  const sourceHistory = persistedMessagesAvailable ? persisted?.history ?? [] : memory?.history ?? persisted?.history ?? [];
349779
349954
  const messages = this.normalizeConversationChatMessages(sourceMessages, sourceHistory);
349780
- const filename = await this.writeSessionArchiveAsync(messages, archiveMode, archiveModel, archiveDir);
349955
+ const filename = await this.writeSessionArchiveAsync(messages, {
349956
+ mode: archiveMode,
349957
+ model: archiveModel,
349958
+ goal: archiveGoal
349959
+ }, archiveDir);
349781
349960
  const archiveEntry = persisted ? JSON.parse(JSON.stringify(persisted)) : {
349782
349961
  title: this.titleFromMessages(messages, clean),
349783
349962
  chatMessages: messages,
@@ -349830,7 +350009,9 @@ ${msg.content}
349830
350009
  this.workspaceConversations.delete(memoryKey);
349831
350010
  const duplicateMemoryKey = `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
349832
350011
  this.workspaceConversations.delete(duplicateMemoryKey);
349833
- if (clean === this.safeConversationId(this.activeConversationId || "default")) {
350012
+ const currentWs = this.workspace.current;
350013
+ const isCurrentWorkspace = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(ws.path);
350014
+ if (isCurrentWorkspace && clean === this.safeConversationId(this.activeConversationId || "default")) {
349834
350015
  this.activeConversationId = nextActiveId || "default";
349835
350016
  this.loadWorkspaceConversationState();
349836
350017
  }
@@ -351075,7 +351256,7 @@ ${persisted.map((guide, index) => `Guide ${index + 1}: ${guide.text}`).join("\n"
351075
351256
  return `${accepted.output}
351076
351257
  ${settled?.result || settled?.error || ""}`.trim();
351077
351258
  }
351078
- async handleSubagentEnvelope(args) {
351259
+ async handleSubagentEnvelope(args, requireRunningBuild = false) {
351079
351260
  try {
351080
351261
  const params = JSON.parse(args);
351081
351262
  const preset = this.resolveSubagentPreset(params);
@@ -351090,6 +351271,36 @@ ${settled?.result || settled?.error || ""}`.trim();
351090
351271
  const requestedModel = this.normalizeSubagentModelSelection(params.model || preset?.model || inheritedModel);
351091
351272
  const activeDeployment = this.activeDeployment();
351092
351273
  const peerModel = requestedModel !== "auto" && !parseDeploymentSelectionValue2(requestedModel) && activeDeployment && requestedModel === activeDeployment.modelId ? `deployment:${encodeURIComponent(activeDeployment.providerId)}:${encodeURIComponent(activeDeployment.modelId)}` : requestedModel;
351274
+ const buildRunId = this.currentWorkRunId();
351275
+ const runningBuild = buildRunId ? this.workRuns.find((run) => run.runId === buildRunId && run.status === "running") : void 0;
351276
+ const limit = this.subagentConcurrencyLimit();
351277
+ if (requireRunningBuild && !runningBuild) {
351278
+ return {
351279
+ ok: false,
351280
+ output: "[SubAgent terminated] No running Build Block owns this call; no SubAgent was created.",
351281
+ error: "SubAgent tool calls require a running Build Block.",
351282
+ metadata: { kind: "subagent", buildRunId: buildRunId || "", limit, terminated: true }
351283
+ };
351284
+ }
351285
+ if (buildRunId && !runningBuild) {
351286
+ return {
351287
+ ok: false,
351288
+ output: `[SubAgent terminated] Build Block ${buildRunId} is not running; no SubAgent was created.`,
351289
+ error: "SubAgent creation requires a running Build Block.",
351290
+ metadata: { kind: "subagent", buildRunId, limit, terminated: true }
351291
+ };
351292
+ }
351293
+ if (runningBuild) {
351294
+ const activeForBuild = this.subagents.activeCountForBuild(buildRunId);
351295
+ if (activeForBuild >= limit) {
351296
+ return {
351297
+ ok: false,
351298
+ output: `[SubAgent terminated] Build Block ${buildRunId} reached the ${this.intelligence === "ultra" ? "Ultra" : "non-Ultra"} hard limit (${limit}); no SubAgent was created or queued.`,
351299
+ error: `SubAgent hard limit reached for Build Block ${buildRunId}: ${activeForBuild}/${limit}.`,
351300
+ metadata: { kind: "subagent", buildRunId, intelligence: this.intelligence, activeForBuild, limit, terminated: true }
351301
+ };
351302
+ }
351303
+ }
351093
351304
  const id = this.subagents.create(
351094
351305
  name50,
351095
351306
  prompt,
@@ -351099,7 +351310,9 @@ ${settled?.result || settled?.error || ""}`.trim();
351099
351310
  this.runtimeActorId,
351100
351311
  peerFlow,
351101
351312
  peerGoal,
351102
- Number(params.flow_pc ?? params.flowPc ?? (peerMode === "flow" ? this.flowPc : 0))
351313
+ Number(params.flow_pc ?? params.flowPc ?? (peerMode === "flow" ? this.flowPc : 0)),
351314
+ runningBuild?.runId || "",
351315
+ this.intelligence
351103
351316
  );
351104
351317
  const sa = this.subagents.get(id);
351105
351318
  if (sa && preset) {
@@ -351838,6 +352051,13 @@ ${items.map((item) => this.formatAutomation(item)).join("\n")}`;
351838
352051
  }
351839
352052
  latestSubmittedImages(attachmentId = "") {
351840
352053
  const normalizedId = String(attachmentId || "").trim();
352054
+ const captured = hydrateConversationImageAttachments(this.rootPath, this.latestCapturedImageInputs).flatMap((attachment) => attachment.dataUrl ? [{ id: attachment.id, dataUrl: attachment.dataUrl }] : []);
352055
+ if (normalizedId) {
352056
+ const capturedMatch = captured.find((item) => item.id === normalizedId);
352057
+ if (capturedMatch) return [capturedMatch];
352058
+ } else if (captured.length) {
352059
+ return captured;
352060
+ }
351841
352061
  for (let index = this.chatMessages.length - 1; index >= 0; index -= 1) {
351842
352062
  const message = this.chatMessages[index];
351843
352063
  if (message?.role !== "user") continue;
@@ -352407,7 +352627,7 @@ ${custom}`);
352407
352627
  if (this.intelligence === "ultra") {
352408
352628
  parts.push([
352409
352629
  "[Ultra Intelligence \u2013 Orchestrator Role]",
352410
- "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.",
352630
+ "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.",
352411
352631
  "Do not attempt to do all the work yourself. Use SubAgents for parallel investigation, verification, implementation, and review."
352412
352632
  ].join("\n"));
352413
352633
  }
@@ -352513,7 +352733,7 @@ ${custom}`);
352513
352733
  "- 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.",
352514
352734
  "- 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.",
352515
352735
  "- 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.",
352516
- `- 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.`,
352736
+ `- 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.`,
352517
352737
  `- Visible output contract: assistant replies are sanitized before display to remove hidden-reasoning markers. ${visibleOutputContract}`,
352518
352738
  "- 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."
352519
352739
  ].join("\n");
@@ -353022,6 +353242,155 @@ var ConversationKernel = class {
353022
353242
  followUp: queued?.followUp.slice() || []
353023
353243
  };
353024
353244
  }
353245
+ queueItems(target) {
353246
+ const runtime = this.findRuntime(target);
353247
+ if (!runtime) return [];
353248
+ return runtime.pendingNextTurn.flatMap((item) => {
353249
+ if (item.queueMode !== "followUp") return [];
353250
+ if (typeof item.message === "string") return [];
353251
+ const id = String(item.message.clientMessageId || "");
353252
+ if (!id) return [];
353253
+ return [{
353254
+ id,
353255
+ text: String(item.message.visibleUserInput || item.message.text || "").replace(/^\[Next queued while current turn is running\]\n/, ""),
353256
+ queueMode: item.queueMode,
353257
+ requestedMode: item.message.visibleMode,
353258
+ goalObjective: item.message.goalObjective,
353259
+ runId: item.message.runId,
353260
+ createdAt: String(item.message.createdAt || "")
353261
+ }];
353262
+ });
353263
+ }
353264
+ enqueueNext(target, input) {
353265
+ const runtime = this.findRuntime(target);
353266
+ if (!runtime?.activePromise || !runtime.runId) throw new Error("Target conversation is not running");
353267
+ const id = String(input.id || "").trim().slice(0, 200);
353268
+ const text = String(input.text || "").trim();
353269
+ if (!id || !text) throw new Error("Queue item id and text are required");
353270
+ const existing = this.queueItems(runtime.target).find((item) => item.id === id);
353271
+ if (existing) return existing;
353272
+ const prompt = `[Next queued while current turn is running]
353273
+ ${text}`;
353274
+ const createdAt = String(input.createdAt || (/* @__PURE__ */ new Date()).toISOString());
353275
+ runtime.pendingNextTurn.push({
353276
+ message: {
353277
+ text: prompt,
353278
+ visibleUserInput: text,
353279
+ visibleMode: String(input.requestedMode || "build"),
353280
+ goalObjective: String(input.goalObjective || "") || void 0,
353281
+ clientMessageId: id,
353282
+ runId: runtime.runId,
353283
+ createdAt
353284
+ },
353285
+ queueMode: "followUp"
353286
+ });
353287
+ runtime.runner.retainConversationContinuations([{
353288
+ content: prompt,
353289
+ queueMode: "followUp",
353290
+ clientMessageId: id,
353291
+ runId: runtime.runId,
353292
+ createdAt
353293
+ }]);
353294
+ this.trackQueuedMessage(runtime, prompt, "followUp");
353295
+ this.emitQueueUpdate(runtime);
353296
+ return this.queueItems(runtime.target).find((item) => item.id === id);
353297
+ }
353298
+ updateQueueItem(target, idInput, textInput) {
353299
+ const runtime = this.findRuntime(target);
353300
+ if (!runtime) throw new Error("Target conversation runtime is unavailable");
353301
+ const id = String(idInput || "").trim();
353302
+ const text = String(textInput || "").trim();
353303
+ if (!id || !text) throw new Error("Queue item id and text are required");
353304
+ const pending3 = runtime.pendingNextTurn.find((item) => typeof item.message !== "string" && item.message.clientMessageId === id);
353305
+ if (!pending3 || typeof pending3.message === "string") throw new Error("Queue item is no longer editable");
353306
+ const oldPrompt = pending3.message.text;
353307
+ pending3.message.text = `[Next queued while current turn is running]
353308
+ ${text}`;
353309
+ pending3.message.visibleUserInput = text;
353310
+ runtime.runner.consumeConversationContinuation({ content: oldPrompt, queueMode: pending3.queueMode, clientMessageId: id });
353311
+ runtime.runner.retainConversationContinuations([{
353312
+ content: pending3.message.text,
353313
+ queueMode: pending3.queueMode,
353314
+ clientMessageId: id,
353315
+ runId: pending3.message.runId
353316
+ }]);
353317
+ this.replaceTrackedQueuedMessage(runtime, oldPrompt, pending3.message.text, pending3.queueMode);
353318
+ this.emitQueueUpdate(runtime);
353319
+ return this.queueItems(runtime.target).find((item) => item.id === id);
353320
+ }
353321
+ deleteQueueItem(target, idInput) {
353322
+ const runtime = this.findRuntime(target);
353323
+ if (!runtime) return false;
353324
+ const id = String(idInput || "").trim();
353325
+ const index = runtime.pendingNextTurn.findIndex((item) => typeof item.message !== "string" && item.message.clientMessageId === id);
353326
+ if (index < 0) return false;
353327
+ const [removed] = runtime.pendingNextTurn.splice(index, 1);
353328
+ if (typeof removed.message !== "string") {
353329
+ runtime.runner.consumeConversationContinuation({ content: removed.message.text, queueMode: removed.queueMode, clientMessageId: id });
353330
+ this.consumeQueuedMessage(runtime, removed.message.text);
353331
+ }
353332
+ this.emitQueueUpdate(runtime);
353333
+ return true;
353334
+ }
353335
+ setQueuePaused(target, paused) {
353336
+ const runtime = this.findRuntime(target);
353337
+ if (!runtime) throw new Error("Target conversation runtime is unavailable");
353338
+ runtime.queuePaused = paused;
353339
+ this.emitQueueUpdate(runtime);
353340
+ if (!paused && !runtime.activePromise && runtime.pendingNextTurn.length) {
353341
+ this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
353342
+ }
353343
+ return runtime.queuePaused;
353344
+ }
353345
+ queueAction(target, action, input = {}) {
353346
+ const runtime = this.findRuntime(target);
353347
+ if (!runtime) throw new Error("Target conversation runtime is unavailable");
353348
+ let receipt;
353349
+ if (action === "enqueue") {
353350
+ this.enqueueNext(runtime.target, {
353351
+ id: String(input.id || ""),
353352
+ text: String(input.text || ""),
353353
+ requestedMode: input.requestedMode,
353354
+ goalObjective: input.goalObjective,
353355
+ createdAt: input.createdAt
353356
+ });
353357
+ } else if (action === "update") {
353358
+ this.updateQueueItem(runtime.target, String(input.id || ""), String(input.text || ""));
353359
+ } else if (action === "delete") {
353360
+ if (!this.deleteQueueItem(runtime.target, String(input.id || ""))) throw new Error("Queue item was not found");
353361
+ } else if (action === "toggle_pause") {
353362
+ this.setQueuePaused(runtime.target, !runtime.queuePaused);
353363
+ } else if (action === "guide") {
353364
+ const item = this.queueItems(runtime.target).find((entry) => entry.id === String(input.id || ""));
353365
+ if (!item) throw new Error("Queue item was not found");
353366
+ receipt = this.enqueueGuide({
353367
+ clientMessageId: item.id,
353368
+ guideId: (0, import_crypto16.randomUUID)(),
353369
+ target: runtime.target,
353370
+ runId: runtime.runId,
353371
+ deliveryMode: "steer",
353372
+ text: item.goalObjective ? `Goal for the current Build:
353373
+ ${item.goalObjective}` : item.text,
353374
+ goalObjective: item.goalObjective,
353375
+ createdAt: item.createdAt || (/* @__PURE__ */ new Date()).toISOString()
353376
+ });
353377
+ if (receipt.status === "rejected") return {
353378
+ ok: false,
353379
+ queueItems: this.queueItems(runtime.target),
353380
+ queuePaused: runtime.queuePaused,
353381
+ queued: this.queued(runtime.target),
353382
+ receipt
353383
+ };
353384
+ this.deleteQueueItem(runtime.target, item.id);
353385
+ }
353386
+ return {
353387
+ ok: true,
353388
+ queueItems: this.queueItems(runtime.target),
353389
+ queuePaused: runtime.queuePaused,
353390
+ queued: this.queued(runtime.target),
353391
+ receipt
353392
+ };
353393
+ }
353025
353394
  events(target) {
353026
353395
  return this.findRuntime(target)?.events.slice() || [];
353027
353396
  }
@@ -353050,6 +353419,8 @@ var ConversationKernel = class {
353050
353419
  workRuns: this.bindWorkRunsToRuntimeTarget(conversationSnapshot.workRuns, normalized),
353051
353420
  target: normalized,
353052
353421
  queued: this.queued(normalized),
353422
+ queueItems: this.queueItems(normalized),
353423
+ queuePaused: runtime?.queuePaused === true,
353053
353424
  workEvents: this.events(normalized),
353054
353425
  runtime: this.runtimeState(normalized),
353055
353426
  mode: runner.mode,
@@ -353485,7 +353856,7 @@ var ConversationKernel = class {
353485
353856
  if (runtime.stopRequestedRunId === runId) {
353486
353857
  stopped = true;
353487
353858
  this.settleCooperativeStop(runtime, runId);
353488
- } else if (runtime.pendingNextTurn.length > 0) {
353859
+ } else if (!runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
353489
353860
  this.schedulePendingRuntimeContinuation(runtime, runId);
353490
353861
  }
353491
353862
  }
@@ -353520,7 +353891,7 @@ var ConversationKernel = class {
353520
353891
  return this.result(runtime, lastTokens);
353521
353892
  }
353522
353893
  for (; ; ) {
353523
- while (runtime.pendingNextTurn.length > 0) {
353894
+ while (!runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
353524
353895
  if (runtime.stopRequestedRunId === runtime.runId) return this.result(runtime, lastTokens);
353525
353896
  const next = runtime.pendingNextTurn.shift();
353526
353897
  if (next.queueMode === "steer" && typeof next.message !== "string" && !!next.message.clientMessageId) {
@@ -353562,11 +353933,11 @@ ${batchText}`,
353562
353933
  this.mirrorHostIfTargetActive(runtime);
353563
353934
  return this.result(runtime, lastTokens);
353564
353935
  }
353565
- this.clearQueued(runtime);
353936
+ if (!runtime.pendingNextTurn.length) this.clearQueued(runtime);
353566
353937
  const completedRunId = runtime.runId;
353567
353938
  runtime.runner.finishConversationWorkRun(completedRunId, "completed");
353568
353939
  await new Promise((resolve16) => setImmediate(resolve16));
353569
- if (runtime.runId === completedRunId && runtime.stopRequestedRunId !== completedRunId && runtime.pendingNextTurn.length > 0) {
353940
+ if (runtime.runId === completedRunId && runtime.stopRequestedRunId !== completedRunId && !runtime.queuePaused && runtime.pendingNextTurn.length > 0) {
353570
353941
  runtime.guideAcceptanceClosedRunId = "";
353571
353942
  if (!runtime.runner.resumeConversationWorkRun(completedRunId)) {
353572
353943
  throw new Error(`Unable to resume deferred Guide work run ${completedRunId}`);
@@ -353657,6 +354028,7 @@ Review this persisted peer result and summarize or continue the parent task as n
353657
354028
  events: [],
353658
354029
  pendingNextTurn: [],
353659
354030
  queued: { steering: [], followUp: [] },
354031
+ queuePaused: false,
353660
354032
  runId: "",
353661
354033
  generation: this.generations.get(target.runtimeKey) || 0,
353662
354034
  stopRequestedRunId: "",
@@ -353752,7 +354124,7 @@ Review this persisted peer result and summarize or continue the parent task as n
353752
354124
  }
353753
354125
  setImmediate(() => {
353754
354126
  runtime.pendingContinuationRunId = void 0;
353755
- if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId) return;
354127
+ if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId || runtime.queuePaused) return;
353756
354128
  const next = runtime.pendingNextTurn.shift();
353757
354129
  if (!next) return;
353758
354130
  const message = typeof next.message === "string" ? { text: next.message, runId } : { ...next.message, runId: next.message.runId || runId };
@@ -353903,7 +354275,36 @@ Review this persisted peer result and summarize or continue the parent task as n
353903
354275
  const text = typeof message === "string" ? message : message.text;
353904
354276
  const prompt = isSteer ? text : `[Next queued while current turn is running]
353905
354277
  ${text}`;
353906
- if (!isSteer) this.trackQueuedMessage(runtime, prompt, queueMode);
354278
+ if (!isSteer) {
354279
+ const structured = typeof message === "string" ? null : message;
354280
+ const clientMessageId = String(structured?.clientMessageId || (0, import_crypto16.randomUUID)());
354281
+ const queuedMessage = {
354282
+ ...structured || {},
354283
+ text: prompt,
354284
+ visibleUserInput: structured?.visibleUserInput || text,
354285
+ visibleMode: structured?.visibleMode || runtime.options.mode,
354286
+ clientMessageId,
354287
+ runId: structured?.runId || runtime.runId,
354288
+ createdAt: String(structured?.createdAt || (/* @__PURE__ */ new Date()).toISOString())
354289
+ };
354290
+ if (!runtime.pendingNextTurn.some((item) => typeof item.message !== "string" && item.message.clientMessageId === clientMessageId)) {
354291
+ runtime.pendingNextTurn.push({ message: queuedMessage, queueMode: "followUp" });
354292
+ }
354293
+ runtime.runner.retainConversationContinuations([{
354294
+ content: prompt,
354295
+ queueMode: "followUp",
354296
+ clientMessageId,
354297
+ runId: queuedMessage.runId,
354298
+ images: queuedMessage.images?.map((image) => ({ ...image })),
354299
+ attachments: queuedMessage.attachments?.map((attachment) => ({ ...attachment })),
354300
+ createdAt: queuedMessage.createdAt
354301
+ }]);
354302
+ this.trackQueuedMessage(runtime, prompt, "followUp");
354303
+ runtime.runner.recordWorkStatus(runtime.stopRequestedRunId === runtime.runId ? "Next message retained while stopping." : "Next message queued.");
354304
+ if (runtime.stopRequestedRunId === runtime.runId) runtime.forceStopArmedRunId = "";
354305
+ this.emitQueueUpdate(runtime);
354306
+ return;
354307
+ }
353907
354308
  if (runtime.stopRequestedRunId === runtime.runId) {
353908
354309
  runtime.forceStopArmedRunId = "";
353909
354310
  const structured = typeof message === "string" ? void 0 : message;
@@ -353949,6 +354350,12 @@ ${text}`;
353949
354350
  }
353950
354351
  if (changed) this.emitQueueUpdate(runtime);
353951
354352
  }
354353
+ replaceTrackedQueuedMessage(runtime, oldMessage, nextMessage, queueMode) {
354354
+ this.queueState(runtime);
354355
+ const list = queueMode === "steer" ? runtime.queued.steering : runtime.queued.followUp;
354356
+ const index = list.indexOf(oldMessage);
354357
+ if (index >= 0) list[index] = nextMessage;
354358
+ }
353952
354359
  emitQueueUpdate(runtime) {
353953
354360
  this.queueState(runtime);
353954
354361
  runtime.runner.emitWorkEvent({
@@ -354070,9 +354477,10 @@ ${text}`;
354070
354477
  const runId = runtime.runId;
354071
354478
  await Promise.resolve();
354072
354479
  if (runtime.runId !== runId || runtime.stopRequestedRunId === runId) return true;
354073
- if (runtime.pendingNextTurn.length > 0 || runtime.runner.subagents.readRootInbox().length > 0) return false;
354480
+ const hasSteering = runtime.pendingNextTurn.some((item) => item.queueMode === "steer");
354481
+ if (hasSteering || runtime.runner.subagents.readRootInbox().length > 0) return false;
354074
354482
  runtime.guideAcceptanceClosedRunId = runId;
354075
- if (runtime.pendingNextTurn.length > 0 || runtime.runner.subagents.readRootInbox().length > 0) {
354483
+ if (runtime.pendingNextTurn.some((item) => item.queueMode === "steer") || runtime.runner.subagents.readRootInbox().length > 0) {
354076
354484
  runtime.guideAcceptanceClosedRunId = "";
354077
354485
  return false;
354078
354486
  }
@@ -354225,6 +354633,9 @@ async function handle(request) {
354225
354633
  target
354226
354634
  });
354227
354635
  }
354636
+ if (request.method === "queue_action") {
354637
+ return kernel.queueAction(checkedTarget(request.params.target), request.params.action, request.params.input);
354638
+ }
354228
354639
  if (request.method === "checkpoint") return kernel.checkpoint(checkedTarget(request.params.target));
354229
354640
  if (request.method === "context_compress") {
354230
354641
  return kernel.compressContext(checkedTarget(request.params.target), request.params.options);