@xopcai/xopc 0.0.229 → 0.0.231

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 (155) 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/{agent-avatar-dicebear-CsFAJTIN.js → agent-avatar-dicebear-CBqUnlvL.js} +1 -1
  4. package/dist/gateway/static/root/assets/agents-7YO6znTT.js +68 -0
  5. package/dist/gateway/static/root/assets/{app-management-settings-panel-7KcMWuah.js → app-management-settings-panel-DP7YVRPn.js} +1 -1
  6. package/dist/gateway/static/root/assets/{appearance-settings-Cqlw7Fif.js → appearance-settings-CVQ5n7y6.js} +1 -1
  7. package/dist/gateway/static/root/assets/{apps-page-CsnhdGZ2.js → apps-page-DSCia0DT.js} +1 -1
  8. package/dist/gateway/static/root/assets/{attachment-load-q4pDpqkF.js → attachment-load-mmr_mHRr.js} +1 -1
  9. package/dist/gateway/static/root/assets/{automations-page-BuTqjKGX.js → automations-page-DfY-T6Ga.js} +1 -1
  10. package/dist/gateway/static/root/assets/automations-workspace-BpvD54D6.js +6 -0
  11. package/dist/gateway/static/root/assets/{binary-plugins-DUce1Bsc.js → binary-plugins-CO9QMEh2.js} +1 -1
  12. package/dist/gateway/static/root/assets/{block-editor-D8Ejp-QR.js → block-editor-CE14CVOy.js} +3 -3
  13. package/dist/gateway/static/root/assets/browser-settings-page-CeESA4Oj.js +8 -0
  14. package/dist/gateway/static/root/assets/{browser-workflows-page-BDvZbR9v.js → browser-workflows-page-1wgXyoSO.js} +2 -2
  15. package/dist/gateway/static/root/assets/capabilities-settings-panel-B_OXTojB.js +7 -0
  16. package/dist/gateway/static/root/assets/capability-presets-settings-panel-D5nZecxV.js +3 -0
  17. package/dist/gateway/static/root/assets/channels-settings-BzmnWx6X.js +1 -0
  18. package/dist/gateway/static/root/assets/{chat-terminal-dock-h1CKexNz.js → chat-terminal-dock-CdRW6Euc.js} +2 -2
  19. package/dist/gateway/static/root/assets/cloud-C4-yfnnk.js +1 -0
  20. package/dist/gateway/static/root/assets/connectors-page-DX6OkwFQ.js +2 -0
  21. package/dist/gateway/static/root/assets/{cron-expression-DlaQQOId.js → cron-expression-DYSMZsms.js} +1 -1
  22. package/dist/gateway/static/root/assets/{date-picker-8R1sXJKb.js → date-picker-DGCfX4tK.js} +1 -1
  23. package/dist/gateway/static/root/assets/{dependency-picker-CWur0821.js → dependency-picker-DHDa4ht1.js} +1 -1
  24. package/dist/gateway/static/root/assets/{desktop-pet-Knl-zNDU.js → desktop-pet-CMO-J3cG.js} +1 -1
  25. package/dist/gateway/static/root/assets/{desktop-pet-settings-CAQMLVtd.js → desktop-pet-settings-MmOR4A3u.js} +1 -1
  26. package/dist/gateway/static/root/assets/{directory-picker-path-field-Caaz4Rw5.js → directory-picker-path-field-B9qoxDcn.js} +1 -1
  27. package/dist/gateway/static/root/assets/{extension-debug-page-B965Mc15.js → extension-debug-page-DdH0dG2_.js} +1 -1
  28. package/dist/gateway/static/root/assets/{extension-page-C0UnkBi5.js → extension-page-Clg-KktN.js} +1 -1
  29. package/dist/gateway/static/root/assets/{extension-settings-page-BUSIAZCR.js → extension-settings-page-eP3fJ2M6.js} +1 -1
  30. package/dist/gateway/static/root/assets/files-Dp8_uGTO.js +1 -0
  31. package/dist/gateway/static/root/assets/{gateway-config-api-DkH0eEgy.js → gateway-config-api-DEHnF4Wm.js} +1 -1
  32. package/dist/gateway/static/root/assets/{gateway-settings-BBgLDm6I.js → gateway-settings-DKkEXXj9.js} +1 -1
  33. package/dist/gateway/static/root/assets/{heartbeat-settings-BtEHVXWD.js → heartbeat-settings-DlBPNubY.js} +1 -1
  34. package/dist/gateway/static/root/assets/home-page-BnOrLRfs.js +1 -0
  35. package/dist/gateway/static/root/assets/index-0xQO5B_Q.js +70 -0
  36. package/dist/gateway/static/root/assets/index-CCyIdGuQ.css +2 -0
  37. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-CsRz8NrS.js → keyboard-shortcuts-settings-BvpEeHnm.js} +1 -1
  38. package/dist/gateway/static/root/assets/local-app-workbench-page-Ch1rdUo8.js +2 -0
  39. package/dist/gateway/static/root/assets/{local-apps-page-CTBNUG4a.js → local-apps-page-Bzvn99eH.js} +1 -1
  40. package/dist/gateway/static/root/assets/logs-page-zxx2ExlY.js +2 -0
  41. package/dist/gateway/static/root/assets/{management-settings-YqUAEwQo.js → management-settings-BQaeyOYm.js} +1 -1
  42. package/dist/gateway/static/root/assets/{markdown-split-dxpSCXb8.js → markdown-split-D9tmIiEm.js} +1 -1
  43. package/dist/gateway/static/root/assets/markdown-view-B1bGSQqt.css +1 -0
  44. package/dist/gateway/static/root/assets/{media-plugins-CJ00i1Lg.js → media-plugins-D8I7OGnT.js} +1 -1
  45. package/dist/gateway/static/root/assets/models-hub-cache-0RjEwafI.js +1 -0
  46. package/dist/gateway/static/root/assets/{notes-api-BFsOGYkd.js → notes-api-EpgG-OQc.js} +1 -1
  47. package/dist/gateway/static/root/assets/{notes-page-DEnbnkIt.js → notes-page-CCvjGMhN.js} +1 -1
  48. package/dist/gateway/static/root/assets/notes-workbench-9cgh77VH.js +4 -0
  49. package/dist/gateway/static/root/assets/oauth-api-BwNLwbI8.js +1 -0
  50. package/dist/gateway/static/root/assets/{product-open-page-BHxo9U95.js → product-open-page-MmMswmOU.js} +1 -1
  51. package/dist/gateway/static/root/assets/project-detail-page-hf6pmH4p.js +1 -0
  52. package/dist/gateway/static/root/assets/projects-page-By2U-Wdh.js +1 -0
  53. package/dist/gateway/static/root/assets/remote-access-hub-CUcpp7CQ.js +1 -0
  54. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-BpQ2GI-0.js → runtime-tools-settings-panel-Dx6o7sLu.js} +1 -1
  55. package/dist/gateway/static/root/assets/{schema-form-HE1G26Lx.js → schema-form-Cdd9c4vt.js} +1 -1
  56. package/dist/gateway/static/root/assets/sessions-page-ByI82xS1.js +1 -0
  57. package/dist/gateway/static/root/assets/settings-advanced-gate-BCoI5IJ8.js +1 -0
  58. package/dist/gateway/static/root/assets/{settings-form-section-CXAn0Xhy.js → settings-form-section-D0YxMnhm.js} +1 -1
  59. package/dist/gateway/static/root/assets/{settings-loading-skeleton-DsFu7Bcv.js → settings-loading-skeleton-y53gEKsF.js} +1 -1
  60. package/dist/gateway/static/root/assets/{settings-page-C4iib9nO.js → settings-page-BoE_tToG.js} +1 -1
  61. package/dist/gateway/static/root/assets/{setup-status-panel-BA83Wr1d.js → setup-status-panel-BQmyb5KC.js} +1 -1
  62. package/dist/gateway/static/root/assets/share-preview-page-DiqBn-oE.js +2 -0
  63. package/dist/gateway/static/root/assets/shares-settings-Bf3VpZ-R.js +1 -0
  64. package/dist/gateway/static/root/assets/skill-reload-api-CXHlbuGS.js +1 -0
  65. package/dist/gateway/static/root/assets/{skills-page-CdaFtNw9.js → skills-page-CZwhWiRl.js} +1 -1
  66. package/dist/gateway/static/root/assets/{skills-page.utils-DYaAddUL.js → skills-page.utils-kBGr8opZ.js} +1 -1
  67. package/dist/gateway/static/root/assets/{system-settings-panel-Co7gilRd.js → system-settings-panel-CPBs6lWi.js} +1 -1
  68. package/dist/gateway/static/root/assets/task-detail-page-Bo0KSZ2v.js +1 -0
  69. package/dist/gateway/static/root/assets/theme-store-Qgvo0Ga7.js +1 -0
  70. package/dist/gateway/static/root/assets/{use-autosave-DnnZtMpA.js → use-autosave-C4YcEiP9.js} +1 -1
  71. package/dist/gateway/static/root/assets/user-context-page-Cnj7gMX2.js +1 -0
  72. package/dist/gateway/static/root/assets/{voice-api-key-field-wetUpi3i.js → voice-api-key-field-BML8zCwZ.js} +1 -1
  73. package/dist/gateway/static/root/assets/{work-discovery-overlay-tbTWASJJ.js → work-discovery-overlay-CKIdPEen.js} +1 -1
  74. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Dy0fDs8w.js → workflow-run-setup-panel-BK44Vm5W.js} +2 -2
  75. package/dist/gateway/static/root/assets/workflows-page-CB98MTwG.js +5 -0
  76. package/dist/gateway/static/root/index.html +10 -6
  77. package/dist/package.js +1 -1
  78. package/dist/src/agent/inbound/turn-dispatcher.js +1 -1
  79. package/dist/src/agent/service/process-direct-streaming.js +4 -1
  80. package/dist/src/agent/service/webchat-tts.d.ts +2 -0
  81. package/dist/src/agent/service/webchat-tts.js +15 -2
  82. package/dist/src/agent/service.d.ts +1 -0
  83. package/dist/src/agent/service.js +5 -2
  84. package/dist/src/agent/skills/skill-manage-ops.js +1 -1
  85. package/dist/src/agent/tools/tts-tool.js +19 -13
  86. package/dist/src/agent/tools/write.js +9 -12
  87. package/dist/src/agent/tools/xopc-use-tool.js +2 -2
  88. package/dist/src/auth/oauth/index.js +2 -2
  89. package/dist/src/auth/oauth/registry.d.ts +1 -1
  90. package/dist/src/auth/oauth/registry.js +5 -1
  91. package/dist/src/auth/oauth/xopc-cloud.d.ts +1 -0
  92. package/dist/src/auth/oauth/xopc-cloud.js +8 -2
  93. package/dist/src/files/file-service.d.ts +36 -0
  94. package/dist/src/files/file-service.js +282 -0
  95. package/dist/src/gateway/chat-stream/mapper.js +11 -0
  96. package/dist/src/gateway/heartbeat/service.js +1 -1
  97. package/dist/src/gateway/hono/app.js +2 -2
  98. package/dist/src/gateway/hono/routes/files.d.ts +3 -0
  99. package/dist/src/gateway/hono/routes/files.js +172 -0
  100. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  101. package/dist/src/gateway/hono/routes/projects.js +0 -508
  102. package/dist/src/gateway/hono/routes/shares.js +410 -21
  103. package/dist/src/gateway/hono/routes/workspace.js +15 -974
  104. package/dist/src/gateway/service.js +9 -9
  105. package/dist/src/heartbeat/index.js +1 -1
  106. package/dist/src/share/hosted-session-share.d.ts +104 -0
  107. package/dist/src/share/hosted-session-share.js +276 -0
  108. package/dist/src/share/note-share-service.js +4 -24
  109. package/dist/src/share/session-share-projector.d.ts +31 -0
  110. package/dist/src/share/session-share-projector.js +79 -0
  111. package/dist/src/share/session-share-service.d.ts +89 -0
  112. package/dist/src/share/session-share-service.js +325 -0
  113. package/dist/src/share/share-asset-ticket.d.ts +7 -0
  114. package/dist/src/share/share-asset-ticket.js +30 -0
  115. package/dist/src/share/share-landing.d.ts +4 -3
  116. package/dist/src/share/share-landing.js +5 -5
  117. package/dist/src/share/share-store.d.ts +27 -2
  118. package/dist/src/share/share-store.js +71 -8
  119. package/dist/src/share/share-types.d.ts +17 -2
  120. package/dist/src/tui/backends/embedded-backend.js +1 -1
  121. package/dist/src/tui/backends/gateway-realtime-backend.js +11 -3
  122. package/package.json +1 -1
  123. package/dist/gateway/static/root/assets/agents-DA4b6_0C.js +0 -68
  124. package/dist/gateway/static/root/assets/automations-workspace-CD0E2ThS.js +0 -6
  125. package/dist/gateway/static/root/assets/browser-settings-page-CY1k3MMd.js +0 -8
  126. package/dist/gateway/static/root/assets/capabilities-settings-panel-D1VfotHQ.js +0 -7
  127. package/dist/gateway/static/root/assets/capability-presets-settings-panel-DXHqRLpU.js +0 -3
  128. package/dist/gateway/static/root/assets/channels-settings-DrFL57M_.js +0 -1
  129. package/dist/gateway/static/root/assets/connectors-page-DUAyfFCF.js +0 -2
  130. package/dist/gateway/static/root/assets/home-page-5SU2TNJj.js +0 -1
  131. package/dist/gateway/static/root/assets/index-BNWhgmjs.js +0 -70
  132. package/dist/gateway/static/root/assets/index-D_hrEKXM.css +0 -2
  133. package/dist/gateway/static/root/assets/local-app-workbench-page-DYPEUgpP.js +0 -2
  134. package/dist/gateway/static/root/assets/logs-page-B0P7rG2B.js +0 -2
  135. package/dist/gateway/static/root/assets/markdown-view-CLzTdVLW.css +0 -1
  136. package/dist/gateway/static/root/assets/notes-workbench-DmVYu_Oc.js +0 -4
  137. package/dist/gateway/static/root/assets/project-detail-page-DV2nGmsq.js +0 -1
  138. package/dist/gateway/static/root/assets/projects-page-BNIFwFfc.js +0 -1
  139. package/dist/gateway/static/root/assets/remote-access-hub-DLXqKiA_.js +0 -1
  140. package/dist/gateway/static/root/assets/sessions-page-DWaXf_25.js +0 -1
  141. package/dist/gateway/static/root/assets/settings-advanced-gate-JFCbnCL2.js +0 -1
  142. package/dist/gateway/static/root/assets/share-preview-page-BOGl3s6M.js +0 -2
  143. package/dist/gateway/static/root/assets/shares-settings-Q2MdGaBi.js +0 -1
  144. package/dist/gateway/static/root/assets/skill-reload-api-cFtfVeFU.js +0 -1
  145. package/dist/gateway/static/root/assets/task-detail-page-5B1pswu8.js +0 -1
  146. package/dist/gateway/static/root/assets/theme-store-DOdCjyuw.js +0 -1
  147. package/dist/gateway/static/root/assets/user-context-page-Dr1FKjXv.js +0 -1
  148. package/dist/gateway/static/root/assets/workflows-page-IVP3GSkI.js +0 -5
  149. package/dist/src/gateway/file-path-classifier.d.ts +0 -30
  150. package/dist/src/gateway/file-path-classifier.js +0 -105
  151. package/dist/src/gateway/file-reference-errors.d.ts +0 -6
  152. package/dist/src/gateway/file-reference-errors.js +0 -21
  153. package/dist/src/gateway/file-reference-registry.d.ts +0 -34
  154. package/dist/src/gateway/file-reference-registry.js +0 -45
  155. /package/dist/gateway/static/root/assets/{markdown-view-DvM5PWEH.js → markdown-view-RxNHwlNo.js} +0 -0
