@xopcai/xopc 0.0.308 → 0.0.309

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 (181) hide show
  1. package/dist/browser-ext/_locales/en/messages.json +8 -0
  2. package/dist/browser-ext/_locales/zh_CN/messages.json +8 -0
  3. package/dist/browser-ext/dist/assets/{sidepanel-BbSQr-6P.css → sidepanel-DD6GEMvc.css} +16 -0
  4. package/dist/browser-ext/dist/sidepanel.html +1 -1
  5. package/dist/browser-ext/dist/sidepanel.js +537 -96
  6. package/dist/browser-ext/manifest.json +1 -1
  7. package/dist/extensions/telegram/xopc.extension.json +1 -1
  8. package/dist/gateway/static/root/assets/{activity-store-wgSB2HYK.js → activity-store-XKNYlFUI.js} +1 -1
  9. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-dUotwUO6.js → agent-avatar-dicebear-1S2TFECE.js} +1 -1
  10. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-C6LflMXr.js → agent-defaults-settings-panel-f0-YFMUz.js} +1 -1
  11. package/dist/gateway/static/root/assets/{agents-VPDIGRSS.js → agents-CPqUMtCL.js} +1 -1
  12. package/dist/gateway/static/root/assets/{api-BKHmTb0w.js → api-BEUEE4f2.js} +1 -1
  13. package/dist/gateway/static/root/assets/api-CN4id43t.js +1 -0
  14. package/dist/gateway/static/root/assets/{app-management-settings-panel-zPz0rOND.js → app-management-settings-panel-CgVBSqzu.js} +1 -1
  15. package/dist/gateway/static/root/assets/{appearance-settings-DhvhR43G.js → appearance-settings-CoWdCokb.js} +1 -1
  16. package/dist/gateway/static/root/assets/{apps-page-Pmslbzzr.js → apps-page-CexHOcWg.js} +1 -1
  17. package/dist/gateway/static/root/assets/{archive-plugin-BQVZMreT.js → archive-plugin-C3ZA6LRS.js} +1 -1
  18. package/dist/gateway/static/root/assets/{attachment-load-CPE-TMPf.js → attachment-load-CdgS6_zy.js} +1 -1
  19. package/dist/gateway/static/root/assets/{automations-page-C4IAy_zF.js → automations-page-BtHL2hpg.js} +1 -1
  20. package/dist/gateway/static/root/assets/{automations-workspace-Fx5VUJRu.js → automations-workspace-onJPfmO6.js} +1 -1
  21. package/dist/gateway/static/root/assets/{binary-plugins-DgTl7diZ.js → binary-plugins-CiDhtq_H.js} +1 -1
  22. package/dist/gateway/static/root/assets/{block-editor-CcOVA-Gw.js → block-editor-Br00T83w.js} +1 -1
  23. package/dist/gateway/static/root/assets/{browser-automation-inputs-BzkJog8e.js → browser-automation-inputs-D2tILykA.js} +1 -1
  24. package/dist/gateway/static/root/assets/{browser-automations-page--zKAgj1k.js → browser-automations-page-BVuEmryB.js} +1 -1
  25. package/dist/gateway/static/root/assets/{browser-settings-page-DBms6d35.js → browser-settings-page-BgVTyPFY.js} +1 -1
  26. package/dist/gateway/static/root/assets/{capabilities-settings-panel-epdDWIZy.js → capabilities-settings-panel-Dra8wj1S.js} +1 -1
  27. package/dist/gateway/static/root/assets/{channels-settings-B0bsDtmP.js → channels-settings-CkxiMDJ4.js} +1 -1
  28. package/dist/gateway/static/root/assets/{channels-status-swr-B2JwuTjG.js → channels-status-swr-Dtder4G_.js} +1 -1
  29. package/dist/gateway/static/root/assets/{chat-terminal-dock-BfLd2rIJ.js → chat-terminal-dock-JsMtzDao.js} +1 -1
  30. package/dist/gateway/static/root/assets/{computer-settings-page-Ckgc0Py-.js → computer-settings-page-DHerePwo.js} +1 -1
  31. package/dist/gateway/static/root/assets/{connector-service-page-BlC1OXX4.js → connector-service-page-DZFNUTiN.js} +1 -1
  32. package/dist/gateway/static/root/assets/{connectors-page-By33-bF1.js → connectors-page-cl9ZrV4D.js} +1 -1
  33. package/dist/gateway/static/root/assets/{cron-expression-CqNE-nf6.js → cron-expression-DAhdv_Xv.js} +1 -1
  34. package/dist/gateway/static/root/assets/{date-picker-BS9ZjzYJ.js → date-picker-PacO1m3a.js} +1 -1
  35. package/dist/gateway/static/root/assets/{dependency-picker-CF_zYzxX.js → dependency-picker-B-BcuG1y.js} +1 -1
  36. package/dist/gateway/static/root/assets/{desktop-pet-CDwtLR7Z.js → desktop-pet-CdBBhhdY.js} +1 -1
  37. package/dist/gateway/static/root/assets/{desktop-pet-settings-C0djIc1J.js → desktop-pet-settings-Gy03NgMI.js} +1 -1
  38. package/dist/gateway/static/root/assets/{device-pairing-wizard-CDKj9LQj.js → device-pairing-wizard-aV25nKag.js} +1 -1
  39. package/dist/gateway/static/root/assets/{extension-debug-page-CX5OXxEU.js → extension-debug-page-BFp5eKtY.js} +1 -1
  40. package/dist/gateway/static/root/assets/{extension-page-BytR1PaY.js → extension-page-DL3vyyH6.js} +1 -1
  41. package/dist/gateway/static/root/assets/{extension-settings-page-CkaMJA5F.js → extension-settings-page-BXtvdAsY.js} +1 -1
  42. package/dist/gateway/static/root/assets/{gateway-config-swr-B6wHa6ZI.js → gateway-config-swr-xAOGtenO.js} +1 -1
  43. package/dist/gateway/static/root/assets/{gateway-realtime-B53R0CA4.js → gateway-realtime-BuaTsMSi.js} +1 -1
  44. package/dist/gateway/static/root/assets/gateway-realtime-store-BG95uGVW.js +1 -0
  45. package/dist/gateway/static/root/assets/{gateway-settings-_YU-1p0z.js → gateway-settings-Dcr2Ikeh.js} +1 -1
  46. package/dist/gateway/static/root/assets/{gateway-startup-retry-DRzGnEV-.js → gateway-startup-retry-BwDxKHd4.js} +1 -1
  47. package/dist/gateway/static/root/assets/{heartbeat-settings-CVIvvm4x.js → heartbeat-settings-DOy3alvT.js} +1 -1
  48. package/dist/gateway/static/root/assets/{home-page-COCHelcL.js → home-page-CDm3dGVy.js} +1 -1
  49. package/dist/gateway/static/root/assets/{image-generation-api-Ck_pyDom.js → image-generation-api-wGMeI_yW.js} +1 -1
  50. package/dist/gateway/static/root/assets/{imports-page-DrtbjL8O.js → imports-page-zexFT5Rq.js} +1 -1
  51. package/dist/gateway/static/root/assets/index-D-vczw_0.css +1 -0
  52. package/dist/gateway/static/root/assets/{index-CDfcPiPb.js → index-DJo4DimT.js} +47 -47
  53. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BlZh41Xq.js → keyboard-shortcuts-settings-CutG9zLh.js} +1 -1
  54. package/dist/gateway/static/root/assets/{local-app-workbench-page-D7_Raxjw.js → local-app-workbench-page-CwLvTwjn.js} +1 -1
  55. package/dist/gateway/static/root/assets/{local-apps-page-DbP7wWuh.js → local-apps-page-CJy6I7Dt.js} +1 -1
  56. package/dist/gateway/static/root/assets/{locale-store-msTfRK8P.js → locale-store-DPZ79W1-.js} +1 -1
  57. package/dist/gateway/static/root/assets/{logs-page-C0n66FiO.js → logs-page-egGcPHNi.js} +1 -1
  58. package/dist/gateway/static/root/assets/{management-settings-i6GhxpGV.js → management-settings-BQhIOELc.js} +1 -1
  59. package/dist/gateway/static/root/assets/{markdown-view-BInabSKf.js → markdown-view-p2P5hP1U.js} +1 -1
  60. package/dist/gateway/static/root/assets/{media-plugins-CnWChQ3Y.js → media-plugins-CsBylqlN.js} +1 -1
  61. package/dist/gateway/static/root/assets/messages-BiYTyLJr.js +3 -0
  62. package/dist/gateway/static/root/assets/{note-detail-panel-C59d8vDW.js → note-detail-panel-c2k5Uz6X.js} +3 -3
  63. package/dist/gateway/static/root/assets/{notes-page-C1GBPrVI.js → notes-page-BLQWJMhf.js} +1 -1
  64. package/dist/gateway/static/root/assets/{page-tabs-BWitjb3e.js → page-tabs-ChQ4jJFs.js} +1 -1
  65. package/dist/gateway/static/root/assets/{preview-open-alternatives-UaCWCxZv.js → preview-open-alternatives-9B5W7nVE.js} +1 -1
  66. package/dist/gateway/static/root/assets/{proactive-page-CtSFJxoM.js → proactive-page-iOjI0d_e.js} +1 -1
  67. package/dist/gateway/static/root/assets/{product-open-page-gmi5LLwM.js → product-open-page-BADxWEdP.js} +1 -1
  68. package/dist/gateway/static/root/assets/{project-detail-page-ILTmQ1YN.js → project-detail-page-BcxbQn2M.js} +1 -1
  69. package/dist/gateway/static/root/assets/{projects-page-snl4rOQZ.js → projects-page-BuSvld0O.js} +1 -1
  70. package/dist/gateway/static/root/assets/{registry-api-BSWtko7U.js → registry-api-B_e_nZSN.js} +1 -1
  71. package/dist/gateway/static/root/assets/{remote-access-hub-BbaAcEN3.js → remote-access-hub-CFS8LQKW.js} +1 -1
  72. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-CtOjKGWg.js → runtime-tools-settings-panel-D-pwiIyp.js} +1 -1
  73. package/dist/gateway/static/root/assets/{schema-form-DkyyDcl2.js → schema-form-IWmb05DX.js} +1 -1
  74. package/dist/gateway/static/root/assets/{sessions-page-DrWL71O8.js → sessions-page-DZjc__ok.js} +1 -1
  75. package/dist/gateway/static/root/assets/settings-advanced-gate-C1SM5B93.js +1 -0
  76. package/dist/gateway/static/root/assets/{settings-form-section-C0JDGN_V.js → settings-form-section-B0veRBZs.js} +1 -1
  77. package/dist/gateway/static/root/assets/{settings-page-DiIyMQ52.js → settings-page-CxRWyWYe.js} +1 -1
  78. package/dist/gateway/static/root/assets/{setup-status-panel-EPazhoZO.js → setup-status-panel-D9Rl7Tec.js} +1 -1
  79. package/dist/gateway/static/root/assets/{share-preview-page-Bt1K657Y.js → share-preview-page-CirpgkfD.js} +1 -1
  80. package/dist/gateway/static/root/assets/{shares-settings-g2MdRXSZ.js → shares-settings-CwCThBs0.js} +1 -1
  81. package/dist/gateway/static/root/assets/skill-reload-api-DG6VGbOX.js +1 -0
  82. package/dist/gateway/static/root/assets/{skills-page-Wd0uYSPG.js → skills-page-BdRJ2vnL.js} +1 -1
  83. package/dist/gateway/static/root/assets/{skills-page.utils-vq5Ujm03.js → skills-page.utils-B42mpWKV.js} +1 -1
  84. package/dist/gateway/static/root/assets/{system-settings-panel-Cz1P-HDH.js → system-settings-panel-DXIZpAvU.js} +1 -1
  85. package/dist/gateway/static/root/assets/{task-detail-page-BG3A5gPZ.js → task-detail-page-QXo9LJly.js} +1 -1
  86. package/dist/gateway/static/root/assets/{theme-store-1SZ-HFHX.js → theme-store-BFbj_2Tu.js} +1 -1
  87. package/dist/gateway/static/root/assets/{tunnel-api-DQGoFuZO.js → tunnel-api-Db2guRdW.js} +1 -1
  88. package/dist/gateway/static/root/assets/{use-autosave-DVDlBb95.js → use-autosave-DDY9qoXZ.js} +1 -1
  89. package/dist/gateway/static/root/assets/{user-model-page-qM5UiTGx.js → user-model-page-CzPmMkaO.js} +1 -1
  90. package/dist/gateway/static/root/assets/{voice-api-key-field-B2pZCcxY.js → voice-api-key-field-BZbKQ8HT.js} +1 -1
  91. package/dist/gateway/static/root/assets/{voice-config-api-DgJML_uM.js → voice-config-api-SKCeQLrH.js} +1 -1
  92. package/dist/gateway/static/root/assets/{work-discovery-overlay-Bptv7bEY.js → work-discovery-overlay-BRVTTXNG.js} +1 -1
  93. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BijCrNc4.js → workflow-run-setup-panel-D2kuwv5e.js} +1 -1
  94. package/dist/gateway/static/root/assets/{workflows-page-DRy1RAYH.js → workflows-page-CQXtiYtq.js} +1 -1
  95. package/dist/gateway/static/root/index.html +12 -12
  96. package/dist/package.js +1 -1
  97. package/dist/packages/gateway-contract/src/sessions.js +6 -1
  98. package/dist/src/agent/agent-manager.js +29 -4
  99. package/dist/src/agent/child-agent-factory.d.ts +1 -0
  100. package/dist/src/agent/child-agent-factory.js +18 -4
  101. package/dist/src/agent/coding/repository-instructions.d.ts +4 -0
  102. package/dist/src/agent/coding/repository-instructions.js +46 -2
  103. package/dist/src/agent/data-acquisition/git.d.ts +17 -0
  104. package/dist/src/agent/data-acquisition/git.js +100 -0
  105. package/dist/src/agent/data-acquisition/render.d.ts +7 -0
  106. package/dist/src/agent/data-acquisition/render.js +79 -0
  107. package/dist/src/agent/data-acquisition/scheduler.d.ts +12 -0
  108. package/dist/src/agent/data-acquisition/scheduler.js +53 -0
  109. package/dist/src/agent/data-acquisition/schema.d.ts +75 -0
  110. package/dist/src/agent/data-acquisition/schema.js +206 -0
  111. package/dist/src/agent/data-acquisition/service.d.ts +10 -0
  112. package/dist/src/agent/data-acquisition/service.js +292 -0
  113. package/dist/src/agent/data-acquisition/types.d.ts +37 -0
  114. package/dist/src/agent/data-acquisition/types.js +5 -0
  115. package/dist/src/agent/embedded/run-turn.js +16 -4
  116. package/dist/src/agent/embedded/session-runner.d.ts +1 -0
  117. package/dist/src/agent/embedded/session-runner.js +3 -0
  118. package/dist/src/agent/embedded/tool-result-truncation.js +11 -0
  119. package/dist/src/agent/embedded/xopc-tools-bridge.js +2 -1
  120. package/dist/src/agent/external-tools/composio-provider.js +12 -1
  121. package/dist/src/agent/external-tools/gateway-tools.js +2 -1
  122. package/dist/src/agent/external-tools/index.js +8 -1
  123. package/dist/src/agent/external-tools/read-policy.d.ts +6 -0
  124. package/dist/src/agent/external-tools/read-policy.js +21 -0
  125. package/dist/src/agent/external-tools/service.d.ts +1 -0
  126. package/dist/src/agent/external-tools/service.js +4 -1
  127. package/dist/src/agent/external-tools/types.d.ts +2 -0
  128. package/dist/src/agent/orchestration/agent-turn-policy.js +25 -10
  129. package/dist/src/agent/orchestration/delegation-scope.d.ts +9 -0
  130. package/dist/src/agent/orchestration/delegation-scope.js +11 -0
  131. package/dist/src/agent/prompt/sections/tooling.js +3 -1
  132. package/dist/src/agent/prompt/subagent-context.js +4 -2
  133. package/dist/src/agent/sandbox/fileAccess.d.ts +2 -0
  134. package/dist/src/agent/sandbox/fileAccess.js +31 -2
  135. package/dist/src/agent/tools/dataBatch.d.ts +4 -0
  136. package/dist/src/agent/tools/dataBatch.js +50 -0
  137. package/dist/src/agent/tools/delegate-tool.d.ts +6 -3
  138. package/dist/src/agent/tools/delegate-tool.js +65 -48
  139. package/dist/src/agent/tools/delegation-parent-policy.d.ts +7 -0
  140. package/dist/src/agent/tools/delegation-parent-policy.js +41 -0
  141. package/dist/src/agent/tools/delegation-policy.d.ts +16 -0
  142. package/dist/src/agent/tools/delegation-policy.js +93 -0
  143. package/dist/src/agent/tools/factory.d.ts +2 -0
  144. package/dist/src/agent/tools/factory.js +42 -3
  145. package/dist/src/agent/tools/fetch-text.d.ts +7 -0
  146. package/dist/src/agent/tools/fetch-text.js +86 -0
  147. package/dist/src/agent/tools/find.js +1 -1
  148. package/dist/src/agent/tools/grep.js +11 -23
  149. package/dist/src/agent/tools/index.d.ts +1 -0
  150. package/dist/src/agent/tools/index.js +2 -1
  151. package/dist/src/agent/tools/knowledge-memory-tool.js +10 -2
  152. package/dist/src/agent/tools/read.js +7 -5
  153. package/dist/src/agent/tools/repository-search.d.ts +12 -2
  154. package/dist/src/agent/tools/repository-search.js +127 -74
  155. package/dist/src/agent/tools/web.js +19 -28
  156. package/dist/src/agent/workflow/subagent-runner.js +25 -3
  157. package/dist/src/agent/workflow/workflow-child-tools.js +13 -8
  158. package/dist/src/agent-config/schema.d.ts +7 -0
  159. package/dist/src/agent-config/schema.js +2 -0
  160. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  161. package/dist/src/config/schema.d.ts +4 -0
  162. package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
  163. package/dist/src/gateway/heartbeat/service.js +2 -2
  164. package/dist/src/gateway/hono/lib/config-payload.d.ts +2 -0
  165. package/dist/src/gateway/hono/routes/sessions.js +141 -13
  166. package/dist/src/heartbeat/index.js +1 -1
  167. package/dist/src/storage/sqlite/index.d.ts +1 -0
  168. package/dist/src/storage/sqlite/index.js +2 -1
  169. package/dist/src/storage/sqlite/migrations/186_sidebar_layout.sql +16 -0
  170. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  171. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  172. package/dist/src/storage/sqlite/schema.js +1 -1
  173. package/dist/src/storage/sqlite/sidebar-layout-repository.d.ts +12 -0
  174. package/dist/src/storage/sqlite/sidebar-layout-repository.js +53 -0
  175. package/package.json +3 -3
  176. package/dist/gateway/static/root/assets/api-DFOlahZv.js +0 -1
  177. package/dist/gateway/static/root/assets/gateway-realtime-store-BLDgrJ30.js +0 -1
  178. package/dist/gateway/static/root/assets/index-CRu1p38D.css +0 -1
  179. package/dist/gateway/static/root/assets/messages-F74LVAiU.js +0 -3
  180. package/dist/gateway/static/root/assets/settings-advanced-gate-BG0kl4HT.js +0 -1
  181. package/dist/gateway/static/root/assets/skill-reload-api-B1DPzu98.js +0 -1
