@xopcai/xopc 0.0.232 → 0.0.233

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 (183) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{activity-store-VXCWz43N.js → activity-store-DQ5L3PTi.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BXRaZYW8.js → agent-avatar-dicebear-Bps8fvBp.js} +1 -1
  5. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-dR7HMaRX.js → agent-defaults-settings-panel-JrK80PvP.js} +1 -1
  6. package/dist/gateway/static/root/assets/{agents-Bileycwe.js → agents-CJYEnz94.js} +1 -1
  7. package/dist/gateway/static/root/assets/{api-wGPgL8xY.js → api-DE6tZKG0.js} +1 -1
  8. package/dist/gateway/static/root/assets/{app-management-settings-panel-BJfClfPx.js → app-management-settings-panel-BryzDVod.js} +1 -1
  9. package/dist/gateway/static/root/assets/{appearance-settings-C_R0vUAu.js → appearance-settings-BoweLBDm.js} +1 -1
  10. package/dist/gateway/static/root/assets/apps-page-Bt8_sWtU.js +1 -0
  11. package/dist/gateway/static/root/assets/{archive-plugin-DnFHkSHE.js → archive-plugin-DyujzZNu.js} +1 -1
  12. package/dist/gateway/static/root/assets/{attachment-load-DZrEwytM.js → attachment-load-DmXGUWve.js} +1 -1
  13. package/dist/gateway/static/root/assets/attachment-preview-renderer-BDTD8YUe.js +12 -0
  14. package/dist/gateway/static/root/assets/{automations-page-DpkKajui.js → automations-page-DrTVSK7o.js} +1 -1
  15. package/dist/gateway/static/root/assets/automations-workspace-86UTesz3.js +6 -0
  16. package/dist/gateway/static/root/assets/{binary-plugins-Ckc_Csue.js → binary-plugins-mFyeC74W.js} +2 -2
  17. package/dist/gateway/static/root/assets/{block-editor-DAC6w-iV.js → block-editor-D5yMFrgH.js} +3 -3
  18. package/dist/gateway/static/root/assets/browser-settings-page-DfMU7yXv.js +8 -0
  19. package/dist/gateway/static/root/assets/{browser-workflow-inputs-BGd7R8GO.js → browser-workflow-inputs-B6rLBtua.js} +1 -1
  20. package/dist/gateway/static/root/assets/{browser-workflows-page-DUfj7ntM.js → browser-workflows-page-C74sg50f.js} +2 -2
  21. package/dist/gateway/static/root/assets/capabilities-settings-panel-lsHRuBlX.js +7 -0
  22. package/dist/gateway/static/root/assets/channels-settings-CxR_clkO.js +1 -0
  23. package/dist/gateway/static/root/assets/{channels-status-swr-CfboECLt.js → channels-status-swr-DowVNjNw.js} +1 -1
  24. package/dist/gateway/static/root/assets/{chat-terminal-dock-CA9fndbZ.js → chat-terminal-dock-BO6_BgjE.js} +1 -1
  25. package/dist/gateway/static/root/assets/connectors-page-CpNLyKB4.js +2 -0
  26. package/dist/gateway/static/root/assets/{cron-expression-CqPu7HYJ.js → cron-expression-CYMXfk4M.js} +1 -1
  27. package/dist/gateway/static/root/assets/{date-picker-C8dJsCgk.js → date-picker-DIM_OkQP.js} +1 -1
  28. package/dist/gateway/static/root/assets/{dependency-picker-Iz2wYimy.js → dependency-picker-Cp8I6rsL.js} +1 -1
  29. package/dist/gateway/static/root/assets/{desktop-pet-settings-YfeVVzLe.js → desktop-pet-settings-NjPiz3M_.js} +1 -1
  30. package/dist/gateway/static/root/assets/{desktop-pet-C3h40FmG.js → desktop-pet-wzSzZ6S8.js} +1 -1
  31. package/dist/gateway/static/root/assets/{extension-debug-page-CChgztz_.js → extension-debug-page-CoMl13ET.js} +1 -1
  32. package/dist/gateway/static/root/assets/{extension-page-DF6ct6_y.js → extension-page-CXc1Lc8x.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-settings-page-jgBsjn0F.js → extension-settings-page-CfEWpgjl.js} +1 -1
  34. package/dist/gateway/static/root/assets/{gateway-config-api-CVajuIGL.js → gateway-config-api-yH4KAEkK.js} +1 -1
  35. package/dist/gateway/static/root/assets/{gateway-config-swr-BGQn8duh.js → gateway-config-swr-YyZJKvFf.js} +1 -1
  36. package/dist/gateway/static/root/assets/{gateway-settings-BbssNGid.js → gateway-settings-BmjJIiKh.js} +1 -1
  37. package/dist/gateway/static/root/assets/{gateway-startup-retry-D2Rnh9fX.js → gateway-startup-retry-Dnrl91pU.js} +1 -1
  38. package/dist/gateway/static/root/assets/{heartbeat-settings-CJtXu8iD.js → heartbeat-settings-DGq9q4b6.js} +1 -1
  39. package/dist/gateway/static/root/assets/home-page-Dj4B1wb3.js +1 -0
  40. package/dist/gateway/static/root/assets/{image-generation-api-D7W2p5_8.js → image-generation-api-BNzAAw4Y.js} +1 -1
  41. package/dist/gateway/static/root/assets/index-BGf-TiZj.js +70 -0
  42. package/dist/gateway/static/root/assets/index-BTUuTnc9.css +2 -0
  43. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BvHYyNus.js → keyboard-shortcuts-settings-CfNV1ysF.js} +1 -1
  44. package/dist/gateway/static/root/assets/local-app-workbench-page-IJg75WaN.js +2 -0
  45. package/dist/gateway/static/root/assets/{local-apps-page-Cb8t2wWM.js → local-apps-page-MPd4Fl_l.js} +1 -1
  46. package/dist/gateway/static/root/assets/{locale-store-DLwVxS3V.js → locale-store-D_dq_0LV.js} +1 -1
  47. package/dist/gateway/static/root/assets/logs-page-NsfoazgK.js +2 -0
  48. package/dist/gateway/static/root/assets/{management-settings-B8Jie4-J.js → management-settings-DeIjN4e0.js} +1 -1
  49. package/dist/gateway/static/root/assets/{markdown-split-BD1r4o2i.js → markdown-split-BUAVv6Gu.js} +1 -1
  50. package/dist/gateway/static/root/assets/markdown-view-CPkBOY3h.js +201 -0
  51. package/dist/gateway/static/root/assets/{media-plugins-BCev8fkW.js → media-plugins-DFsVnOY-.js} +1 -1
  52. package/dist/gateway/static/root/assets/messages-D4qOHT4D.js +3 -0
  53. package/dist/gateway/static/root/assets/{mobile-device-api-DACoHhhk.js → mobile-device-api-D9JevyCE.js} +1 -1
  54. package/dist/gateway/static/root/assets/models-hub-cache-C5y2gzMU.js +1 -0
  55. package/dist/gateway/static/root/assets/{notes-api-DEdNKw0I.js → notes-api-BWSfTP1w.js} +1 -1
  56. package/dist/gateway/static/root/assets/{notes-page-BBTLjJwa.js → notes-page-CV8UJj7g.js} +1 -1
  57. package/dist/gateway/static/root/assets/notes-workbench-DrjMVgEI.js +4 -0
  58. package/dist/gateway/static/root/assets/{oauth-api-BNCO2YK5.js → oauth-api-DnanEBsf.js} +1 -1
  59. package/dist/gateway/static/root/assets/{product-open-page-fEBbRekH.js → product-open-page-zNg1KOy2.js} +1 -1
  60. package/dist/gateway/static/root/assets/project-detail-page-tfcGYL-O.js +1 -0
  61. package/dist/gateway/static/root/assets/{projects-page-dHDXpMCg.js → projects-page-BkjnXLdJ.js} +1 -1
  62. package/dist/gateway/static/root/assets/{registry-api-Cvr4Qm7N.js → registry-api-BeVzfRvs.js} +1 -1
  63. package/dist/gateway/static/root/assets/remote-access-hub-BXn-3n2d.js +1 -0
  64. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-BT5B74IH.js → runtime-tools-settings-panel-BxoioSvT.js} +2 -2
  65. package/dist/gateway/static/root/assets/{schema-form-C4tB34S5.js → schema-form-vzbmscrE.js} +1 -1
  66. package/dist/gateway/static/root/assets/sessions-page-MLNjrOkw.js +1 -0
  67. package/dist/gateway/static/root/assets/settings-advanced-gate-ByNLBY2q.js +1 -0
  68. package/dist/gateway/static/root/assets/{settings-form-section-D1oXhkgI.js → settings-form-section-x9k7MWD3.js} +1 -1
  69. package/dist/gateway/static/root/assets/{settings-loading-skeleton-CldfX6FJ.js → settings-loading-skeleton-BLFOD9h9.js} +1 -1
  70. package/dist/gateway/static/root/assets/{settings-page-c3LJnbvE.js → settings-page-CwgTooar.js} +1 -1
  71. package/dist/gateway/static/root/assets/setup-status-panel-bnueBZg8.js +1 -0
  72. package/dist/gateway/static/root/assets/{share-preview-page-DptCK_mk.js → share-preview-page-COTrPAd-.js} +1 -1
  73. package/dist/gateway/static/root/assets/shares-settings-D-v_psqL.js +1 -0
  74. package/dist/gateway/static/root/assets/skill-reload-api-DJICr4wh.js +1 -0
  75. package/dist/gateway/static/root/assets/{skills-page-OHBeBtSg.js → skills-page-D9wM6_Jn.js} +1 -1
  76. package/dist/gateway/static/root/assets/{skills-page.utils-B9WZbOLC.js → skills-page.utils-PtlQLM5a.js} +1 -1
  77. package/dist/gateway/static/root/assets/{system-settings-panel-Dj_DpOGw.js → system-settings-panel-BR74mNEK.js} +1 -1
  78. package/dist/gateway/static/root/assets/task-detail-page-B-rmeE8t.js +1 -0
  79. package/dist/gateway/static/root/assets/{theme-store-Cpd3Gd5R.js → theme-store-Bud54q9e.js} +1 -1
  80. package/dist/gateway/static/root/assets/{use-autosave-DCl5Yq7N.js → use-autosave-DH47MH7s.js} +1 -1
  81. package/dist/gateway/static/root/assets/user-context-page-B6eyxxOT.js +1 -0
  82. package/dist/gateway/static/root/assets/{voice-api-key-field-BB_ZQV8F.js → voice-api-key-field-B59GaqRa.js} +1 -1
  83. package/dist/gateway/static/root/assets/{voice-config-api-BNiYmpl5.js → voice-config-api-C0ahrZ0e.js} +1 -1
  84. package/dist/gateway/static/root/assets/{work-discovery-overlay-BrIdtdum.js → work-discovery-overlay-DTn4RCcx.js} +1 -1
  85. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-hKh-uCLo.js → workflow-run-setup-panel-yDFoEX06.js} +2 -2
  86. package/dist/gateway/static/root/assets/workflows-page-BOwSbfWm.js +5 -0
  87. package/dist/gateway/static/root/index.html +13 -13
  88. package/dist/package.js +1 -1
  89. package/dist/packages/endpoint-tools-protocol/src/index.js +162 -2
  90. package/dist/packages/gateway-contract/src/product-delivery.js +6 -2
  91. package/dist/packages/gateway-contract/src/turn-outcome.js +77 -1
  92. package/dist/src/agent/capabilities/definitions.d.ts +1 -1
  93. package/dist/src/agent/capabilities/definitions.js +2 -1
  94. package/dist/src/agent/external-tools/endpoint-provider.js +5 -1
  95. package/dist/src/agent/prompt/sections/tooling.js +5 -0
  96. package/dist/src/agent/tools/create-share-tool.js +2 -1
  97. package/dist/src/agent/tools/exec-command.js +2 -1
  98. package/dist/src/agent/tools/factory.js +2 -0
  99. package/dist/src/agent/tools/index.d.ts +1 -0
  100. package/dist/src/agent/tools/index.js +2 -1
  101. package/dist/src/agent/tools/publish-artifacts.d.ts +2 -0
  102. package/dist/src/agent/tools/publish-artifacts.js +82 -0
  103. package/dist/src/agent/tools/send-media.js +2 -1
  104. package/dist/src/cli/command-catalog.js +4 -0
  105. package/dist/src/cli/command-loaders.js +1 -0
  106. package/dist/src/cli/command-manifest.js +4 -0
  107. package/dist/src/cli/commands/auth.js +1 -1
  108. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  109. package/dist/src/cli/commands/onboard/model.js +1 -1
  110. package/dist/src/cli/commands/providers.js +1 -1
  111. package/dist/src/cli/commands/support.d.ts +1 -0
  112. package/dist/src/cli/commands/support.js +51 -0
  113. package/dist/src/endpoint-tools/binding-service.d.ts +22 -0
  114. package/dist/src/endpoint-tools/binding-service.js +45 -0
  115. package/dist/src/endpoint-tools/index.d.ts +1 -0
  116. package/dist/src/endpoint-tools/index.js +2 -1
  117. package/dist/src/endpoint-tools/invocation-service.d.ts +1 -0
  118. package/dist/src/endpoint-tools/invocation-service.js +8 -0
  119. package/dist/src/endpoint-tools/policy.d.ts +4 -1
  120. package/dist/src/endpoint-tools/policy.js +68 -5
  121. package/dist/src/endpoint-tools/registry.js +1 -1
  122. package/dist/src/endpoint-tools/runtime.d.ts +2 -0
  123. package/dist/src/endpoint-tools/runtime.js +8 -0
  124. package/dist/src/files/file-service.js +14 -5
  125. package/dist/src/gateway/chat-stream/turn-outcome-collector.js +41 -6
  126. package/dist/src/gateway/heartbeat/service.js +1 -1
  127. package/dist/src/gateway/hono/middleware/auth.js +3 -2
  128. package/dist/src/gateway/hono/routes/endpoint-tools.js +68 -1
  129. package/dist/src/gateway/hono/routes/files.js +7 -0
  130. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  131. package/dist/src/gateway/hono/routes/media.js +46 -2
  132. package/dist/src/gateway/hono/routes/support.d.ts +3 -0
  133. package/dist/src/gateway/hono/routes/support.js +28 -0
  134. package/dist/src/gateway/hono/routes/you.js +24 -0
  135. package/dist/src/heartbeat/index.js +1 -1
  136. package/dist/src/media/store.js +9 -0
  137. package/dist/src/storage/sqlite/endpoint-session-binding-repository.d.ts +8 -0
  138. package/dist/src/storage/sqlite/endpoint-session-binding-repository.js +32 -0
  139. package/dist/src/storage/sqlite/index.d.ts +1 -0
  140. package/dist/src/storage/sqlite/index.js +2 -1
  141. package/dist/src/storage/sqlite/migrations/139_turn_outcome_artifact_backfill.sql +231 -0
  142. package/dist/src/storage/sqlite/migrations/140_endpoint_session_bindings.sql +8 -0
  143. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  144. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  145. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  146. package/dist/src/storage/sqlite/schema.js +1 -1
  147. package/dist/src/support/build-support-investigation-prompt.d.ts +2 -0
  148. package/dist/src/support/build-support-investigation-prompt.js +29 -0
  149. package/dist/src/support/collect-support-report.d.ts +16 -0
  150. package/dist/src/support/collect-support-report.js +216 -0
  151. package/dist/src/support/redact-support-report.d.ts +16 -0
  152. package/dist/src/support/redact-support-report.js +89 -0
  153. package/dist/src/support/types.d.ts +76 -0
  154. package/dist/src/support/types.js +18 -0
  155. package/dist/src/user-context/user-avatar.d.ts +17 -0
  156. package/dist/src/user-context/user-avatar.js +116 -0
  157. package/package.json +2 -2
  158. package/dist/gateway/static/root/assets/apps-page-CBow8pBI.js +0 -1
  159. package/dist/gateway/static/root/assets/attachment-preview-renderer-Cmo-5BP5.js +0 -11
  160. package/dist/gateway/static/root/assets/automations-workspace-BBntVWgv.js +0 -6
  161. package/dist/gateway/static/root/assets/browser-settings-page-DrsMd-Po.js +0 -8
  162. package/dist/gateway/static/root/assets/capabilities-settings-panel-97gkGeSV.js +0 -7
  163. package/dist/gateway/static/root/assets/channels-settings-CuBsz8UI.js +0 -1
  164. package/dist/gateway/static/root/assets/connectors-page-SuL7td7Z.js +0 -2
  165. package/dist/gateway/static/root/assets/home-page-Be1o1AXQ.js +0 -1
  166. package/dist/gateway/static/root/assets/index-B2OJSXX9.js +0 -70
  167. package/dist/gateway/static/root/assets/index-rHjAElxG.css +0 -2
  168. package/dist/gateway/static/root/assets/local-app-workbench-page-COfelgk7.js +0 -2
  169. package/dist/gateway/static/root/assets/logs-page-DA8oDjeB.js +0 -2
  170. package/dist/gateway/static/root/assets/markdown-view-B-BHEcNd.js +0 -201
  171. package/dist/gateway/static/root/assets/messages-Dp1OxSKt.js +0 -3
  172. package/dist/gateway/static/root/assets/models-hub-cache-C8zjqmIv.js +0 -1
  173. package/dist/gateway/static/root/assets/notes-workbench-DICgUGPv.js +0 -4
  174. package/dist/gateway/static/root/assets/project-detail-page-CqdkznYJ.js +0 -1
  175. package/dist/gateway/static/root/assets/remote-access-hub-2feZudJw.js +0 -1
  176. package/dist/gateway/static/root/assets/sessions-page-CO2JCFdy.js +0 -1
  177. package/dist/gateway/static/root/assets/settings-advanced-gate-DxMVaqdn.js +0 -1
  178. package/dist/gateway/static/root/assets/setup-status-panel-DZNHb6A5.js +0 -1
  179. package/dist/gateway/static/root/assets/shares-settings-DakZ2AGN.js +0 -1
  180. package/dist/gateway/static/root/assets/skill-reload-api-BOiNkWJC.js +0 -1
  181. package/dist/gateway/static/root/assets/task-detail-page-D5Nbe3C8.js +0 -1
  182. package/dist/gateway/static/root/assets/user-context-page-CAPSUK8B.js +0 -1
  183. package/dist/gateway/static/root/assets/workflows-page-ahDvypGO.js +0 -5
