newmark-agent 0.4.5 → 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.
Files changed (40) 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.d.ts +1 -1
  4. package/dist/cli-commands.js +90 -7
  5. package/dist/cli-discovery.js +10 -0
  6. package/dist/conversation-utility-host.bundle.cjs +293 -148
  7. package/dist/core/agent.d.ts +38 -4
  8. package/dist/core/agent.js +231 -49
  9. package/dist/core/agentKernelRunner.d.ts +3 -0
  10. package/dist/core/agentKernelRunner.js +49 -83
  11. package/dist/core/config.js +3 -0
  12. package/dist/core/dshCompatibility.d.ts +23 -6
  13. package/dist/core/dshCompatibility.js +99 -1
  14. package/dist/core/installUpdate.d.ts +67 -0
  15. package/dist/core/installUpdate.js +268 -0
  16. package/dist/core/mobilePairing.d.ts +47 -0
  17. package/dist/core/mobilePairing.js +221 -0
  18. package/dist/core/subagent.d.ts +10 -3
  19. package/dist/core/subagent.js +23 -8
  20. package/dist/core/toolPolicy.js +11 -3
  21. package/dist/launcher.js +14 -11
  22. package/dist/main.js +64 -3
  23. package/dist/preload.js +5 -0
  24. package/dist/providers/chat-completions.adapter.js +6 -2
  25. package/dist/providers/responses.adapter.js +1 -0
  26. package/dist/server.d.ts +1 -0
  27. package/dist/server.js +721 -3
  28. package/dist/toolchain/registry-seeder.js +3 -1
  29. package/dist/tools/index.js +11 -5
  30. package/dist/tools/nativeTools.js +1 -1
  31. package/dist/tui/src/adapters/core-runtime-adapter.js +17 -1
  32. package/dist/tui/src/app.js +41 -0
  33. package/dist/tui/src/data.js +1 -0
  34. package/dist/tui/src/render.js +11 -0
  35. package/dist/tui/src/settings-schema.js +3 -1
  36. package/dist/tui/src/state.js +21 -1
  37. package/dist/ui/index.html +530 -101
  38. package/dist/ui/lucide-sprite.svg +10 -0
  39. package/dist/wsl-agent-host.bundle.cjs +293 -148
  40. package/package.json +14 -8
