@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.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CsFAJTIN.js → agent-avatar-dicebear-CBqUnlvL.js} +1 -1
- package/dist/gateway/static/root/assets/agents-7YO6znTT.js +68 -0
- package/dist/gateway/static/root/assets/{app-management-settings-panel-7KcMWuah.js → app-management-settings-panel-DP7YVRPn.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-Cqlw7Fif.js → appearance-settings-CVQ5n7y6.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-CsnhdGZ2.js → apps-page-DSCia0DT.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-q4pDpqkF.js → attachment-load-mmr_mHRr.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-BuTqjKGX.js → automations-page-DfY-T6Ga.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-BpvD54D6.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-DUce1Bsc.js → binary-plugins-CO9QMEh2.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-D8Ejp-QR.js → block-editor-CE14CVOy.js} +3 -3
- package/dist/gateway/static/root/assets/browser-settings-page-CeESA4Oj.js +8 -0
- package/dist/gateway/static/root/assets/{browser-workflows-page-BDvZbR9v.js → browser-workflows-page-1wgXyoSO.js} +2 -2
- package/dist/gateway/static/root/assets/capabilities-settings-panel-B_OXTojB.js +7 -0
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-D5nZecxV.js +3 -0
- package/dist/gateway/static/root/assets/channels-settings-BzmnWx6X.js +1 -0
- package/dist/gateway/static/root/assets/{chat-terminal-dock-h1CKexNz.js → chat-terminal-dock-CdRW6Euc.js} +2 -2
- package/dist/gateway/static/root/assets/cloud-C4-yfnnk.js +1 -0
- package/dist/gateway/static/root/assets/connectors-page-DX6OkwFQ.js +2 -0
- package/dist/gateway/static/root/assets/{cron-expression-DlaQQOId.js → cron-expression-DYSMZsms.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-8R1sXJKb.js → date-picker-DGCfX4tK.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-CWur0821.js → dependency-picker-DHDa4ht1.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-Knl-zNDU.js → desktop-pet-CMO-J3cG.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-CAQMLVtd.js → desktop-pet-settings-MmOR4A3u.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-Caaz4Rw5.js → directory-picker-path-field-B9qoxDcn.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-B965Mc15.js → extension-debug-page-DdH0dG2_.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-C0UnkBi5.js → extension-page-Clg-KktN.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-BUSIAZCR.js → extension-settings-page-eP3fJ2M6.js} +1 -1
- package/dist/gateway/static/root/assets/files-Dp8_uGTO.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-config-api-DkH0eEgy.js → gateway-config-api-DEHnF4Wm.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-BBgLDm6I.js → gateway-settings-DKkEXXj9.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-BtEHVXWD.js → heartbeat-settings-DlBPNubY.js} +1 -1
- package/dist/gateway/static/root/assets/home-page-BnOrLRfs.js +1 -0
- package/dist/gateway/static/root/assets/index-0xQO5B_Q.js +70 -0
- package/dist/gateway/static/root/assets/index-CCyIdGuQ.css +2 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-CsRz8NrS.js → keyboard-shortcuts-settings-BvpEeHnm.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-Ch1rdUo8.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-CTBNUG4a.js → local-apps-page-Bzvn99eH.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-zxx2ExlY.js +2 -0
- package/dist/gateway/static/root/assets/{management-settings-YqUAEwQo.js → management-settings-BQaeyOYm.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-split-dxpSCXb8.js → markdown-split-D9tmIiEm.js} +1 -1
- package/dist/gateway/static/root/assets/markdown-view-B1bGSQqt.css +1 -0
- package/dist/gateway/static/root/assets/{media-plugins-CJ00i1Lg.js → media-plugins-D8I7OGnT.js} +1 -1
- package/dist/gateway/static/root/assets/models-hub-cache-0RjEwafI.js +1 -0
- package/dist/gateway/static/root/assets/{notes-api-BFsOGYkd.js → notes-api-EpgG-OQc.js} +1 -1
- package/dist/gateway/static/root/assets/{notes-page-DEnbnkIt.js → notes-page-CCvjGMhN.js} +1 -1
- package/dist/gateway/static/root/assets/notes-workbench-9cgh77VH.js +4 -0
- package/dist/gateway/static/root/assets/oauth-api-BwNLwbI8.js +1 -0
- package/dist/gateway/static/root/assets/{product-open-page-BHxo9U95.js → product-open-page-MmMswmOU.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-hf6pmH4p.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-By2U-Wdh.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-CUcpp7CQ.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-BpQ2GI-0.js → runtime-tools-settings-panel-Dx6o7sLu.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-HE1G26Lx.js → schema-form-Cdd9c4vt.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-ByI82xS1.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-BCoI5IJ8.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-CXAn0Xhy.js → settings-form-section-D0YxMnhm.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-DsFu7Bcv.js → settings-loading-skeleton-y53gEKsF.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-C4iib9nO.js → settings-page-BoE_tToG.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-BA83Wr1d.js → setup-status-panel-BQmyb5KC.js} +1 -1
- package/dist/gateway/static/root/assets/share-preview-page-DiqBn-oE.js +2 -0
- package/dist/gateway/static/root/assets/shares-settings-Bf3VpZ-R.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-CXHlbuGS.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-CdaFtNw9.js → skills-page-CZwhWiRl.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-DYaAddUL.js → skills-page.utils-kBGr8opZ.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-Co7gilRd.js → system-settings-panel-CPBs6lWi.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-Bo0KSZ2v.js +1 -0
- package/dist/gateway/static/root/assets/theme-store-Qgvo0Ga7.js +1 -0
- package/dist/gateway/static/root/assets/{use-autosave-DnnZtMpA.js → use-autosave-C4YcEiP9.js} +1 -1
- package/dist/gateway/static/root/assets/user-context-page-Cnj7gMX2.js +1 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-wetUpi3i.js → voice-api-key-field-BML8zCwZ.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-tbTWASJJ.js → work-discovery-overlay-CKIdPEen.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Dy0fDs8w.js → workflow-run-setup-panel-BK44Vm5W.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-CB98MTwG.js +5 -0
- package/dist/gateway/static/root/index.html +10 -6
- package/dist/package.js +1 -1
- package/dist/src/agent/inbound/turn-dispatcher.js +1 -1
- package/dist/src/agent/service/process-direct-streaming.js +4 -1
- package/dist/src/agent/service/webchat-tts.d.ts +2 -0
- package/dist/src/agent/service/webchat-tts.js +15 -2
- package/dist/src/agent/service.d.ts +1 -0
- package/dist/src/agent/service.js +5 -2
- package/dist/src/agent/skills/skill-manage-ops.js +1 -1
- package/dist/src/agent/tools/tts-tool.js +19 -13
- package/dist/src/agent/tools/write.js +9 -12
- package/dist/src/agent/tools/xopc-use-tool.js +2 -2
- package/dist/src/auth/oauth/index.js +2 -2
- package/dist/src/auth/oauth/registry.d.ts +1 -1
- package/dist/src/auth/oauth/registry.js +5 -1
- package/dist/src/auth/oauth/xopc-cloud.d.ts +1 -0
- package/dist/src/auth/oauth/xopc-cloud.js +8 -2
- package/dist/src/files/file-service.d.ts +36 -0
- package/dist/src/files/file-service.js +282 -0
- package/dist/src/gateway/chat-stream/mapper.js +11 -0
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/hono/app.js +2 -2
- package/dist/src/gateway/hono/routes/files.d.ts +3 -0
- package/dist/src/gateway/hono/routes/files.js +172 -0
- package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
- package/dist/src/gateway/hono/routes/projects.js +0 -508
- package/dist/src/gateway/hono/routes/shares.js +410 -21
- package/dist/src/gateway/hono/routes/workspace.js +15 -974
- package/dist/src/gateway/service.js +9 -9
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/share/hosted-session-share.d.ts +104 -0
- package/dist/src/share/hosted-session-share.js +276 -0
- package/dist/src/share/note-share-service.js +4 -24
- package/dist/src/share/session-share-projector.d.ts +31 -0
- package/dist/src/share/session-share-projector.js +79 -0
- package/dist/src/share/session-share-service.d.ts +89 -0
- package/dist/src/share/session-share-service.js +325 -0
- package/dist/src/share/share-asset-ticket.d.ts +7 -0
- package/dist/src/share/share-asset-ticket.js +30 -0
- package/dist/src/share/share-landing.d.ts +4 -3
- package/dist/src/share/share-landing.js +5 -5
- package/dist/src/share/share-store.d.ts +27 -2
- package/dist/src/share/share-store.js +71 -8
- package/dist/src/share/share-types.d.ts +17 -2
- package/dist/src/tui/backends/embedded-backend.js +1 -1
- package/dist/src/tui/backends/gateway-realtime-backend.js +11 -3
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/agents-DA4b6_0C.js +0 -68
- package/dist/gateway/static/root/assets/automations-workspace-CD0E2ThS.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-CY1k3MMd.js +0 -8
- package/dist/gateway/static/root/assets/capabilities-settings-panel-D1VfotHQ.js +0 -7
- package/dist/gateway/static/root/assets/capability-presets-settings-panel-DXHqRLpU.js +0 -3
- package/dist/gateway/static/root/assets/channels-settings-DrFL57M_.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-DUAyfFCF.js +0 -2
- package/dist/gateway/static/root/assets/home-page-5SU2TNJj.js +0 -1
- package/dist/gateway/static/root/assets/index-BNWhgmjs.js +0 -70
- package/dist/gateway/static/root/assets/index-D_hrEKXM.css +0 -2
- package/dist/gateway/static/root/assets/local-app-workbench-page-DYPEUgpP.js +0 -2
- package/dist/gateway/static/root/assets/logs-page-B0P7rG2B.js +0 -2
- package/dist/gateway/static/root/assets/markdown-view-CLzTdVLW.css +0 -1
- package/dist/gateway/static/root/assets/notes-workbench-DmVYu_Oc.js +0 -4
- package/dist/gateway/static/root/assets/project-detail-page-DV2nGmsq.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-BNIFwFfc.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-DLXqKiA_.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-DWaXf_25.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-JFCbnCL2.js +0 -1
- package/dist/gateway/static/root/assets/share-preview-page-BOGl3s6M.js +0 -2
- package/dist/gateway/static/root/assets/shares-settings-Q2MdGaBi.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-cFtfVeFU.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-5B1pswu8.js +0 -1
- package/dist/gateway/static/root/assets/theme-store-DOdCjyuw.js +0 -1
- package/dist/gateway/static/root/assets/user-context-page-Dr1FKjXv.js +0 -1
- package/dist/gateway/static/root/assets/workflows-page-IVP3GSkI.js +0 -5
- package/dist/src/gateway/file-path-classifier.d.ts +0 -30
- package/dist/src/gateway/file-path-classifier.js +0 -105
- package/dist/src/gateway/file-reference-errors.d.ts +0 -6
- package/dist/src/gateway/file-reference-errors.js +0 -21
- package/dist/src/gateway/file-reference-registry.d.ts +0 -34
- package/dist/src/gateway/file-reference-registry.js +0 -45
- /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
|
|
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 :
|
|
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,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"]),
|