@@ -0,0 +1,282 @@
1
+ import { init_agent_scope, listAgentEntries, resolveAgentWorkspaceDir } from "../agent/agent-scope.js";
2
+ import { resolveProjectAgentId } from "../projects/project-agent.js";
3
+ import "../projects/index.js";
4
+ import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
5
+ import { constants } from "node:fs";
6
+ import { createHash } from "node:crypto";
7
+ import { access, lstat, opendir, realpath, stat } from "node:fs/promises";
8
+ //#region src/files/file-service.ts
9
+ init_agent_scope();
10
+ const SKIPPED_NAMES = new Set([
11
+ ".DS_Store",
12
+ ".git",
13
+ "node_modules"
14
+ ]);
15
+ const TEXT_EXTENSIONS = new Set([
16
+ "css",
17
+ "csv",
18
+ "html",
19
+ "htm",
20
+ "js",
21
+ "json",
22
+ "jsx",
23
+ "md",
24
+ "mjs",
25
+ "cjs",
26
+ "svg",
27
+ "ts",
28
+ "tsx",
29
+ "txt",
30
+ "xml",
31
+ "yaml",
32
+ "yml"
33
+ ]);
34
+ const MIME_TYPES = {
35
+ png: "image/png",
36
+ jpg: "image/jpeg",
37
+ jpeg: "image/jpeg",
38
+ gif: "image/gif",
39
+ webp: "image/webp",
40
+ svg: "image/svg+xml",
41
+ md: "text/markdown",
42
+ txt: "text/plain",
43
+ json: "application/json",
44
+ html: "text/html",
45
+ htm: "text/html",
46
+ css: "text/css",
47
+ js: "text/javascript",
48
+ mjs: "text/javascript",
49
+ cjs: "text/javascript",
50
+ ts: "text/typescript",
51
+ tsx: "text/typescript",
52
+ csv: "text/csv",
53
+ xml: "application/xml",
54
+ yaml: "application/yaml",
55
+ yml: "application/yaml",
56
+ pdf: "application/pdf",
57
+ mp3: "audio/mpeg",
58
+ wav: "audio/wav",
59
+ ogg: "audio/ogg",
60
+ m4a: "audio/mp4",
61
+ mp4: "video/mp4",
62
+ mov: "video/quicktime",
63
+ webm: "video/webm"
64
+ };
65
+ function isWithin(root, target) {
66
+ const rel = relative(root, target);
67
+ return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel);
68
+ }
69
+ function normalizeRelativePath(input) {
70
+ const normalized = input.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
71
+ if (normalized.includes("\0") || normalized.split("/").some((part) => part === "..")) throw new FileServiceError(400, "Invalid file path");
72
+ return normalized.split("/").filter((part) => part && part !== ".").join("/");
73
+ }
74
+ function mimeType(name, directory) {
75
+ if (directory) return "inode/directory";
76
+ return MIME_TYPES[name.includes(".") ? name.split(".").pop().toLowerCase() : ""] ?? "application/octet-stream";
77
+ }
78
+ function capabilities(name, directory, writable) {
79
+ if (directory) return writable ? ["upload"] : [];
80
+ const extension = name.includes(".") ? name.split(".").pop().toLowerCase() : "";
81
+ const result = ["download", "share"];
82
+ if (MIME_TYPES[extension] || TEXT_EXTENSIONS.has(extension)) result.unshift("preview");
83
+ if (writable && TEXT_EXTENSIONS.has(extension)) result.push("edit");
84
+ return result;
85
+ }
86
+ function fileSpaceId(root) {
87
+ return createHash("sha256").update(root).digest("base64url").slice(0, 24);
88
+ }
89
+ function fileResourceId(spaceId, relativePath) {
90
+ return `${spaceId}.${Buffer.from(normalizeRelativePath(relativePath)).toString("base64url")}`;
91
+ }
92
+ function parseFileResourceId(id) {
93
+ const separator = id.indexOf(".");
94
+ if (separator <= 0) throw new FileServiceError(400, "Invalid file id");
95
+ try {
96
+ return {
97
+ spaceId: id.slice(0, separator),
98
+ relativePath: normalizeRelativePath(Buffer.from(id.slice(separator + 1), "base64url").toString("utf8"))
99
+ };
100
+ } catch (error) {
101
+ if (error instanceof FileServiceError) throw error;
102
+ throw new FileServiceError(400, "Invalid file id");
103
+ }
104
+ }
105
+ async function resolveFilePath(root, path, mustExist = true) {
106
+ const canonicalRoot = await realpath(root).catch(() => {
107
+ throw new FileServiceError(404, "File space is unavailable");
108
+ });
109
+ const candidate = resolve(canonicalRoot, normalizeRelativePath(path));
110
+ if (!isWithin(canonicalRoot, candidate)) throw new FileServiceError(400, "Invalid file path");
111
+ if (!mustExist) {
112
+ if (!isWithin(canonicalRoot, await realpath(dirname(candidate)).catch(() => {
113
+ throw new FileServiceError(404, "Parent directory not found");
114
+ }))) throw new FileServiceError(400, "Invalid file path");
115
+ return candidate;
116
+ }
117
+ const canonicalTarget = await realpath(candidate).catch(() => {
118
+ throw new FileServiceError(404, "File not found");
119
+ });
120
+ if (!isWithin(canonicalRoot, canonicalTarget)) throw new FileServiceError(400, "File is outside its space");
121
+ return canonicalTarget;
122
+ }
123
+ var FileServiceError = class extends Error {
124
+ constructor(status, message) {
125
+ super(message);
126
+ this.status = status;
127
+ }
128
+ };
129
+ async function fileResourceFromPath(space, absolutePath) {
130
+ const info = await stat(absolutePath);
131
+ const revisionInfo = await stat(absolutePath, { bigint: true });
132
+ const relativePath = relative(await realpath(space.root), absolutePath).split(sep).join("/");
133
+ const name = basename(absolutePath);
134
+ const directory = info.isDirectory();
135
+ return {
136
+ id: fileResourceId(space.id, relativePath),
137
+ spaceId: space.id,
138
+ name,
139
+ relativePath,
140
+ parentPath: dirname(relativePath).split(sep).join("/").replace(/^\.$/, ""),
141
+ kind: directory ? "directory" : "file",
142
+ mimeType: mimeType(name, directory),
143
+ size: info.size,
144
+ modifiedAt: Math.max(0, Math.round(info.mtimeMs)),
145
+ revision: `${revisionInfo.mtimeNs}:${revisionInfo.ctimeNs}:${revisionInfo.ino}:${revisionInfo.size}`,
146
+ capabilities: capabilities(name, directory, space.writable)
147
+ };
148
+ }
149
+ var FileSpaceService = class {
150
+ dynamicSpaces = /* @__PURE__ */ new Map();
151
+ cache = null;
152
+ constructor(getConfig, projects, resolveSessionWorkspace) {
153
+ this.getConfig = getConfig;
154
+ this.projects = projects;
155
+ this.resolveSessionWorkspace = resolveSessionWorkspace;
156
+ }
157
+ async createSpace(root, title, bindings) {
158
+ const canonicalRoot = await realpath(root).catch(() => null);
159
+ if (!canonicalRoot) return null;
160
+ const rootStat = await stat(canonicalRoot).catch(() => null);
161
+ if (!rootStat?.isDirectory()) return null;
162
+ const writable = await access(canonicalRoot, constants.W_OK).then(() => true).catch(() => false);
163
+ return {
164
+ id: fileSpaceId(canonicalRoot),
165
+ title,
166
+ kind: "workspace",
167
+ bindings,
168
+ writable,
169
+ root: canonicalRoot,
170
+ lastActivityAt: Math.max(0, Math.round(rootStat.mtimeMs))
171
+ };
172
+ }
173
+ async list() {
174
+ if (this.cache && Date.now() - this.cache.at < 5e3) return this.cache.spaces;
175
+ const config = this.getConfig();
176
+ const spaces = [];
177
+ for (const agent of listAgentEntries(config)) {
178
+ const space = await this.createSpace(resolveAgentWorkspaceDir(config, agent.id), agent.id, [{
179
+ kind: "agent",
180
+ id: agent.id
181
+ }]);
182
+ if (space) spaces.push(space);
183
+ }
184
+ let offset = 0;
185
+ while (true) {
186
+ const page = this.projects.list({
187
+ limit: 500,
188
+ offset
189
+ });
190
+ for (const project of page.items) {
191
+ const agentId = resolveProjectAgentId({
192
+ config,
193
+ projects: this.projects,
194
+ projectId: project.id
195
+ });
196
+ const root = project.workspaceRoot ?? resolveAgentWorkspaceDir(config, agentId);
197
+ const space = await this.createSpace(root, project.name, [{
198
+ kind: "project",
199
+ id: project.id
200
+ }]);
201
+ if (space) spaces.push(space);
202
+ }
203
+ if (!page.hasMore || page.items.length === 0) break;
204
+ offset += page.items.length;
205
+ }
206
+ const merged = /* @__PURE__ */ new Map();
207
+ for (const space of [...spaces, ...this.dynamicSpaces.values()]) {
208
+ const existing = merged.get(space.id);
209
+ if (existing) existing.bindings = [...existing.bindings, ...space.bindings.filter((binding) => !existing.bindings.some((item) => item.kind === binding.kind && item.id === binding.id))];
210
+ else merged.set(space.id, {
211
+ ...space,
212
+ bindings: [...space.bindings]
213
+ });
214
+ }
215
+ const result = [...merged.values()].sort((a, b) => (b.lastActivityAt ?? 0) - (a.lastActivityAt ?? 0));
216
+ this.cache = {
217
+ at: Date.now(),
218
+ spaces: result
219
+ };
220
+ return result;
221
+ }
222
+ async forContext(kind, id) {
223
+ let root;
224
+ let title = id;
225
+ if (kind === "agent") root = resolveAgentWorkspaceDir(this.getConfig(), id);
226
+ if (kind === "project") {
227
+ const project = this.projects.get(id);
228
+ if (project) {
229
+ const config = this.getConfig();
230
+ const agentId = resolveProjectAgentId({
231
+ config,
232
+ projects: this.projects,
233
+ projectId: id
234
+ });
235
+ root = project.workspaceRoot ?? resolveAgentWorkspaceDir(config, agentId);
236
+ }
237
+ title = project?.name ?? id;
238
+ }
239
+ if (kind === "session") root = this.resolveSessionWorkspace(id);
240
+ if (!root) throw new FileServiceError(404, "File space not found");
241
+ const space = await this.createSpace(root, title, [{
242
+ kind,
243
+ id
244
+ }]);
245
+ if (!space) throw new FileServiceError(404, "File space is unavailable");
246
+ this.dynamicSpaces.set(space.id, space);
247
+ this.cache = null;
248
+ return space;
249
+ }
250
+ async get(id) {
251
+ const space = (await this.list()).find((item) => item.id === id);
252
+ if (!space) throw new FileServiceError(404, "File space not found");
253
+ return space;
254
+ }
255
+ async resource(id) {
256
+ const locator = parseFileResourceId(id);
257
+ const space = await this.get(locator.spaceId);
258
+ const absolutePath = await resolveFilePath(space.root, locator.relativePath);
259
+ return {
260
+ space,
261
+ resource: await fileResourceFromPath(space, absolutePath),
262
+ absolutePath
263
+ };
264
+ }
265
+ async children(spaceId, path = "") {
266
+ const space = await this.get(spaceId);
267
+ const directory = await resolveFilePath(space.root, path);
268
+ if (!(await stat(directory)).isDirectory()) throw new FileServiceError(400, "Path is not a directory");
269
+ const entries = [];
270
+ const dir = await opendir(directory);
271
+ for await (const entry of dir) {
272
+ if (SKIPPED_NAMES.has(entry.name)) continue;
273
+ const target = resolve(directory, entry.name);
274
+ const linkInfo = await lstat(target).catch(() => null);
275
+ if (!linkInfo || linkInfo.isSymbolicLink()) continue;
276
+ entries.push(await fileResourceFromPath(space, target));
277
+ }
278
+ return entries.sort((a, b) => a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind === "directory" ? -1 : 1);
279
+ }
280
+ };
281
+ //#endregion
282
+ export { FileServiceError, FileSpaceService, fileResourceFromPath, fileResourceId, fileSpaceId, parseFileResourceId, resolveFilePath };
@@ -342,6 +342,17 @@ var ChatStreamMapper = class {
342
342
  errorMessage: event.isError ? result?.text ?? extractText(result?.content) : void 0
343
343
  })];
