ship 1.4.41:Arcrun@89b80ff90e95

This commit is contained in:
ship
2026-08-12 20:24:44 +08:00
parent f2bb71112b
commit 643f8746d5
5 changed files with 557 additions and 198 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ Served via jsDelivr; fetched automatically during install — you never need to
- `arcrun-mcp/`**arcrun-mcp**
- `daemon/` — 桌面 AppMacWindows)安裝檔
Built from `Arcrun@1791ffa4972b` by `installer/scripts/ship.mjs`arcrun-rag reporelease 1.4.40built 2026-08-12)。
Built from `Arcrun@89b80ff90e95` by `installer/scripts/ship.mjs`arcrun-rag reporelease 1.4.41built 2026-08-12)。
⚠️ 這份檔案由出貨管線每次自動重寫(`installer/scripts/render-bundles-readme.mjs`)——
不要手動改這裡列的零件清單,要改就改 `installer/scripts/bundle-components.mjs`(唯一真相源,改一個地方兩條出貨路徑同時生效)。
+151 -1
View File
@@ -13285,7 +13285,12 @@ portalRouter.post(
session_token: token,
display_name: rec.values.display_name ?? "",
role: rec.values.role ?? "user",
libraries: parseLibraries(rec.values.libraries)
libraries: parseLibraries(rec.values.libraries),
// session 還能活多久(秒)。**非機密**(是這台實例的 TTL 設定,不是任何人的憑據),
// 但呼叫端需要它才能把自己發的憑證對齊這個上限——arcrun-mcp 用它把 OAuth
// access_token 的 TTL 夾到 min(自己的 TTL, 這個值):否則 MCP token 活 30 天、
// 底下的 portal session 7 天就死,使用者會在第 8 天遇到「連著卻查不到」的鬼打牆。
session_expires_in: sessionTtl(c.env)
// 絕不回租戶字串(design §3.3portal_user 拿到租戶字串就能繞過庫 filter 直打 /kbdb/*
});
})
@@ -15363,6 +15368,151 @@ portalDataRouter.get(
return c.json({ success: true, workflows, total: workflows.length, read_only: true });
})
);
function recordLibrary(values) {
const lib = values?.library;
return typeof lib === "string" && lib.trim() ? lib.trim() : null;
}
function canReadRecord(rec, tenant2, libraries) {
if ((rec.owner_id ?? "") !== tenant2) return false;
const lib = recordLibrary(rec.values);
return lib === null || canReadLibrary(libraries, lib);
}
portalDataRouter.get(
"/portal/data/map",
(c) => run(c, async () => {
const auth = await requirePortalUser(c);
if (!auth.ok) return auth.res;
const libraries = parseLibraries(auth.user.values.libraries);
if (libraries.length === 0) {
return c.json({ success: true, libraries: [], count: 0, note: "\u6B64\u5E33\u865F\u5C1A\u672A\u88AB\u6388\u6B0A\u4EFB\u4F55\u77E5\u8B58\u5EAB\uFF0C\u8ACB\u806F\u7D61\u7BA1\u7406\u54E1\u3002" });
}
const res = await kbdbFetch(c.env, `/map?owner_id=${encodeURIComponent(portalTenant(c.env))}`);
if (!res.ok) {
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
}
const body = await res.json().catch(() => null);
if (!body || !Array.isArray(body.libraries)) {
return c.json({ error: "\u85CF\u66F8\u5730\u5716\u8B80\u53D6\u5931\u6557\uFF1AKBDB \u56DE\u61C9\u4E0D\u662F\u9810\u671F\u7684 libraries \u6E05\u55AE" }, 502);
}
const allowed = body.libraries.filter(
(l) => typeof l?.library === "string" && canReadLibrary(libraries, l.library)
);
return c.json({ success: true, libraries: allowed, count: allowed.length });
})
);
portalDataRouter.get(
"/portal/data/map/:library",
(c) => run(c, async () => {
const auth = await requirePortalUser(c);
if (!auth.ok) return auth.res;
const libraries = parseLibraries(auth.user.values.libraries);
const library = c.req.param("library");
if (!canReadLibrary(libraries, library)) return notFound(c);
const res = await kbdbFetch(
c.env,
`/map/${encodeURIComponent(library)}?owner_id=${encodeURIComponent(portalTenant(c.env))}`
);
if (res.status === 404) return notFound(c);
if (!res.ok) return c.json({ error: `KBDB \u56DE\u932F\uFF08HTTP ${res.status}\uFF09` }, 502);
return new Response(res.body, { status: 200, headers: { "Content-Type": "application/json" } });
})
);
portalDataRouter.get(
"/portal/data/templates",
(c) => run(c, async () => {
const auth = await requirePortalUser(c);
if (!auth.ok) return auth.res;
const res = await kbdbFetch(c.env, "/templates");
if (!res.ok) return c.json({ error: `KBDB \u56DE\u932F\uFF08HTTP ${res.status}\uFF09` }, 502);
return new Response(res.body, { status: 200, headers: { "Content-Type": "application/json" } });
})
);
portalDataRouter.post(
"/portal/data/templates",
(c) => run(c, async () => {
const auth = await requirePortalUser(c);
if (!auth.ok) return auth.res;
const body = await c.req.json().catch(() => null);
if (!body || typeof body.name !== "string" || !body.name.trim() || !Array.isArray(body.slots)) {
return c.json({ error: "name \u8207 slots[] \u5FC5\u586B" }, 400);
}
const res = await kbdbFetch(c.env, "/templates", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: body.name,
slots: body.slots,
description: typeof body.description === "string" ? body.description : void 0,
created_by: portalTenant(c.env)
})
});
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
})
);
portalDataRouter.get(
"/portal/data/records/by-template/:template",
(c) => run(c, async () => {
const auth = await requirePortalUser(c);
if (!auth.ok) return auth.res;
const libraries = parseLibraries(auth.user.values.libraries);
if (libraries.length === 0) return c.json({ success: true, records: [], count: 0 });
const tenant2 = portalTenant(c.env);
const res = await kbdbFetch(
c.env,
`/records/by-template/${encodeURIComponent(c.req.param("template"))}?owner_id=${encodeURIComponent(tenant2)}`
);
if (!res.ok) return c.json({ error: `KBDB \u56DE\u932F\uFF08HTTP ${res.status}\uFF09` }, 502);
const body = await res.json().catch(() => null);
if (!body || !Array.isArray(body.records)) {
return c.json({ error: "record \u8B80\u53D6\u5931\u6557\uFF1AKBDB \u56DE\u61C9\u4E0D\u662F\u9810\u671F\u7684 records \u6E05\u55AE" }, 502);
}
const records = body.records.filter((r) => canReadRecord(r, tenant2, libraries));
return c.json({ success: true, records, count: records.length });
})
);
portalDataRouter.get(
"/portal/data/records/:recordId",
(c) => run(c, async () => {
const auth = await requirePortalUser(c);
if (!auth.ok) return auth.res;
const libraries = parseLibraries(auth.user.values.libraries);
if (libraries.length === 0) return notFound(c);
const res = await kbdbFetch(c.env, `/records/${encodeURIComponent(c.req.param("recordId"))}`);
if (res.status === 404) return notFound(c);
if (!res.ok) return c.json({ error: `KBDB \u56DE\u932F\uFF08HTTP ${res.status}\uFF09` }, 502);
const body = await res.json().catch(() => null);
const record = body?.record;
if (!record) return notFound(c);
if (!canReadRecord(record, portalTenant(c.env), libraries)) return notFound(c);
return c.json({ success: true, record });
})
);
portalDataRouter.post(
"/portal/data/records",
(c) => run(c, async () => {
const auth = await requirePortalUser(c);
if (!auth.ok) return auth.res;
const libraries = parseLibraries(auth.user.values.libraries);
if (libraries.length === 0) {
return c.json({ error: "\u6B64\u5E33\u865F\u5C1A\u672A\u88AB\u6388\u6B0A\u4EFB\u4F55\u77E5\u8B58\u5EAB\uFF0C\u7121\u6CD5\u5BEB\u5165" }, 403);
}
const body = await c.req.json().catch(() => null);
if (!body || typeof body.template !== "string" || !body.template.trim() || !body.values || typeof body.values !== "object") {
return c.json({ error: "template \u8207 values \u5FC5\u586B" }, 400);
}
const values = body.values;
const targetLib = recordLibrary(values);
if (targetLib !== null && !canReadLibrary(libraries, targetLib)) {
return c.json({ error: `\u7121\u300C${targetLib}\u300D\u5EAB\u7684\u6B0A\u9650\uFF0C\u4E0D\u80FD\u5BEB\u5165\u8A72\u5EAB` }, 403);
}
const res = await kbdbFetch(c.env, "/records", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ template: body.template, values, owner_id: portalTenant(c.env) })
});
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
})
);
portalDataRouter.get(
"/portal/data/diagnostics",
(c) => run(c, async () => {
+7 -5
View File
@@ -3281,7 +3281,7 @@ async function createRecord(db, input) {
});
await db.prepare(`INSERT INTO entry_values (id, record_id, template_id, slot_name, entry_id) VALUES (?, ?, ?, ?, ?)`).bind(uid2("ev"), recordId, tpl.id, slot, entry.id).run();
}
return { record_id: recordId, template_id: tpl.id, values: input.values };
return { record_id: recordId, template_id: tpl.id, values: input.values, owner_id: input.owner_id ?? null };
}
async function updateRecord(db, recordId, values) {
const evRes = await db.prepare(
@@ -3312,7 +3312,7 @@ async function updateRecord(db, recordId, values) {
}
async function getRecord(db, recordId) {
const res = await db.prepare(
`SELECT ev.slot_name as slot, e.content as content, ev.template_id as template_id
`SELECT ev.slot_name as slot, e.content as content, ev.template_id as template_id, e.owner_id as owner_id
FROM entry_values ev JOIN entries e ON ev.entry_id = e.id
WHERE ev.record_id = ?`
).bind(recordId).all();
@@ -3320,7 +3320,8 @@ async function getRecord(db, recordId) {
if (rows.length === 0) return null;
const values = {};
for (const r of rows) values[r.slot] = r.content;
return { record_id: recordId, template_id: rows[0].template_id, values };
const owner_id = rows.find((r) => r.owner_id != null)?.owner_id ?? null;
return { record_id: recordId, template_id: rows[0].template_id, values, owner_id };
}
async function searchByTemplate(db, template, owner_id, limit = 100) {
const tpl = await getTemplate(db, template);
@@ -3339,17 +3340,18 @@ async function searchByTemplate(db, template, owner_id, limit = 100) {
const chunk = ids.slice(i, i + 90);
const placeholders = chunk.map(() => "?").join(",");
const evRes = await db.prepare(
`SELECT ev.record_id as record_id, ev.slot_name as slot, e.content as content, ev.template_id as template_id
`SELECT ev.record_id as record_id, ev.slot_name as slot, e.content as content, ev.template_id as template_id, e.owner_id as owner_id
FROM entry_values ev JOIN entries e ON ev.entry_id = e.id
WHERE ev.record_id IN (${placeholders})`
).bind(...chunk).all();
for (const r of evRes.results ?? []) {
let rec = byId.get(r.record_id);
if (!rec) {
rec = { record_id: r.record_id, template_id: r.template_id, values: {} };
rec = { record_id: r.record_id, template_id: r.template_id, values: {}, owner_id: null };
byId.set(r.record_id, rec);
}
rec.values[r.slot] = r.content;
if (rec.owner_id == null && r.owner_id != null) rec.owner_id = r.owner_id;
}
}
return ids.map((id) => byId.get(id)).filter((r) => !!r);
+380 -173
View File
File diff suppressed because it is too large Load Diff
+18 -18
View File
@@ -1,14 +1,14 @@
{
"schema": 1,
"built": "2026-08-12",
"source": "Arcrun@1791ffa4972b",
"source": "Arcrun@89b80ff90e95",
"core": [
{
"name": "arcrun-cypher-executor",
"canonical": null,
"main_module": "worker.mjs",
"main_file": "arcrun-cypher-executor/worker.mjs",
"js_bytes": 577374,
"js_bytes": 584610,
"modules": [],
"compat_date": "2025-02-19",
"compat_flags": [
@@ -49,17 +49,17 @@
"stripped": {
"services": 13
},
"source_commit": "f1370e2275eea62b64a88821a096f2c2cfe76fb0",
"source_content_sha256": "8411ed59b7ad9e1a74ac0d8e3b620d7166e7d0178ac5939e6cc736f2e8d1d2be",
"sha256": "8411ed59b7ad9e1a74ac0d8e3b620d7166e7d0178ac5939e6cc736f2e8d1d2be",
"bytes": 577374
"source_commit": "10d150ac2b4385af95a457f3c411430c4a146cf9",
"source_content_sha256": "6a424274ecf7beb28b747672296c4c33f949c6d7b134e8054e640e92198910ba",
"sha256": "6a424274ecf7beb28b747672296c4c33f949c6d7b134e8054e640e92198910ba",
"bytes": 584610
},
{
"name": "arcrun-kbdb",
"canonical": null,
"main_module": "worker.mjs",
"main_file": "arcrun-kbdb/worker.mjs",
"js_bytes": 149533,
"js_bytes": 149797,
"modules": [],
"compat_date": "2025-02-19",
"compat_flags": [
@@ -79,10 +79,10 @@
"ENVIRONMENT": "production"
}
},
"source_commit": "c497ec418eba6cd94b1d5872671c51fd5812c11c",
"source_content_sha256": "ffb8d43467d0cefbd7545fdc0d347f2b965e3c3de20b3315eed7613f20266891",
"sha256": "ffb8d43467d0cefbd7545fdc0d347f2b965e3c3de20b3315eed7613f20266891",
"bytes": 149533
"source_commit": "10d150ac2b4385af95a457f3c411430c4a146cf9",
"source_content_sha256": "8b23853cbc88aee0ca15ef20ca46e92bd8e75064cd311af2847f4d51811960b1",
"sha256": "8b23853cbc88aee0ca15ef20ca46e92bd8e75064cd311af2847f4d51811960b1",
"bytes": 149797
},
{
"name": "arcrun-http-request",
@@ -152,7 +152,7 @@
"canonical": null,
"main_module": "worker.mjs",
"main_file": "arcrun-mcp/worker.mjs",
"js_bytes": 1165388,
"js_bytes": 1179487,
"modules": [],
"compat_date": "2024-11-27",
"compat_flags": [
@@ -167,10 +167,10 @@
"ai": false,
"vars": {}
},
"source_commit": "035e8b255b0dcbd4238707f7d2ac8ccf9ee1ba72",
"source_content_sha256": "c5ff10f9b9d5a77217be343af12d2be3ee8f9792d3e1e091e48e5e6c8d24ca9d",
"sha256": "c5ff10f9b9d5a77217be343af12d2be3ee8f9792d3e1e091e48e5e6c8d24ca9d",
"bytes": 1165388
"source_commit": "10d150ac2b4385af95a457f3c411430c4a146cf9",
"source_content_sha256": "1cd4c4d079d72bf7cba7c490ba6a88476f70b3ea51af7e5c93f9a184ae3c0ce6",
"sha256": "1cd4c4d079d72bf7cba7c490ba6a88476f70b3ea51af7e5c93f9a184ae3c0ce6",
"bytes": 1179487
},
{
"name": "arcrun-rag-ui",
@@ -207,8 +207,8 @@
"sha256": "782a3b2212884f789e1725f82046cf34ef6bb02e1a8b585cab8e4ae6c6e441ca"
}
},
"release": "1.4.40",
"release": "1.4.41",
"built_for": "oauth-installer-lazy-load",
"notes": [],
"fingerprint": "3f35ae8255861b36a0d41c3c7a004e6b85c9de0c9826124acbf5b86d03968782"
"fingerprint": "a24a5258d011560dfd9cd3f93a5dae0850564c65c4e63761809444060b0b4e92"
}