@@ -83,6 +83,10 @@ const ROOT_HELP_COMMANDS = [
83
83
  name: "doctor [options]",
84
84
  description: "Check xopc installation health and diagnose common issues"
85
85
  },
86
+ {
87
+ name: "support",
88
+ description: "Create a redacted diagnostic report for an xopc problem"
89
+ },
86
90
  {
87
91
  name: "runtime",
88
92
  description: "Manage Node.js and Python tool runtimes used by agents"
@@ -4,9 +4,9 @@ import { CredentialResolver, init_credentials } from "../../auth/credentials.js"
4
4
  import { getAllProviders, init_providers } from "../../providers/index.js";
5
5
  import { formatExamples, register } from "../registry.js";
6
6
  import { disconnectProvider } from "../../providers/provider-disconnect.js";
7
+ import { colorizeStatus, colors } from "../utils/colors.js";
7
8
  import { listAllProfiles, listProfilesForProvider, removeAuthProfile, upsertAuthProfile } from "../../auth/profiles/profiles.js";
8
9
  import { getOAuthProvider, getSupportedOAuthProviders } from "../utils/oauth-providers.js";
9
- import { colorizeStatus, colors } from "../utils/colors.js";
10
10
  import { runCliOAuthLogin } from "../utils/oauth-login.js";
11
11
  import { Command } from "commander";
12
12
  //#region src/cli/commands/auth.ts
@@ -52,7 +52,7 @@ async function checkDatabaseSchema(ctx) {
52
52
  id: "database-schema",
53
53
  label: "Database schema",
54
54
  status: "pass",
55
- message: `SQLite schema v${status.dbVersion} (current release v138).`,
55
+ message: `SQLite schema v${status.dbVersion} (current release v140).`,
56
56
  hints: []
57
57
  };
58
58
  }
