oasis_test_v2 2.2.39 → 2.2.40
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/index.js +617 -366
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -573,7 +573,10 @@ var init_chat_files = __esm({
|
|
|
573
573
|
EDITABLE_TEXT_EXTENSIONS = [
|
|
574
574
|
// 文本与 Markdown
|
|
575
575
|
".txt",
|
|
576
|
+
".text",
|
|
576
577
|
".md",
|
|
578
|
+
".markdown",
|
|
579
|
+
".mdx",
|
|
577
580
|
".rst",
|
|
578
581
|
".log",
|
|
579
582
|
// 代码 / 配置 / 结构化 / 字幕
|
|
@@ -583,20 +586,54 @@ var init_chat_files = __esm({
|
|
|
583
586
|
".java",
|
|
584
587
|
".go",
|
|
585
588
|
".rs",
|
|
589
|
+
".c",
|
|
590
|
+
".h",
|
|
591
|
+
".cpp",
|
|
592
|
+
".hpp",
|
|
593
|
+
".cc",
|
|
594
|
+
".cs",
|
|
595
|
+
".kt",
|
|
596
|
+
".swift",
|
|
597
|
+
".rb",
|
|
598
|
+
".php",
|
|
599
|
+
".lua",
|
|
600
|
+
".r",
|
|
601
|
+
".scala",
|
|
602
|
+
".sh",
|
|
603
|
+
".bash",
|
|
604
|
+
".zsh",
|
|
605
|
+
".ps1",
|
|
606
|
+
".bat",
|
|
586
607
|
".json",
|
|
587
608
|
".jsonl",
|
|
609
|
+
".ndjson",
|
|
588
610
|
".yaml",
|
|
589
611
|
".yml",
|
|
590
612
|
".xml",
|
|
591
613
|
".toml",
|
|
614
|
+
".ini",
|
|
615
|
+
".cfg",
|
|
616
|
+
".conf",
|
|
617
|
+
".properties",
|
|
592
618
|
".sql",
|
|
619
|
+
".graphql",
|
|
620
|
+
".proto",
|
|
621
|
+
".diff",
|
|
622
|
+
".patch",
|
|
593
623
|
".srt",
|
|
594
624
|
".vtt",
|
|
625
|
+
// 表格(纯文本那两种;.xlsx/.xls 是二进制,仍然只预览下载)
|
|
626
|
+
".csv",
|
|
627
|
+
".tsv",
|
|
595
628
|
// 网页资源与演示页
|
|
596
629
|
".html",
|
|
597
630
|
".css",
|
|
631
|
+
".scss",
|
|
632
|
+
".less",
|
|
598
633
|
".jsx",
|
|
599
|
-
".tsx"
|
|
634
|
+
".tsx",
|
|
635
|
+
".vue",
|
|
636
|
+
".svelte"
|
|
600
637
|
];
|
|
601
638
|
}
|
|
602
639
|
});
|
|
@@ -26512,6 +26549,9 @@ async function currentOutput(kernel, artifactId) {
|
|
|
26512
26549
|
if (!node2 || !output) throw new ApiError(404, "NOT_FOUND", "\u8FD9\u4E2A\u8282\u70B9\u5C1A\u672A\u4EA4\u4ED8\u6587\u4EF6");
|
|
26513
26550
|
return { node: node2, output };
|
|
26514
26551
|
}
|
|
26552
|
+
function looksLikeFileName(name) {
|
|
26553
|
+
return /^\.[a-z]{1,8}[0-9]?$/.test(fileExtensionOf(name));
|
|
26554
|
+
}
|
|
26515
26555
|
function decodeText3(bytes2) {
|
|
26516
26556
|
if (bytes2.length > ARTIFACT_EDIT_MAX_BYTES || bytes2.includes(0)) return null;
|
|
26517
26557
|
try {
|
|
@@ -26530,7 +26570,7 @@ async function readEditable(kernel, blobs, artifactId, path41) {
|
|
|
26530
26570
|
if (isEditableFileName(entry.path)) text6 = decodeText3(await blobs.get(entry.hash));
|
|
26531
26571
|
} else if (output.contentKind === "inline") {
|
|
26532
26572
|
const name = node2.title || output.name;
|
|
26533
|
-
if (!
|
|
26573
|
+
if (!looksLikeFileName(name) || isEditableFileName(name)) text6 = decodeText3(await blobs.get(output.contentRef));
|
|
26534
26574
|
}
|
|
26535
26575
|
const view = {
|
|
26536
26576
|
artifactId,
|
|
@@ -165958,6 +165998,13 @@ var init_continuation3 = __esm({
|
|
|
165958
165998
|
});
|
|
165959
165999
|
|
|
165960
166000
|
// ../server/src/chat-workorder-files.ts
|
|
166001
|
+
function workOrderFileEditTarget(workOrderId, file) {
|
|
166002
|
+
if (!isEditableFileName(file.name)) return null;
|
|
166003
|
+
if (file.sourceSessionId && file.sourceMessageId) {
|
|
166004
|
+
return { kind: "chat-attachment", sessionId: file.sourceSessionId, messageId: file.sourceMessageId, name: file.name };
|
|
166005
|
+
}
|
|
166006
|
+
return { kind: "work-order-file", workOrderId, fileId: file.fileId, name: file.name };
|
|
166007
|
+
}
|
|
165961
166008
|
function workOrderFileId(workOrderId, blobId, name) {
|
|
165962
166009
|
return `wof:${(0, import_node_crypto26.createHash)("sha256").update(`${workOrderId}|${blobId}|${name}`).digest("hex").slice(0, 32)}`;
|
|
165963
166010
|
}
|
|
@@ -166015,6 +166062,7 @@ var init_chat_workorder_files = __esm({
|
|
|
166015
166062
|
"../server/src/chat-workorder-files.ts"() {
|
|
166016
166063
|
"use strict";
|
|
166017
166064
|
import_node_crypto26 = require("node:crypto");
|
|
166065
|
+
init_src();
|
|
166018
166066
|
}
|
|
166019
166067
|
});
|
|
166020
166068
|
|
|
@@ -212726,6 +212774,350 @@ var init_src9 = __esm({
|
|
|
212726
212774
|
}
|
|
212727
212775
|
});
|
|
212728
212776
|
|
|
212777
|
+
// ../server/src/domains/chat-sessions/files-view.ts
|
|
212778
|
+
function reasonFromWorkdirCode(code2) {
|
|
212779
|
+
switch (code2) {
|
|
212780
|
+
case "NODE_OFFLINE":
|
|
212781
|
+
return "NODE_OFFLINE";
|
|
212782
|
+
case "TIMEOUT":
|
|
212783
|
+
return "TIMEOUT";
|
|
212784
|
+
default:
|
|
212785
|
+
return "ERROR";
|
|
212786
|
+
}
|
|
212787
|
+
}
|
|
212788
|
+
function joinRel(dir, name) {
|
|
212789
|
+
return dir ? `${dir}/${name}` : name;
|
|
212790
|
+
}
|
|
212791
|
+
function workdirFileItem(rel, name, size, mtime) {
|
|
212792
|
+
return {
|
|
212793
|
+
id: `workdir:${rel}`,
|
|
212794
|
+
name,
|
|
212795
|
+
kind: "file",
|
|
212796
|
+
// 节点侧列目录只报 name/type/size/mtime,没有 MIME——诚实留 null,
|
|
212797
|
+
// 不拿后缀猜(红线:取不到就是取不到,别拿别的字段顶)。
|
|
212798
|
+
content_type: null,
|
|
212799
|
+
size,
|
|
212800
|
+
updated_at: mtime,
|
|
212801
|
+
source: "workdir",
|
|
212802
|
+
blob_ref: null,
|
|
212803
|
+
path: rel,
|
|
212804
|
+
message_id: null,
|
|
212805
|
+
// 工作区文件本期不开写回(brief §4①:要动 node↔server 协议帧,超预算)。
|
|
212806
|
+
// 这里是**按能力不渲染**的判据,不是置灰假钮。
|
|
212807
|
+
editable: false
|
|
212808
|
+
};
|
|
212809
|
+
}
|
|
212810
|
+
async function collectWorkdirFiles(bridge, loc, now = Date.now) {
|
|
212811
|
+
const items = [];
|
|
212812
|
+
if (typeof bridge.walk === "function") {
|
|
212813
|
+
const walked = await bridge.walk(
|
|
212814
|
+
loc.nodeId,
|
|
212815
|
+
{ workdirKey: loc.workdirKey, runtimeKind: loc.runtimeKind, rtId: loc.rtId, path: "" },
|
|
212816
|
+
WORKDIR_TREE_MAX_ENTRIES
|
|
212817
|
+
);
|
|
212818
|
+
if (walked.ok) {
|
|
212819
|
+
let truncated2 = walked.truncated === true;
|
|
212820
|
+
for (const entry of walked.entries) {
|
|
212821
|
+
if (entry.type !== "file") continue;
|
|
212822
|
+
if (items.length >= WORKDIR_WALK_MAX_ENTRIES) {
|
|
212823
|
+
truncated2 = true;
|
|
212824
|
+
break;
|
|
212825
|
+
}
|
|
212826
|
+
const slash = entry.path.lastIndexOf("/");
|
|
212827
|
+
items.push(workdirFileItem(entry.path, entry.path.slice(slash + 1), entry.size, entry.mtime));
|
|
212828
|
+
}
|
|
212829
|
+
return { ok: true, items, truncated: truncated2 };
|
|
212830
|
+
}
|
|
212831
|
+
if (walked.code === "NOT_FOUND") return { ok: true, items: [], truncated: false };
|
|
212832
|
+
if (walked.code !== "UNSUPPORTED") return { ok: false, reason: reasonFromWorkdirCode(walked.code) };
|
|
212833
|
+
}
|
|
212834
|
+
const deadline = now() + WORKDIR_WALK_BUDGET_MS;
|
|
212835
|
+
let listCalls = 0;
|
|
212836
|
+
let truncated = false;
|
|
212837
|
+
const listDir = (path41) => {
|
|
212838
|
+
listCalls += 1;
|
|
212839
|
+
return bridge.list(loc.nodeId, {
|
|
212840
|
+
workdirKey: loc.workdirKey,
|
|
212841
|
+
runtimeKind: loc.runtimeKind,
|
|
212842
|
+
rtId: loc.rtId,
|
|
212843
|
+
path: path41
|
|
212844
|
+
});
|
|
212845
|
+
};
|
|
212846
|
+
const root = await listDir("");
|
|
212847
|
+
if (!root.ok) {
|
|
212848
|
+
if (root.code === "NOT_FOUND") return { ok: true, items: [], truncated: false };
|
|
212849
|
+
return { ok: false, reason: reasonFromWorkdirCode(root.code) };
|
|
212850
|
+
}
|
|
212851
|
+
let level = [{ path: "", outcome: root }];
|
|
212852
|
+
for (let depth = 0; depth <= WORKDIR_WALK_MAX_DEPTH; depth += 1) {
|
|
212853
|
+
const nextDirs = [];
|
|
212854
|
+
for (const { path: dir, outcome } of level) {
|
|
212855
|
+
if (!outcome.ok) {
|
|
212856
|
+
truncated = true;
|
|
212857
|
+
continue;
|
|
212858
|
+
}
|
|
212859
|
+
if (outcome.truncated) truncated = true;
|
|
212860
|
+
for (const entry of outcome.entries) {
|
|
212861
|
+
if (entry.type === "dir") {
|
|
212862
|
+
nextDirs.push(joinRel(dir, entry.name));
|
|
212863
|
+
continue;
|
|
212864
|
+
}
|
|
212865
|
+
if (items.length >= WORKDIR_WALK_MAX_ENTRIES) {
|
|
212866
|
+
truncated = true;
|
|
212867
|
+
continue;
|
|
212868
|
+
}
|
|
212869
|
+
items.push(workdirFileItem(joinRel(dir, entry.name), entry.name, entry.size, entry.mtime));
|
|
212870
|
+
}
|
|
212871
|
+
}
|
|
212872
|
+
if (depth === WORKDIR_WALK_MAX_DEPTH || nextDirs.length === 0) {
|
|
212873
|
+
if (nextDirs.length > 0) truncated = true;
|
|
212874
|
+
break;
|
|
212875
|
+
}
|
|
212876
|
+
const budget = WORKDIR_WALK_MAX_LIST_CALLS - listCalls;
|
|
212877
|
+
if (budget <= 0 || now() >= deadline) {
|
|
212878
|
+
truncated = true;
|
|
212879
|
+
break;
|
|
212880
|
+
}
|
|
212881
|
+
const take = nextDirs.slice(0, budget);
|
|
212882
|
+
if (take.length < nextDirs.length) truncated = true;
|
|
212883
|
+
const outcomes = await Promise.all(take.map((p2) => listDir(p2)));
|
|
212884
|
+
level = take.map((p2, i) => ({ path: p2, outcome: outcomes[i] }));
|
|
212885
|
+
}
|
|
212886
|
+
return { ok: true, items, truncated };
|
|
212887
|
+
}
|
|
212888
|
+
async function collectMessageFiles(messages, blobs) {
|
|
212889
|
+
const rows = [];
|
|
212890
|
+
for (const message of messages) {
|
|
212891
|
+
if (!Array.isArray(message.attachments)) continue;
|
|
212892
|
+
message.attachments.forEach((attachment, index2) => {
|
|
212893
|
+
if (!attachment || typeof attachment.name !== "string" || !attachment.name) return;
|
|
212894
|
+
const blobRef = typeof attachment.blobRef === "string" && attachment.blobRef ? attachment.blobRef : null;
|
|
212895
|
+
rows.push({
|
|
212896
|
+
item: {
|
|
212897
|
+
id: `msg:${message.id}:${index2}`,
|
|
212898
|
+
name: attachment.name,
|
|
212899
|
+
kind: "file",
|
|
212900
|
+
updated_at: attachment.updatedAt ?? message.createdAt,
|
|
212901
|
+
source: "message",
|
|
212902
|
+
blob_ref: blobRef,
|
|
212903
|
+
path: null,
|
|
212904
|
+
message_id: message.id,
|
|
212905
|
+
// 没有 blobRef 的是**历史纯文本附件**(契约注释:legacy text attachments may only
|
|
212906
|
+
// contain `name`)——没有可写回的载体,一律不给编辑入口。
|
|
212907
|
+
editable: blobRef !== null && isEditableFileName(attachment.name)
|
|
212908
|
+
},
|
|
212909
|
+
blobRef,
|
|
212910
|
+
declaredType: typeof attachment.contentType === "string" && attachment.contentType ? attachment.contentType : null
|
|
212911
|
+
});
|
|
212912
|
+
});
|
|
212913
|
+
}
|
|
212914
|
+
const metaCache = /* @__PURE__ */ new Map();
|
|
212915
|
+
const metaOf = (ref2) => {
|
|
212916
|
+
if (!blobs) return Promise.resolve(null);
|
|
212917
|
+
let hit = metaCache.get(ref2);
|
|
212918
|
+
if (!hit) {
|
|
212919
|
+
hit = blobs.meta(ref2).catch(() => null);
|
|
212920
|
+
metaCache.set(ref2, hit);
|
|
212921
|
+
}
|
|
212922
|
+
return hit;
|
|
212923
|
+
};
|
|
212924
|
+
return Promise.all(rows.map(async ({ item, blobRef, declaredType }) => {
|
|
212925
|
+
const meta = blobRef ? await metaOf(blobRef) : null;
|
|
212926
|
+
return {
|
|
212927
|
+
...item,
|
|
212928
|
+
content_type: declaredType ?? meta?.contentType ?? null,
|
|
212929
|
+
size: meta?.size ?? null
|
|
212930
|
+
};
|
|
212931
|
+
}));
|
|
212932
|
+
}
|
|
212933
|
+
function mergeChatSessionFiles(messageItems, workdirItems, materializedCopies = /* @__PURE__ */ new Set()) {
|
|
212934
|
+
const out = [];
|
|
212935
|
+
const seenMessageKeys = /* @__PURE__ */ new Set();
|
|
212936
|
+
for (const item of [...messageItems].sort((a, b2) => b2.updated_at.localeCompare(a.updated_at))) {
|
|
212937
|
+
const key2 = `${item.name}\0${item.blob_ref ?? ""}`;
|
|
212938
|
+
if (seenMessageKeys.has(key2)) continue;
|
|
212939
|
+
seenMessageKeys.add(key2);
|
|
212940
|
+
out.push(item);
|
|
212941
|
+
}
|
|
212942
|
+
const seenWorkdirPaths = /* @__PURE__ */ new Set();
|
|
212943
|
+
for (const item of workdirItems) {
|
|
212944
|
+
if (item.path && materializedCopies.has(item.path)) continue;
|
|
212945
|
+
if (seenWorkdirPaths.has(item.id)) continue;
|
|
212946
|
+
seenWorkdirPaths.add(item.id);
|
|
212947
|
+
out.push(item);
|
|
212948
|
+
}
|
|
212949
|
+
out.sort((a, b2) => b2.updated_at.localeCompare(a.updated_at) || a.name.localeCompare(b2.name));
|
|
212950
|
+
return out;
|
|
212951
|
+
}
|
|
212952
|
+
async function findMaterializedAttachmentCopies(messageItems, workdirItems, read) {
|
|
212953
|
+
const copies = /* @__PURE__ */ new Set();
|
|
212954
|
+
const attachments = messageItems.filter((item) => item.blob_ref && /^[a-f0-9]{64}$/.test(item.blob_ref));
|
|
212955
|
+
let budget = CHAT_ATTACHMENT_MAX_BYTES;
|
|
212956
|
+
const deadline = Date.now() + WORKDIR_WALK_BUDGET_MS;
|
|
212957
|
+
let reads = 0;
|
|
212958
|
+
for (const file of workdirItems) {
|
|
212959
|
+
const rootFile = file.path === file.name;
|
|
212960
|
+
const inputFile = file.path?.startsWith("inputs/attachments/") && !file.path.slice("inputs/attachments/".length).includes("/");
|
|
212961
|
+
if (!file.path || !rootFile && !inputFile) continue;
|
|
212962
|
+
const candidates = attachments.filter((attachment) => {
|
|
212963
|
+
const safe = safeAttachmentName(attachment.name);
|
|
212964
|
+
const numbered = file.name.endsWith(`-${safe}`) && /^\d+$/.test(file.name.slice(0, -(safe.length + 1)));
|
|
212965
|
+
return (rootFile ? file.name === attachment.name : file.name === safe || numbered) && attachment.size !== null && attachment.size === file.size;
|
|
212966
|
+
});
|
|
212967
|
+
if (Date.now() >= deadline) break;
|
|
212968
|
+
if (!candidates.length || file.size === null || file.size > budget || reads >= 10) continue;
|
|
212969
|
+
reads += 1;
|
|
212970
|
+
budget -= file.size;
|
|
212971
|
+
let timer;
|
|
212972
|
+
try {
|
|
212973
|
+
const result = await Promise.race([
|
|
212974
|
+
read(file.path, file.size),
|
|
212975
|
+
new Promise((resolve10) => {
|
|
212976
|
+
timer = setTimeout(() => resolve10(null), Math.max(0, deadline - Date.now()));
|
|
212977
|
+
})
|
|
212978
|
+
]);
|
|
212979
|
+
if (!result?.ok) continue;
|
|
212980
|
+
const bytes2 = Buffer.from(result.contentBase64, "base64");
|
|
212981
|
+
if (bytes2.length !== file.size) continue;
|
|
212982
|
+
const hash3 = (0, import_node_crypto45.createHash)("sha256").update(bytes2).digest("hex");
|
|
212983
|
+
if (candidates.some((attachment) => attachment.blob_ref === hash3)) copies.add(file.path);
|
|
212984
|
+
} catch {
|
|
212985
|
+
} finally {
|
|
212986
|
+
if (timer) clearTimeout(timer);
|
|
212987
|
+
}
|
|
212988
|
+
}
|
|
212989
|
+
return copies;
|
|
212990
|
+
}
|
|
212991
|
+
var import_node_crypto45, WORKDIR_WALK_MAX_DEPTH, WORKDIR_WALK_MAX_ENTRIES, WORKDIR_WALK_MAX_LIST_CALLS, WORKDIR_WALK_BUDGET_MS, WORKDIR_TREE_MAX_ENTRIES;
|
|
212992
|
+
var init_files_view = __esm({
|
|
212993
|
+
"../server/src/domains/chat-sessions/files-view.ts"() {
|
|
212994
|
+
"use strict";
|
|
212995
|
+
import_node_crypto45 = require("node:crypto");
|
|
212996
|
+
init_chat_attachment_files();
|
|
212997
|
+
init_src();
|
|
212998
|
+
WORKDIR_WALK_MAX_DEPTH = 3;
|
|
212999
|
+
WORKDIR_WALK_MAX_ENTRIES = 200;
|
|
213000
|
+
WORKDIR_WALK_MAX_LIST_CALLS = 20;
|
|
213001
|
+
WORKDIR_WALK_BUDGET_MS = 1e4;
|
|
213002
|
+
WORKDIR_TREE_MAX_ENTRIES = 2e3;
|
|
213003
|
+
}
|
|
213004
|
+
});
|
|
213005
|
+
|
|
213006
|
+
// ../server/src/domains/chat-sessions/persistent-files.ts
|
|
213007
|
+
function outputDisplayName(path41) {
|
|
213008
|
+
return path41.startsWith("outputs/") ? path41.slice("outputs/".length) : path41;
|
|
213009
|
+
}
|
|
213010
|
+
async function collectPersistentSessionFiles(messages, copies, blobs, includeOutputAliases = false) {
|
|
213011
|
+
const attachments = mergeChatSessionFiles(await collectMessageFiles(messages, blobs), []);
|
|
213012
|
+
const outputs = Object.values(copies ?? {}).filter((copy) => Boolean(copy.blobRef)).filter((copy) => includeOutputAliases || !attachments.some((a) => a.name === outputDisplayName(copy.path) && a.blob_ref === copy.blobRef)).map((copy) => ({
|
|
213013
|
+
// 保留历史产出卡的 id;引用和读取来源升级为服务端副本。
|
|
213014
|
+
id: `workdir:${copy.path}`,
|
|
213015
|
+
name: outputDisplayName(copy.path),
|
|
213016
|
+
kind: "file",
|
|
213017
|
+
content_type: copy.contentType,
|
|
213018
|
+
size: copy.size,
|
|
213019
|
+
updated_at: Number.isNaN(Date.parse(copy.mtime)) ? copy.copiedAt : new Date(copy.mtime).toISOString(),
|
|
213020
|
+
// 产出文件也能就地改(2026-09-16):改的是**服务端这份副本**,下一轮开轮前压回运行机器。
|
|
213021
|
+
// 判据与附件同一把(后缀在 EDITABLE_TEXT_EXTENSIONS 里);对话正在回复时保存会被端点挡下。
|
|
213022
|
+
source: "output",
|
|
213023
|
+
blob_ref: copy.blobRef,
|
|
213024
|
+
path: copy.path,
|
|
213025
|
+
message_id: null,
|
|
213026
|
+
editable: isEditableFileName(outputDisplayName(copy.path))
|
|
213027
|
+
}));
|
|
213028
|
+
return [...attachments, ...outputs].sort((a, b2) => b2.updated_at.localeCompare(a.updated_at) || a.id.localeCompare(b2.id));
|
|
213029
|
+
}
|
|
213030
|
+
async function readPersistentSessionFiles(store, sessionId, blobs, includeOutputAliases = false) {
|
|
213031
|
+
const [messages, copies] = await Promise.all([
|
|
213032
|
+
store.listSessionAttachmentMessages ? store.listSessionAttachmentMessages(sessionId) : store.listMessages(sessionId),
|
|
213033
|
+
store.readOutputCopies?.(sessionId) ?? Promise.resolve(null)
|
|
213034
|
+
]);
|
|
213035
|
+
return collectPersistentSessionFiles(messages, copies, blobs, includeOutputAliases);
|
|
213036
|
+
}
|
|
213037
|
+
var init_persistent_files = __esm({
|
|
213038
|
+
"../server/src/domains/chat-sessions/persistent-files.ts"() {
|
|
213039
|
+
"use strict";
|
|
213040
|
+
init_src();
|
|
213041
|
+
init_files_view();
|
|
213042
|
+
}
|
|
213043
|
+
});
|
|
213044
|
+
|
|
213045
|
+
// ../server/src/domains/projects/chat-files.ts
|
|
213046
|
+
function isProjectChatSession(session) {
|
|
213047
|
+
if (session.visibility === "internal") return false;
|
|
213048
|
+
if (session.analyzedRunId) return false;
|
|
213049
|
+
if (session.workspace) return false;
|
|
213050
|
+
return true;
|
|
213051
|
+
}
|
|
213052
|
+
function chatFolderTitle(session) {
|
|
213053
|
+
const title = session.title?.trim();
|
|
213054
|
+
return title ? title : "\u672A\u547D\u540D\u5BF9\u8BDD";
|
|
213055
|
+
}
|
|
213056
|
+
async function collectProjectChatFiles(chat, projectId2, meta, options = {}) {
|
|
213057
|
+
const scope = isUncategorizedProjectId(projectId2) ? null : projectId2;
|
|
213058
|
+
const [attachmentMessages, outputSessions, taskSessions] = await Promise.all([
|
|
213059
|
+
chat.listAttachmentMessages?.(scope) ?? [],
|
|
213060
|
+
chat.listOutputCopies?.(scope) ?? [],
|
|
213061
|
+
chat.listFileSessionsByWorkspaces?.(options.workOrderIds ?? []) ?? []
|
|
213062
|
+
]);
|
|
213063
|
+
const ids2 = [.../* @__PURE__ */ new Set([
|
|
213064
|
+
...attachmentMessages.map((m2) => m2.sessionId),
|
|
213065
|
+
...outputSessions.map((s2) => s2.sessionId),
|
|
213066
|
+
...taskSessions.map((s2) => s2.id)
|
|
213067
|
+
])];
|
|
213068
|
+
const out = [];
|
|
213069
|
+
for (const id of ids2) {
|
|
213070
|
+
const session = await chat.getSession(id);
|
|
213071
|
+
if (!session || session.visibility === "internal" || session.analyzedRunId) continue;
|
|
213072
|
+
if (session.workspace) {
|
|
213073
|
+
if (!options.workOrderIds?.includes(session.workspace)) continue;
|
|
213074
|
+
} else if ((session.projectId ?? null) !== scope || !isProjectChatSession(session)) continue;
|
|
213075
|
+
if (options.isDelegationChild && await options.isDelegationChild(id)) continue;
|
|
213076
|
+
const items = await readPersistentSessionFiles(chat, id, { meta });
|
|
213077
|
+
const fileIssues = await chat.readFileIssues?.(id) ?? [];
|
|
213078
|
+
for (const item of items) {
|
|
213079
|
+
if (!item.blob_ref) continue;
|
|
213080
|
+
out.push({
|
|
213081
|
+
projectId: projectId2,
|
|
213082
|
+
...fileIssues.length ? { fileIssues } : {},
|
|
213083
|
+
chatSessionId: id,
|
|
213084
|
+
chatTitle: chatFolderTitle(session),
|
|
213085
|
+
chatOwnerId: session.humanActorId,
|
|
213086
|
+
fileId: `chat:${id}:${item.id}`,
|
|
213087
|
+
name: item.name,
|
|
213088
|
+
blobId: item.blob_ref,
|
|
213089
|
+
...item.content_type ? { contentType: item.content_type } : {},
|
|
213090
|
+
size: item.size,
|
|
213091
|
+
uploadedBy: item.source === "output" ? session.aiActorId : session.humanActorId,
|
|
213092
|
+
uploadedAt: item.updated_at,
|
|
213093
|
+
chatSource: item.source === "output" ? "output" : "attachment",
|
|
213094
|
+
// 保存出口要用的两把钥匙(读侧原样带出,项目页就不必去解析行 id):
|
|
213095
|
+
// 附件认「哪条消息 + 名字 + 当前 blob」,产出认「工作区相对路径」。
|
|
213096
|
+
...item.message_id ? { chatMessageId: item.message_id } : {},
|
|
213097
|
+
...item.source === "output" && item.path ? { chatFilePath: item.path } : {},
|
|
213098
|
+
...session.workspace ? { taskId: session.workspace } : {}
|
|
213099
|
+
});
|
|
213100
|
+
}
|
|
213101
|
+
}
|
|
213102
|
+
return out;
|
|
213103
|
+
}
|
|
213104
|
+
function chatFileEditTarget(sessionId, file) {
|
|
213105
|
+
if (!isEditableFileName(file.name)) return null;
|
|
213106
|
+
if (file.chatSource === "output") {
|
|
213107
|
+
return file.chatFilePath ? { kind: "chat-output", sessionId, path: file.chatFilePath, name: file.name } : null;
|
|
213108
|
+
}
|
|
213109
|
+
return file.chatMessageId ? { kind: "chat-attachment", sessionId, messageId: file.chatMessageId, name: file.name } : null;
|
|
213110
|
+
}
|
|
213111
|
+
var init_chat_files2 = __esm({
|
|
213112
|
+
"../server/src/domains/projects/chat-files.ts"() {
|
|
213113
|
+
"use strict";
|
|
213114
|
+
init_src();
|
|
213115
|
+
init_persistent_files();
|
|
213116
|
+
init_persistent_files();
|
|
213117
|
+
init_uncategorized_project();
|
|
213118
|
+
}
|
|
213119
|
+
});
|
|
213120
|
+
|
|
212729
213121
|
// ../server/src/domains/projects/projection.ts
|
|
212730
213122
|
function decodeDocumentDescription(description) {
|
|
212731
213123
|
if (description === void 0) return {};
|
|
@@ -213307,13 +213699,13 @@ var init_stats = __esm({
|
|
|
213307
213699
|
|
|
213308
213700
|
// ../server/src/domains/projects/project-id.ts
|
|
213309
213701
|
function randomProjectId() {
|
|
213310
|
-
return projectIdFromUuid((0,
|
|
213702
|
+
return projectIdFromUuid((0, import_node_crypto46.randomUUID)());
|
|
213311
213703
|
}
|
|
213312
|
-
var
|
|
213704
|
+
var import_node_crypto46;
|
|
213313
213705
|
var init_project_id = __esm({
|
|
213314
213706
|
"../server/src/domains/projects/project-id.ts"() {
|
|
213315
213707
|
"use strict";
|
|
213316
|
-
|
|
213708
|
+
import_node_crypto46 = require("node:crypto");
|
|
213317
213709
|
init_src();
|
|
213318
213710
|
}
|
|
213319
213711
|
});
|
|
@@ -213427,6 +213819,7 @@ function emitChatFolders(items, chatFiles, resolveName, taskTitles = /* @__PURE_
|
|
|
213427
213819
|
creatorName: file.uploadedBy ? resolveName(file.uploadedBy) : null,
|
|
213428
213820
|
updatedAt: file.uploadedAt,
|
|
213429
213821
|
chatSessionId: sessionId,
|
|
213822
|
+
editTarget: chatFileEditTarget(sessionId, file),
|
|
213430
213823
|
...head.fileIssues?.length ? { fileIssues: head.fileIssues } : {}
|
|
213431
213824
|
});
|
|
213432
213825
|
}
|
|
@@ -213651,6 +214044,10 @@ var DEFAULT_AGENT_ACTOR, CHAT_FOLDER_TASK_LABEL, MemoryProjectStateStore, Memory
|
|
|
213651
214044
|
var init_service4 = __esm({
|
|
213652
214045
|
"../server/src/domains/projects/service.ts"() {
|
|
213653
214046
|
"use strict";
|
|
214047
|
+
init_chat_files2();
|
|
214048
|
+
init_chat_workorder_files();
|
|
214049
|
+
init_src();
|
|
214050
|
+
init_router();
|
|
213654
214051
|
init_src();
|
|
213655
214052
|
init_src5();
|
|
213656
214053
|
init_projection();
|
|
@@ -213887,6 +214284,27 @@ var init_service4 = __esm({
|
|
|
213887
214284
|
await this.projects.upsertProjectFile(file);
|
|
213888
214285
|
return file;
|
|
213889
214286
|
}
|
|
214287
|
+
/**
|
|
214288
|
+
* 改一个项目文件的正文。新字节写成**新 blob**,**`fileId` 不变**(`upsertProjectFile` 按它 upsert)
|
|
214289
|
+
* ——行 id 是列表与预览的锚点,换 id 等于换一行。
|
|
214290
|
+
*
|
|
214291
|
+
* `expectedBlobId` 是乐观并发校验:与当前行对不上说明别人先存过一版,抛 409 让人重开再改,
|
|
214292
|
+
* 而不是拿旧基线把人家的改动盖掉。
|
|
214293
|
+
*
|
|
214294
|
+
* 「更新时间」跳到这一刻(同任务级文件那条口径);**创建人不动**——那一位记的是谁把文件传进来的,
|
|
214295
|
+
* 不是谁最后改的(谁改的这一层本单不做,见变更记录)。
|
|
214296
|
+
*/
|
|
214297
|
+
async updateProjectFile(input) {
|
|
214298
|
+
if (!this.blobs) throw new ApiError(503, "BLOB_STORE_UNAVAILABLE", "\u9879\u76EE\u6587\u4EF6\u4FDD\u5B58\u9700\u8981 blob \u5E93\uFF08domain \u672A\u6CE8\u5165 blobs\uFF09");
|
|
214299
|
+
const current = (await this.projects.listProjectFiles(input.projectId)).find((f2) => f2.fileId === input.fileId);
|
|
214300
|
+
if (!current) throw new ApiError(404, "NOT_FOUND", "\u9879\u76EE\u91CC\u6CA1\u6709\u8FD9\u4E2A\u6587\u4EF6");
|
|
214301
|
+
if (!isEditableFileName(current.name)) throw new ApiError(415, "FILE_NOT_EDITABLE", `${current.name} \u4E0D\u662F\u652F\u6301\u7AD9\u5185\u7F16\u8F91\u7684\u6587\u672C\u7C7B\u578B`);
|
|
214302
|
+
if (current.blobId !== input.expectedBlobId) throw new ApiError(409, "STALE_FILE", "\u8BE5\u6587\u4EF6\u5DF2\u88AB\u66F4\u65B0\u8FC7\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u540E\u518D\u4FDD\u5B58");
|
|
214303
|
+
const blobId = await this.blobs.put(input.bytes);
|
|
214304
|
+
const next = { ...current, blobId, size: input.bytes.byteLength, uploadedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
214305
|
+
await this.projects.upsertProjectFile(next);
|
|
214306
|
+
return next;
|
|
214307
|
+
}
|
|
213890
214308
|
/** 加一个内容位置(external-home:git 仓 / design 空间…)。合法薄旁存,随时可加——修「老项目挂不了仓库」。 */
|
|
213891
214309
|
async addContentLocation(input) {
|
|
213892
214310
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -214423,7 +214841,8 @@ var init_service4 = __esm({
|
|
|
214423
214841
|
creatorKind: creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null,
|
|
214424
214842
|
creatorId,
|
|
214425
214843
|
creatorName: creatorId ? this.resolveActorName(creatorId) : null,
|
|
214426
|
-
updatedAt: file.linkedAt
|
|
214844
|
+
updatedAt: file.linkedAt,
|
|
214845
|
+
editTarget: workOrderFileEditTarget(workOrderId, file)
|
|
214427
214846
|
});
|
|
214428
214847
|
}
|
|
214429
214848
|
}
|
|
@@ -214486,7 +214905,8 @@ var init_service4 = __esm({
|
|
|
214486
214905
|
creatorKind: file.uploadedBy?.startsWith("actor:human:") ? "human" : file.uploadedBy ? "agent" : null,
|
|
214487
214906
|
creatorId: file.uploadedBy ?? null,
|
|
214488
214907
|
creatorName: file.uploadedBy ? this.resolveActorName(file.uploadedBy) : null,
|
|
214489
|
-
updatedAt: file.uploadedAt
|
|
214908
|
+
updatedAt: file.uploadedAt,
|
|
214909
|
+
editTarget: isEditableFileName(file.name) ? { kind: "project-file", projectId: file.projectId, fileId: file.fileId, name: file.name } : null
|
|
214490
214910
|
});
|
|
214491
214911
|
}
|
|
214492
214912
|
emitChatFolders(items, chatFiles, (actorId) => this.resolveActorName(actorId), workorderTitleById);
|
|
@@ -215340,12 +215760,12 @@ var init_service4 = __esm({
|
|
|
215340
215760
|
});
|
|
215341
215761
|
|
|
215342
215762
|
// ../server/src/dev-store.ts
|
|
215343
|
-
var
|
|
215763
|
+
var import_node_crypto47, fs20, path21, NdjsonOplogStore, DirBlobStore, MUTATORS, FileTypeRegistryStore, FileRoleRegistryStore, FileRegistryStore, FileAssistantBindStore, FileHumanPrefsStore, FileProjectStateStore, FileArtifactStateStore, FileTraceStore, MemoryChatSessionStore, FileChatSessionStore, MemoryChatTurnStore, FileChatTurnStore, MemoryReadMarkerStore, FileReadMarkerStore;
|
|
215344
215764
|
var init_dev_store = __esm({
|
|
215345
215765
|
"../server/src/dev-store.ts"() {
|
|
215346
215766
|
"use strict";
|
|
215347
215767
|
init_src();
|
|
215348
|
-
|
|
215768
|
+
import_node_crypto47 = require("node:crypto");
|
|
215349
215769
|
fs20 = __toESM(require("node:fs"), 1);
|
|
215350
215770
|
path21 = __toESM(require("node:path"), 1);
|
|
215351
215771
|
init_src();
|
|
@@ -215399,7 +215819,7 @@ var init_dev_store = __esm({
|
|
|
215399
215819
|
return path21.join(this.dir, hash3);
|
|
215400
215820
|
}
|
|
215401
215821
|
async put(bytes2) {
|
|
215402
|
-
const hash3 = (0,
|
|
215822
|
+
const hash3 = (0, import_node_crypto47.createHash)("sha256").update(bytes2).digest("hex");
|
|
215403
215823
|
const file = this.fileOf(hash3);
|
|
215404
215824
|
if (!fs20.existsSync(file)) fs20.writeFileSync(file, bytes2);
|
|
215405
215825
|
return hash3;
|
|
@@ -216640,7 +217060,7 @@ var init_company_engine_router = __esm({
|
|
|
216640
217060
|
|
|
216641
217061
|
// ../server/src/skill-market-client.ts
|
|
216642
217062
|
function digestOf2(bytes2) {
|
|
216643
|
-
return `sha256:${(0,
|
|
217063
|
+
return `sha256:${(0, import_node_crypto48.createHash)("sha256").update(bytes2).digest("hex")}`;
|
|
216644
217064
|
}
|
|
216645
217065
|
function createSkillMarketClientFromEnv(env = process.env) {
|
|
216646
217066
|
const base = env["SKILL_MARKET_BASE_URL"];
|
|
@@ -216651,11 +217071,11 @@ function createSkillMarketClientFromEnv(env = process.env) {
|
|
|
216651
217071
|
function sleep4(ms) {
|
|
216652
217072
|
return new Promise((r) => setTimeout(r, ms));
|
|
216653
217073
|
}
|
|
216654
|
-
var
|
|
217074
|
+
var import_node_crypto48, DisabledSkillMarketClient, HttpSkillMarketClient;
|
|
216655
217075
|
var init_skill_market_client = __esm({
|
|
216656
217076
|
"../server/src/skill-market-client.ts"() {
|
|
216657
217077
|
"use strict";
|
|
216658
|
-
|
|
217078
|
+
import_node_crypto48 = require("node:crypto");
|
|
216659
217079
|
init_src();
|
|
216660
217080
|
DisabledSkillMarketClient = class {
|
|
216661
217081
|
async listSkills(_input) {
|
|
@@ -217132,7 +217552,7 @@ var init_company_scoped_registry = __esm({
|
|
|
217132
217552
|
});
|
|
217133
217553
|
|
|
217134
217554
|
// ../server/src/domains/chat-sessions/outputs-scan.ts
|
|
217135
|
-
function
|
|
217555
|
+
function joinRel2(dir, name) {
|
|
217136
217556
|
return dir ? `${dir}/${name}` : name;
|
|
217137
217557
|
}
|
|
217138
217558
|
async function scanSessionOutputs(bridge, loc, now = Date.now) {
|
|
@@ -217182,14 +217602,14 @@ async function scanLayered(list2, now, markIncomplete = () => {
|
|
|
217182
217602
|
if (outcome.truncated) markIncomplete();
|
|
217183
217603
|
for (const entry of outcome.entries) {
|
|
217184
217604
|
if (entry.type === "dir") {
|
|
217185
|
-
nextDirs.push(
|
|
217605
|
+
nextDirs.push(joinRel2(dir, entry.name));
|
|
217186
217606
|
continue;
|
|
217187
217607
|
}
|
|
217188
217608
|
if (out.length >= MAX_ENTRIES) {
|
|
217189
217609
|
markIncomplete();
|
|
217190
217610
|
continue;
|
|
217191
217611
|
}
|
|
217192
|
-
out.push({ path:
|
|
217612
|
+
out.push({ path: joinRel2(dir, entry.name), size: entry.size, mtime: entry.mtime });
|
|
217193
217613
|
}
|
|
217194
217614
|
}
|
|
217195
217615
|
if (nextDirs.length === 0) break;
|
|
@@ -217636,11 +218056,11 @@ function typeDefError(def, mode) {
|
|
|
217636
218056
|
}
|
|
217637
218057
|
return null;
|
|
217638
218058
|
}
|
|
217639
|
-
var
|
|
218059
|
+
var import_node_crypto49, RESERVED_FIELD_NAMES, ARTIFACT_TYPE_ACCENTS, TYPE_DEF_SCHEMA, TypesAdminService;
|
|
217640
218060
|
var init_types_admin = __esm({
|
|
217641
218061
|
"../server/src/types-admin.ts"() {
|
|
217642
218062
|
"use strict";
|
|
217643
|
-
|
|
218063
|
+
import_node_crypto49 = require("node:crypto");
|
|
217644
218064
|
init_zod();
|
|
217645
218065
|
init_src5();
|
|
217646
218066
|
init_config_audit();
|
|
@@ -217767,7 +218187,7 @@ var init_types_admin = __esm({
|
|
|
217767
218187
|
async trace(change, target, by, detail) {
|
|
217768
218188
|
try {
|
|
217769
218189
|
await this.audit?.({
|
|
217770
|
-
id: `reg_${(0,
|
|
218190
|
+
id: `reg_${(0, import_node_crypto49.randomUUID)()}`,
|
|
217771
218191
|
actor: by ?? SYSTEM_CONFIG_ACTOR,
|
|
217772
218192
|
kind: "registry_change",
|
|
217773
218193
|
target,
|
|
@@ -217915,11 +218335,11 @@ function roleDefError(def, mode) {
|
|
|
217915
218335
|
}
|
|
217916
218336
|
return null;
|
|
217917
218337
|
}
|
|
217918
|
-
var
|
|
218338
|
+
var import_node_crypto50, ACCENTS, ROLE_DEF_SCHEMA, RolesAdminService;
|
|
217919
218339
|
var init_roles_admin = __esm({
|
|
217920
218340
|
"../server/src/roles-admin.ts"() {
|
|
217921
218341
|
"use strict";
|
|
217922
|
-
|
|
218342
|
+
import_node_crypto50 = require("node:crypto");
|
|
217923
218343
|
init_zod();
|
|
217924
218344
|
init_src5();
|
|
217925
218345
|
init_config_audit();
|
|
@@ -217944,7 +218364,7 @@ var init_roles_admin = __esm({
|
|
|
217944
218364
|
async trace(change, target, by, detail) {
|
|
217945
218365
|
try {
|
|
217946
218366
|
await this.deps.audit?.({
|
|
217947
|
-
id: `reg_${(0,
|
|
218367
|
+
id: `reg_${(0, import_node_crypto50.randomUUID)()}`,
|
|
217948
218368
|
actor: by ?? SYSTEM_CONFIG_ACTOR,
|
|
217949
218369
|
kind: "registry_change",
|
|
217950
218370
|
target,
|
|
@@ -219273,271 +219693,6 @@ var init_recovery_plan = __esm({
|
|
|
219273
219693
|
}
|
|
219274
219694
|
});
|
|
219275
219695
|
|
|
219276
|
-
// ../server/src/domains/chat-sessions/files-view.ts
|
|
219277
|
-
function reasonFromWorkdirCode(code2) {
|
|
219278
|
-
switch (code2) {
|
|
219279
|
-
case "NODE_OFFLINE":
|
|
219280
|
-
return "NODE_OFFLINE";
|
|
219281
|
-
case "TIMEOUT":
|
|
219282
|
-
return "TIMEOUT";
|
|
219283
|
-
default:
|
|
219284
|
-
return "ERROR";
|
|
219285
|
-
}
|
|
219286
|
-
}
|
|
219287
|
-
function joinRel2(dir, name) {
|
|
219288
|
-
return dir ? `${dir}/${name}` : name;
|
|
219289
|
-
}
|
|
219290
|
-
function workdirFileItem(rel, name, size, mtime) {
|
|
219291
|
-
return {
|
|
219292
|
-
id: `workdir:${rel}`,
|
|
219293
|
-
name,
|
|
219294
|
-
kind: "file",
|
|
219295
|
-
// 节点侧列目录只报 name/type/size/mtime,没有 MIME——诚实留 null,
|
|
219296
|
-
// 不拿后缀猜(红线:取不到就是取不到,别拿别的字段顶)。
|
|
219297
|
-
content_type: null,
|
|
219298
|
-
size,
|
|
219299
|
-
updated_at: mtime,
|
|
219300
|
-
source: "workdir",
|
|
219301
|
-
blob_ref: null,
|
|
219302
|
-
path: rel,
|
|
219303
|
-
message_id: null,
|
|
219304
|
-
// 工作区文件本期不开写回(brief §4①:要动 node↔server 协议帧,超预算)。
|
|
219305
|
-
// 这里是**按能力不渲染**的判据,不是置灰假钮。
|
|
219306
|
-
editable: false
|
|
219307
|
-
};
|
|
219308
|
-
}
|
|
219309
|
-
async function collectWorkdirFiles(bridge, loc, now = Date.now) {
|
|
219310
|
-
const items = [];
|
|
219311
|
-
if (typeof bridge.walk === "function") {
|
|
219312
|
-
const walked = await bridge.walk(
|
|
219313
|
-
loc.nodeId,
|
|
219314
|
-
{ workdirKey: loc.workdirKey, runtimeKind: loc.runtimeKind, rtId: loc.rtId, path: "" },
|
|
219315
|
-
WORKDIR_TREE_MAX_ENTRIES
|
|
219316
|
-
);
|
|
219317
|
-
if (walked.ok) {
|
|
219318
|
-
let truncated2 = walked.truncated === true;
|
|
219319
|
-
for (const entry of walked.entries) {
|
|
219320
|
-
if (entry.type !== "file") continue;
|
|
219321
|
-
if (items.length >= WORKDIR_WALK_MAX_ENTRIES) {
|
|
219322
|
-
truncated2 = true;
|
|
219323
|
-
break;
|
|
219324
|
-
}
|
|
219325
|
-
const slash = entry.path.lastIndexOf("/");
|
|
219326
|
-
items.push(workdirFileItem(entry.path, entry.path.slice(slash + 1), entry.size, entry.mtime));
|
|
219327
|
-
}
|
|
219328
|
-
return { ok: true, items, truncated: truncated2 };
|
|
219329
|
-
}
|
|
219330
|
-
if (walked.code === "NOT_FOUND") return { ok: true, items: [], truncated: false };
|
|
219331
|
-
if (walked.code !== "UNSUPPORTED") return { ok: false, reason: reasonFromWorkdirCode(walked.code) };
|
|
219332
|
-
}
|
|
219333
|
-
const deadline = now() + WORKDIR_WALK_BUDGET_MS;
|
|
219334
|
-
let listCalls = 0;
|
|
219335
|
-
let truncated = false;
|
|
219336
|
-
const listDir = (path41) => {
|
|
219337
|
-
listCalls += 1;
|
|
219338
|
-
return bridge.list(loc.nodeId, {
|
|
219339
|
-
workdirKey: loc.workdirKey,
|
|
219340
|
-
runtimeKind: loc.runtimeKind,
|
|
219341
|
-
rtId: loc.rtId,
|
|
219342
|
-
path: path41
|
|
219343
|
-
});
|
|
219344
|
-
};
|
|
219345
|
-
const root = await listDir("");
|
|
219346
|
-
if (!root.ok) {
|
|
219347
|
-
if (root.code === "NOT_FOUND") return { ok: true, items: [], truncated: false };
|
|
219348
|
-
return { ok: false, reason: reasonFromWorkdirCode(root.code) };
|
|
219349
|
-
}
|
|
219350
|
-
let level = [{ path: "", outcome: root }];
|
|
219351
|
-
for (let depth = 0; depth <= WORKDIR_WALK_MAX_DEPTH; depth += 1) {
|
|
219352
|
-
const nextDirs = [];
|
|
219353
|
-
for (const { path: dir, outcome } of level) {
|
|
219354
|
-
if (!outcome.ok) {
|
|
219355
|
-
truncated = true;
|
|
219356
|
-
continue;
|
|
219357
|
-
}
|
|
219358
|
-
if (outcome.truncated) truncated = true;
|
|
219359
|
-
for (const entry of outcome.entries) {
|
|
219360
|
-
if (entry.type === "dir") {
|
|
219361
|
-
nextDirs.push(joinRel2(dir, entry.name));
|
|
219362
|
-
continue;
|
|
219363
|
-
}
|
|
219364
|
-
if (items.length >= WORKDIR_WALK_MAX_ENTRIES) {
|
|
219365
|
-
truncated = true;
|
|
219366
|
-
continue;
|
|
219367
|
-
}
|
|
219368
|
-
items.push(workdirFileItem(joinRel2(dir, entry.name), entry.name, entry.size, entry.mtime));
|
|
219369
|
-
}
|
|
219370
|
-
}
|
|
219371
|
-
if (depth === WORKDIR_WALK_MAX_DEPTH || nextDirs.length === 0) {
|
|
219372
|
-
if (nextDirs.length > 0) truncated = true;
|
|
219373
|
-
break;
|
|
219374
|
-
}
|
|
219375
|
-
const budget = WORKDIR_WALK_MAX_LIST_CALLS - listCalls;
|
|
219376
|
-
if (budget <= 0 || now() >= deadline) {
|
|
219377
|
-
truncated = true;
|
|
219378
|
-
break;
|
|
219379
|
-
}
|
|
219380
|
-
const take = nextDirs.slice(0, budget);
|
|
219381
|
-
if (take.length < nextDirs.length) truncated = true;
|
|
219382
|
-
const outcomes = await Promise.all(take.map((p2) => listDir(p2)));
|
|
219383
|
-
level = take.map((p2, i) => ({ path: p2, outcome: outcomes[i] }));
|
|
219384
|
-
}
|
|
219385
|
-
return { ok: true, items, truncated };
|
|
219386
|
-
}
|
|
219387
|
-
async function collectMessageFiles(messages, blobs) {
|
|
219388
|
-
const rows = [];
|
|
219389
|
-
for (const message of messages) {
|
|
219390
|
-
if (!Array.isArray(message.attachments)) continue;
|
|
219391
|
-
message.attachments.forEach((attachment, index2) => {
|
|
219392
|
-
if (!attachment || typeof attachment.name !== "string" || !attachment.name) return;
|
|
219393
|
-
const blobRef = typeof attachment.blobRef === "string" && attachment.blobRef ? attachment.blobRef : null;
|
|
219394
|
-
rows.push({
|
|
219395
|
-
item: {
|
|
219396
|
-
id: `msg:${message.id}:${index2}`,
|
|
219397
|
-
name: attachment.name,
|
|
219398
|
-
kind: "file",
|
|
219399
|
-
updated_at: attachment.updatedAt ?? message.createdAt,
|
|
219400
|
-
source: "message",
|
|
219401
|
-
blob_ref: blobRef,
|
|
219402
|
-
path: null,
|
|
219403
|
-
message_id: message.id,
|
|
219404
|
-
// 没有 blobRef 的是**历史纯文本附件**(契约注释:legacy text attachments may only
|
|
219405
|
-
// contain `name`)——没有可写回的载体,一律不给编辑入口。
|
|
219406
|
-
editable: blobRef !== null && isEditableFileName(attachment.name)
|
|
219407
|
-
},
|
|
219408
|
-
blobRef,
|
|
219409
|
-
declaredType: typeof attachment.contentType === "string" && attachment.contentType ? attachment.contentType : null
|
|
219410
|
-
});
|
|
219411
|
-
});
|
|
219412
|
-
}
|
|
219413
|
-
const metaCache = /* @__PURE__ */ new Map();
|
|
219414
|
-
const metaOf = (ref2) => {
|
|
219415
|
-
if (!blobs) return Promise.resolve(null);
|
|
219416
|
-
let hit = metaCache.get(ref2);
|
|
219417
|
-
if (!hit) {
|
|
219418
|
-
hit = blobs.meta(ref2).catch(() => null);
|
|
219419
|
-
metaCache.set(ref2, hit);
|
|
219420
|
-
}
|
|
219421
|
-
return hit;
|
|
219422
|
-
};
|
|
219423
|
-
return Promise.all(rows.map(async ({ item, blobRef, declaredType }) => {
|
|
219424
|
-
const meta = blobRef ? await metaOf(blobRef) : null;
|
|
219425
|
-
return {
|
|
219426
|
-
...item,
|
|
219427
|
-
content_type: declaredType ?? meta?.contentType ?? null,
|
|
219428
|
-
size: meta?.size ?? null
|
|
219429
|
-
};
|
|
219430
|
-
}));
|
|
219431
|
-
}
|
|
219432
|
-
function mergeChatSessionFiles(messageItems, workdirItems, materializedCopies = /* @__PURE__ */ new Set()) {
|
|
219433
|
-
const out = [];
|
|
219434
|
-
const seenMessageKeys = /* @__PURE__ */ new Set();
|
|
219435
|
-
for (const item of [...messageItems].sort((a, b2) => b2.updated_at.localeCompare(a.updated_at))) {
|
|
219436
|
-
const key2 = `${item.name}\0${item.blob_ref ?? ""}`;
|
|
219437
|
-
if (seenMessageKeys.has(key2)) continue;
|
|
219438
|
-
seenMessageKeys.add(key2);
|
|
219439
|
-
out.push(item);
|
|
219440
|
-
}
|
|
219441
|
-
const seenWorkdirPaths = /* @__PURE__ */ new Set();
|
|
219442
|
-
for (const item of workdirItems) {
|
|
219443
|
-
if (item.path && materializedCopies.has(item.path)) continue;
|
|
219444
|
-
if (seenWorkdirPaths.has(item.id)) continue;
|
|
219445
|
-
seenWorkdirPaths.add(item.id);
|
|
219446
|
-
out.push(item);
|
|
219447
|
-
}
|
|
219448
|
-
out.sort((a, b2) => b2.updated_at.localeCompare(a.updated_at) || a.name.localeCompare(b2.name));
|
|
219449
|
-
return out;
|
|
219450
|
-
}
|
|
219451
|
-
async function findMaterializedAttachmentCopies(messageItems, workdirItems, read) {
|
|
219452
|
-
const copies = /* @__PURE__ */ new Set();
|
|
219453
|
-
const attachments = messageItems.filter((item) => item.blob_ref && /^[a-f0-9]{64}$/.test(item.blob_ref));
|
|
219454
|
-
let budget = CHAT_ATTACHMENT_MAX_BYTES;
|
|
219455
|
-
const deadline = Date.now() + WORKDIR_WALK_BUDGET_MS;
|
|
219456
|
-
let reads = 0;
|
|
219457
|
-
for (const file of workdirItems) {
|
|
219458
|
-
const rootFile = file.path === file.name;
|
|
219459
|
-
const inputFile = file.path?.startsWith("inputs/attachments/") && !file.path.slice("inputs/attachments/".length).includes("/");
|
|
219460
|
-
if (!file.path || !rootFile && !inputFile) continue;
|
|
219461
|
-
const candidates = attachments.filter((attachment) => {
|
|
219462
|
-
const safe = safeAttachmentName(attachment.name);
|
|
219463
|
-
const numbered = file.name.endsWith(`-${safe}`) && /^\d+$/.test(file.name.slice(0, -(safe.length + 1)));
|
|
219464
|
-
return (rootFile ? file.name === attachment.name : file.name === safe || numbered) && attachment.size !== null && attachment.size === file.size;
|
|
219465
|
-
});
|
|
219466
|
-
if (Date.now() >= deadline) break;
|
|
219467
|
-
if (!candidates.length || file.size === null || file.size > budget || reads >= 10) continue;
|
|
219468
|
-
reads += 1;
|
|
219469
|
-
budget -= file.size;
|
|
219470
|
-
let timer;
|
|
219471
|
-
try {
|
|
219472
|
-
const result = await Promise.race([
|
|
219473
|
-
read(file.path, file.size),
|
|
219474
|
-
new Promise((resolve10) => {
|
|
219475
|
-
timer = setTimeout(() => resolve10(null), Math.max(0, deadline - Date.now()));
|
|
219476
|
-
})
|
|
219477
|
-
]);
|
|
219478
|
-
if (!result?.ok) continue;
|
|
219479
|
-
const bytes2 = Buffer.from(result.contentBase64, "base64");
|
|
219480
|
-
if (bytes2.length !== file.size) continue;
|
|
219481
|
-
const hash3 = (0, import_node_crypto50.createHash)("sha256").update(bytes2).digest("hex");
|
|
219482
|
-
if (candidates.some((attachment) => attachment.blob_ref === hash3)) copies.add(file.path);
|
|
219483
|
-
} catch {
|
|
219484
|
-
} finally {
|
|
219485
|
-
if (timer) clearTimeout(timer);
|
|
219486
|
-
}
|
|
219487
|
-
}
|
|
219488
|
-
return copies;
|
|
219489
|
-
}
|
|
219490
|
-
var import_node_crypto50, WORKDIR_WALK_MAX_DEPTH, WORKDIR_WALK_MAX_ENTRIES, WORKDIR_WALK_MAX_LIST_CALLS, WORKDIR_WALK_BUDGET_MS, WORKDIR_TREE_MAX_ENTRIES;
|
|
219491
|
-
var init_files_view = __esm({
|
|
219492
|
-
"../server/src/domains/chat-sessions/files-view.ts"() {
|
|
219493
|
-
"use strict";
|
|
219494
|
-
import_node_crypto50 = require("node:crypto");
|
|
219495
|
-
init_chat_attachment_files();
|
|
219496
|
-
init_src();
|
|
219497
|
-
WORKDIR_WALK_MAX_DEPTH = 3;
|
|
219498
|
-
WORKDIR_WALK_MAX_ENTRIES = 200;
|
|
219499
|
-
WORKDIR_WALK_MAX_LIST_CALLS = 20;
|
|
219500
|
-
WORKDIR_WALK_BUDGET_MS = 1e4;
|
|
219501
|
-
WORKDIR_TREE_MAX_ENTRIES = 2e3;
|
|
219502
|
-
}
|
|
219503
|
-
});
|
|
219504
|
-
|
|
219505
|
-
// ../server/src/domains/chat-sessions/persistent-files.ts
|
|
219506
|
-
function outputDisplayName(path41) {
|
|
219507
|
-
return path41.startsWith("outputs/") ? path41.slice("outputs/".length) : path41;
|
|
219508
|
-
}
|
|
219509
|
-
async function collectPersistentSessionFiles(messages, copies, blobs, includeOutputAliases = false) {
|
|
219510
|
-
const attachments = mergeChatSessionFiles(await collectMessageFiles(messages, blobs), []);
|
|
219511
|
-
const outputs = Object.values(copies ?? {}).filter((copy) => Boolean(copy.blobRef)).filter((copy) => includeOutputAliases || !attachments.some((a) => a.name === outputDisplayName(copy.path) && a.blob_ref === copy.blobRef)).map((copy) => ({
|
|
219512
|
-
// 保留历史产出卡的 id;引用和读取来源升级为服务端副本。
|
|
219513
|
-
id: `workdir:${copy.path}`,
|
|
219514
|
-
name: outputDisplayName(copy.path),
|
|
219515
|
-
kind: "file",
|
|
219516
|
-
content_type: copy.contentType,
|
|
219517
|
-
size: copy.size,
|
|
219518
|
-
updated_at: Number.isNaN(Date.parse(copy.mtime)) ? copy.copiedAt : new Date(copy.mtime).toISOString(),
|
|
219519
|
-
source: "output",
|
|
219520
|
-
blob_ref: copy.blobRef,
|
|
219521
|
-
path: copy.path,
|
|
219522
|
-
message_id: null,
|
|
219523
|
-
editable: false
|
|
219524
|
-
}));
|
|
219525
|
-
return [...attachments, ...outputs].sort((a, b2) => b2.updated_at.localeCompare(a.updated_at) || a.id.localeCompare(b2.id));
|
|
219526
|
-
}
|
|
219527
|
-
async function readPersistentSessionFiles(store, sessionId, blobs, includeOutputAliases = false) {
|
|
219528
|
-
const [messages, copies] = await Promise.all([
|
|
219529
|
-
store.listSessionAttachmentMessages ? store.listSessionAttachmentMessages(sessionId) : store.listMessages(sessionId),
|
|
219530
|
-
store.readOutputCopies?.(sessionId) ?? Promise.resolve(null)
|
|
219531
|
-
]);
|
|
219532
|
-
return collectPersistentSessionFiles(messages, copies, blobs, includeOutputAliases);
|
|
219533
|
-
}
|
|
219534
|
-
var init_persistent_files = __esm({
|
|
219535
|
-
"../server/src/domains/chat-sessions/persistent-files.ts"() {
|
|
219536
|
-
"use strict";
|
|
219537
|
-
init_files_view();
|
|
219538
|
-
}
|
|
219539
|
-
});
|
|
219540
|
-
|
|
219541
219696
|
// ../server/src/domains/chat-sessions/file-restore.ts
|
|
219542
219697
|
async function recordSessionFileRestoreExit(store, sessionId, info) {
|
|
219543
219698
|
if (info.fileRestoreError) await store.writeFileIssues?.(sessionId, "restore", [{
|
|
@@ -219605,7 +219760,8 @@ async function prepareSessionFileRestore(input) {
|
|
|
219605
219760
|
};
|
|
219606
219761
|
for (const item of items) {
|
|
219607
219762
|
const path41 = item.source === "output" ? item.path : attachmentRestorePath(item.name, item.blob_ref ?? item.id);
|
|
219608
|
-
|
|
219763
|
+
const editedOutput = item.source === "output" && Boolean(copies[item.path ?? ""]?.editedAt);
|
|
219764
|
+
await add(item.name, item.blob_ref, path41, item.source === "message" || input.fresh || editedOutput);
|
|
219609
219765
|
if (item.source === "message") {
|
|
219610
219766
|
await add(item.name, item.blob_ref, `inputs/attachments/${safeAttachmentName(item.name)}`, true);
|
|
219611
219767
|
}
|
|
@@ -226186,70 +226342,6 @@ var init_assistant_tier_dispatch = __esm({
|
|
|
226186
226342
|
}
|
|
226187
226343
|
});
|
|
226188
226344
|
|
|
226189
|
-
// ../server/src/domains/projects/chat-files.ts
|
|
226190
|
-
function isProjectChatSession(session) {
|
|
226191
|
-
if (session.visibility === "internal") return false;
|
|
226192
|
-
if (session.analyzedRunId) return false;
|
|
226193
|
-
if (session.workspace) return false;
|
|
226194
|
-
return true;
|
|
226195
|
-
}
|
|
226196
|
-
function chatFolderTitle(session) {
|
|
226197
|
-
const title = session.title?.trim();
|
|
226198
|
-
return title ? title : "\u672A\u547D\u540D\u5BF9\u8BDD";
|
|
226199
|
-
}
|
|
226200
|
-
async function collectProjectChatFiles(chat, projectId2, meta, options = {}) {
|
|
226201
|
-
const scope = isUncategorizedProjectId(projectId2) ? null : projectId2;
|
|
226202
|
-
const [attachmentMessages, outputSessions, taskSessions] = await Promise.all([
|
|
226203
|
-
chat.listAttachmentMessages?.(scope) ?? [],
|
|
226204
|
-
chat.listOutputCopies?.(scope) ?? [],
|
|
226205
|
-
chat.listFileSessionsByWorkspaces?.(options.workOrderIds ?? []) ?? []
|
|
226206
|
-
]);
|
|
226207
|
-
const ids2 = [.../* @__PURE__ */ new Set([
|
|
226208
|
-
...attachmentMessages.map((m2) => m2.sessionId),
|
|
226209
|
-
...outputSessions.map((s2) => s2.sessionId),
|
|
226210
|
-
...taskSessions.map((s2) => s2.id)
|
|
226211
|
-
])];
|
|
226212
|
-
const out = [];
|
|
226213
|
-
for (const id of ids2) {
|
|
226214
|
-
const session = await chat.getSession(id);
|
|
226215
|
-
if (!session || session.visibility === "internal" || session.analyzedRunId) continue;
|
|
226216
|
-
if (session.workspace) {
|
|
226217
|
-
if (!options.workOrderIds?.includes(session.workspace)) continue;
|
|
226218
|
-
} else if ((session.projectId ?? null) !== scope || !isProjectChatSession(session)) continue;
|
|
226219
|
-
if (options.isDelegationChild && await options.isDelegationChild(id)) continue;
|
|
226220
|
-
const items = await readPersistentSessionFiles(chat, id, { meta });
|
|
226221
|
-
const fileIssues = await chat.readFileIssues?.(id) ?? [];
|
|
226222
|
-
for (const item of items) {
|
|
226223
|
-
if (!item.blob_ref) continue;
|
|
226224
|
-
out.push({
|
|
226225
|
-
projectId: projectId2,
|
|
226226
|
-
...fileIssues.length ? { fileIssues } : {},
|
|
226227
|
-
chatSessionId: id,
|
|
226228
|
-
chatTitle: chatFolderTitle(session),
|
|
226229
|
-
chatOwnerId: session.humanActorId,
|
|
226230
|
-
fileId: `chat:${id}:${item.id}`,
|
|
226231
|
-
name: item.name,
|
|
226232
|
-
blobId: item.blob_ref,
|
|
226233
|
-
...item.content_type ? { contentType: item.content_type } : {},
|
|
226234
|
-
size: item.size,
|
|
226235
|
-
uploadedBy: item.source === "output" ? session.aiActorId : session.humanActorId,
|
|
226236
|
-
uploadedAt: item.updated_at,
|
|
226237
|
-
chatSource: item.source === "output" ? "output" : "attachment",
|
|
226238
|
-
...session.workspace ? { taskId: session.workspace } : {}
|
|
226239
|
-
});
|
|
226240
|
-
}
|
|
226241
|
-
}
|
|
226242
|
-
return out;
|
|
226243
|
-
}
|
|
226244
|
-
var init_chat_files2 = __esm({
|
|
226245
|
-
"../server/src/domains/projects/chat-files.ts"() {
|
|
226246
|
-
"use strict";
|
|
226247
|
-
init_persistent_files();
|
|
226248
|
-
init_persistent_files();
|
|
226249
|
-
init_uncategorized_project();
|
|
226250
|
-
}
|
|
226251
|
-
});
|
|
226252
|
-
|
|
226253
226345
|
// ../server/src/domains/projects/chat-sessions.ts
|
|
226254
226346
|
async function listProjectChatSessions(chat, projectId2, delegationChildIds) {
|
|
226255
226347
|
if (!chat.listSessionsByProject) return [];
|
|
@@ -226483,6 +226575,20 @@ function projectsDomain(opts) {
|
|
|
226483
226575
|
});
|
|
226484
226576
|
return { status: 201, body: file };
|
|
226485
226577
|
});
|
|
226578
|
+
router.put("/api/projects/:id/files/:fileId", async (req) => {
|
|
226579
|
+
const b2 = req.body ?? {};
|
|
226580
|
+
const base642 = b2.content_base64 ?? b2.contentBase64;
|
|
226581
|
+
const blobRef = b2.blob_ref ?? b2.blobRef;
|
|
226582
|
+
if (base642 === void 0) throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 content_base64");
|
|
226583
|
+
if (!blobRef) throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 blob_ref\uFF08\u5F53\u524D\u5F15\u7528\uFF0C\u517C\u4F5C\u5E76\u53D1\u6821\u9A8C\uFF09");
|
|
226584
|
+
const file = await (await svc(req)).updateProjectFile({
|
|
226585
|
+
projectId: req.params.id,
|
|
226586
|
+
fileId: req.params.fileId,
|
|
226587
|
+
expectedBlobId: blobRef,
|
|
226588
|
+
bytes: new Uint8Array(Buffer.from(base642, "base64"))
|
|
226589
|
+
});
|
|
226590
|
+
return { status: 200, body: file };
|
|
226591
|
+
});
|
|
226486
226592
|
router.post("/api/projects/:id/content-locations", async (req) => {
|
|
226487
226593
|
const b2 = req.body ?? {};
|
|
226488
226594
|
if (b2.kind !== "blob" && b2.kind !== "external-home") throw new ApiError(400, "BAD_REQUEST", "kind \u987B\u4E3A blob | external-home");
|
|
@@ -227210,6 +227316,8 @@ function toApiProjectFilesView(view) {
|
|
|
227210
227316
|
folder_kind: item.folderKind ?? null,
|
|
227211
227317
|
// 对话文件夹及其子文件所属的对话;前端「所属任务」列的「对话」筛选档靠它。其余行为 null。
|
|
227212
227318
|
chat_session_id: item.chatSessionId ?? null,
|
|
227319
|
+
// 这一行怎么存回去(服务端算好判别器,前端不解析行 id)。不能改的行为 null。
|
|
227320
|
+
edit_target: item.editTarget ?? null,
|
|
227213
227321
|
...item.fileIssues?.length ? { file_issues: item.fileIssues } : {}
|
|
227214
227322
|
}))
|
|
227215
227323
|
};
|
|
@@ -231300,7 +231408,8 @@ function collabDomain(opts) {
|
|
|
231300
231408
|
source_session_id: f2.sourceSessionId ?? null,
|
|
231301
231409
|
source_message_id: f2.sourceMessageId ?? null,
|
|
231302
231410
|
uploaded_by: f2.uploadedBy ?? null,
|
|
231303
|
-
uploader_name: f2.uploadedBy ? resolve10?.(f2.uploadedBy)?.name ?? null : null
|
|
231411
|
+
uploader_name: f2.uploadedBy ? resolve10?.(f2.uploadedBy)?.name ?? null : null,
|
|
231412
|
+
edit_target: workOrderFileEditTarget(ws, f2)
|
|
231304
231413
|
}));
|
|
231305
231414
|
return { status: 200, body: { total: items.length, items } };
|
|
231306
231415
|
});
|
|
@@ -231363,10 +231472,75 @@ function collabDomain(opts) {
|
|
|
231363
231472
|
source_session_id: null,
|
|
231364
231473
|
source_message_id: null,
|
|
231365
231474
|
uploaded_by: req.auth.actor,
|
|
231366
|
-
uploader_name: resolve10?.(req.auth.actor)?.name ?? null
|
|
231475
|
+
uploader_name: resolve10?.(req.auth.actor)?.name ?? null,
|
|
231476
|
+
edit_target: workOrderFileEditTarget(ws, file)
|
|
231367
231477
|
};
|
|
231368
231478
|
return { status: 201, body: { item } };
|
|
231369
231479
|
});
|
|
231480
|
+
router.put("/api/workorders/:id/files/:fileId", async (req) => {
|
|
231481
|
+
const { kernel, registry: registry2, artifacts } = await resolveCtx(req.auth.companyId);
|
|
231482
|
+
const ws = req.params.id;
|
|
231483
|
+
const exists = [...kernel.model.artifacts.values()].some((a) => a.workspace === ws);
|
|
231484
|
+
if (!exists) {
|
|
231485
|
+
return { status: 404, body: { error: { code: "not_found", message: `\u5DE5\u5355\u4E0D\u5B58\u5728: ${ws}` } } };
|
|
231486
|
+
}
|
|
231487
|
+
if (!opts.assets || !artifacts?.addWorkOrderFile || !artifacts.listWorkOrderFiles) {
|
|
231488
|
+
return {
|
|
231489
|
+
status: 501,
|
|
231490
|
+
body: { error: { code: "unavailable", message: "\u672C\u90E8\u7F72\u672A\u63A5\u5165\u4EFB\u52A1\u6587\u4EF6\uFF08\u7F3A\u8D44\u4EA7\u67DC\u6216 work_order_files\uFF09" } }
|
|
231491
|
+
};
|
|
231492
|
+
}
|
|
231493
|
+
const body2 = req.body ?? {};
|
|
231494
|
+
if (typeof body2.contentBase64 !== "string") {
|
|
231495
|
+
return { status: 400, body: { error: { code: "bad_request", message: "\u7F3A\u5C11 contentBase64" } } };
|
|
231496
|
+
}
|
|
231497
|
+
if (typeof body2.blobRef !== "string" || !body2.blobRef) {
|
|
231498
|
+
return { status: 400, body: { error: { code: "bad_request", message: "\u7F3A\u5C11 blobRef\uFF08\u5F53\u524D\u5F15\u7528\uFF0C\u517C\u4F5C\u5E76\u53D1\u6821\u9A8C\uFF09" } } };
|
|
231499
|
+
}
|
|
231500
|
+
const current = (await artifacts.listWorkOrderFiles(ws)).find((f2) => f2.fileId === req.params.fileId);
|
|
231501
|
+
if (!current) {
|
|
231502
|
+
return { status: 404, body: { error: { code: "not_found", message: "\u4EFB\u52A1\u91CC\u6CA1\u6709\u8FD9\u4E2A\u6587\u4EF6" } } };
|
|
231503
|
+
}
|
|
231504
|
+
if (!isEditableFileName(current.name)) {
|
|
231505
|
+
return { status: 415, body: { error: { code: "file_not_editable", message: `${current.name} \u4E0D\u662F\u652F\u6301\u7AD9\u5185\u7F16\u8F91\u7684\u6587\u672C\u7C7B\u578B` } } };
|
|
231506
|
+
}
|
|
231507
|
+
if (current.blobId !== body2.blobRef) {
|
|
231508
|
+
return { status: 409, body: { error: { code: "stale_file", message: "\u8BE5\u6587\u4EF6\u5DF2\u88AB\u66F4\u65B0\u8FC7\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u540E\u518D\u4FDD\u5B58" } } };
|
|
231509
|
+
}
|
|
231510
|
+
let bytes2;
|
|
231511
|
+
try {
|
|
231512
|
+
bytes2 = new Uint8Array(Buffer.from(body2.contentBase64, "base64"));
|
|
231513
|
+
} catch (err) {
|
|
231514
|
+
return { status: 400, body: { error: { code: "bad_request", message: `contentBase64 \u89E3\u7801\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}` } } };
|
|
231515
|
+
}
|
|
231516
|
+
const blobId = await opts.assets.put(bytes2);
|
|
231517
|
+
const linkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
231518
|
+
const next = { ...current, blobId, size: bytes2.byteLength, linkedAt };
|
|
231519
|
+
try {
|
|
231520
|
+
await artifacts.addWorkOrderFile(next);
|
|
231521
|
+
} catch (err) {
|
|
231522
|
+
return {
|
|
231523
|
+
status: 500,
|
|
231524
|
+
body: { error: { code: "write_failed", message: `\u5199 work_order_files \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}` } }
|
|
231525
|
+
};
|
|
231526
|
+
}
|
|
231527
|
+
const resolve10 = await buildResolver(registry2);
|
|
231528
|
+
const item = {
|
|
231529
|
+
id: next.fileId,
|
|
231530
|
+
name: next.name,
|
|
231531
|
+
kind: "file",
|
|
231532
|
+
content_type: next.contentType ?? null,
|
|
231533
|
+
size: next.size,
|
|
231534
|
+
updated_at: next.linkedAt,
|
|
231535
|
+
blob_ref: next.blobId,
|
|
231536
|
+
source_session_id: next.sourceSessionId ?? null,
|
|
231537
|
+
source_message_id: next.sourceMessageId ?? null,
|
|
231538
|
+
uploaded_by: next.uploadedBy ?? null,
|
|
231539
|
+
uploader_name: next.uploadedBy ? resolve10?.(next.uploadedBy)?.name ?? null : null,
|
|
231540
|
+
edit_target: workOrderFileEditTarget(ws, next)
|
|
231541
|
+
};
|
|
231542
|
+
return { status: 200, body: { item } };
|
|
231543
|
+
});
|
|
231370
231544
|
router.post("/api/workorders/:id/hold", async (req) => {
|
|
231371
231545
|
if (!isHumanActor(req.auth.actor)) {
|
|
231372
231546
|
return { status: 403, body: { error: { code: "forbidden", message: "\u6682\u505C\u6574\u4E2A\u5DE5\u5355\u662F\u6CBB\u7406\u64CD\u4F5C\uFF0C\u4EC5\u4EBA\u7C7B\u53EF\u6267\u884C" } } };
|
|
@@ -231788,6 +231962,7 @@ var init_collab = __esm({
|
|
|
231788
231962
|
"use strict";
|
|
231789
231963
|
init_src5();
|
|
231790
231964
|
init_src();
|
|
231965
|
+
init_chat_workorder_files();
|
|
231791
231966
|
init_src();
|
|
231792
231967
|
init_workorders();
|
|
231793
231968
|
init_workorder_detail();
|
|
@@ -239528,18 +239703,18 @@ function createChatSessionsDomain(opts) {
|
|
|
239528
239703
|
router.put("/api/chat-sessions/:id/files", async (req) => {
|
|
239529
239704
|
const store2 = await storeFor(req);
|
|
239530
239705
|
const session = await store2.getSession(req.params.id);
|
|
239531
|
-
if (!session
|
|
239706
|
+
if (!session) throw new ApiError(404, "NOT_FOUND", "session not found");
|
|
239532
239707
|
const body2 = req.body ?? {};
|
|
239533
239708
|
if (body2.source === "workdir") {
|
|
239534
239709
|
throw new ApiError(
|
|
239535
239710
|
400,
|
|
239536
239711
|
"WORKDIR_WRITE_UNSUPPORTED",
|
|
239537
|
-
"\
|
|
239712
|
+
"\u5DE5\u4F5C\u533A\u91CC\u7684\u4E34\u65F6\u6587\u4EF6\u53EA\u8BFB\uFF1A\u5B83\u53EA\u5728\u8FD0\u884C\u673A\u5668\u4E0A\u3001\u670D\u52A1\u7AEF\u6CA1\u6709\u526F\u672C\u3002\u8981\u80FD\u6539\u8BF7\u8BA9 agent \u628A\u6587\u4EF6\u653E\u8FDB outputs/\u3002"
|
|
239538
239713
|
);
|
|
239539
239714
|
}
|
|
239540
|
-
if (body2.source !== "message"
|
|
239541
|
-
|
|
239542
|
-
|
|
239715
|
+
if (body2.source !== "message" && body2.source !== "output") {
|
|
239716
|
+
throw new ApiError(400, "BAD_REQUEST", 'source \u5FC5\u987B\u662F "message" \u6216 "output"');
|
|
239717
|
+
}
|
|
239543
239718
|
if (typeof body2.blobRef !== "string" || !body2.blobRef) throw new ApiError(400, "BAD_REQUEST", "blobRef required\uFF08\u5F53\u524D\u5F15\u7528\uFF0C\u517C\u4F5C\u5E76\u53D1\u6821\u9A8C\uFF09");
|
|
239544
239719
|
if (typeof body2.contentBase64 !== "string") throw new ApiError(400, "BAD_REQUEST", "contentBase64 required");
|
|
239545
239720
|
if (body2.contentBase64.length > Math.ceil(CHAT_ATTACHMENT_MAX_BYTES * 4 / 3) + 4096) {
|
|
@@ -239547,6 +239722,63 @@ function createChatSessionsDomain(opts) {
|
|
|
239547
239722
|
}
|
|
239548
239723
|
const blobAccess = opts.resolveBlobs ? await opts.resolveBlobs(req.auth?.companyId) : opts.blobs;
|
|
239549
239724
|
if (!blobAccess) throw new ApiError(503, "BLOB_STORE_UNAVAILABLE", "\u672C\u5B9E\u4F8B\u672A\u63A5\u5165 blob \u5B58\u50A8\uFF0C\u65E0\u6CD5\u4FDD\u5B58");
|
|
239725
|
+
const bytes2 = Buffer.from(body2.contentBase64, "base64");
|
|
239726
|
+
if (body2.source === "output") {
|
|
239727
|
+
const path41 = typeof body2.path === "string" ? body2.path.trim() : "";
|
|
239728
|
+
if (!path41) throw new ApiError(400, "BAD_REQUEST", "path required\uFF08\u4EA7\u51FA\u6587\u4EF6\u6309\u5DE5\u4F5C\u533A\u76F8\u5BF9\u8DEF\u5F84\u5B9A\u4F4D\uFF09");
|
|
239729
|
+
const name2 = outputDisplayName(path41);
|
|
239730
|
+
if (!isEditableFileName(name2)) {
|
|
239731
|
+
throw new ApiError(415, "FILE_NOT_EDITABLE", `${name2} \u4E0D\u662F\u652F\u6301\u7AD9\u5185\u7F16\u8F91\u7684\u6587\u672C\u7C7B\u578B`);
|
|
239732
|
+
}
|
|
239733
|
+
if (!store2.mergeOutputCopies || !store2.readOutputCopies || !store2.writeOutputFingerprints || !store2.readOutputFingerprints) {
|
|
239734
|
+
throw new ApiError(503, "OUTPUT_STORE_UNAVAILABLE", "\u672C\u5B9E\u4F8B\u672A\u63A5\u5165\u4EA7\u51FA\u526F\u672C\u8868\uFF0C\u65E0\u6CD5\u4FDD\u5B58\u4EA7\u51FA\u6587\u4EF6");
|
|
239735
|
+
}
|
|
239736
|
+
const turns = await turnsFor(req);
|
|
239737
|
+
const activeTurn = turns ? await turns.activeTurn(session.id).catch(() => null) : null;
|
|
239738
|
+
if (activeTurn && (activeTurn.status === "reserved" || activeTurn.status === "running")) {
|
|
239739
|
+
throw new ApiError(409, "CHAT_TURN_RUNNING", "\u8FD9\u6761\u5BF9\u8BDD\u6B63\u5728\u56DE\u590D\uFF0C\u7B49\u8FD9\u4E00\u8F6E\u7ED3\u675F\u518D\u6539");
|
|
239740
|
+
}
|
|
239741
|
+
const copies = await store2.readOutputCopies(session.id) ?? {};
|
|
239742
|
+
const copy = copies[path41];
|
|
239743
|
+
if (!copy?.blobRef) {
|
|
239744
|
+
throw new ApiError(409, "OUTPUT_NOT_SAVED", "\u8FD9\u4EFD\u4EA7\u51FA\u8FD8\u6CA1\u6709\u670D\u52A1\u7AEF\u526F\u672C\uFF1A\u5148\u56DE\u539F\u673A\u5668\u8DD1\u5B8C\u4E00\u8F6E\u628A\u5B83\u5B58\u4F4F\uFF0C\u518D\u6765\u6539");
|
|
239745
|
+
}
|
|
239746
|
+
const saveIssue = (await store2.readFileIssues?.(session.id) ?? []).find((issue2) => issue2.phase === "save" && issue2.path === path41);
|
|
239747
|
+
if (saveIssue) throw new ApiError(409, "OUTPUT_NOT_SAVED", `\u8FD9\u4EFD\u4EA7\u51FA\u4E0A\u4E00\u8F6E\u6CA1\u4FDD\u5B58\u6210\u529F\uFF0C\u5148\u89E3\u51B3\u518D\u6539\uFF1A${saveIssue.message}`);
|
|
239748
|
+
if (copy.blobRef !== body2.blobRef) {
|
|
239749
|
+
throw new ApiError(409, "STALE_OUTPUT", "\u8BE5\u6587\u4EF6\u5DF2\u88AB\u66F4\u65B0\u8FC7\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00\u540E\u518D\u4FDD\u5B58");
|
|
239750
|
+
}
|
|
239751
|
+
const blobRef2 = await blobAccess.put(new Uint8Array(bytes2));
|
|
239752
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
239753
|
+
await store2.mergeOutputCopies(session.id, [{
|
|
239754
|
+
path: path41,
|
|
239755
|
+
name: name2,
|
|
239756
|
+
blobRef: blobRef2,
|
|
239757
|
+
contentType: copy.contentType ?? null,
|
|
239758
|
+
size: bytes2.byteLength,
|
|
239759
|
+
mtime: at,
|
|
239760
|
+
copiedAt: at,
|
|
239761
|
+
editedAt: at
|
|
239762
|
+
}]);
|
|
239763
|
+
const fingerprints = await store2.readOutputFingerprints(session.id) ?? {};
|
|
239764
|
+
await store2.writeOutputFingerprints(session.id, { ...fingerprints, [path41]: { size: bytes2.byteLength, mtime: at } });
|
|
239765
|
+
const item2 = {
|
|
239766
|
+
id: `workdir:${path41}`,
|
|
239767
|
+
name: name2,
|
|
239768
|
+
kind: "file",
|
|
239769
|
+
content_type: copy.contentType ?? null,
|
|
239770
|
+
size: bytes2.byteLength,
|
|
239771
|
+
updated_at: at,
|
|
239772
|
+
source: "output",
|
|
239773
|
+
blob_ref: blobRef2,
|
|
239774
|
+
path: path41,
|
|
239775
|
+
message_id: null,
|
|
239776
|
+
editable: true
|
|
239777
|
+
};
|
|
239778
|
+
return { status: 200, body: { item: item2 } };
|
|
239779
|
+
}
|
|
239780
|
+
if (typeof body2.messageId !== "string" || !body2.messageId) throw new ApiError(400, "BAD_REQUEST", "messageId required");
|
|
239781
|
+
if (typeof body2.name !== "string" || !body2.name.trim()) throw new ApiError(400, "BAD_REQUEST", "name required\uFF08\u4E0E blobRef \u4E00\u8D77\u6807\u8BC6\u90A3\u6761\u9644\u4EF6\uFF09");
|
|
239550
239782
|
const messages = await store2.listMessages(session.id);
|
|
239551
239783
|
const message = messages.find((m2) => m2.id === body2.messageId);
|
|
239552
239784
|
if (!message || !Array.isArray(message.attachments)) throw new ApiError(404, "NOT_FOUND", "attachment not found");
|
|
@@ -239560,12 +239792,29 @@ function createChatSessionsDomain(opts) {
|
|
|
239560
239792
|
}
|
|
239561
239793
|
const current = message.attachments[index2];
|
|
239562
239794
|
if (!isEditableFileName(name)) {
|
|
239563
|
-
throw new ApiError(415, "FILE_NOT_EDITABLE", `${name} \u4E0D\u662F\
|
|
239795
|
+
throw new ApiError(415, "FILE_NOT_EDITABLE", `${name} \u4E0D\u662F\u652F\u6301\u7AD9\u5185\u7F16\u8F91\u7684\u6587\u672C\u7C7B\u578B`);
|
|
239564
239796
|
}
|
|
239565
|
-
const blobRef = await blobAccess.put(new Uint8Array(
|
|
239797
|
+
const blobRef = await blobAccess.put(new Uint8Array(bytes2));
|
|
239566
239798
|
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
239567
239799
|
const nextAttachments = message.attachments.map((a, i) => i === index2 ? { ...a, blobRef, updatedAt } : a);
|
|
239568
239800
|
await store2.updateMessage(message.id, { attachments: nextAttachments });
|
|
239801
|
+
const warnings = [];
|
|
239802
|
+
const artifacts = await artifactsFor(req);
|
|
239803
|
+
if (artifacts?.listWorkOrderFiles && artifacts.addWorkOrderFile) {
|
|
239804
|
+
try {
|
|
239805
|
+
const workOrderIds = await store2.listWorkOrdersForSession(session.id);
|
|
239806
|
+
for (const workOrderId of workOrderIds) {
|
|
239807
|
+
const linked = await artifacts.listWorkOrderFiles(workOrderId);
|
|
239808
|
+
for (const file of linked) {
|
|
239809
|
+
if (file.sourceSessionId !== session.id || file.sourceMessageId !== message.id) continue;
|
|
239810
|
+
if (file.name !== name || file.blobId !== body2.blobRef) continue;
|
|
239811
|
+
await artifacts.addWorkOrderFile({ ...file, blobId: blobRef, size: bytes2.byteLength });
|
|
239812
|
+
}
|
|
239813
|
+
}
|
|
239814
|
+
} catch (error2) {
|
|
239815
|
+
warnings.push(`\u4EFB\u52A1\u91CC\u5F15\u7528\u540C\u4E00\u4EFD\u6587\u4EF6\u7684\u90A3\u51E0\u884C\u6CA1\u80FD\u4E00\u8D77\u66F4\u65B0\uFF1A${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
239816
|
+
}
|
|
239817
|
+
}
|
|
239569
239818
|
const meta = await blobAccess.meta(blobRef).catch(() => null);
|
|
239570
239819
|
const item = {
|
|
239571
239820
|
id: `msg:${message.id}:${index2}`,
|
|
@@ -239580,7 +239829,7 @@ function createChatSessionsDomain(opts) {
|
|
|
239580
239829
|
message_id: message.id,
|
|
239581
239830
|
editable: true
|
|
239582
239831
|
};
|
|
239583
|
-
return { status: 200, body: { item } };
|
|
239832
|
+
return { status: 200, body: { item, ...warnings.length ? { warnings } : {} } };
|
|
239584
239833
|
});
|
|
239585
239834
|
};
|
|
239586
239835
|
}
|
|
@@ -270997,7 +271246,8 @@ var SessionProcessState = class {
|
|
|
270997
271246
|
exited = false;
|
|
270998
271247
|
stopping = false;
|
|
270999
271248
|
reconnectMs = RECONNECT_MIN_MS;
|
|
271000
|
-
|
|
271249
|
+
/** 连续失联的起点;首次连通前也计时,正常连接期间不累计失联时间。 */
|
|
271250
|
+
unreachableSince = Date.now();
|
|
271001
271251
|
startedAt = Date.now();
|
|
271002
271252
|
hardTtlMs;
|
|
271003
271253
|
suicideAfterUnreachableMs;
|
|
@@ -271071,7 +271321,7 @@ var SessionProcessState = class {
|
|
|
271071
271321
|
this.ws = ws;
|
|
271072
271322
|
ws.on("open", () => {
|
|
271073
271323
|
this.connected = true;
|
|
271074
|
-
this.
|
|
271324
|
+
this.unreachableSince = null;
|
|
271075
271325
|
this.reconnectMs = RECONNECT_MIN_MS;
|
|
271076
271326
|
log2("[session]", `${this.spec.dispatchId}: \u6570\u636E\u9762\u5DF2\u8FDE\u4E0A ${this.spec.auth.gatewayUrl}`);
|
|
271077
271327
|
this.rawSend({
|
|
@@ -271101,6 +271351,7 @@ var SessionProcessState = class {
|
|
|
271101
271351
|
});
|
|
271102
271352
|
ws.on("close", () => {
|
|
271103
271353
|
this.connected = false;
|
|
271354
|
+
this.unreachableSince ??= Date.now();
|
|
271104
271355
|
if (this.stopping) return;
|
|
271105
271356
|
setTimeout(() => this.connect(), this.reconnectMs).unref?.();
|
|
271106
271357
|
this.reconnectMs = Math.min(this.reconnectMs * 2, RECONNECT_MAX_MS);
|
|
@@ -271182,7 +271433,7 @@ var SessionProcessState = class {
|
|
|
271182
271433
|
void this.selfTerminate("ttl");
|
|
271183
271434
|
return;
|
|
271184
271435
|
}
|
|
271185
|
-
if (
|
|
271436
|
+
if (this.unreachableSince !== null && now - this.unreachableSince >= this.suicideAfterUnreachableMs) {
|
|
271186
271437
|
log2("[session]", `${this.spec.dispatchId}: \u5931\u8054\u8D85\u8FC7 ${this.suicideAfterUnreachableMs}ms\uFF0C\u81EA\u5C3D\uFF08\u9632\u53CC\u8DD1\uFF09`);
|
|
271187
271438
|
void this.selfTerminate("unreachable");
|
|
271188
271439
|
}
|
|
@@ -278078,7 +278329,7 @@ function shimScript() {
|
|
|
278078
278329
|
}
|
|
278079
278330
|
|
|
278080
278331
|
// src/index.ts
|
|
278081
|
-
var PKG_VERSION = true ? "2.2.
|
|
278332
|
+
var PKG_VERSION = true ? "2.2.40" : "dev";
|
|
278082
278333
|
var LOCAL_BIN = localBin();
|
|
278083
278334
|
var NPM_PREFIX = npmPrefix();
|
|
278084
278335
|
var INSTANCE = DEFAULT_INSTANCE;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oasis_test_v2",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.40",
|
|
4
4
|
"description": "Oasis node daemon + CLI — background daemon, auto-start, full server CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"oasis": "./dist/index.js"
|
|
@@ -26,6 +26,6 @@
|
|
|
26
26
|
"node": ">=20"
|
|
27
27
|
},
|
|
28
28
|
"oasisRelease": {
|
|
29
|
-
"sourceHead": "
|
|
29
|
+
"sourceHead": "69a60134741027d75bde467eafc173401ea28bc8"
|
|
30
30
|
}
|
|
31
31
|
}
|