nextclaw 0.16.31 → 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 (73) hide show
  1. package/dist/cli/index.js +1031 -318
  2. package/package.json +11 -11
  3. package/ui-dist/assets/{ChannelsList-ByHWHkQS.js → ChannelsList-DVDu1xvz.js} +6 -6
  4. package/ui-dist/assets/ChatPage-Z9tRzm_n.js +43 -0
  5. package/ui-dist/assets/DocBrowser-B9OaZjmg.js +1 -0
  6. package/ui-dist/assets/{DocBrowser-3y_NHZ71.js → DocBrowser-BmtBLFU0.js} +1 -1
  7. package/ui-dist/assets/{DocBrowserContext-CVJuwCcw.js → DocBrowserContext-YIKkPb76.js} +1 -1
  8. package/ui-dist/assets/{LogoBadge-D8fyilO-.js → LogoBadge-F7ZWdxLT.js} +1 -1
  9. package/ui-dist/assets/{MarketplacePage-CmhsZXr1.js → MarketplacePage-Buo9HrOz.js} +2 -2
  10. package/ui-dist/assets/MarketplacePage-D6rVQEQR.js +1 -0
  11. package/ui-dist/assets/{McpMarketplacePage-C7PkCYbp.js → McpMarketplacePage-JnkYwK7p.js} +2 -2
  12. package/ui-dist/assets/ModelConfig-BYRhgp0c.js +1 -0
  13. package/ui-dist/assets/ProvidersList-DmLyyHvX.js +1 -0
  14. package/ui-dist/assets/RemoteAccessPage-CDSSvH7Z.js +1 -0
  15. package/ui-dist/assets/RuntimeConfig-v7a7Fe3x.js +1 -0
  16. package/ui-dist/assets/{SearchConfig-Dm7r2yfp.js → SearchConfig-D5f1EkLE.js} +1 -1
  17. package/ui-dist/assets/{SecretsConfig-BBP_mbQh.js → SecretsConfig-D61IKcYt.js} +2 -2
  18. package/ui-dist/assets/{SessionsConfig-6wNJloZN.js → SessionsConfig-BRIxVTEv.js} +2 -2
  19. package/ui-dist/assets/{book-open-B26jGBjY.js → book-open-CXoF5nQC.js} +1 -1
  20. package/ui-dist/assets/chat-session-display-D0WpnuRZ.js +1 -0
  21. package/ui-dist/assets/{chunk-JZWAC4HX-B-4B29RN.js → chunk-JZWAC4HX-CvRWvTy5.js} +1 -1
  22. package/ui-dist/assets/{config-BaC29Qf-.js → config-DJswxxE8.js} +1 -1
  23. package/ui-dist/assets/{createLucideIcon-DiFAvXmK.js → createLucideIcon-CjGHOWb6.js} +1 -1
  24. package/ui-dist/assets/{dist-pCfWPG1A.js → dist-Cl2QB-2y.js} +1 -1
  25. package/ui-dist/assets/{dist-kW_O3kyZ.js → dist-nqTTbVdA.js} +1 -1
  26. package/ui-dist/assets/{external-link-D5-p-Gmm.js → external-link-tIO7zING.js} +1 -1
  27. package/ui-dist/assets/{hash-BlwrSV0q.js → hash-JWUyl1pT.js} +1 -1
  28. package/ui-dist/assets/i18n-CDHMXlRZ.js +1 -0
  29. package/ui-dist/assets/{index-DvKS3L9j.js → index-BuwbBgmT.js} +3 -3
  30. package/ui-dist/assets/index-bZ8cqQIS.css +1 -0
  31. package/ui-dist/assets/{label-RyXfZqkP.js → label-BIpeNu4r.js} +1 -1
  32. package/ui-dist/assets/loader-circle-Cs8XVFTw.js +1 -0
  33. package/ui-dist/assets/{logos-Bpl8QTgI.js → logos-DThdM9lk.js} +1 -1
  34. package/ui-dist/assets/{page-layout--S0YBU0W.js → page-layout-D3Xo605Z.js} +1 -1
  35. package/ui-dist/assets/plus-PHf8q-Ct.js +1 -0
  36. package/ui-dist/assets/{popover-BEjfbEwy.js → popover-BJRUGA_H.js} +1 -1
  37. package/ui-dist/assets/provider-models-bz5y28rq.js +1 -0
  38. package/ui-dist/assets/{react-BuSP2-8B.js → react-7ZHqQtEV.js} +1 -1
  39. package/ui-dist/assets/refresh-ccw-CC6-_QuL.js +1 -0
  40. package/ui-dist/assets/{save-DPPPpD_c.js → save-DJM5RRWW.js} +1 -1
  41. package/ui-dist/assets/search-C91yH_6y.js +1 -0
  42. package/ui-dist/assets/{security-config-6t78Ph-I.js → security-config-DbUyWcQz.js} +1 -1
  43. package/ui-dist/assets/{select-CT50pzod.js → select-DSkTc61S.js} +1 -1
  44. package/ui-dist/assets/skeleton-Dzg-HOiN.js +1 -0
  45. package/ui-dist/assets/{status-dot-BbBqRHfh.js → status-dot-LNBlDu3q.js} +1 -1
  46. package/ui-dist/assets/{switch-D3l6AcCk.js → switch-Bo-Y46HZ.js} +1 -1
  47. package/ui-dist/assets/tabs-custom-DXv507_2.js +1 -0
  48. package/ui-dist/assets/{trash-2-B2_AGVE3.js → trash-2-DFZmW6Gg.js} +1 -1
  49. package/ui-dist/assets/useConfirmDialog-COwYXDKm.js +1 -0
  50. package/ui-dist/assets/{useMutation-BzCrO8j-.js → useMutation-DrZrOgVL.js} +1 -1
  51. package/ui-dist/assets/x-D7Q1yqSF.js +1 -0
  52. package/ui-dist/index.html +18 -18
  53. package/ui-dist/assets/ChatPage-FdT3pDnw.js +0 -42
  54. package/ui-dist/assets/DocBrowser-CMdPdbZj.js +0 -1
  55. package/ui-dist/assets/MarketplacePage-9oKmxN2n.js +0 -1
  56. package/ui-dist/assets/ModelConfig-DmCY6jWM.js +0 -1
  57. package/ui-dist/assets/ProvidersList-ClT-34aX.js +0 -1
  58. package/ui-dist/assets/RemoteAccessPage-B6hUZl1O.js +0 -1
  59. package/ui-dist/assets/RuntimeConfig-C5aqliGk.js +0 -1
  60. package/ui-dist/assets/chat-session-display-Bjmn4aIZ.js +0 -1
  61. package/ui-dist/assets/i18n-CSytxMFI.js +0 -1
  62. package/ui-dist/assets/index-CUy6doWo.css +0 -1
  63. package/ui-dist/assets/loader-circle-B2J777gj.js +0 -1
  64. package/ui-dist/assets/plus-CM9XJ0Tf.js +0 -1
  65. package/ui-dist/assets/provider-models-C8JQUd1E.js +0 -1
  66. package/ui-dist/assets/search-Ctaw34Kp.js +0 -1
  67. package/ui-dist/assets/skeleton-Bycyb0zU.js +0 -1
  68. package/ui-dist/assets/tabs-custom-TZQ5WPWP.js +0 -1
  69. package/ui-dist/assets/useConfirmDialog-BDpdjfIO.js +0 -1
  70. package/ui-dist/assets/x-CHOBE-63.js +0 -1
  71. /package/ui-dist/assets/{config-hints-fGnUjDe9.js → config-hints-WtpHP_DW.js} +0 -0
  72. /package/ui-dist/assets/{config-layout-B-7erZRN.js → config-layout-LQ10ozRC.js} +0 -0
  73. /package/ui-dist/assets/{marketplace-localization-CXeGRf6E.js → marketplace-localization-CxSTG9wr.js} +0 -0