@@ -10,9 +10,9 @@ import { discoverProviderModels, isProviderApiDiscoverable } from "../../../prov
10
10
  import { getXopcCloudCatalogCoordinator } from "../../../providers/xopc-cloud-catalog-coordinator.js";
11
11
  import { getDomesticProviderBaseUrl, getDomesticProviderPreset, getDomesticProviderPresetIds, providerConfigFromDomesticPreset } from "../../../providers/domestic-presets.js";
12
12
  import { getProviderHint, getRecommendedModelsForProvider, sortModelsForPicker, sortProvidersForPicker } from "../../../providers/presentation.js";
13
+ import { colors } from "../../utils/colors.js";
13
14
  import { listProfilesForProvider } from "../../../auth/profiles/profiles.js";
14
15
  import { getOAuthProvider } from "../../utils/oauth-providers.js";
15
- import { colors } from "../../utils/colors.js";
16
16
  import { runCliOAuthLogin } from "../../utils/oauth-login.js";
17
17
  import { confirm, input, select } from "@inquirer/prompts";
18
18
  //#region src/cli/commands/onboard/model.ts
@@ -1,8 +1,8 @@
1
1
  import { PROVIDER_ENV_MAP, getApiKeyFromEnv } from "../../providers/env-keys.js";
2
2
  import { PROVIDER_META, getAllProviders, getSortedProviders, init_providers } from "../../providers/index.js";
