oasis_test_v2 2.2.30 → 2.2.31
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 +438 -33
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -23350,7 +23350,7 @@ function accumulateClaudeUsage(acc, p2) {
|
|
|
23350
23350
|
acc.saw = true;
|
|
23351
23351
|
}
|
|
23352
23352
|
} else if (type === "result") {
|
|
23353
|
-
acc.finalUsage = mergeFinalUsage(
|
|
23353
|
+
acc.finalUsage = mergeFinalUsage(parseModelUsage(p2["modelUsage"]), parseResultUsage(p2["usage"]));
|
|
23354
23354
|
const c = p2["total_cost_usd"];
|
|
23355
23355
|
if (typeof c === "number" && Number.isFinite(c)) {
|
|
23356
23356
|
acc.costUsd = c;
|
|
@@ -160517,6 +160517,35 @@ var init_infra_error = __esm({
|
|
|
160517
160517
|
}
|
|
160518
160518
|
});
|
|
160519
160519
|
|
|
160520
|
+
// ../server/src/keyed-mutex.ts
|
|
160521
|
+
function createKeyedMutex() {
|
|
160522
|
+
const tails = /* @__PURE__ */ new Map();
|
|
160523
|
+
return {
|
|
160524
|
+
async acquire(key) {
|
|
160525
|
+
const previous3 = tails.get(key) ?? Promise.resolve();
|
|
160526
|
+
let release;
|
|
160527
|
+
const mine = new Promise((resolve10) => {
|
|
160528
|
+
release = resolve10;
|
|
160529
|
+
});
|
|
160530
|
+
const tail = previous3.then(() => mine);
|
|
160531
|
+
tails.set(key, tail);
|
|
160532
|
+
await previous3;
|
|
160533
|
+
let released = false;
|
|
160534
|
+
return () => {
|
|
160535
|
+
if (released) return;
|
|
160536
|
+
released = true;
|
|
160537
|
+
release();
|
|
160538
|
+
if (tails.get(key) === tail) tails.delete(key);
|
|
160539
|
+
};
|
|
160540
|
+
}
|
|
160541
|
+
};
|
|
160542
|
+
}
|
|
160543
|
+
var init_keyed_mutex = __esm({
|
|
160544
|
+
"../server/src/keyed-mutex.ts"() {
|
|
160545
|
+
"use strict";
|
|
160546
|
+
}
|
|
160547
|
+
});
|
|
160548
|
+
|
|
160520
160549
|
// ../server/src/live-chat.ts
|
|
160521
160550
|
function normalizedKindToItemType(kind) {
|
|
160522
160551
|
if (kind === "text") return "message";
|
|
@@ -203113,6 +203142,15 @@ function toApiError(err) {
|
|
|
203113
203142
|
if (isInfrastructureError(err)) return new ApiError(INFRA_ERROR_STATUS, INFRA_ERROR_CODE, message);
|
|
203114
203143
|
return new ApiError(400, "BAD_REQUEST", message);
|
|
203115
203144
|
}
|
|
203145
|
+
function workorderApplyTarget(rawBody) {
|
|
203146
|
+
try {
|
|
203147
|
+
const body2 = JSON.parse(rawBody);
|
|
203148
|
+
const briefSpawn = body2.plan?.ops?.find((op) => op.action === "spawn" && op.id === body2.briefArtifactId && op.workspace);
|
|
203149
|
+
return briefSpawn?.workspace ?? body2.plan?.workspace ?? "";
|
|
203150
|
+
} catch {
|
|
203151
|
+
return "";
|
|
203152
|
+
}
|
|
203153
|
+
}
|
|
203116
203154
|
async function loadWorkorderSnapshot(kernel, workorderId) {
|
|
203117
203155
|
if (!workorderId) return null;
|
|
203118
203156
|
const store = kernel.getStore?.();
|
|
@@ -204616,6 +204654,7 @@ async function startOasisServer(opts) {
|
|
|
204616
204654
|
const stagingWorkspace = /* @__PURE__ */ new Map();
|
|
204617
204655
|
const discussSessions = opts.discussSessions ?? /* @__PURE__ */ new Map();
|
|
204618
204656
|
const openingDiscussions = /* @__PURE__ */ new Map();
|
|
204657
|
+
const workorderApplyLocks = createKeyedMutex();
|
|
204619
204658
|
const reviewWaiters = /* @__PURE__ */ new Map();
|
|
204620
204659
|
const leftWaiterIds = /* @__PURE__ */ new Map();
|
|
204621
204660
|
const LEFT_WAITER_IDS_MAX_DRAFTS = 512;
|
|
@@ -205908,6 +205947,7 @@ async function startOasisServer(opts) {
|
|
|
205908
205947
|
}
|
|
205909
205948
|
}
|
|
205910
205949
|
if (url.pathname === "/api/workorder/apply" && req.method === "POST") {
|
|
205950
|
+
const releaseApplyLock = await workorderApplyLocks.acquire(`${currentCompanyId ?? ""}\0${workorderApplyTarget(rawBody)}`);
|
|
205911
205951
|
try {
|
|
205912
205952
|
const body2 = JSON.parse(rawBody);
|
|
205913
205953
|
let targetWs = body2.plan.workspace;
|
|
@@ -206066,6 +206106,8 @@ async function startOasisServer(opts) {
|
|
|
206066
206106
|
res.writeHead(status, { "content-type": "application/json" }).end(
|
|
206067
206107
|
JSON.stringify({ error: code2, message: err instanceof Error ? err.message : String(err) })
|
|
206068
206108
|
);
|
|
206109
|
+
} finally {
|
|
206110
|
+
releaseApplyLock();
|
|
206069
206111
|
}
|
|
206070
206112
|
return;
|
|
206071
206113
|
}
|
|
@@ -208787,6 +208829,7 @@ var init_server3 = __esm({
|
|
|
208787
208829
|
init_company_data_store_router();
|
|
208788
208830
|
init_router();
|
|
208789
208831
|
init_infra_error();
|
|
208832
|
+
init_keyed_mutex();
|
|
208790
208833
|
init_live_chat();
|
|
208791
208834
|
init_chat_turn_wait();
|
|
208792
208835
|
init_append_now();
|
|
@@ -212404,6 +212447,66 @@ function foldSingleNodeTasks(items) {
|
|
|
212404
212447
|
}
|
|
212405
212448
|
return folded;
|
|
212406
212449
|
}
|
|
212450
|
+
function basenameOfPath(name) {
|
|
212451
|
+
const i = name.lastIndexOf("/");
|
|
212452
|
+
return i >= 0 ? name.slice(i + 1) : name;
|
|
212453
|
+
}
|
|
212454
|
+
function creatorKindOf(actorId) {
|
|
212455
|
+
if (!actorId) return null;
|
|
212456
|
+
return actorId.startsWith("actor:human:") ? "human" : "agent";
|
|
212457
|
+
}
|
|
212458
|
+
function emitChatFolders(items, chatFiles, resolveName) {
|
|
212459
|
+
const groups = /* @__PURE__ */ new Map();
|
|
212460
|
+
for (const file of chatFiles) {
|
|
212461
|
+
const list2 = groups.get(file.chatSessionId) ?? [];
|
|
212462
|
+
list2.push(file);
|
|
212463
|
+
groups.set(file.chatSessionId, list2);
|
|
212464
|
+
}
|
|
212465
|
+
for (const [sessionId, group] of groups) {
|
|
212466
|
+
const head = group[0];
|
|
212467
|
+
const folderPath = `chat:${sessionId}`;
|
|
212468
|
+
const latest = group.reduce((acc, file) => file.uploadedAt > acc ? file.uploadedAt : acc, group[0].uploadedAt);
|
|
212469
|
+
items.push({
|
|
212470
|
+
kind: "folder",
|
|
212471
|
+
id: `folder:${folderPath}`,
|
|
212472
|
+
name: head.chatTitle,
|
|
212473
|
+
path: folderPath,
|
|
212474
|
+
filePath: null,
|
|
212475
|
+
depth: 0,
|
|
212476
|
+
type: "folder",
|
|
212477
|
+
size: null,
|
|
212478
|
+
taskId: null,
|
|
212479
|
+
taskTitle: CHAT_FOLDER_TASK_LABEL,
|
|
212480
|
+
creatorKind: creatorKindOf(head.chatOwnerId),
|
|
212481
|
+
creatorId: head.chatOwnerId,
|
|
212482
|
+
creatorName: resolveName(head.chatOwnerId),
|
|
212483
|
+
updatedAt: latest,
|
|
212484
|
+
folderKind: "chat",
|
|
212485
|
+
chatSessionId: sessionId
|
|
212486
|
+
});
|
|
212487
|
+
for (const file of group) {
|
|
212488
|
+
items.push({
|
|
212489
|
+
kind: "file",
|
|
212490
|
+
id: file.fileId,
|
|
212491
|
+
name: file.name,
|
|
212492
|
+
path: `${folderPath}/${file.name}`,
|
|
212493
|
+
filePath: null,
|
|
212494
|
+
depth: 1,
|
|
212495
|
+
type: file.contentType ?? inferTypeFromName(basenameOfPath(file.name)),
|
|
212496
|
+
blobRef: file.blobId,
|
|
212497
|
+
contentType: file.contentType ?? null,
|
|
212498
|
+
size: file.size,
|
|
212499
|
+
taskId: null,
|
|
212500
|
+
taskTitle: CHAT_FOLDER_TASK_LABEL,
|
|
212501
|
+
creatorKind: creatorKindOf(file.uploadedBy),
|
|
212502
|
+
creatorId: file.uploadedBy ?? null,
|
|
212503
|
+
creatorName: file.uploadedBy ? resolveName(file.uploadedBy) : null,
|
|
212504
|
+
updatedAt: file.uploadedAt,
|
|
212505
|
+
chatSessionId: sessionId
|
|
212506
|
+
});
|
|
212507
|
+
}
|
|
212508
|
+
}
|
|
212509
|
+
}
|
|
212407
212510
|
function fillFolderSizes(items) {
|
|
212408
212511
|
const folders = items.filter((i) => i.kind === "folder" && i.path);
|
|
212409
212512
|
if (folders.length === 0) return;
|
|
@@ -212623,7 +212726,7 @@ function laterOf(a, b2) {
|
|
|
212623
212726
|
if (!b2) return a;
|
|
212624
212727
|
return b2 > a ? b2 : a;
|
|
212625
212728
|
}
|
|
212626
|
-
var DEFAULT_AGENT_ACTOR, MemoryProjectStateStore, MemoryArtifactStateStore, ProjectsService;
|
|
212729
|
+
var DEFAULT_AGENT_ACTOR, CHAT_FOLDER_TASK_LABEL, MemoryProjectStateStore, MemoryArtifactStateStore, ProjectsService;
|
|
212627
212730
|
var init_service4 = __esm({
|
|
212628
212731
|
"../server/src/domains/projects/service.ts"() {
|
|
212629
212732
|
"use strict";
|
|
@@ -212635,6 +212738,7 @@ var init_service4 = __esm({
|
|
|
212635
212738
|
init_stats();
|
|
212636
212739
|
init_node_state();
|
|
212637
212740
|
DEFAULT_AGENT_ACTOR = "actor:agent:system";
|
|
212741
|
+
CHAT_FOLDER_TASK_LABEL = "\u5BF9\u8BDD";
|
|
212638
212742
|
MemoryProjectStateStore = class {
|
|
212639
212743
|
projects = /* @__PURE__ */ new Map();
|
|
212640
212744
|
members = /* @__PURE__ */ new Map();
|
|
@@ -213410,12 +213514,12 @@ var init_service4 = __esm({
|
|
|
213410
213514
|
...items.filter((item) => item.blobRef).map((item) => `${item.name}\0${item.blobRef}`),
|
|
213411
213515
|
...files.map((file) => `${file.name}\0${file.blobId}`)
|
|
213412
213516
|
]);
|
|
213413
|
-
|
|
213517
|
+
const chatFiles = [];
|
|
213518
|
+
if (this.listChatFiles) {
|
|
213414
213519
|
for (const file of await this.listChatFiles(projectId2)) {
|
|
213415
|
-
const key = `${file.name}\0${file.blobId}`;
|
|
213520
|
+
const key = `${basenameOfPath(file.name)}\0${file.blobId}`;
|
|
213416
213521
|
if (existingFiles.has(key)) continue;
|
|
213417
|
-
|
|
213418
|
-
files.push(file);
|
|
213522
|
+
chatFiles.push(file);
|
|
213419
213523
|
}
|
|
213420
213524
|
}
|
|
213421
213525
|
for (const file of files) {
|
|
@@ -213441,8 +213545,18 @@ var init_service4 = __esm({
|
|
|
213441
213545
|
updatedAt: file.uploadedAt
|
|
213442
213546
|
});
|
|
213443
213547
|
}
|
|
213548
|
+
emitChatFolders(items, chatFiles, (actorId) => this.resolveActorName(actorId));
|
|
213444
213549
|
fillFolderSizes(items);
|
|
213550
|
+
const chatFolderTime = new Map(items.filter((item) => item.folderKind === "chat" && item.chatSessionId).map((item) => [item.chatSessionId, item.updatedAt]));
|
|
213445
213551
|
items.sort((a, b2) => {
|
|
213552
|
+
const chatA = a.chatSessionId ? 1 : 0;
|
|
213553
|
+
const chatB = b2.chatSessionId ? 1 : 0;
|
|
213554
|
+
if (chatA !== chatB) return chatA - chatB;
|
|
213555
|
+
if (a.chatSessionId && b2.chatSessionId && a.chatSessionId !== b2.chatSessionId) {
|
|
213556
|
+
const timeA = chatFolderTime.get(a.chatSessionId) ?? "";
|
|
213557
|
+
const timeB = chatFolderTime.get(b2.chatSessionId) ?? "";
|
|
213558
|
+
return timeB.localeCompare(timeA) || a.chatSessionId.localeCompare(b2.chatSessionId);
|
|
213559
|
+
}
|
|
213446
213560
|
const pathA = a.path ?? "";
|
|
213447
213561
|
const pathB = b2.path ?? "";
|
|
213448
213562
|
if (pathA !== pathB) return pathA.localeCompare(pathB);
|
|
@@ -214767,6 +214881,8 @@ var init_dev_store = __esm({
|
|
|
214767
214881
|
sessionWorkOrderKinds = /* @__PURE__ */ new Map();
|
|
214768
214882
|
/** ADR-0166 D6 待发队列(每会话一条,按 seq 升序 = 入队序)。 */
|
|
214769
214883
|
pending = /* @__PURE__ */ new Map();
|
|
214884
|
+
/** `outputs/` 产出文件的服务端副本表(2026-09-14):sessionId → 路径 → 副本。 */
|
|
214885
|
+
outputCopies = /* @__PURE__ */ new Map();
|
|
214770
214886
|
pendingSeq = 0;
|
|
214771
214887
|
async claimPendingInsert(m2) {
|
|
214772
214888
|
const list2 = this.pending.get(m2.chatSessionId) ?? [];
|
|
@@ -214950,6 +215066,7 @@ var init_dev_store = __esm({
|
|
|
214950
215066
|
this.messages.delete(id);
|
|
214951
215067
|
this.sessionWorkOrders.delete(id);
|
|
214952
215068
|
this.sessionWorkOrderKinds.delete(id);
|
|
215069
|
+
this.outputCopies.delete(id);
|
|
214953
215070
|
}
|
|
214954
215071
|
async appendMessage(m2) {
|
|
214955
215072
|
return (await this.appendMessageOnce(m2)).message;
|
|
@@ -215078,6 +215195,27 @@ var init_dev_store = __esm({
|
|
|
215078
215195
|
}
|
|
215079
215196
|
return rows.sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.messageId.localeCompare(right.messageId));
|
|
215080
215197
|
}
|
|
215198
|
+
async readOutputCopies(sessionId) {
|
|
215199
|
+
const hit = this.outputCopies.get(sessionId);
|
|
215200
|
+
return hit ? { ...hit } : null;
|
|
215201
|
+
}
|
|
215202
|
+
async mergeOutputCopies(sessionId, copies) {
|
|
215203
|
+
if (copies.length === 0 || !this.sessions.has(sessionId)) return;
|
|
215204
|
+
const next = { ...this.outputCopies.get(sessionId) ?? {} };
|
|
215205
|
+
for (const copy of copies) next[copy.path] = { ...copy };
|
|
215206
|
+
this.outputCopies.set(sessionId, next);
|
|
215207
|
+
}
|
|
215208
|
+
async listOutputCopies(projectId2) {
|
|
215209
|
+
const rows = [];
|
|
215210
|
+
for (const session of this.sessions.values()) {
|
|
215211
|
+
if (session.visibility === "internal") continue;
|
|
215212
|
+
if ((session.projectId ?? null) !== projectId2) continue;
|
|
215213
|
+
const copies = this.outputCopies.get(session.id);
|
|
215214
|
+
if (!copies || Object.keys(copies).length === 0) continue;
|
|
215215
|
+
rows.push({ sessionId: session.id, ...session.projectId ? { projectId: session.projectId } : {}, copies: { ...copies } });
|
|
215216
|
+
}
|
|
215217
|
+
return rows.sort((left, right) => left.sessionId.localeCompare(right.sessionId));
|
|
215218
|
+
}
|
|
215081
215219
|
async listRunningAssistantMessages() {
|
|
215082
215220
|
const out = [];
|
|
215083
215221
|
for (const list2 of this.messages.values()) {
|
|
@@ -216103,6 +216241,148 @@ var init_outputs_scan = __esm({
|
|
|
216103
216241
|
}
|
|
216104
216242
|
});
|
|
216105
216243
|
|
|
216244
|
+
// ../server/src/domains/chat-sessions/outputs-copy.ts
|
|
216245
|
+
function isOutputPath(path41) {
|
|
216246
|
+
return path41.startsWith(`${SESSION_OUTPUTS_DIR}/`);
|
|
216247
|
+
}
|
|
216248
|
+
function basenameOf2(path41) {
|
|
216249
|
+
const i = path41.lastIndexOf("/");
|
|
216250
|
+
return i >= 0 ? path41.slice(i + 1) : path41;
|
|
216251
|
+
}
|
|
216252
|
+
function planOutputCopies(fingerprints, copies, limits = OUTPUT_COPY_LIMITS) {
|
|
216253
|
+
const have = copies ?? {};
|
|
216254
|
+
const plan = { toCopy: [], skipped: [] };
|
|
216255
|
+
let totalBytes = 0;
|
|
216256
|
+
for (const path41 of Object.keys(fingerprints).sort((a, b2) => a.localeCompare(b2))) {
|
|
216257
|
+
if (!isOutputPath(path41)) continue;
|
|
216258
|
+
const fp = fingerprints[path41];
|
|
216259
|
+
if (typeof fp.size !== "number" || !Number.isFinite(fp.size) || typeof fp.mtime !== "string" || fp.mtime === "") continue;
|
|
216260
|
+
const copy = have[path41];
|
|
216261
|
+
if (copy && copy.size === fp.size && copy.mtime === fp.mtime) continue;
|
|
216262
|
+
if (fp.size > limits.maxFileBytes) {
|
|
216263
|
+
plan.skipped.push({ path: path41, code: "TOO_LARGE" });
|
|
216264
|
+
continue;
|
|
216265
|
+
}
|
|
216266
|
+
if (plan.toCopy.length >= limits.maxFiles || totalBytes + fp.size > limits.maxTotalBytes) {
|
|
216267
|
+
plan.skipped.push({ path: path41, code: "BUDGET" });
|
|
216268
|
+
continue;
|
|
216269
|
+
}
|
|
216270
|
+
totalBytes += fp.size;
|
|
216271
|
+
plan.toCopy.push({ path: path41, size: fp.size, mtime: fp.mtime });
|
|
216272
|
+
}
|
|
216273
|
+
return plan;
|
|
216274
|
+
}
|
|
216275
|
+
async function copySessionOutputs(deps) {
|
|
216276
|
+
const now = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
216277
|
+
const limits = deps.limits ?? OUTPUT_COPY_LIMITS;
|
|
216278
|
+
const result = { copied: [], skipped: [] };
|
|
216279
|
+
let existing;
|
|
216280
|
+
try {
|
|
216281
|
+
existing = await deps.readCopies(deps.sessionId);
|
|
216282
|
+
} catch {
|
|
216283
|
+
return result;
|
|
216284
|
+
}
|
|
216285
|
+
const plan = planOutputCopies(deps.fingerprints, existing, limits);
|
|
216286
|
+
result.skipped.push(...plan.skipped);
|
|
216287
|
+
for (const file of plan.toCopy) {
|
|
216288
|
+
let read;
|
|
216289
|
+
try {
|
|
216290
|
+
read = await deps.bridge.read(
|
|
216291
|
+
deps.loc.nodeId,
|
|
216292
|
+
{ workdirKey: deps.loc.workdirKey, runtimeKind: deps.loc.runtimeKind, rtId: deps.loc.rtId, path: file.path },
|
|
216293
|
+
limits.maxFileBytes
|
|
216294
|
+
);
|
|
216295
|
+
} catch {
|
|
216296
|
+
result.skipped.push({ path: file.path, code: "READ_FAILED" });
|
|
216297
|
+
continue;
|
|
216298
|
+
}
|
|
216299
|
+
if (!read.ok) {
|
|
216300
|
+
result.skipped.push({ path: file.path, code: read.code === "TOO_LARGE" ? "TOO_LARGE" : "READ_FAILED" });
|
|
216301
|
+
continue;
|
|
216302
|
+
}
|
|
216303
|
+
const bytes2 = Buffer.from(read.contentBase64, "base64");
|
|
216304
|
+
let blobRef;
|
|
216305
|
+
try {
|
|
216306
|
+
blobRef = await deps.putBlob(new Uint8Array(bytes2));
|
|
216307
|
+
} catch {
|
|
216308
|
+
result.skipped.push({ path: file.path, code: "STORE_FAILED" });
|
|
216309
|
+
continue;
|
|
216310
|
+
}
|
|
216311
|
+
result.copied.push({
|
|
216312
|
+
path: file.path,
|
|
216313
|
+
name: basenameOf2(file.path),
|
|
216314
|
+
blobRef,
|
|
216315
|
+
contentType: read.contentType ? read.contentType : null,
|
|
216316
|
+
size: bytes2.length,
|
|
216317
|
+
// 记**扫描时**的 mtime(不是读的那一刻):下一轮拿同一把节点时钟的指纹来比,相等才算复制过。
|
|
216318
|
+
mtime: file.mtime,
|
|
216319
|
+
copiedAt: now()
|
|
216320
|
+
});
|
|
216321
|
+
}
|
|
216322
|
+
if (result.copied.length > 0) {
|
|
216323
|
+
try {
|
|
216324
|
+
await deps.mergeCopies(deps.sessionId, result.copied);
|
|
216325
|
+
} catch {
|
|
216326
|
+
result.skipped.push(...result.copied.map((copy) => ({ path: copy.path, code: "STORE_FAILED" })));
|
|
216327
|
+
result.copied = [];
|
|
216328
|
+
}
|
|
216329
|
+
}
|
|
216330
|
+
return result;
|
|
216331
|
+
}
|
|
216332
|
+
function createOutputCopyScheduler(deps) {
|
|
216333
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
216334
|
+
return {
|
|
216335
|
+
schedule(args) {
|
|
216336
|
+
const { store, chatSessionId, companyId } = args;
|
|
216337
|
+
const readCopies = store.readOutputCopies?.bind(store);
|
|
216338
|
+
const mergeCopies = store.mergeOutputCopies?.bind(store);
|
|
216339
|
+
if (!readCopies || !mergeCopies) return Promise.resolve(null);
|
|
216340
|
+
const key = `${companyId ?? ""}|${chatSessionId}`;
|
|
216341
|
+
if (inFlight.has(key)) return Promise.resolve(null);
|
|
216342
|
+
inFlight.add(key);
|
|
216343
|
+
return (async () => {
|
|
216344
|
+
try {
|
|
216345
|
+
const session = await store.getSession(chatSessionId);
|
|
216346
|
+
if (!session || session.visibility === "internal" || session.analyzedRunId) return null;
|
|
216347
|
+
if (deps.isDelegationChild && await deps.isDelegationChild(companyId, chatSessionId).catch(() => false)) return null;
|
|
216348
|
+
const result = await copySessionOutputs({
|
|
216349
|
+
bridge: args.bridge,
|
|
216350
|
+
loc: args.loc,
|
|
216351
|
+
sessionId: chatSessionId,
|
|
216352
|
+
fingerprints: args.fingerprints,
|
|
216353
|
+
readCopies,
|
|
216354
|
+
mergeCopies,
|
|
216355
|
+
putBlob: deps.putBlob,
|
|
216356
|
+
...deps.limits ? { limits: deps.limits } : {}
|
|
216357
|
+
});
|
|
216358
|
+
if (result.skipped.length > 0) {
|
|
216359
|
+
const head = result.skipped.slice(0, 5).map((s2) => `${s2.path}:${s2.code}`).join(", ");
|
|
216360
|
+
deps.log?.(`[outputs-copy] ${chatSessionId}: \u590D\u5236 ${result.copied.length} \u4E2A\uFF0C\u8DF3\u8FC7 ${result.skipped.length} \u4E2A\uFF08${head}${result.skipped.length > 5 ? " \u2026" : ""}\uFF09`);
|
|
216361
|
+
}
|
|
216362
|
+
return result;
|
|
216363
|
+
} catch (err) {
|
|
216364
|
+
deps.log?.(`[outputs-copy] ${chatSessionId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
216365
|
+
return null;
|
|
216366
|
+
} finally {
|
|
216367
|
+
inFlight.delete(key);
|
|
216368
|
+
}
|
|
216369
|
+
})();
|
|
216370
|
+
}
|
|
216371
|
+
};
|
|
216372
|
+
}
|
|
216373
|
+
var OUTPUT_COPY_LIMITS;
|
|
216374
|
+
var init_outputs_copy = __esm({
|
|
216375
|
+
"../server/src/domains/chat-sessions/outputs-copy.ts"() {
|
|
216376
|
+
"use strict";
|
|
216377
|
+
init_src();
|
|
216378
|
+
OUTPUT_COPY_LIMITS = {
|
|
216379
|
+
maxFileBytes: 2 * 1024 * 1024,
|
|
216380
|
+
maxFiles: 50,
|
|
216381
|
+
maxTotalBytes: 50 * 1024 * 1024
|
|
216382
|
+
};
|
|
216383
|
+
}
|
|
216384
|
+
});
|
|
216385
|
+
|
|
216106
216386
|
// ../server/src/org-model-backfill.ts
|
|
216107
216387
|
function resolveDisplayName(existing, humanActor, accountName) {
|
|
216108
216388
|
const pick3 = existing?.trim() || humanActor?.name?.trim() || accountName?.trim();
|
|
@@ -225139,7 +225419,11 @@ function toApiProjectFilesView(view) {
|
|
|
225139
225419
|
creator_kind: item.creatorKind,
|
|
225140
225420
|
creator_id: item.creatorId,
|
|
225141
225421
|
creator_name: item.creatorName,
|
|
225142
|
-
updated_at: item.updatedAt
|
|
225422
|
+
updated_at: item.updatedAt,
|
|
225423
|
+
// 对话文件夹(`"chat"`):前端据此换带对话气泡的文件夹图标。其余文件夹与文件行为 null。
|
|
225424
|
+
folder_kind: item.folderKind ?? null,
|
|
225425
|
+
// 对话文件夹及其子文件所属的对话;前端「所属任务」列的「对话」筛选档靠它。其余行为 null。
|
|
225426
|
+
chat_session_id: item.chatSessionId ?? null
|
|
225143
225427
|
}))
|
|
225144
225428
|
};
|
|
225145
225429
|
}
|
|
@@ -229589,37 +229873,93 @@ var init_files_view = __esm({
|
|
|
229589
229873
|
});
|
|
229590
229874
|
|
|
229591
229875
|
// ../server/src/domains/projects/chat-files.ts
|
|
229592
|
-
|
|
229593
|
-
|
|
229594
|
-
|
|
229595
|
-
|
|
229596
|
-
|
|
229597
|
-
|
|
229598
|
-
|
|
229599
|
-
|
|
229600
|
-
const
|
|
229601
|
-
const
|
|
229602
|
-
|
|
229603
|
-
|
|
229604
|
-
|
|
229605
|
-
|
|
229606
|
-
|
|
229607
|
-
|
|
229608
|
-
|
|
229609
|
-
|
|
229610
|
-
|
|
229611
|
-
|
|
229612
|
-
|
|
229613
|
-
|
|
229614
|
-
|
|
229615
|
-
|
|
229616
|
-
|
|
229876
|
+
function chatFolderTitle(session) {
|
|
229877
|
+
const title = session.title?.trim();
|
|
229878
|
+
return title ? title : "\u672A\u547D\u540D\u5BF9\u8BDD";
|
|
229879
|
+
}
|
|
229880
|
+
function outputDisplayName(path41) {
|
|
229881
|
+
return path41.startsWith("outputs/") ? path41.slice("outputs/".length) : path41;
|
|
229882
|
+
}
|
|
229883
|
+
async function collectProjectChatFiles(chat, projectId2, meta, options = {}) {
|
|
229884
|
+
const scope = isUncategorizedProjectId(projectId2) ? null : projectId2;
|
|
229885
|
+
const [attachmentMessages, outputSessions] = await Promise.all([
|
|
229886
|
+
chat.listAttachmentMessages ? chat.listAttachmentMessages(scope) : Promise.resolve([]),
|
|
229887
|
+
chat.listOutputCopies ? chat.listOutputCopies(scope) : Promise.resolve([])
|
|
229888
|
+
]);
|
|
229889
|
+
const sessionIds = [.../* @__PURE__ */ new Set([
|
|
229890
|
+
...attachmentMessages.map((m2) => m2.sessionId),
|
|
229891
|
+
...outputSessions.map((s2) => s2.sessionId)
|
|
229892
|
+
])];
|
|
229893
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
229894
|
+
await Promise.all(sessionIds.map(async (id) => {
|
|
229895
|
+
const session = await chat.getSession(id);
|
|
229896
|
+
if (!session) return;
|
|
229897
|
+
if ((session.projectId ?? null) !== scope) return;
|
|
229898
|
+
if (session.visibility === "internal" || session.analyzedRunId) return;
|
|
229899
|
+
if (options.isDelegationChild && await options.isDelegationChild(id).catch(() => false)) return;
|
|
229900
|
+
sessions.set(id, session);
|
|
229901
|
+
}));
|
|
229902
|
+
const out = [];
|
|
229903
|
+
const base = (session) => ({
|
|
229904
|
+
projectId: projectId2,
|
|
229905
|
+
chatSessionId: session.id,
|
|
229906
|
+
chatTitle: chatFolderTitle(session),
|
|
229907
|
+
chatOwnerId: session.humanActorId
|
|
229617
229908
|
});
|
|
229909
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
229910
|
+
for (const message of attachmentMessages) {
|
|
229911
|
+
if (!sessions.has(message.sessionId)) continue;
|
|
229912
|
+
const list2 = bySession.get(message.sessionId) ?? [];
|
|
229913
|
+
list2.push(message);
|
|
229914
|
+
bySession.set(message.sessionId, list2);
|
|
229915
|
+
}
|
|
229916
|
+
for (const [sessionId, messages] of bySession) {
|
|
229917
|
+
const session = sessions.get(sessionId);
|
|
229918
|
+
const rows = await collectMessageFiles(messages.map((m2) => ({ ...m2, id: m2.messageId })), { meta });
|
|
229919
|
+
for (const row of mergeChatSessionFiles(rows, [])) {
|
|
229920
|
+
if (row.blob_ref === null) continue;
|
|
229921
|
+
out.push({
|
|
229922
|
+
...base(session),
|
|
229923
|
+
fileId: `chat:${sessionId}:${row.id}`,
|
|
229924
|
+
name: row.name,
|
|
229925
|
+
blobId: row.blob_ref,
|
|
229926
|
+
...row.content_type ? { contentType: row.content_type } : {},
|
|
229927
|
+
size: row.size,
|
|
229928
|
+
uploadedBy: session.humanActorId,
|
|
229929
|
+
uploadedAt: row.updated_at,
|
|
229930
|
+
chatSource: "attachment"
|
|
229931
|
+
});
|
|
229932
|
+
}
|
|
229933
|
+
}
|
|
229934
|
+
for (const record8 of outputSessions) {
|
|
229935
|
+
const session = sessions.get(record8.sessionId);
|
|
229936
|
+
if (!session) continue;
|
|
229937
|
+
const seen = new Set(out.filter((file) => file.chatSessionId === session.id).map((file) => `${file.name}\0${file.blobId}`));
|
|
229938
|
+
const copies = record8.copies ?? {};
|
|
229939
|
+
for (const path41 of Object.keys(copies).sort((a, b2) => a.localeCompare(b2))) {
|
|
229940
|
+
const copy = copies[path41];
|
|
229941
|
+
if (!copy?.blobRef) continue;
|
|
229942
|
+
if (seen.has(`${copy.name}\0${copy.blobRef}`)) continue;
|
|
229943
|
+
out.push({
|
|
229944
|
+
...base(session),
|
|
229945
|
+
fileId: `chat:${session.id}:out:${copy.path}`,
|
|
229946
|
+
name: outputDisplayName(copy.path),
|
|
229947
|
+
blobId: copy.blobRef,
|
|
229948
|
+
...copy.contentType ? { contentType: copy.contentType } : {},
|
|
229949
|
+
size: copy.size,
|
|
229950
|
+
uploadedBy: session.aiActorId,
|
|
229951
|
+
uploadedAt: Number.isNaN(Date.parse(copy.mtime)) ? copy.copiedAt : new Date(copy.mtime).toISOString(),
|
|
229952
|
+
chatSource: "output"
|
|
229953
|
+
});
|
|
229954
|
+
}
|
|
229955
|
+
}
|
|
229956
|
+
return out;
|
|
229618
229957
|
}
|
|
229619
229958
|
var init_chat_files2 = __esm({
|
|
229620
229959
|
"../server/src/domains/projects/chat-files.ts"() {
|
|
229621
229960
|
"use strict";
|
|
229622
229961
|
init_files_view();
|
|
229962
|
+
init_uncategorized_project();
|
|
229623
229963
|
}
|
|
229624
229964
|
});
|
|
229625
229965
|
|
|
@@ -248817,6 +249157,7 @@ var init_postgres_chat_sessions = __esm({
|
|
|
248817
249157
|
await pool.query(`ALTER TABLE ${q2}.chat_sessions ADD COLUMN IF NOT EXISTS model text`);
|
|
248818
249158
|
await pool.query(`ALTER TABLE ${q2}.chat_sessions ADD COLUMN IF NOT EXISTS visibility text`);
|
|
248819
249159
|
await pool.query(`ALTER TABLE ${q2}.chat_sessions ADD COLUMN IF NOT EXISTS outputs_fingerprints jsonb`);
|
|
249160
|
+
await pool.query(`ALTER TABLE ${q2}.chat_sessions ADD COLUMN IF NOT EXISTS outputs_copies jsonb`);
|
|
248820
249161
|
await pool.query(`
|
|
248821
249162
|
WITH ranked AS (
|
|
248822
249163
|
SELECT id, row_number() OVER (PARTITION BY session_id ORDER BY seq, created_at, id) AS rn
|
|
@@ -249064,6 +249405,53 @@ var init_postgres_chat_sessions = __esm({
|
|
|
249064
249405
|
[JSON.stringify(fingerprints), sessionId, this.companyId]
|
|
249065
249406
|
);
|
|
249066
249407
|
}
|
|
249408
|
+
/**
|
|
249409
|
+
* `outputs/` 产出文件的服务端副本表(2026-09-14)。读:没有副本 → null。
|
|
249410
|
+
*/
|
|
249411
|
+
async readOutputCopies(sessionId) {
|
|
249412
|
+
const r = await this.pool.query(
|
|
249413
|
+
`SELECT outputs_copies FROM ${this.s}.chat_sessions WHERE id = $1 AND company_id = $2`,
|
|
249414
|
+
[sessionId, this.companyId]
|
|
249415
|
+
);
|
|
249416
|
+
return r.rows[0]?.outputs_copies ?? null;
|
|
249417
|
+
}
|
|
249418
|
+
/**
|
|
249419
|
+
* 按路径**合并**写入:同一路径新副本覆盖旧副本,其余路径保留。合并在一条 UPDATE 里用 jsonb `||` 完成,
|
|
249420
|
+
* 不是读出来改完再整份写回——两次收尾的复制前后脚落库时,后写的不会把先写的吞掉。
|
|
249421
|
+
*/
|
|
249422
|
+
async mergeOutputCopies(sessionId, copies) {
|
|
249423
|
+
if (copies.length === 0) return;
|
|
249424
|
+
const patch = {};
|
|
249425
|
+
for (const copy of copies) patch[copy.path] = copy;
|
|
249426
|
+
await this.pool.query(
|
|
249427
|
+
`UPDATE ${this.s}.chat_sessions
|
|
249428
|
+
SET outputs_copies = COALESCE(outputs_copies, '{}'::jsonb) || $1::jsonb
|
|
249429
|
+
WHERE id = $2 AND company_id = $3`,
|
|
249430
|
+
[JSON.stringify(patch), sessionId, this.companyId]
|
|
249431
|
+
);
|
|
249432
|
+
}
|
|
249433
|
+
/**
|
|
249434
|
+
* 某个范围内有副本的会话。口径同 {@link listAttachmentMessages}:排除内部会话、按公司收口、
|
|
249435
|
+
* `project_id IS NOT DISTINCT FROM $1`(null = 没绑项目的会话)。只取三列,不碰会话其余字段。
|
|
249436
|
+
*/
|
|
249437
|
+
async listOutputCopies(projectId2) {
|
|
249438
|
+
const r = await this.pool.query(
|
|
249439
|
+
`SELECT id, project_id, outputs_copies
|
|
249440
|
+
FROM ${this.s}.chat_sessions
|
|
249441
|
+
WHERE outputs_copies IS NOT NULL
|
|
249442
|
+
AND outputs_copies <> '{}'::jsonb
|
|
249443
|
+
AND visibility IS DISTINCT FROM 'internal'
|
|
249444
|
+
AND project_id IS NOT DISTINCT FROM $1
|
|
249445
|
+
AND company_id = $2
|
|
249446
|
+
ORDER BY id`,
|
|
249447
|
+
[projectId2, this.companyId]
|
|
249448
|
+
);
|
|
249449
|
+
return r.rows.map((row) => ({
|
|
249450
|
+
sessionId: row.id,
|
|
249451
|
+
...row.project_id ? { projectId: row.project_id } : {},
|
|
249452
|
+
copies: row.outputs_copies ?? {}
|
|
249453
|
+
}));
|
|
249454
|
+
}
|
|
249067
249455
|
/** 删项目时把本公司里绑到它的会话回落成「不指定项目」。会话一条都不删,只清 project_id。 */
|
|
249068
249456
|
async clearProjectBinding(projectId2) {
|
|
249069
249457
|
if (!projectId2) return 0;
|
|
@@ -259242,6 +259630,7 @@ var init_src11 = __esm({
|
|
|
259242
259630
|
init_company_scoped_registry();
|
|
259243
259631
|
init_session_workdir();
|
|
259244
259632
|
init_outputs_scan();
|
|
259633
|
+
init_outputs_copy();
|
|
259245
259634
|
init_org_model_backfill();
|
|
259246
259635
|
init_types_admin();
|
|
259247
259636
|
init_roles_admin();
|
|
@@ -261050,6 +261439,13 @@ async function startServe(opts) {
|
|
|
261050
261439
|
}
|
|
261051
261440
|
}
|
|
261052
261441
|
return null;
|
|
261442
|
+
}, {
|
|
261443
|
+
// 委派子会话不进项目(ADR 0555:它的 outputs/ 是交给协调者的)。闭包在请求时才读
|
|
261444
|
+
// `delegationStoreFor`,避开它在下方声明的 TDZ。
|
|
261445
|
+
isDelegationChild: async (sessionId) => {
|
|
261446
|
+
const ledger = await delegationStoreFor(companyId).catch(() => null);
|
|
261447
|
+
return Boolean(ledger && await ledger.getByChildSession(sessionId).catch(() => null));
|
|
261448
|
+
}
|
|
261053
261449
|
});
|
|
261054
261450
|
},
|
|
261055
261451
|
projects: projectStateStore,
|
|
@@ -262212,6 +262608,14 @@ async function startServe(opts) {
|
|
|
262212
262608
|
delegationStores.set(id, store);
|
|
262213
262609
|
return store;
|
|
262214
262610
|
};
|
|
262611
|
+
const outputCopyScheduler = createOutputCopyScheduler({
|
|
262612
|
+
putBlob: (bytes2) => assets.put(bytes2),
|
|
262613
|
+
isDelegationChild: async (companyId, sessionId) => {
|
|
262614
|
+
const ledger = await delegationStoreFor(companyId).catch(() => null);
|
|
262615
|
+
return Boolean(ledger && await ledger.getByChildSession(sessionId).catch(() => null));
|
|
262616
|
+
},
|
|
262617
|
+
log: (message) => console.warn(message)
|
|
262618
|
+
});
|
|
262215
262619
|
const chatBroadcastMarkers = /* @__PURE__ */ new Map([
|
|
262216
262620
|
[defaultCompanyId, chatBroadcastMarker]
|
|
262217
262621
|
]);
|
|
@@ -262943,6 +263347,7 @@ async function startServe(opts) {
|
|
|
262943
263347
|
readFingerprints: read,
|
|
262944
263348
|
writeFingerprints: write
|
|
262945
263349
|
}).catch(() => null);
|
|
263350
|
+
if (diff && store) void outputCopyScheduler.schedule({ store, bridge, loc: located.loc, chatSessionId, companyId, fingerprints: diff.fingerprints });
|
|
262946
263351
|
return diff?.changed ?? null;
|
|
262947
263352
|
},
|
|
262948
263353
|
delegationWorkdir: createDelegationWorkdirPort({
|
|
@@ -273778,7 +274183,7 @@ function shimScript() {
|
|
|
273778
274183
|
}
|
|
273779
274184
|
|
|
273780
274185
|
// src/index.ts
|
|
273781
|
-
var PKG_VERSION = true ? "2.2.
|
|
274186
|
+
var PKG_VERSION = true ? "2.2.31" : "dev";
|
|
273782
274187
|
var LOCAL_BIN = localBin();
|
|
273783
274188
|
var NPM_PREFIX = npmPrefix();
|
|
273784
274189
|
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.31",
|
|
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": "bbc56bf65b7e0c10506c3919f42c9c7d8b7c7342"
|
|
30
30
|
}
|
|
31
31
|
}
|