oasis_test 0.1.36 → 0.1.37
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 +332 -57
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -184,6 +184,7 @@ function fold(model, op) {
|
|
|
184
184
|
anchor: { target: op.target, ...p2.anchorSpan !== void 0 ? { span: p2.anchorSpan } : {} },
|
|
185
185
|
author: op.actor,
|
|
186
186
|
body: p2.body,
|
|
187
|
+
...p2.attachments !== void 0 && p2.attachments.length > 0 ? { attachments: p2.attachments.map((a) => ({ ...a })) } : {},
|
|
187
188
|
state: "open",
|
|
188
189
|
kind: p2.kind ?? defaultKind,
|
|
189
190
|
stagedResolution: null,
|
|
@@ -1858,17 +1859,17 @@ var init_kernel = __esm({
|
|
|
1858
1859
|
const artifact = this.artifactOrThrow(args.artifactId);
|
|
1859
1860
|
this.enforceAcl("propose", this.schema.get(artifact.type), args.actor, artifact.owner);
|
|
1860
1861
|
const proposingPart = args.part ?? null;
|
|
1861
|
-
if (unresolvedGapsOf(this.model, args.artifactId).some((g2) => g2.part === proposingPart)) {
|
|
1862
|
+
if (proposingPart !== null && unresolvedGapsOf(this.model, args.artifactId).some((g2) => g2.part === proposingPart)) {
|
|
1862
1863
|
throw new KernelError(
|
|
1863
|
-
|
|
1864
|
+
`\u5931\u8D25\uFF1A\u8FD9\u4E2A\u90E8\u4EF6\uFF08${args.part}\uFF09\u8FD8\u6709\u672A\u89E3\u51B3\u7684 gap\uFF0C\u4E0D\u80FD\u63D0\u4EA4\u3002\u53EF\u4EE5\u76F4\u63A5\u6536\u5DE5\u2014\u2014gap \u6709\u7ED3\u679C\u540E\u7CFB\u7EDF\u4F1A\u91CD\u65B0\u5524\u8D77\u4F60\u7EED\u505A\uFF0C\u4E0D\u7528\u7B49\u5728\u8FD9\u91CC\u3002`
|
|
1864
1865
|
);
|
|
1865
1866
|
}
|
|
1866
|
-
const proposingHasOpenBackpressure = [...this.model.annotations.values()].some(
|
|
1867
|
+
const proposingHasOpenBackpressure = proposingPart !== null && [...this.model.annotations.values()].some(
|
|
1867
1868
|
(a) => a.raisedFrom === args.artifactId && a.state === "open" && a.raisedFromPart === proposingPart
|
|
1868
1869
|
);
|
|
1869
1870
|
if (proposingHasOpenBackpressure) {
|
|
1870
1871
|
throw new KernelError(
|
|
1871
|
-
|
|
1872
|
+
`\u5931\u8D25\uFF1A\u8FD9\u4E2A\u90E8\u4EF6\uFF08${args.part}\uFF09\u8FD8\u6709\u672A\u7B54\u590D\u7684\u56DE\u538B\uFF08\u4F60\u5411\u4E0A\u6E38\u63D0\u7684\u7591\u95EE\u8FD8\u6CA1\u56DE\uFF09\uFF0C\u4E0D\u80FD\u63D0\u4EA4\u3002\u53EF\u4EE5\u76F4\u63A5\u6536\u5DE5\u2014\u2014\u4E0A\u6E38\u7B54\u590D\u540E\u7CFB\u7EDF\u4F1A\u91CD\u65B0\u5524\u8D77\u4F60\u7EED\u505A\u3002`
|
|
1872
1873
|
);
|
|
1873
1874
|
}
|
|
1874
1875
|
const base = args.base === void 0 ? artifact.currentRev : args.base;
|
|
@@ -1892,6 +1893,14 @@ var init_kernel = __esm({
|
|
|
1892
1893
|
`part "${args.part}" \u4E0D\u662F ${args.artifactId} \u58F0\u660E\u7684\u90E8\u4EF6\uFF08\u6709\uFF1A${(artifact.parts ?? []).map((p2) => p2.name).join("\u3001") || "\u65E0"}\uFF09`
|
|
1893
1894
|
);
|
|
1894
1895
|
}
|
|
1896
|
+
const alreadyProduced = revisionsOf(this.model, args.artifactId).some(
|
|
1897
|
+
(r) => r.part === args.part && r.state !== "rejected"
|
|
1898
|
+
);
|
|
1899
|
+
if (alreadyProduced) {
|
|
1900
|
+
throw new KernelError(
|
|
1901
|
+
`\u5931\u8D25\uFF1A\u8FD9\u4E2A\u90E8\u4EF6\uFF08${args.part}\uFF09\u5DF2\u7ECF\u63D0\u4EA4\u8FC7\u4E00\u7248\u4E86\u2014\u2014\u4E00\u90E8\u4EF6\u4E00\u7248\u3001\u4EA4\u4E86\u5373\u7EC8\u7ED3\uFF0C\u4E0D\u80FD\u518D\u4EA4\u3002\u53EF\u4EE5\u76F4\u63A5\u6536\u5DE5\u3002`
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1895
1904
|
}
|
|
1896
1905
|
if (args.rebasedFrom !== void 0) {
|
|
1897
1906
|
const old = this.model.revisions.get(args.rebasedFrom);
|
|
@@ -2081,6 +2090,12 @@ var init_kernel = __esm({
|
|
|
2081
2090
|
`\u8FD8\u6709\u672A\u7B54\u590D\u7684\u56DE\u538B annotation\uFF08\u81EA\u63D0 ${openSelfAnno.length} \u6761\uFF09\uFF0C\u4E0D\u80FD conclude\u2014\u2014\u7B49\u4E0A\u6E38\u56DE\u5E94\u3001\u90A3\u6761 revision \u7EED\u5B8C\u518D\u6536\u53E3\uFF08\u7EBF2 \u2462\uFF09`
|
|
2082
2091
|
);
|
|
2083
2092
|
}
|
|
2093
|
+
const openReceived = openBackpressureOf(this.model, args.artifactId);
|
|
2094
|
+
if (openReceived.length > 0) {
|
|
2095
|
+
throw new KernelError(
|
|
2096
|
+
`\u5931\u8D25\uFF1A\u8FD8\u6709 ${openReceived.length} \u6761\u4E0B\u6E38\u56DE\u538B\u6CA1\u7B54\u590D\uFF08\u4E0B\u6E38\u660E\u786E\u6253\u56DE\u4E86\u5F53\u524D\u7248\u672C\uFF09\uFF0C\u4E0D\u80FD conclude\u2014\u2014\u5148\u6309\u56DE\u538B\u6539\u5185\u5BB9\u3001resolve \u56DE\u5E94\u4E0B\u6E38\uFF0C\u518D\u6536\u53E3\uFF08\u4E24\u5C42\u9501\u70B9 \xA76.3\uFF09\u3002`
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
2084
2099
|
const unproduced = (artifact.parts ?? []).filter(
|
|
2085
2100
|
(p2) => !revisionsOf(this.model, args.artifactId).some((r) => r.part === p2.name && r.state !== "rejected")
|
|
2086
2101
|
);
|
|
@@ -2262,6 +2277,7 @@ var init_kernel = __esm({
|
|
|
2262
2277
|
const payload = {
|
|
2263
2278
|
annotationId,
|
|
2264
2279
|
body: args.body,
|
|
2280
|
+
...args.attachments !== void 0 && args.attachments.length > 0 ? { attachments: args.attachments } : {},
|
|
2265
2281
|
// 信箱语义(v3.1 ①a):命令层一律盖章——新批注都是"寄给 owner 的信";存量旧 op 无此字段不参与唤醒
|
|
2266
2282
|
mail: true,
|
|
2267
2283
|
...args.span !== void 0 ? { anchorSpan: args.span } : {},
|
|
@@ -2577,7 +2593,7 @@ var init_kernel = __esm({
|
|
|
2577
2593
|
* 派生里程碑带 forced(by, reason),消费者可见。
|
|
2578
2594
|
*/
|
|
2579
2595
|
async forceConclude(args) {
|
|
2580
|
-
this.bounceHeadMovers(args.artifactId);
|
|
2596
|
+
this.bounceHeadMovers(args.artifactId, { allowPendingGate: true });
|
|
2581
2597
|
this.ensureActive(args.artifactId);
|
|
2582
2598
|
const artifact = this.artifactOrThrow(args.artifactId);
|
|
2583
2599
|
if (artifact.currentRev === null) {
|
|
@@ -3029,10 +3045,10 @@ ${res.candidates.map((c) => ` - ${c.type}:${c.title ?? "(\u65E0title)"} \u2192
|
|
|
3029
3045
|
return this.blobs;
|
|
3030
3046
|
}
|
|
3031
3047
|
/** 推进 head / 改变队列结局的命令:两档冻结都拦;收尾 gate 待审中 = 派生的 merge 档冻结(§5.7)。 */
|
|
3032
|
-
bounceHeadMovers(artifactId) {
|
|
3048
|
+
bounceHeadMovers(artifactId, opts) {
|
|
3033
3049
|
const scope = this.frozen.get(artifactId);
|
|
3034
3050
|
if (scope) throw new FrozenError(artifactId, scope);
|
|
3035
|
-
if (pendingConcludeAttempt(this.model, artifactId)) throw new FrozenError(artifactId, "merge");
|
|
3051
|
+
if (!opts?.allowPendingGate && pendingConcludeAttempt(this.model, artifactId)) throw new FrozenError(artifactId, "merge");
|
|
3036
3052
|
}
|
|
3037
3053
|
/** propose(emit)类:只有 write 档(§11.7 干预)才拦——merge 档下照常入队。 */
|
|
3038
3054
|
bounceWrites(artifactId) {
|
|
@@ -3757,31 +3773,6 @@ function partHasOpenBackpressure(model, id, part) {
|
|
|
3757
3773
|
function partProduced(model, artifactId, part) {
|
|
3758
3774
|
return revisionsOf(model, artifactId).some((r) => r.part === part && r.state !== "rejected");
|
|
3759
3775
|
}
|
|
3760
|
-
function partLastRevSeq(model, artifactId, part) {
|
|
3761
|
-
let max = null;
|
|
3762
|
-
for (const r of revisionsOf(model, artifactId)) {
|
|
3763
|
-
if (r.part !== part || r.state === "rejected") continue;
|
|
3764
|
-
const seq = model.proposeSeq.get(r.id);
|
|
3765
|
-
if (seq !== void 0 && (max === null || seq > max)) max = seq;
|
|
3766
|
-
}
|
|
3767
|
-
return max;
|
|
3768
|
-
}
|
|
3769
|
-
function partReworkNeeded(model, artifactId, part) {
|
|
3770
|
-
const mySeq = partLastRevSeq(model, artifactId, part);
|
|
3771
|
-
if (mySeq === null) return false;
|
|
3772
|
-
for (const a of pendingBackpressureResponsesOf(model, artifactId)) {
|
|
3773
|
-
if (a.raisedFromPart === part) return true;
|
|
3774
|
-
}
|
|
3775
|
-
for (const g2 of model.gaps.get(artifactId) ?? []) {
|
|
3776
|
-
if (g2.part === part && g2.resolved && g2.resolvedAtSeq !== void 0 && g2.resolvedAtSeq > mySeq) return true;
|
|
3777
|
-
}
|
|
3778
|
-
const spec = (model.artifacts.get(artifactId)?.parts ?? []).find((p2) => p2.name === part);
|
|
3779
|
-
for (const dep of spec?.deps ?? []) {
|
|
3780
|
-
const depSeq = partLastRevSeq(model, artifactId, dep);
|
|
3781
|
-
if (depSeq !== null && depSeq > mySeq) return true;
|
|
3782
|
-
}
|
|
3783
|
-
return false;
|
|
3784
|
-
}
|
|
3785
3776
|
function computeProduceJobs(model) {
|
|
3786
3777
|
const out = [];
|
|
3787
3778
|
for (const artifact of model.artifacts.values()) {
|
|
@@ -3801,7 +3792,7 @@ function computeProduceJobs(model) {
|
|
|
3801
3792
|
if (partHasOpenBackpressure(model, artifact.id, null)) continue;
|
|
3802
3793
|
for (const part of parts) {
|
|
3803
3794
|
if (gaps.some((g2) => g2.part === part.name) || partHasOpenBackpressure(model, artifact.id, part.name)) continue;
|
|
3804
|
-
if (partProduced(model, artifact.id, part.name)
|
|
3795
|
+
if (partProduced(model, artifact.id, part.name)) continue;
|
|
3805
3796
|
const deps = part.deps ?? [];
|
|
3806
3797
|
if (!deps.every((d) => partProduced(model, artifact.id, d))) continue;
|
|
3807
3798
|
out.push({ artifactId: artifact.id, part: part.name });
|
|
@@ -3817,7 +3808,6 @@ function computeIntegrateJobs(model) {
|
|
|
3817
3808
|
if (!isProducible(model, artifact.id)) continue;
|
|
3818
3809
|
if (openBackpressureOf(model, artifact.id).length > 0) continue;
|
|
3819
3810
|
if (!parts.every((p2) => partProduced(model, artifact.id, p2.name))) continue;
|
|
3820
|
-
if (parts.some((p2) => partReworkNeeded(model, artifact.id, p2.name))) continue;
|
|
3821
3811
|
if (queueOf(model, artifact.id).length === 0) continue;
|
|
3822
3812
|
out.push(artifact.id);
|
|
3823
3813
|
}
|
|
@@ -21687,6 +21677,121 @@ var init_router = __esm({
|
|
|
21687
21677
|
}
|
|
21688
21678
|
});
|
|
21689
21679
|
|
|
21680
|
+
// ../server/src/live-chat.ts
|
|
21681
|
+
var DEFAULT_BUFFER_MAX, DEFAULT_GRACE_MS, LiveChatRegistry;
|
|
21682
|
+
var init_live_chat = __esm({
|
|
21683
|
+
"../server/src/live-chat.ts"() {
|
|
21684
|
+
"use strict";
|
|
21685
|
+
DEFAULT_BUFFER_MAX = 4e3;
|
|
21686
|
+
DEFAULT_GRACE_MS = 6e4;
|
|
21687
|
+
LiveChatRegistry = class {
|
|
21688
|
+
turns = /* @__PURE__ */ new Map();
|
|
21689
|
+
bufferMax;
|
|
21690
|
+
graceMs;
|
|
21691
|
+
constructor(opts = {}) {
|
|
21692
|
+
this.bufferMax = opts.bufferMax ?? DEFAULT_BUFFER_MAX;
|
|
21693
|
+
this.graceMs = opts.graceMs ?? DEFAULT_GRACE_MS;
|
|
21694
|
+
}
|
|
21695
|
+
/**
|
|
21696
|
+
* 开始一轮。若该会话已有驻留的旧轮(上一轮已 done 但还在 grace 期),先清掉。
|
|
21697
|
+
* 同一会话不允许两轮 running 并存——上层保证一个 chatSessionId 一次只发一轮。
|
|
21698
|
+
*/
|
|
21699
|
+
start(chatSessionId, handle) {
|
|
21700
|
+
const prev = this.turns.get(chatSessionId);
|
|
21701
|
+
if (prev?.evictTimer) clearTimeout(prev.evictTimer);
|
|
21702
|
+
const turn = {
|
|
21703
|
+
chatSessionId,
|
|
21704
|
+
runtimeSessionId: handle.runtimeSessionId,
|
|
21705
|
+
...handle.runId ? { runId: handle.runId } : {},
|
|
21706
|
+
seq: 0,
|
|
21707
|
+
buffer: [],
|
|
21708
|
+
minBufferedSeq: 0,
|
|
21709
|
+
subscribers: /* @__PURE__ */ new Set(),
|
|
21710
|
+
status: "running",
|
|
21711
|
+
handle
|
|
21712
|
+
};
|
|
21713
|
+
this.turns.set(chatSessionId, turn);
|
|
21714
|
+
return {
|
|
21715
|
+
runtimeSessionId: turn.runtimeSessionId,
|
|
21716
|
+
emit: (part) => {
|
|
21717
|
+
const withSeq = { ...part, seq: ++turn.seq };
|
|
21718
|
+
turn.buffer.push(withSeq);
|
|
21719
|
+
if (turn.minBufferedSeq === 0) turn.minBufferedSeq = withSeq.seq;
|
|
21720
|
+
if (turn.buffer.length > this.bufferMax) {
|
|
21721
|
+
const dropped = turn.buffer.splice(0, turn.buffer.length - this.bufferMax);
|
|
21722
|
+
const last = dropped[dropped.length - 1];
|
|
21723
|
+
if (last) turn.minBufferedSeq = last.seq + 1;
|
|
21724
|
+
}
|
|
21725
|
+
for (const sub of turn.subscribers) {
|
|
21726
|
+
try {
|
|
21727
|
+
sub(withSeq);
|
|
21728
|
+
} catch {
|
|
21729
|
+
}
|
|
21730
|
+
}
|
|
21731
|
+
return withSeq;
|
|
21732
|
+
},
|
|
21733
|
+
subscribe: (sub) => {
|
|
21734
|
+
turn.subscribers.add(sub);
|
|
21735
|
+
return () => {
|
|
21736
|
+
turn.subscribers.delete(sub);
|
|
21737
|
+
};
|
|
21738
|
+
},
|
|
21739
|
+
finish: (status) => {
|
|
21740
|
+
turn.status = status;
|
|
21741
|
+
turn.subscribers.clear();
|
|
21742
|
+
turn.evictTimer = setTimeout(() => {
|
|
21743
|
+
if (this.turns.get(chatSessionId) === turn) this.turns.delete(chatSessionId);
|
|
21744
|
+
}, this.graceMs);
|
|
21745
|
+
turn.evictTimer.unref?.();
|
|
21746
|
+
},
|
|
21747
|
+
bufferedParts: () => turn.buffer.slice()
|
|
21748
|
+
};
|
|
21749
|
+
}
|
|
21750
|
+
/**
|
|
21751
|
+
* 观众接入运行中的一轮。无该会话的 live 轮时返回 null(前端回退 getChatSessionDetail 读 DB 最终态)。
|
|
21752
|
+
* 有则返回需补发的历史 part + 当前状态 + detach。live part 后续经 sub 推送。
|
|
21753
|
+
*/
|
|
21754
|
+
attach(chatSessionId, fromSeq, sub) {
|
|
21755
|
+
const turn = this.turns.get(chatSessionId);
|
|
21756
|
+
if (!turn) return null;
|
|
21757
|
+
const gap = turn.minBufferedSeq > 0 && fromSeq + 1 < turn.minBufferedSeq;
|
|
21758
|
+
const replay = turn.buffer.filter((p2) => p2.seq > fromSeq);
|
|
21759
|
+
if (turn.status === "running") {
|
|
21760
|
+
turn.subscribers.add(sub);
|
|
21761
|
+
}
|
|
21762
|
+
return {
|
|
21763
|
+
replay,
|
|
21764
|
+
status: turn.status,
|
|
21765
|
+
gap,
|
|
21766
|
+
detach: () => {
|
|
21767
|
+
turn.subscribers.delete(sub);
|
|
21768
|
+
}
|
|
21769
|
+
};
|
|
21770
|
+
}
|
|
21771
|
+
/** 显式停止:kill agent 这一轮。返回是否命中一个 live 轮。 */
|
|
21772
|
+
stop(chatSessionId) {
|
|
21773
|
+
const turn = this.turns.get(chatSessionId);
|
|
21774
|
+
if (!turn) return false;
|
|
21775
|
+
if (turn.status === "running") {
|
|
21776
|
+
try {
|
|
21777
|
+
turn.handle.kill();
|
|
21778
|
+
} catch {
|
|
21779
|
+
}
|
|
21780
|
+
}
|
|
21781
|
+
return true;
|
|
21782
|
+
}
|
|
21783
|
+
/** 该会话当前是否有一轮 running(供上层判断是否要挡并发发送)。 */
|
|
21784
|
+
isRunning(chatSessionId) {
|
|
21785
|
+
return this.turns.get(chatSessionId)?.status === "running";
|
|
21786
|
+
}
|
|
21787
|
+
/** 测试/排障用:当前驻留的会话数。 */
|
|
21788
|
+
size() {
|
|
21789
|
+
return this.turns.size;
|
|
21790
|
+
}
|
|
21791
|
+
};
|
|
21792
|
+
}
|
|
21793
|
+
});
|
|
21794
|
+
|
|
21690
21795
|
// ../server/src/governance/workorder-drafts.ts
|
|
21691
21796
|
var import_node_fs, WorkorderDraftStore;
|
|
21692
21797
|
var init_workorder_drafts = __esm({
|
|
@@ -23315,6 +23420,17 @@ function truncateOutput(text) {
|
|
|
23315
23420
|
return text.length > CHAT_OUTPUT_LIMIT ? `${text.slice(0, CHAT_OUTPUT_LIMIT)}
|
|
23316
23421
|
...[truncated]` : text;
|
|
23317
23422
|
}
|
|
23423
|
+
function startStreamKeepalive(res, ms = 15e3) {
|
|
23424
|
+
const timer = setInterval(() => {
|
|
23425
|
+
if (res.destroyed || !res.writable) return;
|
|
23426
|
+
try {
|
|
23427
|
+
res.write("\n");
|
|
23428
|
+
} catch {
|
|
23429
|
+
}
|
|
23430
|
+
}, ms);
|
|
23431
|
+
timer.unref?.();
|
|
23432
|
+
return () => clearInterval(timer);
|
|
23433
|
+
}
|
|
23318
23434
|
function chatPartFromTrajectoryEvent(event) {
|
|
23319
23435
|
const payload = asObj2(event.payload);
|
|
23320
23436
|
if (event.kind === "message") {
|
|
@@ -23418,6 +23534,16 @@ function optStr(args, key) {
|
|
|
23418
23534
|
if (typeof v2 !== "string") throw new Error(`\u53C2\u6570 ${key} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
|
|
23419
23535
|
return v2;
|
|
23420
23536
|
}
|
|
23537
|
+
function attachmentArgs(args) {
|
|
23538
|
+
const raw = args["attachments"];
|
|
23539
|
+
if (!Array.isArray(raw)) return void 0;
|
|
23540
|
+
return raw.filter((a) => typeof a === "object" && a !== null).filter((a) => typeof a.name === "string" && (typeof a.text === "string" || typeof a.blobRef === "string")).slice(0, 5).map((a) => ({
|
|
23541
|
+
name: a.name,
|
|
23542
|
+
...typeof a.text === "string" ? { text: a.text } : {},
|
|
23543
|
+
...typeof a.blobRef === "string" ? { blobRef: a.blobRef } : {},
|
|
23544
|
+
...typeof a.contentType === "string" ? { contentType: a.contentType } : {}
|
|
23545
|
+
}));
|
|
23546
|
+
}
|
|
23421
23547
|
function hasOpenHumanChangeRequest2(kernel, artifactId) {
|
|
23422
23548
|
return hasOpenHumanChangeRequest(kernel.model, artifactId);
|
|
23423
23549
|
}
|
|
@@ -23802,7 +23928,7 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
|
|
|
23802
23928
|
});
|
|
23803
23929
|
const parts = kernel.model.artifacts.get(artifactId)?.parts ?? [];
|
|
23804
23930
|
let landed = false;
|
|
23805
|
-
if (!holdForReview && !requiresHumanReview && qBefore === 0 && parts.length <= 1) {
|
|
23931
|
+
if (!holdForReview && !requiresHumanReview && qBefore === 0 && (revision.part === void 0 || parts.length <= 1)) {
|
|
23806
23932
|
try {
|
|
23807
23933
|
const r = await kernel.advanceQueue(artifactId, actor);
|
|
23808
23934
|
landed = r.landed.some((x2) => x2.id === revision.id);
|
|
@@ -23963,6 +24089,7 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
|
|
|
23963
24089
|
}
|
|
23964
24090
|
case "annotate": {
|
|
23965
24091
|
const raisedFrom = optStr(args, "raisedFrom");
|
|
24092
|
+
const attachments = attachmentArgs(args);
|
|
23966
24093
|
const mentions = Array.isArray(args["mentions"]) ? args["mentions"].filter((x2) => typeof x2 === "string" && x2.startsWith("actor:")) : void 0;
|
|
23967
24094
|
if (mentions !== void 0 && mentions.length > 0 && isAgent(actor)) {
|
|
23968
24095
|
throw new Error("@ \u6743\u53EA\u5C5E\u4E8E\u4EBA\uFF1Aagent \u627E agent \u7528\u56DE\u538B\uFF08annotate --from\uFF09\u6216\u5199\u4FE1\u5230\u5B83\u7684\u4EA7\u7269\uFF1B\u66FF\u4EBA @ \u9700\u5728 1:1 \u4F1A\u8BDD\u4E2D\uFF08\u7F72\u540D\u4EE3\u7F72\uFF09\u3002");
|
|
@@ -23974,6 +24101,7 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
|
|
|
23974
24101
|
...hand !== void 0 ? { hand } : {},
|
|
23975
24102
|
// 署名(§四):1:1 会话中作者=对话的人,agent 手留审计
|
|
23976
24103
|
...mentions !== void 0 && mentions.length > 0 ? { mentions } : {},
|
|
24104
|
+
...attachments !== void 0 && attachments.length > 0 ? { attachments } : {},
|
|
23977
24105
|
...optStr(args, "revisionId") !== void 0 ? { revisionId: optStr(args, "revisionId") } : {},
|
|
23978
24106
|
...raisedFrom !== void 0 ? { raisedFrom } : {},
|
|
23979
24107
|
...optStr(args, "raisedFromPart") !== void 0 ? { raisedFromPart: optStr(args, "raisedFromPart") } : {},
|
|
@@ -24311,6 +24439,7 @@ async function startOasisServer(opts) {
|
|
|
24311
24439
|
const resolveEngine = opts.resolveEngine ?? (async () => ({ kernel: opts.kernel, oplog: opts.oplog, blobs: opts.blobs, ...opts.registry ? { registry: opts.registry } : {} }));
|
|
24312
24440
|
const domainRouter = new Router();
|
|
24313
24441
|
for (const register of opts.domains ?? []) register(domainRouter);
|
|
24442
|
+
const liveChat = new LiveChatRegistry();
|
|
24314
24443
|
const httpServer = http.createServer((req, res) => {
|
|
24315
24444
|
void (async () => {
|
|
24316
24445
|
try {
|
|
@@ -24937,6 +25066,61 @@ async function startOasisServer(opts) {
|
|
|
24937
25066
|
}
|
|
24938
25067
|
return;
|
|
24939
25068
|
}
|
|
25069
|
+
if (req.method === "GET" && /^\/api\/chat-sessions\/[^/]+\/stream$/.test(url.pathname)) {
|
|
25070
|
+
const sid = decodeURIComponent(url.pathname.split("/")[3] ?? "");
|
|
25071
|
+
const cs = opts.chatSession ? await opts.chatSession.getSession(sid).catch(() => null) : null;
|
|
25072
|
+
if (!cs || cs.humanActorId !== actor) {
|
|
25073
|
+
res.writeHead(404, { "content-type": "application/json" }).end(JSON.stringify({ error: "CHAT_SESSION_NOT_FOUND" }));
|
|
25074
|
+
return;
|
|
25075
|
+
}
|
|
25076
|
+
const fromSeq = Math.max(0, Number(url.searchParams.get("fromSeq") ?? "0") || 0);
|
|
25077
|
+
const sub = (part) => {
|
|
25078
|
+
if (res.destroyed) return;
|
|
25079
|
+
res.write(`${JSON.stringify(part)}
|
|
25080
|
+
`);
|
|
25081
|
+
if (part.type === "done") res.end();
|
|
25082
|
+
};
|
|
25083
|
+
const att = liveChat.attach(sid, fromSeq, sub);
|
|
25084
|
+
if (!att) {
|
|
25085
|
+
res.writeHead(204).end();
|
|
25086
|
+
return;
|
|
25087
|
+
}
|
|
25088
|
+
res.writeHead(200, {
|
|
25089
|
+
"content-type": "application/x-ndjson; charset=utf-8",
|
|
25090
|
+
"transfer-encoding": "chunked",
|
|
25091
|
+
"access-control-expose-headers": "X-Session-Id, X-Replay-Gap",
|
|
25092
|
+
"x-session-id": cs.runtimeSessionId ?? "",
|
|
25093
|
+
...att.gap ? { "x-replay-gap": "1" } : {}
|
|
25094
|
+
});
|
|
25095
|
+
res.flushHeaders();
|
|
25096
|
+
const stopKa = startStreamKeepalive(res);
|
|
25097
|
+
for (const part of att.replay) {
|
|
25098
|
+
if (!res.destroyed) res.write(`${JSON.stringify(part)}
|
|
25099
|
+
`);
|
|
25100
|
+
}
|
|
25101
|
+
if (att.status !== "running") {
|
|
25102
|
+
att.detach();
|
|
25103
|
+
stopKa();
|
|
25104
|
+
res.end();
|
|
25105
|
+
return;
|
|
25106
|
+
}
|
|
25107
|
+
res.on("close", () => {
|
|
25108
|
+
stopKa();
|
|
25109
|
+
att.detach();
|
|
25110
|
+
});
|
|
25111
|
+
return;
|
|
25112
|
+
}
|
|
25113
|
+
if (req.method === "POST" && /^\/api\/chat-sessions\/[^/]+\/stop$/.test(url.pathname)) {
|
|
25114
|
+
const sid = decodeURIComponent(url.pathname.split("/")[3] ?? "");
|
|
25115
|
+
const cs = opts.chatSession ? await opts.chatSession.getSession(sid).catch(() => null) : null;
|
|
25116
|
+
if (!cs || cs.humanActorId !== actor) {
|
|
25117
|
+
res.writeHead(404, { "content-type": "application/json" }).end(JSON.stringify({ error: "CHAT_SESSION_NOT_FOUND" }));
|
|
25118
|
+
return;
|
|
25119
|
+
}
|
|
25120
|
+
const stopped = liveChat.stop(sid);
|
|
25121
|
+
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ stopped }));
|
|
25122
|
+
return;
|
|
25123
|
+
}
|
|
24940
25124
|
if (url.pathname === "/api/chat" && req.method === "POST") {
|
|
24941
25125
|
try {
|
|
24942
25126
|
const body = JSON.parse(rawBody);
|
|
@@ -24982,11 +25166,14 @@ async function startOasisServer(opts) {
|
|
|
24982
25166
|
}
|
|
24983
25167
|
let assistantText = "";
|
|
24984
25168
|
let persisted = false;
|
|
24985
|
-
|
|
25169
|
+
let live = null;
|
|
25170
|
+
const localParts = [];
|
|
25171
|
+
const collectedParts = () => (live ? live.bufferedParts() : localParts).filter((p2) => p2.type !== "done");
|
|
24986
25172
|
const finalizeAssistant = async () => {
|
|
24987
25173
|
if (persisted || !chatStore || !persistTarget) return;
|
|
24988
25174
|
persisted = true;
|
|
24989
|
-
|
|
25175
|
+
const parts = collectedParts();
|
|
25176
|
+
if (!assistantText && !session.runId && parts.length === 0) return;
|
|
24990
25177
|
const { randomUUID: randomUUID19 } = await import("node:crypto");
|
|
24991
25178
|
await chatStore.appendMessage({
|
|
24992
25179
|
id: randomUUID19(),
|
|
@@ -24995,7 +25182,7 @@ async function startOasisServer(opts) {
|
|
|
24995
25182
|
content: assistantText,
|
|
24996
25183
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24997
25184
|
...session.runId ? { runId: session.runId } : {},
|
|
24998
|
-
...
|
|
25185
|
+
...parts.length ? { parts } : {}
|
|
24999
25186
|
}).catch(() => void 0);
|
|
25000
25187
|
await chatStore.updateSession(persistTarget.id, {
|
|
25001
25188
|
// 优先落 runtime-native id(codex rollout / ACP session/new 返回),让下一轮 resume
|
|
@@ -25005,21 +25192,38 @@ async function startOasisServer(opts) {
|
|
|
25005
25192
|
}).catch(() => void 0);
|
|
25006
25193
|
};
|
|
25007
25194
|
let finished = false;
|
|
25195
|
+
let detachViewer;
|
|
25196
|
+
let stopKeepalive;
|
|
25008
25197
|
res.on("close", () => {
|
|
25009
|
-
|
|
25198
|
+
stopKeepalive?.();
|
|
25199
|
+
if (finished) return;
|
|
25200
|
+
if (live) {
|
|
25201
|
+
detachViewer?.();
|
|
25202
|
+
} else {
|
|
25010
25203
|
void session.kill?.();
|
|
25011
25204
|
void finalizeAssistant();
|
|
25012
25205
|
}
|
|
25013
25206
|
});
|
|
25014
25207
|
if (typedParts) {
|
|
25015
|
-
|
|
25016
|
-
|
|
25017
|
-
|
|
25018
|
-
|
|
25019
|
-
|
|
25020
|
-
|
|
25208
|
+
live = body.chatSessionId ? liveChat.start(body.chatSessionId, {
|
|
25209
|
+
runtimeSessionId: session.id,
|
|
25210
|
+
...session.runId ? { runId: session.runId } : {},
|
|
25211
|
+
kill: () => {
|
|
25212
|
+
void session.kill?.();
|
|
25213
|
+
}
|
|
25214
|
+
}) : null;
|
|
25215
|
+
let localSeq = 0;
|
|
25216
|
+
const writeToRes = (part) => {
|
|
25217
|
+
if (!res.destroyed) res.write(`${JSON.stringify(part)}
|
|
25021
25218
|
`);
|
|
25022
25219
|
};
|
|
25220
|
+
const emit = (part) => {
|
|
25221
|
+
if (live) return live.emit(part);
|
|
25222
|
+
const withSeq = { ...part, seq: ++localSeq };
|
|
25223
|
+
if (part.type !== "done") localParts.push(withSeq);
|
|
25224
|
+
writeToRes(withSeq);
|
|
25225
|
+
return withSeq;
|
|
25226
|
+
};
|
|
25023
25227
|
res.writeHead(200, {
|
|
25024
25228
|
"content-type": "application/x-ndjson; charset=utf-8",
|
|
25025
25229
|
"transfer-encoding": "chunked",
|
|
@@ -25027,25 +25231,32 @@ async function startOasisServer(opts) {
|
|
|
25027
25231
|
"x-session-id": session.id,
|
|
25028
25232
|
...session.runId ? { "x-run-id": session.runId } : {}
|
|
25029
25233
|
});
|
|
25234
|
+
res.flushHeaders();
|
|
25235
|
+
stopKeepalive = startStreamKeepalive(res);
|
|
25236
|
+
if (live) detachViewer = live.subscribe(writeToRes);
|
|
25030
25237
|
let sawTextOutput = false;
|
|
25031
25238
|
session.onOutput((chunk) => {
|
|
25032
25239
|
sawTextOutput = true;
|
|
25033
25240
|
assistantText += chunk;
|
|
25034
|
-
|
|
25241
|
+
emit({ type: "text", text: chunk });
|
|
25035
25242
|
});
|
|
25036
25243
|
session.onTelemetry?.((event) => {
|
|
25037
25244
|
if (event.kind === "message" && sawTextOutput) return;
|
|
25038
25245
|
const part = chatPartFromTrajectoryEvent(event);
|
|
25039
|
-
if (part)
|
|
25246
|
+
if (part) emit(part);
|
|
25040
25247
|
});
|
|
25248
|
+
let errored = false;
|
|
25041
25249
|
try {
|
|
25042
25250
|
await session.done;
|
|
25043
25251
|
} catch (err) {
|
|
25044
|
-
|
|
25252
|
+
errored = true;
|
|
25253
|
+
emit({ type: "error", text: err instanceof Error ? err.message : String(err) });
|
|
25045
25254
|
}
|
|
25046
25255
|
finished = true;
|
|
25047
25256
|
await finalizeAssistant();
|
|
25048
|
-
|
|
25257
|
+
emit({ type: "done", sessionId: session.id });
|
|
25258
|
+
live?.finish(errored ? "error" : "done");
|
|
25259
|
+
stopKeepalive?.();
|
|
25049
25260
|
res.end();
|
|
25050
25261
|
return;
|
|
25051
25262
|
}
|
|
@@ -25481,6 +25692,7 @@ var init_server3 = __esm({
|
|
|
25481
25692
|
init_src2();
|
|
25482
25693
|
init_mcp2();
|
|
25483
25694
|
init_router();
|
|
25695
|
+
init_live_chat();
|
|
25484
25696
|
init_workorder_drafts();
|
|
25485
25697
|
init_draft_edit();
|
|
25486
25698
|
init_workorder_detail();
|
|
@@ -37964,14 +38176,15 @@ var init_codex = __esm({
|
|
|
37964
38176
|
const task = job.bundle.files["TASK.md"];
|
|
37965
38177
|
if (!task) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
|
|
37966
38178
|
const prompt = job.systemPrompt ? ["# System Prompt", job.systemPrompt, "", "# Task", task].join("\n") : task;
|
|
37967
|
-
const
|
|
38179
|
+
const isResume = Boolean(job.resumeRuntimeSession && job.runtimeSessionId);
|
|
37968
38180
|
const args = [
|
|
37969
|
-
|
|
38181
|
+
"exec",
|
|
38182
|
+
"-C",
|
|
38183
|
+
dir,
|
|
38184
|
+
...isResume ? ["resume", job.runtimeSessionId] : [],
|
|
37970
38185
|
"--json",
|
|
37971
38186
|
"--dangerously-bypass-approvals-and-sandbox",
|
|
37972
38187
|
"--skip-git-repo-check",
|
|
37973
|
-
"-C",
|
|
37974
|
-
dir,
|
|
37975
38188
|
...job.model ?? this.opts.model ? ["--model", job.model ?? this.opts.model] : [],
|
|
37976
38189
|
// 打开 reasoning 摘要通道:默认 auto,让 codex 产出 reasoning item(→ thought → 前端思考块)。
|
|
37977
38190
|
...this.opts.reasoningSummary !== "none" ? ["-c", `model_reasoning_summary=${this.opts.reasoningSummary ?? "auto"}`] : [],
|
|
@@ -38577,6 +38790,9 @@ function parseArgsJSON(argsText) {
|
|
|
38577
38790
|
function extractSessionID(result) {
|
|
38578
38791
|
return result?.sessionId ?? "";
|
|
38579
38792
|
}
|
|
38793
|
+
function extractStopReason(result) {
|
|
38794
|
+
return result?.stopReason ?? "";
|
|
38795
|
+
}
|
|
38580
38796
|
async function runACPSession(job, cfg) {
|
|
38581
38797
|
const slug6 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
|
|
38582
38798
|
const binTag = path9.basename(cfg.bin).replace(/[^a-zA-Z0-9]/g, "").slice(0, 12) || "acp";
|
|
@@ -38611,8 +38827,11 @@ async function runACPSession(job, cfg) {
|
|
|
38611
38827
|
let killedByUs = false;
|
|
38612
38828
|
let actualModel;
|
|
38613
38829
|
let capturedSessionId;
|
|
38830
|
+
let usageClient;
|
|
38614
38831
|
const finish = (info) => {
|
|
38615
38832
|
if (exited) return;
|
|
38833
|
+
const usage = info.usage ?? (usageClient ? buildUsage(usageClient.usage, estimateCost(usageClient.usage, actualModel)) : void 0);
|
|
38834
|
+
if (usage && !info.usage) info = { ...info, usage };
|
|
38616
38835
|
if (actualModel && !info.model) info = { ...info, model: actualModel };
|
|
38617
38836
|
if (capturedSessionId && !info.runtimeSessionId) info = { ...info, runtimeSessionId: capturedSessionId };
|
|
38618
38837
|
if (cfg.cleanupWorkdir) {
|
|
@@ -38660,6 +38879,7 @@ async function runACPSession(job, cfg) {
|
|
|
38660
38879
|
},
|
|
38661
38880
|
() => streamingCurrentTurn
|
|
38662
38881
|
);
|
|
38882
|
+
usageClient = client;
|
|
38663
38883
|
const rl = readline3.createInterface({ input: child.stdout });
|
|
38664
38884
|
const readerDone = new Promise((resolve4) => {
|
|
38665
38885
|
rl.on("line", (line) => {
|
|
@@ -38677,7 +38897,9 @@ async function runACPSession(job, cfg) {
|
|
|
38677
38897
|
});
|
|
38678
38898
|
child.on("exit", (code) => {
|
|
38679
38899
|
clearTimeout(timer);
|
|
38680
|
-
|
|
38900
|
+
void readerDone.then(() => {
|
|
38901
|
+
finish({ code: killedByUs ? null : code, reason: killedByUs ? "timeout" : code === 0 ? "clean" : "error", transcriptRef });
|
|
38902
|
+
});
|
|
38681
38903
|
});
|
|
38682
38904
|
void (async () => {
|
|
38683
38905
|
try {
|
|
@@ -38688,6 +38910,7 @@ async function runACPSession(job, cfg) {
|
|
|
38688
38910
|
});
|
|
38689
38911
|
const resolvedModel = job.model ?? cfg.model;
|
|
38690
38912
|
let sessionId = "";
|
|
38913
|
+
let ambiguousResume = false;
|
|
38691
38914
|
if (job.resumeRuntimeSession && job.runtimeSessionId) {
|
|
38692
38915
|
const method = cfg.resumeMethod ?? "session/load";
|
|
38693
38916
|
try {
|
|
@@ -38697,7 +38920,11 @@ async function runACPSession(job, cfg) {
|
|
|
38697
38920
|
mcpServers: [],
|
|
38698
38921
|
...resolvedModel ? { model: resolvedModel } : {}
|
|
38699
38922
|
});
|
|
38700
|
-
sessionId = extractSessionID(resumeRes)
|
|
38923
|
+
sessionId = extractSessionID(resumeRes);
|
|
38924
|
+
if (!sessionId) {
|
|
38925
|
+
sessionId = job.runtimeSessionId;
|
|
38926
|
+
ambiguousResume = true;
|
|
38927
|
+
}
|
|
38701
38928
|
} catch {
|
|
38702
38929
|
sessionId = "";
|
|
38703
38930
|
}
|
|
@@ -38722,10 +38949,24 @@ async function runACPSession(job, cfg) {
|
|
|
38722
38949
|
|
|
38723
38950
|
${task}` : task;
|
|
38724
38951
|
streamingCurrentTurn = true;
|
|
38725
|
-
await client.request("session/prompt", {
|
|
38952
|
+
let promptResult = await client.request("session/prompt", {
|
|
38726
38953
|
sessionId,
|
|
38727
38954
|
prompt: [{ type: "text", text: userText }]
|
|
38728
38955
|
});
|
|
38956
|
+
if (ambiguousResume && extractStopReason(promptResult) === "refusal" && !hasUsage(client.usage)) {
|
|
38957
|
+
const sessionRes = await client.request("session/new", {
|
|
38958
|
+
cwd: dir,
|
|
38959
|
+
mcpServers: [],
|
|
38960
|
+
...resolvedModel ? { model: resolvedModel } : {}
|
|
38961
|
+
});
|
|
38962
|
+
sessionId = extractSessionID(sessionRes);
|
|
38963
|
+
if (!sessionId) throw new Error("session/new returned no sessionId");
|
|
38964
|
+
capturedSessionId = sessionId;
|
|
38965
|
+
promptResult = await client.request("session/prompt", {
|
|
38966
|
+
sessionId,
|
|
38967
|
+
prompt: [{ type: "text", text: userText }]
|
|
38968
|
+
});
|
|
38969
|
+
}
|
|
38729
38970
|
const usage = buildUsage(client.usage, estimateCost(client.usage, actualModel));
|
|
38730
38971
|
if (!exited) finish({ code: 0, reason: "clean", transcriptRef, usage, model: actualModel });
|
|
38731
38972
|
child.kill("SIGTERM");
|
|
@@ -40821,6 +41062,39 @@ function collabDomain(opts) {
|
|
|
40821
41062
|
if (draft) return { status: 200, body: buildWorkorderDraftDetail(draft, resolve4) };
|
|
40822
41063
|
return { status: 404, body: { error: { code: "not_found", message: `\u5DE5\u5355\u4E0D\u5B58\u5728: ${req.params.id}` } } };
|
|
40823
41064
|
});
|
|
41065
|
+
router.post("/api/workorders/:id/hold", async (req) => {
|
|
41066
|
+
const { kernel, artifacts } = await resolveCtx(req.auth.companyId);
|
|
41067
|
+
const ws = req.params.id;
|
|
41068
|
+
const arts = [...kernel.model.artifacts.values()].filter((a) => a.workspace === ws);
|
|
41069
|
+
if (arts.length === 0) {
|
|
41070
|
+
return { status: 404, body: { error: { code: "not_found", message: `\u5DE5\u5355\u4E0D\u5B58\u5728: ${ws}` } } };
|
|
41071
|
+
}
|
|
41072
|
+
if (!artifacts) {
|
|
41073
|
+
return { status: 503, body: { error: { code: "no_store", message: "\u63A7\u5236\u9762\u65C1\u5B58\u672A\u63A5\u5165\uFF0C\u65E0\u6CD5\u6682\u505C" } } };
|
|
41074
|
+
}
|
|
41075
|
+
const held = (await artifacts.listWorkspaceDispatchHeld()).includes(ws);
|
|
41076
|
+
if (held) return { status: 200, body: { held: false } };
|
|
41077
|
+
await artifacts.setWorkspaceDispatchHold(ws, true);
|
|
41078
|
+
return { status: 200, body: { held: true } };
|
|
41079
|
+
});
|
|
41080
|
+
router.post("/api/workorders/:id/cancel", async (req) => {
|
|
41081
|
+
if (!isHumanActor(req.auth.actor)) {
|
|
41082
|
+
return { status: 403, body: { error: { code: "forbidden", message: "\u53D6\u6D88\u5DE5\u5355\u662F\u9500\u6BC1\u6027\u6CBB\u7406\u64CD\u4F5C\uFF0C\u4EC5\u4EBA\u7C7B\u53EF\u6267\u884C" } } };
|
|
41083
|
+
}
|
|
41084
|
+
const { kernel } = await resolveCtx(req.auth.companyId);
|
|
41085
|
+
const ws = req.params.id;
|
|
41086
|
+
const active = [...kernel.model.artifacts.values()].filter((a) => a.workspace === ws && lifecycleOf(kernel.model, a.id) === "active");
|
|
41087
|
+
const all = [...kernel.model.artifacts.values()].filter((a) => a.workspace === ws);
|
|
41088
|
+
if (all.length === 0) {
|
|
41089
|
+
return { status: 404, body: { error: { code: "not_found", message: `\u5DE5\u5355\u4E0D\u5B58\u5728: ${ws}` } } };
|
|
41090
|
+
}
|
|
41091
|
+
let cancelled = 0;
|
|
41092
|
+
for (const a of active) {
|
|
41093
|
+
await kernel.seal({ artifactId: a.id, actor: req.auth.actor, reason: "cancelled", note: "\u5DE5\u5355\u53D6\u6D88\uFF08\u6279\u91CF\uFF09" });
|
|
41094
|
+
cancelled += 1;
|
|
41095
|
+
}
|
|
41096
|
+
return { status: 200, body: { cancelled } };
|
|
41097
|
+
});
|
|
40824
41098
|
router.post("/api/workorders/:id/dispatch", async (req) => {
|
|
40825
41099
|
const { kernel, artifacts } = await resolveCtx(req.auth.companyId);
|
|
40826
41100
|
const ws = req.params.id;
|
|
@@ -40891,6 +41165,7 @@ function collabDomain(opts) {
|
|
|
40891
41165
|
var init_collab = __esm({
|
|
40892
41166
|
"../server/src/domains/collab/index.ts"() {
|
|
40893
41167
|
"use strict";
|
|
41168
|
+
init_src2();
|
|
40894
41169
|
init_workorders();
|
|
40895
41170
|
init_workorder_detail();
|
|
40896
41171
|
init_inbox();
|
|
@@ -57730,7 +58005,7 @@ ${res.warning}`);
|
|
|
57730
58005
|
}
|
|
57731
58006
|
|
|
57732
58007
|
// src/index.ts
|
|
57733
|
-
var PKG_VERSION = true ? "0.1.
|
|
58008
|
+
var PKG_VERSION = true ? "0.1.37" : "dev";
|
|
57734
58009
|
var OASIS_DIR = path18.join(os10.homedir(), ".oasis");
|
|
57735
58010
|
var CONFIG_FILE = path18.join(OASIS_DIR, "node-config.json");
|
|
57736
58011
|
var PID_FILE = path18.join(OASIS_DIR, "node.pid");
|