3
3
  import { formatExamples, register } from "../registry.js";
4
- import { getProfile, listAllProfiles, listProfilesForProvider, removeAuthProfile, upsertAuthProfile } from "../../auth/profiles/profiles.js";
5
4
  import { colors } from "../utils/colors.js";
5
+ import { getProfile, listAllProfiles, listProfilesForProvider, removeAuthProfile, upsertAuthProfile } from "../../auth/profiles/profiles.js";
6
6
  import { SETUP_EXIT } from "./setup-shared/types.js";
7
7
  import { emitTask } from "./setup-shared/output.js";
8
8
  import { isPromptCancelled, promptSecret } from "./setup-shared/prompts.js";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import { init_paths_state, resolveStateDir } from "../../config/paths-state.js";
2
+ import { formatExamples, register } from "../registry.js";
3
+ import { collectSupportReport } from "../../support/collect-support-report.js";
4
+ import { SupportReportInputSchema } from "../../support/types.js";
5
+ import { resolve } from "node:path";
6
+ import { writeFile } from "node:fs/promises";
7
+ import { Command } from "commander";
8
+ //#region src/cli/commands/support.ts
9
+ init_paths_state();
10
+ function defaultOutputPath(capturedAt) {
11
+ return resolve(`xopc-diagnostics-${capturedAt.replace(/[:.]/g, "-")}.json`);
12
+ }
13
+ async function runSupportReport(ctx, options) {
14
+ const report = await collectSupportReport(SupportReportInputSchema.parse({
15
+ problem: options.problem ?? "xopc startup or runtime problem",
16
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString()
17
+ }), { paths: {
18
+ configPath: ctx.configPath,
19
+ stateDir: resolveStateDir(),
20
+ workspaceDir: ctx.workspacePath
21
+ } });
22
+ const outputPath = options.output ? resolve(options.output) : defaultOutputPath(report.capturedAt);
23
+ await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`, {
24
+ encoding: "utf8",
25
+ flag: "wx",
26
+ mode: 384
27
+ });
28
+ console.log(report.markdown);
29
+ console.log(`\nDiagnostic file: ${outputPath}`);
30
+ }
31
+ function createSupportCommand(ctx) {
32
+ const command = new Command("support").description("Create a redacted diagnostic report for an xopc problem").addHelpText("after", formatExamples([
33
+ "xopc support report",
34
+ "xopc support report --problem \"Telegram receives messages but does not reply\"",
35
+ "xopc support report --output ./xopc-diagnostics.json"
36
+ ]));
37
+ command.command("report").description("Collect Doctor results and nearby warning/error logs").option("-p, --problem <text>", "Short description of the problem").option("-o, --output <path>", "Diagnostic JSON output path (must not already exist)").action((options) => runSupportReport(ctx, options));
38
+ return command;
39
+ }
40
+ register({
41
+ id: "support",
42
+ name: "support",
43
+ description: "Create a redacted diagnostic report for an xopc problem",
44
+ factory: createSupportCommand,
45
+ metadata: {
46
+ category: "maintenance",
47
+ examples: ["xopc support report"]
48
+ }
49
+ });
50
+ //#endregion
51
+ export {};
@@ -0,0 +1,22 @@
1
+ import type { EndpointConnectionSnapshot } from './registry.js';
2
+ import { EndpointRegistry } from './registry.js';
3
+ export interface EndpointSessionBinding {
4
+ sessionKey: string;
5
+ endpointId: string;
6
+ boundAt: number;
7
+ }
8
+ export interface EndpointSessionBindingStore {
9
+ get(sessionKey: string): EndpointSessionBinding | undefined;
10
+ set(binding: EndpointSessionBinding): EndpointSessionBinding;
11
+ delete(sessionKey: string): boolean;
12
+ }
13
+ export declare class EndpointBindingService {
14
+ private readonly registry;
15
+ private readonly store;
16
+ constructor(registry: EndpointRegistry, store?: EndpointSessionBindingStore);
17
+ bind(sessionKey: string, endpointId: string, now?: number): EndpointSessionBinding;
18
+ get(sessionKey: string): EndpointSessionBinding | undefined;
19
+ resolve(sessionKey: string): EndpointConnectionSnapshot | undefined;
20
+ unbind(sessionKey: string): boolean;
21
+ private normalizeSessionKey;
22
+ }
@@ -0,0 +1,45 @@
1
+ //#region src/endpoint-tools/binding-service.ts
2
+ function memoryStore() {
3
+ const bindings = /* @__PURE__ */ new Map();
4
+ return {
5
+ get: (sessionKey) => bindings.get(sessionKey),
6
+ set: (binding) => {
7
+ bindings.set(binding.sessionKey, binding);
8
+ return binding;
9
+ },
10
+ delete: (sessionKey) => bindings.delete(sessionKey)
11
+ };
12
+ }
13
+ var EndpointBindingService = class {
14
+ constructor(registry, store = memoryStore()) {
15
+ this.registry = registry;
16
+ this.store = store;
17
+ }
18
+ bind(sessionKey, endpointId, now = Date.now()) {
19
+ const normalizedSessionKey = this.normalizeSessionKey(sessionKey);
20
+ if (!this.registry.get(endpointId)) throw new Error("Endpoint is offline");
21
+ const binding = {
22
+ sessionKey: normalizedSessionKey,
23
+ endpointId,
24
+ boundAt: now
25
+ };
26
+ return this.store.set(binding);
27
+ }
28
+ get(sessionKey) {
29
+ return this.store.get(this.normalizeSessionKey(sessionKey));
30
+ }
31
+ resolve(sessionKey) {
32
+ const binding = this.get(sessionKey);
33
+ return binding ? this.registry.get(binding.endpointId) : void 0;
34
+ }
35
+ unbind(sessionKey) {
36
+ return this.store.delete(this.normalizeSessionKey(sessionKey));
37
+ }
38
+ normalizeSessionKey(sessionKey) {
39
+ const normalized = sessionKey.trim();
40
+ if (!normalized || normalized.length > 500) throw new TypeError("Invalid session key");
41
+ return normalized;
42
+ }
43
+ };
44
+ //#endregion
45
+ export { EndpointBindingService };
@@ -1,4 +1,5 @@
1
1
  export { EndpointToolRuntime } from './runtime.js';
2
+ export { EndpointBindingService, type EndpointSessionBinding, type EndpointSessionBindingStore, } from './binding-service.js';
2
3
  export { EndpointInvocationService, EndpointToolExecutionError, type EndpointInvocationAuditSink, type EndpointInvocationResult, } from './invocation-service.js';
3
4
  export { EndpointToolPolicy, EndpointToolPolicyError } from './policy.js';
4
5
  export { EndpointRegistry, endpointToolRevision, type EndpointConnectionSnapshot, type RegisteredEndpointTool, } from './registry.js';
@@ -1,6 +1,7 @@
1
+ import { EndpointBindingService } from "./binding-service.js";
1
2
  import { EndpointToolPolicy, EndpointToolPolicyError } from "./policy.js";
2
3
  import { EndpointInvocationService, EndpointToolExecutionError } from "./invocation-service.js";
3
4
  import { EndpointRegistry, endpointToolRevision } from "./registry.js";
4
5
  import { ENDPOINT_UPLOAD_MAX_BYTES, EndpointUploadError, EndpointUploadService } from "./upload-service.js";
5
6
  import { EndpointToolRuntime } from "./runtime.js";
6
- export { ENDPOINT_UPLOAD_MAX_BYTES, EndpointInvocationService, EndpointRegistry, EndpointToolExecutionError, EndpointToolPolicy, EndpointToolPolicyError, EndpointToolRuntime, EndpointUploadError, EndpointUploadService, endpointToolRevision };
7
+ export { ENDPOINT_UPLOAD_MAX_BYTES, EndpointBindingService, EndpointInvocationService, EndpointRegistry, EndpointToolExecutionError, EndpointToolPolicy, EndpointToolPolicyError, EndpointToolRuntime, EndpointUploadError, EndpointUploadService, endpointToolRevision };
@@ -43,6 +43,7 @@ export declare class EndpointInvocationService {
43
43
  private readonly options;
44
44
  private readonly pending;
45
45
  private readonly policy;
46
+ private readonly ajv;
46
47
  constructor(registry: EndpointRegistry, options?: EndpointInvocationServiceOptions);
47
48
  invoke(params: {
48
49
  endpointId: string;
@@ -1,6 +1,7 @@
1
1
  import { ENDPOINT_INVOCATION_RECEIPT_TIMEOUT_MS, canonicalJson } from "../../packages/endpoint-tools-protocol/src/index.js";
2
2
  import { EndpointToolPolicy, EndpointToolPolicyError } from "./policy.js";
3
3
  import crypto from "node:crypto";
4
+ import Ajv2020 from "ajv/dist/2020.js";
4
5
  //#region src/endpoint-tools/invocation-service.ts
5
6
  var EndpointToolExecutionError = class extends Error {
6
7
  constructor(code, message) {
@@ -12,6 +13,10 @@ var EndpointToolExecutionError = class extends Error {
12
13
  var EndpointInvocationService = class {
13
14
  pending = /* @__PURE__ */ new Map();
14
15
  policy;
16
+ ajv = new Ajv2020({
17
+ allErrors: true,
18
+ strict: true
19
+ });
15
20
  constructor(registry, options = {}) {
16
21
  this.registry = registry;
17
22
  this.options = options;
@@ -58,6 +63,7 @@ var EndpointInvocationService = class {
58
63
  id: invocationId,
59
64
  endpointId: params.endpointId,
60
65
  toolName: params.toolName,
66
+ descriptor: tool.descriptor,
61
67
  state: "sent",
62
68
  receiptTimer: setTimeout(() => {
63
69
  this.fail(invocationId, this.error("TOOL_TIMEOUT", "Endpoint did not receive the invocation"));
@@ -111,6 +117,8 @@ var EndpointInvocationService = class {
111
117
  case "tool.result":
112
118
  if (pending.state !== "running") return;
113
119
  try {
120
+ const validate = this.ajv.compile(pending.descriptor.outputSchema);
121
+ if (!validate(message.payload.content)) throw new Error(`Endpoint result does not match its contract: ${this.ajv.errorsText(validate.errors)}`);
114
122
  this.options.uploads?.validateAndClose(invocationId, message.payload.content);
115
123
  } catch (error) {
116
124
  this.fail(invocationId, this.error("PROTOCOL_ERROR", error instanceof Error ? error.message : String(error)));
@@ -1,10 +1,13 @@
1
- import type { EndpointAvailability, EndpointKind, EndpointToolDescriptor } from '@xopcai/endpoint-tools-protocol';
1
+ import { type EndpointAvailability, type EndpointKind, type EndpointToolDescriptor } from '@xopcai/endpoint-tools-protocol';
2
2
  export interface EndpointToolPolicyDecision {
3
3
  confirmationRequired: boolean;
4
4
  }
5
5
  export declare class EndpointToolPolicyError extends Error {
6
6
  }
7
7
  export declare class EndpointToolPolicy {
8
+ private readonly ajv;
8
9
  validateDescriptor(kind: EndpointKind, descriptor: EndpointToolDescriptor): void;
9
10
  evaluate(descriptor: EndpointToolDescriptor, availability: EndpointAvailability): EndpointToolPolicyDecision;
11
+ private validatePolicyFields;
12
+ private assertFields;
10
13
  }
@@ -1,17 +1,80 @@
1
+ import { ENDPOINT_CONTACT_LIST_OUTPUT_SCHEMA, ENDPOINT_CONTACT_OUTPUT_SCHEMA, ENDPOINT_FILE_OUTPUT_SCHEMA, ENDPOINT_TEXT_OUTPUT_SCHEMA, canonicalJson } from "../../packages/endpoint-tools-protocol/src/index.js";
2
+ import Ajv2020 from "ajv/dist/2020.js";
1
3
  //#region src/endpoint-tools/policy.ts
2
4
  var EndpointToolPolicyError = class extends Error {};
5
+ function contract(policyId, outputSchema, resultKinds, permissions) {
6
+ return {
7
+ policyId,
8
+ outputSchema,
9
+ resultKinds,
10
+ permissions
11
+ };
12
+ }
13
+ const POLICY_BY_TOOL = {
14
+ "web.file.pick": contract("personal.foreground-read", ENDPOINT_FILE_OUTPUT_SCHEMA, ["file"], ["file-read"]),
15
+ "web.file.download": contract("user.foreground-write", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["file-download"]),
16
+ "web.page.get_selection": contract("public.foreground-read", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], []),
17
+ "web.clipboard.write": contract("user.foreground-write", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["clipboard-write"]),
18
+ "web.page.navigate": contract("user.foreground-write", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["navigation"]),
19
+ "desktop.file.pick": contract("personal.foreground-read", ENDPOINT_FILE_OUTPUT_SCHEMA, ["file"], ["file-read"]),
20
+ "desktop.file.save": contract("user.foreground-write", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["file-write"]),
21
+ "desktop.notification.show": contract("user.foreground-write", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["notifications"]),
22
+ "desktop.clipboard.read": contract("personal.foreground-read", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["clipboard-read"]),
23
+ "desktop.clipboard.write": contract("user.foreground-write", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["clipboard-write"]),
24
+ "desktop.app.open_external": contract("user.foreground-write", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["open-external-url"]),
25
+ "mobile.contacts.pick": contract("personal.foreground-read", ENDPOINT_CONTACT_OUTPUT_SCHEMA, ["json"], ["contacts-read-selected"]),
26
+ "mobile.contacts.search": contract("personal.foreground-read", ENDPOINT_CONTACT_LIST_OUTPUT_SCHEMA, ["json"], ["contacts-read"]),
27
+ "mobile.contacts.get": contract("personal.foreground-read", ENDPOINT_CONTACT_OUTPUT_SCHEMA, ["json"], ["contacts-read"]),
28
+ "mobile.file.pick": contract("personal.foreground-read", ENDPOINT_FILE_OUTPUT_SCHEMA, ["file"], ["file-read"]),
29
+ "mobile.file.share": contract("user.foreground-write", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["file-share"]),
30
+ "mobile.notification.show": contract("user.foreground-write", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["notifications"]),
31
+ "mobile.device.get_info": contract("public.background-read", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], []),
32
+ "mobile.clipboard.write": contract("user.foreground-write", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["clipboard-write"]),
33
+ "mobile.app.open_url": contract("user.foreground-write", ENDPOINT_TEXT_OUTPUT_SCHEMA, ["text"], ["open-external-url"])
34
+ };
3
35
  var EndpointToolPolicy = class {
36
+ ajv = new Ajv2020({
37
+ allErrors: true,
38
+ strict: true
39
+ });
4
40
  validateDescriptor(kind, descriptor) {
5
41
  if (!descriptor.name.startsWith(`${kind}.`)) throw new EndpointToolPolicyError(`Tool ${descriptor.name} does not belong to ${kind}`);
6
- if (descriptor.effect !== "read") {
7
- if (descriptor.confirmation !== "always") throw new EndpointToolPolicyError(`Mutating tool ${descriptor.name} must always require confirmation`);
8
- if (!descriptor.requiresForeground) throw new EndpointToolPolicyError(`Mutating tool ${descriptor.name} must require foreground`);
9
- if (descriptor.requiredPermissions.length === 0) throw new EndpointToolPolicyError(`Mutating tool ${descriptor.name} must declare a permission`);
42
+ const contract = POLICY_BY_TOOL[descriptor.name];
43
+ if (!contract) throw new EndpointToolPolicyError(`Tool ${descriptor.name} has no trusted server policy`);
44
+ if (descriptor.policyId !== contract.policyId) throw new EndpointToolPolicyError(`Tool ${descriptor.name} does not match its trusted server policy`);
45
+ if (canonicalJson(descriptor.outputSchema) !== canonicalJson(contract.outputSchema) || canonicalJson(descriptor.resultKinds) !== canonicalJson(contract.resultKinds) || canonicalJson([...descriptor.requiredPermissions].sort()) !== canonicalJson([...contract.permissions].sort())) throw new EndpointToolPolicyError(`Tool ${descriptor.name} does not match its trusted server contract`);
46
+ this.validatePolicyFields(descriptor);
47
+ try {
48
+ this.ajv.compile(descriptor.outputSchema);
49
+ } catch (error) {
50
+ throw new EndpointToolPolicyError(`Tool ${descriptor.name} has an invalid output schema: ${error instanceof Error ? error.message : String(error)}`);
10
51
  }
11
52
  }
12
53
  evaluate(descriptor, availability) {
13
54
  if (descriptor.requiresForeground && availability !== "foreground") throw new EndpointToolPolicyError("Endpoint is not in the foreground");
14
- return { confirmationRequired: descriptor.confirmation === "always" || descriptor.effect !== "read" };
55
+ return { confirmationRequired: descriptor.confirmation === "always" };
56
+ }
57
+ validatePolicyFields(descriptor) {
58
+ switch (descriptor.policyId) {
59
+ case "public.background-read":
60
+ this.assertFields(descriptor, "read", "never", false, "public");
61
+ return;
62
+ case "public.foreground-read":
63
+ this.assertFields(descriptor, "read", "never", true, "public");
64
+ return;
65
+ case "personal.foreground-read":
66
+ this.assertFields(descriptor, "read", "always", true, "personal");
67
+ if (descriptor.requiredPermissions.length === 0) throw new EndpointToolPolicyError(`Personal tool ${descriptor.name} must declare a permission`);
68
+ return;
69
+ case "user.foreground-write":
70
+ if (descriptor.effect === "read") throw new EndpointToolPolicyError(`Mutating tool ${descriptor.name} cannot declare a read effect`);
71
+ if (descriptor.confirmation !== "always" || !descriptor.requiresForeground || descriptor.sensitivity !== "personal" || descriptor.requiredPermissions.length === 0) throw new EndpointToolPolicyError(`Mutating tool ${descriptor.name} violates its trusted policy`);
72
+ return;
73
+ default: throw new EndpointToolPolicyError(`Unknown endpoint policy: ${descriptor.policyId}`);
74
+ }
75
+ }
76
+ assertFields(descriptor, effect, confirmation, requiresForeground, sensitivity) {
77
+ if (descriptor.effect !== effect || descriptor.confirmation !== confirmation || descriptor.requiresForeground !== requiresForeground || descriptor.sensitivity !== sensitivity) throw new EndpointToolPolicyError(`Tool ${descriptor.name} violates its trusted policy`);
15
78
  }
16
79
  };
17
80
  //#endregion
@@ -93,7 +93,7 @@ var EndpointRegistry = class {
93
93
  const connection = this.connectionByEndpointId.get(endpointId);
94
94
  if (!connection || connection.transport.readyState !== 1) throw new Error(`Endpoint is offline: ${endpointId}`);
95
95
  const message = {
96
- protocolVersion: 1,
96
+ protocolVersion: 2,
97
97
  messageId: crypto.randomUUID(),
98
98
  type,
99
99
  sentAt: Date.now(),
@@ -1,5 +1,6 @@
1
1
  import { type ClientEndpointMessage, type EndpointHelloPayload } from '@xopcai/endpoint-tools-protocol';
2
2
  import { type EndpointAuthenticatorDeps } from './auth.js';
3
+ import { EndpointBindingService } from './binding-service.js';
3
4
  import { EndpointInvocationService, type EndpointInvocationAuditSink } from './invocation-service.js';
4
5
  import { EndpointRegistry, type EndpointRegistration, type EndpointTransport } from './registry.js';
5
6
  import { EndpointUploadService } from './upload-service.js';
@@ -7,6 +8,7 @@ export declare class EndpointToolRuntime {
7
8
  readonly registry: EndpointRegistry;
8
9
  readonly invocations: EndpointInvocationService;
9
10
  readonly uploads: EndpointUploadService;
11
+ readonly bindings: EndpointBindingService;
10
12
  private readonly authenticator;
11
13
  private closed;
12
14
  constructor(options?: {
@@ -1,11 +1,13 @@
1
1
  import { resolveStateDir } from "../config/paths-state.js";
2
2
  import { createLogger } from "../utils/logger/index.js";
3
3
  import { init_logger } from "../utils/logger.js";
4
+ import { deleteEndpointSessionBinding, getEndpointSessionBinding, setEndpointSessionBinding } from "../storage/sqlite/endpoint-session-binding-repository.js";
4
5
  import { bindEndpointPrincipal, getEndpointPrincipal, touchEndpointPrincipal } from "../storage/sqlite/endpoint-principal-repository.js";
5
6
  import { finishEndpointToolInvocationAudit, startEndpointToolInvocationAudit } from "../storage/sqlite/endpoint-tool-invocation-repository.js";
6
7
  import "../storage/sqlite/index.js";
7
8
  import { init_paths } from "../config/paths.js";
8
9
  import { EndpointAuthenticator } from "./auth.js";
10
+ import { EndpointBindingService } from "./binding-service.js";
9
11
  import { EndpointToolPolicy } from "./policy.js";
10
12
  import { EndpointInvocationService } from "./invocation-service.js";
11
13
  import { EndpointRegistry } from "./registry.js";
@@ -20,11 +22,17 @@ var EndpointToolRuntime = class {
20
22
  registry;
21
23
  invocations;
22
24
  uploads;
25
+ bindings;
23
26
  authenticator;
24
27
  closed = false;
25
28
  constructor(options = {}) {
26
29
  const policy = new EndpointToolPolicy();
27
30
  this.registry = new EndpointRegistry(policy);
31
+ this.bindings = new EndpointBindingService(this.registry, {
32
+ get: getEndpointSessionBinding,
33
+ set: setEndpointSessionBinding,
34
+ delete: deleteEndpointSessionBinding
35
+ });
28
36
  this.authenticator = new EndpointAuthenticator(options.auth ?? {
29
37
  getPrincipal: getEndpointPrincipal,
30
38
  bindEndpoint: bindEndpointPrincipal,
@@ -26,6 +26,7 @@ const TEXT_EXTENSIONS = new Set([
26
26
  "svg",
27
27
  "ts",
28
28
  "tsx",
29
+ "tsv",
29
30
  "txt",
30
31
  "xml",
31
32
  "yaml",
@@ -50,10 +51,18 @@ const MIME_TYPES = {
50
51
  ts: "text/typescript",
51
52
  tsx: "text/typescript",
52
53
  csv: "text/csv",
54
+ tsv: "text/tab-separated-values",
53
55
  xml: "application/xml",
54
56
  yaml: "application/yaml",
55
57
  yml: "application/yaml",
56
58
  pdf: "application/pdf",
59
+ doc: "application/msword",
60
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
61
+ xls: "application/vnd.ms-excel",
62
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
63
+ ppt: "application/vnd.ms-powerpoint",
64
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
65
+ zip: "application/zip",
57
66
  mp3: "audio/mpeg",
58
67
  wav: "audio/wav",
59
68
  ogg: "audio/ogg",
@@ -106,13 +115,13 @@ async function resolveFilePath(root, path, mustExist = true) {
106
115
  const canonicalRoot = await realpath(root).catch(() => {
107
116
  throw new FileServiceError(404, "File space is unavailable");
108
117
  });
109
- const candidate = resolve(canonicalRoot, normalizeRelativePath(path));
110
- if (!isWithin(canonicalRoot, candidate)) throw new FileServiceError(400, "Invalid file path");
118
+ const candidate = isAbsolute(path) ? resolve(path) : resolve(canonicalRoot, normalizeRelativePath(path));
111
119
  if (!mustExist) {
112
- if (!isWithin(canonicalRoot, await realpath(dirname(candidate)).catch(() => {
120
+ const parent = await realpath(dirname(candidate)).catch(() => {
113
121
  throw new FileServiceError(404, "Parent directory not found");
114
- }))) throw new FileServiceError(400, "Invalid file path");
115
- return candidate;
122
+ });
123
+ if (!isWithin(canonicalRoot, parent)) throw new FileServiceError(400, "Invalid file path");
124
+ return resolve(parent, basename(candidate));
116
125
  }
117
126
  const canonicalTarget = await realpath(candidate).catch(() => {
118
127
  throw new FileServiceError(404, "File not found");
@@ -1,4 +1,6 @@
1
- import { TurnOutcomeDeliverableSchema } from "../../../packages/gateway-contract/src/turn-outcome.js";
1
+ import { TurnOutcomeDeliverableSchema, fileResourceArtifactUri, turnOutcomeKindFromFileName, turnOutcomeMimeTypeFromFileName } from "../../../packages/gateway-contract/src/turn-outcome.js";
2
+ import { parseProductDeliveryEnvelope } from "../../../packages/gateway-contract/src/product-delivery.js";
3
+ import { parseFileResourceId } from "../../files/file-service.js";
2
4
  //#region src/gateway/chat-stream/turn-outcome-collector.ts
3
5
  const VERIFICATION_COMMAND = /(^|[\s:/_-])(test|vitest|jest|pytest|lint|typecheck|type-check|build)([\s:/_-]|$)/i;
4
6
  const MAX_OUTCOME_DIFF_CHARS = 2e5;
@@ -27,13 +29,46 @@ function changedFiles(changes) {
27
29
  }];
28
30
  });
29
31
  }
30
- function structuredDeliverables(details) {
32
+ function explicitArtifacts(details) {
31
33
  if (!Array.isArray(details.artifacts)) return [];
32
34
  return details.artifacts.flatMap((value) => {
33
35
  const parsed = TurnOutcomeDeliverableSchema.safeParse(value);
34
36
  return parsed.success ? [parsed.data] : [];
35
37
  });
36
38
  }
39
+ function fileReferenceDeliverable(reference, operation, environment) {
40
+ if (reference.kind !== "file") return null;
41
+ let workspaceRelativePath;
42
+ try {
43
+ workspaceRelativePath = parseFileResourceId(reference.id).relativePath;
44
+ } catch {}
45
+ const mimeType = turnOutcomeMimeTypeFromFileName(reference.title);
46
+ const available = operation !== "failed";
47
+ const capabilities = available ? [
48
+ ...reference.capabilities.includes("preview") ? ["preview"] : [],
49
+ "download",
50
+ ...reference.capabilities.includes("share") ? ["share"] : []
51
+ ] : ["regenerate"];
52
+ return {
53
+ artifactId: reference.id,
54
+ title: reference.title,
55
+ kind: turnOutcomeKindFromFileName(reference.title),
56
+ ...mimeType ? { mimeType } : {},
57
+ availability: available ? "available" : "failed",
58
+ location: environment,
59
+ capabilities,
60
+ ...available && environment === "workspace" ? { uri: fileResourceArtifactUri(reference.id) } : {},
61
+ ...workspaceRelativePath ? { workspaceRelativePath } : {}
62
+ };
63
+ }
64
+ function productDeliveryArtifacts(delivery, environment) {
65
+ if (!delivery) return [];
66
+ return [delivery.primary, ...delivery.related ?? []].flatMap((reference) => {
67
+ if (!reference) return [];
68
+ const artifact = fileReferenceDeliverable(reference, delivery.operation, environment);
69
+ return artifact ? [artifact] : [];
70
+ });
71
+ }
37
72
  var TurnOutcomeCollector = class {
38
73
  deliverables = /* @__PURE__ */ new Map();
39
74
  evidence = /* @__PURE__ */ new Map();
@@ -52,9 +87,9 @@ var TurnOutcomeCollector = class {
52
87
  this.failedToolCount += 1;
53
88
  return;
54
89
  }
55
- const details = asRecord(event.payload.result?.details);
56
- if (!details) return;
57
- for (const artifact of structuredDeliverables(details)) this.deliverables.set(artifact.artifactId, artifact);
90
+ const details = asRecord(event.payload.result?.details) ?? {};
91
+ const artifacts = [...explicitArtifacts(details), ...productDeliveryArtifacts(parseProductDeliveryEnvelope(details.delivery), this.environment)];
92
+ for (const artifact of artifacts) this.deliverables.set(artifact.artifactId, artifact);
58
93
  return;
59
94
  }
60
95
  if (event.type === "patch_applied") {
@@ -82,7 +117,7 @@ var TurnOutcomeCollector = class {
82
117
  }
83
118
  finalize(status, summary) {
84
119
  const evidence = [...this.evidence.values()];
85
- const partial = this.failedToolCount > 0 || evidence.some((item) => item.status === "failed");
120
+ const partial = this.failedToolCount > 0 || evidence.some((item) => item.status === "failed") || [...this.deliverables.values()].some((item) => item.availability === "failed");
86
121
  const fullDiff = this.diffs.join("\n");
87
122
  const diffTruncated = fullDiff.length > MAX_OUTCOME_DIFF_CHARS;
88
123
  return {
@@ -1,9 +1,9 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
3
  import { shouldSilence, stripHeartbeatToken } from "../../heartbeat/tokens.js";
4
- import { appendCronEventLines } from "../../heartbeat/event-prompt.js";
5
4
  import { isWithinActiveHours } from "../../heartbeat/active-hours.js";
6
5
  import { isHeartbeatContentEmpty } from "../../heartbeat/content-check.js";
6
+ import { appendCronEventLines } from "../../heartbeat/event-prompt.js";
7
7
  import { createHeartbeatWake } from "../../heartbeat/wake.js";
8
8
  import { resolveHeartbeatMdPath } from "../workspace-heartbeat-path.js";
9
9
  import { readFile } from "fs/promises";
@@ -31,7 +31,7 @@ function extractTokenFromHeader(authHeader) {
31
31
  }
32
32
  /**
33
33
  * SECURITY: query-string tokens leak into server logs, Referer headers, and
34
- * browser history. We accept them only for agent avatar `<img>` loads, where an
34
+ * browser history. We accept them only for avatar `<img>` loads, where an
35
35
  * Authorization header cannot be set. Realtime WebSocket authentication uses a
36
36
  * one-time ticket acquired over authenticated HTTP.
37
37
  */
@@ -39,9 +39,10 @@ function extractTokenFromQuery(url) {
39
39
  return new URL(url).searchParams.get("token");
40
40
  }
41
41
  const AGENT_AVATAR_GET_PATH = /^\/api\/agents\/[^/]+\/avatar$/;
42
+ const USER_AVATAR_GET_PATH = "/api/you/avatar";
42
43
  /** Exported for gateway security tests. */
43
44
  function isQueryTokenAllowedPath(path, method) {
44
- if (method === "GET" && AGENT_AVATAR_GET_PATH.test(path)) return true;
45
+ if (method === "GET" && (AGENT_AVATAR_GET_PATH.test(path) || path === USER_AVATAR_GET_PATH)) return true;
45
46
  return false;
46
47
  }
47
48
  function resolveRemoteAddress(c) {
@@ -1,4 +1,4 @@
1
- import { endpointPrincipalRegistrationSchema } from "../../../../packages/endpoint-tools-protocol/src/index.js";
1
+ import { endpointPrincipalRegistrationSchema, endpointSessionBindingRequestSchema } from "../../../../packages/endpoint-tools-protocol/src/index.js";
2
2
  import { createEndpointPrincipal, getEndpointPrincipal, listEndpointPrincipals, revokeEndpointPrincipal } from "../../../storage/sqlite/endpoint-principal-repository.js";
3
3
  import { listEndpointToolInvocationAudits } from "../../../storage/sqlite/endpoint-tool-invocation-repository.js";
4
4
  import "../../../storage/sqlite/index.js";
@@ -130,6 +130,73 @@ function registerEndpointToolRoutes(authenticated, deps) {
130
130
  payload: listEndpointToolInvocationAudits(Number.isFinite(limit) ? limit : 100)
131
131
  });
132
132
  });
133
+ authenticated.get("/api/endpoint-tools/bindings/:sessionKey", (c) => {
134
+ try {
135
+ const binding = deps.service.endpointTools.bindings.get(c.req.param("sessionKey"));
136
+ return binding ? c.json({
137
+ ok: true,
138
+ payload: binding
139
+ }) : c.json({
140
+ ok: false,
141
+ error: {
142
+ code: "NOT_FOUND",
143
+ message: "Endpoint binding not found"
144
+ }
145
+ }, 404);
146
+ } catch {
147
+ return c.json({
148
+ ok: false,
149
+ error: {
150
+ code: "BAD_REQUEST",
151
+ message: "Invalid session key"
152
+ }
153
+ }, 400);
154
+ }
155
+ });
156
+ authenticated.put("/api/endpoint-tools/bindings/:sessionKey", async (c) => {
157
+ const parsed = endpointSessionBindingRequestSchema.safeParse(await c.req.json().catch(() => null));
158
+ if (!parsed.success) return c.json({
159
+ ok: false,
160
+ error: {
161
+ code: "BAD_REQUEST",
162
+ message: "Invalid endpoint binding"
163
+ }
164
+ }, 400);
165
+ try {
166
+ const binding = deps.service.endpointTools.bindings.bind(c.req.param("sessionKey"), parsed.data.endpointId);
167
+ return c.json({
168
+ ok: true,
169
+ payload: binding
170
+ });
171
+ } catch (error) {
172
+ const message = error instanceof Error ? error.message : String(error);
173
+ const status = message === "Endpoint is offline" ? 409 : 400;
174
+ return c.json({
175
+ ok: false,
176
+ error: {
177
+ code: "BINDING_FAILED",
178
+ message
179
+ }
180
+ }, status);
181
+ }
182
+ });
183
+ authenticated.delete("/api/endpoint-tools/bindings/:sessionKey", (c) => {
184
+ try {
185
+ const removed = deps.service.endpointTools.bindings.unbind(c.req.param("sessionKey"));
186
+ return c.json({
187
+ ok: true,
188
+ payload: { removed }
189
+ });
190
+ } catch {
191
+ return c.json({
192
+ ok: false,
193
+ error: {
194
+ code: "BAD_REQUEST",
195
+ message: "Invalid session key"
196
+ }
197
+ }, 400);
198
+ }
199
+ });
133
200
  authenticated.post("/api/endpoint-tools/invocations/:invocationId/files", async (c) => {
134
201
  const invocationId = c.req.param("invocationId");
135
202
  const endpointId = c.req.header("x-endpoint-id") ?? "";