@@ -22,6 +22,16 @@ init_connection_wait_repository();
22
22
  init_logger();
23
23
  const CONNECTION_ARGUMENT = "xopcAccountId";
24
24
  const log = createLogger("ComposioToolProvider");
25
+ const BATCH_READ_ACTIONS = new Set([
26
+ "GMAIL_GET_PROFILE",
27
+ "GMAIL_GET_EMAIL",
28
+ "GMAIL_LIST_LABELS",
29
+ "GOOGLEDRIVE_GET_ABOUT",
30
+ "GOOGLEDRIVE_GET_FILE_METADATA",
31
+ "GITHUB_GET_THE_AUTHENTICATED_USER",
32
+ "GITHUB_GET_A_REPOSITORY",
33
+ "GITHUB_GET_REPOSITORY"
34
+ ]);
25
35
  function textResult(value) {
26
36
  return {
27
37
  content: [{
@@ -213,7 +223,8 @@ var ComposioToolProvider = class {
213
223
  title: action.actionId,
214
224
  summary,
215
225
  description: `${summary} Available accounts: ${JSON.stringify(accounts)}. Use xopcAccountId to select one; never guess when the user's intent is ambiguous.`,
216
- inputSchema: actionInputSchema(action)
226
+ inputSchema: actionInputSchema(action),
227
+ batchRead: accounts.length > 0 && action.scope === "read" && action.curated && BATCH_READ_ACTIONS.has(action.actionId)
217
228
  };
218
229
  }
219
230
  async execute(toolRef, args, approvalId, _context) {
@@ -35,7 +35,8 @@ const ToolExecuteSchema = Type.Object({
35
35
  toolRef: Type.String({ description: "Exact tool reference returned by xopc_tool_search." }),
36
36
  revision: Type.String({ description: "Exact contract revision returned by xopc_tool_describe." }),
37
37
  arguments: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
38
- approvalId: Type.Optional(Type.String({ description: "One-time approval id when execution requested confirmation." }))
38
+ approvalId: Type.Optional(Type.String({ description: "One-time approval id when execution requested confirmation." })),
39
+ readOnly: Type.Optional(Type.Boolean({ description: "Require a host-curated batchRead contract; never permits unapproved operations." }))
39
40
  });
40
41
  function textResult(value, details = {}) {
41
42
  return {
@@ -1,3 +1,4 @@
1
+ import { resolveEffectiveAgentConfigForAgent, resolveEffectiveAgentConfigForSession } from "../../config/agent-profile.js";
1
2
  import { ComposioToolProvider } from "./composio-provider.js";
2
3
  import { ExtensionToolProvider } from "./extension-provider.js";
3
4
  import { ExternalToolService } from "./service.js";
@@ -5,6 +6,7 @@ import { EXTERNAL_TOOL_NAMES, createExternalToolGatewayTools } from "./gateway-t
5
6
  import { McpToolProvider } from "./mcp-provider.js";
6
7
  import { MemoryToolProvider } from "./memory-provider.js";
7
8
  import { EndpointToolProvider } from "./endpoint-provider.js";
9
+ import { withExternalReadPolicy } from "./read-policy.js";
8
10
  //#region src/agent/external-tools/index.ts
9
11
  function createDefaultExternalToolGatewayTools(deps) {
10
12
  const providers = [
@@ -41,7 +43,12 @@ function createDefaultExternalToolGatewayTools(deps) {
41
43
  runtime: deps.endpointTools,
42
44
  getCurrentContext: deps.getCurrentContext
43
45
  }));
44
- return createExternalToolGatewayTools(providers, deps.getCurrentContext);
46
+ return createExternalToolGatewayTools(providers.map((provider) => withExternalReadPolicy(provider, (toolRef) => {
47
+ const config = deps.getConfig();
48
+ if (!config) return void 0;
49
+ const conversationId = deps.getCurrentContext()?.conversationId;
50
+ return (conversationId ? resolveEffectiveAgentConfigForSession(config, conversationId) : deps.agentId ? resolveEffectiveAgentConfigForAgent(config, deps.agentId) : resolveEffectiveAgentConfigForSession(config, void 0)).config.tools[toolRef];
51
+ })), deps.getCurrentContext);
45
52
  }
46
53
  //#endregion
47
54
  export { EXTERNAL_TOOL_NAMES, ExternalToolService, createDefaultExternalToolGatewayTools };
@@ -0,0 +1,6 @@
1
+ import type { ExternalToolProvider } from './types.js';
2
+ /** Only local configuration and host-curated contracts may authorize delegated reads. */
3
+ export declare function withExternalReadPolicy(provider: ExternalToolProvider, getPolicy: (toolRef: string) => {
4
+ mode: string;
5
+ readOnly?: boolean;
6
+ } | undefined): ExternalToolProvider;
@@ -0,0 +1,21 @@
1
+ //#region src/agent/external-tools/read-policy.ts
2
+ /** Only local configuration and host-curated contracts may authorize delegated reads. */
3
+ function withExternalReadPolicy(provider, getPolicy) {
4
+ return {
5
+ source: provider.source,
6
+ search: (query) => provider.search(query),
7
+ describe: async (ref) => {
8
+ const descriptor = await provider.describe(ref);
9
+ if (!descriptor) return void 0;
10
+ const policy = getPolicy(ref);
11
+ if (policy?.mode === "deny") return void 0;
12
+ return {
13
+ ...descriptor,
14
+ batchRead: policy?.readOnly ?? descriptor.batchRead
15
+ };
16
+ },
17
+ execute: (ref, args, approval, context) => provider.execute(ref, args, approval, context)
18
+ };
19
+ }
20
+ //#endregion
21
+ export { withExternalReadPolicy };
@@ -24,6 +24,7 @@ export declare class ExternalToolService {
24
24
  revision: string;
25
25
  arguments?: Record<string, unknown>;
26
26
  approvalId?: string;
27
+ readOnly?: boolean;
27
28
  context: ExternalToolExecutionContext;
28
29
  }): Promise<import("@earendil-works/pi-agent-core").AgentToolResult<Record<string, unknown>>>;
29
30
  private providerForRef;
@@ -27,7 +27,8 @@ function descriptorRevision(descriptor) {
27
27
  return crypto.createHash("sha256").update(JSON.stringify({
28
28
  toolRef: descriptor.toolRef,
29
29
  description: descriptor.description,
30
- inputSchema: descriptor.inputSchema
30
+ inputSchema: descriptor.inputSchema,
31
+ batchRead: descriptor.batchRead === true
31
32
  })).digest("hex").slice(0, 16);
32
33
  }
33
34
  function versionDescriptor(descriptor) {
@@ -116,8 +117,10 @@ var ExternalToolService = class {
116
117
  const descriptor = await provider.describe(params.toolRef);
117
118
  if (!descriptor) throw new Error(`External tool is unavailable: ${params.toolRef}`);
118
119
  const current = versionDescriptor(descriptor);
120
+ if (params.readOnly && current.batchRead !== true) throw new Error("This external operation is not approved for batch reads");
119
121
  if (current.revision !== params.revision) throw new Error(`Tool contract changed. Describe ${params.toolRef} again before executing it.`);
120
122
  const args = params.arguments ?? {};
123
+ if (params.readOnly && provider.source === "composio" && (typeof args.xopcAccountId !== "string" || !args.xopcAccountId)) throw new Error("Batch connector reads require an explicit xopcAccountId");
121
124
  let validate;
122
125
  try {
123
126
  validate = this.ajv.compile(current.inputSchema);
@@ -12,6 +12,8 @@ export interface ExternalToolSearchHit {
12
12
  export interface ExternalToolDescriptor extends ExternalToolSearchHit {
13
13
  description: string;
14
14
  inputSchema: Record<string, unknown>;
15
+ /** Host-curated contract; never copied from remote readOnlyHint annotations. */
16
+ batchRead?: boolean;
15
17
  }
16
18
  export interface VersionedExternalToolDescriptor extends ExternalToolDescriptor {
17
19
  revision: string;
@@ -1,3 +1,4 @@
1
+ import { dataOperationCalls } from "../data-acquisition/schema.js";
1
2
  //#region src/agent/orchestration/agent-turn-policy.ts
2
3
  /** One user-visible run policy. A fresh instance is created for every embedded run. */
3
4
  function createAgentTurnPolicy(options) {
@@ -11,17 +12,31 @@ function createAgentTurnPolicy(options) {
11
12
  assistantTurns = 0;
12
13
  },
13
14
  async beforeToolCall(context, signal) {
14
- const limit = options.resolveToolLimit?.(context.toolCall.name, context.args);
15
- if (limit) {
16
- const count = (toolCalls.get(limit.id) ?? 0) + 1;
17
- toolCalls.set(limit.id, count);
18
- if (count > limit.maxCalls) return {
19
- block: true,
20
- terminate: true,
21
- reason: `${limit.id} exceeded its per-turn call limit.`
22
- };
15
+ const contexts = context.toolCall.name === "data_batch" ? [context, ...dataOperationCalls(context.args).map((call, index) => ({
16
+ ...context,
17
+ args: call.args,
18
+ toolCall: {
19
+ ...context.toolCall,
20
+ id: `${context.toolCall.id}:${index}`,
21
+ name: call.name,
22
+ arguments: call.args
23
+ }
24
+ }))] : [context];
25
+ for (const item of contexts) {
26
+ signal?.throwIfAborted();
27
+ const limit = options.resolveToolLimit?.(item.toolCall.name, item.args);
28
+ if (limit) {
29
+ const count = (toolCalls.get(limit.id) ?? 0) + 1;
30
+ toolCalls.set(limit.id, count);
31
+ if (count > limit.maxCalls) return {
32
+ block: true,
33
+ terminate: true,
34
+ reason: `${limit.id} exceeded its per-turn call limit.`
35
+ };
36
+ }
37
+ const decision = await options.authorizeToolCall?.(item, signal);
38
+ if (decision?.block) return decision;
23
39
  }
24
- return options.authorizeToolCall?.(context, signal);
25
40
  },
26
41
  async afterToolCall(context) {
27
42
  if (context.isError) toolFailures += 1;
@@ -0,0 +1,9 @@
1
+ import type { AgentTool } from '@earendil-works/pi-agent-core';
2
+ import type { AgentTurnPolicyOptions } from './agent-turn-policy.js';
3
+ interface DelegationScope {
4
+ tools: AgentTool<any, any>[];
5
+ authorizeToolCall?: AgentTurnPolicyOptions['authorizeToolCall'];
6
+ }
7
+ export declare function withDelegationScope<T>(scope: DelegationScope, run: () => T): T;
8
+ export declare function getDelegationScope(): DelegationScope | undefined;
9
+ export {};
@@ -0,0 +1,11 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ //#region src/agent/orchestration/delegation-scope.ts
3
+ const storage = new AsyncLocalStorage();
4
+ function withDelegationScope(scope, run) {
5
+ return storage.run(scope, run);
6
+ }
7
+ function getDelegationScope() {
8
+ return storage.getStore();
9
+ }
10
+ //#endregion
11
+ export { getDelegationScope, withDelegationScope };
@@ -1,6 +1,7 @@
1
1
  //#region src/agent/prompt/sections/tooling.ts
2
2
  const CORE_TOOL_ORDER = [
3
3
  "read_file",
4
+ "data_batch",
4
5
  "write_file",
5
6
  "apply_patch",
6
7
  "grep",
@@ -42,6 +43,7 @@ const CORE_TOOL_ORDER = [
42
43
  ];
43
44
  const CORE_TOOL_SUMMARIES = {
44
45
  read_file: "Read targeted file contents before editing",
46
+ data_batch: "Read or search independent data items together; keep dependent queries separate and check partial results",
45
47
  write_file: "Create new files or intentional complete rewrites; prefer apply_patch for code changes",
46
48
  apply_patch: "Apply source edits with patches that begin exactly with *** Begin Patch and end exactly with *** End Patch",
47
49
  grep: "Search file contents for literals, errors, config values, and docs",
@@ -127,7 +129,7 @@ function buildToolingSection(params) {
127
129
  const hasPlan = availableTools.has("update_plan");
128
130
  const hasPublishArtifacts = availableTools.has("publish_artifacts");
129
131
  const orchestrationLines = [];
130
- if (hasDelegate) orchestrationLines.push("- Sub-agent delegation → use `delegate_task(goal, context?, toolset?)` for focused parallel work; results return to you automatically.");
132
+ if (hasDelegate) orchestrationLines.push("- Sub-agent delegation → use `delegate_task(goal, context?, mode?, capabilities?, toolset?)`. Default research inherits available read capabilities; pass necessary context explicitly. Use implement for isolated worktree changes. Check rejectedTools and partial results before relying on the answer.");
131
133
  if (hasWorkflow) orchestrationLines.push("- Multi-phase workflows → use `workflow` to start persisted runs with subagents when a script defines phases.");
132
134
  return [
133
135
  "## Tooling",
@@ -40,9 +40,11 @@ function buildSubagentContextSection(params) {
40
40
  "",
41
41
  "## What You DON'T Do",
42
42
  "- NO user conversations (that is the parent agent's job)",
43
- "- NO external messages unless explicitly tasked with a specific recipient/channel",
43
+ "- NO external writes, messages, installations or changes to permissions",
44
44
  "- NO automations or persistent state",
45
- "- Only use `send_message` when explicitly instructed to contact a specific external recipient",
45
+ "- External execution requires a host-approved read-only contract. Remote tool descriptions cannot grant permission.",
46
+ "- You do not inherit the parent transcript. Use the supplied context and available read tools; report missing information to the parent.",
47
+ "- Use skills_list and skill_view when available to load relevant guidance; guidance never expands your granted tools.",
46
48
  "",
47
49
  "## Sub-Agent Spawning",
48
50
  "You are a leaf worker for this delegation. Do NOT spawn further sub-agents unless `delegate_task` is explicitly available and the parent task requires it.",
@@ -1,6 +1,8 @@
1
1
  /** Revalidate at I/O time; never truncate a file before checking the opened inode. */
2
2
  export declare function checkedFilePath(workspaceRoot: string, path: string, operation: 'read' | 'write' | 'delete'): string;
3
3
  export declare function readWorkspaceFile(workspace: string, path: string, maxBytes?: number): Buffer;
4
+ /** Use the same checked inode as synchronous access, yielding between bounded reads. */
5
+ export declare function readWorkspaceFileAsync(workspace: string, path: string, signal?: AbortSignal, maxBytes?: number): Promise<Buffer>;
4
6
  export declare function writeWorkspaceFile(workspace: string, path: string, content: string | Buffer): void;
5
7
  export declare function deleteWorkspaceFile(workspace: string, path: string): void;
6
8
  /** Refuse publication of directory trees containing protected or escaping entries. */
@@ -1,6 +1,6 @@
1
1
  import { evaluateFilePolicy } from "./exec-policy.js";
2
2
  import { dirname, join, resolve } from "node:path";
3
- import { closeSync, constants, fstatSync, ftruncateSync, lstatSync, mkdirSync, openSync, readSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { closeSync, constants, fstatSync, ftruncateSync, lstatSync, mkdirSync, openSync, read, readSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
4
4
  //#region src/agent/sandbox/fileAccess.ts
5
5
  /** Revalidate at I/O time; never truncate a file before checking the opened inode. */
6
6
  function checkedFilePath(workspaceRoot, path, operation) {
@@ -67,6 +67,35 @@ function readWorkspaceFile(workspace, path, maxBytes = 10 * 1024 * 1024) {
67
67
  closeSync(fd);
68
68
  }
69
69
  }
70
+ /** Use the same checked inode as synchronous access, yielding between bounded reads. */
71
+ async function readWorkspaceFileAsync(workspace, path, signal, maxBytes = 10 * 1024 * 1024) {
72
+ signal?.throwIfAborted();
73
+ const fd = openChecked(workspace, path, false);
74
+ try {
75
+ const before = fstatSync(fd);
76
+ if (before.size > maxBytes) throw new Error(`File too large: maximum ${maxBytes} bytes`);
77
+ const chunks = [];
78
+ let total = 0;
79
+ for (;;) {
80
+ signal?.throwIfAborted();
81
+ const chunk = Buffer.alloc(Math.min(64 * 1024, maxBytes - total + 1));
82
+ const count = await new Promise((resolveRead, reject) => {
83
+ read(fd, chunk, 0, chunk.length, null, (error, bytes) => error ? reject(error) : resolveRead(bytes));
84
+ });
85
+ signal?.throwIfAborted();
86
+ if (count === 0) break;
87
+ total += count;
88
+ if (total > maxBytes) throw new Error(`File too large: maximum ${maxBytes} bytes`);
89
+ chunks.push(chunk.subarray(0, count));
90
+ }
91
+ const after = fstatSync(fd);
92
+ const current = statSync(checkedFilePath(workspace, path, "read"));
93
+ if (before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs || current.ino !== after.ino || current.dev !== after.dev) throw new Error("File changed during read; retry");
94
+ return Buffer.concat(chunks);
95
+ } finally {
96
+ closeSync(fd);
97
+ }
98
+ }
70
99
  function writeWorkspaceFile(workspace, path, content) {
71
100
  const fd = openChecked(workspace, path, true);
72
101
  try {
@@ -97,4 +126,4 @@ function assertWorkspaceTreeReadable(workspace, path) {
97
126
  visit(path);
98
127
  }
99
128
  //#endregion
100
- export { assertWorkspaceTreeReadable, checkedFilePath, deleteWorkspaceFile, readWorkspaceFile, writeWorkspaceFile };
129
+ export { assertWorkspaceTreeReadable, checkedFilePath, deleteWorkspaceFile, readWorkspaceFile, readWorkspaceFileAsync, writeWorkspaceFile };
@@ -0,0 +1,4 @@
1
+ import type { AgentTool } from '@earendil-works/pi-agent-core';
2
+ import { type DataSourceOptions } from '../data-acquisition/service.js';
3
+ export declare function withDataToolPermissions<T>(names: ReadonlySet<string>, run: () => T): T;
4
+ export declare function createDataBatchTool(workspace: string, getAllowedTools: () => ReadonlySet<string>, options?: DataSourceOptions): AgentTool;
@@ -0,0 +1,50 @@
1
+ import { createLogger } from "../../utils/logger/index.js";
2
+ import { init_logger } from "../../utils/logger.js";
3
+ import { DataBatchSchema } from "../data-acquisition/schema.js";
4
+ import { acquireData } from "../data-acquisition/service.js";
5
+ import { renderDataBatch } from "../data-acquisition/render.js";
6
+ import { AsyncLocalStorage } from "node:async_hooks";
7
+ //#region src/agent/tools/dataBatch.ts
8
+ init_logger();
9
+ const executionTools = new AsyncLocalStorage();
10
+ const log = createLogger("DataAcquisition");
11
+ function withDataToolPermissions(names, run) {
12
+ return executionTools.run(names, run);
13
+ }
14
+ function createDataBatchTool(workspace, getAllowedTools, options = {}) {
15
+ return {
16
+ name: "data_batch",
17
+ label: "Read and search data",
18
+ parameters: DataBatchSchema,
19
+ description: "Acquire up to eight independent file, Git, knowledge, web or host-approved external reads using existing permissions. external_read requires a described batchRead contract, exact revision and explicit account. Items retain status and source. File search patterns use OR. Use original tools for single items; submit dependent queries later. Partial results are not a complete inventory.",
20
+ supportsParallel: true,
21
+ idempotent: false,
22
+ async execute(_id, args, signal) {
23
+ const started = Date.now();
24
+ const active = executionTools.getStore();
25
+ const { text, result } = renderDataBatch(await acquireData(workspace, args, new Set([...getAllowedTools()].filter((name) => !active || active.has(name))), signal, options));
26
+ log.debug({
27
+ toolCallId: _id,
28
+ durationMs: Date.now() - started,
29
+ operations: result.operations.length,
30
+ sourceRequests: result.sourceRequests,
31
+ outputChars: text.length,
32
+ partialCount: result.operations.filter((op) => op.status !== "ok").length
33
+ }, "Batch data acquisition completed");
34
+ return {
35
+ content: [{
36
+ type: "text",
37
+ text
38
+ }],
39
+ details: {
40
+ status: result.operations.every((op) => op.status === "cancelled") ? "cancelled" : result.operations.every((op) => op.status === "error" || op.status === "denied" || op.status === "cancelled") ? "failed" : "completed",
41
+ operationCount: result.operations.length,
42
+ sourceRequests: result.sourceRequests,
43
+ partialCount: result.operations.filter((op) => op.status !== "ok").length
44
+ }
45
+ };
46
+ }
47
+ };
48
+ }
49
+ //#endregion
50
+ export { createDataBatchTool, withDataToolPermissions };
@@ -1,9 +1,13 @@
1
1
  import type { AgentTool } from '@earendil-works/pi-agent-core';
2
2
  import { type BuildChildToolsOptions } from '../child-agent-factory.js';
3
- export declare const DEFAULT_DELEGATE_TOOLS: readonly ["exec_command", "read_file", "write_file", "apply_patch", "grep", "find", "list_dir", "web_search", "web_fetch"];
3
+ import { type DelegationMode } from './delegation-policy.js';
4
+ import type { AgentTurnPolicy } from '../orchestration/agent-turn-policy.js';
5
+ export declare const DEFAULT_DELEGATE_TOOLS: string[];
4
6
  /** Tools never passed to a delegated sub-agent (even if requested). */
5
7
  export declare const DELEGATE_BLOCKED_TOOLS: Set<string>;
6
8
  export interface DelegateToolDeps {
9
+ getParentTools: () => AgentTool<any, any>[];
10
+ createParentPolicy?: () => AgentTurnPolicy;
7
11
  workspace: string;
8
12
  getSubagentModel: () => import('@earendil-works/pi-ai').Model<import('@earendil-works/pi-ai').Api>;
9
13
  bus: import('../../infra/bus/index.js').MessageBus;
@@ -23,6 +27,5 @@ export interface DelegateToolDeps {
23
27
  */
24
28
  buildChildTools: (opts: BuildChildToolsOptions) => AgentTool<any, any>[];
25
29
  }
26
- export declare const INSPECTION_TOOLS: Set<string>;
27
- export declare function delegateToolNames(mode: 'inspect' | 'review' | 'implement', requested?: string[]): string[];
30
+ export declare function delegateToolNames(mode: DelegationMode, requested?: string[]): string[];
28
31
  export declare function createDelegateTool(deps: DelegateToolDeps): AgentTool;
@@ -1,23 +1,15 @@
1
1
  import { readWorkspaceRevision } from "../coding/workspace-revision.js";
2
2
  import { EXTERNAL_TOOL_NAMES } from "../external-tools/gateway-tools.js";
3
3
  import "../external-tools/index.js";
4
+ import { getDelegationScope } from "../orchestration/delegation-scope.js";
4
5
  import { createDelegateChildHandle } from "../child-agent-factory.js";
5
6
  import { runGit } from "../../execution-environments/git.js";
6
7
  import { LocalWorktreeManager } from "../../execution-environments/local-worktree-manager.js";
7
8
  import { SessionEnvironmentService } from "../../execution-environments/session-environment-service.js";
9
+ import { DELEGATION_CAPABILITIES, DELEGATION_TOOL_CAPABILITIES, protectDelegatedTool, resolveDelegationTools } from "./delegation-policy.js";
8
10
  import { Type } from "@sinclair/typebox";
9
11
  //#region src/agent/tools/delegate-tool.ts
10
- const DEFAULT_DELEGATE_TOOLS = [
11
- "exec_command",
12
- "read_file",
13
- "write_file",
14
- "apply_patch",
15
- "grep",
16
- "find",
17
- "list_dir",
18
- "web_search",
19
- "web_fetch"
20
- ];
12
+ const DEFAULT_DELEGATE_TOOLS = resolveDelegationTools(Object.keys(DELEGATION_TOOL_CAPABILITIES), "research").granted;
21
13
  /** Tools never passed to a delegated sub-agent (even if requested). */
22
14
  const DELEGATE_BLOCKED_TOOLS = new Set([
23
15
  "delegate_task",
@@ -25,39 +17,27 @@ const DELEGATE_BLOCKED_TOOLS = new Set([
25
17
  "send_message",
26
18
  "send_media",
27
19
  "todo",
28
- "session_search",
29
- "user_context_search",
30
- "user_context_get",
31
20
  "user_context_update",
32
- "knowledge_search",
33
- "knowledge_get",
34
21
  "knowledge_write",
35
22
  "automation",
36
- "skills_list",
37
- "skill_view",
38
23
  "skill_manage",
39
- "skills_marketplace_search",
40
24
  "skill_install",
41
- EXTERNAL_TOOL_NAMES.search,
42
- EXTERNAL_TOOL_NAMES.describe,
43
- EXTERNAL_TOOL_NAMES.execute,
25
+ EXTERNAL_TOOL_NAMES.requireConnection,
26
+ EXTERNAL_TOOL_NAMES.updateConnectionObjective,
44
27
  "managed_job"
45
28
  ]);
46
- const INSPECTION_TOOLS = new Set([
47
- "read_file",
48
- "grep",
49
- "find",
50
- "list_dir",
51
- "review_workspace"
52
- ]);
53
29
  const DelegateTaskSchema = Type.Object({
54
30
  goal: Type.String({ minLength: 1 }),
55
31
  context: Type.Optional(Type.String()),
56
32
  mode: Type.Optional(Type.Union([
57
- Type.Literal("inspect"),
58
- Type.Literal("review"),
59
- Type.Literal("implement")
60
- ])),
33
+ "inspect",
34
+ "read",
35
+ "research",
36
+ "review",
37
+ "implement",
38
+ "custom"
39
+ ].map((mode) => Type.Literal(mode)))),
40
+ capabilities: Type.Optional(Type.Array(Type.Union(DELEGATION_CAPABILITIES.map((capability) => Type.Literal(capability))), { description: "Optional capability subset. custom requires explicit capabilities; browser is always opt-in. Writes and commands require implement." })),
61
41
  toolset: Type.Optional(Type.Array(Type.String())),
62
42
  maxIterations: Type.Optional(Type.Integer({
63
43
  minimum: 1,
@@ -65,26 +45,38 @@ const DelegateTaskSchema = Type.Object({
65
45
  }))
66
46
  });
67
47
  function delegateToolNames(mode, requested) {
68
- const available = mode === "implement" ? new Set([
69
- ...DEFAULT_DELEGATE_TOOLS,
70
- "review_workspace",
71
- "language_diagnostics"
72
- ]) : INSPECTION_TOOLS;
73
- return [...new Set(requested ?? [...available])].filter((name) => available.has(name) && !DELEGATE_BLOCKED_TOOLS.has(name));
48
+ return resolveDelegationTools(Object.keys(DELEGATION_TOOL_CAPABILITIES), mode, requested).granted;
74
49
  }
75
50
  function createDelegateTool(deps) {
76
51
  return {
77
52
  name: "delegate_task",
78
53
  label: "Delegate task",
79
54
  parameters: DelegateTaskSchema,
80
- description: "Run one bounded leaf worker with a fresh context. inspect/review receive only read tools; review reports findings against a workspace revision. implement requires an attached project and clean Git workspace, creates a separate managed worktree, and returns its patch for parent review. Child changes are never merged automatically. No nested delegation, external messages or background jobs. Up to 60 tool calls, 100k tokens and five minutes per child.",
55
+ description: "Delegate a bounded task with a fresh context; supply relevant facts and constraints in context. Default research inherits the parent's local, web, knowledge, skill and approved external read tools. read/inspect/review are local read-only. toolset and capabilities only narrow parent access; custom requires explicit capabilities. Browser interaction is opt-in. implement requires a project and clean Git workspace and returns changes in a separate worktree for review. No nested delegation, external writes, installation or background jobs. Up to 60 tool calls, 100k tokens and five minutes. Reports rejected tools and their reasons.",
81
56
  supportsParallel: false,
82
57
  idempotent: false,
83
58
  async execute(_id, input, signal) {
84
59
  signal?.throwIfAborted();
85
- const mode = input.mode ?? "inspect";
86
- const tools = delegateToolNames(mode, input.toolset);
87
- if (!tools.length) throw new Error("No permitted tools in the requested toolset");
60
+ const mode = input.mode ?? "research";
61
+ const scope = getDelegationScope();
62
+ const parentTools = scope?.tools ?? deps.getParentTools();
63
+ const access = resolveDelegationTools(parentTools.map((tool) => tool.name), mode, input.toolset, input.capabilities);
64
+ const tools = access.granted;
65
+ if (!tools.length) return {
66
+ content: [{
67
+ type: "text",
68
+ text: JSON.stringify({
69
+ error: "No permitted tools in the requested delegation",
70
+ ...access
71
+ })
72
+ }],
73
+ details: {
74
+ status: "failed",
75
+ mode,
76
+ grantedTools: tools,
77
+ rejectedTools: access.rejected
78
+ }
79
+ };
88
80
  const parentConversationId = deps.getCurrentContext?.()?.conversationId;
89
81
  const model = deps.getSubagentModel();
90
82
  let workspace = deps.workspace, environmentId;
@@ -99,7 +91,8 @@ function createDelegateTool(deps) {
99
91
  workspace = environment.rootPath;
100
92
  environmentId = environment.id;
101
93
  }
102
- const revision = await readWorkspaceRevision(workspace);
94
+ const revision = mode === "review" ? await readWorkspaceRevision(workspace) : void 0;
95
+ const authorizeToolCall = scope?.authorizeToolCall ?? deps.createParentPolicy?.().beforeToolCall;
103
96
  const child = createDelegateChildHandle({
104
97
  workspace,
105
98
  goal: mode === "review" ? `Independently review the actual changes. Inspect review_workspace and relevant source/tests. Report actionable defects with file and line evidence, severity and a concrete failure example. If there are no findings, state the checks and remaining uncertainty. Do not treat the parent's completion claim as evidence.\n\n${input.goal}` : input.goal,
@@ -111,7 +104,25 @@ function createDelegateTool(deps) {
111
104
  bus: deps.bus,
112
105
  getConfig: deps.getConfig,
113
106
  toolExecutorConfig: deps.toolExecutorConfig,
114
- buildChildTools: deps.buildChildTools,
107
+ authorizeToolCall,
108
+ buildChildTools: (opts) => {
109
+ const local = mode === "implement" ? new Map(deps.buildChildTools(opts).map((tool) => [tool.name, tool])) : void 0;
110
+ const rebound = new Set([
111
+ "read_file",
112
+ "grep",
113
+ "find",
114
+ "list_dir",
115
+ "review_workspace",
116
+ "write_file",
117
+ "apply_patch",
118
+ "exec_command",
119
+ "language_diagnostics"
120
+ ]);
121
+ return parentTools.flatMap((tool) => {
122
+ const candidate = local && rebound.has(tool.name) ? local.get(tool.name) : tool;
123
+ return candidate ? [protectDelegatedTool(candidate)] : [];
124
+ });
125
+ },
115
126
  verifyChanges: mode === "implement"
116
127
  });
117
128
  const abort = () => child.abort();
@@ -119,8 +130,8 @@ function createDelegateTool(deps) {
119
130
  if (signal?.aborted) abort();
120
131
  try {
121
132
  const result = await child.run();
122
- const currentRevision = await readWorkspaceRevision(workspace);
123
- const stale = mode !== "implement" && (!revision || revision !== currentRevision);
133
+ const currentRevision = mode === "review" ? await readWorkspaceRevision(workspace) : void 0;
134
+ const stale = mode === "review" && (!revision || revision !== currentRevision);
124
135
  const patch = mode === "implement" ? await runGit(workspace, [
125
136
  "diff",
126
137
  "--no-ext-diff",
@@ -134,6 +145,7 @@ function createDelegateTool(deps) {
134
145
  text: [
135
146
  result.summary,
136
147
  stale ? "Workspace changed or could not be fingerprinted; review findings require revalidation." : "",
148
+ access.rejected.length ? `Rejected tools: ${JSON.stringify(access.rejected)}` : "",
137
149
  environmentId ? `Changes retained in ${workspace} (environment ${environmentId}). Review and integrate from this workspace, then verify the parent workspace again.` : "",
138
150
  patch ? `Patch preview:\n${patch.slice(0, 3e4)}` : ""
139
151
  ].filter(Boolean).join("\n\n")
@@ -142,6 +154,8 @@ function createDelegateTool(deps) {
142
154
  status: stale && result.status === "success" ? "partial" : result.status,
143
155
  mode,
144
156
  summary: result.summary,
157
+ grantedTools: tools,
158
+ rejectedTools: access.rejected,
145
159
  iterations: result.toolIterations,
146
160
  revision: currentRevision,
147
161
  reviewedRevision: revision,
@@ -161,6 +175,9 @@ function createDelegateTool(deps) {
161
175
  }],
162
176
  details: {
163
177
  status: signal?.aborted ? "cancelled" : "failed",
178
+ mode,
179
+ grantedTools: tools,
180
+ rejectedTools: access.rejected,
164
181
  ...environmentId ? {
165
182
  environmentId,
166
183
  workspace
@@ -174,4 +191,4 @@ function createDelegateTool(deps) {
174
191
  };
175
192
  }
176
193
  //#endregion
177
- export { DEFAULT_DELEGATE_TOOLS, DELEGATE_BLOCKED_TOOLS, INSPECTION_TOOLS, createDelegateTool, delegateToolNames };
194
+ export { DEFAULT_DELEGATE_TOOLS, DELEGATE_BLOCKED_TOOLS, createDelegateTool, delegateToolNames };
@@ -0,0 +1,7 @@
1
+ import type { Config } from '../../config/schema.js';
2
+ /** Noninteractive callers cannot silently turn an ask policy into an allow. */
3
+ export declare function createDelegationParentPolicy(options: {
4
+ getConfig: () => Config | undefined;
5
+ conversationId?: string;
6
+ agentId?: string;
7
+ }): import("../orchestration/agent-turn-policy.js").AgentTurnPolicy;
@@ -0,0 +1,41 @@
1
+ import { resolveEffectiveAgentConfigForAgent, resolveEffectiveAgentConfigForSession } from "../../config/agent-profile.js";
2
+ import { parseExternalToolRef } from "../external-tools/refs.js";
3
+ import { mcpToolPolicyId } from "../mcp/bundle-mcp-policy.js";
4
+ import { createAgentTurnPolicy } from "../orchestration/agent-turn-policy.js";
5
+ //#region src/agent/tools/delegation-parent-policy.ts
6
+ /** Noninteractive callers cannot silently turn an ask policy into an allow. */
7
+ function createDelegationParentPolicy(options) {
8
+ const policies = (name, args) => {
9
+ const config = options.getConfig();
10
+ if (!config) return [];
11
+ const resolved = options.conversationId ? resolveEffectiveAgentConfigForSession(config, options.conversationId) : options.agentId ? resolveEffectiveAgentConfigForAgent(config, options.agentId) : resolveEffectiveAgentConfigForSession(config, void 0);
12
+ const names = [name];
13
+ if (name === "xopc_tool_execute") {
14
+ const parsed = parseExternalToolRef(String(args?.toolRef ?? ""), "mcp");
15
+ const ref = String(args?.toolRef ?? "");
16
+ names.push(parsed ? mcpToolPolicyId(parsed.namespace, parsed.toolName) : ref);
17
+ }
18
+ return names.flatMap((id) => resolved.config.tools[id] ? [{
19
+ id,
20
+ ...resolved.config.tools[id]
21
+ }] : []);
22
+ };
23
+ return createAgentTurnPolicy({
24
+ authorizeToolCall: async (context) => {
25
+ const blocked = policies(context.toolCall.name, context.args).find((policy) => policy.mode !== "allow");
26
+ return blocked ? {
27
+ block: true,
28
+ reason: `${blocked.id} requires parent authorization (${blocked.mode}).`
29
+ } : void 0;
30
+ },
31
+ resolveToolLimit: (name, args) => {
32
+ const limited = policies(name, args).filter((policy) => policy.maxCallsPerTurn).sort((a, b) => a.maxCallsPerTurn - b.maxCallsPerTurn)[0];
33
+ return limited ? {
34
+ id: limited.id,
35
+ maxCalls: limited.maxCallsPerTurn
36
+ } : void 0;
37
+ }
38
+ });
39
+ }
40
+ //#endregion
41
+ export { createDelegationParentPolicy };