newmark-agent 0.4.6 → 0.4.7

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.
@@ -327567,7 +327567,7 @@ var NATIVE_TOOL_CATALOG = [
327567
327567
  { 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
327568
  { name: "terminal_takeover", label: "Terminal takeover", description: "Maintain a persistent Agent-controlled shell session.", category: "desktop", defaultEnabled: true },
327569
327569
  { 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" },
327570
+ { name: "SubAgent", label: "SubAgent", description: "Create one real same-conversation peer SubAgent.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327571
327571
  { name: "subagent_list", label: "Subagent list", description: "List same-conversation peer agents.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327572
327572
  { 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
327573
  { 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 +328319,7 @@ function defaultConfig() {
328319
328319
  auto_archive_on_close: { _description: "Auto archive on close", _type: "boolean", value: true }
328320
328320
  },
328321
328321
  remote: {
328322
- touch_enabled: { _description: "Allow mobile devices to reach this Newmark instance over Tailscale", _type: "boolean", value: true }
328322
+ touch_enabled: { _description: "Allow mobile devices to reach this Newmark instance on the same LAN / Tailscale network", _type: "boolean", value: true }
328323
328323
  },
328324
328324
  models: {
328325
328325
  providers: { _description: "LLM providers", _type: "array", value: [] },
@@ -328867,14 +328867,18 @@ var ChatCompletionsAdapter = class {
328867
328867
  ...request.systemPrompt ? [{ role: CHAT_SYSTEM_ROLE, content: request.systemPrompt }] : [],
328868
328868
  ...openAIChatMessages(request.messages)
328869
328869
  ];
328870
+ const tools = this.serializeTools(request.tools);
328870
328871
  const body = {
328871
328872
  model: request.model,
328872
328873
  messages,
328873
328874
  temperature: request.temperature,
328874
- max_tokens: request.maxOutputTokens,
328875
- tools: this.serializeTools(request.tools),
328876
- tool_choice: "auto"
328875
+ max_tokens: request.maxOutputTokens
328877
328876
  };
328877
+ if (tools.length) {
328878
+ body.tools = tools;
328879
+ body.tool_choice = "auto";
328880
+ body.parallel_tool_calls = true;
328881
+ }
328878
328882
  if (request.reasoningEffort) body.reasoning_effort = request.reasoningEffort;
328879
328883
  if (request.sessionId) body.session_id = request.sessionId;
328880
328884
  const base2 = request.baseUrl.replace(/\/+$/, "");
@@ -329128,6 +329132,7 @@ var ResponsesAdapter = class {
329128
329132
  if (tools.length) {
329129
329133
  body.tools = tools;
329130
329134
  body.tool_choice = "auto";
329135
+ body.parallel_tool_calls = true;
329131
329136
  }
329132
329137
  const base2 = request.baseUrl.replace(/\/+$/, "");
329133
329138
  return {
@@ -335765,6 +335770,10 @@ var MODE_SCOPED_TOOLS = /* @__PURE__ */ new Set([
335765
335770
  "task_read",
335766
335771
  "task_create",
335767
335772
  "question",
335773
+ "SubAgent",
335774
+ "subagent_create",
335775
+ // Legacy runtime alias. It is no longer published to models because its
335776
+ // generic name collides with the persistent task checklist.
335768
335777
  "task",
335769
335778
  "subagent_list",
335770
335779
  "subagent_read",
@@ -335798,6 +335807,8 @@ var PLAN_READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
335798
335807
  "skill",
335799
335808
  "linked_plan",
335800
335809
  "build_history_query",
335810
+ "SubAgent",
335811
+ "subagent_create",
335801
335812
  "task",
335802
335813
  "subagent_list",
335803
335814
  "subagent_read",
@@ -335821,7 +335832,8 @@ var CONCURRENCY_SAFE_TOOLS = /* @__PURE__ */ new Set([
335821
335832
  "web_fetch",
335822
335833
  "git_status",
335823
335834
  "file_audit",
335824
- "repo_security_audit"
335835
+ "repo_security_audit",
335836
+ "SubAgent"
335825
335837
  ]);
335826
335838
  function isConcurrencySafeTool(name50, riskLevel) {
335827
335839
  const toolName = String(name50 || "").trim();
@@ -336985,7 +336997,7 @@ var ToolExecutor = class {
336985
336997
  remote_root: { type: "string" },
336986
336998
  remote_path: { type: "string" }
336987
336999
  }, ["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"]),
337000
+ 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
337001
  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
337002
  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
337003
  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 +337174,8 @@ var ToolExecutor = class {
337162
337174
  return normalizeToolResult(output, { tool, workspacePath: wsPath, mode: context.mode || "" });
337163
337175
  }
337164
337176
  validateInvocation(tool, argsStr, _mode = "", inputSchema) {
337165
- if (inputSchema === void 0 && !isNativeToolEnabled(tool, this.config.nativeToolEnabled())) {
337177
+ const schemaTool = tool === "task" || tool === "subagent_create" ? "SubAgent" : tool;
337178
+ if (inputSchema === void 0 && !isNativeToolEnabled(schemaTool, this.config.nativeToolEnabled())) {
337166
337179
  return { ok: false, error: `[tool disabled] ${tool} is disabled in Settings > Tools.` };
337167
337180
  }
337168
337181
  let parsed;
@@ -337184,11 +337197,11 @@ var ToolExecutor = class {
337184
337197
  }, args);
337185
337198
  return validation2.ok ? { ok: false, error: "[permission] Question is disabled by fully_autonomous option feedback." } : { ok: false, error: `[tool schema error] ${validation2.error}` };
337186
337199
  }
337187
- const definition = inputSchema === void 0 ? this.definitions().find((candidate) => candidate.function?.name === tool) : { function: { name: tool, parameters: inputSchema } };
337200
+ const definition = inputSchema === void 0 ? this.definitions().find((candidate) => candidate.function?.name === schemaTool) : { function: { name: tool, parameters: inputSchema } };
337188
337201
  if (!definition) {
337189
337202
  return { ok: false, error: `[tool unsupported] ${tool || "(missing tool)"} is not available for the ${this.hostProfile.kind} host on ${this.hostProfile.platform}.` };
337190
337203
  }
337191
- const validation = this.argumentValidators.validate(tool, definition.function.parameters, args);
337204
+ const validation = this.argumentValidators.validate(schemaTool, definition.function.parameters, args);
337192
337205
  return validation.ok ? { ok: true, args } : { ok: false, error: `[tool schema error] ${validation.error}` };
337193
337206
  }
337194
337207
  async execute(tool, argsStr, wsPath, context = {}) {
@@ -337510,7 +337523,9 @@ var ToolExecutor = class {
337510
337523
  case "ssh_workspace":
337511
337524
  return await this.sshWorkspace(args, wsPath, context.signal);
337512
337525
  case "task":
337513
- return `[task] Subagent request accepted: ${g2("name")}`;
337526
+ case "subagent_create":
337527
+ case "SubAgent":
337528
+ return `[SubAgent] SubAgent request accepted: ${g2("name")}`;
337514
337529
  case "subagent_send":
337515
337530
  return `[subagent_send] Routed to Agent runtime: ${g2("name")}`;
337516
337531
  case "subagent_read":
@@ -338683,6 +338698,7 @@ var SubagentManager = class {
338683
338698
  queueMicrotask(() => this.pump());
338684
338699
  }
338685
338700
  bind(options) {
338701
+ if (options.concurrency !== void 0) this.setConcurrencyLimit(options.concurrency);
338686
338702
  if (options.executor) this.executor = options.executor;
338687
338703
  if (options.onChange) this.onChange = options.onChange;
338688
338704
  if (options.persist) this.persist = options.persist;
@@ -338699,12 +338715,13 @@ var SubagentManager = class {
338699
338715
  removeRootInboxListener(listener) {
338700
338716
  this.rootInboxListeners.delete(listener);
338701
338717
  }
338702
- create(name50, prompt, model, inputMode, agentMode = "build", createdByAgentId = this.rootAgentId, flowName = "", goalObjective = "", flowPc = 0) {
338718
+ create(name50, prompt, model, inputMode, agentMode = "build", createdByAgentId = this.rootAgentId, flowName = "", goalObjective = "", flowPc = 0, buildRunId = "", intelligenceTier = "") {
338703
338719
  const id = (0, import_crypto8.randomUUID)();
338704
338720
  const shortId = id.replace(/-/g, "").slice(0, 8);
338705
338721
  const slug = natureSlug(name50);
338706
- const displayName = `${slug}-${shortId}`;
338707
- const qualifiedName = `${displayName}--${id}`;
338722
+ const createdName = String(name50 || "SubAgent").replace(/\s+/g, " ").trim().slice(0, 160) || "SubAgent";
338723
+ const displayName = createdName;
338724
+ const qualifiedName = `${slug}--${id}`;
338708
338725
  const stamp = now();
338709
338726
  const record = {
338710
338727
  id,
@@ -338712,9 +338729,11 @@ var SubagentManager = class {
338712
338729
  natureSlug: slug,
338713
338730
  displayName,
338714
338731
  qualifiedName,
338715
- name: slug,
338732
+ name: createdName,
338716
338733
  conversationId: this.conversationId,
338717
338734
  createdByAgentId,
338735
+ buildRunId: String(buildRunId || "").trim() || void 0,
338736
+ intelligenceTier: String(intelligenceTier || "").trim() || void 0,
338718
338737
  prompt,
338719
338738
  model: model || "default",
338720
338739
  inputMode: inputMode || "guide",
@@ -339037,6 +339056,20 @@ var SubagentManager = class {
339037
339056
  listAll() {
339038
339057
  return [...this.subs.values()].map(cloneRecord);
339039
339058
  }
339059
+ activeCountForBuild(buildRunId) {
339060
+ const target = String(buildRunId || "").trim();
339061
+ if (!target) return 0;
339062
+ return [...this.subs.values()].filter(
339063
+ (record) => record.buildRunId === target && (record.status === "queued" || record.status === "working")
339064
+ ).length;
339065
+ }
339066
+ setConcurrencyLimit(value) {
339067
+ this.concurrency = Math.max(1, Math.min(16, Math.floor(Number(value) || 4)));
339068
+ this.pump();
339069
+ }
339070
+ concurrencyLimit() {
339071
+ return this.concurrency;
339072
+ }
339040
339073
  pauseScheduling() {
339041
339074
  if (this.schedulingPaused) return;
339042
339075
  this.schedulingPaused = true;
@@ -340202,7 +340235,8 @@ function inferDomain(name50) {
340202
340235
  }
340203
340236
  if (name50 === "question") return "interaction";
340204
340237
  if (name50 === "skill") return "skills";
340205
- if (name50 === "task") return "subagent";
340238
+ if (name50 === "task" || name50 === "subagent_create" || name50 === "SubAgent") return "subagent";
340239
+ if (/^task_(read|create)$/.test(name50)) return "plan";
340206
340240
  if (/^(linked_plan|build_history_query)$/.test(name50)) return "plan";
340207
340241
  return "general";
340208
340242
  }
@@ -340466,7 +340500,7 @@ function resetPublicAssistantDeltaFilter(agent) {
340466
340500
  function prepareAssistantToolVisibility(agent, definitions) {
340467
340501
  const names = definitions.map(toolDefinitionName);
340468
340502
  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)),
340503
+ brokerOnly: names.includes(TOOL_PROVISION_NAME) && names.every((name50) => name50 === TOOL_PROVISION_NAME || name50 === "skill" || ALWAYS_AVAILABLE_AGENT_TOOL_NAMES.has(name50)),
340470
340504
  pending: [],
340471
340505
  released: false
340472
340506
  });
@@ -340758,7 +340792,7 @@ async function runAgentKernel(agent) {
340758
340792
  const requestStartedAt = Date.now();
340759
340793
  let firstTokenRecorded = false;
340760
340794
  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));
340795
+ 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
340796
  currentAgent.beginRouteAttempt();
340763
340797
  try {
340764
340798
  const currentProvider = currentAgent.engineModel();
@@ -340993,6 +341027,8 @@ function buildBuildContextBootstrap(agent, messages, options) {
340993
341027
  buildConversationTaskLedger(agent),
340994
341028
  "## Tool Awareness Bootstrap",
340995
341029
  "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.",
341030
+ "Only bash, pwd, read, write, edit, delete_file, glob, and grep are foundational tools with initial full schemas (subject to mode and policy filtering).",
341031
+ "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
341032
  ...catalogLines.length ? catalogLines : ["- No callable tools are available for this provider turn."],
340997
341033
  `Necessary full schemas supplied natively for this provider turn: ${activeNames.length ? activeNames.join(", ") : "(none; use tool_provision when its schema is available)"}.`,
340998
341034
  "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 +341218,8 @@ function routeTransitionNotice(agent, previous) {
341182
341218
  var TOOL_PROVISION_NAME = "tool_provision";
341183
341219
  var INITIAL_TOOL_SCHEMA_LIMIT = 8;
341184
341220
  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"]);
341221
+ var BASIC_INITIAL_TOOL_NAMES = /* @__PURE__ */ new Set(["bash", "pwd", "read", "write", "edit", "delete_file", "glob", "grep"]);
341222
+ var ALWAYS_AVAILABLE_AGENT_TOOL_NAMES = BASIC_INITIAL_TOOL_NAMES;
341186
341223
  var ToolProvisionSession = class {
341187
341224
  definitionsByName = /* @__PURE__ */ new Map();
341188
341225
  initialNames = /* @__PURE__ */ new Set();
@@ -341206,7 +341243,7 @@ var ToolProvisionSession = class {
341206
341243
  const name50 = toolDefinitionName(definition);
341207
341244
  if (this.definitionsByName.has(name50)) this.initialNames.add(name50);
341208
341245
  }
341209
- for (const name50 of SUBAGENT_CORE_TOOL_NAMES) {
341246
+ for (const name50 of ALWAYS_AVAILABLE_AGENT_TOOL_NAMES) {
341210
341247
  if (this.definitionsByName.has(name50)) this.initialNames.add(name50);
341211
341248
  }
341212
341249
  for (const name50 of this.provisionedNames) {
@@ -341364,29 +341401,6 @@ function toolSurfaceIdentityForAgent(agent) {
341364
341401
  optionFeedback: agent.config.getStr("agent", "option_feedback")
341365
341402
  });
341366
341403
  }
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
341404
  function routeToolSurfaceV2(agent, definitions, toolchain, task) {
341391
341405
  if (!agent.shouldExposeToolInterface()) {
341392
341406
  return {
@@ -341398,67 +341412,34 @@ function routeToolSurfaceV2(agent, definitions, toolchain, task) {
341398
341412
  ].join("\n")
341399
341413
  };
341400
341414
  }
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
- };
341415
+ const surface = definitions.filter((definition) => BASIC_INITIAL_TOOL_NAMES.has(toolDefinitionName(definition)));
341416
+ const advancedCount = Math.max(0, definitions.length - surface.length);
341417
+ let planFingerprint = "";
341418
+ if (toolchain) {
341419
+ const planner = new ToolExposurePlanner(toolchain.registry, toolchain.catalog);
341420
+ const plan = planner.plan({
341421
+ agentRunId: agent.runtimeActorId,
341422
+ buildBlockId: agent.activeConversationId || "build",
341423
+ userInput: task,
341424
+ objective: "",
341425
+ previousToolCalls: [],
341426
+ toolUsageFrequency: /* @__PURE__ */ new Map(),
341427
+ permissionScope: ["workspace"],
341428
+ tokenBudget: 2e4,
341429
+ providerToolLimit: 0
341430
+ });
341431
+ planFingerprint = plan.plan.stableToolsetHash.slice(0, 8);
341454
341432
  }
341455
341433
  return {
341456
341434
  definitions: surface,
341457
341435
  systemPromptNotice: [
341458
341436
  "## 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(",")}.`
341437
+ `The initial full-schema surface is restricted to foundational workspace tools: ${surface.map(toolDefinitionName).join(", ") || "(none allowed in this mode)"}.`,
341438
+ `${advancedCount} advanced tools are advertised by capability in the compact ${TOOL_PROVISION_NAME} catalog without loading their schemas.`,
341439
+ "Advanced tools include SubAgent, task tools, Git/GitHub, browser, Computer Use, skills, MCP, automations, Flow, and Memory Lab.",
341440
+ `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.`,
341441
+ "Do not call an advanced tool directly from the initial catalog: capability presence is not callability until provisioning has completed.",
341442
+ planFingerprint ? `Capability routing fingerprint: ${planFingerprint}.` : "Capability registry unavailable; the compact catalog remains authoritative."
341462
341443
  ].join("\n")
341463
341444
  };
341464
341445
  }
@@ -341567,7 +341548,7 @@ var INLINE_TOOL_RESULT_MAX_CHARS = 24e3;
341567
341548
  function spillOversizedToolResult(agent, name50, text) {
341568
341549
  const value = String(text || "");
341569
341550
  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)) {
341551
+ if (["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
341552
  return value;
341572
341553
  }
341573
341554
  const artifactId = agent.storeToolResultArtifact(name50, value);
@@ -341668,7 +341649,7 @@ async function executeNewmarkTool(agent, name50, args, inputSchema, signal) {
341668
341649
  if (signal?.aborted) throw abortError4();
341669
341650
  return result2;
341670
341651
  };
341671
- if (name50 === "task") return (await agent.handleSubagentEnvelope(args)).output;
341652
+ if (name50 === "task" || name50 === "subagent_create" || name50 === "SubAgent") return (await agent.handleSubagentEnvelope(args, true)).output;
341672
341653
  if (name50 === "subagent_send") return (await agent.handleSubagentContinueEnvelope(args)).output;
341673
341654
  if (name50 === "subagent_list") return agent.handleSubagentListEnvelope(args).output;
341674
341655
  if (name50 === "subagent_read") return agent.handleSubagentReadEnvelope(args).output;
@@ -344820,7 +344801,7 @@ var Agent4 = class _Agent {
344820
344801
  this.tools = new ToolExecutor(rootPath, this.config, this.ssh, this.workspace);
344821
344802
  this.skills = new SkillsManager(rootPath);
344822
344803
  this.memoryLab = new MemoryLabManager(rootPath, this.config.getStr("general", "language"));
344823
- this.subagents = new SubagentManager({ rootAgentId: this.runtimeActorId });
344804
+ this.subagents = new SubagentManager({ rootAgentId: this.runtimeActorId, concurrency: this.subagentConcurrencyLimit() });
344824
344805
  if (this.mode === "goal" && !this.goal) {
344825
344806
  this.goal = new GoalStateImpl("Set your objective");
344826
344807
  }
@@ -345419,6 +345400,7 @@ var Agent4 = class _Agent {
345419
345400
  }
345420
345401
  setIntelligence(tier, persist = false) {
345421
345402
  this.intelligence = normalizeIntelligenceTier(tier);
345403
+ this.subagents?.setConcurrencyLimit(this.subagentConcurrencyLimit());
345422
345404
  if (persist) {
345423
345405
  this.config.set("models", "default_intelligence", this.intelligence);
345424
345406
  this.config.save();
@@ -345623,12 +345605,17 @@ var Agent4 = class _Agent {
345623
345605
  return path28.join(ws.path, "conversations", "state.json");
345624
345606
  }
345625
345607
  workspaceConversationStateKey(conversationId = this.activeConversationId) {
345626
- const prefix = this.workspaceConversationPrefix();
345608
+ return this.workspaceConversationStateKeyFor(conversationId, this.workspace.current);
345609
+ }
345610
+ workspaceConversationStateKeyFor(conversationId, ws) {
345611
+ const prefix = this.workspaceConversationPrefixFor(ws);
345627
345612
  if (!prefix) return null;
345628
345613
  return `${prefix}-${this.safeConversationId(conversationId)}`;
345629
345614
  }
345630
345615
  workspaceConversationPrefix() {
345631
- const ws = this.workspace.current;
345616
+ return this.workspaceConversationPrefixFor(this.workspace.current);
345617
+ }
345618
+ workspaceConversationPrefixFor(ws) {
345632
345619
  if (!ws) return null;
345633
345620
  const supplied = String(ws.conversationStatePrefix || "").trim();
345634
345621
  if (/^(?:internal|external)-[a-f0-9]{16}$/i.test(supplied)) return supplied.toLowerCase();
@@ -347078,8 +347065,49 @@ ${String(event.toolArgs || "")}`;
347078
347065
  }
347079
347066
  }
347080
347067
  listConversationStates() {
347081
- const stored = this.readStoredConversationState();
347082
- const prefix = this.workspaceConversationPrefix() || "";
347068
+ return this.listWorkspaceConversationStates(this.workspace.current);
347069
+ }
347070
+ subagentConcurrencyLimit() {
347071
+ return this.intelligence === "ultra" ? 16 : 4;
347072
+ }
347073
+ /** 当前工作区使用内存中的前台选择;后台工作区从各自持久化状态读取 active id。 */
347074
+ activeConversationIdForWorkspace(ws) {
347075
+ const targetWs = ws || this.workspace.current;
347076
+ if (!targetWs) return "default";
347077
+ const currentWs = this.workspace.current;
347078
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347079
+ if (isCurrent) return this.safeConversationId(this.activeConversationId || "default");
347080
+ const stored = this.readStoredConversationState(targetWs);
347081
+ return this.safeConversationId(stored.activeConversationId || "default");
347082
+ }
347083
+ /**
347084
+ * 持久化的完整 work run 记录(含 interrupted/force_interrupted)。
347085
+ * 不依赖运行时内存(run 结束后内存清空,state 端点曾因此丢失被中断的构建记录),
347086
+ * 供 mobile 端点稳定透出完整对话信息;移动端按同一格式解析。
347087
+ */
347088
+ getPersistedConversationWorkRuns(conversationId, ws = null) {
347089
+ const targetWs = ws || this.workspace.current;
347090
+ const clean = this.safeConversationId(conversationId || "default");
347091
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, targetWs);
347092
+ const stored = this.readStoredConversationState(targetWs);
347093
+ const persisted = stateKey2 && stored.conversations ? stored.conversations[stateKey2] : void 0;
347094
+ const tree = persisted ? this.normalizeConversationTree(persisted) : null;
347095
+ const runtimeNodeId = String(tree?.activeNodeId || "");
347096
+ const viewedNodeId = tree ? this.resolveConversationTreePath(tree, this.storedConversationTreePath(tree, persisted?.viewedBranchNodePath, runtimeNodeId)) : "";
347097
+ const viewedNode = tree?.nodes[viewedNodeId];
347098
+ return this.normalizeWorkRuns(viewedNode?.workRuns || persisted?.workRuns);
347099
+ }
347100
+ /** Exact membership check against the raw persisted state key, before UI content deduplication. */
347101
+ hasConversationInWorkspace(conversationId, ws) {
347102
+ const targetWs = ws || this.workspace.current;
347103
+ const stateKey2 = this.workspaceConversationStateKeyFor(this.safeConversationId(conversationId || "default"), targetWs);
347104
+ if (!stateKey2) return false;
347105
+ return Object.prototype.hasOwnProperty.call(this.readStoredConversationState(targetWs).conversations || {}, stateKey2);
347106
+ }
347107
+ /** 按工作区列对话(任意 ws,key 前缀 = kind-sha256(path).slice(0,16));供 mobile API 透出工作区从属对话 */
347108
+ listWorkspaceConversationStates(ws) {
347109
+ const stored = this.readStoredConversationState(ws);
347110
+ const prefix = this.workspaceConversationPrefixFor(ws) || "";
347083
347111
  const scopedEntries = Object.entries(stored.conversations || {}).filter(([key3]) => !prefix || key3.startsWith(prefix));
347084
347112
  if (scopedEntries.some(([, value]) => !Number.isFinite(value.order))) {
347085
347113
  const legacyOrder = [...scopedEntries].sort(([, a3], [, b2]) => {
@@ -347090,7 +347118,7 @@ ${String(event.toolArgs || "")}`;
347090
347118
  legacyOrder.forEach(([, value], index) => {
347091
347119
  value.order = index;
347092
347120
  });
347093
- this.writeStoredConversationState(stored);
347121
+ this.writeStoredConversationState(stored, ws);
347094
347122
  }
347095
347123
  const rows = [];
347096
347124
  for (const [key3, value] of Object.entries(stored.conversations || {})) {
@@ -347144,6 +347172,7 @@ ${String(event.toolArgs || "")}`;
347144
347172
  this.subagents = sharedSubagentManager(this.subagentManagerKey(clean), {
347145
347173
  conversationId: clean,
347146
347174
  rootAgentId: state?.rootAgentId || this.runtimeActorId,
347175
+ concurrency: this.subagentConcurrencyLimit(),
347147
347176
  state,
347148
347177
  executor: (job) => this.runSubagentJob(job.record.id, job.prompt, job.flowName, job.reason),
347149
347178
  persist: (subagentState) => this.persistSubagentState(clean, subagentState),
@@ -347213,15 +347242,14 @@ Review this persisted peer result and summarize or continue the parent task as n
347213
347242
  }
347214
347243
  getConversationSnapshot(conversationId = this.activeConversationId, options = {}) {
347215
347244
  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
- })();
347245
+ const ws = options.workspace || this.workspace.current;
347246
+ const currentWs = this.workspace.current;
347247
+ const isActiveWorkspace = !ws && !currentWs || !!ws && !!currentWs && path28.resolve(ws.path) === path28.resolve(currentWs.path);
347248
+ const isActiveConversation = isActiveWorkspace && clean === this.safeConversationId(this.activeConversationId || "default");
347249
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, ws);
347250
+ const memoryKey = ws ? `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}` : null;
347223
347251
  const memory = memoryKey ? this.workspaceConversations.get(memoryKey) : void 0;
347224
- const stored = this.readStoredConversationState();
347252
+ const stored = this.readStoredConversationState(ws);
347225
347253
  const persisted = stateKey2 && stored.conversations ? stored.conversations[stateKey2] : void 0;
347226
347254
  const tree = persisted ? this.normalizeConversationTree(persisted) : null;
347227
347255
  const runtimeNodeId = String(tree?.activeNodeId || "");
@@ -347241,7 +347269,7 @@ Review this persisted peer result and summarize or continue the parent task as n
347241
347269
  const continuations = this.normalizeContinuations(isActiveConversation && viewingRuntimeNode ? this.continuations : viewedNode?.continuations || persisted?.continuations || memory?.continuations);
347242
347270
  return {
347243
347271
  conversationId: clean,
347244
- conversations: this.listConversationStates(),
347272
+ conversations: this.listWorkspaceConversationStates(ws),
347245
347273
  conversationPlan: this.normalizeConversationPlan(isActiveConversation ? this.conversationPlan : persisted?.plan || memory?.plan),
347246
347274
  linkedPlan: this.normalizeLinkedPlan(isActiveConversation ? this.linkedPlan : persisted?.linkedPlan || memory?.linkedPlan),
347247
347275
  subagents: this.recordsForState(isActiveConversation ? this.subagents.serialize() : persisted?.subagentState || memory?.subagentState),
@@ -347251,9 +347279,9 @@ Review this persisted peer result and summarize or continue the parent task as n
347251
347279
  historyMessages: history.length,
347252
347280
  workRuns,
347253
347281
  continuations,
347254
- modelSelection: isActiveConversation ? this.currentConversationModelSelection() : persisted?.modelSelection || memory?.modelSelection || this.currentConversationModelSelection(),
347282
+ modelSelection: isActiveConversation ? this.currentConversationModelSelection() : persisted?.modelSelection || memory?.modelSelection || { kind: "auto" },
347255
347283
  flowSelection: isActiveConversation ? this.currentConversationFlowSelection() : persisted?.flowSelection || memory?.flowSelection || null,
347256
- inputMode: this.inputMode,
347284
+ inputMode: isActiveConversation ? this.inputMode : persisted?.inputMode || memory?.inputMode || "guide",
347257
347285
  mode: isActiveConversation ? this.mode : persisted?.mode || memory?.mode || "build",
347258
347286
  goal: isActiveConversation ? this.serializeGoal() : persisted?.goal || memory?.goal || null,
347259
347287
  branches: this.branchGroupMetadata(tree),
@@ -347540,12 +347568,16 @@ Review this persisted peer result and summarize or continue the parent task as n
347540
347568
  if (clean === this.safeConversationId(this.activeConversationId)) this.setConversationFromStorage(clean);
347541
347569
  return this.getConversationSnapshot(clean);
347542
347570
  }
347543
- setConversationPinned(id, pinned) {
347571
+ setConversationPinned(id, pinned, ws = this.workspace.current) {
347572
+ const targetWs = ws || this.workspace.current;
347573
+ if (!targetWs) return false;
347544
347574
  const clean = this.safeConversationId(id || "default");
347545
- this.saveWorkspaceConversationState();
347546
- const stateKey2 = this.workspaceConversationStateKey(clean);
347575
+ const currentWs = this.workspace.current;
347576
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347577
+ if (isCurrent) this.saveWorkspaceConversationState();
347578
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, targetWs);
347547
347579
  if (!stateKey2) return false;
347548
- const stored = this.readStoredConversationState();
347580
+ const stored = this.readStoredConversationState(targetWs);
347549
347581
  stored.conversations = stored.conversations || {};
347550
347582
  const existing = stored.conversations[stateKey2];
347551
347583
  if (!existing) return false;
@@ -347554,23 +347586,65 @@ Review this persisted peer result and summarize or continue the parent task as n
347554
347586
  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
347587
  existing.order = siblingOrders.length ? Math.min(...siblingOrders) - 1 : 0;
347556
347588
  existing.updatedAt = existing.updatedAt || (/* @__PURE__ */ new Date()).toISOString();
347557
- this.writeStoredConversationState(stored);
347589
+ this.writeStoredConversationState(stored, targetWs);
347558
347590
  return true;
347559
347591
  }
347560
- renameConversation(id, title) {
347592
+ renameConversation(id, title, ws = this.workspace.current) {
347593
+ const targetWs = ws || this.workspace.current;
347594
+ if (!targetWs) return false;
347561
347595
  const clean = this.safeConversationId(id || "default");
347562
347596
  const nextTitle = String(title || "").replace(/\s+/g, " ").trim().slice(0, 80);
347563
347597
  if (!nextTitle) return false;
347564
- this.saveWorkspaceConversationState();
347565
- const stateKey2 = this.workspaceConversationStateKey(clean);
347598
+ const currentWs = this.workspace.current;
347599
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347600
+ if (isCurrent) this.saveWorkspaceConversationState();
347601
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, targetWs);
347566
347602
  if (!stateKey2) return false;
347567
- const stored = this.readStoredConversationState();
347603
+ const stored = this.readStoredConversationState(targetWs);
347568
347604
  const existing = stored.conversations?.[stateKey2];
347569
347605
  if (!existing) return false;
347570
347606
  existing.title = nextTitle;
347571
- this.writeStoredConversationState(stored);
347607
+ this.writeStoredConversationState(stored, targetWs);
347572
347608
  return true;
347573
347609
  }
347610
+ /** 为指定工作区创建空白对话,不临时切换全局前台工作区。 */
347611
+ createConversationInWorkspace(ws, title = "") {
347612
+ const existing = this.listWorkspaceConversationStates(ws);
347613
+ const id = this.safeConversationId(`conv-${Date.now()}-${crypto14.randomUUID().slice(0, 8)}`);
347614
+ const resolvedTitle = String(title || "").replace(/\s+/g, " ").trim().slice(0, 80) || `New chat ${existing.length + 1}`;
347615
+ const stateKey2 = this.workspaceConversationStateKeyFor(id, ws);
347616
+ if (!stateKey2) throw new Error("Conversation workspace is unavailable.");
347617
+ const unpinnedOrders = existing.filter((item) => !item.pinned).map((item) => Number(item.order || 0));
347618
+ const order = unpinnedOrders.length ? Math.min(...unpinnedOrders) - 1 : 0;
347619
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
347620
+ this.mutateStoredConversationState(ws, (latest) => {
347621
+ latest.version = 3;
347622
+ latest.activeConversationId = id;
347623
+ latest.conversations = latest.conversations || {};
347624
+ latest.conversations[stateKey2] = {
347625
+ title: resolvedTitle,
347626
+ chatMessages: [],
347627
+ history: [],
347628
+ plan: { items: [] },
347629
+ linkedPlan: { markdown: "", revision: 0 },
347630
+ workRuns: [],
347631
+ continuations: [],
347632
+ inputMode: this.defaultInputMode(),
347633
+ mode: "build",
347634
+ updatedAt: now2,
347635
+ pinned: false,
347636
+ pinnedAt: "",
347637
+ order,
347638
+ branchCommunication: false
347639
+ };
347640
+ return latest;
347641
+ });
347642
+ const currentWs = this.workspace.current;
347643
+ if (currentWs && path28.resolve(currentWs.path) === path28.resolve(ws.path)) {
347644
+ this.setConversationFromStorage(id);
347645
+ }
347646
+ return { id, title: resolvedTitle };
347647
+ }
347574
347648
  /**
347575
347649
  * 首 Build 命名判定:仅当 (1) 当前对话尚无历史 Build(排除当前 run)且
347576
347650
  * (2) 其持久化 title 仍是自动生成(含为空)时返回 true。dev-0.4.3 起不再
@@ -347681,6 +347755,34 @@ Conversation title (a few words):`;
347681
347755
  this.writeStoredConversationState(stored);
347682
347756
  return true;
347683
347757
  }
347758
+ /** Reorder one pinned group inside an explicit workspace without changing membership. */
347759
+ reorderWorkspaceConversationGroup(ids, ws) {
347760
+ const targetWs = ws || this.workspace.current;
347761
+ if (!targetWs || !Array.isArray(ids) || ids.length < 2) return false;
347762
+ const normalized = ids.map((id) => this.safeConversationId(String(id || "")));
347763
+ if (normalized.some((id) => !id) || new Set(normalized).size !== normalized.length) return false;
347764
+ const currentWs = this.workspace.current;
347765
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347766
+ if (isCurrent) this.saveWorkspaceConversationState();
347767
+ let accepted = false;
347768
+ this.mutateStoredConversationState(targetWs, (latest) => {
347769
+ const prefix = this.workspaceConversationPrefixFor(targetWs) || "";
347770
+ const entries = Object.entries(latest.conversations || {}).filter(([key3]) => !prefix || key3.startsWith(prefix));
347771
+ const entryById = new Map(entries.map(([key3, value]) => [key3.slice(prefix.length + 1) || key3, value]));
347772
+ const requested = normalized.map((id) => entryById.get(id));
347773
+ if (requested.some((entry) => !entry)) return latest;
347774
+ if (new Set(requested.map((entry) => !!entry.pinned)).size !== 1) return latest;
347775
+ const orderSlots = requested.map((entry) => Number(entry.order));
347776
+ if (orderSlots.some((order) => !Number.isFinite(order))) return latest;
347777
+ orderSlots.sort((a3, b2) => a3 - b2);
347778
+ normalized.forEach((id, index) => {
347779
+ entryById.get(id).order = orderSlots[index];
347780
+ });
347781
+ accepted = true;
347782
+ return latest;
347783
+ });
347784
+ return accepted;
347785
+ }
347684
347786
  flushConversationState() {
347685
347787
  this.saveWorkspaceConversationState();
347686
347788
  }
@@ -349629,21 +349731,21 @@ ${summary}`, segment, "local-summarize", true);
349629
349731
  this.saveWorkspaceConversationState(true);
349630
349732
  return { text, hiddenUserInput: true, goalContinuation: true };
349631
349733
  }
349632
- buildSessionArchive(messages, mode, model, archiveDir) {
349734
+ buildSessionArchive(messages, context, archiveDir) {
349633
349735
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").replace("T", "_").replace("Z", "");
349634
349736
  const filename = `session_${stamp}_${crypto14.randomUUID().slice(0, 8)}.md`;
349635
349737
  let markdown = `# Newmark Session \u2014 ${stamp}
349636
349738
 
349637
349739
  `;
349638
- markdown += `**Mode**: ${mode}
349639
- **Model**: ${model}
349740
+ markdown += `**Mode**: ${context.mode}
349741
+ **Model**: ${context.model}
349640
349742
  `;
349641
349743
  markdown += `**Messages**: ${messages.length}
349642
349744
 
349643
349745
  ---
349644
349746
 
349645
349747
  `;
349646
- if (this.goal) markdown += `**Goal**: ${this.goal.objective}
349748
+ if (context.goal?.objective) markdown += `**Goal**: ${context.goal.objective}
349647
349749
 
349648
349750
  `;
349649
349751
  for (const msg of messages) {
@@ -349666,12 +349768,12 @@ ${msg.content}
349666
349768
  writeSessionArchive(messages, mode, model) {
349667
349769
  const archiveDir = this.archiveDir();
349668
349770
  fs25.mkdirSync(archiveDir, { recursive: true });
349669
- const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
349771
+ const archive = this.buildSessionArchive(messages, { mode, model, goal: this.serializeGoal() }, archiveDir);
349670
349772
  fs25.writeFileSync(path28.join(archiveDir, archive.filename), archive.markdown, "utf-8");
349671
349773
  return archive.filename;
349672
349774
  }
349673
- async writeSessionArchiveAsync(messages, mode, model, archiveDir = this.archiveDir()) {
349674
- const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
349775
+ async writeSessionArchiveAsync(messages, context, archiveDir = this.archiveDir()) {
349776
+ const archive = this.buildSessionArchive(messages, context, archiveDir);
349675
349777
  await fs25.promises.mkdir(archiveDir, { recursive: true });
349676
349778
  const outPath = path28.join(archiveDir, archive.filename);
349677
349779
  const tempPath = `${outPath}.${process.pid}.${crypto14.randomUUID()}.tmp`;
@@ -349758,30 +349860,36 @@ ${msg.content}
349758
349860
  * large markdown payload and manifest use promise-based filesystem I/O so
349759
349861
  * independent workspaces can archive in parallel without freezing Electron.
349760
349862
  */
349761
- async archiveConversationAsync(conversationId) {
349762
- return await this.archiveConversationAsyncUnlocked(conversationId);
349863
+ async archiveConversationAsync(conversationId, ws = this.workspace.current) {
349864
+ return await this.archiveConversationAsyncUnlocked(conversationId, ws);
349763
349865
  }
349764
- async archiveConversationAsyncUnlocked(conversationId) {
349765
- const ws = this.workspace.current;
349866
+ async archiveConversationAsyncUnlocked(conversationId, targetWorkspace = this.workspace.current) {
349867
+ const ws = targetWorkspace || this.workspace.current;
349766
349868
  if (!ws) return null;
349767
349869
  const clean = this.safeConversationId(conversationId || "default");
349768
- const stateKey2 = this.workspaceConversationStateKey(clean);
349870
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, ws);
349769
349871
  if (!stateKey2) return null;
349770
349872
  const memoryKey = `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
349771
349873
  const archiveDir = path28.join(ws.path, "archive");
349772
- const workspacePrefix = this.workspaceConversationPrefix() || "";
349773
- const archiveMode = this.modeName();
349774
- const archiveModel = this.model;
349874
+ const workspacePrefix = this.workspaceConversationPrefixFor(ws) || "";
349775
349875
  const cachedStored = this.readStoredConversationState(ws);
349776
349876
  const stored = JSON.parse(JSON.stringify(cachedStored || {}));
349777
349877
  const persisted = stored.conversations?.[stateKey2];
349778
349878
  if (persisted) this.normalizeConversationTree(persisted);
349779
349879
  const memory = this.workspaceConversations.get(memoryKey);
349880
+ const archiveMode = persisted?.mode || memory?.mode || "build";
349881
+ const archiveSelection = persisted?.modelSelection || memory?.modelSelection;
349882
+ const archiveModel = archiveSelection?.kind === "deployment" ? archiveSelection.modelId : archiveSelection?.kind === "auto" ? "auto" : "auto";
349883
+ const archiveGoal = persisted?.goal || memory?.goal || null;
349780
349884
  const persistedMessagesAvailable = persisted?.chatMessages !== void 0;
349781
349885
  const sourceMessages = persisted?.chatMessages ?? memory?.chatMessages ?? [];
349782
349886
  const sourceHistory = persistedMessagesAvailable ? persisted?.history ?? [] : memory?.history ?? persisted?.history ?? [];
349783
349887
  const messages = this.normalizeConversationChatMessages(sourceMessages, sourceHistory);
349784
- const filename = await this.writeSessionArchiveAsync(messages, archiveMode, archiveModel, archiveDir);
349888
+ const filename = await this.writeSessionArchiveAsync(messages, {
349889
+ mode: archiveMode,
349890
+ model: archiveModel,
349891
+ goal: archiveGoal
349892
+ }, archiveDir);
349785
349893
  const archiveEntry = persisted ? JSON.parse(JSON.stringify(persisted)) : {
349786
349894
  title: this.titleFromMessages(messages, clean),
349787
349895
  chatMessages: messages,
@@ -349834,7 +349942,9 @@ ${msg.content}
349834
349942
  this.workspaceConversations.delete(memoryKey);
349835
349943
  const duplicateMemoryKey = `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
349836
349944
  this.workspaceConversations.delete(duplicateMemoryKey);
349837
- if (clean === this.safeConversationId(this.activeConversationId || "default")) {
349945
+ const currentWs = this.workspace.current;
349946
+ const isCurrentWorkspace = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(ws.path);
349947
+ if (isCurrentWorkspace && clean === this.safeConversationId(this.activeConversationId || "default")) {
349838
349948
  this.activeConversationId = nextActiveId || "default";
349839
349949
  this.loadWorkspaceConversationState();
349840
349950
  }
@@ -351079,7 +351189,7 @@ ${persisted.map((guide, index) => `Guide ${index + 1}: ${guide.text}`).join("\n"
351079
351189
  return `${accepted.output}
351080
351190
  ${settled?.result || settled?.error || ""}`.trim();
351081
351191
  }
351082
- async handleSubagentEnvelope(args) {
351192
+ async handleSubagentEnvelope(args, requireRunningBuild = false) {
351083
351193
  try {
351084
351194
  const params = JSON.parse(args);
351085
351195
  const preset = this.resolveSubagentPreset(params);
@@ -351094,6 +351204,36 @@ ${settled?.result || settled?.error || ""}`.trim();
351094
351204
  const requestedModel = this.normalizeSubagentModelSelection(params.model || preset?.model || inheritedModel);
351095
351205
  const activeDeployment = this.activeDeployment();
351096
351206
  const peerModel = requestedModel !== "auto" && !parseDeploymentSelectionValue2(requestedModel) && activeDeployment && requestedModel === activeDeployment.modelId ? `deployment:${encodeURIComponent(activeDeployment.providerId)}:${encodeURIComponent(activeDeployment.modelId)}` : requestedModel;
351207
+ const buildRunId = this.currentWorkRunId();
351208
+ const runningBuild = buildRunId ? this.workRuns.find((run) => run.runId === buildRunId && run.status === "running") : void 0;
351209
+ const limit = this.subagentConcurrencyLimit();
351210
+ if (requireRunningBuild && !runningBuild) {
351211
+ return {
351212
+ ok: false,
351213
+ output: "[SubAgent terminated] No running Build Block owns this call; no SubAgent was created.",
351214
+ error: "SubAgent tool calls require a running Build Block.",
351215
+ metadata: { kind: "subagent", buildRunId: buildRunId || "", limit, terminated: true }
351216
+ };
351217
+ }
351218
+ if (buildRunId && !runningBuild) {
351219
+ return {
351220
+ ok: false,
351221
+ output: `[SubAgent terminated] Build Block ${buildRunId} is not running; no SubAgent was created.`,
351222
+ error: "SubAgent creation requires a running Build Block.",
351223
+ metadata: { kind: "subagent", buildRunId, limit, terminated: true }
351224
+ };
351225
+ }
351226
+ if (runningBuild) {
351227
+ const activeForBuild = this.subagents.activeCountForBuild(buildRunId);
351228
+ if (activeForBuild >= limit) {
351229
+ return {
351230
+ ok: false,
351231
+ output: `[SubAgent terminated] Build Block ${buildRunId} reached the ${this.intelligence === "ultra" ? "Ultra" : "non-Ultra"} hard limit (${limit}); no SubAgent was created or queued.`,
351232
+ error: `SubAgent hard limit reached for Build Block ${buildRunId}: ${activeForBuild}/${limit}.`,
351233
+ metadata: { kind: "subagent", buildRunId, intelligence: this.intelligence, activeForBuild, limit, terminated: true }
351234
+ };
351235
+ }
351236
+ }
351097
351237
  const id = this.subagents.create(
351098
351238
  name50,
351099
351239
  prompt,
@@ -351103,7 +351243,9 @@ ${settled?.result || settled?.error || ""}`.trim();
351103
351243
  this.runtimeActorId,
351104
351244
  peerFlow,
351105
351245
  peerGoal,
351106
- Number(params.flow_pc ?? params.flowPc ?? (peerMode === "flow" ? this.flowPc : 0))
351246
+ Number(params.flow_pc ?? params.flowPc ?? (peerMode === "flow" ? this.flowPc : 0)),
351247
+ runningBuild?.runId || "",
351248
+ this.intelligence
351107
351249
  );
351108
351250
  const sa = this.subagents.get(id);
351109
351251
  if (sa && preset) {
@@ -352411,7 +352553,7 @@ ${custom}`);
352411
352553
  if (this.intelligence === "ultra") {
352412
352554
  parts.push([
352413
352555
  "[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.",
352556
+ "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
352557
  "Do not attempt to do all the work yourself. Use SubAgents for parallel investigation, verification, implementation, and review."
352416
352558
  ].join("\n"));
352417
352559
  }
@@ -352517,7 +352659,7 @@ ${custom}`);
352517
352659
  "- 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
352660
  "- 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
352661
  "- 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.`,
352662
+ `- 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
352663
  `- Visible output contract: assistant replies are sanitized before display to remove hidden-reasoning markers. ${visibleOutputContract}`,
352522
352664
  "- 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
352665
  ].join("\n");