@xopcai/xopc 0.0.234 → 0.0.235

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 (117) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  3. package/dist/extensions/telegram/xopc.extension.json +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CztNAQyE.js → agent-avatar-dicebear-dvh9aEm6.js} +1 -1
  5. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-BHeLVAW5.js → agent-defaults-settings-panel-B9tkmkUj.js} +1 -1
  6. package/dist/gateway/static/root/assets/{agents-B8c0U7Ze.js → agents-DTXYo-7z.js} +1 -1
  7. package/dist/gateway/static/root/assets/{app-management-settings-panel-BxBMAdWc.js → app-management-settings-panel-DXFTk7x6.js} +1 -1
  8. package/dist/gateway/static/root/assets/{appearance-settings-DVAxCOPL.js → appearance-settings-D-FD5SOq.js} +1 -1
  9. package/dist/gateway/static/root/assets/{apps-page-BtzXxSYK.js → apps-page-BT102W58.js} +1 -1
  10. package/dist/gateway/static/root/assets/{attachment-load-BpCKsdiO.js → attachment-load-DmBAKvsi.js} +1 -1
  11. package/dist/gateway/static/root/assets/{automations-page-a6_hTplT.js → automations-page-BGkF6XNH.js} +1 -1
  12. package/dist/gateway/static/root/assets/{automations-workspace-C7ZwZl04.js → automations-workspace-BRb45-_R.js} +1 -1
  13. package/dist/gateway/static/root/assets/{binary-plugins-OqUf-uET.js → binary-plugins-B-6Ski7l.js} +1 -1
  14. package/dist/gateway/static/root/assets/{block-editor-9OAYqVKF.js → block-editor-BgeKQN1F.js} +1 -1
  15. package/dist/gateway/static/root/assets/{browser-settings-page-CEHhbDj0.js → browser-settings-page-BkRWxC7D.js} +1 -1
  16. package/dist/gateway/static/root/assets/{browser-workflows-page-CUgwXXsi.js → browser-workflows-page-D92g81lS.js} +1 -1
  17. package/dist/gateway/static/root/assets/{capabilities-settings-panel-CAbI9beX.js → capabilities-settings-panel-DfT-vWTA.js} +1 -1
  18. package/dist/gateway/static/root/assets/{channels-settings-BksDnAyv.js → channels-settings-C9oGpLR0.js} +1 -1
  19. package/dist/gateway/static/root/assets/{chat-terminal-dock-DB9zi_dy.js → chat-terminal-dock-C_9LUs7G.js} +1 -1
  20. package/dist/gateway/static/root/assets/{connectors-page-CnY8lcGJ.js → connectors-page-SJKV6SHT.js} +1 -1
  21. package/dist/gateway/static/root/assets/{cron-expression-a9Jyianb.js → cron-expression-BYmr5ZuN.js} +1 -1
  22. package/dist/gateway/static/root/assets/{date-picker-B_l2CHWO.js → date-picker-FKL_b_S0.js} +1 -1
  23. package/dist/gateway/static/root/assets/{dependency-picker-DjcEIzVJ.js → dependency-picker-BP68vqBI.js} +1 -1
  24. package/dist/gateway/static/root/assets/{desktop-pet-ZCBsj8Je.js → desktop-pet-DOXjRTqK.js} +1 -1
  25. package/dist/gateway/static/root/assets/{desktop-pet-settings-DJJf0EvL.js → desktop-pet-settings-CuG2qYoY.js} +1 -1
  26. package/dist/gateway/static/root/assets/{extension-debug-page-CMXbAkx-.js → extension-debug-page-BeJ1-5ID.js} +1 -1
  27. package/dist/gateway/static/root/assets/{extension-page-WWePedpo.js → extension-page-CncDndLS.js} +1 -1
  28. package/dist/gateway/static/root/assets/{extension-settings-page-DjIhNJVI.js → extension-settings-page-BxxL0HqU.js} +1 -1
  29. package/dist/gateway/static/root/assets/{files-Dp8_uGTO.js → files-DQjhZjr2.js} +1 -1
  30. package/dist/gateway/static/root/assets/{gateway-config-api-BNj6srhV.js → gateway-config-api-Dc6A6HyJ.js} +1 -1
  31. package/dist/gateway/static/root/assets/{gateway-settings-DvlFDxhZ.js → gateway-settings-C2XcazGD.js} +1 -1
  32. package/dist/gateway/static/root/assets/{heartbeat-settings-Dlahslof.js → heartbeat-settings-BjUEqkkC.js} +1 -1
  33. package/dist/gateway/static/root/assets/{home-page-DJhUd30b.js → home-page-DTSjwfAQ.js} +1 -1
  34. package/dist/gateway/static/root/assets/{index-CXGQihfj.js → index-DFfvY8IN.js} +40 -40
  35. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-d12AAEaC.js → keyboard-shortcuts-settings-Cwinks_5.js} +1 -1
  36. package/dist/gateway/static/root/assets/{local-app-workbench-page-DUP0UiFY.js → local-app-workbench-page-0wntj9mK.js} +1 -1
  37. package/dist/gateway/static/root/assets/{local-apps-page-_z2o0cD1.js → local-apps-page-WeKm5vZ0.js} +1 -1
  38. package/dist/gateway/static/root/assets/{logs-page-CPUaLcGH.js → logs-page-DFgJr7Zx.js} +1 -1
  39. package/dist/gateway/static/root/assets/{management-settings-eTBfNgXU.js → management-settings-8bEvKO0R.js} +1 -1
  40. package/dist/gateway/static/root/assets/{markdown-split-7rTfQCN_.js → markdown-split-B6wQhwLV.js} +1 -1
  41. package/dist/gateway/static/root/assets/{media-plugins-BIfKo5Ow.js → media-plugins-BSEPuO76.js} +1 -1
  42. package/dist/gateway/static/root/assets/{notes-page-dOvbZBOQ.js → notes-page-CPgbeO1u.js} +1 -1
  43. package/dist/gateway/static/root/assets/{notes-workbench-DQMUpDc9.js → notes-workbench-DIXCtwhs.js} +2 -2
  44. package/dist/gateway/static/root/assets/{product-open-page-CgZhadZQ.js → product-open-page-DNHWn_26.js} +1 -1
  45. package/dist/gateway/static/root/assets/{project-detail-page-D1HidxgT.js → project-detail-page-6jgRJg4w.js} +1 -1
  46. package/dist/gateway/static/root/assets/{projects-page-CxwNbbud.js → projects-page-Bd4nXs4y.js} +1 -1
  47. package/dist/gateway/static/root/assets/{remote-access-hub-CK59rSc_.js → remote-access-hub-C_5-u9U-.js} +1 -1
  48. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-D3PS55Yg.js → runtime-tools-settings-panel-D3TCJGy3.js} +1 -1
  49. package/dist/gateway/static/root/assets/{schema-form-H-aeWTzM.js → schema-form-DUAlwNoO.js} +1 -1
  50. package/dist/gateway/static/root/assets/{sessions-page-C2tP7363.js → sessions-page-BqYAR86S.js} +1 -1
  51. package/dist/gateway/static/root/assets/settings-advanced-gate-a146I0uZ.js +1 -0
  52. package/dist/gateway/static/root/assets/{settings-form-section-B9oF8Rhw.js → settings-form-section-D7nQ3Lfu.js} +1 -1
  53. package/dist/gateway/static/root/assets/{settings-loading-skeleton-CazDM3n5.js → settings-loading-skeleton-CwGM9zKH.js} +1 -1
  54. package/dist/gateway/static/root/assets/{settings-page-C6pmQNzY.js → settings-page-CMAkMc3I.js} +1 -1
  55. package/dist/gateway/static/root/assets/{setup-status-panel-Cn2E0G2o.js → setup-status-panel-rhaERxAL.js} +1 -1
  56. package/dist/gateway/static/root/assets/{share-preview-page-XgxvE8cx.js → share-preview-page-BzkXvuH6.js} +1 -1
  57. package/dist/gateway/static/root/assets/{shares-settings-Czph0_fM.js → shares-settings-BvsM3Im5.js} +1 -1
  58. package/dist/gateway/static/root/assets/{skill-reload-api-DDq_QEqd.js → skill-reload-api-u7E0gqFo.js} +1 -1
  59. package/dist/gateway/static/root/assets/{skills-page-CfNV6kgb.js → skills-page-DJRr5HFw.js} +1 -1
  60. package/dist/gateway/static/root/assets/{skills-page.utils-DgxGb7v7.js → skills-page.utils-DcrBe39q.js} +1 -1
  61. package/dist/gateway/static/root/assets/{system-settings-panel-B4W--Ukh.js → system-settings-panel-BUsykmjx.js} +1 -1
  62. package/dist/gateway/static/root/assets/{task-detail-page-UY5kNWoR.js → task-detail-page-C9QgWfYG.js} +1 -1
  63. package/dist/gateway/static/root/assets/{use-autosave-DR7YWBzR.js → use-autosave-DDEypreQ.js} +1 -1
  64. package/dist/gateway/static/root/assets/{user-context-page-DnUL8kJU.js → user-context-page-Ch0cfQfI.js} +1 -1
  65. package/dist/gateway/static/root/assets/{voice-api-key-field-BZhqoxBR.js → voice-api-key-field-BLa_vdXE.js} +1 -1
  66. package/dist/gateway/static/root/assets/{work-discovery-overlay-CouKiK45.js → work-discovery-overlay-DS3w1Fyq.js} +1 -1
  67. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-CUqAESrr.js → workflow-run-setup-panel-jQl_wmYC.js} +1 -1
  68. package/dist/gateway/static/root/assets/{workflows-page-B8dJRjMJ.js → workflows-page-DyTwkdzs.js} +1 -1
  69. package/dist/gateway/static/root/index.html +2 -2
  70. package/dist/package.js +1 -1
  71. package/dist/src/agent/embedded/run-for-session.js +1 -1
  72. package/dist/src/agent/tools/create-share-tool.js +1 -1
  73. package/dist/src/agent/tools/publish-artifacts.js +1 -1
  74. package/dist/src/agent/tools/send-media.js +1 -1
  75. package/dist/src/agent/tools/workflow-tool.js +1 -1
  76. package/dist/src/agent/tools/write.js +1 -1
  77. package/dist/src/agent/tools/xopc-use-tool.js +4 -4
  78. package/dist/src/cli/commands/channels.js +1 -1
  79. package/dist/src/cli/commands/connectors.js +1 -1
  80. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  81. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  82. package/dist/src/cli/commands/models.js +1 -1
  83. package/dist/src/cli/commands/onboard.js +1 -1
  84. package/dist/src/extensions/sdk/index.js +1 -1
  85. package/dist/src/files/file-service.d.ts +10 -2
  86. package/dist/src/files/file-service.js +33 -12
  87. package/dist/src/gateway/file-path-classifier.d.ts +30 -0
  88. package/dist/src/gateway/file-path-classifier.js +105 -0
  89. package/dist/src/gateway/file-reference-errors.d.ts +6 -0
  90. package/dist/src/gateway/file-reference-errors.js +21 -0
  91. package/dist/src/gateway/file-reference-registry.d.ts +34 -0
  92. package/dist/src/gateway/file-reference-registry.js +45 -0
  93. package/dist/src/gateway/file-space-service.d.ts +4 -0
  94. package/dist/src/gateway/file-space-service.js +19 -0
  95. package/dist/src/gateway/heartbeat/service.js +1 -1
  96. package/dist/src/gateway/hono/app.js +3 -1
  97. package/dist/src/gateway/hono/routes/connectors.js +1 -1
  98. package/dist/src/gateway/hono/routes/file-references.d.ts +4 -0
  99. package/dist/src/gateway/hono/routes/file-references.js +98 -0
  100. package/dist/src/gateway/hono/routes/files.js +39 -17
  101. package/dist/src/gateway/hono/routes/html-preview.d.ts +4 -0
  102. package/dist/src/gateway/hono/routes/html-preview.js +45 -0
  103. package/dist/src/gateway/hono/routes/shares.js +18 -10
  104. package/dist/src/gateway/hono/routes/tasks.js +3 -3
  105. package/dist/src/gateway/security/csp.d.ts +1 -1
  106. package/dist/src/gateway/security/csp.js +2 -2
  107. package/dist/src/gateway/service.js +5 -5
  108. package/dist/src/heartbeat/index.js +1 -1
  109. package/dist/src/infra/update-restart.js +1 -1
  110. package/dist/src/storage/sqlite/config-repository.d.ts +5 -0
  111. package/dist/src/storage/sqlite/config-repository.js +8 -1
  112. package/dist/src/tasks/home-query-service.js +2 -2
  113. package/dist/src/tasks/task-signal-service.js +1 -1
  114. package/dist/src/tui/backends/embedded-backend.js +1 -1
  115. package/dist/src/workflows/service/workflow-run-service.js +1 -1
  116. package/package.json +4 -4
  117. package/dist/gateway/static/root/assets/settings-advanced-gate-DlYBA2N-.js +0 -1
