@agent-native/core 0.49.12 → 0.49.13
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/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/recap.d.ts +37 -0
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +240 -0
- package/dist/cli/recap.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +5 -10
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/blocks/library/question-form.js +1 -1
- package/dist/client/blocks/library/question-form.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +4 -0
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +12 -4
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.js +14 -9
- package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js +6 -4
- package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
- package/dist/client/extensions/iframe-bridge.d.ts +8 -0
- package/dist/client/extensions/iframe-bridge.d.ts.map +1 -1
- package/dist/client/extensions/iframe-bridge.js +54 -0
- package/dist/client/extensions/iframe-bridge.js.map +1 -1
- package/dist/client/progress/RunsTray.d.ts.map +1 -1
- package/dist/client/progress/RunsTray.js +12 -3
- package/dist/client/progress/RunsTray.js.map +1 -1
- package/dist/client/resources/ResourceEditor.d.ts +1 -3
- package/dist/client/resources/ResourceEditor.d.ts.map +1 -1
- package/dist/client/resources/ResourceEditor.js +8 -23
- package/dist/client/resources/ResourceEditor.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +4 -9
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.d.ts.map +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +5 -1
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +15 -7
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
- package/dist/client/sharing/ShareDialog.js +6 -2
- package/dist/client/sharing/ShareDialog.js.map +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +70 -2
- package/dist/extensions/actions.js.map +1 -1
- package/dist/extensions/html-shell.d.ts +12 -0
- package/dist/extensions/html-shell.d.ts.map +1 -1
- package/dist/extensions/html-shell.js.map +1 -1
- package/dist/extensions/local.d.ts +35 -0
- package/dist/extensions/local.d.ts.map +1 -0
- package/dist/extensions/local.js +334 -0
- package/dist/extensions/local.js.map +1 -0
- package/dist/extensions/routes.d.ts.map +1 -1
- package/dist/extensions/routes.js +92 -12
- package/dist/extensions/routes.js.map +1 -1
- package/dist/extensions/slots/store.d.ts.map +1 -1
- package/dist/extensions/slots/store.js +72 -4
- package/dist/extensions/slots/store.js.map +1 -1
- package/dist/local-artifacts/index.d.ts +4 -0
- package/dist/local-artifacts/index.d.ts.map +1 -1
- package/dist/local-artifacts/index.js +60 -35
- package/dist/local-artifacts/index.js.map +1 -1
- package/docs/content/extensions.md +65 -0
- package/docs/content/local-file-mode.md +153 -0
- package/docs/content/template-content.md +51 -4
- package/package.json +1 -1
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import fsSync from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { loadAgentNativeManifest, resolveAgentNativeDataMode, } from "../local-artifacts/index.js";
|
|
5
|
+
const LOCAL_EXTENSION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,79}$/;
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function errorCode(error) {
|
|
10
|
+
return isRecord(error) && typeof error.code === "string"
|
|
11
|
+
? error.code
|
|
12
|
+
: undefined;
|
|
13
|
+
}
|
|
14
|
+
function asStringArray(value) {
|
|
15
|
+
if (typeof value === "string" && value.trim())
|
|
16
|
+
return [value.trim()];
|
|
17
|
+
if (!Array.isArray(value))
|
|
18
|
+
return [];
|
|
19
|
+
return value.filter((item) => typeof item === "string");
|
|
20
|
+
}
|
|
21
|
+
function normalizeSlash(filePath) {
|
|
22
|
+
return filePath.replace(/\\/g, "/");
|
|
23
|
+
}
|
|
24
|
+
function normalizeRelativePath(filePath, label = "path") {
|
|
25
|
+
if (!filePath || typeof filePath !== "string") {
|
|
26
|
+
throw new Error(`${label} is required`);
|
|
27
|
+
}
|
|
28
|
+
if (filePath.includes("\0")) {
|
|
29
|
+
throw new Error(`${label} must not contain null bytes`);
|
|
30
|
+
}
|
|
31
|
+
if (path.isAbsolute(filePath)) {
|
|
32
|
+
throw new Error(`${label} must be relative`);
|
|
33
|
+
}
|
|
34
|
+
const normalized = normalizeSlash(path.posix.normalize(normalizeSlash(filePath)));
|
|
35
|
+
if (!normalized ||
|
|
36
|
+
normalized === "." ||
|
|
37
|
+
normalized === ".." ||
|
|
38
|
+
normalized.startsWith("../") ||
|
|
39
|
+
normalized.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
40
|
+
throw new Error(`${label} must be a safe relative path`);
|
|
41
|
+
}
|
|
42
|
+
return normalized;
|
|
43
|
+
}
|
|
44
|
+
function resolveInside(basePath, relativePath, label) {
|
|
45
|
+
const safePath = normalizeRelativePath(relativePath, label);
|
|
46
|
+
const absolutePath = path.resolve(basePath, safePath);
|
|
47
|
+
const relative = path.relative(basePath, absolutePath);
|
|
48
|
+
if (relative === "" ||
|
|
49
|
+
relative.startsWith("..") ||
|
|
50
|
+
path.isAbsolute(relative)) {
|
|
51
|
+
throw new Error(`${label} "${relativePath}" is outside the workspace`);
|
|
52
|
+
}
|
|
53
|
+
return { safePath, absolutePath };
|
|
54
|
+
}
|
|
55
|
+
function noFollowOpenFlags() {
|
|
56
|
+
return fsSync.constants.O_RDONLY | (fsSync.constants.O_NOFOLLOW ?? 0);
|
|
57
|
+
}
|
|
58
|
+
function assertNoSymlinkPathSync(rootPath, absolutePath) {
|
|
59
|
+
const relative = path.relative(rootPath, absolutePath);
|
|
60
|
+
const segments = relative.split(path.sep).filter(Boolean);
|
|
61
|
+
let current = rootPath;
|
|
62
|
+
const pathsToCheck = [
|
|
63
|
+
current,
|
|
64
|
+
...segments.map((segment) => {
|
|
65
|
+
current = path.join(current, segment);
|
|
66
|
+
return current;
|
|
67
|
+
}),
|
|
68
|
+
];
|
|
69
|
+
for (const candidate of pathsToCheck) {
|
|
70
|
+
const stat = fsSync.lstatSync(candidate);
|
|
71
|
+
if (stat.isSymbolicLink()) {
|
|
72
|
+
throw new Error(`Path "${candidate}" must not traverse a symlink`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function readTextFileWithoutSymlink(rootPath, filePath) {
|
|
77
|
+
assertNoSymlinkPathSync(rootPath, filePath);
|
|
78
|
+
const fd = fsSync.openSync(filePath, noFollowOpenFlags());
|
|
79
|
+
try {
|
|
80
|
+
return {
|
|
81
|
+
content: fsSync.readFileSync(fd, "utf8"),
|
|
82
|
+
stat: fsSync.fstatSync(fd),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
fsSync.closeSync(fd);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function normalizeExtensionId(rawId, manifestPath) {
|
|
90
|
+
const id = rawId.trim();
|
|
91
|
+
if (!LOCAL_EXTENSION_ID_RE.test(id)) {
|
|
92
|
+
throw new Error(`Local extension id in ${manifestPath} must match ${LOCAL_EXTENSION_ID_RE}`);
|
|
93
|
+
}
|
|
94
|
+
return id;
|
|
95
|
+
}
|
|
96
|
+
function titleFromId(id) {
|
|
97
|
+
return (id
|
|
98
|
+
.replace(/[-_]+/g, " ")
|
|
99
|
+
.replace(/\s+/g, " ")
|
|
100
|
+
.trim()
|
|
101
|
+
.replace(/\b\w/g, (char) => char.toUpperCase()) || id);
|
|
102
|
+
}
|
|
103
|
+
function normalizeActionPermission(value) {
|
|
104
|
+
const trimmed = value.trim();
|
|
105
|
+
if (!trimmed)
|
|
106
|
+
return null;
|
|
107
|
+
if (trimmed === "*")
|
|
108
|
+
return "*";
|
|
109
|
+
if (/^[A-Za-z0-9_.:-]+$/.test(trimmed))
|
|
110
|
+
return trimmed;
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
function normalizePermissions(value) {
|
|
114
|
+
const permissions = {
|
|
115
|
+
appActions: [],
|
|
116
|
+
extensionData: true,
|
|
117
|
+
sql: false,
|
|
118
|
+
externalFetch: false,
|
|
119
|
+
};
|
|
120
|
+
const appActions = new Set();
|
|
121
|
+
const addAction = (action) => {
|
|
122
|
+
const normalized = normalizeActionPermission(action);
|
|
123
|
+
if (normalized)
|
|
124
|
+
appActions.add(normalized);
|
|
125
|
+
};
|
|
126
|
+
if (Array.isArray(value)) {
|
|
127
|
+
for (const item of value) {
|
|
128
|
+
if (typeof item !== "string")
|
|
129
|
+
continue;
|
|
130
|
+
if (item === "extensionData" || item === "storage") {
|
|
131
|
+
permissions.extensionData = true;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (item === "sql" || item === "dbQuery" || item === "dbExec") {
|
|
135
|
+
permissions.sql = true;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (item === "externalFetch" || item === "extensionFetch") {
|
|
139
|
+
permissions.externalFetch = true;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const actionMatch = item.match(/^(?:appAction|action):(.+)$/);
|
|
143
|
+
if (actionMatch?.[1])
|
|
144
|
+
addAction(actionMatch[1]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else if (isRecord(value)) {
|
|
148
|
+
const rawActions = value.appActions === "*" || value.actions === "*"
|
|
149
|
+
? ["*"]
|
|
150
|
+
: [...asStringArray(value.appActions), ...asStringArray(value.actions)];
|
|
151
|
+
for (const action of rawActions)
|
|
152
|
+
addAction(action);
|
|
153
|
+
if (typeof value.extensionData === "boolean") {
|
|
154
|
+
permissions.extensionData = value.extensionData;
|
|
155
|
+
}
|
|
156
|
+
if (typeof value.storage === "boolean") {
|
|
157
|
+
permissions.extensionData = value.storage;
|
|
158
|
+
}
|
|
159
|
+
if (typeof value.sql === "boolean")
|
|
160
|
+
permissions.sql = value.sql;
|
|
161
|
+
if (typeof value.externalFetch === "boolean") {
|
|
162
|
+
permissions.externalFetch = value.externalFetch;
|
|
163
|
+
}
|
|
164
|
+
if (typeof value.extensionFetch === "boolean") {
|
|
165
|
+
permissions.externalFetch = value.extensionFetch;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
permissions.appActions = [...appActions].sort();
|
|
169
|
+
return permissions;
|
|
170
|
+
}
|
|
171
|
+
function manifestAppExtensions(app) {
|
|
172
|
+
return asStringArray(app.extensions);
|
|
173
|
+
}
|
|
174
|
+
function readJsonFile(rootPath, filePath) {
|
|
175
|
+
try {
|
|
176
|
+
const { content, stat } = readTextFileWithoutSymlink(rootPath, filePath);
|
|
177
|
+
return { value: JSON.parse(content), stat };
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (errorCode(error) === "ENOENT")
|
|
181
|
+
return null;
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function readLocalExtensionFolder(options) {
|
|
186
|
+
const extensionAbsolutePath = path.join(options.rootAbsolutePath, options.folderName);
|
|
187
|
+
const extensionRelativePath = normalizeSlash(path.relative(options.workspaceRoot, extensionAbsolutePath));
|
|
188
|
+
const manifestAbsolutePath = path.join(extensionAbsolutePath, "extension.json");
|
|
189
|
+
const manifestRead = readJsonFile(extensionAbsolutePath, manifestAbsolutePath);
|
|
190
|
+
if (!manifestRead)
|
|
191
|
+
return null;
|
|
192
|
+
const rawManifest = manifestRead.value;
|
|
193
|
+
if (!isRecord(rawManifest)) {
|
|
194
|
+
throw new Error(`Local extension manifest must be an object: ${manifestAbsolutePath}`);
|
|
195
|
+
}
|
|
196
|
+
const manifest = rawManifest;
|
|
197
|
+
const manifestRelativePath = normalizeSlash(path.relative(options.workspaceRoot, manifestAbsolutePath));
|
|
198
|
+
const id = normalizeExtensionId(typeof manifest.id === "string" ? manifest.id : options.folderName, manifestAbsolutePath);
|
|
199
|
+
const entry = String(typeof manifest.entry === "string"
|
|
200
|
+
? manifest.entry
|
|
201
|
+
: typeof manifest.main === "string"
|
|
202
|
+
? manifest.main
|
|
203
|
+
: "index.html");
|
|
204
|
+
const { safePath: entrySafePath, absolutePath: entryAbsolutePath } = resolveInside(extensionAbsolutePath, entry, "entry");
|
|
205
|
+
const { content, stat: entryStat } = readTextFileWithoutSymlink(extensionAbsolutePath, entryAbsolutePath);
|
|
206
|
+
const manifestStat = manifestRead.stat;
|
|
207
|
+
const updatedAt = new Date(Math.max(manifestStat.mtimeMs, entryStat.mtimeMs)).toISOString();
|
|
208
|
+
const createdAt = new Date(Math.min(manifestStat.birthtimeMs, entryStat.birthtimeMs)).toISOString();
|
|
209
|
+
const slots = [
|
|
210
|
+
...new Set([
|
|
211
|
+
...asStringArray(manifest.slots),
|
|
212
|
+
...asStringArray(manifest.slot),
|
|
213
|
+
]),
|
|
214
|
+
].sort();
|
|
215
|
+
const permissions = normalizePermissions(manifest.permissions);
|
|
216
|
+
const name = typeof manifest.name === "string" && manifest.name.trim()
|
|
217
|
+
? manifest.name.trim()
|
|
218
|
+
: titleFromId(id);
|
|
219
|
+
return {
|
|
220
|
+
id,
|
|
221
|
+
name,
|
|
222
|
+
description: typeof manifest.description === "string" ? manifest.description : "",
|
|
223
|
+
content,
|
|
224
|
+
icon: typeof manifest.icon === "string" ? manifest.icon : null,
|
|
225
|
+
createdAt,
|
|
226
|
+
updatedAt,
|
|
227
|
+
hiddenAt: null,
|
|
228
|
+
hiddenBy: null,
|
|
229
|
+
ownerEmail: "local-files",
|
|
230
|
+
orgId: null,
|
|
231
|
+
visibility: "private",
|
|
232
|
+
source: {
|
|
233
|
+
mode: "local-files",
|
|
234
|
+
appId: options.appId,
|
|
235
|
+
rootPath: options.rootPath,
|
|
236
|
+
extensionPath: extensionRelativePath,
|
|
237
|
+
manifestPath: manifestRelativePath,
|
|
238
|
+
entryPath: normalizeSlash(path.posix.join(extensionRelativePath, entrySafePath)),
|
|
239
|
+
permissions,
|
|
240
|
+
slots,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
async function listLocalExtensionsForRoot(options) {
|
|
245
|
+
let rootStat;
|
|
246
|
+
try {
|
|
247
|
+
rootStat = await fs.lstat(options.rootAbsolutePath);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
if (errorCode(error) === "ENOENT")
|
|
251
|
+
return [];
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
if (!rootStat.isDirectory() || rootStat.isSymbolicLink())
|
|
255
|
+
return [];
|
|
256
|
+
const entries = await fs.readdir(options.rootAbsolutePath, {
|
|
257
|
+
withFileTypes: true,
|
|
258
|
+
});
|
|
259
|
+
const rows = [];
|
|
260
|
+
for (const entry of entries) {
|
|
261
|
+
if (!entry.isDirectory() ||
|
|
262
|
+
entry.isSymbolicLink() ||
|
|
263
|
+
entry.name.startsWith(".")) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const row = await readLocalExtensionFolder({
|
|
267
|
+
appId: options.appId,
|
|
268
|
+
workspaceRoot: options.workspaceRoot,
|
|
269
|
+
rootPath: options.rootPath,
|
|
270
|
+
rootAbsolutePath: options.rootAbsolutePath,
|
|
271
|
+
folderName: entry.name,
|
|
272
|
+
});
|
|
273
|
+
if (row)
|
|
274
|
+
rows.push(row);
|
|
275
|
+
}
|
|
276
|
+
return rows;
|
|
277
|
+
}
|
|
278
|
+
export async function listLocalExtensions(options = {}) {
|
|
279
|
+
const loaded = await loadAgentNativeManifest({ ...options, optional: true });
|
|
280
|
+
if (!loaded)
|
|
281
|
+
return [];
|
|
282
|
+
const rows = [];
|
|
283
|
+
const seenIds = new Map();
|
|
284
|
+
for (const [appId, app] of Object.entries(loaded.manifest.apps ?? {})) {
|
|
285
|
+
const extensionRoots = manifestAppExtensions(app);
|
|
286
|
+
if (extensionRoots.length === 0)
|
|
287
|
+
continue;
|
|
288
|
+
const mode = await resolveAgentNativeDataMode({
|
|
289
|
+
...options,
|
|
290
|
+
manifestPath: loaded.path,
|
|
291
|
+
appId,
|
|
292
|
+
defaults: { mode: app.mode },
|
|
293
|
+
});
|
|
294
|
+
if (mode !== "local-files")
|
|
295
|
+
continue;
|
|
296
|
+
for (const root of extensionRoots) {
|
|
297
|
+
const { safePath, absolutePath } = resolveInside(loaded.rootDir, root, "extensions");
|
|
298
|
+
rows.push(...(await listLocalExtensionsForRoot({
|
|
299
|
+
appId,
|
|
300
|
+
workspaceRoot: loaded.rootDir,
|
|
301
|
+
rootPath: safePath,
|
|
302
|
+
rootAbsolutePath: absolutePath,
|
|
303
|
+
})));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
for (const row of rows) {
|
|
307
|
+
const existingPath = seenIds.get(row.id);
|
|
308
|
+
if (existingPath) {
|
|
309
|
+
throw new Error(`Duplicate local extension id "${row.id}" in ${existingPath} and ${row.source.manifestPath}`);
|
|
310
|
+
}
|
|
311
|
+
seenIds.set(row.id, row.source.manifestPath);
|
|
312
|
+
}
|
|
313
|
+
return rows.sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id));
|
|
314
|
+
}
|
|
315
|
+
export async function getLocalExtension(id, options = {}) {
|
|
316
|
+
const rows = await listLocalExtensions(options);
|
|
317
|
+
return rows.find((row) => row.id === id) ?? null;
|
|
318
|
+
}
|
|
319
|
+
export function isLocalExtensionRow(row) {
|
|
320
|
+
return (isRecord(row) && isRecord(row.source) && row.source.mode === "local-files");
|
|
321
|
+
}
|
|
322
|
+
export function localExtensionSourceSummary(row) {
|
|
323
|
+
return {
|
|
324
|
+
mode: row.source.mode,
|
|
325
|
+
appId: row.source.appId,
|
|
326
|
+
rootPath: row.source.rootPath,
|
|
327
|
+
extensionPath: row.source.extensionPath,
|
|
328
|
+
manifestPath: row.source.manifestPath,
|
|
329
|
+
entryPath: row.source.entryPath,
|
|
330
|
+
slots: row.source.slots,
|
|
331
|
+
permissions: row.source.permissions,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
//# sourceMappingURL=local.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/extensions/local.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from \"node:fs/promises\";\nimport fsSync from \"node:fs\";\nimport path from \"node:path\";\nimport {\n loadAgentNativeManifest,\n resolveAgentNativeDataMode,\n type AgentNativeManifestApp,\n type LoadAgentNativeManifestOptions,\n} from \"../local-artifacts/index.js\";\nimport type { ExtensionRow } from \"./store.js\";\n\nexport interface LocalExtensionPermissions {\n appActions: string[];\n extensionData: boolean;\n sql: boolean;\n externalFetch: boolean;\n}\n\nexport interface LocalExtensionSource {\n mode: \"local-files\";\n appId: string;\n rootPath: string;\n extensionPath: string;\n manifestPath: string;\n entryPath: string;\n permissions: LocalExtensionPermissions;\n slots: string[];\n}\n\nexport interface LocalExtensionRow extends ExtensionRow {\n source: LocalExtensionSource;\n}\n\ninterface LocalExtensionManifest {\n id?: unknown;\n name?: unknown;\n description?: unknown;\n icon?: unknown;\n entry?: unknown;\n main?: unknown;\n slots?: unknown;\n slot?: unknown;\n permissions?: unknown;\n}\n\nconst LOCAL_EXTENSION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,79}$/;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return isRecord(error) && typeof error.code === \"string\"\n ? error.code\n : undefined;\n}\n\nfunction asStringArray(value: unknown): string[] {\n if (typeof value === \"string\" && value.trim()) return [value.trim()];\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is string => typeof item === \"string\");\n}\n\nfunction normalizeSlash(filePath: string): string {\n return filePath.replace(/\\\\/g, \"/\");\n}\n\nfunction normalizeRelativePath(filePath: string, label = \"path\"): string {\n if (!filePath || typeof filePath !== \"string\") {\n throw new Error(`${label} is required`);\n }\n if (filePath.includes(\"\\0\")) {\n throw new Error(`${label} must not contain null bytes`);\n }\n if (path.isAbsolute(filePath)) {\n throw new Error(`${label} must be relative`);\n }\n const normalized = normalizeSlash(\n path.posix.normalize(normalizeSlash(filePath)),\n );\n if (\n !normalized ||\n normalized === \".\" ||\n normalized === \"..\" ||\n normalized.startsWith(\"../\") ||\n normalized.split(\"/\").some((part) => !part || part === \".\" || part === \"..\")\n ) {\n throw new Error(`${label} must be a safe relative path`);\n }\n return normalized;\n}\n\nfunction resolveInside(basePath: string, relativePath: string, label: string) {\n const safePath = normalizeRelativePath(relativePath, label);\n const absolutePath = path.resolve(basePath, safePath);\n const relative = path.relative(basePath, absolutePath);\n if (\n relative === \"\" ||\n relative.startsWith(\"..\") ||\n path.isAbsolute(relative)\n ) {\n throw new Error(`${label} \"${relativePath}\" is outside the workspace`);\n }\n return { safePath, absolutePath };\n}\n\nfunction noFollowOpenFlags(): number {\n return fsSync.constants.O_RDONLY | (fsSync.constants.O_NOFOLLOW ?? 0);\n}\n\nfunction assertNoSymlinkPathSync(rootPath: string, absolutePath: string): void {\n const relative = path.relative(rootPath, absolutePath);\n const segments = relative.split(path.sep).filter(Boolean);\n let current = rootPath;\n const pathsToCheck = [\n current,\n ...segments.map((segment) => {\n current = path.join(current, segment);\n return current;\n }),\n ];\n\n for (const candidate of pathsToCheck) {\n const stat = fsSync.lstatSync(candidate);\n if (stat.isSymbolicLink()) {\n throw new Error(`Path \"${candidate}\" must not traverse a symlink`);\n }\n }\n}\n\nfunction readTextFileWithoutSymlink(rootPath: string, filePath: string) {\n assertNoSymlinkPathSync(rootPath, filePath);\n const fd = fsSync.openSync(filePath, noFollowOpenFlags());\n try {\n return {\n content: fsSync.readFileSync(fd, \"utf8\"),\n stat: fsSync.fstatSync(fd),\n };\n } finally {\n fsSync.closeSync(fd);\n }\n}\n\nfunction normalizeExtensionId(rawId: string, manifestPath: string): string {\n const id = rawId.trim();\n if (!LOCAL_EXTENSION_ID_RE.test(id)) {\n throw new Error(\n `Local extension id in ${manifestPath} must match ${LOCAL_EXTENSION_ID_RE}`,\n );\n }\n return id;\n}\n\nfunction titleFromId(id: string): string {\n return (\n id\n .replace(/[-_]+/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n .replace(/\\b\\w/g, (char) => char.toUpperCase()) || id\n );\n}\n\nfunction normalizeActionPermission(value: string): string | null {\n const trimmed = value.trim();\n if (!trimmed) return null;\n if (trimmed === \"*\") return \"*\";\n if (/^[A-Za-z0-9_.:-]+$/.test(trimmed)) return trimmed;\n return null;\n}\n\nfunction normalizePermissions(value: unknown): LocalExtensionPermissions {\n const permissions: LocalExtensionPermissions = {\n appActions: [],\n extensionData: true,\n sql: false,\n externalFetch: false,\n };\n const appActions = new Set<string>();\n\n const addAction = (action: string) => {\n const normalized = normalizeActionPermission(action);\n if (normalized) appActions.add(normalized);\n };\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (typeof item !== \"string\") continue;\n if (item === \"extensionData\" || item === \"storage\") {\n permissions.extensionData = true;\n continue;\n }\n if (item === \"sql\" || item === \"dbQuery\" || item === \"dbExec\") {\n permissions.sql = true;\n continue;\n }\n if (item === \"externalFetch\" || item === \"extensionFetch\") {\n permissions.externalFetch = true;\n continue;\n }\n const actionMatch = item.match(/^(?:appAction|action):(.+)$/);\n if (actionMatch?.[1]) addAction(actionMatch[1]);\n }\n } else if (isRecord(value)) {\n const rawActions =\n value.appActions === \"*\" || value.actions === \"*\"\n ? [\"*\"]\n : [...asStringArray(value.appActions), ...asStringArray(value.actions)];\n for (const action of rawActions) addAction(action);\n if (typeof value.extensionData === \"boolean\") {\n permissions.extensionData = value.extensionData;\n }\n if (typeof value.storage === \"boolean\") {\n permissions.extensionData = value.storage;\n }\n if (typeof value.sql === \"boolean\") permissions.sql = value.sql;\n if (typeof value.externalFetch === \"boolean\") {\n permissions.externalFetch = value.externalFetch;\n }\n if (typeof value.extensionFetch === \"boolean\") {\n permissions.externalFetch = value.extensionFetch;\n }\n }\n\n permissions.appActions = [...appActions].sort();\n return permissions;\n}\n\nfunction manifestAppExtensions(app: AgentNativeManifestApp): string[] {\n return asStringArray(app.extensions);\n}\n\nfunction readJsonFile(\n rootPath: string,\n filePath: string,\n): { value: unknown; stat: fsSync.Stats } | null {\n try {\n const { content, stat } = readTextFileWithoutSymlink(rootPath, filePath);\n return { value: JSON.parse(content) as unknown, stat };\n } catch (error) {\n if (errorCode(error) === \"ENOENT\") return null;\n throw error;\n }\n}\n\nasync function readLocalExtensionFolder(options: {\n appId: string;\n workspaceRoot: string;\n rootPath: string;\n rootAbsolutePath: string;\n folderName: string;\n}): Promise<LocalExtensionRow | null> {\n const extensionAbsolutePath = path.join(\n options.rootAbsolutePath,\n options.folderName,\n );\n const extensionRelativePath = normalizeSlash(\n path.relative(options.workspaceRoot, extensionAbsolutePath),\n );\n const manifestAbsolutePath = path.join(\n extensionAbsolutePath,\n \"extension.json\",\n );\n const manifestRead = readJsonFile(\n extensionAbsolutePath,\n manifestAbsolutePath,\n );\n if (!manifestRead) return null;\n const rawManifest = manifestRead.value;\n if (!isRecord(rawManifest)) {\n throw new Error(\n `Local extension manifest must be an object: ${manifestAbsolutePath}`,\n );\n }\n const manifest = rawManifest as LocalExtensionManifest;\n const manifestRelativePath = normalizeSlash(\n path.relative(options.workspaceRoot, manifestAbsolutePath),\n );\n const id = normalizeExtensionId(\n typeof manifest.id === \"string\" ? manifest.id : options.folderName,\n manifestAbsolutePath,\n );\n const entry = String(\n typeof manifest.entry === \"string\"\n ? manifest.entry\n : typeof manifest.main === \"string\"\n ? manifest.main\n : \"index.html\",\n );\n const { safePath: entrySafePath, absolutePath: entryAbsolutePath } =\n resolveInside(extensionAbsolutePath, entry, \"entry\");\n const { content, stat: entryStat } = readTextFileWithoutSymlink(\n extensionAbsolutePath,\n entryAbsolutePath,\n );\n const manifestStat = manifestRead.stat;\n const updatedAt = new Date(\n Math.max(manifestStat.mtimeMs, entryStat.mtimeMs),\n ).toISOString();\n const createdAt = new Date(\n Math.min(manifestStat.birthtimeMs, entryStat.birthtimeMs),\n ).toISOString();\n const slots = [\n ...new Set([\n ...asStringArray(manifest.slots),\n ...asStringArray(manifest.slot),\n ]),\n ].sort();\n const permissions = normalizePermissions(manifest.permissions);\n const name =\n typeof manifest.name === \"string\" && manifest.name.trim()\n ? manifest.name.trim()\n : titleFromId(id);\n\n return {\n id,\n name,\n description:\n typeof manifest.description === \"string\" ? manifest.description : \"\",\n content,\n icon: typeof manifest.icon === \"string\" ? manifest.icon : null,\n createdAt,\n updatedAt,\n hiddenAt: null,\n hiddenBy: null,\n ownerEmail: \"local-files\",\n orgId: null,\n visibility: \"private\",\n source: {\n mode: \"local-files\",\n appId: options.appId,\n rootPath: options.rootPath,\n extensionPath: extensionRelativePath,\n manifestPath: manifestRelativePath,\n entryPath: normalizeSlash(\n path.posix.join(extensionRelativePath, entrySafePath),\n ),\n permissions,\n slots,\n },\n };\n}\n\nasync function listLocalExtensionsForRoot(options: {\n appId: string;\n workspaceRoot: string;\n rootPath: string;\n rootAbsolutePath: string;\n}): Promise<LocalExtensionRow[]> {\n let rootStat;\n try {\n rootStat = await fs.lstat(options.rootAbsolutePath);\n } catch (error) {\n if (errorCode(error) === \"ENOENT\") return [];\n throw error;\n }\n if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) return [];\n\n const entries = await fs.readdir(options.rootAbsolutePath, {\n withFileTypes: true,\n });\n const rows: LocalExtensionRow[] = [];\n for (const entry of entries) {\n if (\n !entry.isDirectory() ||\n entry.isSymbolicLink() ||\n entry.name.startsWith(\".\")\n ) {\n continue;\n }\n const row = await readLocalExtensionFolder({\n appId: options.appId,\n workspaceRoot: options.workspaceRoot,\n rootPath: options.rootPath,\n rootAbsolutePath: options.rootAbsolutePath,\n folderName: entry.name,\n });\n if (row) rows.push(row);\n }\n return rows;\n}\n\nexport async function listLocalExtensions(\n options: LoadAgentNativeManifestOptions = {},\n): Promise<LocalExtensionRow[]> {\n const loaded = await loadAgentNativeManifest({ ...options, optional: true });\n if (!loaded) return [];\n\n const rows: LocalExtensionRow[] = [];\n const seenIds = new Map<string, string>();\n for (const [appId, app] of Object.entries(loaded.manifest.apps ?? {})) {\n const extensionRoots = manifestAppExtensions(app);\n if (extensionRoots.length === 0) continue;\n const mode = await resolveAgentNativeDataMode({\n ...options,\n manifestPath: loaded.path,\n appId,\n defaults: { mode: app.mode },\n });\n if (mode !== \"local-files\") continue;\n\n for (const root of extensionRoots) {\n const { safePath, absolutePath } = resolveInside(\n loaded.rootDir,\n root,\n \"extensions\",\n );\n rows.push(\n ...(await listLocalExtensionsForRoot({\n appId,\n workspaceRoot: loaded.rootDir,\n rootPath: safePath,\n rootAbsolutePath: absolutePath,\n })),\n );\n }\n }\n\n for (const row of rows) {\n const existingPath = seenIds.get(row.id);\n if (existingPath) {\n throw new Error(\n `Duplicate local extension id \"${row.id}\" in ${existingPath} and ${row.source.manifestPath}`,\n );\n }\n seenIds.set(row.id, row.source.manifestPath);\n }\n\n return rows.sort(\n (a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id),\n );\n}\n\nexport async function getLocalExtension(\n id: string,\n options: LoadAgentNativeManifestOptions = {},\n): Promise<LocalExtensionRow | null> {\n const rows = await listLocalExtensions(options);\n return rows.find((row) => row.id === id) ?? null;\n}\n\nexport function isLocalExtensionRow(\n row: ExtensionRow | LocalExtensionRow | null | undefined,\n): row is LocalExtensionRow {\n return (\n isRecord(row) && isRecord(row.source) && row.source.mode === \"local-files\"\n );\n}\n\nexport function localExtensionSourceSummary(row: LocalExtensionRow) {\n return {\n mode: row.source.mode,\n appId: row.source.appId,\n rootPath: row.source.rootPath,\n extensionPath: row.source.extensionPath,\n manifestPath: row.source.manifestPath,\n entryPath: row.source.entryPath,\n slots: row.source.slots,\n permissions: row.source.permissions,\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/extensions/routes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/extensions/routes.ts"],"names":[],"mappings":"AA8DA,wBAAgB,uBAAuB,2FAuCtC;AAizBD,eAAO,MAAM,kBAAkB,QACqa,CAAC;AAMrc,eAAO,MAAM,gBAAgB,QACwkB,CAAC;AAMtmB,eAAO,MAAM,oBAAoB,QACgB,CAAC;AAMlD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAQ/D"}
|
|
@@ -6,6 +6,7 @@ import { runWithRequestContext, getRequestOrgId, } from "../server/request-conte
|
|
|
6
6
|
import { getOrgContext } from "../org/context.js";
|
|
7
7
|
import { getDbExec, isPostgres } from "../db/client.js";
|
|
8
8
|
import { listExtensions, getExtension, getExtensionHistoryVersion, listExtensionHistory, createExtension, updateExtension, updateExtensionContent, restoreExtensionHistoryVersion, deleteExtension, hideExtension, unhideExtension, globalHideExtension, globalUnhideExtension, ensureExtensionsTables, } from "./store.js";
|
|
9
|
+
import { getLocalExtension, isLocalExtensionRow, listLocalExtensions, } from "./local.js";
|
|
9
10
|
import { buildExtensionHtml, EXTENSION_IFRAME_CSP } from "./html-shell.js";
|
|
10
11
|
import { getThemeVars } from "./theme.js";
|
|
11
12
|
import { resolveKeyReferencesWithRequestScopes, validateUrlAllowlist, getResolvedKeyAllowlist, } from "../secrets/substitution.js";
|
|
@@ -88,7 +89,8 @@ async function dispatch(event, method, parts, userEmail) {
|
|
|
88
89
|
const includeGloballyHidden = event.url?.searchParams?.get("includeGloballyHidden") === "true";
|
|
89
90
|
const includeContent = event.url?.searchParams?.get("includeContent") === "true";
|
|
90
91
|
const rows = await listExtensions({ includeGloballyHidden });
|
|
91
|
-
|
|
92
|
+
const localRows = includeGloballyHidden ? [] : await listLocalExtensions();
|
|
93
|
+
return Promise.all([...rows, ...localRows].map((row) => extensionResponse(row, undefined, { includeContent })));
|
|
92
94
|
}
|
|
93
95
|
// POST / — create
|
|
94
96
|
if (method === "POST" && parts.length === 0) {
|
|
@@ -103,6 +105,25 @@ async function dispatch(event, method, parts, userEmail) {
|
|
|
103
105
|
}
|
|
104
106
|
// GET /:id/render
|
|
105
107
|
if (method === "GET" && parts.length === 2 && parts[1] === "render") {
|
|
108
|
+
const localExtension = await getLocalExtension(parts[0]);
|
|
109
|
+
if (localExtension) {
|
|
110
|
+
const search = event.url?.search || "";
|
|
111
|
+
const isDark = search.includes("dark=1") || search.includes("dark=true");
|
|
112
|
+
const themeVars = getThemeVars(isDark);
|
|
113
|
+
const html = buildExtensionHtml(localExtension.content, themeVars, isDark, parts[0], {
|
|
114
|
+
authorEmail: localExtension.ownerEmail,
|
|
115
|
+
viewerEmail: userEmail,
|
|
116
|
+
isAuthor: false,
|
|
117
|
+
role: "viewer",
|
|
118
|
+
source: "local-files",
|
|
119
|
+
permissions: localExtension.source.permissions,
|
|
120
|
+
});
|
|
121
|
+
setResponseHeader(event, "Content-Type", "text/html; charset=utf-8");
|
|
122
|
+
setResponseHeader(event, "Content-Security-Policy", EXTENSION_IFRAME_CSP);
|
|
123
|
+
setResponseHeader(event, "X-Content-Type-Options", "nosniff");
|
|
124
|
+
setResponseHeader(event, "Referrer-Policy", "no-referrer");
|
|
125
|
+
return html;
|
|
126
|
+
}
|
|
106
127
|
const access = await resolveAccess("extension", parts[0]);
|
|
107
128
|
const extension = access?.resource;
|
|
108
129
|
if (!extension) {
|
|
@@ -132,6 +153,9 @@ async function dispatch(event, method, parts, userEmail) {
|
|
|
132
153
|
}
|
|
133
154
|
// GET /:id/history — list saved snapshots for an extension
|
|
134
155
|
if (method === "GET" && parts.length === 2 && parts[1] === "history") {
|
|
156
|
+
const localResponse = await localExtensionSqlOnlyResponse(event, parts[0]);
|
|
157
|
+
if (localResponse)
|
|
158
|
+
return localResponse;
|
|
135
159
|
const limitParam = event.url?.searchParams?.get("limit");
|
|
136
160
|
const limit = limitParam === null || limitParam === undefined
|
|
137
161
|
? undefined
|
|
@@ -146,6 +170,9 @@ async function dispatch(event, method, parts, userEmail) {
|
|
|
146
170
|
}
|
|
147
171
|
// GET /:id/history/:version — fetch one snapshot plus its previous-version diff
|
|
148
172
|
if (method === "GET" && parts.length === 3 && parts[1] === "history") {
|
|
173
|
+
const localResponse = await localExtensionSqlOnlyResponse(event, parts[0]);
|
|
174
|
+
if (localResponse)
|
|
175
|
+
return localResponse;
|
|
149
176
|
const detail = await getExtensionHistoryVersion(parts[0], parts[2]);
|
|
150
177
|
if (!detail) {
|
|
151
178
|
setResponseStatus(event, 404);
|
|
@@ -158,6 +185,9 @@ async function dispatch(event, method, parts, userEmail) {
|
|
|
158
185
|
parts.length === 4 &&
|
|
159
186
|
parts[1] === "history" &&
|
|
160
187
|
parts[3] === "restore") {
|
|
188
|
+
const localResponse = await localExtensionSqlOnlyResponse(event, parts[0]);
|
|
189
|
+
if (localResponse)
|
|
190
|
+
return localResponse;
|
|
161
191
|
const restored = await restoreExtensionHistoryVersion(parts[0], parts[2]);
|
|
162
192
|
if (!restored) {
|
|
163
193
|
setResponseStatus(event, 404);
|
|
@@ -167,6 +197,10 @@ async function dispatch(event, method, parts, userEmail) {
|
|
|
167
197
|
}
|
|
168
198
|
// GET /:id
|
|
169
199
|
if (method === "GET" && parts.length === 1) {
|
|
200
|
+
const localExtension = await getLocalExtension(parts[0]);
|
|
201
|
+
if (localExtension) {
|
|
202
|
+
return extensionResponse(localExtension, "viewer");
|
|
203
|
+
}
|
|
170
204
|
const access = await resolveAccess("extension", parts[0]);
|
|
171
205
|
if (!access) {
|
|
172
206
|
setResponseStatus(event, 404);
|
|
@@ -177,6 +211,9 @@ async function dispatch(event, method, parts, userEmail) {
|
|
|
177
211
|
// POST /:id/hide — remove from the current user's Extensions list/sidebar
|
|
178
212
|
// without deleting the underlying extension for teammates or shared slots.
|
|
179
213
|
if (method === "POST" && parts.length === 2 && parts[1] === "hide") {
|
|
214
|
+
const localResponse = await localExtensionSqlOnlyResponse(event, parts[0]);
|
|
215
|
+
if (localResponse)
|
|
216
|
+
return localResponse;
|
|
180
217
|
const ok = await hideExtension(parts[0]);
|
|
181
218
|
if (!ok) {
|
|
182
219
|
setResponseStatus(event, 404);
|
|
@@ -186,6 +223,9 @@ async function dispatch(event, method, parts, userEmail) {
|
|
|
186
223
|
}
|
|
187
224
|
// POST /:id/unhide — restore an extension hidden by the current user.
|
|
188
225
|
if (method === "POST" && parts.length === 2 && parts[1] === "unhide") {
|
|
226
|
+
const localResponse = await localExtensionSqlOnlyResponse(event, parts[0]);
|
|
227
|
+
if (localResponse)
|
|
228
|
+
return localResponse;
|
|
189
229
|
const ok = await unhideExtension(parts[0]);
|
|
190
230
|
if (!ok) {
|
|
191
231
|
setResponseStatus(event, 404);
|
|
@@ -195,6 +235,9 @@ async function dispatch(event, method, parts, userEmail) {
|
|
|
195
235
|
}
|
|
196
236
|
// POST /:id/global-hide — admin/owner hides the extension from EVERYONE.
|
|
197
237
|
if (method === "POST" && parts.length === 2 && parts[1] === "global-hide") {
|
|
238
|
+
const localResponse = await localExtensionSqlOnlyResponse(event, parts[0]);
|
|
239
|
+
if (localResponse)
|
|
240
|
+
return localResponse;
|
|
198
241
|
const ok = await globalHideExtension(parts[0]);
|
|
199
242
|
if (!ok) {
|
|
200
243
|
setResponseStatus(event, 404);
|
|
@@ -204,6 +247,9 @@ async function dispatch(event, method, parts, userEmail) {
|
|
|
204
247
|
}
|
|
205
248
|
// POST /:id/global-unhide — admin/owner reverses a global hide.
|
|
206
249
|
if (method === "POST" && parts.length === 2 && parts[1] === "global-unhide") {
|
|
250
|
+
const localResponse = await localExtensionSqlOnlyResponse(event, parts[0]);
|
|
251
|
+
if (localResponse)
|
|
252
|
+
return localResponse;
|
|
207
253
|
const ok = await globalUnhideExtension(parts[0]);
|
|
208
254
|
if (!ok) {
|
|
209
255
|
setResponseStatus(event, 404);
|
|
@@ -213,6 +259,9 @@ async function dispatch(event, method, parts, userEmail) {
|
|
|
213
259
|
}
|
|
214
260
|
// PUT /:id
|
|
215
261
|
if (method === "PUT" && parts.length === 1) {
|
|
262
|
+
const localResponse = await localExtensionSqlOnlyResponse(event, parts[0]);
|
|
263
|
+
if (localResponse)
|
|
264
|
+
return localResponse;
|
|
216
265
|
const body = await readBody(event);
|
|
217
266
|
const hasContentUpdate = body.content !== undefined ||
|
|
218
267
|
body.patches !== undefined ||
|
|
@@ -245,6 +294,9 @@ async function dispatch(event, method, parts, userEmail) {
|
|
|
245
294
|
}
|
|
246
295
|
// DELETE /:id
|
|
247
296
|
if (method === "DELETE" && parts.length === 1) {
|
|
297
|
+
const localResponse = await localExtensionSqlOnlyResponse(event, parts[0]);
|
|
298
|
+
if (localResponse)
|
|
299
|
+
return localResponse;
|
|
248
300
|
const ok = await deleteExtension(parts[0]);
|
|
249
301
|
if (!ok) {
|
|
250
302
|
setResponseStatus(event, 404);
|
|
@@ -256,27 +308,49 @@ async function dispatch(event, method, parts, userEmail) {
|
|
|
256
308
|
return { error: "Not found" };
|
|
257
309
|
}
|
|
258
310
|
async function extensionResponse(row, role, options = {}) {
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
311
|
+
const local = isLocalExtensionRow(row);
|
|
312
|
+
const resolvedRole = local
|
|
313
|
+
? "viewer"
|
|
314
|
+
: (role ??
|
|
315
|
+
(await resolveAccess("extension", row.id)
|
|
316
|
+
.then((access) => access?.role ?? null)
|
|
317
|
+
.catch(() => null)));
|
|
263
318
|
const responseRow = options.includeContent === false
|
|
264
319
|
? (({ content: _content, ...rest }) => rest)(row)
|
|
265
320
|
: row;
|
|
266
321
|
return {
|
|
267
322
|
...responseRow,
|
|
268
323
|
role: resolvedRole,
|
|
269
|
-
canEdit:
|
|
270
|
-
?
|
|
271
|
-
:
|
|
272
|
-
|
|
324
|
+
canEdit: local
|
|
325
|
+
? false
|
|
326
|
+
: resolvedRole
|
|
327
|
+
? ["owner", "admin", "editor"].includes(resolvedRole)
|
|
328
|
+
: false,
|
|
329
|
+
canDelete: local
|
|
330
|
+
? false
|
|
331
|
+
: resolvedRole
|
|
332
|
+
? ["owner", "admin"].includes(resolvedRole)
|
|
333
|
+
: false,
|
|
273
334
|
globallyHidden: row.hiddenAt != null,
|
|
274
335
|
};
|
|
275
336
|
}
|
|
337
|
+
async function localExtensionSqlOnlyResponse(event, extensionId) {
|
|
338
|
+
const localExtension = await getLocalExtension(extensionId);
|
|
339
|
+
if (!localExtension)
|
|
340
|
+
return null;
|
|
341
|
+
setResponseStatus(event, 400);
|
|
342
|
+
return {
|
|
343
|
+
error: "This extension is backed by local files. Edit its extension.json or entry file in the workspace; SQL-backed extension history, sharing, hide, update, and delete operations do not apply.",
|
|
344
|
+
source: localExtension.source,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
276
347
|
async function handleExtensionDataList(event, extensionId, collection, userEmail) {
|
|
277
348
|
await ensureExtensionsTables();
|
|
278
349
|
const extension = await getExtension(extensionId);
|
|
279
|
-
|
|
350
|
+
const localExtension = extension
|
|
351
|
+
? null
|
|
352
|
+
: await getLocalExtension(extensionId);
|
|
353
|
+
if (!extension && !localExtension) {
|
|
280
354
|
setResponseStatus(event, 404);
|
|
281
355
|
return { error: "Extension not found" };
|
|
282
356
|
}
|
|
@@ -328,7 +402,10 @@ async function handleExtensionDataList(event, extensionId, collection, userEmail
|
|
|
328
402
|
async function handleExtensionDataUpsert(event, extensionId, collection, userEmail) {
|
|
329
403
|
await ensureExtensionsTables();
|
|
330
404
|
const extension = await getExtension(extensionId);
|
|
331
|
-
|
|
405
|
+
const localExtension = extension
|
|
406
|
+
? null
|
|
407
|
+
: await getLocalExtension(extensionId);
|
|
408
|
+
if (!extension && !localExtension) {
|
|
332
409
|
setResponseStatus(event, 404);
|
|
333
410
|
return { error: "Extension not found" };
|
|
334
411
|
}
|
|
@@ -387,7 +464,10 @@ async function handleExtensionDataUpsert(event, extensionId, collection, userEma
|
|
|
387
464
|
async function handleExtensionDataDelete(event, extensionId, collection, itemId, userEmail) {
|
|
388
465
|
await ensureExtensionsTables();
|
|
389
466
|
const extension = await getExtension(extensionId);
|
|
390
|
-
|
|
467
|
+
const localExtension = extension
|
|
468
|
+
? null
|
|
469
|
+
: await getLocalExtension(extensionId);
|
|
470
|
+
if (!extension && !localExtension) {
|
|
391
471
|
setResponseStatus(event, 404);
|
|
392
472
|
return { error: "Extension not found" };
|
|
393
473
|
}
|