344
344
  const details = asRecord(fullResult?.details);
345
+ if (toolName === "text_to_speech" && !event.isError && details) {
346
+ const audio = (Array.isArray(details.media) ? details.media : []).map(asRecord).find((item) => item?.type === "voice" || String(item?.mimeType ?? "").startsWith("audio/"));
347
+ const uri = typeof audio?.uri === "string" ? audio.uri.trim() : "";
348
+ if (uri) events.push(this.make("tts_audio", {
349
+ uri,
350
+ mimeType: typeof audio?.mimeType === "string" ? audio.mimeType : "audio/mpeg",
351
+ name: typeof audio?.name === "string" ? audio.name : "voice.mp3",
352
+ attachTo: "last_assistant",
353
+ messageId
354
+ }));
355
+ }
345
356
  if (toolName === "exec_command" && details) events.push(this.make("command_completed", {
346
357
  messageId,
347
358
  toolCallId,
@@ -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";
4
5
  import { isWithinActiveHours } from "../../heartbeat/active-hours.js";
5
6
  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";
@@ -155,7 +155,7 @@ function createHonoApp(config) {
155
155
  if (/^\/api\/notes\/[^/]+\/media$/.test(path)) return true;
156
156
  return path === "/api/notes" && contentType?.includes("multipart/form-data") === true;
157
157
  };
158
- const isProjectFileUploadRequest = (path, method, contentType) => method === "POST" && /^\/api\/projects\/[^/]+\/files\/upload$/.test(path) && contentType?.includes("multipart/form-data") === true;
158
+ const isFileUploadRequest = (path, method, contentType) => method === "POST" && /^\/api\/files\/spaces\/[^/]+\/upload$/.test(path) && contentType?.includes("multipart/form-data") === true;
159
159
  const discussionUploadBodyMax = (path, method, contentType) => {
160
160
  if (method !== "PUT" || contentType?.includes("multipart/form-data") !== true) return void 0;
161
161
  if (/^\/api\/discussions\/[^/]+\/recording$/.test(path)) return NOTE_MEDIA_BODY_MAX;
@@ -176,7 +176,7 @@ function createHonoApp(config) {
176
176
  }));
177
177
  app.use("/api/*", async (c, next) => {
178
178
  const contentType = c.req.header("content-type");
179
- const maxSize = c.req.method === "POST" && c.req.path === "/api/tasks" || c.req.method === "POST" && /^\/api\/sessions\/[^/]+\/inputs$/.test(c.req.path) || c.req.method === "POST" && /^\/api\/tasks\/[^/]+\/inputs$/.test(c.req.path) || c.req.method === "PATCH" && /^\/api\/sessions\/[^/]+\/inputs\/[^/]+$/.test(c.req.path) ? SESSION_INPUT_BODY_MAX : c.req.path === "/api/skills/upload" ? SKILL_UPLOAD_BODY_MAX : c.req.path === "/api/voice/transcriptions" ? VOICE_TRANSCRIBE_BODY_MAX : c.req.path === "/api/media" && c.req.method === "POST" ? MEDIA_UPLOAD_BODY_MAX : isNoteMediaUploadRequest(c.req.path, c.req.method, contentType) ? NOTE_MEDIA_BODY_MAX : isProjectFileUploadRequest(c.req.path, c.req.method, contentType) ? NOTE_MEDIA_BODY_MAX : discussionUploadBodyMax(c.req.path, c.req.method, contentType) ?? DEFAULT_API_BODY_MAX;
179
+ const maxSize = c.req.method === "POST" && c.req.path === "/api/tasks" || c.req.method === "POST" && /^\/api\/sessions\/[^/]+\/inputs$/.test(c.req.path) || c.req.method === "POST" && /^\/api\/tasks\/[^/]+\/inputs$/.test(c.req.path) || c.req.method === "PATCH" && /^\/api\/sessions\/[^/]+\/inputs\/[^/]+$/.test(c.req.path) ? SESSION_INPUT_BODY_MAX : c.req.path === "/api/skills/upload" ? SKILL_UPLOAD_BODY_MAX : c.req.path === "/api/voice/transcriptions" ? VOICE_TRANSCRIBE_BODY_MAX : c.req.path === "/api/media" && c.req.method === "POST" ? MEDIA_UPLOAD_BODY_MAX : isNoteMediaUploadRequest(c.req.path, c.req.method, contentType) ? NOTE_MEDIA_BODY_MAX : isFileUploadRequest(c.req.path, c.req.method, contentType) ? NOTE_MEDIA_BODY_MAX : discussionUploadBodyMax(c.req.path, c.req.method, contentType) ?? DEFAULT_API_BODY_MAX;
180
180
  const maxSizeMb = Math.ceil(maxSize / (1024 * 1024));
181
181
  return bodyLimit({
182
182
  maxSize,
@@ -0,0 +1,3 @@
1
+ import type { Hono } from 'hono';
2
+ import type { AuthenticatedRouteDeps } from './deps.js';
3
+ export declare function registerFilesRoutes(authenticated: Hono, deps: AuthenticatedRouteDeps): void;
@@ -0,0 +1,172 @@
1
+ import { FileServiceError, FileSpaceService, fileResourceFromPath, resolveFilePath } from "../../../files/file-service.js";
2
+ import { basename, join } from "node:path";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ //#region src/gateway/hono/routes/files.ts
5
+ const services = /* @__PURE__ */ new WeakMap();
6
+ const mutationLocks = /* @__PURE__ */ new Map();
7
+ async function withMutationLock(key, action) {
8
+ const previous = mutationLocks.get(key) ?? Promise.resolve();
9
+ let release;
10
+ const gate = new Promise((resolve) => {
11
+ release = resolve;
12
+ });
13
+ const tail = previous.then(() => gate);
14
+ mutationLocks.set(key, tail);
15
+ await previous;
16
+ try {
17
+ return await action();
18
+ } finally {
19
+ release();
20
+ if (mutationLocks.get(key) === tail) mutationLocks.delete(key);
21
+ }
22
+ }
23
+ function getFiles(deps) {
24
+ const existing = services.get(deps.service);
25
+ if (existing) return existing;
26
+ const created = new FileSpaceService(() => deps.service.currentConfig, deps.service.projects, (sessionKey) => deps.service.agentService.getResolvedWorkspaceForSession(sessionKey));
27
+ services.set(deps.service, created);
28
+ return created;
29
+ }
30
+ function publicSpace(space) {
31
+ const { root: _root, ...value } = space;
32
+ return value;
33
+ }
34
+ function errorResponse(c, error) {
35
+ if (error instanceof FileServiceError) return c.json({ error: { message: error.message } }, error.status);
36
+ throw error;
37
+ }
38
+ async function collectFiles(files, spaceId, query, max) {
39
+ const output = [];
40
+ const directories = [""];
41
+ while (directories.length && output.length < max) {
42
+ const directory = directories.shift();
43
+ const children = await files.children(spaceId, directory);
44
+ for (const child of children) {
45
+ if (child.kind === "directory") directories.push(child.relativePath);
46
+ else if (!query || child.name.toLocaleLowerCase().includes(query)) output.push(child);
47
+ if (output.length >= max) break;
48
+ }
49
+ }
50
+ return output;
51
+ }
52
+ function registerFilesRoutes(authenticated, deps) {
53
+ const files = getFiles(deps);
54
+ authenticated.get("/api/files/spaces", async (c) => {
55
+ try {
56
+ return c.json({ spaces: (await files.list()).map(publicSpace) });
57
+ } catch (error) {
58
+ return errorResponse(c, error);
59
+ }
60
+ });
61
+ authenticated.get("/api/files/contexts/:kind/:id", async (c) => {
62
+ try {
63
+ const kind = c.req.param("kind");
64
+ if (kind !== "agent" && kind !== "project" && kind !== "session") throw new FileServiceError(400, "Invalid context kind");
65
+ return c.json({ space: publicSpace(await files.forContext(kind, c.req.param("id"))) });
66
+ } catch (error) {
67
+ return errorResponse(c, error);
68
+ }
69
+ });
70
+ authenticated.get("/api/files/spaces/:id/children", async (c) => {
71
+ try {
72
+ return c.json({ items: await files.children(c.req.param("id"), c.req.query("path") ?? "") });
73
+ } catch (error) {
74
+ return errorResponse(c, error);
75
+ }
76
+ });
77
+ authenticated.get("/api/files/recent", async (c) => {
78
+ try {
79
+ const limit = Math.min(100, Math.max(1, Number(c.req.query("limit")) || 50));
80
+ const spaces = await files.list();
81
+ const items = (await Promise.all(spaces.map((space) => collectFiles(files, space.id, "", 5e3)))).flat().sort((a, b) => b.modifiedAt - a.modifiedAt).slice(0, limit);
82
+ return c.json({ items });
83
+ } catch (error) {
84
+ return errorResponse(c, error);
85
+ }
86
+ });
87
+ authenticated.get("/api/files/search", async (c) => {
88
+ try {
89
+ const query = (c.req.query("q") ?? "").trim().toLocaleLowerCase();
90
+ if (!query) return c.json({ items: [] });
91
+ const limit = Math.min(100, Math.max(1, Number(c.req.query("limit")) || 50));
92
+ const requestedSpaceId = c.req.query("spaceId");
93
+ const spaces = requestedSpaceId ? [await files.get(requestedSpaceId)] : await files.list();
94
+ const items = (await Promise.all(spaces.map((space) => collectFiles(files, space.id, query, 5e3)))).flat().sort((a, b) => b.modifiedAt - a.modifiedAt).slice(0, limit);
95
+ return c.json({ items });
96
+ } catch (error) {
97
+ return errorResponse(c, error);
98
+ }
99
+ });
100
+ authenticated.post("/api/files/resolve", async (c) => {
101
+ try {
102
+ const body = await c.req.json();
103
+ if (!body.spaceId || !body.path) throw new FileServiceError(400, "spaceId and path are required");
104
+ const space = await files.get(body.spaceId);
105
+ const absolutePath = await resolveFilePath(space.root, body.path);
106
+ return c.json({ resource: await fileResourceFromPath(space, absolutePath) });
107
+ } catch (error) {
108
+ return errorResponse(c, error);
109
+ }
110
+ });
111
+ authenticated.get("/api/files/:id", async (c) => {
112
+ try {
113
+ return c.json({ resource: (await files.resource(c.req.param("id"))).resource });
114
+ } catch (error) {
115
+ return errorResponse(c, error);
116
+ }
117
+ });
118
+ authenticated.get("/api/files/:id/content", async (c) => {
119
+ try {
120
+ const { resource, absolutePath } = await files.resource(c.req.param("id"));
121
+ if (resource.kind !== "file") throw new FileServiceError(400, "Resource is not a file");
122
+ if (c.req.header("if-none-match") === resource.revision) return c.body(null, 304);
123
+ c.header("Content-Type", resource.mimeType);
124
+ c.header("ETag", resource.revision);
125
+ c.header("Content-Disposition", `inline; filename="${resource.name.replace(/["\r\n]/g, "_")}"`);
126
+ return c.body(await readFile(absolutePath));
127
+ } catch (error) {
128
+ return errorResponse(c, error);
129
+ }
130
+ });
131
+ authenticated.put("/api/files/:id/content", async (c) => {
132
+ try {
133
+ const body = await c.req.json();
134
+ if (typeof body.content !== "string" || !body.revision) throw new FileServiceError(400, "content and revision are required");
135
+ const id = c.req.param("id");
136
+ const resource = await withMutationLock(id, async () => {
137
+ const current = await files.resource(id);
138
+ if (!current.resource.capabilities.includes("edit")) throw new FileServiceError(403, "File is not editable");
139
+ if (body.revision !== current.resource.revision) throw new FileServiceError(409, "File has changed");
140
+ await writeFile(current.absolutePath, body.content, "utf8");
141
+ return fileResourceFromPath(current.space, current.absolutePath);
142
+ });
143
+ return c.json({ resource });
144
+ } catch (error) {
145
+ return errorResponse(c, error);
146
+ }
147
+ });
148
+ authenticated.post("/api/files/spaces/:id/upload", async (c) => {
149
+ try {
150
+ const space = await files.get(c.req.param("id"));
151
+ if (!space.writable) throw new FileServiceError(403, "File space is read-only");
152
+ const form = await c.req.formData();
153
+ const upload = form.get("file");
154
+ const directory = String(form.get("directory") ?? "");
155
+ if (!(upload instanceof File)) throw new FileServiceError(400, "file is required");
156
+ const name = basename(upload.name).trim();
157
+ if (!name || name !== upload.name) throw new FileServiceError(400, "Invalid file name");
158
+ const parent = await resolveFilePath(space.root, directory);
159
+ const target = await resolveFilePath(space.root, join(directory, name), false);
160
+ if (target === parent) throw new FileServiceError(400, "Invalid file name");
161
+ await writeFile(target, new Uint8Array(await upload.arrayBuffer()), { flag: "wx" }).catch((error) => {
162
+ if (error.code === "EEXIST") throw new FileServiceError(409, "File already exists");
163
+ throw error;
164
+ });
165
+ return c.json({ resource: await fileResourceFromPath(space, target) }, 201);
166
+ } catch (error) {
167
+ return errorResponse(c, error);
168
+ }
169
+ });
170
+ }
171
+ //#endregion
172
+ export { registerFilesRoutes };
@@ -19,6 +19,14 @@ const AUTHENTICATED_LAZY_ROUTE_BUNDLES = [
19
19
  return { register: registerMediaRoutes };
20
20
  }
21
21
  },
22
+ {
23
+ id: "files",
24
+ match: (path) => startsWithAny(path, ["/api/files"]),
25
+ load: async () => {
26
+ const { registerFilesRoutes } = await import("./files.js");
27
+ return { register: registerFilesRoutes };
28
+ }
29
+ },
22
30
  {
23
31
  id: "workspace",
24
32
  match: (path) => startsWithAny(path, ["/api/workspace"]),