@@ -327563,7 +327563,7 @@ var NATIVE_TOOL_CATALOG = [
327563
327563
  { 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
327564
  { name: "terminal_takeover", label: "Terminal takeover", description: "Maintain a persistent Agent-controlled shell session.", category: "desktop", defaultEnabled: true },
327565
327565
  { 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" },
327566
+ { name: "SubAgent", label: "SubAgent", description: "Create one real same-conversation peer SubAgent.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327567
327567
  { name: "subagent_list", label: "Subagent list", description: "List same-conversation peer agents.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
327568
327568
  { 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
327569
  { name: "subagent_send", label: "Subagent send", description: "Persist a message to a peer mailbox.", category: "agent", defaultEnabled: true, protected: true, availability: "mode-scoped" },
@@ -328314,6 +328314,9 @@ function defaultConfig() {
328314
328314
  default_input: { _description: "Default input mode", _type: "choice", _values: ["guide", "next"], value: "guide" },
328315
328315
  auto_archive_on_close: { _description: "Auto archive on close", _type: "boolean", value: true }
328316
328316
  },
328317
+ remote: {
328318
+ touch_enabled: { _description: "Allow mobile devices to reach this Newmark instance on the same LAN / Tailscale network", _type: "boolean", value: true }
328319
+ },
328317
328320
  models: {
328318
328321
  providers: { _description: "LLM providers", _type: "array", value: [] },
328319
328322
  default_model: { _description: "Default model", _type: "string", value: "" },
@@ -328860,14 +328863,18 @@ var ChatCompletionsAdapter = class {
328860
328863
  ...request.systemPrompt ? [{ role: CHAT_SYSTEM_ROLE, content: request.systemPrompt }] : [],
328861
328864
  ...openAIChatMessages(request.messages)
328862
328865
  ];
328866
+ const tools = this.serializeTools(request.tools);
328863
328867
  const body = {
328864
328868
  model: request.model,
328865
328869
  messages,
328866
328870
  temperature: request.temperature,
328867
- max_tokens: request.maxOutputTokens,
328868
- tools: this.serializeTools(request.tools),
328869
- tool_choice: "auto"
328871
+ max_tokens: request.maxOutputTokens
328870
328872
  };
328873
+ if (tools.length) {
328874
+ body.tools = tools;
328875
+ body.tool_choice = "auto";
328876
+ body.parallel_tool_calls = true;
328877
+ }
328871
328878
  if (request.reasoningEffort) body.reasoning_effort = request.reasoningEffort;
328872
328879
  if (request.sessionId) body.session_id = request.sessionId;
328873
328880
  const base2 = request.baseUrl.replace(/\/+$/, "");
@@ -329121,6 +329128,7 @@ var ResponsesAdapter = class {
329121
329128
  if (tools.length) {
329122
329129
  body.tools = tools;
329123
329130
  body.tool_choice = "auto";
329131
+ body.parallel_tool_calls = true;
329124
329132
  }
329125
329133
  const base2 = request.baseUrl.replace(/\/+$/, "");
329126
329134
  return {
@@ -335754,6 +335762,10 @@ var MODE_SCOPED_TOOLS = /* @__PURE__ */ new Set([
335754
335762
  "task_read",
335755
335763
  "task_create",
335756
335764
  "question",
335765
+ "SubAgent",
335766
+ "subagent_create",
335767
+ // Legacy runtime alias. It is no longer published to models because its
335768
+ // generic name collides with the persistent task checklist.
335757
335769
  "task",
335758
335770
  "subagent_list",
335759
335771
  "subagent_read",
@@ -335787,6 +335799,8 @@ var PLAN_READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
335787
335799
  "skill",
335788
335800
  "linked_plan",
335789
335801
  "build_history_query",
335802
+ "SubAgent",
335803
+ "subagent_create",
335790
335804
  "task",
335791
335805
  "subagent_list",
335792
335806
  "subagent_read",
@@ -335810,7 +335824,8 @@ var CONCURRENCY_SAFE_TOOLS = /* @__PURE__ */ new Set([
335810
335824
  "web_fetch",
335811
335825
  "git_status",
335812
335826
  "file_audit",
335813
- "repo_security_audit"
335827
+ "repo_security_audit",
335828
+ "SubAgent"
335814
335829
  ]);
335815
335830
  function isConcurrencySafeTool(name50, riskLevel) {
335816
335831
  const toolName = String(name50 || "").trim();
@@ -336978,7 +336993,7 @@ var ToolExecutor = class {
336978
336993
  remote_root: { type: "string" },
336979
336994
  remote_path: { type: "string" }
336980
336995
  }, ["action"]),
336981
- 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"]),
336996
+ 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"]),
336982
336997
  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"] } }, []),
336983
336998
  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." } }, []),
336984
336999
  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" } }, []),
@@ -337155,7 +337170,8 @@ var ToolExecutor = class {
337155
337170
  return normalizeToolResult(output, { tool, workspacePath: wsPath, mode: context.mode || "" });
337156
337171
  }
337157
337172
  validateInvocation(tool, argsStr, _mode = "", inputSchema) {
337158
- if (inputSchema === void 0 && !isNativeToolEnabled(tool, this.config.nativeToolEnabled())) {
337173
+ const schemaTool = tool === "task" || tool === "subagent_create" ? "SubAgent" : tool;
337174
+ if (inputSchema === void 0 && !isNativeToolEnabled(schemaTool, this.config.nativeToolEnabled())) {
337159
337175
  return { ok: false, error: `[tool disabled] ${tool} is disabled in Settings > Tools.` };
337160
337176
  }
337161
337177
  let parsed;
@@ -337177,11 +337193,11 @@ var ToolExecutor = class {
337177
337193
  }, args);
337178
337194
  return validation2.ok ? { ok: false, error: "[permission] Question is disabled by fully_autonomous option feedback." } : { ok: false, error: `[tool schema error] ${validation2.error}` };
337179
337195
  }
337180
- const definition = inputSchema === void 0 ? this.definitions().find((candidate) => candidate.function?.name === tool) : { function: { name: tool, parameters: inputSchema } };
337196
+ const definition = inputSchema === void 0 ? this.definitions().find((candidate) => candidate.function?.name === schemaTool) : { function: { name: tool, parameters: inputSchema } };
337181
337197
  if (!definition) {
337182
337198
  return { ok: false, error: `[tool unsupported] ${tool || "(missing tool)"} is not available for the ${this.hostProfile.kind} host on ${this.hostProfile.platform}.` };
337183
337199
  }
337184
- const validation = this.argumentValidators.validate(tool, definition.function.parameters, args);
337200
+ const validation = this.argumentValidators.validate(schemaTool, definition.function.parameters, args);
337185
337201
  return validation.ok ? { ok: true, args } : { ok: false, error: `[tool schema error] ${validation.error}` };
337186
337202
  }
337187
337203
  async execute(tool, argsStr, wsPath, context = {}) {
@@ -337503,7 +337519,9 @@ var ToolExecutor = class {
337503
337519
  case "ssh_workspace":
337504
337520
  return await this.sshWorkspace(args, wsPath, context.signal);
337505
337521
  case "task":
337506
- return `[task] Subagent request accepted: ${g2("name")}`;
337522
+ case "subagent_create":
337523
+ case "SubAgent":
337524
+ return `[SubAgent] SubAgent request accepted: ${g2("name")}`;
337507
337525
  case "subagent_send":
337508
337526
  return `[subagent_send] Routed to Agent runtime: ${g2("name")}`;
337509
337527
  case "subagent_read":
@@ -338676,6 +338694,7 @@ var SubagentManager = class {
338676
338694
  queueMicrotask(() => this.pump());
338677
338695
  }
338678
338696
  bind(options) {
338697
+ if (options.concurrency !== void 0) this.setConcurrencyLimit(options.concurrency);
338679
338698
  if (options.executor) this.executor = options.executor;
338680
338699
  if (options.onChange) this.onChange = options.onChange;
338681
338700
  if (options.persist) this.persist = options.persist;
@@ -338692,12 +338711,13 @@ var SubagentManager = class {
338692
338711
  removeRootInboxListener(listener) {
338693
338712
  this.rootInboxListeners.delete(listener);
338694
338713
  }
338695
- create(name50, prompt, model, inputMode, agentMode = "build", createdByAgentId = this.rootAgentId, flowName = "", goalObjective = "", flowPc = 0) {
338714
+ create(name50, prompt, model, inputMode, agentMode = "build", createdByAgentId = this.rootAgentId, flowName = "", goalObjective = "", flowPc = 0, buildRunId = "", intelligenceTier = "") {
338696
338715
  const id = (0, import_crypto8.randomUUID)();
338697
338716
  const shortId = id.replace(/-/g, "").slice(0, 8);
338698
338717
  const slug = natureSlug(name50);
338699
- const displayName = `${slug}-${shortId}`;
338700
- const qualifiedName = `${displayName}--${id}`;
338718
+ const createdName = String(name50 || "SubAgent").replace(/\s+/g, " ").trim().slice(0, 160) || "SubAgent";
338719
+ const displayName = createdName;
338720
+ const qualifiedName = `${slug}--${id}`;
338701
338721
  const stamp = now();
338702
338722
  const record = {
338703
338723
  id,
@@ -338705,9 +338725,11 @@ var SubagentManager = class {
338705
338725
  natureSlug: slug,
338706
338726
  displayName,
338707
338727
  qualifiedName,
338708
- name: slug,
338728
+ name: createdName,
338709
338729
  conversationId: this.conversationId,
338710
338730
  createdByAgentId,
338731
+ buildRunId: String(buildRunId || "").trim() || void 0,
338732
+ intelligenceTier: String(intelligenceTier || "").trim() || void 0,
338711
338733
  prompt,
338712
338734
  model: model || "default",
338713
338735
  inputMode: inputMode || "guide",
@@ -339030,6 +339052,20 @@ var SubagentManager = class {
339030
339052
  listAll() {
339031
339053
  return [...this.subs.values()].map(cloneRecord);
339032
339054
  }
339055
+ activeCountForBuild(buildRunId) {
339056
+ const target = String(buildRunId || "").trim();
339057
+ if (!target) return 0;
339058
+ return [...this.subs.values()].filter(
339059
+ (record) => record.buildRunId === target && (record.status === "queued" || record.status === "working")
339060
+ ).length;
339061
+ }
339062
+ setConcurrencyLimit(value) {
339063
+ this.concurrency = Math.max(1, Math.min(16, Math.floor(Number(value) || 4)));
339064
+ this.pump();
339065
+ }
339066
+ concurrencyLimit() {
339067
+ return this.concurrency;
339068
+ }
339033
339069
  pauseScheduling() {
339034
339070
  if (this.schedulingPaused) return;
339035
339071
  this.schedulingPaused = true;
@@ -340195,7 +340231,8 @@ function inferDomain(name50) {
340195
340231
  }
340196
340232
  if (name50 === "question") return "interaction";
340197
340233
  if (name50 === "skill") return "skills";
340198
- if (name50 === "task") return "subagent";
340234
+ if (name50 === "task" || name50 === "subagent_create" || name50 === "SubAgent") return "subagent";
340235
+ if (/^task_(read|create)$/.test(name50)) return "plan";
340199
340236
  if (/^(linked_plan|build_history_query)$/.test(name50)) return "plan";
340200
340237
  return "general";
340201
340238
  }
@@ -340459,7 +340496,7 @@ function resetPublicAssistantDeltaFilter(agent) {
340459
340496
  function prepareAssistantToolVisibility(agent, definitions) {
340460
340497
  const names = definitions.map(toolDefinitionName);
340461
340498
  brokerOnlyAssistantBuffers.set(agent, {
340462
- brokerOnly: names.includes(TOOL_PROVISION_NAME) && names.every((name50) => name50 === TOOL_PROVISION_NAME || name50 === "skill" || SUBAGENT_CORE_TOOL_NAMES.has(name50)),
340499
+ brokerOnly: names.includes(TOOL_PROVISION_NAME) && names.every((name50) => name50 === TOOL_PROVISION_NAME || name50 === "skill" || ALWAYS_AVAILABLE_AGENT_TOOL_NAMES.has(name50)),
340463
340500
  pending: [],
340464
340501
  released: false
340465
340502
  });
@@ -340751,7 +340788,7 @@ async function runAgentKernel(agent) {
340751
340788
  const requestStartedAt = Date.now();
340752
340789
  let firstTokenRecorded = false;
340753
340790
  const tools = context.tools || [];
340754
- const brokerOnlySurface = tools.length > 0 && tools.every((tool) => tool.name === TOOL_PROVISION_NAME || tool.name === "skill" || SUBAGENT_CORE_TOOL_NAMES.has(tool.name));
340791
+ const brokerOnlySurface = tools.length > 0 && tools.every((tool) => tool.name === TOOL_PROVISION_NAME || tool.name === "skill" || ALWAYS_AVAILABLE_AGENT_TOOL_NAMES.has(tool.name));
340755
340792
  currentAgent.beginRouteAttempt();
340756
340793
  try {
340757
340794
  const currentProvider = currentAgent.engineModel();
@@ -340986,6 +341023,8 @@ function buildBuildContextBootstrap(agent, messages, options) {
340986
341023
  buildConversationTaskLedger(agent),
340987
341024
  "## Tool Awareness Bootstrap",
340988
341025
  "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.",
341026
+ "Only bash, pwd, read, write, edit, delete_file, glob, and grep are foundational tools with initial full schemas (subject to mode and policy filtering).",
341027
+ "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.",
340989
341028
  ...catalogLines.length ? catalogLines : ["- No callable tools are available for this provider turn."],
340990
341029
  `Necessary full schemas supplied natively for this provider turn: ${activeNames.length ? activeNames.join(", ") : "(none; use tool_provision when its schema is available)"}.`,
340991
341030
  "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."
@@ -341175,7 +341214,8 @@ function routeTransitionNotice(agent, previous) {
341175
341214
  var TOOL_PROVISION_NAME = "tool_provision";
341176
341215
  var INITIAL_TOOL_SCHEMA_LIMIT = 8;
341177
341216
  var TOOL_PROVISION_BATCH_LIMIT = 8;
341178
- var SUBAGENT_CORE_TOOL_NAMES = /* @__PURE__ */ new Set(["task", "subagent_list", "subagent_read", "subagent_send", "subagent_result", "subagent_close"]);
341217
+ var BASIC_INITIAL_TOOL_NAMES = /* @__PURE__ */ new Set(["bash", "pwd", "read", "write", "edit", "delete_file", "glob", "grep"]);
341218
+ var ALWAYS_AVAILABLE_AGENT_TOOL_NAMES = BASIC_INITIAL_TOOL_NAMES;
341179
341219
  var ToolProvisionSession = class {
341180
341220
  definitionsByName = /* @__PURE__ */ new Map();
341181
341221
  initialNames = /* @__PURE__ */ new Set();
@@ -341199,7 +341239,7 @@ var ToolProvisionSession = class {
341199
341239
  const name50 = toolDefinitionName(definition);
341200
341240
  if (this.definitionsByName.has(name50)) this.initialNames.add(name50);
341201
341241
  }
341202
- for (const name50 of SUBAGENT_CORE_TOOL_NAMES) {
341242
+ for (const name50 of ALWAYS_AVAILABLE_AGENT_TOOL_NAMES) {
341203
341243
  if (this.definitionsByName.has(name50)) this.initialNames.add(name50);
341204
341244
  }
341205
341245
  for (const name50 of this.provisionedNames) {
@@ -341357,29 +341397,6 @@ function toolSurfaceIdentityForAgent(agent) {
341357
341397
  optionFeedback: agent.config.getStr("agent", "option_feedback")
341358
341398
  });
341359
341399
  }
341360
- var CAPABILITY_TO_DOMAIN = {
341361
- "vcs.inspect": ["git"],
341362
- "code.search": ["core"],
341363
- "test.run": ["core"],
341364
- "web.search": ["web"],
341365
- "automation.manage": ["automation"],
341366
- "flow.manage": ["flow"],
341367
- "memory.manage": ["memory"],
341368
- "computer.manage": ["computer"],
341369
- "browser.manage": ["browser"],
341370
- "terminal.manage": ["general", "core"],
341371
- "github.manage": ["general"],
341372
- "ssh.manage": ["general"],
341373
- "skill.manage": ["skills", "general"],
341374
- "plan.manage": ["plan"],
341375
- "history.query": ["plan"],
341376
- "media.display": ["media"],
341377
- "interaction.manage": ["interaction"]
341378
- };
341379
- var CAPABILITY_TOOL_HINTS = {
341380
- "skill.manage": ["skill", "skill_download"],
341381
- "memory.manage": ["memory_lab_read", "memory_lab_query", "memory_lab_update", "memory_lab_reindex"]
341382
- };
341383
341400
  function routeToolSurfaceV2(agent, definitions, toolchain, task) {
341384
341401
  if (!agent.shouldExposeToolInterface()) {
341385
341402
  return {
@@ -341391,67 +341408,34 @@ function routeToolSurfaceV2(agent, definitions, toolchain, task) {
341391
341408
  ].join("\n")
341392
341409
  };
341393
341410
  }
341394
- if (!toolchain) return { definitions, systemPromptNotice: "" };
341395
- const planner = new ToolExposurePlanner(toolchain.registry, toolchain.catalog);
341396
- const plan = planner.plan({
341397
- agentRunId: agent.runtimeActorId,
341398
- buildBlockId: agent.activeConversationId || "build",
341399
- userInput: task,
341400
- objective: "",
341401
- previousToolCalls: [],
341402
- toolUsageFrequency: /* @__PURE__ */ new Map(),
341403
- permissionScope: ["workspace"],
341404
- tokenBudget: 2e4,
341405
- providerToolLimit: 0
341406
- });
341407
- const domains = /* @__PURE__ */ new Set();
341408
- const toolHints = /* @__PURE__ */ new Set();
341409
- for (const capabilityId of plan.suggestedCapabilityIds) {
341410
- for (const domain of CAPABILITY_TO_DOMAIN[capabilityId] || []) domains.add(domain);
341411
- for (const toolName of CAPABILITY_TOOL_HINTS[capabilityId] || []) toolHints.add(toolName);
341412
- }
341413
- const names = definitions.map(toolDefinitionName);
341414
- for (const name50 of names) {
341415
- if (name50 && new RegExp(`(?:^|[^A-Za-z0-9_])${name50.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:$|[^A-Za-z0-9_])`, "i").test(task)) {
341416
- toolHints.add(name50);
341417
- }
341418
- }
341419
- const selected = definitions.filter((definition) => {
341420
- const name50 = toolDefinitionName(definition);
341421
- const descriptor = toolchain.registry.get(name50);
341422
- if (!descriptor || descriptor.riskLevel === "destructive") return false;
341423
- if (toolHints.has(name50)) return true;
341424
- if (!domains.has(descriptor.capabilityId.slice(4))) return false;
341425
- return true;
341426
- }).sort((a3, b2) => {
341427
- const aHint = toolHints.has(toolDefinitionName(a3)) ? 0 : 1;
341428
- const bHint = toolHints.has(toolDefinitionName(b2)) ? 0 : 1;
341429
- return aHint - bHint || names.indexOf(toolDefinitionName(a3)) - names.indexOf(toolDefinitionName(b2));
341430
- }).slice(0, INITIAL_TOOL_SCHEMA_LIMIT);
341431
- const selectedNames = new Set(selected.map(toolDefinitionName));
341432
- const core = definitions.filter((definition) => {
341433
- const name50 = toolDefinitionName(definition);
341434
- return SUBAGENT_CORE_TOOL_NAMES.has(name50) && !selectedNames.has(name50);
341435
- });
341436
- const surface = core.length ? selected.concat(core) : selected;
341437
- if (surface.length === definitions.length) return { definitions, systemPromptNotice: "" };
341438
- if (!selected.length) {
341439
- return {
341440
- definitions: surface,
341441
- systemPromptNotice: [
341442
- "## Tool Interface Availability",
341443
- "This turn was classified as conversational, so no task-specific tool schema was preloaded.",
341444
- `The ${TOOL_PROVISION_NAME} interface still exposes the complete compact capability catalog and can provision an original tool schema when the task requires it.`
341445
- ].join("\n")
341446
- };
341411
+ const surface = definitions.filter((definition) => BASIC_INITIAL_TOOL_NAMES.has(toolDefinitionName(definition)));
341412
+ const advancedCount = Math.max(0, definitions.length - surface.length);
341413
+ let planFingerprint = "";
341414
+ if (toolchain) {
341415
+ const planner = new ToolExposurePlanner(toolchain.registry, toolchain.catalog);
341416
+ const plan = planner.plan({
341417
+ agentRunId: agent.runtimeActorId,
341418
+ buildBlockId: agent.activeConversationId || "build",
341419
+ userInput: task,
341420
+ objective: "",
341421
+ previousToolCalls: [],
341422
+ toolUsageFrequency: /* @__PURE__ */ new Map(),
341423
+ permissionScope: ["workspace"],
341424
+ tokenBudget: 2e4,
341425
+ providerToolLimit: 0
341426
+ });
341427
+ planFingerprint = plan.plan.stableToolsetHash.slice(0, 8);
341447
341428
  }
341448
341429
  return {
341449
341430
  definitions: surface,
341450
341431
  systemPromptNotice: [
341451
341432
  "## Tool Interface Availability",
341452
- `At most ${INITIAL_TOOL_SCHEMA_LIMIT} deterministic task-relevant schemas are preloaded for this turn; the always-available subagent orchestration tools are appended separately.`,
341453
- `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.`,
341454
- `Adaptive exposure plan ${plan.plan.stableToolsetHash.slice(0, 8)}: ${plan.activeToolIds.length} planned tools, ${plan.plan.suggestedCapabilityIds.join(",")}.`
341433
+ `The initial full-schema surface is restricted to foundational workspace tools: ${surface.map(toolDefinitionName).join(", ") || "(none allowed in this mode)"}.`,
341434
+ `${advancedCount} advanced tools are advertised by capability in the compact ${TOOL_PROVISION_NAME} catalog without loading their schemas.`,
341435
+ "Advanced tools include SubAgent, task tools, Git/GitHub, browser, Computer Use, skills, MCP, automations, Flow, and Memory Lab.",
341436
+ `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.`,
341437
+ "Do not call an advanced tool directly from the initial catalog: capability presence is not callability until provisioning has completed.",
341438
+ planFingerprint ? `Capability routing fingerprint: ${planFingerprint}.` : "Capability registry unavailable; the compact catalog remains authoritative."
341455
341439
  ].join("\n")
341456
341440
  };
341457
341441
  }
@@ -341560,7 +341544,7 @@ var INLINE_TOOL_RESULT_MAX_CHARS = 24e3;
341560
341544
  function spillOversizedToolResult(agent, name50, text) {
341561
341545
  const value = String(text || "");
341562
341546
  if (value.length <= INLINE_TOOL_RESULT_MAX_CHARS) return value;
341563
- if (["computer_use", "browser_use", "pdf_read", "image_inspect", "image_display", "task", "subagent_send", "subagent_result", "subagent_read", "linked_plan", "question"].includes(name50)) {
341547
+ 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)) {
341564
341548
  return value;
341565
341549
  }
341566
341550
  const artifactId = agent.storeToolResultArtifact(name50, value);
@@ -341661,7 +341645,7 @@ async function executeNewmarkTool(agent, name50, args, inputSchema, signal) {
341661
341645
  if (signal?.aborted) throw abortError4();
341662
341646
  return result2;
341663
341647
  };
341664
- if (name50 === "task") return (await agent.handleSubagentEnvelope(args)).output;
341648
+ if (name50 === "task" || name50 === "subagent_create" || name50 === "SubAgent") return (await agent.handleSubagentEnvelope(args, true)).output;
341665
341649
  if (name50 === "subagent_send") return (await agent.handleSubagentContinueEnvelope(args)).output;
341666
341650
  if (name50 === "subagent_list") return agent.handleSubagentListEnvelope(args).output;
341667
341651
  if (name50 === "subagent_read") return agent.handleSubagentReadEnvelope(args).output;
@@ -344813,7 +344797,7 @@ var Agent4 = class _Agent {
344813
344797
  this.tools = new ToolExecutor(rootPath, this.config, this.ssh, this.workspace);
344814
344798
  this.skills = new SkillsManager(rootPath);
344815
344799
  this.memoryLab = new MemoryLabManager(rootPath, this.config.getStr("general", "language"));
344816
- this.subagents = new SubagentManager({ rootAgentId: this.runtimeActorId });
344800
+ this.subagents = new SubagentManager({ rootAgentId: this.runtimeActorId, concurrency: this.subagentConcurrencyLimit() });
344817
344801
  if (this.mode === "goal" && !this.goal) {
344818
344802
  this.goal = new GoalStateImpl("Set your objective");
344819
344803
  }
@@ -345412,6 +345396,7 @@ var Agent4 = class _Agent {
345412
345396
  }
345413
345397
  setIntelligence(tier, persist = false) {
345414
345398
  this.intelligence = normalizeIntelligenceTier(tier);
345399
+ this.subagents?.setConcurrencyLimit(this.subagentConcurrencyLimit());
345415
345400
  if (persist) {
345416
345401
  this.config.set("models", "default_intelligence", this.intelligence);
345417
345402
  this.config.save();
@@ -345616,12 +345601,17 @@ var Agent4 = class _Agent {
345616
345601
  return path28.join(ws.path, "conversations", "state.json");
345617
345602
  }
345618
345603
  workspaceConversationStateKey(conversationId = this.activeConversationId) {
345619
- const prefix = this.workspaceConversationPrefix();
345604
+ return this.workspaceConversationStateKeyFor(conversationId, this.workspace.current);
345605
+ }
345606
+ workspaceConversationStateKeyFor(conversationId, ws) {
345607
+ const prefix = this.workspaceConversationPrefixFor(ws);
345620
345608
  if (!prefix) return null;
345621
345609
  return `${prefix}-${this.safeConversationId(conversationId)}`;
345622
345610
  }
345623
345611
  workspaceConversationPrefix() {
345624
- const ws = this.workspace.current;
345612
+ return this.workspaceConversationPrefixFor(this.workspace.current);
345613
+ }
345614
+ workspaceConversationPrefixFor(ws) {
345625
345615
  if (!ws) return null;
345626
345616
  const supplied = String(ws.conversationStatePrefix || "").trim();
345627
345617
  if (/^(?:internal|external)-[a-f0-9]{16}$/i.test(supplied)) return supplied.toLowerCase();
@@ -347071,8 +347061,49 @@ ${String(event.toolArgs || "")}`;
347071
347061
  }
347072
347062
  }
347073
347063
  listConversationStates() {
347074
- const stored = this.readStoredConversationState();
347075
- const prefix = this.workspaceConversationPrefix() || "";
347064
+ return this.listWorkspaceConversationStates(this.workspace.current);
347065
+ }
347066
+ subagentConcurrencyLimit() {
347067
+ return this.intelligence === "ultra" ? 16 : 4;
347068
+ }
347069
+ /** 当前工作区使用内存中的前台选择;后台工作区从各自持久化状态读取 active id。 */
347070
+ activeConversationIdForWorkspace(ws) {
347071
+ const targetWs = ws || this.workspace.current;
347072
+ if (!targetWs) return "default";
347073
+ const currentWs = this.workspace.current;
347074
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347075
+ if (isCurrent) return this.safeConversationId(this.activeConversationId || "default");
347076
+ const stored = this.readStoredConversationState(targetWs);
347077
+ return this.safeConversationId(stored.activeConversationId || "default");
347078
+ }
347079
+ /**
347080
+ * 持久化的完整 work run 记录(含 interrupted/force_interrupted)。
347081
+ * 不依赖运行时内存(run 结束后内存清空,state 端点曾因此丢失被中断的构建记录),
347082
+ * 供 mobile 端点稳定透出完整对话信息;移动端按同一格式解析。
347083
+ */
347084
+ getPersistedConversationWorkRuns(conversationId, ws = null) {
347085
+ const targetWs = ws || this.workspace.current;
347086
+ const clean = this.safeConversationId(conversationId || "default");
347087
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, targetWs);
347088
+ const stored = this.readStoredConversationState(targetWs);
347089
+ const persisted = stateKey2 && stored.conversations ? stored.conversations[stateKey2] : void 0;
347090
+ const tree = persisted ? this.normalizeConversationTree(persisted) : null;
347091
+ const runtimeNodeId = String(tree?.activeNodeId || "");
347092
+ const viewedNodeId = tree ? this.resolveConversationTreePath(tree, this.storedConversationTreePath(tree, persisted?.viewedBranchNodePath, runtimeNodeId)) : "";
347093
+ const viewedNode = tree?.nodes[viewedNodeId];
347094
+ return this.normalizeWorkRuns(viewedNode?.workRuns || persisted?.workRuns);
347095
+ }
347096
+ /** Exact membership check against the raw persisted state key, before UI content deduplication. */
347097
+ hasConversationInWorkspace(conversationId, ws) {
347098
+ const targetWs = ws || this.workspace.current;
347099
+ const stateKey2 = this.workspaceConversationStateKeyFor(this.safeConversationId(conversationId || "default"), targetWs);
347100
+ if (!stateKey2) return false;
347101
+ return Object.prototype.hasOwnProperty.call(this.readStoredConversationState(targetWs).conversations || {}, stateKey2);
347102
+ }
347103
+ /** 按工作区列对话(任意 ws,key 前缀 = kind-sha256(path).slice(0,16));供 mobile API 透出工作区从属对话 */
347104
+ listWorkspaceConversationStates(ws) {
347105
+ const stored = this.readStoredConversationState(ws);
347106
+ const prefix = this.workspaceConversationPrefixFor(ws) || "";
347076
347107
  const scopedEntries = Object.entries(stored.conversations || {}).filter(([key3]) => !prefix || key3.startsWith(prefix));
347077
347108
  if (scopedEntries.some(([, value]) => !Number.isFinite(value.order))) {
347078
347109
  const legacyOrder = [...scopedEntries].sort(([, a3], [, b2]) => {
@@ -347083,7 +347114,7 @@ ${String(event.toolArgs || "")}`;
347083
347114
  legacyOrder.forEach(([, value], index) => {
347084
347115
  value.order = index;
347085
347116
  });
347086
- this.writeStoredConversationState(stored);
347117
+ this.writeStoredConversationState(stored, ws);
347087
347118
  }
347088
347119
  const rows = [];
347089
347120
  for (const [key3, value] of Object.entries(stored.conversations || {})) {
@@ -347137,6 +347168,7 @@ ${String(event.toolArgs || "")}`;
347137
347168
  this.subagents = sharedSubagentManager(this.subagentManagerKey(clean), {
347138
347169
  conversationId: clean,
347139
347170
  rootAgentId: state?.rootAgentId || this.runtimeActorId,
347171
+ concurrency: this.subagentConcurrencyLimit(),
347140
347172
  state,
347141
347173
  executor: (job) => this.runSubagentJob(job.record.id, job.prompt, job.flowName, job.reason),
347142
347174
  persist: (subagentState) => this.persistSubagentState(clean, subagentState),
@@ -347206,15 +347238,14 @@ Review this persisted peer result and summarize or continue the parent task as n
347206
347238
  }
347207
347239
  getConversationSnapshot(conversationId = this.activeConversationId, options = {}) {
347208
347240
  const clean = this.safeConversationId(conversationId || "default");
347209
- const isActiveConversation = clean === this.safeConversationId(this.activeConversationId || "default");
347210
- const stateKey2 = this.workspaceConversationStateKey(clean);
347211
- const memoryKey = (() => {
347212
- const ws = this.workspace.current;
347213
- if (!ws) return null;
347214
- return `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
347215
- })();
347241
+ const ws = options.workspace || this.workspace.current;
347242
+ const currentWs = this.workspace.current;
347243
+ const isActiveWorkspace = !ws && !currentWs || !!ws && !!currentWs && path28.resolve(ws.path) === path28.resolve(currentWs.path);
347244
+ const isActiveConversation = isActiveWorkspace && clean === this.safeConversationId(this.activeConversationId || "default");
347245
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, ws);
347246
+ const memoryKey = ws ? `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}` : null;
347216
347247
  const memory = memoryKey ? this.workspaceConversations.get(memoryKey) : void 0;
347217
- const stored = this.readStoredConversationState();
347248
+ const stored = this.readStoredConversationState(ws);
347218
347249
  const persisted = stateKey2 && stored.conversations ? stored.conversations[stateKey2] : void 0;
347219
347250
  const tree = persisted ? this.normalizeConversationTree(persisted) : null;
347220
347251
  const runtimeNodeId = String(tree?.activeNodeId || "");
@@ -347234,7 +347265,7 @@ Review this persisted peer result and summarize or continue the parent task as n
347234
347265
  const continuations = this.normalizeContinuations(isActiveConversation && viewingRuntimeNode ? this.continuations : viewedNode?.continuations || persisted?.continuations || memory?.continuations);
347235
347266
  return {
347236
347267
  conversationId: clean,
347237
- conversations: this.listConversationStates(),
347268
+ conversations: this.listWorkspaceConversationStates(ws),
347238
347269
  conversationPlan: this.normalizeConversationPlan(isActiveConversation ? this.conversationPlan : persisted?.plan || memory?.plan),
347239
347270
  linkedPlan: this.normalizeLinkedPlan(isActiveConversation ? this.linkedPlan : persisted?.linkedPlan || memory?.linkedPlan),
347240
347271
  subagents: this.recordsForState(isActiveConversation ? this.subagents.serialize() : persisted?.subagentState || memory?.subagentState),
@@ -347244,9 +347275,9 @@ Review this persisted peer result and summarize or continue the parent task as n
347244
347275
  historyMessages: history.length,
347245
347276
  workRuns,
347246
347277
  continuations,
347247
- modelSelection: isActiveConversation ? this.currentConversationModelSelection() : persisted?.modelSelection || memory?.modelSelection || this.currentConversationModelSelection(),
347278
+ modelSelection: isActiveConversation ? this.currentConversationModelSelection() : persisted?.modelSelection || memory?.modelSelection || { kind: "auto" },
347248
347279
  flowSelection: isActiveConversation ? this.currentConversationFlowSelection() : persisted?.flowSelection || memory?.flowSelection || null,
347249
- inputMode: this.inputMode,
347280
+ inputMode: isActiveConversation ? this.inputMode : persisted?.inputMode || memory?.inputMode || "guide",
347250
347281
  mode: isActiveConversation ? this.mode : persisted?.mode || memory?.mode || "build",
347251
347282
  goal: isActiveConversation ? this.serializeGoal() : persisted?.goal || memory?.goal || null,
347252
347283
  branches: this.branchGroupMetadata(tree),
@@ -347533,12 +347564,16 @@ Review this persisted peer result and summarize or continue the parent task as n
347533
347564
  if (clean === this.safeConversationId(this.activeConversationId)) this.setConversationFromStorage(clean);
347534
347565
  return this.getConversationSnapshot(clean);
347535
347566
  }
347536
- setConversationPinned(id, pinned) {
347567
+ setConversationPinned(id, pinned, ws = this.workspace.current) {
347568
+ const targetWs = ws || this.workspace.current;
347569
+ if (!targetWs) return false;
347537
347570
  const clean = this.safeConversationId(id || "default");
347538
- this.saveWorkspaceConversationState();
347539
- const stateKey2 = this.workspaceConversationStateKey(clean);
347571
+ const currentWs = this.workspace.current;
347572
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347573
+ if (isCurrent) this.saveWorkspaceConversationState();
347574
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, targetWs);
347540
347575
  if (!stateKey2) return false;
347541
- const stored = this.readStoredConversationState();
347576
+ const stored = this.readStoredConversationState(targetWs);
347542
347577
  stored.conversations = stored.conversations || {};
347543
347578
  const existing = stored.conversations[stateKey2];
347544
347579
  if (!existing) return false;
@@ -347547,23 +347582,65 @@ Review this persisted peer result and summarize or continue the parent task as n
347547
347582
  const siblingOrders = Object.entries(stored.conversations).filter(([key3, value]) => key3 !== stateKey2 && !!value.pinned === existing.pinned && Number.isFinite(value.order)).map(([, value]) => Number(value.order));
347548
347583
  existing.order = siblingOrders.length ? Math.min(...siblingOrders) - 1 : 0;
347549
347584
  existing.updatedAt = existing.updatedAt || (/* @__PURE__ */ new Date()).toISOString();
347550
- this.writeStoredConversationState(stored);
347585
+ this.writeStoredConversationState(stored, targetWs);
347551
347586
  return true;
347552
347587
  }
347553
- renameConversation(id, title) {
347588
+ renameConversation(id, title, ws = this.workspace.current) {
347589
+ const targetWs = ws || this.workspace.current;
347590
+ if (!targetWs) return false;
347554
347591
  const clean = this.safeConversationId(id || "default");
347555
347592
  const nextTitle = String(title || "").replace(/\s+/g, " ").trim().slice(0, 80);
347556
347593
  if (!nextTitle) return false;
347557
- this.saveWorkspaceConversationState();
347558
- const stateKey2 = this.workspaceConversationStateKey(clean);
347594
+ const currentWs = this.workspace.current;
347595
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347596
+ if (isCurrent) this.saveWorkspaceConversationState();
347597
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, targetWs);
347559
347598
  if (!stateKey2) return false;
347560
- const stored = this.readStoredConversationState();
347599
+ const stored = this.readStoredConversationState(targetWs);
347561
347600
  const existing = stored.conversations?.[stateKey2];
347562
347601
  if (!existing) return false;
347563
347602
  existing.title = nextTitle;
347564
- this.writeStoredConversationState(stored);
347603
+ this.writeStoredConversationState(stored, targetWs);
347565
347604
  return true;
347566
347605
  }
347606
+ /** 为指定工作区创建空白对话,不临时切换全局前台工作区。 */
347607
+ createConversationInWorkspace(ws, title = "") {
347608
+ const existing = this.listWorkspaceConversationStates(ws);
347609
+ const id = this.safeConversationId(`conv-${Date.now()}-${crypto14.randomUUID().slice(0, 8)}`);
347610
+ const resolvedTitle = String(title || "").replace(/\s+/g, " ").trim().slice(0, 80) || `New chat ${existing.length + 1}`;
347611
+ const stateKey2 = this.workspaceConversationStateKeyFor(id, ws);
347612
+ if (!stateKey2) throw new Error("Conversation workspace is unavailable.");
347613
+ const unpinnedOrders = existing.filter((item) => !item.pinned).map((item) => Number(item.order || 0));
347614
+ const order = unpinnedOrders.length ? Math.min(...unpinnedOrders) - 1 : 0;
347615
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
347616
+ this.mutateStoredConversationState(ws, (latest) => {
347617
+ latest.version = 3;
347618
+ latest.activeConversationId = id;
347619
+ latest.conversations = latest.conversations || {};
347620
+ latest.conversations[stateKey2] = {
347621
+ title: resolvedTitle,
347622
+ chatMessages: [],
347623
+ history: [],
347624
+ plan: { items: [] },
347625
+ linkedPlan: { markdown: "", revision: 0 },
347626
+ workRuns: [],
347627
+ continuations: [],
347628
+ inputMode: this.defaultInputMode(),
347629
+ mode: "build",
347630
+ updatedAt: now2,
347631
+ pinned: false,
347632
+ pinnedAt: "",
347633
+ order,
347634
+ branchCommunication: false
347635
+ };
347636
+ return latest;
347637
+ });
347638
+ const currentWs = this.workspace.current;
347639
+ if (currentWs && path28.resolve(currentWs.path) === path28.resolve(ws.path)) {
347640
+ this.setConversationFromStorage(id);
347641
+ }
347642
+ return { id, title: resolvedTitle };
347643
+ }
347567
347644
  /**
347568
347645
  * 首 Build 命名判定:仅当 (1) 当前对话尚无历史 Build(排除当前 run)且
347569
347646
  * (2) 其持久化 title 仍是自动生成(含为空)时返回 true。dev-0.4.3 起不再
@@ -347674,6 +347751,34 @@ Conversation title (a few words):`;
347674
347751
  this.writeStoredConversationState(stored);
347675
347752
  return true;
347676
347753
  }
347754
+ /** Reorder one pinned group inside an explicit workspace without changing membership. */
347755
+ reorderWorkspaceConversationGroup(ids, ws) {
347756
+ const targetWs = ws || this.workspace.current;
347757
+ if (!targetWs || !Array.isArray(ids) || ids.length < 2) return false;
347758
+ const normalized = ids.map((id) => this.safeConversationId(String(id || "")));
347759
+ if (normalized.some((id) => !id) || new Set(normalized).size !== normalized.length) return false;
347760
+ const currentWs = this.workspace.current;
347761
+ const isCurrent = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(targetWs.path);
347762
+ if (isCurrent) this.saveWorkspaceConversationState();
347763
+ let accepted = false;
347764
+ this.mutateStoredConversationState(targetWs, (latest) => {
347765
+ const prefix = this.workspaceConversationPrefixFor(targetWs) || "";
347766
+ const entries = Object.entries(latest.conversations || {}).filter(([key3]) => !prefix || key3.startsWith(prefix));
347767
+ const entryById = new Map(entries.map(([key3, value]) => [key3.slice(prefix.length + 1) || key3, value]));
347768
+ const requested = normalized.map((id) => entryById.get(id));
347769
+ if (requested.some((entry) => !entry)) return latest;
347770
+ if (new Set(requested.map((entry) => !!entry.pinned)).size !== 1) return latest;
347771
+ const orderSlots = requested.map((entry) => Number(entry.order));
347772
+ if (orderSlots.some((order) => !Number.isFinite(order))) return latest;
347773
+ orderSlots.sort((a3, b2) => a3 - b2);
347774
+ normalized.forEach((id, index) => {
347775
+ entryById.get(id).order = orderSlots[index];
347776
+ });
347777
+ accepted = true;
347778
+ return latest;
347779
+ });
347780
+ return accepted;
347781
+ }
347677
347782
  flushConversationState() {
347678
347783
  this.saveWorkspaceConversationState();
347679
347784
  }
@@ -349622,21 +349727,21 @@ ${summary}`, segment, "local-summarize", true);
349622
349727
  this.saveWorkspaceConversationState(true);
349623
349728
  return { text, hiddenUserInput: true, goalContinuation: true };
349624
349729
  }
349625
- buildSessionArchive(messages, mode, model, archiveDir) {
349730
+ buildSessionArchive(messages, context, archiveDir) {
349626
349731
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").replace("T", "_").replace("Z", "");
349627
349732
  const filename = `session_${stamp}_${crypto14.randomUUID().slice(0, 8)}.md`;
349628
349733
  let markdown = `# Newmark Session \u2014 ${stamp}
349629
349734
 
349630
349735
  `;
349631
- markdown += `**Mode**: ${mode}
349632
- **Model**: ${model}
349736
+ markdown += `**Mode**: ${context.mode}
349737
+ **Model**: ${context.model}
349633
349738
  `;
349634
349739
  markdown += `**Messages**: ${messages.length}
349635
349740
 
349636
349741
  ---
349637
349742
 
349638
349743
  `;
349639
- if (this.goal) markdown += `**Goal**: ${this.goal.objective}
349744
+ if (context.goal?.objective) markdown += `**Goal**: ${context.goal.objective}
349640
349745
 
349641
349746
  `;
349642
349747
  for (const msg of messages) {
@@ -349659,12 +349764,12 @@ ${msg.content}
349659
349764
  writeSessionArchive(messages, mode, model) {
349660
349765
  const archiveDir = this.archiveDir();
349661
349766
  fs25.mkdirSync(archiveDir, { recursive: true });
349662
- const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
349767
+ const archive = this.buildSessionArchive(messages, { mode, model, goal: this.serializeGoal() }, archiveDir);
349663
349768
  fs25.writeFileSync(path28.join(archiveDir, archive.filename), archive.markdown, "utf-8");
349664
349769
  return archive.filename;
349665
349770
  }
349666
- async writeSessionArchiveAsync(messages, mode, model, archiveDir = this.archiveDir()) {
349667
- const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
349771
+ async writeSessionArchiveAsync(messages, context, archiveDir = this.archiveDir()) {
349772
+ const archive = this.buildSessionArchive(messages, context, archiveDir);
349668
349773
  await fs25.promises.mkdir(archiveDir, { recursive: true });
349669
349774
  const outPath = path28.join(archiveDir, archive.filename);
349670
349775
  const tempPath = `${outPath}.${process.pid}.${crypto14.randomUUID()}.tmp`;
@@ -349751,30 +349856,36 @@ ${msg.content}
349751
349856
  * large markdown payload and manifest use promise-based filesystem I/O so
349752
349857
  * independent workspaces can archive in parallel without freezing Electron.
349753
349858
  */
349754
- async archiveConversationAsync(conversationId) {
349755
- return await this.archiveConversationAsyncUnlocked(conversationId);
349859
+ async archiveConversationAsync(conversationId, ws = this.workspace.current) {
349860
+ return await this.archiveConversationAsyncUnlocked(conversationId, ws);
349756
349861
  }
349757
- async archiveConversationAsyncUnlocked(conversationId) {
349758
- const ws = this.workspace.current;
349862
+ async archiveConversationAsyncUnlocked(conversationId, targetWorkspace = this.workspace.current) {
349863
+ const ws = targetWorkspace || this.workspace.current;
349759
349864
  if (!ws) return null;
349760
349865
  const clean = this.safeConversationId(conversationId || "default");
349761
- const stateKey2 = this.workspaceConversationStateKey(clean);
349866
+ const stateKey2 = this.workspaceConversationStateKeyFor(clean, ws);
349762
349867
  if (!stateKey2) return null;
349763
349868
  const memoryKey = `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
349764
349869
  const archiveDir = path28.join(ws.path, "archive");
349765
- const workspacePrefix = this.workspaceConversationPrefix() || "";
349766
- const archiveMode = this.modeName();
349767
- const archiveModel = this.model;
349870
+ const workspacePrefix = this.workspaceConversationPrefixFor(ws) || "";
349768
349871
  const cachedStored = this.readStoredConversationState(ws);
349769
349872
  const stored = JSON.parse(JSON.stringify(cachedStored || {}));
349770
349873
  const persisted = stored.conversations?.[stateKey2];
349771
349874
  if (persisted) this.normalizeConversationTree(persisted);
349772
349875
  const memory = this.workspaceConversations.get(memoryKey);
349876
+ const archiveMode = persisted?.mode || memory?.mode || "build";
349877
+ const archiveSelection = persisted?.modelSelection || memory?.modelSelection;
349878
+ const archiveModel = archiveSelection?.kind === "deployment" ? archiveSelection.modelId : archiveSelection?.kind === "auto" ? "auto" : "auto";
349879
+ const archiveGoal = persisted?.goal || memory?.goal || null;
349773
349880
  const persistedMessagesAvailable = persisted?.chatMessages !== void 0;
349774
349881
  const sourceMessages = persisted?.chatMessages ?? memory?.chatMessages ?? [];
349775
349882
  const sourceHistory = persistedMessagesAvailable ? persisted?.history ?? [] : memory?.history ?? persisted?.history ?? [];
349776
349883
  const messages = this.normalizeConversationChatMessages(sourceMessages, sourceHistory);
349777
- const filename = await this.writeSessionArchiveAsync(messages, archiveMode, archiveModel, archiveDir);
349884
+ const filename = await this.writeSessionArchiveAsync(messages, {
349885
+ mode: archiveMode,
349886
+ model: archiveModel,
349887
+ goal: archiveGoal
349888
+ }, archiveDir);
349778
349889
  const archiveEntry = persisted ? JSON.parse(JSON.stringify(persisted)) : {
349779
349890
  title: this.titleFromMessages(messages, clean),
349780
349891
  chatMessages: messages,
@@ -349827,7 +349938,9 @@ ${msg.content}
349827
349938
  this.workspaceConversations.delete(memoryKey);
349828
349939
  const duplicateMemoryKey = `${ws.isInternal ? "internal" : "external"}:${path28.resolve(ws.path)}::conversation:${clean}`;
349829
349940
  this.workspaceConversations.delete(duplicateMemoryKey);
349830
- if (clean === this.safeConversationId(this.activeConversationId || "default")) {
349941
+ const currentWs = this.workspace.current;
349942
+ const isCurrentWorkspace = !!currentWs && path28.resolve(currentWs.path) === path28.resolve(ws.path);
349943
+ if (isCurrentWorkspace && clean === this.safeConversationId(this.activeConversationId || "default")) {
349831
349944
  this.activeConversationId = nextActiveId || "default";
349832
349945
  this.loadWorkspaceConversationState();
349833
349946
  }
@@ -351072,7 +351185,7 @@ ${persisted.map((guide, index) => `Guide ${index + 1}: ${guide.text}`).join("\n"
351072
351185
  return `${accepted.output}
351073
351186
  ${settled?.result || settled?.error || ""}`.trim();
351074
351187
  }
351075
- async handleSubagentEnvelope(args) {
351188
+ async handleSubagentEnvelope(args, requireRunningBuild = false) {
351076
351189
  try {
351077
351190
  const params = JSON.parse(args);
351078
351191
  const preset = this.resolveSubagentPreset(params);
@@ -351087,6 +351200,36 @@ ${settled?.result || settled?.error || ""}`.trim();
351087
351200
  const requestedModel = this.normalizeSubagentModelSelection(params.model || preset?.model || inheritedModel);
351088
351201
  const activeDeployment = this.activeDeployment();
351089
351202
  const peerModel = requestedModel !== "auto" && !parseDeploymentSelectionValue2(requestedModel) && activeDeployment && requestedModel === activeDeployment.modelId ? `deployment:${encodeURIComponent(activeDeployment.providerId)}:${encodeURIComponent(activeDeployment.modelId)}` : requestedModel;
351203
+ const buildRunId = this.currentWorkRunId();
351204
+ const runningBuild = buildRunId ? this.workRuns.find((run) => run.runId === buildRunId && run.status === "running") : void 0;
351205
+ const limit = this.subagentConcurrencyLimit();
351206
+ if (requireRunningBuild && !runningBuild) {
351207
+ return {
351208
+ ok: false,
351209
+ output: "[SubAgent terminated] No running Build Block owns this call; no SubAgent was created.",
351210
+ error: "SubAgent tool calls require a running Build Block.",
351211
+ metadata: { kind: "subagent", buildRunId: buildRunId || "", limit, terminated: true }
351212
+ };
351213
+ }
351214
+ if (buildRunId && !runningBuild) {
351215
+ return {
351216
+ ok: false,
351217
+ output: `[SubAgent terminated] Build Block ${buildRunId} is not running; no SubAgent was created.`,
351218
+ error: "SubAgent creation requires a running Build Block.",
351219
+ metadata: { kind: "subagent", buildRunId, limit, terminated: true }
351220
+ };
351221
+ }
351222
+ if (runningBuild) {
351223
+ const activeForBuild = this.subagents.activeCountForBuild(buildRunId);
351224
+ if (activeForBuild >= limit) {
351225
+ return {
351226
+ ok: false,
351227
+ output: `[SubAgent terminated] Build Block ${buildRunId} reached the ${this.intelligence === "ultra" ? "Ultra" : "non-Ultra"} hard limit (${limit}); no SubAgent was created or queued.`,
351228
+ error: `SubAgent hard limit reached for Build Block ${buildRunId}: ${activeForBuild}/${limit}.`,
351229
+ metadata: { kind: "subagent", buildRunId, intelligence: this.intelligence, activeForBuild, limit, terminated: true }
351230
+ };
351231
+ }
351232
+ }
351090
351233
  const id = this.subagents.create(
351091
351234
  name50,
351092
351235
  prompt,
@@ -351096,7 +351239,9 @@ ${settled?.result || settled?.error || ""}`.trim();
351096
351239
  this.runtimeActorId,
351097
351240
  peerFlow,
351098
351241
  peerGoal,
351099
- Number(params.flow_pc ?? params.flowPc ?? (peerMode === "flow" ? this.flowPc : 0))
351242
+ Number(params.flow_pc ?? params.flowPc ?? (peerMode === "flow" ? this.flowPc : 0)),
351243
+ runningBuild?.runId || "",
351244
+ this.intelligence
351100
351245
  );
351101
351246
  const sa = this.subagents.get(id);
351102
351247
  if (sa && preset) {
@@ -352404,7 +352549,7 @@ ${custom}`);
352404
352549
  if (this.intelligence === "ultra") {
352405
352550
  parts.push([
352406
352551
  "[Ultra Intelligence \u2013 Orchestrator Role]",
352407
- "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.",
352552
+ "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.",
352408
352553
  "Do not attempt to do all the work yourself. Use SubAgents for parallel investigation, verification, implementation, and review."
352409
352554
  ].join("\n"));
352410
352555
  }
@@ -352510,7 +352655,7 @@ ${custom}`);
352510
352655
  "- 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.",
352511
352656
  "- 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.",
352512
352657
  "- 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.",
352513
- `- 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.`,
352658
+ `- 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.`,
352514
352659
  `- Visible output contract: assistant replies are sanitized before display to remove hidden-reasoning markers. ${visibleOutputContract}`,
352515
352660
  "- 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."
352516
352661
  ].join("\n");