nextclaw 0.16.32 → 0.16.33

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 (24) hide show
  1. package/dist/cli/index.js +963 -270
  2. package/package.json +11 -11
  3. package/ui-dist/assets/{ChannelsList-Zeys_w43.js → ChannelsList-DVDu1xvz.js} +1 -1
  4. package/ui-dist/assets/ChatPage-Z9tRzm_n.js +43 -0
  5. package/ui-dist/assets/{MarketplacePage-Cd4faegU.js → MarketplacePage-Buo9HrOz.js} +1 -1
  6. package/ui-dist/assets/MarketplacePage-D6rVQEQR.js +1 -0
  7. package/ui-dist/assets/{McpMarketplacePage-C09Ngs7O.js → McpMarketplacePage-JnkYwK7p.js} +1 -1
  8. package/ui-dist/assets/{ModelConfig-DJgdcgvQ.js → ModelConfig-BYRhgp0c.js} +1 -1
  9. package/ui-dist/assets/{ProvidersList-w0rVFIBf.js → ProvidersList-DmLyyHvX.js} +1 -1
  10. package/ui-dist/assets/{RemoteAccessPage-BJ_ckkOV.js → RemoteAccessPage-CDSSvH7Z.js} +1 -1
  11. package/ui-dist/assets/{RuntimeConfig-Cmn2xPQO.js → RuntimeConfig-v7a7Fe3x.js} +1 -1
  12. package/ui-dist/assets/{SearchConfig-BT13qpR_.js → SearchConfig-D5f1EkLE.js} +1 -1
  13. package/ui-dist/assets/{SecretsConfig-CvqEVn0B.js → SecretsConfig-D61IKcYt.js} +1 -1
  14. package/ui-dist/assets/{SessionsConfig-DHHcYznk.js → SessionsConfig-BRIxVTEv.js} +2 -2
  15. package/ui-dist/assets/chat-session-display-D0WpnuRZ.js +1 -0
  16. package/ui-dist/assets/{index-C6d0xmtm.js → index-BuwbBgmT.js} +2 -2
  17. package/ui-dist/assets/index-bZ8cqQIS.css +1 -0
  18. package/ui-dist/assets/{security-config-T5zpg16O.js → security-config-DbUyWcQz.js} +1 -1
  19. package/ui-dist/assets/{useConfirmDialog-Bs5Ll17m.js → useConfirmDialog-COwYXDKm.js} +1 -1
  20. package/ui-dist/index.html +2 -2
  21. package/ui-dist/assets/ChatPage-DWOU_8P6.js +0 -43
  22. package/ui-dist/assets/MarketplacePage-BfaTTqN6.js +0 -1
  23. package/ui-dist/assets/chat-session-display-VW6ZMvZP.js +0 -1
  24. package/ui-dist/assets/index-BlH4-cBw.css +0 -1
package/dist/cli/index.js CHANGED
@@ -698,9 +698,7 @@ function collectFiles(rootDir) {
698
698
  }