@@ -0,0 +1,98 @@
1
+ import { FileServiceError } from "../../../files/file-service.js";
2
+ import { buildFilePathClassifierContext, classifyFileLocation, displayNameForPath, fileRefSessionKeysMatch, resolveFileReferenceCandidate } from "../../file-path-classifier.js";
3
+ import { classifyFileReferenceFsError } from "../../file-reference-errors.js";
4
+ import { FileReferenceRegistry } from "../../file-reference-registry.js";
5
+ import { getGatewayFileSpaceService } from "../../file-space-service.js";
6
+ import { realpath, stat } from "node:fs/promises";
7
+ //#region src/gateway/hono/routes/file-references.ts
8
+ /** Metadata and explicit desktop actions for links outside a managed workspace. */
9
+ function registerFileReferenceRoutes(app, deps) {
10
+ const files = getGatewayFileSpaceService(deps.service);
11
+ const refs = new FileReferenceRegistry();
12
+ app.post("/api/files/resolve-reference", async (c) => {
13
+ const body = await c.req.json();
14
+ if (!body.spaceId || !body.path?.trim()) return c.json({ error: { message: "spaceId and path are required" } }, 400);
15
+ try {
16
+ const space = await files.get(body.spaceId);
17
+ const sessionKey = body.sessionKey?.trim();
18
+ const agentId = space.bindings.find((binding) => binding.kind === "agent")?.id;
19
+ const ctx = {
20
+ ...buildFilePathClassifierContext(deps.service.currentConfig, sessionKey || (agentId ? `agent:${agentId}:main` : void 0)),
21
+ workspaceRoot: space.root
22
+ };
23
+ const inputPath = body.path.trim();
24
+ const base = {
25
+ inputPath,
26
+ displayName: displayNameForPath(inputPath)
27
+ };
28
+ const { candidate, invalid } = await resolveFileReferenceCandidate(inputPath, space.root, ctx);
29
+ if (!candidate || invalid) return c.json({ reference: {
30
+ ...base,
31
+ scope: "invalid",
32
+ exists: false,
33
+ capabilities: []
34
+ } });
35
+ let absolutePath;
36
+ let info;
37
+ try {
38
+ absolutePath = await realpath(candidate);
39
+ info = await stat(absolutePath);
40
+ } catch (error) {
41
+ const failure = classifyFileReferenceFsError(error);
42
+ if (failure.code !== "FILE_NOT_FOUND") return c.json({ error: { message: failure.message } }, failure.status === 403 ? 403 : 500);
43
+ return c.json({ reference: {
44
+ ...base,
45
+ scope: "missing",
46
+ absolutePath: candidate,
47
+ exists: false,
48
+ capabilities: ["copyPath"]
49
+ } });
50
+ }
51
+ const classified = classifyFileLocation(absolutePath, ctx);
52
+ const capabilities = info.isFile() || info.isDirectory() ? [
53
+ "openExternal",
54
+ "revealInFolder",
55
+ "copyPath"
56
+ ] : ["copyPath"];
57
+ const ref = refs.register({
58
+ absolutePath,
59
+ sessionKey,
60
+ ...classified,
61
+ capabilities
62
+ });
63
+ return c.json({ reference: {
64
+ ...base,
65
+ ...classified,
66
+ fileRefId: ref.id,
67
+ absolutePath,
68
+ exists: true,
69
+ isDirectory: info.isDirectory(),
70
+ capabilities,
71
+ mtimeMs: info.mtimeMs
72
+ } });
73
+ } catch (error) {
74
+ if (error instanceof FileServiceError) return c.json({ error: { message: error.message } }, error.status);
75
+ throw error;
76
+ }
77
+ });
78
+ app.post("/api/files/references/:id/action", async (c) => {
79
+ const ref = refs.resolve(c.req.param("id"));
80
+ if (!ref) return c.json({ error: { message: "File reference expired" } }, 404);
81
+ const body = await c.req.json();
82
+ if (!fileRefSessionKeysMatch(ref.sessionKey, body.sessionKey)) return c.json({ error: { message: "File reference forbidden" } }, 403);
83
+ if (body.action !== "openExternal" && body.action !== "revealInFolder" || !ref.capabilities.includes(body.action)) return c.json({ error: { message: "Action not allowed" } }, 403);
84
+ try {
85
+ if (await realpath(ref.absolutePath) !== ref.absolutePath) return c.json({ error: { message: "File reference changed" } }, 409);
86
+ const info = await stat(ref.absolutePath);
87
+ if (!info.isFile() && !info.isDirectory()) return c.json({ error: { message: "Not a file or directory" } }, 400);
88
+ return c.json({
89
+ absolutePath: ref.absolutePath,
90
+ isDirectory: info.isDirectory()
91
+ });
92
+ } catch {
93
+ return c.json({ error: { message: "File not found" } }, 404);
94
+ }
95
+ });
96
+ }
97
+ //#endregion
98
+ export { registerFileReferenceRoutes };
@@ -1,8 +1,10 @@
1
- import { FileServiceError, FileSpaceService, fileResourceFromPath, resolveFilePath } from "../../../files/file-service.js";
1
+ import { FileServiceError, fileResourceFromPath, resolveFilePath } from "../../../files/file-service.js";
2
+ import { getGatewayFileSpaceService } from "../../file-space-service.js";
3
+ import { fuzzySearchWorkspaceFiles, fuzzySubsequenceScore } from "../../workspace-file-search.js";
4
+ import { registerFileReferenceRoutes } from "./file-references.js";
2
5
  import { basename, join } from "node:path";
