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.
Files changed (2) hide show
  1. package/dist/index.js +617 -366
  2. 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 (!fileExtensionOf(name) || isEditableFileName(name)) text6 = decodeText3(await blobs.get(output.contentRef));
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, import_node_crypto45.randomUUID)());
213702
+ return projectIdFromUuid((0, import_node_crypto46.randomUUID)());
213311
213703
  }
213312
- var import_node_crypto45;
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
- import_node_crypto45 = require("node:crypto");
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 import_node_crypto46, fs20, path21, NdjsonOplogStore, DirBlobStore, MUTATORS, FileTypeRegistryStore, FileRoleRegistryStore, FileRegistryStore, FileAssistantBindStore, FileHumanPrefsStore, FileProjectStateStore, FileArtifactStateStore, FileTraceStore, MemoryChatSessionStore, FileChatSessionStore, MemoryChatTurnStore, FileChatTurnStore, MemoryReadMarkerStore, FileReadMarkerStore;
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
- import_node_crypto46 = require("node:crypto");
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, import_node_crypto46.createHash)("sha256").update(bytes2).digest("hex");
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, import_node_crypto47.createHash)("sha256").update(bytes2).digest("hex")}`;
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 import_node_crypto47, DisabledSkillMarketClient, HttpSkillMarketClient;
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
- import_node_crypto47 = require("node:crypto");
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 joinRel(dir, name) {
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(joinRel(dir, entry.name));
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: joinRel(dir, entry.name), size: entry.size, mtime: entry.mtime });
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 import_node_crypto48, RESERVED_FIELD_NAMES, ARTIFACT_TYPE_ACCENTS, TYPE_DEF_SCHEMA, TypesAdminService;
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
- import_node_crypto48 = require("node:crypto");
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, import_node_crypto48.randomUUID)()}`,
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 import_node_crypto49, ACCENTS, ROLE_DEF_SCHEMA, RolesAdminService;
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
- import_node_crypto49 = require("node:crypto");
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, import_node_crypto49.randomUUID)()}`,
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
- await add(item.name, item.blob_ref, path41, item.source === "message" || input.fresh);
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 || session.humanActorId !== req.auth.actor) throw new ApiError(404, "NOT_FOUND", "session not found");
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
- "\u4F1A\u8BDD\u5DE5\u4F5C\u533A\u7684\u6587\u4EF6\u672C\u671F\u53EA\u8BFB\uFF1A\u5199\u56DE\u8981\u65B0\u589E node\u2194server \u534F\u8BAE\u5E27\uFF0C\u4E0D\u5728\u672C\u671F\u8303\u56F4\u5185\u3002\u8BF7\u4E0B\u8F7D\u540E\u91CD\u65B0\u4F5C\u4E3A\u9644\u4EF6\u53D1\u9001\u3002"
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") throw new ApiError(400, "BAD_REQUEST", 'source \u5FC5\u987B\u662F "message"');
239541
- if (typeof body2.messageId !== "string" || !body2.messageId) throw new ApiError(400, "BAD_REQUEST", "messageId required");
239542
- 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");
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\u672C\u671F\u652F\u6301\u7AD9\u5185\u7F16\u8F91\u7684\u6587\u672C\u7C7B\u578B`);
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(Buffer.from(body2.contentBase64, "base64")));
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
- lastConnectedAt = Date.now();
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.lastConnectedAt = Date.now();
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 (!this.connected && now - this.lastConnectedAt >= this.suicideAfterUnreachableMs) {
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.39" : "dev";
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.39",
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": "3538428ba9e941635ecc56e2a7cd5c7d46419c24"
29
+ "sourceHead": "69a60134741027d75bde467eafc173401ea28bc8"
30
30
  }
31
31
  }