699
699
  function installBuiltinSkill(workdir, destinationDir, skillName) {
700
700
  const loader = new SkillsLoader(workdir);
701
- const workspaceSkill = loader.listSkills(false).find((skill) => {
702
- return skill.name === skillName && skill.source === "workspace";
703
- });
701
+ const workspaceSkill = loader.listSkills(false).find((skill) => skill.name === skillName && skill.source === "workspace");
704
702
  if (!workspaceSkill) {
705
703
  throw new Error(`Workspace skill not found in local installation: ${skillName}`);
706
704
  }
@@ -5642,14 +5640,230 @@ import {
5642
5640
  SessionsHistoryTool,
5643
5641
  SessionsListTool,
5644
5642
  SessionsSendTool,
5645
- SpawnTool,
5646
- SubagentManager,
5647
- SubagentsTool,
5648
5643
  ToolRegistry,
5649
5644
  WebFetchTool,
5650
5645
  WebSearchTool,
5651
5646
  WriteFileTool
5652
5647
  } from "@nextclaw/core";
5648
+
5649
+ // src/cli/commands/ncp/session-request/session-request.tool.ts
5650
+ import { Tool } from "@nextclaw/core";
5651
+ function readRequiredString(params, key) {
5652
+ const value = params[key];
5653
+ if (typeof value !== "string" || value.trim().length === 0) {
5654
+ throw new Error(`${key} must be a non-empty string.`);
5655
+ }
5656
+ return value.trim();
5657
+ }
5658
+ function readOptionalString2(params, key) {
5659
+ const value = params[key];
5660
+ if (typeof value !== "string") {
5661
+ return void 0;
5662
+ }
5663
+ const trimmed = value.trim();
5664
+ return trimmed.length > 0 ? trimmed : void 0;
5665
+ }
5666
+ var SpawnChildSessionTool = class extends Tool {
5667
+ constructor(broker) {
5668
+ super();
5669
+ this.broker = broker;
5670
+ }
5671
+ sourceSessionId = "";
5672
+ sourceSessionMetadata = {};
5673
+ agentId;
5674
+ handoffDepth = 0;
5675
+ get name() {
5676
+ return "spawn";
5677
+ }
5678
+ get description() {
5679
+ return "Create a child session, delegate a task to it, and continue this session when it finishes.";
5680
+ }
5681
+ get parameters() {
5682
+ return {
5683
+ type: "object",
5684
+ properties: {
5685
+ task: { type: "string", description: "Task to run inside the child session." },
5686
+ label: { type: "string", description: "Optional child session title." },
5687
+ model: { type: "string", description: "Optional model override for the child session." }
5688
+ },
5689
+ required: ["task"]
5690
+ };
5691
+ }
5692
+ setContext = (params) => {
5693
+ this.sourceSessionId = params.sourceSessionId;
5694
+ this.sourceSessionMetadata = structuredClone(params.sourceSessionMetadata);
5695
+ this.agentId = params.agentId;
5696
+ this.handoffDepth = params.handoffDepth ?? 0;
5697
+ };
5698
+ execute = async (params, toolCallId) => {
5699
+ const task = readRequiredString(params, "task");
5700
+ return this.broker.spawnChildSessionAndRequest({
5701
+ sourceSessionId: this.sourceSessionId,
5702
+ sourceToolCallId: toolCallId,
5703
+ sourceSessionMetadata: this.sourceSessionMetadata,
5704
+ task,
5705
+ title: readOptionalString2(params, "label"),
5706
+ model: readOptionalString2(params, "model"),
5707
+ handoffDepth: this.handoffDepth,
5708
+ agentId: this.agentId
5709
+ });
5710
+ };
5711
+ };
5712
+ var SessionRequestTool = class extends Tool {
5713
+ constructor(broker) {
5714
+ super();
5715
+ this.broker = broker;
5716
+ }
5717
+ sourceSessionId = "";
5718
+ handoffDepth = 0;
5719
+ get name() {
5720
+ return "sessions_request";
5721
+ }
5722
+ get description() {
5723
+ return "Send one task to another session. Use this after sessions_spawn or to reuse an existing session, and optionally resume this session when the target final reply is ready.";
5724
+ }
5725
+ get parameters() {
5726
+ return {
5727
+ type: "object",
5728
+ properties: {
5729
+ target: {
5730
+ type: "object",
5731
+ description: 'Target session reference. Pass an object like {"session_id":"..."}, not a bare string.',
5732
+ properties: {
5733
+ session_id: {
5734
+ type: "string",
5735
+ description: "Existing target session id."
5736
+ }
5737
+ },
5738
+ required: ["session_id"]
5739
+ },
5740
+ task: {
5741
+ type: "string",
5742
+ description: "Task to send to the target session."
5743
+ },
5744
+ await: {
5745
+ type: "string",
5746
+ enum: ["final_reply"],
5747
+ description: "Phase 1 requires waiting for the target final reply."
5748
+ },
5749
+ delivery: {
5750
+ type: "string",
5751
+ enum: ["none", "resume_source"],
5752
+ description: "How the completion should be delivered back to the source session."
5753
+ },
5754
+ title: {
5755
+ type: "string",
5756
+ description: "Optional card title override."
5757
+ }
5758
+ },
5759
+ required: ["target", "task", "await", "delivery"]
5760
+ };
5761
+ }
5762
+ setContext = (params) => {
5763
+ this.sourceSessionId = params.sourceSessionId;
5764
+ this.handoffDepth = params.handoffDepth ?? 0;
5765
+ };
5766
+ execute = async (params, toolCallId) => {
5767
+ const target = params.target;
5768
+ if (!target || typeof target !== "object" || Array.isArray(target)) {
5769
+ throw new Error("target must be an object.");
5770
+ }
5771
+ const task = readRequiredString(params, "task");
5772
+ const awaitMode = readRequiredString(params, "await");
5773
+ const deliveryMode = readRequiredString(params, "delivery");
5774
+ if (awaitMode !== "final_reply") {
5775
+ throw new Error('Phase 1 only supports await="final_reply".');
5776
+ }
5777
+ return this.broker.requestSession({
5778
+ sourceSessionId: this.sourceSessionId,
5779
+ sourceToolCallId: toolCallId,
5780
+ targetSessionId: readRequiredString(target, "session_id"),
5781
+ task,
5782
+ title: readOptionalString2(params, "title"),
5783
+ awaitMode: "final_reply",
5784
+ deliveryMode,
5785
+ handoffDepth: this.handoffDepth
5786
+ });
5787
+ };
5788
+ };
5789
+
5790
+ // src/cli/commands/ncp/session-request/session-spawn.tool.ts
5791
+ import { Tool as Tool2 } from "@nextclaw/core";
5792
+ function readRequiredString2(value, key) {
5793
+ if (typeof value !== "string" || value.trim().length === 0) {
5794
+ throw new Error(`${key} must be a non-empty string.`);
5795
+ }
5796
+ return value.trim();
5797
+ }
5798
+ function readOptionalString3(value) {
5799
+ if (typeof value !== "string") {
5800
+ return void 0;
5801
+ }
5802
+ const trimmed = value.trim();
5803
+ return trimmed.length > 0 ? trimmed : void 0;
5804
+ }
5805
+ var SessionSpawnTool = class extends Tool2 {
5806
+ constructor(sessionCreationService) {
5807
+ super();
5808
+ this.sessionCreationService = sessionCreationService;
5809
+ }
5810
+ sourceSessionId = "";
5811
+ sourceSessionMetadata = {};
5812
+ agentId;
5813
+ get name() {
5814
+ return "sessions_spawn";
5815
+ }
5816
+ get description() {
5817
+ return "Create a standalone session. Usually follow this with sessions_request if you want the new session to start working immediately.";
5818
+ }
5819
+ get parameters() {
5820
+ return {
5821
+ type: "object",
5822
+ properties: {
5823
+ task: {
5824
+ type: "string",
5825
+ description: "Seed text used to title the new session."
5826
+ },
5827
+ title: {
5828
+ type: "string",
5829
+ description: "Optional explicit session title."
5830
+ },
5831
+ model: {
5832
+ type: "string",
5833
+ description: "Optional model override for the new session."
5834
+ }
5835
+ },
5836
+ required: ["task"]
5837
+ };
5838
+ }
5839
+ setContext = (params) => {
5840
+ this.sourceSessionId = params.sourceSessionId;
5841
+ this.sourceSessionMetadata = structuredClone(params.sourceSessionMetadata);
5842
+ this.agentId = params.agentId;
5843
+ };
5844
+ execute = async (params) => {
5845
+ const task = readRequiredString2(params.task, "task");
5846
+ const session = this.sessionCreationService.createSession({
5847
+ task,
5848
+ title: readOptionalString3(params.title),
5849
+ sourceSessionMetadata: this.sourceSessionMetadata,
5850
+ agentId: this.agentId,
5851
+ model: readOptionalString3(params.model)
5852
+ });
5853
+ return {
5854
+ kind: "nextclaw.session",
5855
+ sessionId: session.sessionId,
5856
+ ...session.parentSessionId ? { parentSessionId: session.parentSessionId } : {},
5857
+ isChildSession: false,
5858
+ lifecycle: session.lifecycle,
5859
+ title: session.title,
5860
+ sessionType: session.sessionType,
5861
+ createdAt: session.createdAt
5862
+ };
5863
+ };
5864
+ };
5865
+
5866
+ // src/cli/commands/ncp/nextclaw-ncp-tool-registry.ts
5653
5867
  function toToolParams(args) {
5654
5868
  if (isRecord3(args)) {
5655
5869
  return args;
@@ -5699,46 +5913,11 @@ var CoreToolNcpAdapter = class {
5699
5913
  var NextclawNcpToolRegistry = class {
5700
5914
  constructor(options) {
5701
5915
  this.options = options;
5702
- const initialConfig = this.options.getConfig();
5703
- this.subagents = new SubagentManager({
5704
- providerManager: this.options.providerManager,
5705
- workspace: initialConfig.agents.defaults.workspace,
5706
- bus: this.options.bus,
5707
- model: initialConfig.agents.defaults.model,
5708
- contextTokens: initialConfig.agents.defaults.contextTokens,
5709
- searchConfig: initialConfig.search,
5710
- execConfig: initialConfig.tools.exec,
5711
- restrictToWorkspace: initialConfig.tools.restrictToWorkspace,
5712
- completionSink: async (params) => {
5713
- const sessionId = params.origin.sessionKey?.trim();
5714
- if (!sessionId || !this.options.writeSubagentCompletionToSession) {
5715
- return;
5716
- }
5717
- await this.options.writeSubagentCompletionToSession({
5718
- sessionId,
5719
- runId: params.runId,
5720
- toolCallId: params.origin.toolCallId,
5721
- label: params.label,
5722
- task: params.task,
5723
- result: params.result,
5724
- status: params.status
5725
- });
5726
- }
5727
- });
5728
5916
  }
5729
- subagents;
5730
5917
  registry = new ToolRegistry();
5731
5918
  tools = /* @__PURE__ */ new Map();
5732
5919
  currentExtensionToolContext = {};
5733
- prepareForRun(context) {
5734
- this.subagents.updateRuntimeOptions({
5735
- model: context.model,
5736
- maxTokens: context.maxTokens,
5737
- contextTokens: context.contextTokens,
5738
- searchConfig: context.searchConfig,
5739
- execConfig: { timeout: context.execTimeoutSeconds },
5740
- restrictToWorkspace: context.restrictToWorkspace
5741
- });
5920
+ prepareForRun = (context) => {
5742
5921
  this.currentExtensionToolContext = {
5743
5922
  config: context.config,
5744
5923
  workspaceDir: context.workspace,
@@ -5752,30 +5931,30 @@ var NextclawNcpToolRegistry = class {
5752
5931
  this.registerDefaultTools(context);
5753
5932
  this.registerExtensionTools(context);
5754
5933
  this.registerAdditionalTools(context);
5755
- }
5756
- listTools() {
5934
+ };
5935
+ listTools = () => {
5757
5936
  return [...this.tools.values()].filter((tool) => this.isToolAvailable(tool.name));
5758
- }
5759
- getTool(name) {
5937
+ };
5938
+ getTool = (name) => {
5760
5939
  if (!this.isToolAvailable(name)) {
5761
5940
  return void 0;
5762
5941
  }
5763
5942
  return this.tools.get(name);
5764
- }
5765
- getToolDefinitions() {
5943
+ };
5944
+ getToolDefinitions = () => {
5766
5945
  return this.listTools().map((tool) => ({
5767
5946
  name: tool.name,
5768
5947
  description: tool.description,
5769
5948
  parameters: tool.parameters
5770
5949
  }));
5771
- }
5772
- async execute(toolCallId, toolName, args) {
5950
+ };
5951
+ execute = async (toolCallId, toolName, args) => {
5773
5952
  if (this.registry.has(toolName)) {
5774
5953
  return this.registry.executeRaw(toolName, toToolParams(args), toolCallId);
5775
5954
  }
5776
5955
  return this.tools.get(toolName)?.execute(args);
5777
- }
5778
- registerDefaultTools(context) {
5956
+ };
5957
+ registerDefaultTools = (context) => {
5779
5958
  const allowedDir = context.restrictToWorkspace ? context.workspace : void 0;
5780
5959
  this.registerTool(new ReadFileTool(allowedDir));
5781
5960
  this.registerTool(new WriteFileTool(allowedDir));
@@ -5795,15 +5974,31 @@ var NextclawNcpToolRegistry = class {
5795
5974
  this.registerTool(new WebSearchTool(context.searchConfig));
5796
5975
  this.registerTool(new WebFetchTool());
5797
5976
  this.registerMessagingTools(context);
5798
- const spawnTool = new SpawnTool(this.subagents);
5799
- spawnTool.setContext(
5800
- context.channel,
5801
- context.chatId,
5802
- context.model,
5803
- context.sessionId,
5804
- context.agentId
5805
- );
5977
+ const spawnTool = new SpawnChildSessionTool(this.options.sessionRequestBroker);
5978
+ spawnTool.setContext({
5979
+ sourceSessionId: context.sessionId,
5980
+ sourceSessionMetadata: context.metadata,
5981
+ agentId: context.agentId,
5982
+ handoffDepth: context.handoffDepth
5983
+ });
5806
5984
  this.registerTool(spawnTool);
5985
+ const sessionsSpawnTool = new SessionSpawnTool(
5986
+ this.options.sessionCreationService
5987
+ );
5988
+ sessionsSpawnTool.setContext({
5989
+ sourceSessionId: context.sessionId,
5990
+ sourceSessionMetadata: context.metadata,
5991
+ agentId: context.agentId
5992
+ });
5993
+ this.registerTool(sessionsSpawnTool);
5994
+ const sessionsRequestTool = new SessionRequestTool(
5995
+ this.options.sessionRequestBroker
5996
+ );
5997
+ sessionsRequestTool.setContext({
5998
+ sourceSessionId: context.sessionId,
5999
+ handoffDepth: context.handoffDepth
6000
+ });
6001
+ this.registerTool(sessionsRequestTool);
5807
6002
  this.registerTool(new SessionsListTool(this.options.sessionManager));
5808
6003
  this.registerTool(new SessionsHistoryTool(this.options.sessionManager));
5809
6004
  const sessionsSendTool = new SessionsSendTool(this.options.sessionManager, this.options.bus);
@@ -5818,12 +6013,11 @@ var NextclawNcpToolRegistry = class {
5818
6013
  this.registerTool(sessionsSendTool);
5819
6014
  this.registerTool(new MemorySearchTool(context.workspace));
5820
6015
  this.registerTool(new MemoryGetTool(context.workspace));
5821
- this.registerTool(new SubagentsTool(this.subagents));
5822
6016
  const gatewayTool = new GatewayTool(this.options.gatewayController);
5823
6017
  gatewayTool.setContext({ sessionKey: context.sessionId });
5824
6018
  this.registerTool(gatewayTool);
5825
- }
5826
- registerMessagingTools(context) {
6019
+ };
6020
+ registerMessagingTools = (context) => {
5827
6021
  const accountId = readMetadataAccountId(context.metadata, {});
5828
6022
  const messageTool = new MessageTool((message) => this.options.bus.publishOutbound(message));
5829
6023
  messageTool.setContext(context.channel, context.chatId, accountId ?? null);
@@ -5833,8 +6027,8 @@ var NextclawNcpToolRegistry = class {
5833
6027
  cronTool.setContext(context.channel, context.chatId, accountId ?? null);
5834
6028
  this.registerTool(cronTool);
5835
6029
  }
5836
- }
5837
- registerExtensionTools(context) {
6030
+ };
6031
+ registerExtensionTools = (context) => {
5838
6032
  const extensionRegistry = this.options.getExtensionRegistry?.();
5839
6033
  if (!extensionRegistry || extensionRegistry.tools.length === 0) {
5840
6034
  return;
@@ -5858,15 +6052,15 @@ var NextclawNcpToolRegistry = class {
5858
6052
  );
5859
6053
  }
5860
6054
  }
5861
- }
5862
- registerTool(tool) {
6055
+ };
6056
+ registerTool = (tool) => {
5863
6057
  this.registry.register(tool);
5864
6058
  this.tools.set(
5865
6059
  tool.name,
5866
6060
  new CoreToolNcpAdapter(tool, async (toolName, args) => this.registry.execute(toolName, toToolParams(args)))
5867
6061
  );
5868
- }
5869
- registerAdditionalTools(context) {
6062
+ };
6063
+ registerAdditionalTools = (context) => {
5870
6064
  const tools = this.options.getAdditionalTools?.(context) ?? [];
5871
6065
  for (const tool of tools) {
5872
6066
  if (this.tools.has(tool.name)) {
@@ -5874,11 +6068,11 @@ var NextclawNcpToolRegistry = class {
5874
6068
  }
5875
6069
  this.tools.set(tool.name, tool);
5876
6070
  }
5877
- }
5878
- isToolAvailable(name) {
6071
+ };
6072
+ isToolAvailable = (name) => {
5879
6073
  const coreTool = this.registry.get(name);
5880
6074
  return coreTool ? coreTool.isAvailable() : true;
5881
- }
6075
+ };
5882
6076
  };
5883
6077
  function resolveAgentHandoffDepth(metadata) {
5884
6078
  const rawDepth = Number(metadata.agent_handoff_depth ?? 0);
@@ -5986,6 +6180,19 @@ function prependRequestedSkills(content, requestedSkillNames) {
5986
6180
 
5987
6181
  ${content}`;
5988
6182
  }
6183
+ function buildSessionOrchestrationSection() {
6184
+ return [
6185
+ "## Session Orchestration",
6186
+ "- `spawn` creates a child session for delegated sub-work that should report completion back into the current session.",
6187
+ "- Use `spawn` when the work is a subtask of the current flow and the user expects this session to continue after that child finishes.",
6188
+ "- `sessions_spawn` creates a standalone session. Use it when the work should live in its own thread, remain independently reviewable later, or continue outside the current flow.",
6189
+ "- `sessions_request` sends one task to another session. Use it to reuse an existing session, or immediately after `sessions_spawn` when a new standalone session should start working right away.",
6190
+ "- If the goal is 'open a new session and have it do something now', the usual sequence is: 1) call `sessions_spawn`; 2) call `sessions_request` with that returned `sessionId`.",
6191
+ '- `sessions_request.target` must be an object shaped like `{ "session_id": "<target-session-id>" }`. Do not pass a bare string.',
6192
+ '- Prefer `delivery="resume_source"` when the current session should continue after the target session produces its final reply. Use `delivery="none"` when you only want the target session to run independently.',
6193
+ "- Do not use `spawn` for long-lived independent threads when `sessions_spawn` plus `sessions_request` would match the user's intent better."
6194
+ ].join("\n");
6195
+ }
5989
6196
  function filterTools(toolDefinitions, requestedToolNames) {
5990
6197
  if (toolDefinitions.length === 0) {
5991
6198
  return void 0;
@@ -6076,6 +6283,7 @@ var NextclawNcpContextBuilder = class {
6076
6283
  accountId: accountId ?? null
6077
6284
  });
6078
6285
  const toolDefinitions = this.options.toolRegistry.getToolDefinitions();
6286
+ const additionalSystemSections = [buildSessionOrchestrationSection()];
6079
6287
  const contextBuilder = new ContextBuilder(
6080
6288
  effectiveWorkspace,
6081
6289
  config2.agents.context,
@@ -6097,7 +6305,8 @@ var NextclawNcpContextBuilder = class {
6097
6305
  thinkingLevel: runtimeThinking,
6098
6306
  skillNames: requestedSkillNames,
6099
6307
  messageToolHints,
6100
- availableTools: buildToolCatalogEntries(toolDefinitions)
6308
+ availableTools: buildToolCatalogEntries(toolDefinitions),
6309
+ additionalSystemSections
6101
6310
  });
6102
6311
  messages[messages.length - 1] = {
6103
6312
  role: currentTurn.currentRole,
@@ -6455,102 +6664,6 @@ var NextclawAgentSessionStore = class {
6455
6664
  };
6456
6665
  };
6457
6666
 
6458
- // src/cli/commands/ncp/ncp-subagent-completion-message.ts
6459
- import {
6460
- NCP_INTERNAL_VISIBILITY_METADATA_KEY
6461
- } from "@nextclaw/ncp";
6462
- function escapeXml(value) {
6463
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
6464
- }
6465
- function buildSubagentCompletionFollowUpMessage(params) {
6466
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
6467
- const statusLabel = params.status === "ok" ? "completed" : "failed";
6468
- return {
6469
- id: `${params.sessionId}:system:subagent-follow-up:${timestamp}`,
6470
- sessionId: params.sessionId,
6471
- role: "user",
6472
- status: "final",
6473
- timestamp,
6474
- parts: [
6475
- {
6476
- type: "text",
6477
- text: [
6478
- "<task-notification>",
6479
- "<source>subagent_completion</source>",
6480
- `<label>${escapeXml(params.label)}</label>`,
6481
- `<status>${statusLabel}</status>`,
6482
- `<delegated-task>${escapeXml(params.task)}</delegated-task>`,
6483
- `<result>${escapeXml(params.result)}</result>`,
6484
- "<instructions>This is an internal worker completion notification, not a new end-user message. Continue the parent task using this result. If the user's request is complete, answer directly. If more work is needed, continue reasoning and use tools. Do not mention this hidden notification unless the user explicitly asks about internal behavior.</instructions>",
6485
- "</task-notification>"
6486
- ].join("\n")
6487
- }
6488
- ],
6489
- metadata: {
6490
- [NCP_INTERNAL_VISIBILITY_METADATA_KEY]: "hidden",
6491
- system_event_kind: "subagent_completion_follow_up",
6492
- subagent_label: params.label,
6493
- subagent_status: params.status,
6494
- subagent_task: params.task
6495
- }
6496
- };
6497
- }
6498
-
6499
- // src/cli/commands/ncp/ncp-subagent-completion-follow-up.ts
6500
- async function consumeAgentRun(events) {
6501
- for await (const _event of events) {
6502
- void _event;
6503
- }
6504
- }
6505
- async function sleep(ms) {
6506
- await new Promise((resolve16) => setTimeout(resolve16, ms));
6507
- }
6508
- async function waitForSessionToBecomeIdle(params) {
6509
- const timeoutMs = params.timeoutMs ?? 15e3;
6510
- const intervalMs = params.intervalMs ?? 150;
6511
- const deadline = Date.now() + timeoutMs;
6512
- while (Date.now() <= deadline) {
6513
- const summary = await params.backend.getSession(params.sessionId);
6514
- if (!summary) {
6515
- return false;
6516
- }
6517
- if (summary.status !== "running") {
6518
- return true;
6519
- }
6520
- await sleep(intervalMs);
6521
- }
6522
- return false;
6523
- }
6524
- async function persistSubagentCompletionAndResumeParent(params) {
6525
- const isIdle = await waitForSessionToBecomeIdle({
6526
- backend: params.backend,
6527
- sessionId: params.completion.sessionId
6528
- });
6529
- if (!isIdle) {
6530
- return;
6531
- }
6532
- if (params.completion.toolCallId?.trim()) {
6533
- await params.backend.updateToolCallResult(
6534
- params.completion.sessionId,
6535
- params.completion.toolCallId.trim(),
6536
- {
6537
- kind: "nextclaw.subagent_run",
6538
- runId: params.completion.runId,
6539
- label: params.completion.label,
6540
- task: params.completion.task,
6541
- status: params.completion.status === "ok" ? "completed" : "failed",
6542
- result: params.completion.result
6543
- }
6544
- );
6545
- }
6546
- await consumeAgentRun(
6547
- params.backend.send({
6548
- sessionId: params.completion.sessionId,
6549
- message: buildSubagentCompletionFollowUpMessage(params.completion)
6550
- })
6551
- );
6552
- }
6553
-
6554
6667
  // src/cli/commands/ncp/provider-manager-ncp-llm-api.ts
6555
6668
  import { parseThinkingLevel as parseThinkingLevel3 } from "@nextclaw/core";
6556
6669
  function normalizeModel(value) {
@@ -6668,6 +6781,569 @@ var ProviderManagerNcpLLMApi = class {
6668
6781
  }
6669
6782
  };
6670
6783
 
6784
+ // src/cli/commands/ncp/session-request/session-creation.service.ts
6785
+ import { randomUUID } from "crypto";
6786
+ var DEFAULT_SESSION_TYPE = "native";
6787
+ var DEFAULT_LIFECYCLE = "persistent";
6788
+ var SESSION_METADATA_LABEL_KEY = "label";
6789
+ var CHILD_SESSION_PARENT_METADATA_KEY = "parent_session_id";
6790
+ var CHILD_SESSION_REQUEST_METADATA_KEY = "spawned_by_request_id";
6791
+ var CHILD_SESSION_LIFECYCLE_METADATA_KEY = "session_lifecycle";
6792
+ var CHILD_SESSION_PROMOTED_METADATA_KEY = "child_session_promoted";
6793
+ function readOptionalString4(value) {
6794
+ if (typeof value !== "string") {
6795
+ return null;
6796
+ }
6797
+ const trimmed = value.trim();
6798
+ return trimmed.length > 0 ? trimmed : null;
6799
+ }
6800
+ function summarizeTask(task) {
6801
+ const normalized = task.trim().replace(/\s+/g, " ");
6802
+ if (!normalized) {
6803
+ return "Session";
6804
+ }
6805
+ if (normalized.length <= 72) {
6806
+ return normalized;
6807
+ }
6808
+ return `${normalized.slice(0, 69)}...`;
6809
+ }
6810
+ function cloneInheritedMetadata(sourceMetadata) {
6811
+ const nextMetadata = {};
6812
+ const inheritedKeys = [
6813
+ "session_type",
6814
+ "preferred_model",
6815
+ "preferred_thinking",
6816
+ "project_root",
6817
+ "requested_skill_refs",
6818
+ "codex_runtime_backend",
6819
+ "reasoningNormalizationMode",
6820
+ "reasoning_normalization_mode"
6821
+ ];
6822
+ for (const key of inheritedKeys) {
6823
+ if (!Object.prototype.hasOwnProperty.call(sourceMetadata, key)) {
6824
+ continue;
6825
+ }
6826
+ nextMetadata[key] = structuredClone(sourceMetadata[key]);
6827
+ }
6828
+ return nextMetadata;
6829
+ }
6830
+ function buildSessionId(agentId) {
6831
+ void agentId;
6832
+ return `ncp-${Date.now().toString(36)}-${randomUUID().replace(/-/g, "").slice(0, 8)}`;
6833
+ }
6834
+ var SessionCreationService = class {
6835
+ constructor(sessionManager, onSessionUpdated) {
6836
+ this.sessionManager = sessionManager;
6837
+ this.onSessionUpdated = onSessionUpdated;
6838
+ }
6839
+ createSession = (params) => {
6840
+ const sessionId = buildSessionId(params.agentId);
6841
+ const now3 = (/* @__PURE__ */ new Date()).toISOString();
6842
+ const session = this.sessionManager.getOrCreate(sessionId);
6843
+ const title = readOptionalString4(params.title) ?? summarizeTask(params.task);
6844
+ const metadata = cloneInheritedMetadata(params.sourceSessionMetadata);
6845
+ const parentSessionId = readOptionalString4(params.parentSessionId);
6846
+ const requestId = readOptionalString4(params.requestId);
6847
+ const sessionType = readOptionalString4(params.sessionType) ?? readOptionalString4(metadata.session_type) ?? DEFAULT_SESSION_TYPE;
6848
+ metadata.session_type = sessionType;
6849
+ metadata[SESSION_METADATA_LABEL_KEY] = title;
6850
+ metadata[CHILD_SESSION_LIFECYCLE_METADATA_KEY] = DEFAULT_LIFECYCLE;
6851
+ if (parentSessionId) {
6852
+ metadata[CHILD_SESSION_PARENT_METADATA_KEY] = parentSessionId;
6853
+ metadata[CHILD_SESSION_PROMOTED_METADATA_KEY] = false;
6854
+ }
6855
+ if (requestId) {
6856
+ metadata[CHILD_SESSION_REQUEST_METADATA_KEY] = requestId;
6857
+ }
6858
+ if (readOptionalString4(params.model)) {
6859
+ metadata.model = params.model?.trim();
6860
+ metadata.preferred_model = params.model?.trim();
6861
+ }
6862
+ if (readOptionalString4(params.thinkingLevel)) {
6863
+ metadata.thinking = params.thinkingLevel?.trim();
6864
+ metadata.preferred_thinking = params.thinkingLevel?.trim();
6865
+ }
6866
+ if (readOptionalString4(params.projectRoot)) {
6867
+ metadata.project_root = params.projectRoot?.trim();
6868
+ }
6869
+ session.metadata = metadata;
6870
+ session.updatedAt = new Date(now3);
6871
+ this.sessionManager.save(session);
6872
+ this.onSessionUpdated?.(sessionId);
6873
+ return {
6874
+ sessionId,
6875
+ sessionType,
6876
+ runtimeFamily: "native",
6877
+ ...parentSessionId ? { parentSessionId } : {},
6878
+ ...requestId ? { spawnedByRequestId: requestId } : {},
6879
+ lifecycle: DEFAULT_LIFECYCLE,
6880
+ title,
6881
+ metadata,
6882
+ createdAt: session.createdAt.toISOString(),
6883
+ updatedAt: session.updatedAt.toISOString()
6884
+ };
6885
+ };
6886
+ createChildSession = (params) => {
6887
+ return this.createSession(params);
6888
+ };
6889
+ promoteChildSession = (params) => {
6890
+ const session = this.sessionManager.getIfExists(params.sessionId);
6891
+ if (!session) {
6892
+ return false;
6893
+ }
6894
+ session.metadata = {
6895
+ ...session.metadata,
6896
+ [CHILD_SESSION_PROMOTED_METADATA_KEY]: params.promoted
6897
+ };
6898
+ session.updatedAt = /* @__PURE__ */ new Date();
6899
+ this.sessionManager.save(session);
6900
+ this.onSessionUpdated?.(params.sessionId);
6901
+ return true;
6902
+ };
6903
+ isChildSessionRecord = (metadata) => {
6904
+ return Boolean(readOptionalString4(metadata?.[CHILD_SESSION_PARENT_METADATA_KEY]));
6905
+ };
6906
+ };
6907
+
6908
+ // src/cli/commands/ncp/session-request/session-request-broker.ts
6909
+ import {
6910
+ NcpEventType
6911
+ } from "@nextclaw/ncp";
6912
+ import { randomUUID as randomUUID2 } from "crypto";
6913
+ function readOptionalString5(value) {
6914
+ if (typeof value !== "string") {
6915
+ return null;
6916
+ }
6917
+ const trimmed = value.trim();
6918
+ return trimmed.length > 0 ? trimmed : null;
6919
+ }
6920
+ function summarizeTask2(task) {
6921
+ const normalized = task.trim().replace(/\s+/g, " ");
6922
+ if (!normalized) {
6923
+ return "Session request";
6924
+ }
6925
+ if (normalized.length <= 72) {
6926
+ return normalized;
6927
+ }
6928
+ return `${normalized.slice(0, 69)}...`;
6929
+ }
6930
+ function buildUserMessage(params) {
6931
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
6932
+ return {
6933
+ id: `${params.sessionId}:user:session-request:${params.requestId}`,
6934
+ sessionId: params.sessionId,
6935
+ role: "user",
6936
+ status: "final",
6937
+ timestamp,
6938
+ parts: [{ type: "text", text: params.task }],
6939
+ metadata: {
6940
+ session_request_id: params.requestId
6941
+ }
6942
+ };
6943
+ }
6944
+ function extractMessageText(message) {
6945
+ if (!message) {
6946
+ return void 0;
6947
+ }
6948
+ const parts = message.parts.flatMap((part) => {
6949
+ if (part.type === "text" || part.type === "rich-text") {
6950
+ return [part.text];
6951
+ }
6952
+ return [];
6953
+ }).map((part) => part.trim()).filter((part) => part.length > 0);
6954
+ if (parts.length === 0) {
6955
+ return void 0;
6956
+ }
6957
+ return parts.join("\n\n");
6958
+ }
6959
+ function findLatestAssistantMessage(messages) {
6960
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
6961
+ const message = messages[index];
6962
+ if (message?.role === "assistant") {
6963
+ return message;
6964
+ }
6965
+ }
6966
+ return void 0;
6967
+ }
6968
+ function readParentSessionId(metadata) {
6969
+ return readOptionalString5(metadata?.[CHILD_SESSION_PARENT_METADATA_KEY]) ?? void 0;
6970
+ }
6971
+ function buildToolResult(params) {
6972
+ return {
6973
+ kind: "nextclaw.session_request",
6974
+ requestId: params.request.requestId,
6975
+ sessionId: params.request.targetSessionId,
6976
+ targetKind: params.isChildSession ? "child" : "session",
6977
+ ...params.parentSessionId ? { parentSessionId: params.parentSessionId } : {},
6978
+ ...params.spawnedByRequestId ? { spawnedByRequestId: params.spawnedByRequestId } : {},
6979
+ isChildSession: params.isChildSession,
6980
+ lifecycle: "persistent",
6981
+ ...params.title.trim() ? { title: params.title } : {},
6982
+ task: params.task,
6983
+ status: params.request.status,
6984
+ awaitMode: params.request.awaitMode,
6985
+ deliveryMode: params.request.deliveryMode,
6986
+ ...params.request.finalResponseText ? { finalResponseText: params.request.finalResponseText } : {},
6987
+ ...params.request.error ? { error: params.request.error } : {},
6988
+ ...params.message ? { message: params.message } : {}
6989
+ };
6990
+ }
6991
+ var SessionRequestBroker = class {
6992
+ constructor(sessionManager, sessionCreationService, deliveryService, resolveBackend, onSessionUpdated) {
6993
+ this.sessionManager = sessionManager;
6994
+ this.sessionCreationService = sessionCreationService;
6995
+ this.deliveryService = deliveryService;
6996
+ this.resolveBackend = resolveBackend;
6997
+ this.onSessionUpdated = onSessionUpdated;
6998
+ }
6999
+ spawnChildSessionAndRequest = async (params) => {
7000
+ const requestId = randomUUID2();
7001
+ const childSession = this.sessionCreationService.createChildSession({
7002
+ parentSessionId: params.sourceSessionId,
7003
+ task: params.task,
7004
+ title: params.title,
7005
+ sourceSessionMetadata: params.sourceSessionMetadata,
7006
+ agentId: params.agentId,
7007
+ model: params.model,
7008
+ thinkingLevel: params.thinkingLevel,
7009
+ sessionType: params.sessionType,
7010
+ projectRoot: params.projectRoot,
7011
+ requestId
7012
+ });
7013
+ return this.dispatchRequest({
7014
+ requestId,
7015
+ sourceSessionId: params.sourceSessionId,
7016
+ sourceToolCallId: params.sourceToolCallId,
7017
+ targetSessionId: childSession.sessionId,
7018
+ task: params.task,
7019
+ title: childSession.title ?? summarizeTask2(params.task),
7020
+ handoffDepth: params.handoffDepth ?? 0,
7021
+ awaitMode: "final_reply",
7022
+ deliveryMode: "resume_source",
7023
+ isChildSession: true,
7024
+ parentSessionId: params.sourceSessionId,
7025
+ spawnedByRequestId: requestId
7026
+ });
7027
+ };
7028
+ requestSession = async (params) => {
7029
+ if (params.targetSessionId.trim() === params.sourceSessionId.trim()) {
7030
+ throw new Error("sessions_request cannot target the current session.");
7031
+ }
7032
+ const backend = this.resolveBackend();
7033
+ if (!backend) {
7034
+ throw new Error("NCP backend is not ready for session requests.");
7035
+ }
7036
+ const targetSummary = await backend.getSession(params.targetSessionId.trim());
7037
+ if (!targetSummary) {
7038
+ throw new Error(`Target session not found: ${params.targetSessionId}`);
7039
+ }
7040
+ const parentSessionId = readParentSessionId(targetSummary.metadata);
7041
+ return this.dispatchRequest({
7042
+ requestId: randomUUID2(),
7043
+ sourceSessionId: params.sourceSessionId,
7044
+ sourceToolCallId: params.sourceToolCallId,
7045
+ targetSessionId: params.targetSessionId.trim(),
7046
+ task: params.task,
7047
+ title: readOptionalString5(params.title) ?? readOptionalString5(targetSummary.metadata?.label) ?? summarizeTask2(params.task),
7048
+ handoffDepth: params.handoffDepth ?? 0,
7049
+ awaitMode: params.awaitMode,
7050
+ deliveryMode: params.deliveryMode,
7051
+ isChildSession: Boolean(parentSessionId),
7052
+ parentSessionId: parentSessionId ?? void 0,
7053
+ spawnedByRequestId: void 0
7054
+ });
7055
+ };
7056
+ dispatchRequest = async (params) => {
7057
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
7058
+ const request = {
7059
+ requestId: params.requestId,
7060
+ sourceSessionId: params.sourceSessionId,
7061
+ targetSessionId: params.targetSessionId,
7062
+ sourceToolCallId: params.sourceToolCallId,
7063
+ rootRequestId: params.requestId,
7064
+ handoffDepth: params.handoffDepth,
7065
+ awaitMode: params.awaitMode,
7066
+ deliveryMode: params.deliveryMode,
7067
+ status: "running",
7068
+ createdAt,
7069
+ startedAt: createdAt,
7070
+ metadata: {
7071
+ title: params.title,
7072
+ task: params.task,
7073
+ is_child_session: params.isChildSession,
7074
+ ...params.parentSessionId ? { parent_session_id: params.parentSessionId } : {}
7075
+ }
7076
+ };
7077
+ void this.runRequest({
7078
+ request,
7079
+ task: params.task,
7080
+ title: params.title,
7081
+ isChildSession: params.isChildSession,
7082
+ parentSessionId: params.parentSessionId
7083
+ }).catch((error) => {
7084
+ console.error(
7085
+ `[session-request] Background request ${params.requestId} crashed: ${error instanceof Error ? error.message : String(error)}`
7086
+ );
7087
+ });
7088
+ return buildToolResult({
7089
+ request,
7090
+ task: params.task,
7091
+ title: params.title,
7092
+ isChildSession: params.isChildSession,
7093
+ parentSessionId: params.parentSessionId,
7094
+ spawnedByRequestId: params.spawnedByRequestId,
7095
+ message: `Session request started. You'll receive the final reply when it finishes.`
7096
+ });
7097
+ };
7098
+ runRequest = async (params) => {
7099
+ let completedMessage;
7100
+ try {
7101
+ const backend = this.resolveBackend();
7102
+ if (!backend) {
7103
+ throw new Error("NCP backend is not ready for session request execution.");
7104
+ }
7105
+ const message = buildUserMessage({
7106
+ sessionId: params.request.targetSessionId,
7107
+ requestId: params.request.requestId,
7108
+ task: params.task
7109
+ });
7110
+ for await (const event of backend.send({
7111
+ sessionId: params.request.targetSessionId,
7112
+ message
7113
+ })) {
7114
+ if (event.type === NcpEventType.MessageAccepted) {
7115
+ this.handleRequestEvent(params.request, event);
7116
+ continue;
7117
+ }
7118
+ if (event.type === NcpEventType.MessageFailed) {
7119
+ throw new Error(event.payload.error.message);
7120
+ }
7121
+ if (event.type === NcpEventType.RunError) {
7122
+ throw new Error(event.payload.error ?? "Session request failed.");
7123
+ }
7124
+ if (event.type === NcpEventType.MessageCompleted) {
7125
+ completedMessage = event.payload.message;
7126
+ }
7127
+ }
7128
+ if (!completedMessage) {
7129
+ const targetMessages = await backend.listSessionMessages(
7130
+ params.request.targetSessionId
7131
+ );
7132
+ completedMessage = findLatestAssistantMessage(targetMessages);
7133
+ }
7134
+ if (!completedMessage) {
7135
+ throw new Error("Session request completed without a final reply.");
7136
+ }
7137
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
7138
+ const finalResponseText = extractMessageText(completedMessage);
7139
+ const completedRequest = {
7140
+ ...params.request,
7141
+ status: "completed",
7142
+ completedAt,
7143
+ finalResponseMessageId: completedMessage?.id,
7144
+ finalResponseText
7145
+ };
7146
+ this.appendRequestEvent(params.request.sourceSessionId, "session.request.completed", completedRequest);
7147
+ this.appendRequestEvent(params.request.targetSessionId, "session.request.completed", completedRequest);
7148
+ const result = buildToolResult({
7149
+ request: completedRequest,
7150
+ task: params.task,
7151
+ title: params.title,
7152
+ isChildSession: params.isChildSession,
7153
+ parentSessionId: params.parentSessionId,
7154
+ spawnedByRequestId: params.spawnedByRequestId
7155
+ });
7156
+ await this.deliveryService.publishToolResult({
7157
+ request: completedRequest,
7158
+ result
7159
+ });
7160
+ if (completedRequest.deliveryMode === "resume_source") {
7161
+ await this.deliveryService.resumeSourceSession({
7162
+ request: completedRequest,
7163
+ result
7164
+ });
7165
+ }
7166
+ } catch (error) {
7167
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
7168
+ const failedRequest = {
7169
+ ...params.request,
7170
+ status: "failed",
7171
+ completedAt,
7172
+ error: error instanceof Error ? error.message : String(error)
7173
+ };
7174
+ this.appendRequestEvent(params.request.sourceSessionId, "session.request.failed", failedRequest);
7175
+ this.appendRequestEvent(params.request.targetSessionId, "session.request.failed", failedRequest);
7176
+ const result = buildToolResult({
7177
+ request: failedRequest,
7178
+ task: params.task,
7179
+ title: params.title,
7180
+ isChildSession: params.isChildSession,
7181
+ parentSessionId: params.parentSessionId,
7182
+ spawnedByRequestId: params.spawnedByRequestId
7183
+ });
7184
+ await this.deliveryService.publishToolResult({
7185
+ request: failedRequest,
7186
+ result
7187
+ });
7188
+ if (failedRequest.deliveryMode === "resume_source") {
7189
+ await this.deliveryService.resumeSourceSession({
7190
+ request: failedRequest,
7191
+ result
7192
+ });
7193
+ }
7194
+ }
7195
+ };
7196
+ handleRequestEvent = (request, event) => {
7197
+ if (event.type === NcpEventType.MessageAccepted) {
7198
+ const acceptedRequest = {
7199
+ ...request,
7200
+ targetMessageId: event.payload.messageId
7201
+ };
7202
+ this.appendRequestEvent(request.sourceSessionId, "session.request.accepted", acceptedRequest);
7203
+ this.appendRequestEvent(request.targetSessionId, "session.request.accepted", acceptedRequest);
7204
+ }
7205
+ };
7206
+ appendRequestEvent = (sessionId, type, request) => {
7207
+ const session = this.sessionManager.getOrCreate(sessionId);
7208
+ this.sessionManager.appendEvent(session, {
7209
+ type,
7210
+ data: {
7211
+ request: structuredClone(request)
7212
+ }
7213
+ });
7214
+ this.sessionManager.save(session);
7215
+ this.onSessionUpdated?.(sessionId);
7216
+ };
7217
+ };
7218
+
7219
+ // src/cli/commands/ncp/session-request/session-request-delivery.service.ts
7220
+ import {
7221
+ NCP_INTERNAL_VISIBILITY_METADATA_KEY
7222
+ } from "@nextclaw/ncp";
7223
+ function escapeXml(value) {
7224
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
7225
+ }
7226
+ async function consumeAgentRun(events) {
7227
+ for await (const _event of events) {
7228
+ void _event;
7229
+ }
7230
+ }
7231
+ function runDetached(task, label) {
7232
+ void task.catch((error) => {
7233
+ const message = error instanceof Error ? error.message : String(error);
7234
+ console.warn(`[session-request] ${label} failed: ${message}`);
7235
+ });
7236
+ }
7237
+ function scheduleDetached(taskFactory, label) {
7238
+ setTimeout(() => {
7239
+ runDetached(taskFactory(), label);
7240
+ }, 0);
7241
+ }
7242
+ async function sleep(ms) {
7243
+ await new Promise((resolve16) => setTimeout(resolve16, ms));
7244
+ }
7245
+ async function waitForSessionToBecomeIdle(params) {
7246
+ const timeoutMs = params.timeoutMs ?? 15e3;
7247
+ const intervalMs = params.intervalMs ?? 150;
7248
+ const deadline = Date.now() + timeoutMs;
7249
+ while (Date.now() <= deadline) {
7250
+ const summary = await params.backend.getSession(params.sessionId);
7251
+ if (!summary) {
7252
+ return false;
7253
+ }
7254
+ if (summary.status !== "running") {
7255
+ return true;
7256
+ }
7257
+ await sleep(intervalMs);
7258
+ }
7259
+ return false;
7260
+ }
7261
+ function buildSessionRequestCompletionMessage(params) {
7262
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
7263
+ const status = params.result.status === "completed" ? "completed" : "failed";
7264
+ const responseText = params.result.finalResponseText ?? params.result.error ?? "";
7265
+ const targetKind = params.result.targetKind;
7266
+ const title = params.result.title ?? params.result.task;
7267
+ return {
7268
+ id: `${params.request.sourceSessionId}:system:session-request:${params.request.requestId}:${timestamp}`,
7269
+ sessionId: params.request.sourceSessionId,
7270
+ role: "user",
7271
+ status: "final",
7272
+ timestamp,
7273
+ parts: [
7274
+ {
7275
+ type: "text",
7276
+ text: [
7277
+ "<session-request-completion>",
7278
+ `<request-id>${escapeXml(params.request.requestId)}</request-id>`,
7279
+ `<target-session-id>${escapeXml(params.request.targetSessionId)}</target-session-id>`,
7280
+ `<target-kind>${escapeXml(targetKind)}</target-kind>`,
7281
+ `<title>${escapeXml(title)}</title>`,
7282
+ `<status>${escapeXml(status)}</status>`,
7283
+ `<final-response>${escapeXml(responseText)}</final-response>`,
7284
+ "<instructions>This is an internal delegated session completion notification. Continue the current task using this result. Do not mention this hidden notification unless the user explicitly asks about internal behavior.</instructions>",
7285
+ "</session-request-completion>"
7286
+ ].join("\n")
7287
+ }
7288
+ ],
7289
+ metadata: {
7290
+ [NCP_INTERNAL_VISIBILITY_METADATA_KEY]: "hidden",
7291
+ system_event_kind: "session_request_completion",
7292
+ session_request_id: params.request.requestId,
7293
+ target_session_id: params.request.targetSessionId,
7294
+ session_request_status: params.result.status
7295
+ }
7296
+ };
7297
+ }
7298
+ var SessionRequestDeliveryService = class {
7299
+ constructor(resolveBackend) {
7300
+ this.resolveBackend = resolveBackend;
7301
+ }
7302
+ publishToolResult = async (params) => {
7303
+ if (!params.request.sourceToolCallId?.trim()) {
7304
+ return;
7305
+ }
7306
+ const backend = this.resolveBackend();
7307
+ if (!backend) {
7308
+ throw new Error("NCP backend is not ready for session request delivery.");
7309
+ }
7310
+ const isIdle = await waitForSessionToBecomeIdle({
7311
+ backend,
7312
+ sessionId: params.request.sourceSessionId
7313
+ });
7314
+ if (!isIdle) {
7315
+ return;
7316
+ }
7317
+ await backend.updateToolCallResult(
7318
+ params.request.sourceSessionId,
7319
+ params.request.sourceToolCallId.trim(),
7320
+ params.result
7321
+ );
7322
+ };
7323
+ resumeSourceSession = async (params) => {
7324
+ const backend = this.resolveBackend();
7325
+ if (!backend) {
7326
+ throw new Error("NCP backend is not ready for source session resume.");
7327
+ }
7328
+ const isIdle = await waitForSessionToBecomeIdle({
7329
+ backend,
7330
+ sessionId: params.request.sourceSessionId
7331
+ });
7332
+ if (!isIdle) {
7333
+ return;
7334
+ }
7335
+ scheduleDetached(
7336
+ async () => consumeAgentRun(
7337
+ backend.send({
7338
+ sessionId: params.request.sourceSessionId,
7339
+ message: buildSessionRequestCompletionMessage(params)
7340
+ })
7341
+ ),
7342
+ `resume source session ${params.request.sourceSessionId}`
7343
+ );
7344
+ };
7345
+ };
7346
+
6671
7347
  // src/cli/commands/ncp/ui-ncp-runtime-registry.ts
6672
7348
  import { toDisposable } from "@nextclaw/core";
6673
7349
  var DEFAULT_UI_NCP_RUNTIME_KIND = "native";
@@ -6822,10 +7498,13 @@ async function createMcpRuntimeSupport(getConfig) {
6822
7498
  console.warn(`[mcp] Failed to warm ${warmResult.name}: ${warmResult.error}`);
6823
7499
  }
6824
7500
  }
7501
+ },
7502
+ dispose: async () => {
7503
+ await mcpRegistryService.close();
6825
7504
  }
6826
7505
  };
6827
7506
  }
6828
- function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore, resolveBackend) {
7507
+ function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore, sessionCreationService, sessionRequestBroker) {
6829
7508
  return ({
6830
7509
  stateManager,
6831
7510
  sessionMetadata,
@@ -6851,24 +7530,8 @@ function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore,
6851
7530
  gatewayController: params.gatewayController,
6852
7531
  getConfig: params.getConfig,
6853
7532
  getExtensionRegistry: params.getExtensionRegistry,
6854
- writeSubagentCompletionToSession: async (completion) => {
6855
- const backend = resolveBackend();
6856
- if (!backend) {
6857
- throw new Error("NCP backend is not ready for subagent completion persistence.");
6858
- }
6859
- await persistSubagentCompletionAndResumeParent({
6860
- backend,
6861
- completion: {
6862
- sessionId: completion.sessionId,
6863
- runId: completion.runId,
6864
- toolCallId: completion.toolCallId,
6865
- label: completion.label,
6866
- task: completion.task,
6867
- result: completion.result,
6868
- status: completion.status
6869
- }
6870
- });
6871
- },
7533
+ sessionCreationService,
7534
+ sessionRequestBroker,
6872
7535
  getAdditionalTools: (context) => [
6873
7536
  ...createAssetTools({
6874
7537
  assetStore
@@ -6905,25 +7568,29 @@ function createCodexAwareRuntimeFactory(params) {
6905
7568
  function resolveRegisteredRuntimeFactory(params) {
6906
7569
  return params.registration.kind === CODEX_RUNTIME_KIND ? createCodexAwareRuntimeFactory(params) : params.registration.createRuntime;
6907
7570
  }
6908
- function createPluginRuntimeRegistrationController(params) {
6909
- const pluginRuntimeScopes = /* @__PURE__ */ new Map();
6910
- let pluginRuntimeSnapshotKey = "";
6911
- let activeExtensionRegistry;
6912
- const syncPluginRuntimeRegistrations = (extensionRegistry) => {
7571
+ var PluginRuntimeRegistrationController = class {
7572
+ constructor(runtimeRegistry, getExtensionRegistry) {
7573
+ this.runtimeRegistry = runtimeRegistry;
7574
+ this.getExtensionRegistry = getExtensionRegistry;
7575
+ }
7576
+ pluginRuntimeScopes = /* @__PURE__ */ new Map();
7577
+ pluginRuntimeSnapshotKey = "";
7578
+ activeExtensionRegistry;
7579
+ syncPluginRuntimeRegistrations = (extensionRegistry) => {
6913
7580
  const nextSnapshotKey = buildPluginRuntimeSnapshotKey(extensionRegistry);
6914
- if (nextSnapshotKey === pluginRuntimeSnapshotKey) {
7581
+ if (nextSnapshotKey === this.pluginRuntimeSnapshotKey) {
6915
7582
  return;
6916
7583
  }
6917
- pluginRuntimeSnapshotKey = nextSnapshotKey;
6918
- for (const scope of pluginRuntimeScopes.values()) {
7584
+ this.pluginRuntimeSnapshotKey = nextSnapshotKey;
7585
+ for (const scope of this.pluginRuntimeScopes.values()) {
6919
7586
  scope.dispose();
6920
7587
  }
6921
- pluginRuntimeScopes.clear();
7588
+ this.pluginRuntimeScopes.clear();
6922
7589
  for (const registration of extensionRegistry?.ncpAgentRuntimes ?? []) {
6923
7590
  const pluginId = registration.pluginId.trim() || registration.kind;
6924
- const scope = pluginRuntimeScopes.get(pluginId) ?? new DisposableStore();
6925
- pluginRuntimeScopes.set(pluginId, scope);
6926
- scope.add(params.runtimeRegistry.register({
7591
+ const scope = this.pluginRuntimeScopes.get(pluginId) ?? new DisposableStore();
7592
+ this.pluginRuntimeScopes.set(pluginId, scope);
7593
+ scope.add(this.runtimeRegistry.register({
6927
7594
  kind: registration.kind,
6928
7595
  label: registration.label,
6929
7596
  createRuntime: resolveRegisteredRuntimeFactory({
@@ -6933,17 +7600,23 @@ function createPluginRuntimeRegistrationController(params) {
6933
7600
  }));
6934
7601
  }
6935
7602
  };
6936
- const resolveActiveExtensionRegistry = () => activeExtensionRegistry ?? params.getExtensionRegistry?.();
6937
- return {
6938
- refreshPluginRuntimeRegistrations: () => {
6939
- syncPluginRuntimeRegistrations(resolveActiveExtensionRegistry());
6940
- },
6941
- applyExtensionRegistry: (extensionRegistry) => {
6942
- activeExtensionRegistry = extensionRegistry;
6943
- syncPluginRuntimeRegistrations(extensionRegistry);
7603
+ resolveActiveExtensionRegistry = () => this.activeExtensionRegistry ?? this.getExtensionRegistry?.();
7604
+ refreshPluginRuntimeRegistrations = () => {
7605
+ this.syncPluginRuntimeRegistrations(this.resolveActiveExtensionRegistry());
7606
+ };
7607
+ applyExtensionRegistry = (extensionRegistry) => {
7608
+ this.activeExtensionRegistry = extensionRegistry;
7609
+ this.syncPluginRuntimeRegistrations(extensionRegistry);
7610
+ };
7611
+ dispose = () => {
7612
+ for (const scope of this.pluginRuntimeScopes.values()) {
7613
+ scope.dispose();
6944
7614
  }
7615
+ this.pluginRuntimeScopes.clear();
7616
+ this.activeExtensionRegistry = void 0;
7617
+ this.pluginRuntimeSnapshotKey = "";
6945
7618
  };
6946
- }
7619
+ };
6947
7620
  function createUiNcpAgentHandle(params) {
6948
7621
  return {
6949
7622
  basePath: "/api/ncp/agent",
@@ -6965,7 +7638,8 @@ function createUiNcpAgentHandle(params) {
6965
7638
  resolveContentPath: (uri) => params.assetStore.resolveContentPath(uri)
6966
7639
  },
6967
7640
  applyExtensionRegistry: params.applyExtensionRegistry,
6968
- applyMcpConfig: params.applyMcpConfig
7641
+ applyMcpConfig: params.applyMcpConfig,
7642
+ dispose: params.dispose
6969
7643
  };
6970
7644
  }
6971
7645
  async function createUiNcpAgent(params) {
@@ -6973,28 +7647,38 @@ async function createUiNcpAgent(params) {
6973
7647
  onSessionUpdated: params.onSessionUpdated
6974
7648
  });
6975
7649
  const runtimeRegistry = new UiNcpRuntimeRegistry();
6976
- const { toolRegistryAdapter, applyMcpConfig } = await createMcpRuntimeSupport(params.getConfig);
7650
+ const { toolRegistryAdapter, applyMcpConfig, dispose: disposeMcpRuntimeSupport } = await createMcpRuntimeSupport(params.getConfig);
6977
7651
  const assetStore = new LocalAssetStore({
6978
7652
  rootDir: join5(getDataDir7(), "assets")
6979
7653
  });
6980
7654
  let backend = null;
7655
+ const sessionCreationService = new SessionCreationService(
7656
+ params.sessionManager,
7657
+ params.onSessionUpdated
7658
+ );
7659
+ const sessionRequestBroker = new SessionRequestBroker(
7660
+ params.sessionManager,
7661
+ sessionCreationService,
7662
+ new SessionRequestDeliveryService(() => backend),
7663
+ () => backend,
7664
+ params.onSessionUpdated
7665
+ );
6981
7666
  const createNativeRuntime = createNativeRuntimeFactory(
6982
7667
  params,
6983
7668
  toolRegistryAdapter,
6984
7669
  assetStore,
6985
- () => backend
7670
+ sessionCreationService,
7671
+ sessionRequestBroker
6986
7672
  );
6987
7673
  runtimeRegistry.register({
6988
7674
  kind: "native",
6989
7675
  label: "Native",
6990
7676
  createRuntime: createNativeRuntime
6991
7677
  });
6992
- const pluginRuntimeRegistrationController = createPluginRuntimeRegistrationController({
7678
+ const pluginRuntimeRegistrationController = new PluginRuntimeRegistrationController(
6993
7679
  runtimeRegistry,
6994
- getConfig: params.getConfig,
6995
- getExtensionRegistry: params.getExtensionRegistry,
6996
- createNativeRuntime
6997
- });
7680
+ params.getExtensionRegistry
7681
+ );
6998
7682
  pluginRuntimeRegistrationController.refreshPluginRuntimeRegistrations();
6999
7683
  backend = new DefaultNcpAgentBackend({
7000
7684
  endpointId: "nextclaw-ui-agent",
@@ -7012,6 +7696,11 @@ async function createUiNcpAgent(params) {
7012
7696
  refreshPluginRuntimeRegistrations: pluginRuntimeRegistrationController.refreshPluginRuntimeRegistrations,
7013
7697
  applyExtensionRegistry: pluginRuntimeRegistrationController.applyExtensionRegistry,
7014
7698
  applyMcpConfig,
7699
+ dispose: async () => {
7700
+ pluginRuntimeRegistrationController.dispose();
7701
+ await backend?.stop();
7702
+ await disposeMcpRuntimeSupport();
7703
+ },
7015
7704
  assetStore
7016
7705
  });
7017
7706
  }
@@ -8200,9 +8889,17 @@ var DEFERRED_NCP_AGENT_UNAVAILABLE = "ncp agent unavailable during startup";
8200
8889
  function createUnavailableError() {
8201
8890
  return new Error(DEFERRED_NCP_AGENT_UNAVAILABLE);
8202
8891
  }
8203
- function createDeferredUiNcpAgent(basePath = DEFAULT_BASE_PATH) {
8204
- let activeAgent = null;
8205
- const endpoint = {
8892
+ var DeferredUiNcpAgentControllerOwner = class {
8893
+ constructor(basePath) {
8894
+ this.basePath = basePath;
8895
+ this.agent = {
8896
+ basePath,
8897
+ agentClientEndpoint: this.endpoint
8898
+ };
8899
+ }
8900
+ agent;
8901
+ activeAgent = null;
8902
+ endpoint = {
8206
8903
  manifest: {
8207
8904
  endpointKind: "agent",
8208
8905
  endpointId: "nextclaw-ui-agent-deferred",
@@ -8217,73 +8914,69 @@ function createDeferredUiNcpAgent(basePath = DEFAULT_BASE_PATH) {
8217
8914
  deferred: true
8218
8915
  }
8219
8916
  },
8220
- async start() {
8221
- await activeAgent?.agentClientEndpoint.start();
8917
+ start: async () => {
8918
+ await this.activeAgent?.agentClientEndpoint.start();
8222
8919
  },
8223
- async stop() {
8224
- await activeAgent?.agentClientEndpoint.stop();
8920
+ stop: async () => {
8921
+ await this.activeAgent?.agentClientEndpoint.stop();
8225
8922
  },
8226
- async emit(event) {
8227
- if (!activeAgent) {
8923
+ emit: async (event) => {
8924
+ if (!this.activeAgent) {
8228
8925
  throw createUnavailableError();
8229
8926
  }
8230
- await activeAgent.agentClientEndpoint.emit(event);
8927
+ await this.activeAgent.agentClientEndpoint.emit(event);
8231
8928
  },
8232
- subscribe(listener) {
8233
- if (!activeAgent) {
8929
+ subscribe: (listener) => {
8930
+ if (!this.activeAgent) {
8234
8931
  return () => void 0;
8235
8932
  }
8236
- return activeAgent.agentClientEndpoint.subscribe(listener);
8933
+ return this.activeAgent.agentClientEndpoint.subscribe(listener);
8237
8934
  },
8238
- async send(envelope) {
8239
- if (!activeAgent) {
8935
+ send: async (envelope) => {
8936
+ if (!this.activeAgent) {
8240
8937
  throw createUnavailableError();
8241
8938
  }
8242
- await activeAgent.agentClientEndpoint.send(envelope);
8939
+ await this.activeAgent.agentClientEndpoint.send(envelope);
8243
8940
  },
8244
- async stream(payload) {
8245
- if (!activeAgent) {
8941
+ stream: async (payload) => {
8942
+ if (!this.activeAgent) {
8246
8943
  throw createUnavailableError();
8247
8944
  }
8248
- await activeAgent.agentClientEndpoint.stream(payload);
8945
+ await this.activeAgent.agentClientEndpoint.stream(payload);
8249
8946
  },
8250
- async abort(payload) {
8251
- if (!activeAgent) {
8947
+ abort: async (payload) => {
8948
+ if (!this.activeAgent) {
8252
8949
  throw createUnavailableError();
8253
8950
  }
8254
- await activeAgent.agentClientEndpoint.abort(payload);
8951
+ await this.activeAgent.agentClientEndpoint.abort(payload);
8255
8952
  }
8256
8953
  };
8257
- const agent = {
8258
- basePath,
8259
- agentClientEndpoint: endpoint
8954
+ activate = (nextAgent) => {
8955
+ this.activeAgent = nextAgent;
8956
+ this.agent.basePath = nextAgent.basePath ?? this.basePath;
8957
+ this.agent.streamProvider = nextAgent.streamProvider;
8958
+ this.agent.listSessionTypes = nextAgent.listSessionTypes;
8959
+ this.agent.assetApi = nextAgent.assetApi;
8260
8960
  };
8261
- const clear = () => {
8262
- activeAgent = null;
8263
- agent.basePath = basePath;
8264
- agent.streamProvider = void 0;
8265
- agent.listSessionTypes = void 0;
8266
- agent.assetApi = void 0;
8961
+ clear = () => {
8962
+ this.activeAgent = null;
8963
+ this.agent.basePath = this.basePath;
8964
+ this.agent.streamProvider = void 0;
8965
+ this.agent.listSessionTypes = void 0;
8966
+ this.agent.assetApi = void 0;
8267
8967
  };
8268
- return {
8269
- agent,
8270
- activate(nextAgent) {
8271
- activeAgent = nextAgent;
8272
- agent.basePath = nextAgent.basePath ?? basePath;
8273
- agent.streamProvider = nextAgent.streamProvider;
8274
- agent.listSessionTypes = nextAgent.listSessionTypes;
8275
- agent.assetApi = nextAgent.assetApi;
8276
- },
8277
- clear,
8278
- async close() {
8279
- const current = activeAgent;
8280
- clear();
8281
- await current?.agentClientEndpoint.stop();
8282
- },
8283
- isReady() {
8284
- return activeAgent !== null;
8285
- }
8968
+ close = async () => {
8969
+ const current = this.activeAgent;
8970
+ this.clear();
8971
+ await current?.agentClientEndpoint.stop();
8972
+ await current?.dispose?.();
8286
8973
  };
8974
+ isReady = () => {
8975
+ return this.activeAgent !== null;
8976
+ };
8977
+ };
8978
+ function createDeferredUiNcpAgent(basePath = DEFAULT_BASE_PATH) {
8979
+ return new DeferredUiNcpAgentControllerOwner(basePath);
8287
8980
  }
8288
8981
 
8289
8982
  // src/cli/commands/service-gateway-startup.ts
@@ -9026,7 +9719,7 @@ function readStringList(value) {
9026
9719
  }
9027
9720
  return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
9028
9721
  }
9029
- function readOptionalString2(value) {
9722
+ function readOptionalString6(value) {
9030
9723
  if (typeof value !== "string") {
9031
9724
  return void 0;
9032
9725
  }
@@ -9036,10 +9729,10 @@ function readOptionalString2(value) {
9036
9729
  function resolvePluginRuntimeAttachments(ctx) {
9037
9730
  const mediaPaths = readStringList(ctx.MediaPaths);
9038
9731
  const mediaUrls = readStringList(ctx.MediaUrls);
9039
- const fallbackPath = readOptionalString2(ctx.MediaPath);
9040
- const fallbackUrl = readOptionalString2(ctx.MediaUrl);
9732
+ const fallbackPath = readOptionalString6(ctx.MediaPath);
9733
+ const fallbackUrl = readOptionalString6(ctx.MediaUrl);
9041
9734
  const mediaTypes = readStringList(ctx.MediaTypes);
9042
- const fallbackType = readOptionalString2(ctx.MediaType);
9735
+ const fallbackType = readOptionalString6(ctx.MediaType);
9043
9736
  const entryCount = Math.max(
9044
9737
  mediaPaths.length,
9045
9738
  mediaUrls.length,