3
6
  import { readFile, writeFile } from "node:fs/promises";
4
7
  //#region src/gateway/hono/routes/files.ts
5
- const services = /* @__PURE__ */ new WeakMap();
6
8
  const mutationLocks = /* @__PURE__ */ new Map();
7
9
  async function withMutationLock(key, action) {
8
10
  const previous = mutationLocks.get(key) ?? Promise.resolve();
@@ -20,13 +22,6 @@ async function withMutationLock(key, action) {
20
22
  if (mutationLocks.get(key) === tail) mutationLocks.delete(key);
21
23
  }
22
24
  }
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
25
  function publicSpace(space) {
31
26
  const { root: _root, ...value } = space;
32
27
  return value;
@@ -39,22 +34,28 @@ function contentDisposition(disposition, fileName) {
39
34
  const sanitized = fileName.replace(/[\u0000-\u001f\u007f]/g, "_");
40
35
  return `${disposition}; filename="${sanitized.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_")}"; filename*=UTF-8''${encodeURIComponent(sanitized).replace(/['()*]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`)}`;
41
36
  }
42
- async function collectFiles(files, spaceId, query, max) {
37
+ async function collectFiles(files, spaceId, max) {
43
38
  const output = [];
44
39
  const directories = [""];
40
+ const visited = /* @__PURE__ */ new Set();
41
+ const space = await files.get(spaceId);
45
42
  while (directories.length && output.length < max) {
46
43
  const directory = directories.shift();
44
+ const canonical = await resolveFilePath(space.root, directory).catch(() => null);
45
+ if (!canonical || visited.has(canonical)) continue;
46
+ visited.add(canonical);
47
47
  const children = await files.children(spaceId, directory);
48
48
  for (const child of children) {
49
49
  if (child.kind === "directory") directories.push(child.relativePath);
50
- else if (!query || child.name.toLocaleLowerCase().includes(query)) output.push(child);
50
+ else output.push(child);
51
51
  if (output.length >= max) break;
52
52
  }
53
53
  }
54
54
  return output;
55
55
  }
56
56
  function registerFilesRoutes(authenticated, deps) {
57
- const files = getFiles(deps);
57
+ const files = getGatewayFileSpaceService(deps.service);
58
+ registerFileReferenceRoutes(authenticated, deps);
58
59
  authenticated.get("/api/files/spaces", async (c) => {
59
60
  try {
60
61
  return c.json({ spaces: (await files.list()).map(publicSpace) });
@@ -62,6 +63,13 @@ function registerFilesRoutes(authenticated, deps) {
62
63
  return errorResponse(c, error);
63
64
  }
64
65
  });
66
+ authenticated.get("/api/files/default-space", async (c) => {
67
+ try {
68
+ return c.json({ space: publicSpace(await files.defaultSpace()) });
69
+ } catch (error) {
70
+ return errorResponse(c, error);
71
+ }
72
+ });
65
73
  authenticated.get("/api/files/contexts/:kind/:id", async (c) => {
66
74
  try {
67
75
  const kind = c.req.param("kind");
@@ -82,7 +90,7 @@ function registerFilesRoutes(authenticated, deps) {
82
90
  try {
83
91
  const limit = Math.min(100, Math.max(1, Number(c.req.query("limit")) || 50));
84
92
  const spaces = await files.list();
85
- const items = (await Promise.all(spaces.map((space) => collectFiles(files, space.id, "", 5e3)))).flat().sort((a, b) => b.modifiedAt - a.modifiedAt).slice(0, limit);
93
+ const items = (await Promise.all(spaces.map((space) => collectFiles(files, space.id, 5e3)))).flat().sort((a, b) => b.modifiedAt - a.modifiedAt).slice(0, limit);
86
94
  return c.json({ items });
87
95
  } catch (error) {
88
96
  return errorResponse(c, error);
@@ -91,11 +99,23 @@ function registerFilesRoutes(authenticated, deps) {
91
99
  authenticated.get("/api/files/search", async (c) => {
92
100
  try {
93
101
  const query = (c.req.query("q") ?? "").trim().toLocaleLowerCase();
94
- if (!query) return c.json({ items: [] });
95
102
  const limit = Math.min(100, Math.max(1, Number(c.req.query("limit")) || 50));
96
103
  const requestedSpaceId = c.req.query("spaceId");
97
104
  const spaces = requestedSpaceId ? [await files.get(requestedSpaceId)] : await files.list();
98
- 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);
105
+ const items = (await Promise.all(spaces.map(async (space) => {
106
+ const candidates = await fuzzySearchWorkspaceFiles(space.root, query, limit);
107
+ return Promise.all(candidates.map(async (candidate) => {
108
+ try {
109
+ return await fileResourceFromPath(space, await resolveFilePath(space.root, candidate.path));
110
+ } catch (error) {
111
+ if (error instanceof FileServiceError) return null;
112
+ throw error;
113
+ }
114
+ }));
115
+ }))).flat().filter((file) => file !== null).map((file) => ({
116
+ file,
117
+ score: Math.max(fuzzySubsequenceScore(query, file.relativePath) ?? -Infinity, fuzzySubsequenceScore(query, file.name) ?? -Infinity)
118
+ })).sort((a, b) => b.score - a.score || a.file.relativePath.localeCompare(b.file.relativePath)).slice(0, limit).map((row) => row.file);
99
119
  return c.json({ items });
100
120
  } catch (error) {
101
121
  return errorResponse(c, error);
@@ -144,12 +164,14 @@ function registerFilesRoutes(authenticated, deps) {
144
164
  const body = await c.req.json();
145
165
  if (typeof body.content !== "string" || !body.revision) throw new FileServiceError(400, "content and revision are required");
146
166
  const id = c.req.param("id");
147
- const resource = await withMutationLock(id, async () => {
167
+ const initial = await files.resource(id);
168
+ const resource = await withMutationLock(initial.absolutePath, async () => {
148
169
  const current = await files.resource(id);
170
+ if (current.absolutePath !== initial.absolutePath) throw new FileServiceError(409, "File has changed");
149
171
  if (!current.resource.capabilities.includes("edit")) throw new FileServiceError(403, "File is not editable");
150
172
  if (body.revision !== current.resource.revision) throw new FileServiceError(409, "File has changed");
151
173
  await writeFile(current.absolutePath, body.content, "utf8");
152
- return fileResourceFromPath(current.space, current.absolutePath);
174
+ return (await files.resource(id)).resource;
153
175
  });
154
176
  return c.json({ resource });
155
177
  } catch (error) {
@@ -0,0 +1,4 @@
1
+ import type { Hono } from 'hono';
2
+ export declare const HTML_PREVIEW_PATH = "/api/preview/html";
3
+ /** Public static shell only: file contents arrive from the authenticated UI, never a URL. */
4
+ export declare function registerPublicHtmlPreviewRoute(app: Hono): void;
@@ -0,0 +1,45 @@
1
+ //#region src/gateway/hono/routes/html-preview.ts
2
+ const HTML_PREVIEW_PATH = "/api/preview/html";
3
+ const HTML_PREVIEW_SANDBOX = "allow-scripts allow-popups allow-popups-to-escape-sandbox allow-downloads allow-forms allow-modals";
4
+ const HTML_PREVIEW_CSP = [
5
+ "default-src 'none'",
6
+ `sandbox ${HTML_PREVIEW_SANDBOX}`,
7
+ "script-src 'unsafe-inline' https: blob:",
8
+ "style-src 'unsafe-inline' https:",
9
+ "img-src https: data: blob:",
10
+ "media-src https: data: blob:",
11
+ "font-src https: data:",
12
+ "connect-src https:",
13
+ "frame-src 'self' about:",
14
+ "frame-ancestors 'self'",
15
+ "base-uri 'none'",
16
+ "object-src 'none'",
17
+ "form-action https:"
18
+ ].join("; ");
19
+ const HTML_PREVIEW_SHELL = `<!doctype html>
20
+ <html><head><meta charset="utf-8"><title>HTML preview</title>
21
+ <style>html,body{height:100%;margin:0;overflow:hidden}iframe{display:block;width:100%;height:100%;border:0}</style>
22
+ </head><body><iframe title="HTML preview" sandbox="${HTML_PREVIEW_SANDBOX}"></iframe>
23
+ <script>
24
+ const preview = document.querySelector('iframe');
25
+ window.addEventListener('message', (event) => {
26
+ if (event.source !== window.parent || window.parent === window) return;
27
+ const data = event.data;
28
+ if (!data || data.type !== 'xopc-html-preview' || typeof data.html !== 'string') return;
29
+ // Repeated load notifications must not restart scripts or discard preview state.
30
+ if (preview.srcdoc !== data.html) preview.srcdoc = data.html;
31
+ });
32
+ <\/script></body></html>`;
33
+ /** Public static shell only: file contents arrive from the authenticated UI, never a URL. */
34
+ function registerPublicHtmlPreviewRoute(app) {
35
+ app.get(HTML_PREVIEW_PATH, (c) => c.html(HTML_PREVIEW_SHELL, 200, {
36
+ "Content-Security-Policy": HTML_PREVIEW_CSP,
37
+ "X-Frame-Options": "SAMEORIGIN",
38
+ "X-Content-Type-Options": "nosniff",
39
+ "Referrer-Policy": "no-referrer",
40
+ "Permissions-Policy": "camera=(), microphone=(), geolocation=()",
41
+ "Cache-Control": "no-store"
42
+ }));
43
+ }
44
+ //#endregion
45
+ export { HTML_PREVIEW_PATH, registerPublicHtmlPreviewRoute };
@@ -1,5 +1,6 @@
1
1
  import { loadCompactionSourceSnapshot } from "../../../storage/sqlite/transcript-repository.js";
2
2
  import "../../../storage/sqlite/index.js";
3
+ import { FileServiceError } from "../../../files/file-service.js";
3
4
  import { resolveGatewayEffectiveHost } from "../../../config/gateway-bind.js";
4
5
  import { SHARE_CONFIG_DEFAULTS } from "../../../share/share-types.js";
5
6
  import { resolveSiteShareConfig } from "../../../share/site-share-config.js";
@@ -11,6 +12,7 @@ import { resolveReverseProxyPublicUrl } from "../../public-url.js";
11
12
  import { audienceDefaults, cleanupStagedSite, decideShareKind, forgetStagedSite, makeDescription, makeTitle, probeShareTarget, rememberStagedSite, stageSingleHtmlAsSite } from "../../../share/share-auto.js";
12
13
  import { deleteThumbnail, placeholderSvg, readThumbnail, scheduleThumbnail, thumbnailContentType, thumbnailExists } from "../../../share/share-thumbnail.js";
13
14
  import { extractToken } from "../../auth.js";
15
+ import { getGatewayFileSpaceService } from "../../file-space-service.js";
14
16
  import { getClientIpFromHeaders } from "../../security/loopback.js";
15
17
  import { renderFolderLandingPage, renderShareExpiredPage, renderShareLandingPage, renderSnapshotShareLandingPage } from "../../../share/share-landing.js";
16
18
  import { SessionShareService, SessionShareSnapshotConflictError } from "../../../share/session-share-service.js";
@@ -902,10 +904,11 @@ function registerShareRoutes(authenticated, deps) {
902
904
  error: { message: "Invalid JSON" }
903
905
  }, 400);
904
906
  }
905
- const path = typeof body.path === "string" ? body.path.trim() : "";
906
- if (!path) return c.json({
907
+ const fileId = typeof body.fileId === "string" ? body.fileId.trim() : "";
908
+ let path = typeof body.path === "string" ? body.path.trim() : "";
909
+ if (!fileId && !path) return c.json({
907
910
  ok: false,
908
- error: { message: "Missing path" }
911
+ error: { message: "Missing path or fileId" }
909
912
  }, 400);
910
913
  const sessionKey = typeof body.sessionKey === "string" ? body.sessionKey.trim() : void 0;
911
914
  const agentId = typeof body.agentId === "string" ? body.agentId.trim() : void 0;
@@ -921,20 +924,25 @@ function registerShareRoutes(authenticated, deps) {
921
924
  const ttlOverride = typeof body.ttlMs === "number" ? body.ttlMs : void 0;
922
925
  const maxViewsOverride = body.maxViews === null ? null : typeof body.maxViews === "number" ? body.maxViews : void 0;
923
926
  const wantThumbnail = body.thumbnail !== false;
924
- const workspaceRoot = await resolveWorkspaceRootForShare(service, sessionKey, agentId);
925
- if (!workspaceRoot) return c.json({
926
- ok: false,
927
- error: { message: "Workspace not configured" }
928
- }, 400);
927
+ let workspaceRoot;
929
928
  let probe;
930
929
  try {
930
+ if (fileId) {
931
+ const target = await getGatewayFileSpaceService(service).resource(fileId);
932
+ workspaceRoot = target.space.root;
933
+ path = target.resource.relativePath || ".";
934
+ } else workspaceRoot = await resolveWorkspaceRootForShare(service, sessionKey, agentId);
935
+ if (!workspaceRoot) return c.json({
936
+ ok: false,
937
+ error: { message: "Workspace not configured" }
938
+ }, 400);
931
939
  probe = await probeShareTarget(workspaceRoot, path);
932
940
  } catch (err) {
933
941
  const message = err instanceof Error ? err.message : String(err);
934
942
  return c.json({
935
943
  ok: false,
936
944
  error: { message }
937
- }, 400);
945
+ }, err instanceof FileServiceError ? err.status : 400);
938
946
  }
939
947
  let decision;
940
948
  try {
@@ -1438,7 +1446,7 @@ async function createFileShareResponse(args) {
1438
1446
  }
1439
1447
  function relPathFromAbs(workspaceRoot, abs) {
1440
1448
  const root = workspaceRoot.replace(/[\\/]+$/, "");
1441
- if (abs === root) return "";
1449
+ if (abs === root) return ".";
1442
1450
  if (abs.startsWith(`${root}/`)) return abs.slice(root.length + 1);
1443
1451
  if (abs.startsWith(`${root}\\`)) return abs.slice(root.length + 1).replace(/\\/g, "/");
1444
1452
  return abs.split(/[\\/]/).pop() ?? abs;
@@ -1,12 +1,12 @@
1
1
  import { runSqliteWriteTransaction } from "../../../storage/sqlite/transaction.js";
2
+ import { TaskPhaseSchema } from "../../../../packages/gateway-contract/src/task-lifecycle.js";
3
+ import { TaskBoardPositionRequestSchema, TaskCommandRequestSchema, TaskContextInputSchema, TaskCreateRequestSchema, TaskDependencyUpdateRequestSchema, TaskHandoffRequestSchema, TaskPatchRequestSchema } from "../../../../packages/gateway-contract/src/tasks.js";
4
+ import { ProjectMonitoringUpdateSchema } from "../../../../packages/gateway-contract/src/project-monitoring.js";
2
5
  import { TaskRepository } from "../../../tasks/task-repository.js";
3
6
  import { TaskConversationRepository } from "../../../tasks/task-conversation-repository.js";
4
7
  import { enqueueTaskChangedEvent } from "../../../tasks/task-change-events.js";
5
8
  import { TaskRunRepository } from "../../../tasks/task-run-repository.js";
6
9
  import { TaskReadModelProjector } from "../../../tasks/task-read-model-projector.js";
7
- import { TaskPhaseSchema } from "../../../../packages/gateway-contract/src/task-lifecycle.js";
8
- import { TaskBoardPositionRequestSchema, TaskCommandRequestSchema, TaskContextInputSchema, TaskCreateRequestSchema, TaskDependencyUpdateRequestSchema, TaskHandoffRequestSchema, TaskPatchRequestSchema } from "../../../../packages/gateway-contract/src/tasks.js";
9
- import { ProjectMonitoringUpdateSchema } from "../../../../packages/gateway-contract/src/project-monitoring.js";
10
10
  import { resolveProjectAgentId } from "../../../projects/project-agent.js";
11
11
  import { TaskContextRepository } from "../../../tasks/task-context-repository.js";
12
12
  import { TaskDependencyError, TaskDependencyService } from "../../../tasks/task-dependency-service.js";
@@ -11,7 +11,7 @@ export declare function computeInlineScriptHashes(html: string): string[];
11
11
  * - `style-src 'self' 'unsafe-inline'` (Tailwind + runtime style injection)
12
12
  * - `media-src 'self' blob:` (chat voice previews / recorded clips use blob URLs)
13
13
  * - `frame-ancestors 'none'` (prevent clickjacking)
14
- * - `frame-src 'none'` (the console never embeds frames)
14
+ * - `frame-src 'self'` (sandboxed previews and extension UI)
15
15
  * - `form-action 'self'` (prevent form submissions to untrusted origins)
16
16
  * - `base-uri 'none'` (prevent base tag hijacking)
17
17
  * - `object-src 'none'` (prevent plugin execution)
@@ -29,7 +29,7 @@ function hasSrcAttribute(openTag) {
29
29
  * - `style-src 'self' 'unsafe-inline'` (Tailwind + runtime style injection)
30
30
  * - `media-src 'self' blob:` (chat voice previews / recorded clips use blob URLs)
31
31
  * - `frame-ancestors 'none'` (prevent clickjacking)
32
- * - `frame-src 'none'` (the console never embeds frames)
32
+ * - `frame-src 'self'` (sandboxed previews and extension UI)
33
33
  * - `form-action 'self'` (prevent form submissions to untrusted origins)
34
34
  * - `base-uri 'none'` (prevent base tag hijacking)
35
35
  * - `object-src 'none'` (prevent plugin execution)
@@ -41,7 +41,7 @@ function buildGatewayConsoleCspHeader(options) {
41
41
  "base-uri 'none'",
42
42
  "object-src 'none'",
43
43
  "frame-ancestors 'none'",
44
- "frame-src 'none'",
44
+ "frame-src 'self'",
45
45
  "form-action 'self'",
46
46
  hashes?.length ? `script-src 'self' ${hashes.map((hash) => `'${hash}'`).join(" ")}` : "script-src 'self'",
47
47
  "style-src 'self' 'unsafe-inline'",
@@ -18,6 +18,9 @@ import { loadConfig, saveConfig } from "../config/loader.js";
18
18
  import { buildSessionKey, init_session_key, sanitizeSegment } from "../routing/session-key.js";
19
19
  import { getDefaultAgentId, init_resolve_route } from "../routing/resolve-route.js";
20
20
  import { init_providers, providers_exports } from "../providers/index.js";
21
+ import { AutomationService } from "../automations/service/automation-service.js";
22
+ import { onAutomationProductEvent, publishAutomationProductEvent } from "../automations/product-events.js";
23
+ import "../automations/index.js";
21
24
  import { TaskRepository } from "../tasks/task-repository.js";
22
25
  import { TaskConversationRepository } from "../tasks/task-conversation-repository.js";
23
26
  import { disposeAllSessionMcpRuntimes } from "../agent/mcp/bundle-mcp-runtime.js";
@@ -25,9 +28,6 @@ import { ProjectService } from "../projects/project-service.js";
25
28
  import { resolveProjectAgentId } from "../projects/project-agent.js";
26
29
  import "../projects/index.js";
27
30
  import { resolveEffectiveGatewayPort } from "./host.js";
28
- import { AutomationService } from "../automations/service/automation-service.js";
29
- import { onAutomationProductEvent, publishAutomationProductEvent } from "../automations/product-events.js";
30
- import "../automations/index.js";
31
31
  import { TaskApplicationService } from "../tasks/task-application-service.js";
32
32
  import { ProactiveScenarioService } from "../proactive/scenarios/service.js";
33
33
  import { TaskOutboxDispatcher } from "../tasks/task-outbox-dispatcher.js";
@@ -45,11 +45,11 @@ import { MessageBus, MessageBusShutdownError } from "../infra/bus/queue.js";
45
45
  import "../infra/bus/index.js";
46
46
  import { AgentService } from "../agent/service.js";
47
47
  import { ensureStarterAgentsInitialized } from "../agent/starter-agents.js";
48
+ import { getWorkspacePath } from "../config/workspace-path-helpers.js";
49
+ import "../config/index.js";
48
50
  import { setPairingBroadcastSink } from "../channels/pairing/pairing-events.js";
49
51
  import { registerClarifyBridge } from "./clarify-runtime.js";
50
52
  import { ChannelManager } from "../channels/manager.js";
51
- import { getWorkspacePath } from "../config/workspace-path-helpers.js";
52
- import "../config/index.js";
53
53
  import { ManagedComposioEventPoller } from "../connectors/composio-managed-events.js";
54
54
  import { startConnectedKnowledgeCoordinator } from "../knowledge/coordinator.js";
55
55
  import "../knowledge/index.js";
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, shouldSilence, stripHeartbeatToken } from "./tokens.js";
2
- import { appendCronEventLines } from "./event-prompt.js";
3
2
  import { isWithinActiveHours } from "./active-hours.js";
4
3
  import { isHeartbeatContentEmpty } from "./content-check.js";
4
+ import { appendCronEventLines } from "./event-prompt.js";
5
5
  import { createHeartbeatWake } from "./wake.js";
6
6
  export { DEFAULT_ACK_MAX_CHARS, HEARTBEAT_OK, NO_REPLY, appendCronEventLines, createHeartbeatWake, isHeartbeatContentEmpty, isWithinActiveHours, shouldSilence, stripHeartbeatToken };
@@ -2,8 +2,8 @@ import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
3
  import { init_paths, resolveConfigPath } from "../config/paths.js";
4
4
  import { init_loader, loadConfig } from "../config/loader.js";
5
- import { isRestartEnabled } from "../config/commands.flags.js";
6
5
  import { resolveGatewayService } from "../daemon/service.js";
6
+ import { isRestartEnabled } from "../config/commands.flags.js";
7
7
  import { authorizeGatewaySigusr1Restart, writeGatewayRestartIntentSync } from "./restart.js";
8
8
  import { findVerifiedGatewayListenerPidsOnPortSync, signalVerifiedGatewayPidSync } from "./gateway-processes.js";
9
9
  import { refreshGatewayServiceAfterUpdate } from "./update-service-refresh.js";
@@ -1,5 +1,10 @@
1
1
  import type { SessionAgentConfig } from '../../session/config-types.js';
2
2
  export declare function getSessionConfig(sessionKey: string): SessionAgentConfig | null;
3
+ /** Persisted file locations must be discoverable before a session runs again. */
4
+ export declare function listSessionWorkspaceOverrides(): Array<{
5
+ sessionKey: string;
6
+ workingDirectoryOverride: string;
7
+ }>;
3
8
  export declare function setSessionConfig(sessionKey: string, config: SessionAgentConfig, cwd: string): SessionAgentConfig;
4
9
  export declare function updateSessionConfig(sessionKey: string, partial: Partial<SessionAgentConfig>, cwd: string): SessionAgentConfig;
5
10
  export declare function deleteSessionConfig(sessionKey: string): void;
@@ -13,6 +13,13 @@ function getSessionConfig(sessionKey) {
13
13
  if (!row) return null;
14
14
  return sessionConfigRowToConfig(row);
15
15
  }
16
+ /** Persisted file locations must be discoverable before a session runs again. */
17
+ function listSessionWorkspaceOverrides() {
18
+ return getSqliteDatabase().prepare(`
19
+ SELECT session_key AS sessionKey, working_directory_override AS workingDirectoryOverride
20
+ FROM session_config WHERE length(trim(working_directory_override)) > 0
21
+ `).all();
22
+ }
16
23
  function setSessionConfig(sessionKey, config, cwd) {
17
24
  return runSqliteWriteTransaction((db) => {
18
25
  ensureSessionInTransaction(db, sessionKey, cwd);
@@ -54,4 +61,4 @@ function hasSessionConfig(sessionKey) {
54
61
  return getSqliteDatabase().prepare(`SELECT 1 AS ok FROM session_config WHERE session_key = ?`).get(sessionKey)?.ok === 1;
55
62
  }
56
63
  //#endregion
57
- export { deleteSessionConfig, getSessionConfig, hasSessionConfig, setSessionConfig, updateSessionConfig };
64
+ export { deleteSessionConfig, getSessionConfig, hasSessionConfig, listSessionWorkspaceOverrides, setSessionConfig, updateSessionConfig };
@@ -2,10 +2,10 @@ import { isHomeAttentionAcknowledged } from "../storage/sqlite/home-attention-re
2
2
  import { getRelationshipSettings } from "../storage/sqlite/relationship-settings-repository.js";
3
3
  import { listConnectorApprovals } from "../storage/sqlite/connector-repository.js";
4
4
  import "../storage/sqlite/index.js";
5
- import { TaskRepository } from "./task-repository.js";
6
- import { TaskReadModelProjector } from "./task-read-model-projector.js";
7
5
  import "../automations/domain/defaults.js";
8
6
  import "../automations/index.js";
7
+ import { TaskRepository } from "./task-repository.js";
8
+ import { TaskReadModelProjector } from "./task-read-model-projector.js";
9
9
  import { AttentionGovernor } from "./attention-governor.js";
10
10
  import { listGatewayAgents } from "../gateway/agents-admin.js";
11
11
  //#region src/tasks/home-query-service.ts
@@ -1,7 +1,7 @@
1
1
  import { getSqliteDatabase } from "../storage/sqlite/transaction.js";
2
+ import { TaskExecutorSelectionSchema } from "../../packages/gateway-contract/src/tasks.js";
2
3
  import { TaskRepository } from "./task-repository.js";
3
4
  import { TaskRunRepository } from "./task-run-repository.js";
4
- import { TaskExecutorSelectionSchema } from "../../packages/gateway-contract/src/tasks.js";
5
5
  import { TaskApplicationService } from "./task-application-service.js";
6
6
  import { randomUUID } from "node:crypto";
7
7
  //#region src/tasks/task-signal-service.ts
@@ -23,9 +23,9 @@ import { parseModelRef } from "../../agent/models/selection.js";
23
23
  import { buildReviewContext, resolveGitRoot } from "../../review/review-git.js";
24
24
  import { MessageBus, MessageBusShutdownError } from "../../infra/bus/queue.js";
25
25
  import "../../infra/bus/index.js";
26
- import { prependEnvelopeTimestamp } from "../../channels/envelope-timestamp.js";
27
26
  import { getWorkspacePath } from "../../config/workspace-path-helpers.js";
28
27
  import "../../config/index.js";
28
+ import { prependEnvelopeTimestamp } from "../../channels/envelope-timestamp.js";
29
29
  import { buildWorkflowChildTools } from "../../agent/workflow/workflow-child-tools.js";
30
30
  import { WorkflowRunService } from "../../workflows/service/workflow-run-service.js";
31
31
  import { WorkflowSessionBridge } from "../../workflows/service/workflow-session-bridge.js";
@@ -1,11 +1,11 @@
1
1
  import { getDefaultAgentId, init_resolve_route } from "../../routing/resolve-route.js";
2
2
  import { init_providers, resolveModel } from "../../providers/index.js";
3
+ import { publishAutomationProductEvent } from "../../automations/product-events.js";
3
4
  import { extractProfileAgentId, resolveEffectiveAgentProfileForSession } from "../../config/agent-profile.js";
4
5
  import { TaskRepository } from "../../tasks/task-repository.js";
5
6
  import { TaskConversationRepository } from "../../tasks/task-conversation-repository.js";
6
7
  import { TaskRunRepository } from "../../tasks/task-run-repository.js";
7
8
  import { getProjectWorkspacePathForSession } from "../../projects/workspace.js";
8
- import { publishAutomationProductEvent } from "../../automations/product-events.js";
9
9
  import { TaskContextRepository } from "../../tasks/task-context-repository.js";
10
10
  import { TaskApplicationService } from "../../tasks/task-application-service.js";
11
11
  import { resolveModelSelector } from "../../config/agent-model-intents.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xopcai/xopc",
3
- "version": "0.0.234",
3
+ "version": "0.0.235",
4
4
  "description": "Local-first personal AI assistant with reviewable user understanding, durable tasks, workflows, automations, and local or cloud models.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -163,10 +163,10 @@
163
163
  "vitepress": "^1.6.4",
164
164
  "vitest": "4.1.5",
165
165
  "vue": "^3.5.35",
166
- "@xopcai/gateway-contract": "0.0.0",
167
- "@xopcai/realtime-protocol": "0.0.0",
168
166
  "@xopcai/endpoint-tools-protocol": "0.0.0",
169
- "@xopcai/realtime-client": "0.0.0"
167
+ "@xopcai/realtime-protocol": "0.0.0",
168
+ "@xopcai/realtime-client": "0.0.0",
169
+ "@xopcai/gateway-contract": "0.0.0"
170
170
  },
171
171
  "scripts": {
172
172
  "assets:brand": "node scripts/generate-brand-assets.mjs",
@@ -1 +0,0 @@
1
- import{$n as e}from"./index-CXGQihfj.js";function t(t){let{children:n,fallback:r}=t,i=r===void 0?null:r;return e()?n:i}export{t};