package/dist/cli/index.js CHANGED
@@ -698,11 +698,11 @@ function collectFiles(rootDir) {
698
698
  }
699
699
  function installBuiltinSkill(workdir, destinationDir, skillName) {
700
700
  const loader = new SkillsLoader(workdir);
701
- const builtin = loader.listSkills(false).find((skill) => skill.name === skillName && skill.source === "builtin");
702
- if (!builtin) {
703
- throw new Error(`Builtin skill not found in local core bundle: ${skillName}`);
701
+ const workspaceSkill = loader.listSkills(false).find((skill) => skill.name === skillName && skill.source === "workspace");
702
+ if (!workspaceSkill) {
703
+ throw new Error(`Workspace skill not found in local installation: ${skillName}`);
704
704
  }
705
- cpSync(dirname(builtin.path), destinationDir, { recursive: true, force: true });
705
+ cpSync(dirname(workspaceSkill.path), destinationDir, { recursive: true, force: true });
706
706
  }
707
707
  async function fetchMarketplaceSkillItem(apiBase, slug) {
708
708
  return runWithMarketplaceNetworkRetry(async () => {
@@ -5261,6 +5261,8 @@ import {
5261
5261
  InputBudgetPruner,
5262
5262
  getWorkspacePath as getWorkspacePath7,
5263
5263
  parseThinkingLevel as parseThinkingLevel2,
5264
+ readSessionProjectRoot,
5265
+ resolveSessionWorkspacePath,
5264
5266
  resolveThinkingLevel
5265
5267
  } from "@nextclaw/core";
5266
5268
 
@@ -5638,14 +5640,230 @@ import {
5638
5640
  SessionsHistoryTool,
5639
5641
  SessionsListTool,
5640
5642
  SessionsSendTool,
5641
- SpawnTool,
5642
- SubagentManager,
5643
- SubagentsTool,
5644
5643
  ToolRegistry,
5645
5644
  WebFetchTool,
5646
5645
  WebSearchTool,
5647
5646
  WriteFileTool
5648
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
5649
5867
  function toToolParams(args) {
5650
5868
  if (isRecord3(args)) {
5651
5869
  return args;
@@ -5695,46 +5913,11 @@ var CoreToolNcpAdapter = class {
5695
5913
  var NextclawNcpToolRegistry = class {
5696
5914
  constructor(options) {
5697
5915
  this.options = options;
5698
- const initialConfig = this.options.getConfig();
5699
- this.subagents = new SubagentManager({
5700
- providerManager: this.options.providerManager,
5701
- workspace: initialConfig.agents.defaults.workspace,
5702
- bus: this.options.bus,
5703
- model: initialConfig.agents.defaults.model,
5704
- contextTokens: initialConfig.agents.defaults.contextTokens,
5705
- searchConfig: initialConfig.search,
5706
- execConfig: initialConfig.tools.exec,
5707
- restrictToWorkspace: initialConfig.tools.restrictToWorkspace,
5708
- completionSink: async (params) => {
5709
- const sessionId = params.origin.sessionKey?.trim();
5710
- if (!sessionId || !this.options.writeSubagentCompletionToSession) {
5711
- return;
5712
- }
5713
- await this.options.writeSubagentCompletionToSession({
5714
- sessionId,
5715
- runId: params.runId,
5716
- toolCallId: params.origin.toolCallId,
5717
- label: params.label,
5718
- task: params.task,
5719
- result: params.result,
5720
- status: params.status
5721
- });
5722
- }
5723
- });
5724
5916
  }
5725
- subagents;
5726
5917
  registry = new ToolRegistry();
5727
5918
  tools = /* @__PURE__ */ new Map();
5728
5919
  currentExtensionToolContext = {};
5729
- prepareForRun(context) {
5730
- this.subagents.updateRuntimeOptions({
5731
- model: context.model,
5732
- maxTokens: context.maxTokens,
5733
- contextTokens: context.contextTokens,
5734
- searchConfig: context.searchConfig,
5735
- execConfig: { timeout: context.execTimeoutSeconds },
5736
- restrictToWorkspace: context.restrictToWorkspace
5737
- });
5920
+ prepareForRun = (context) => {
5738
5921
  this.currentExtensionToolContext = {
5739
5922
  config: context.config,
5740
5923
  workspaceDir: context.workspace,
@@ -5748,30 +5931,30 @@ var NextclawNcpToolRegistry = class {
5748
5931
  this.registerDefaultTools(context);
5749
5932
  this.registerExtensionTools(context);
5750
5933
  this.registerAdditionalTools(context);
5751
- }
5752
- listTools() {
5934
+ };
5935
+ listTools = () => {
5753
5936
  return [...this.tools.values()].filter((tool) => this.isToolAvailable(tool.name));
5754
- }
5755
- getTool(name) {
5937
+ };
5938
+ getTool = (name) => {
5756
5939
  if (!this.isToolAvailable(name)) {
5757
5940
  return void 0;
5758
5941
  }
5759
5942
  return this.tools.get(name);
5760
- }
5761
- getToolDefinitions() {
5943
+ };
5944
+ getToolDefinitions = () => {
5762
5945
  return this.listTools().map((tool) => ({
5763
5946
  name: tool.name,
5764
5947
  description: tool.description,
5765
5948
  parameters: tool.parameters
5766
5949
  }));
5767
- }
5768
- async execute(toolCallId, toolName, args) {
5950
+ };
5951
+ execute = async (toolCallId, toolName, args) => {
5769
5952
  if (this.registry.has(toolName)) {
5770
5953
  return this.registry.executeRaw(toolName, toToolParams(args), toolCallId);
5771
5954
  }
5772
5955
  return this.tools.get(toolName)?.execute(args);
5773
- }
5774
- registerDefaultTools(context) {
5956
+ };
5957
+ registerDefaultTools = (context) => {
5775
5958
  const allowedDir = context.restrictToWorkspace ? context.workspace : void 0;
5776
5959
  this.registerTool(new ReadFileTool(allowedDir));
5777
5960
  this.registerTool(new WriteFileTool(allowedDir));
@@ -5791,15 +5974,31 @@ var NextclawNcpToolRegistry = class {
5791
5974
  this.registerTool(new WebSearchTool(context.searchConfig));
5792
5975
  this.registerTool(new WebFetchTool());
5793
5976
  this.registerMessagingTools(context);
5794
- const spawnTool = new SpawnTool(this.subagents);
5795
- spawnTool.setContext(
5796
- context.channel,
5797
- context.chatId,
5798
- context.model,
5799
- context.sessionId,
5800
- context.agentId
5801
- );
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
+ });
5802
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);
5803
6002
  this.registerTool(new SessionsListTool(this.options.sessionManager));
5804
6003
  this.registerTool(new SessionsHistoryTool(this.options.sessionManager));
5805
6004
  const sessionsSendTool = new SessionsSendTool(this.options.sessionManager, this.options.bus);
@@ -5814,12 +6013,11 @@ var NextclawNcpToolRegistry = class {
5814
6013
  this.registerTool(sessionsSendTool);
5815
6014
  this.registerTool(new MemorySearchTool(context.workspace));
5816
6015
  this.registerTool(new MemoryGetTool(context.workspace));
5817
- this.registerTool(new SubagentsTool(this.subagents));
5818
6016
  const gatewayTool = new GatewayTool(this.options.gatewayController);
5819
6017
  gatewayTool.setContext({ sessionKey: context.sessionId });
5820
6018
  this.registerTool(gatewayTool);
5821
- }
5822
- registerMessagingTools(context) {
6019
+ };
6020
+ registerMessagingTools = (context) => {
5823
6021
  const accountId = readMetadataAccountId(context.metadata, {});
5824
6022
  const messageTool = new MessageTool((message) => this.options.bus.publishOutbound(message));
5825
6023
  messageTool.setContext(context.channel, context.chatId, accountId ?? null);
@@ -5829,8 +6027,8 @@ var NextclawNcpToolRegistry = class {
5829
6027
  cronTool.setContext(context.channel, context.chatId, accountId ?? null);
5830
6028
  this.registerTool(cronTool);
5831
6029
  }
5832
- }
5833
- registerExtensionTools(context) {
6030
+ };
6031
+ registerExtensionTools = (context) => {
5834
6032
  const extensionRegistry = this.options.getExtensionRegistry?.();
5835
6033
  if (!extensionRegistry || extensionRegistry.tools.length === 0) {
5836
6034
  return;
@@ -5854,15 +6052,15 @@ var NextclawNcpToolRegistry = class {
5854
6052
  );
5855
6053
  }
5856
6054
  }
5857
- }
5858
- registerTool(tool) {
6055
+ };
6056
+ registerTool = (tool) => {
5859
6057
  this.registry.register(tool);
5860
6058
  this.tools.set(
5861
6059
  tool.name,
5862
6060
  new CoreToolNcpAdapter(tool, async (toolName, args) => this.registry.execute(toolName, toToolParams(args)))
5863
6061
  );
5864
- }
5865
- registerAdditionalTools(context) {
6062
+ };
6063
+ registerAdditionalTools = (context) => {
5866
6064
  const tools = this.options.getAdditionalTools?.(context) ?? [];
5867
6065
  for (const tool of tools) {
5868
6066
  if (this.tools.has(tool.name)) {
@@ -5870,11 +6068,11 @@ var NextclawNcpToolRegistry = class {
5870
6068
  }
5871
6069
  this.tools.set(tool.name, tool);
5872
6070
  }
5873
- }
5874
- isToolAvailable(name) {
6071
+ };
6072
+ isToolAvailable = (name) => {
5875
6073
  const coreTool = this.registry.get(name);
5876
6074
  return coreTool ? coreTool.isAvailable() : true;
5877
- }
6075
+ };
5878
6076
  };
5879
6077
  function resolveAgentHandoffDepth(metadata) {
5880
6078
  const rawDepth = Number(metadata.agent_handoff_depth ?? 0);
@@ -5982,6 +6180,19 @@ function prependRequestedSkills(content, requestedSkillNames) {
5982
6180
 
5983
6181
  ${content}`;
5984
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
+ }
5985
6196
  function filterTools(toolDefinitions, requestedToolNames) {
5986
6197
  if (toolDefinitions.length === 0) {
5987
6198
  return void 0;
@@ -6011,7 +6222,7 @@ var NextclawNcpContextBuilder = class {
6011
6222
  this.options = options;
6012
6223
  }
6013
6224
  inputBudgetPruner = new InputBudgetPruner();
6014
- prepare(input, _options) {
6225
+ prepare = (input, _options) => {
6015
6226
  const config2 = this.options.getConfig();
6016
6227
  const profile = resolvePrimaryAgentProfile(config2);
6017
6228
  const requestMetadata = mergeInputMetadata(input);
@@ -6021,6 +6232,10 @@ var NextclawNcpContextBuilder = class {
6021
6232
  requestMetadata,
6022
6233
  fallbackModel: profile.model
6023
6234
  });
6235
+ const effectiveWorkspace = resolveSessionWorkspacePath({
6236
+ sessionMetadata: session.metadata,
6237
+ workspace: profile.workspace
6238
+ });
6024
6239
  syncSessionThinkingPreference({ session, requestMetadata });
6025
6240
  const { channel, chatId } = resolveSessionChannelContext({
6026
6241
  session,
@@ -6058,7 +6273,7 @@ var NextclawNcpContextBuilder = class {
6058
6273
  model: effectiveModel,
6059
6274
  restrictToWorkspace: profile.restrictToWorkspace,
6060
6275
  searchConfig: profile.searchConfig,
6061
- workspace: profile.workspace
6276
+ workspace: effectiveWorkspace
6062
6277
  });
6063
6278
  const accountId = readAccountIdForHints(requestMetadata, session.metadata);
6064
6279
  const messageToolHints = this.options.resolveMessageToolHints?.({
@@ -6068,7 +6283,15 @@ var NextclawNcpContextBuilder = class {
6068
6283
  accountId: accountId ?? null
6069
6284
  });
6070
6285
  const toolDefinitions = this.options.toolRegistry.getToolDefinitions();
6071
- const contextBuilder = new ContextBuilder(profile.workspace, config2.agents.context);
6286
+ const additionalSystemSections = [buildSessionOrchestrationSection()];
6287
+ const contextBuilder = new ContextBuilder(
6288
+ effectiveWorkspace,
6289
+ config2.agents.context,
6290
+ {
6291
+ hostWorkspace: profile.workspace,
6292
+ sessionProjectRoot: readSessionProjectRoot(session.metadata)
6293
+ }
6294
+ );
6072
6295
  const sessionMessages = _options?.sessionMessages ?? [];
6073
6296
  const messages = contextBuilder.buildMessages({
6074
6297
  history: toLegacyMessages([...sessionMessages], {
@@ -6082,7 +6305,8 @@ var NextclawNcpContextBuilder = class {
6082
6305
  thinkingLevel: runtimeThinking,
6083
6306
  skillNames: requestedSkillNames,
6084
6307
  messageToolHints,
6085
- availableTools: buildToolCatalogEntries(toolDefinitions)
6308
+ availableTools: buildToolCatalogEntries(toolDefinitions),
6309
+ additionalSystemSections
6086
6310
  });
6087
6311
  messages[messages.length - 1] = {
6088
6312
  role: currentTurn.currentRole,
@@ -6098,11 +6322,20 @@ var NextclawNcpContextBuilder = class {
6098
6322
  model: effectiveModel,
6099
6323
  thinkingLevel: runtimeThinking
6100
6324
  };
6101
- }
6325
+ };
6102
6326
  };
6103
6327
 
6104
6328
  // src/cli/commands/ncp/nextclaw-agent-session-store.ts
6105
6329
  import { sanitizeAssistantReplyTags as sanitizeAssistantReplyTags2 } from "@nextclaw/ncp";
6330
+
6331
+ // src/cli/commands/ncp/nextclaw-agent-session-metadata.utils.ts
6332
+ function resolvePersistedSessionMetadata(params) {
6333
+ const messageMetadata = extractMessageMetadata(params.sessionRecord.messages);
6334
+ const nextMetadata = params.preserveExistingMetadata ? mergeSessionMetadata(params.currentMetadata, messageMetadata) : mergeSessionMetadata({}, messageMetadata);
6335
+ return mergeSessionMetadata(nextMetadata, cloneMetadata(params.sessionRecord.metadata));
6336
+ }
6337
+
6338
+ // src/cli/commands/ncp/nextclaw-agent-session-store.ts
6106
6339
  function tryParseJson(value) {
6107
6340
  try {
6108
6341
  return JSON.parse(value);
@@ -6182,8 +6415,7 @@ function parseLegacyToolCalls(value) {
6182
6415
  }).filter((entry) => entry !== null);
6183
6416
  }
6184
6417
  function createMessageId(sessionId, index, role, timestamp) {
6185
- const safeRole = role.trim().toLowerCase() || "message";
6186
- return `${sessionId}:${safeRole}:${index}:${timestamp}`;
6418
+ return `${sessionId}:${role.trim().toLowerCase() || "message"}:${index}:${timestamp}`;
6187
6419
  }
6188
6420
  function isNcpMessagePart(value) {
6189
6421
  return Boolean(value) && typeof value === "object" && !Array.isArray(value) && typeof value.type === "string";
@@ -6360,19 +6592,14 @@ var NextclawAgentSessionStore = class {
6360
6592
  this.sessionManager = sessionManager;
6361
6593
  this.options = options;
6362
6594
  }
6363
- async getSession(sessionId) {
6595
+ getSession = async (sessionId) => {
6364
6596
  const session = this.sessionManager.getIfExists(sessionId);
6365
6597
  if (!session) {
6366
6598
  return null;
6367
6599
  }
6368
- return {
6369
- sessionId,
6370
- messages: toNcpMessages(sessionId, session.messages),
6371
- updatedAt: session.updatedAt.toISOString(),
6372
- metadata: structuredClone(session.metadata)
6373
- };
6374
- }
6375
- async listSessions() {
6600
+ return { sessionId, messages: toNcpMessages(sessionId, session.messages), updatedAt: session.updatedAt.toISOString(), metadata: structuredClone(session.metadata) };
6601
+ };
6602
+ listSessions = async () => {
6376
6603
  const records = this.sessionManager.listSessions();
6377
6604
  const sessions = [];
6378
6605
  for (const record of records) {
@@ -6384,27 +6611,22 @@ var NextclawAgentSessionStore = class {
6384
6611
  if (!session) {
6385
6612
  continue;
6386
6613
  }
6387
- sessions.push({
6388
- sessionId,
6389
- messages: toNcpMessages(sessionId, session.messages),
6390
- updatedAt: session.updatedAt.toISOString(),
6391
- metadata: structuredClone(session.metadata)
6392
- });
6614
+ sessions.push({ sessionId, messages: toNcpMessages(sessionId, session.messages), updatedAt: session.updatedAt.toISOString(), metadata: structuredClone(session.metadata) });
6393
6615
  }
6394
6616
  sessions.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
6395
6617
  return sessions;
6396
- }
6397
- async saveSession(sessionRecord) {
6618
+ };
6619
+ persistSession = async (sessionRecord, options) => {
6398
6620
  if (this.options.writeMode === "runtime-owned") {
6399
6621
  return;
6400
6622
  }
6401
6623
  const session = this.sessionManager.getIfExists(sessionRecord.sessionId) ?? this.sessionManager.getOrCreate(sessionRecord.sessionId);
6402
6624
  const legacyMessages = toLegacyMessages(sessionRecord.messages);
6403
- const nextMetadata = mergeSessionMetadata(
6404
- session.metadata,
6405
- extractMessageMetadata(sessionRecord.messages)
6406
- );
6407
- session.metadata = mergeSessionMetadata(nextMetadata, cloneMetadata(sessionRecord.metadata));
6625
+ session.metadata = resolvePersistedSessionMetadata({
6626
+ currentMetadata: session.metadata,
6627
+ sessionRecord,
6628
+ preserveExistingMetadata: options.preserveExistingMetadata
6629
+ });
6408
6630
  this.sessionManager.clear(session);
6409
6631
  for (const message of legacyMessages) {
6410
6632
  this.sessionManager.appendEvent(session, {
@@ -6420,8 +6642,18 @@ var NextclawAgentSessionStore = class {
6420
6642
  }
6421
6643
  this.sessionManager.save(session);
6422
6644
  this.options.onSessionUpdated?.(sessionRecord.sessionId);
6423
- }
6424
- async deleteSession(sessionId) {
6645
+ };
6646
+ saveSession = async (sessionRecord) => {
6647
+ await this.persistSession(sessionRecord, {
6648
+ preserveExistingMetadata: true
6649
+ });
6650
+ };
6651
+ replaceSession = async (sessionRecord) => {
6652
+ await this.persistSession(sessionRecord, {
6653
+ preserveExistingMetadata: false
6654
+ });
6655
+ };
6656
+ deleteSession = async (sessionId) => {
6425
6657
  const existing = await this.getSession(sessionId);
6426
6658
  if (!existing) {
6427
6659
  return null;
@@ -6429,104 +6661,8 @@ var NextclawAgentSessionStore = class {
6429
6661
  this.sessionManager.delete(sessionId);
6430
6662
  this.options.onSessionUpdated?.(sessionId);
6431
6663
  return existing;
6432
- }
6433
- };
6434
-
6435
- // src/cli/commands/ncp/ncp-subagent-completion-message.ts
6436
- import {
6437
- NCP_INTERNAL_VISIBILITY_METADATA_KEY
6438
- } from "@nextclaw/ncp";
6439
- function escapeXml(value) {
6440
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
6441
- }
6442
- function buildSubagentCompletionFollowUpMessage(params) {
6443
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
6444
- const statusLabel = params.status === "ok" ? "completed" : "failed";
6445
- return {
6446
- id: `${params.sessionId}:system:subagent-follow-up:${timestamp}`,
6447
- sessionId: params.sessionId,
6448
- role: "user",
6449
- status: "final",
6450
- timestamp,
6451
- parts: [
6452
- {
6453
- type: "text",
6454
- text: [
6455
- "<task-notification>",
6456
- "<source>subagent_completion</source>",
6457
- `<label>${escapeXml(params.label)}</label>`,
6458
- `<status>${statusLabel}</status>`,
6459
- `<delegated-task>${escapeXml(params.task)}</delegated-task>`,
6460
- `<result>${escapeXml(params.result)}</result>`,
6461
- "<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>",
6462
- "</task-notification>"
6463
- ].join("\n")
6464
- }
6465
- ],
6466
- metadata: {
6467
- [NCP_INTERNAL_VISIBILITY_METADATA_KEY]: "hidden",
6468
- system_event_kind: "subagent_completion_follow_up",
6469
- subagent_label: params.label,
6470
- subagent_status: params.status,
6471
- subagent_task: params.task
6472
- }
6473
6664
  };
6474
- }
6475
-
6476
- // src/cli/commands/ncp/ncp-subagent-completion-follow-up.ts
6477
- async function consumeAgentRun(events) {
6478
- for await (const _event of events) {
6479
- void _event;
6480
- }
6481
- }
6482
- async function sleep(ms) {
6483
- await new Promise((resolve16) => setTimeout(resolve16, ms));
6484
- }
6485
- async function waitForSessionToBecomeIdle(params) {
6486
- const timeoutMs = params.timeoutMs ?? 15e3;
6487
- const intervalMs = params.intervalMs ?? 150;
6488
- const deadline = Date.now() + timeoutMs;
6489
- while (Date.now() <= deadline) {
6490
- const summary = await params.backend.getSession(params.sessionId);
6491
- if (!summary) {
6492
- return false;
6493
- }
6494
- if (summary.status !== "running") {
6495
- return true;
6496
- }
6497
- await sleep(intervalMs);
6498
- }
6499
- return false;
6500
- }
6501
- async function persistSubagentCompletionAndResumeParent(params) {
6502
- const isIdle = await waitForSessionToBecomeIdle({
6503
- backend: params.backend,
6504
- sessionId: params.completion.sessionId
6505
- });
6506
- if (!isIdle) {
6507
- return;
6508
- }
6509
- if (params.completion.toolCallId?.trim()) {
6510
- await params.backend.updateToolCallResult(
6511
- params.completion.sessionId,
6512
- params.completion.toolCallId.trim(),
6513
- {
6514
- kind: "nextclaw.subagent_run",
6515
- runId: params.completion.runId,
6516
- label: params.completion.label,
6517
- task: params.completion.task,
6518
- status: params.completion.status === "ok" ? "completed" : "failed",
6519
- result: params.completion.result
6520
- }
6521
- );
6522
- }
6523
- await consumeAgentRun(
6524
- params.backend.send({
6525
- sessionId: params.completion.sessionId,
6526
- message: buildSubagentCompletionFollowUpMessage(params.completion)
6527
- })
6528
- );
6529
- }
6665
+ };
6530
6666
 
6531
6667
  // src/cli/commands/ncp/provider-manager-ncp-llm-api.ts
6532
6668
  import { parseThinkingLevel as parseThinkingLevel3 } from "@nextclaw/core";
@@ -6645,6 +6781,569 @@ var ProviderManagerNcpLLMApi = class {
6645
6781
  }
6646
6782
  };
6647
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
+
6648
7347
  // src/cli/commands/ncp/ui-ncp-runtime-registry.ts
6649
7348
  import { toDisposable } from "@nextclaw/core";
6650
7349
  var DEFAULT_UI_NCP_RUNTIME_KIND = "native";
@@ -6799,10 +7498,13 @@ async function createMcpRuntimeSupport(getConfig) {
6799
7498
  console.warn(`[mcp] Failed to warm ${warmResult.name}: ${warmResult.error}`);
6800
7499
  }
6801
7500
  }
7501
+ },
7502
+ dispose: async () => {
7503
+ await mcpRegistryService.close();
6802
7504
  }
6803
7505
  };
6804
7506
  }
6805
- function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore, resolveBackend) {
7507
+ function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore, sessionCreationService, sessionRequestBroker) {
6806
7508
  return ({
6807
7509
  stateManager,
6808
7510
  sessionMetadata,
@@ -6828,24 +7530,8 @@ function createNativeRuntimeFactory(params, mcpToolRegistryAdapter, assetStore,
6828
7530
  gatewayController: params.gatewayController,
6829
7531
  getConfig: params.getConfig,
6830
7532
  getExtensionRegistry: params.getExtensionRegistry,
6831
- writeSubagentCompletionToSession: async (completion) => {
6832
- const backend = resolveBackend();
6833
- if (!backend) {
6834
- throw new Error("NCP backend is not ready for subagent completion persistence.");
6835
- }
6836
- await persistSubagentCompletionAndResumeParent({
6837
- backend,
6838
- completion: {
6839
- sessionId: completion.sessionId,
6840
- runId: completion.runId,
6841
- toolCallId: completion.toolCallId,
6842
- label: completion.label,
6843
- task: completion.task,
6844
- result: completion.result,
6845
- status: completion.status
6846
- }
6847
- });
6848
- },
7533
+ sessionCreationService,
7534
+ sessionRequestBroker,
6849
7535
  getAdditionalTools: (context) => [
6850
7536
  ...createAssetTools({
6851
7537
  assetStore
@@ -6882,25 +7568,29 @@ function createCodexAwareRuntimeFactory(params) {
6882
7568
  function resolveRegisteredRuntimeFactory(params) {
6883
7569
  return params.registration.kind === CODEX_RUNTIME_KIND ? createCodexAwareRuntimeFactory(params) : params.registration.createRuntime;
6884
7570
  }
6885
- function createPluginRuntimeRegistrationController(params) {
6886
- const pluginRuntimeScopes = /* @__PURE__ */ new Map();
6887
- let pluginRuntimeSnapshotKey = "";
6888
- let activeExtensionRegistry;
6889
- 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) => {
6890
7580
  const nextSnapshotKey = buildPluginRuntimeSnapshotKey(extensionRegistry);
6891
- if (nextSnapshotKey === pluginRuntimeSnapshotKey) {
7581
+ if (nextSnapshotKey === this.pluginRuntimeSnapshotKey) {
6892
7582
  return;
6893
7583
  }
6894
- pluginRuntimeSnapshotKey = nextSnapshotKey;
6895
- for (const scope of pluginRuntimeScopes.values()) {
7584
+ this.pluginRuntimeSnapshotKey = nextSnapshotKey;
7585
+ for (const scope of this.pluginRuntimeScopes.values()) {
6896
7586
  scope.dispose();
6897
7587
  }
6898
- pluginRuntimeScopes.clear();
7588
+ this.pluginRuntimeScopes.clear();
6899
7589
  for (const registration of extensionRegistry?.ncpAgentRuntimes ?? []) {
6900
7590
  const pluginId = registration.pluginId.trim() || registration.kind;
6901
- const scope = pluginRuntimeScopes.get(pluginId) ?? new DisposableStore();
6902
- pluginRuntimeScopes.set(pluginId, scope);
6903
- 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({
6904
7594
  kind: registration.kind,
6905
7595
  label: registration.label,
6906
7596
  createRuntime: resolveRegisteredRuntimeFactory({
@@ -6910,17 +7600,23 @@ function createPluginRuntimeRegistrationController(params) {
6910
7600
  }));
6911
7601
  }
6912
7602
  };
6913
- const resolveActiveExtensionRegistry = () => activeExtensionRegistry ?? params.getExtensionRegistry?.();
6914
- return {
6915
- refreshPluginRuntimeRegistrations: () => {
6916
- syncPluginRuntimeRegistrations(resolveActiveExtensionRegistry());
6917
- },
6918
- applyExtensionRegistry: (extensionRegistry) => {
6919
- activeExtensionRegistry = extensionRegistry;
6920
- 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();
6921
7614
  }
7615
+ this.pluginRuntimeScopes.clear();
7616
+ this.activeExtensionRegistry = void 0;
7617
+ this.pluginRuntimeSnapshotKey = "";
6922
7618
  };
6923
- }
7619
+ };
6924
7620
  function createUiNcpAgentHandle(params) {
6925
7621
  return {
6926
7622
  basePath: "/api/ncp/agent",
@@ -6942,7 +7638,8 @@ function createUiNcpAgentHandle(params) {
6942
7638
  resolveContentPath: (uri) => params.assetStore.resolveContentPath(uri)
6943
7639
  },
6944
7640
  applyExtensionRegistry: params.applyExtensionRegistry,
6945
- applyMcpConfig: params.applyMcpConfig
7641
+ applyMcpConfig: params.applyMcpConfig,
7642
+ dispose: params.dispose
6946
7643
  };
6947
7644
  }
6948
7645
  async function createUiNcpAgent(params) {
@@ -6950,28 +7647,38 @@ async function createUiNcpAgent(params) {
6950
7647
  onSessionUpdated: params.onSessionUpdated
6951
7648
  });
6952
7649
  const runtimeRegistry = new UiNcpRuntimeRegistry();
6953
- const { toolRegistryAdapter, applyMcpConfig } = await createMcpRuntimeSupport(params.getConfig);
7650
+ const { toolRegistryAdapter, applyMcpConfig, dispose: disposeMcpRuntimeSupport } = await createMcpRuntimeSupport(params.getConfig);
6954
7651
  const assetStore = new LocalAssetStore({
6955
7652
  rootDir: join5(getDataDir7(), "assets")
6956
7653
  });
6957
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
+ );
6958
7666
  const createNativeRuntime = createNativeRuntimeFactory(
6959
7667
  params,
6960
7668
  toolRegistryAdapter,
6961
7669
  assetStore,
6962
- () => backend
7670
+ sessionCreationService,
7671
+ sessionRequestBroker
6963
7672
  );
6964
7673
  runtimeRegistry.register({
6965
7674
  kind: "native",
6966
7675
  label: "Native",
6967
7676
  createRuntime: createNativeRuntime
6968
7677
  });
6969
- const pluginRuntimeRegistrationController = createPluginRuntimeRegistrationController({
7678
+ const pluginRuntimeRegistrationController = new PluginRuntimeRegistrationController(
6970
7679
  runtimeRegistry,
6971
- getConfig: params.getConfig,
6972
- getExtensionRegistry: params.getExtensionRegistry,
6973
- createNativeRuntime
6974
- });
7680
+ params.getExtensionRegistry
7681
+ );
6975
7682
  pluginRuntimeRegistrationController.refreshPluginRuntimeRegistrations();
6976
7683
  backend = new DefaultNcpAgentBackend({
6977
7684
  endpointId: "nextclaw-ui-agent",
@@ -6989,6 +7696,11 @@ async function createUiNcpAgent(params) {
6989
7696
  refreshPluginRuntimeRegistrations: pluginRuntimeRegistrationController.refreshPluginRuntimeRegistrations,
6990
7697
  applyExtensionRegistry: pluginRuntimeRegistrationController.applyExtensionRegistry,
6991
7698
  applyMcpConfig,
7699
+ dispose: async () => {
7700
+ pluginRuntimeRegistrationController.dispose();
7701
+ await backend?.stop();
7702
+ await disposeMcpRuntimeSupport();
7703
+ },
6992
7704
  assetStore
6993
7705
  });
6994
7706
  }
@@ -8177,9 +8889,17 @@ var DEFERRED_NCP_AGENT_UNAVAILABLE = "ncp agent unavailable during startup";
8177
8889
  function createUnavailableError() {
8178
8890
  return new Error(DEFERRED_NCP_AGENT_UNAVAILABLE);
8179
8891
  }
8180
- function createDeferredUiNcpAgent(basePath = DEFAULT_BASE_PATH) {
8181
- let activeAgent = null;
8182
- 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 = {
8183
8903
  manifest: {
8184
8904
  endpointKind: "agent",
8185
8905
  endpointId: "nextclaw-ui-agent-deferred",
@@ -8194,73 +8914,69 @@ function createDeferredUiNcpAgent(basePath = DEFAULT_BASE_PATH) {
8194
8914
  deferred: true
8195
8915
  }
8196
8916
  },
8197
- async start() {
8198
- await activeAgent?.agentClientEndpoint.start();
8917
+ start: async () => {
8918
+ await this.activeAgent?.agentClientEndpoint.start();
8199
8919
  },
8200
- async stop() {
8201
- await activeAgent?.agentClientEndpoint.stop();
8920
+ stop: async () => {
8921
+ await this.activeAgent?.agentClientEndpoint.stop();
8202
8922
  },
8203
- async emit(event) {
8204
- if (!activeAgent) {
8923
+ emit: async (event) => {
8924
+ if (!this.activeAgent) {
8205
8925
  throw createUnavailableError();
8206
8926
  }
8207
- await activeAgent.agentClientEndpoint.emit(event);
8927
+ await this.activeAgent.agentClientEndpoint.emit(event);
8208
8928
  },
8209
- subscribe(listener) {
8210
- if (!activeAgent) {
8929
+ subscribe: (listener) => {
8930
+ if (!this.activeAgent) {
8211
8931
  return () => void 0;
8212
8932
  }
8213
- return activeAgent.agentClientEndpoint.subscribe(listener);
8933
+ return this.activeAgent.agentClientEndpoint.subscribe(listener);
8214
8934
  },
8215
- async send(envelope) {
8216
- if (!activeAgent) {
8935
+ send: async (envelope) => {
8936
+ if (!this.activeAgent) {
8217
8937
  throw createUnavailableError();
8218
8938
  }
8219
- await activeAgent.agentClientEndpoint.send(envelope);
8939
+ await this.activeAgent.agentClientEndpoint.send(envelope);
8220
8940
  },
8221
- async stream(payload) {
8222
- if (!activeAgent) {
8941
+ stream: async (payload) => {
8942
+ if (!this.activeAgent) {
8223
8943
  throw createUnavailableError();
8224
8944
  }
8225
- await activeAgent.agentClientEndpoint.stream(payload);
8945
+ await this.activeAgent.agentClientEndpoint.stream(payload);
8226
8946
  },
8227
- async abort(payload) {
8228
- if (!activeAgent) {
8947
+ abort: async (payload) => {
8948
+ if (!this.activeAgent) {
8229
8949
  throw createUnavailableError();
8230
8950
  }
8231
- await activeAgent.agentClientEndpoint.abort(payload);
8951
+ await this.activeAgent.agentClientEndpoint.abort(payload);
8232
8952
  }
8233
8953
  };
8234
- const agent = {
8235
- basePath,
8236
- 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;
8237
8960
  };
8238
- const clear = () => {
8239
- activeAgent = null;
8240
- agent.basePath = basePath;
8241
- agent.streamProvider = void 0;
8242
- agent.listSessionTypes = void 0;
8243
- 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;
8244
8967
  };
8245
- return {
8246
- agent,
8247
- activate(nextAgent) {
8248
- activeAgent = nextAgent;
8249
- agent.basePath = nextAgent.basePath ?? basePath;
8250
- agent.streamProvider = nextAgent.streamProvider;
8251
- agent.listSessionTypes = nextAgent.listSessionTypes;
8252
- agent.assetApi = nextAgent.assetApi;
8253
- },
8254
- clear,
8255
- async close() {
8256
- const current = activeAgent;
8257
- clear();
8258
- await current?.agentClientEndpoint.stop();
8259
- },
8260
- isReady() {
8261
- return activeAgent !== null;
8262
- }
8968
+ close = async () => {
8969
+ const current = this.activeAgent;
8970
+ this.clear();
8971
+ await current?.agentClientEndpoint.stop();
8972
+ await current?.dispose?.();
8263
8973
  };
8974
+ isReady = () => {
8975
+ return this.activeAgent !== null;
8976
+ };
8977
+ };
8978
+ function createDeferredUiNcpAgent(basePath = DEFAULT_BASE_PATH) {
8979
+ return new DeferredUiNcpAgentControllerOwner(basePath);
8264
8980
  }
8265
8981
 
8266
8982
  // src/cli/commands/service-gateway-startup.ts
@@ -8448,7 +9164,7 @@ var UiSessionService = class {
8448
9164
  onSessionUpdated: options.onSessionUpdated
8449
9165
  });
8450
9166
  }
8451
- async listSessions(options) {
9167
+ listSessions = async (options) => {
8452
9168
  const sessions = await this.sessionStore.listSessions();
8453
9169
  return applyLimit(
8454
9170
  sessions.map(
@@ -8462,15 +9178,15 @@ var UiSessionService = class {
8462
9178
  ),
8463
9179
  options?.limit
8464
9180
  );
8465
- }
8466
- async listSessionMessages(sessionId, options) {
9181
+ };
9182
+ listSessionMessages = async (sessionId, options) => {
8467
9183
  const session = await this.sessionStore.getSession(sessionId);
8468
9184
  if (!session) {
8469
9185
  return [];
8470
9186
  }
8471
9187
  return applyLimit(session.messages.map((message) => structuredClone(message)), options?.limit);
8472
- }
8473
- async getSession(sessionId) {
9188
+ };
9189
+ getSession = async (sessionId) => {
8474
9190
  const session = await this.sessionStore.getSession(sessionId);
8475
9191
  if (!session) {
8476
9192
  return null;
@@ -8482,26 +9198,23 @@ var UiSessionService = class {
8482
9198
  status: "idle",
8483
9199
  metadata: session.metadata
8484
9200
  });
8485
- }
8486
- async updateSession(sessionId, patch) {
9201
+ };
9202
+ updateSession = async (sessionId, patch) => {
8487
9203
  const session = await this.sessionStore.getSession(sessionId);
8488
- if (!session) {
8489
- return null;
8490
- }
8491
- await this.sessionStore.saveSession({
9204
+ await this.sessionStore.replaceSession({
8492
9205
  sessionId,
8493
- messages: session.messages.map((message) => structuredClone(message)),
9206
+ messages: session ? session.messages.map((message) => structuredClone(message)) : [],
8494
9207
  updatedAt: now(),
8495
9208
  metadata: buildUpdatedMetadata({
8496
- existingMetadata: session.metadata,
9209
+ existingMetadata: session?.metadata,
8497
9210
  patch
8498
9211
  })
8499
9212
  });
8500
9213
  return await this.getSession(sessionId);
8501
- }
8502
- async deleteSession(sessionId) {
9214
+ };
9215
+ deleteSession = async (sessionId) => {
8503
9216
  await this.sessionStore.deleteSession(sessionId);
8504
- }
9217
+ };
8505
9218
  };
8506
9219
 
8507
9220
  // src/cli/commands/service-deferred-ncp-session-service.ts
@@ -9006,7 +9719,7 @@ function readStringList(value) {
9006
9719
  }
9007
9720
  return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
9008
9721
  }
9009
- function readOptionalString2(value) {
9722
+ function readOptionalString6(value) {
9010
9723
  if (typeof value !== "string") {
9011
9724
  return void 0;
9012
9725
  }
@@ -9016,10 +9729,10 @@ function readOptionalString2(value) {
9016
9729
  function resolvePluginRuntimeAttachments(ctx) {
9017
9730
  const mediaPaths = readStringList(ctx.MediaPaths);
9018
9731
  const mediaUrls = readStringList(ctx.MediaUrls);
9019
- const fallbackPath = readOptionalString2(ctx.MediaPath);
9020
- const fallbackUrl = readOptionalString2(ctx.MediaUrl);
9732
+ const fallbackPath = readOptionalString6(ctx.MediaPath);
9733
+ const fallbackUrl = readOptionalString6(ctx.MediaUrl);
9021
9734
  const mediaTypes = readStringList(ctx.MediaTypes);
9022
- const fallbackType = readOptionalString2(ctx.MediaType);
9735
+ const fallbackType = readOptionalString6(ctx.MediaType);
9023
9736
  const entryCount = Math.max(
9024
9737
  mediaPaths.length,
9025
9738
  mediaUrls.length,