oasis_test 0.1.12 → 0.1.14
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 +1868 -408
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -161,6 +161,7 @@ function fold(model, op) {
|
|
|
161
161
|
author: op.actor,
|
|
162
162
|
body: p2.body,
|
|
163
163
|
state: "open",
|
|
164
|
+
createdAt: op.timestamp,
|
|
164
165
|
thread: [],
|
|
165
166
|
raisedFrom: p2.raisedFrom ?? null,
|
|
166
167
|
raisedFromRev: p2.raisedFromRev ?? null,
|
|
@@ -618,6 +619,20 @@ function deriveArtifactId(type, workspace, title, usedIds) {
|
|
|
618
619
|
usedIds.add(id);
|
|
619
620
|
return id;
|
|
620
621
|
}
|
|
622
|
+
function resolveNodeRef(ref, candidates) {
|
|
623
|
+
const r = ref.trim();
|
|
624
|
+
if (r.startsWith("artifact:")) {
|
|
625
|
+
const hit = candidates.find((n) => n.id === r);
|
|
626
|
+
return hit ? { ok: true, id: hit.id } : { ok: false, reason: "not_found" };
|
|
627
|
+
}
|
|
628
|
+
const sep = r.indexOf(":");
|
|
629
|
+
const type = (sep >= 0 ? r.slice(0, sep) : r).trim().toLowerCase();
|
|
630
|
+
const title = sep >= 0 ? r.slice(sep + 1).trim().toLowerCase() : null;
|
|
631
|
+
const matches = candidates.filter((n) => n.type.toLowerCase() === type && (title === null || (n.title ?? "").toLowerCase() === title));
|
|
632
|
+
if (matches.length === 0) return { ok: false, reason: "not_found" };
|
|
633
|
+
if (matches.length === 1) return { ok: true, id: matches[0].id };
|
|
634
|
+
return { ok: false, reason: "ambiguous", candidates: matches };
|
|
635
|
+
}
|
|
621
636
|
var init_node_identity = __esm({
|
|
622
637
|
"../core/src/node-identity.ts"() {
|
|
623
638
|
"use strict";
|
|
@@ -1321,6 +1336,7 @@ var init_kernel = __esm({
|
|
|
1321
1336
|
init_src();
|
|
1322
1337
|
init_read_model();
|
|
1323
1338
|
init_content_handler();
|
|
1339
|
+
init_node_identity();
|
|
1324
1340
|
init_governance();
|
|
1325
1341
|
init_manifest_store();
|
|
1326
1342
|
init_grounding();
|
|
@@ -1729,14 +1745,18 @@ var init_kernel = __esm({
|
|
|
1729
1745
|
const override = await this.resolveReviewerOverride?.(artifact.owner, artifact);
|
|
1730
1746
|
const overrideApplied = !!override && override.length > 0;
|
|
1731
1747
|
const configured = overrideApplied ? override : gateSpec.reviewers.flatMap((role) => this.roleIndex.get(role) ?? []);
|
|
1732
|
-
const
|
|
1748
|
+
const configuredUnique = [...new Set(configured)];
|
|
1749
|
+
const eligible = configuredUnique.filter((a) => a !== head.author);
|
|
1733
1750
|
const quorum = overrideApplied ? Math.min(gateSpec.quorum, Math.max(1, eligible.length)) : gateSpec.quorum;
|
|
1734
1751
|
if (eligible.length < quorum) {
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1752
|
+
if (!(configuredUnique.includes(head.author) && configuredUnique.length >= quorum)) {
|
|
1753
|
+
throw new KernelError(
|
|
1754
|
+
`\u6536\u5C3E gate \u65E0\u6CD5\u6EE1\u8DB3\uFF1A\u5408\u683C reviewer ${eligible.length} \u4EBA < quorum ${quorum}\uFF08reviewers=${eligible.join(",") || "none"}\uFF1Broles=${gateSpec.reviewers.join(",")}\uFF0C\u5DF2\u5254\u9664 head \u4F5C\u8005 ${head.author}\uFF09\u2014\u2014\u914D\u7F6E\u95EE\u9898\u8981\u54CD\u4EAE\uFF0C\u4E0D\u9759\u9ED8\u653E\u884C`
|
|
1755
|
+
);
|
|
1756
|
+
}
|
|
1757
|
+
} else {
|
|
1758
|
+
gate = { quorum, eligible };
|
|
1738
1759
|
}
|
|
1739
|
-
gate = { quorum, eligible };
|
|
1740
1760
|
}
|
|
1741
1761
|
const payload = {
|
|
1742
1762
|
...args.note !== void 0 ? { note: args.note } : {},
|
|
@@ -2060,7 +2080,64 @@ var init_kernel = __esm({
|
|
|
2060
2080
|
* 预检(②终态校验 + ③advisory + ④爆炸半径)。preview 白送:
|
|
2061
2081
|
* structuredClone 读模型、灌假设 op、看终态——derived-not-stored 的红利,无须仿真引擎。
|
|
2062
2082
|
*/
|
|
2083
|
+
/**
|
|
2084
|
+
* 二期 title-identity:把 plan 里的【节点引用】(`type` | `type:title` | `id`)解析成最终 id。
|
|
2085
|
+
* - spawn 省略 id → 据 (type,title) 派生(usedIds 预种工单现有 id + 本批显式 id,不和存量撞)。
|
|
2086
|
+
* - 引用现有/本批节点 → 在 plan.workspace 范围内 {现有 ∪ 本批 spawn} 匹配;撞多个 throw 带候选、无则 throw。
|
|
2087
|
+
* - 批内**先派生所有 spawn 的 id,再解引用**(link 才引得到本批新建的)。
|
|
2088
|
+
* - workspace 缺省 = 只走 id(向后兼容 passthrough)。**幂等**:解析一份已解析的 plan = 原样。
|
|
2089
|
+
* 落 oplog 的是解析后的 id(重放确定)。
|
|
2090
|
+
*/
|
|
2091
|
+
resolveInterventionRefs(plan) {
|
|
2092
|
+
const ws = plan.workspace;
|
|
2093
|
+
const existing = ws ? [...this.model.artifacts.values()].filter((a) => a.workspace === ws).map((a) => ({ id: a.id, type: a.type, ...a.title ? { title: a.title } : {} })) : [];
|
|
2094
|
+
const usedIds = new Set(existing.map((n) => n.id));
|
|
2095
|
+
for (const op of plan.ops) if (op.action === "spawn" && op.id) usedIds.add(op.id);
|
|
2096
|
+
const batch = [];
|
|
2097
|
+
const spawnIds = plan.ops.map((op) => {
|
|
2098
|
+
if (op.action !== "spawn") return void 0;
|
|
2099
|
+
if (op.id) {
|
|
2100
|
+
batch.push({ id: op.id, type: op.type, ...op.title ? { title: op.title } : {} });
|
|
2101
|
+
return op.id;
|
|
2102
|
+
}
|
|
2103
|
+
if (!ws) throw new KernelError("spawn \u7701\u7565 id \u9700\u8981 InterventionPlan.workspace + title \u624D\u80FD\u6D3E\u751F");
|
|
2104
|
+
const title = op.title?.trim();
|
|
2105
|
+
if (!title) throw new KernelError("spawn \u7701\u7565 id \u65F6 title \u5FC5\u586B\uFF08\u636E (type,title) \u6D3E\u751F id\uFF09");
|
|
2106
|
+
if ([...existing, ...batch].some((n) => n.type === op.type && (n.title ?? "").toLowerCase() === title.toLowerCase()))
|
|
2107
|
+
throw new KernelError(`\u8282\u70B9 title \u649E\u540D\uFF1A${op.type}\u300C${title}\u300D\u5728\u672C\u5DE5\u5355\u5DF2\u5B58\u5728,\u8BF7\u6362\u4E2A\u533A\u5206\u7684\u540D\u5B57`);
|
|
2108
|
+
const id = deriveArtifactId(op.type, ws, title, usedIds);
|
|
2109
|
+
batch.push({ id, type: op.type, title });
|
|
2110
|
+
return id;
|
|
2111
|
+
});
|
|
2112
|
+
const candidates = [...existing, ...batch];
|
|
2113
|
+
const ref = (r, what) => {
|
|
2114
|
+
if (r === void 0) return void 0;
|
|
2115
|
+
if (!ws) return r;
|
|
2116
|
+
const res = resolveNodeRef(r, candidates);
|
|
2117
|
+
if (res.ok) return res.id;
|
|
2118
|
+
if (res.reason === "ambiguous")
|
|
2119
|
+
throw new KernelError(`${what}\u300C${r}\u300D\u5339\u914D\u5230\u591A\u4E2A\u8282\u70B9,\u8BF7\u6307\u660E\u54EA\u4E2A:
|
|
2120
|
+
${res.candidates.map((c) => ` - ${c.type}:${c.title ?? "(\u65E0title)"} \u2192 ${c.id}`).join("\n")}`);
|
|
2121
|
+
throw new KernelError(`${what}\u300C${r}\u300D\u627E\u4E0D\u5230\u5BF9\u5E94\u8282\u70B9\uFF08\u53EF\u7528 type / type:title / id\uFF09`);
|
|
2122
|
+
};
|
|
2123
|
+
const ops = plan.ops.map((op, i) => {
|
|
2124
|
+
switch (op.action) {
|
|
2125
|
+
case "spawn":
|
|
2126
|
+
return { ...op, id: spawnIds[i], ...op.workspace === void 0 && ws ? { workspace: ws } : {}, ...op.inputs ? { inputs: op.inputs.map((e) => ({ to: ref(e.to, "spawn \u4F9D\u8D56"), ...e.required !== void 0 ? { required: e.required } : {} })) } : {} };
|
|
2127
|
+
case "link":
|
|
2128
|
+
case "unlink":
|
|
2129
|
+
case "pin":
|
|
2130
|
+
return { ...op, artifactId: ref(op.artifactId, op.action), to: ref(op.to, `${op.action} \u4E0A\u6E38`) };
|
|
2131
|
+
case "annotate":
|
|
2132
|
+
return { ...op, artifactId: ref(op.artifactId, "annotate"), ...op.raisedFrom ? { raisedFrom: ref(op.raisedFrom, "annotate raisedFrom") } : {} };
|
|
2133
|
+
default:
|
|
2134
|
+
return { ...op, artifactId: ref(op.artifactId, op.action) };
|
|
2135
|
+
}
|
|
2136
|
+
});
|
|
2137
|
+
return { ...plan, ops };
|
|
2138
|
+
}
|
|
2063
2139
|
async previewIntervention(plan) {
|
|
2140
|
+
plan = this.resolveInterventionRefs(plan);
|
|
2064
2141
|
const constituents = this.buildConstituents(plan);
|
|
2065
2142
|
const touched = [...new Set(constituents.map((c) => c.artifactId))];
|
|
2066
2143
|
const baseSeqs = Object.fromEntries(touched.map((id) => [id, this.model.lastSeq.get(id) ?? 0]));
|
|
@@ -2189,6 +2266,7 @@ var init_kernel = __esm({
|
|
|
2189
2266
|
* v1 注:inverse 批次未物化(oplog 信息足以推导),可逆性=再发一个 intervention。
|
|
2190
2267
|
*/
|
|
2191
2268
|
async applyIntervention(plan, baseSeqs, actor) {
|
|
2269
|
+
plan = this.resolveInterventionRefs(plan);
|
|
2192
2270
|
const { changeClass, nonMonotonicOps } = classifyIntervention(plan);
|
|
2193
2271
|
if (changeClass === "C" && !isHumanActor(actor)) {
|
|
2194
2272
|
throw new KernelError(
|
|
@@ -2266,25 +2344,27 @@ var init_kernel = __esm({
|
|
|
2266
2344
|
buildConstituentsRaw(plan) {
|
|
2267
2345
|
const order = { spawn: 0, edit: 1, reopen: 2, assign: 3, link: 4, pin: 5, unlink: 6, cancelPart: 7, seal: 8, annotate: 9 };
|
|
2268
2346
|
const sorted = [...plan.ops].sort((a, b2) => order[a.action] - order[b2.action]);
|
|
2269
|
-
const spawnedHere = new Set(plan.ops.flatMap((op) => op.action === "spawn" ? [op.id] : []));
|
|
2347
|
+
const spawnedHere = new Set(plan.ops.flatMap((op) => op.action === "spawn" && op.id ? [op.id] : []));
|
|
2270
2348
|
const isPrereqFill = (id) => plan.ops.some((op) => op.action === "link" && op.to === id && !spawnedHere.has(op.artifactId));
|
|
2271
2349
|
return sorted.map((op) => {
|
|
2272
2350
|
switch (op.action) {
|
|
2273
2351
|
case "spawn": {
|
|
2352
|
+
const spawnId = op.id;
|
|
2353
|
+
if (!spawnId) throw new KernelError("internal: spawn id \u672A\u89E3\u6790\uFF08\u5E94\u5148\u8FC7 resolveInterventionRefs\uFF09");
|
|
2274
2354
|
const typeDef = this.schema.get(op.type);
|
|
2275
2355
|
if (this.schema.size > 0 && !typeDef) throw new KernelError(`unknown ArtifactType: ${op.type}`);
|
|
2276
2356
|
const spawnFields = this.schema.size > 0 ? validateArtifactFields(op.fields, typeDef?.fieldDefs, "spawn") : op.fields;
|
|
2277
2357
|
const owner = op.owner ?? this.resolveOwner(op.type, typeDef);
|
|
2278
2358
|
return {
|
|
2279
|
-
artifactId:
|
|
2359
|
+
artifactId: spawnId,
|
|
2280
2360
|
kind: "spawn_artifact",
|
|
2281
|
-
target:
|
|
2361
|
+
target: spawnId,
|
|
2282
2362
|
payload: {
|
|
2283
2363
|
type: op.type,
|
|
2284
2364
|
owner,
|
|
2285
2365
|
workspace: op.workspace ?? "ws:default",
|
|
2286
2366
|
persistence: "persistent",
|
|
2287
|
-
createdVia: isPrereqFill(
|
|
2367
|
+
createdVia: isPrereqFill(spawnId) ? "prereq" : "manual-spawn",
|
|
2288
2368
|
...op.title !== void 0 ? { title: op.title } : {},
|
|
2289
2369
|
...op.description !== void 0 ? { description: op.description } : {},
|
|
2290
2370
|
...op.docType !== void 0 ? { docType: op.docType } : {},
|
|
@@ -3228,14 +3308,26 @@ var init_dispatcher = __esm({
|
|
|
3228
3308
|
...this.opts.resolveActorContext !== void 0 ? { resolveActorContext: this.opts.resolveActorContext } : {},
|
|
3229
3309
|
...materializeSkills ? { materializeSkills } : {}
|
|
3230
3310
|
});
|
|
3311
|
+
const limits = { wallClockMs: this.wallClockFor(spec.artifactId) };
|
|
3312
|
+
const credentialCtx = {
|
|
3313
|
+
actor: spec.actor,
|
|
3314
|
+
artifactId: spec.artifactId,
|
|
3315
|
+
action: spec.action,
|
|
3316
|
+
...spec.part !== void 0 ? { part: spec.part } : {},
|
|
3317
|
+
...binding !== void 0 ? { binding } : {},
|
|
3318
|
+
limits
|
|
3319
|
+
};
|
|
3320
|
+
const serverUrl = this.opts.serverUrlFor ? await this.opts.serverUrlFor(credentialCtx) : this.opts.serverUrl;
|
|
3321
|
+
const tokenFor = this.opts.tokenFor ?? ((a, _ctx) => `token:${a}`);
|
|
3322
|
+
const actorToken = tokenFor(spec.actor, credentialCtx);
|
|
3231
3323
|
const provisioned = this.opts.provision ? await this.opts.provision(spec.actor) : void 0;
|
|
3232
3324
|
const job = {
|
|
3233
3325
|
actor: spec.actor,
|
|
3234
|
-
actorToken
|
|
3326
|
+
actorToken,
|
|
3235
3327
|
artifactId: spec.artifactId,
|
|
3236
3328
|
bundle: { files: bundle.files },
|
|
3237
|
-
server: { url:
|
|
3238
|
-
limits
|
|
3329
|
+
server: { url: serverUrl },
|
|
3330
|
+
limits,
|
|
3239
3331
|
...binding !== void 0 ? { binding } : {},
|
|
3240
3332
|
...provisioned?.env && Object.keys(provisioned.env).length > 0 ? { env: provisioned.env } : {},
|
|
3241
3333
|
...provisioned?.wrapperPaths && provisioned.wrapperPaths.length > 0 ? { wrapperPaths: provisioned.wrapperPaths } : {}
|
|
@@ -3258,6 +3350,7 @@ var init_dispatcher = __esm({
|
|
|
3258
3350
|
artifactId: spec.artifactId,
|
|
3259
3351
|
actor: spec.actor,
|
|
3260
3352
|
action: spec.action,
|
|
3353
|
+
...binding?.runtimeKind ? { runtimeKind: binding.runtimeKind } : {},
|
|
3261
3354
|
bundleManifest: manifest,
|
|
3262
3355
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3263
3356
|
};
|
|
@@ -3406,7 +3499,7 @@ var init_dispatcher = __esm({
|
|
|
3406
3499
|
if (!artifact) return false;
|
|
3407
3500
|
if (!artifact.owner.startsWith("actor:agent:")) return false;
|
|
3408
3501
|
if (lifecycleOf(model, spec.artifactId) !== "active") return false;
|
|
3409
|
-
if (isConcluded(model, spec.artifactId)) return false;
|
|
3502
|
+
if (isConcluded(model, spec.artifactId) && concludedHead(model, spec.artifactId) === artifact.currentRev) return false;
|
|
3410
3503
|
if (pendingConcludeAttempt(model, spec.artifactId)) return false;
|
|
3411
3504
|
if (!artifact.currentRev) return false;
|
|
3412
3505
|
if (queueOf(model, spec.artifactId).length > 0) return false;
|
|
@@ -16361,7 +16454,7 @@ var require_compile = __commonJS({
|
|
|
16361
16454
|
}
|
|
16362
16455
|
}
|
|
16363
16456
|
exports2.compileSchema = compileSchema;
|
|
16364
|
-
function
|
|
16457
|
+
function resolveRef2(root, baseId, ref) {
|
|
16365
16458
|
var _a;
|
|
16366
16459
|
ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
|
|
16367
16460
|
const schOrFunc = root.refs[ref];
|
|
@@ -16378,7 +16471,7 @@ var require_compile = __commonJS({
|
|
|
16378
16471
|
return;
|
|
16379
16472
|
return root.refs[ref] = inlineOrCompile.call(this, _sch);
|
|
16380
16473
|
}
|
|
16381
|
-
exports2.resolveRef =
|
|
16474
|
+
exports2.resolveRef = resolveRef2;
|
|
16382
16475
|
function inlineOrCompile(sch) {
|
|
16383
16476
|
if ((0, resolve_1.inlineRef)(sch.schema, this.opts.inlineRefs))
|
|
16384
16477
|
return sch.schema;
|
|
@@ -20259,12 +20352,12 @@ var require_dist = __commonJS({
|
|
|
20259
20352
|
throw new Error(`Unknown format "${name}"`);
|
|
20260
20353
|
return f2;
|
|
20261
20354
|
};
|
|
20262
|
-
function addFormats(ajv, list,
|
|
20355
|
+
function addFormats(ajv, list, fs17, exportName) {
|
|
20263
20356
|
var _a;
|
|
20264
20357
|
var _b;
|
|
20265
20358
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
20266
20359
|
for (const f2 of list)
|
|
20267
|
-
ajv.addFormat(f2,
|
|
20360
|
+
ajv.addFormat(f2, fs17[f2]);
|
|
20268
20361
|
}
|
|
20269
20362
|
module2.exports = exports2 = formatsPlugin;
|
|
20270
20363
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
@@ -20452,16 +20545,35 @@ var init_drafts = __esm({
|
|
|
20452
20545
|
});
|
|
20453
20546
|
|
|
20454
20547
|
// ../server/src/governance/workorder-drafts.ts
|
|
20455
|
-
var WorkorderDraftStore;
|
|
20548
|
+
var import_node_fs, WorkorderDraftStore;
|
|
20456
20549
|
var init_workorder_drafts = __esm({
|
|
20457
20550
|
"../server/src/governance/workorder-drafts.ts"() {
|
|
20458
20551
|
"use strict";
|
|
20552
|
+
import_node_fs = __toESM(require("node:fs"), 1);
|
|
20459
20553
|
WorkorderDraftStore = class {
|
|
20554
|
+
constructor(file) {
|
|
20555
|
+
this.file = file;
|
|
20556
|
+
if (file && import_node_fs.default.existsSync(file)) {
|
|
20557
|
+
try {
|
|
20558
|
+
const data = JSON.parse(import_node_fs.default.readFileSync(file, "utf8"));
|
|
20559
|
+
this.seq = data.seq ?? 0;
|
|
20560
|
+
for (const d of data.drafts ?? []) this.drafts.set(d.id, d);
|
|
20561
|
+
} catch {
|
|
20562
|
+
}
|
|
20563
|
+
}
|
|
20564
|
+
}
|
|
20460
20565
|
drafts = /* @__PURE__ */ new Map();
|
|
20461
20566
|
seq = 0;
|
|
20567
|
+
persist() {
|
|
20568
|
+
if (!this.file) return;
|
|
20569
|
+
const tmp = `${this.file}.tmp`;
|
|
20570
|
+
import_node_fs.default.writeFileSync(tmp, JSON.stringify({ seq: this.seq, drafts: [...this.drafts.values()] }));
|
|
20571
|
+
import_node_fs.default.renameSync(tmp, this.file);
|
|
20572
|
+
}
|
|
20462
20573
|
submit(d) {
|
|
20463
20574
|
const draft = { ...d, id: `wodraft:${++this.seq}`, at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
20464
20575
|
this.drafts.set(draft.id, draft);
|
|
20576
|
+
this.persist();
|
|
20465
20577
|
return draft;
|
|
20466
20578
|
}
|
|
20467
20579
|
list() {
|
|
@@ -20471,16 +20583,159 @@ var init_workorder_drafts = __esm({
|
|
|
20471
20583
|
forWorkspace(workspace) {
|
|
20472
20584
|
return [...this.drafts.values()].reverse().find((d) => d.workspace === workspace);
|
|
20473
20585
|
}
|
|
20586
|
+
/** 按 chat 会话取最新一份:agent 不自带 ws(后端生成)时,friday 据本会话 id 取回它刚建的草案。 */
|
|
20587
|
+
forSession(chatSessionId) {
|
|
20588
|
+
return [...this.drafts.values()].reverse().find((d) => d.chatSessionId === chatSessionId);
|
|
20589
|
+
}
|
|
20590
|
+
/** 给草案打上 chat 会话标(建单经 chat 路时,/api/cmd 拿到 X-Chat-Session-Id 后调)。 */
|
|
20591
|
+
tagSession(draftId, chatSessionId) {
|
|
20592
|
+
const d = this.drafts.get(draftId);
|
|
20593
|
+
if (d) {
|
|
20594
|
+
d.chatSessionId = chatSessionId;
|
|
20595
|
+
this.persist();
|
|
20596
|
+
}
|
|
20597
|
+
}
|
|
20474
20598
|
get(id) {
|
|
20475
20599
|
return this.drafts.get(id);
|
|
20476
20600
|
}
|
|
20477
20601
|
remove(id) {
|
|
20478
|
-
|
|
20602
|
+
const ok = this.drafts.delete(id);
|
|
20603
|
+
if (ok) this.persist();
|
|
20604
|
+
return ok;
|
|
20479
20605
|
}
|
|
20480
20606
|
};
|
|
20481
20607
|
}
|
|
20482
20608
|
});
|
|
20483
20609
|
|
|
20610
|
+
// ../server/src/governance/draft-edit.ts
|
|
20611
|
+
function resolveRef(ref, draft) {
|
|
20612
|
+
const res = resolveNodeRef(ref, candidatesOf(draft));
|
|
20613
|
+
if (res.ok) return res.id;
|
|
20614
|
+
if (res.reason === "ambiguous")
|
|
20615
|
+
throw new Error(`\u300C${ref}\u300D\u5728\u672C\u8349\u6848\u5339\u914D\u5230\u591A\u4E2A\u8282\u70B9\uFF0C\u8BF7\u66F4\u5177\u4F53\uFF08type:title \u6216 id\uFF09\uFF1A${res.candidates.map((c) => `${c.type}:${c.title ?? "?"}`).join("\u3001")}`);
|
|
20616
|
+
throw new Error(`\u300C${ref}\u300D\u5728\u672C\u8349\u6848\u91CC\u627E\u4E0D\u5230\u5BF9\u5E94\u8282\u70B9\uFF08\u53EF\u7528 type / type:title / id\uFF09`);
|
|
20617
|
+
}
|
|
20618
|
+
function editDraftPlan(draft, op) {
|
|
20619
|
+
switch (op.action) {
|
|
20620
|
+
case "spawn": {
|
|
20621
|
+
const title = op.title?.trim();
|
|
20622
|
+
if (!title) throw new Error("\u7ED9\u8349\u6848\u52A0\u8282\u70B9\u8981 --title\uFF08\u636E (type,title) \u6D3E\u751F id\uFF09");
|
|
20623
|
+
if (candidatesOf(draft).some((c) => c.type === op.type && (c.title ?? "").toLowerCase() === title.toLowerCase()))
|
|
20624
|
+
throw new Error(`\u8282\u70B9 title \u649E\u540D\uFF1A${op.type}\u300C${title}\u300D\u8349\u6848\u91CC\u5DF2\u6709\uFF0C\u6362\u4E2A\u533A\u5206\u7684\u540D`);
|
|
20625
|
+
const used = new Set(spawnOps(draft).map((o) => o.id));
|
|
20626
|
+
const id = deriveArtifactId(op.type, draft.workspace, title, used);
|
|
20627
|
+
const inputs = op.inputs?.map((e) => ({ to: resolveRef(e.to, draft), ...e.required !== void 0 ? { required: e.required } : {} }));
|
|
20628
|
+
draft.plan.ops.push({
|
|
20629
|
+
action: "spawn",
|
|
20630
|
+
id,
|
|
20631
|
+
type: op.type,
|
|
20632
|
+
workspace: draft.workspace,
|
|
20633
|
+
title,
|
|
20634
|
+
...op.description !== void 0 ? { description: op.description } : {},
|
|
20635
|
+
...op.fields ? { fields: op.fields } : {},
|
|
20636
|
+
...inputs && inputs.length > 0 ? { inputs } : {}
|
|
20637
|
+
});
|
|
20638
|
+
if (op.description) draft.plan.ops.push({ action: "annotate", artifactId: id, body: op.description, blocks: false });
|
|
20639
|
+
break;
|
|
20640
|
+
}
|
|
20641
|
+
case "edit": {
|
|
20642
|
+
const id = resolveRef(op.artifactId, draft);
|
|
20643
|
+
const target = spawnOps(draft).find((o) => o.id === id);
|
|
20644
|
+
if (!target) throw new Error(`\u8349\u6848\u91CC\u6CA1\u6709\u8282\u70B9 ${id} \u7684 spawn`);
|
|
20645
|
+
if (op.title !== void 0) target.title = op.title;
|
|
20646
|
+
if (op.fields) target.fields = { ...target.fields ?? {}, ...op.fields };
|
|
20647
|
+
if (op.description !== void 0) {
|
|
20648
|
+
target.description = op.description;
|
|
20649
|
+
const ann = draft.plan.ops.find((o) => o.action === "annotate" && o.artifactId === id);
|
|
20650
|
+
if (ann && ann.action === "annotate") ann.body = op.description;
|
|
20651
|
+
else draft.plan.ops.push({ action: "annotate", artifactId: id, body: op.description, blocks: false });
|
|
20652
|
+
}
|
|
20653
|
+
break;
|
|
20654
|
+
}
|
|
20655
|
+
case "link": {
|
|
20656
|
+
const consumer = resolveRef(op.artifactId, draft);
|
|
20657
|
+
const upstream = resolveRef(op.to, draft);
|
|
20658
|
+
draft.plan.ops.push({ action: "link", artifactId: consumer, to: upstream, ...op.required !== void 0 ? { required: op.required } : {} });
|
|
20659
|
+
break;
|
|
20660
|
+
}
|
|
20661
|
+
case "unlink": {
|
|
20662
|
+
const consumer = resolveRef(op.artifactId, draft);
|
|
20663
|
+
const upstream = resolveRef(op.to, draft);
|
|
20664
|
+
draft.plan.ops = draft.plan.ops.filter((o) => !(o.action === "link" && o.artifactId === consumer && o.to === upstream));
|
|
20665
|
+
for (const o of draft.plan.ops) if (o.action === "spawn" && o.id === consumer && o.inputs) o.inputs = o.inputs.filter((e) => e.to !== upstream);
|
|
20666
|
+
break;
|
|
20667
|
+
}
|
|
20668
|
+
case "seal": {
|
|
20669
|
+
if (op.reason !== "cancelled") throw new Error(`\u8349\u6848\u8282\u70B9\u53EA\u652F\u6301 cancel\uFF08\u5220\u9664\uFF09\uFF0C\u4E0D\u652F\u6301 ${op.reason}\u2014\u2014\u8282\u70B9\u8FD8\u6CA1\u843D\u5E93`);
|
|
20670
|
+
const id = resolveRef(op.artifactId, draft);
|
|
20671
|
+
draft.plan.ops = draft.plan.ops.filter((o) => {
|
|
20672
|
+
if (o.action === "spawn" && o.id === id) return false;
|
|
20673
|
+
if (o.action === "annotate" && o.artifactId === id) return false;
|
|
20674
|
+
if (o.action === "link" && (o.artifactId === id || o.to === id)) return false;
|
|
20675
|
+
return true;
|
|
20676
|
+
});
|
|
20677
|
+
for (const o of draft.plan.ops) if (o.action === "spawn" && o.inputs) o.inputs = o.inputs.filter((e) => e.to !== id);
|
|
20678
|
+
break;
|
|
20679
|
+
}
|
|
20680
|
+
default:
|
|
20681
|
+
throw new Error(`\u8349\u6848\u7F16\u8F91\u6682\u4E0D\u652F\u6301 ${op.action}\uFF08\u8349\u6848\u9636\u6BB5\u53EA\u652F\u6301 spawn / edit / link / unlink / cancel\uFF09`);
|
|
20682
|
+
}
|
|
20683
|
+
}
|
|
20684
|
+
var spawnOps, candidatesOf;
|
|
20685
|
+
var init_draft_edit = __esm({
|
|
20686
|
+
"../server/src/governance/draft-edit.ts"() {
|
|
20687
|
+
"use strict";
|
|
20688
|
+
init_src2();
|
|
20689
|
+
spawnOps = (draft) => draft.plan.ops.filter((o) => o.action === "spawn" && !!o.id);
|
|
20690
|
+
candidatesOf = (draft) => spawnOps(draft).map((o) => ({ id: o.id, type: o.type, ...o.title ? { title: o.title } : {} }));
|
|
20691
|
+
}
|
|
20692
|
+
});
|
|
20693
|
+
|
|
20694
|
+
// ../server/src/coordinator/identity.ts
|
|
20695
|
+
function isCoordinatorAgentName(name) {
|
|
20696
|
+
return COORDINATOR_NAME_RE.test(name);
|
|
20697
|
+
}
|
|
20698
|
+
function isActiveCoordinatorAgent(actor) {
|
|
20699
|
+
return actor.kind === "agent" && actor.status === "active" && isCoordinatorAgentName(actor.name);
|
|
20700
|
+
}
|
|
20701
|
+
async function listActiveCoordinatorAgents(registry2) {
|
|
20702
|
+
if (!registry2) return [];
|
|
20703
|
+
return (await registry2.listActors({ kind: "agent", status: "active" })).filter(isActiveCoordinatorAgent);
|
|
20704
|
+
}
|
|
20705
|
+
async function resolveCoordinatorRequirement(registry2) {
|
|
20706
|
+
const candidates = await listActiveCoordinatorAgents(registry2);
|
|
20707
|
+
const actor = candidates[0];
|
|
20708
|
+
if (!actor) {
|
|
20709
|
+
return {
|
|
20710
|
+
ok: false,
|
|
20711
|
+
code: "coordinator_missing",
|
|
20712
|
+
message: "\u7CFB\u7EDF\u7F3A\u5C11\u534F\u8C03\u8005\u667A\u80FD\u4F53\u3002\u8BF7\u5148\u521B\u5EFA\u4E00\u4E2A\u540D\u79F0\u5305\u542B\u201C\u534F\u8C03\u8005\u201D\u6216\u201Ccoordinator\u201D\u7684\u6D3B\u8DC3\u667A\u80FD\u4F53\uFF0C\u5E76\u7ED1\u5B9A\u53EF\u7528\u8FD0\u884C\u65F6\u3002"
|
|
20713
|
+
};
|
|
20714
|
+
}
|
|
20715
|
+
const binding = await registry2.getBinding(actor.id);
|
|
20716
|
+
if (!binding || binding.status !== "active") {
|
|
20717
|
+
return {
|
|
20718
|
+
ok: false,
|
|
20719
|
+
actor,
|
|
20720
|
+
code: "coordinator_binding_missing",
|
|
20721
|
+
message: `${actor.name}\uFF08${actor.id}\uFF09\u7F3A\u5C11 active runtime \u7ED1\u5B9A\u3002\u534F\u8C03\u8005\u65E0\u6CD5\u5904\u7406\u540E\u7EED gap / \u6B7B\u9501 / \u6539\u56FE\u6D41\u7A0B\u3002`
|
|
20722
|
+
};
|
|
20723
|
+
}
|
|
20724
|
+
return { ok: true, actor, binding };
|
|
20725
|
+
}
|
|
20726
|
+
async function resolveCoordinator(registry2) {
|
|
20727
|
+
const resolved = await resolveCoordinatorRequirement(registry2);
|
|
20728
|
+
return resolved.ok && resolved.actor && resolved.binding ? { actor: resolved.actor, binding: resolved.binding } : null;
|
|
20729
|
+
}
|
|
20730
|
+
var LEGACY_COORDINATOR_ACTOR, COORDINATOR_NAME_RE;
|
|
20731
|
+
var init_identity = __esm({
|
|
20732
|
+
"../server/src/coordinator/identity.ts"() {
|
|
20733
|
+
"use strict";
|
|
20734
|
+
LEGACY_COORDINATOR_ACTOR = "actor:agent:coordinator";
|
|
20735
|
+
COORDINATOR_NAME_RE = /协调者|coordinator/i;
|
|
20736
|
+
}
|
|
20737
|
+
});
|
|
20738
|
+
|
|
20484
20739
|
// ../server/src/domains/collab/planner.ts
|
|
20485
20740
|
function ensureDefaultWorkorderTypes(schema) {
|
|
20486
20741
|
const out = [...schema];
|
|
@@ -20501,6 +20756,46 @@ function upgradeDefaultFieldDefs(schema) {
|
|
|
20501
20756
|
}
|
|
20502
20757
|
return changed;
|
|
20503
20758
|
}
|
|
20759
|
+
function isPlannerCoordinator(actor) {
|
|
20760
|
+
return actor.kind === "agent" && (actor.roles.includes("coordinator") || isCoordinatorAgentName(actor.name));
|
|
20761
|
+
}
|
|
20762
|
+
async function plannerAvailableActors(registry2) {
|
|
20763
|
+
if (!registry2) return [];
|
|
20764
|
+
const active = await registry2.listActors({ status: "active" });
|
|
20765
|
+
const out = [];
|
|
20766
|
+
for (const actor of active) {
|
|
20767
|
+
if (isPlannerCoordinator(actor)) continue;
|
|
20768
|
+
const roles = actor.roles.filter((role) => role !== "coordinator");
|
|
20769
|
+
if (roles.length === 0) continue;
|
|
20770
|
+
if (actor.kind === "agent") {
|
|
20771
|
+
const binding = await registry2.getBinding(actor.id);
|
|
20772
|
+
if (!binding || binding.status !== "active") continue;
|
|
20773
|
+
}
|
|
20774
|
+
out.push({
|
|
20775
|
+
id: actor.id,
|
|
20776
|
+
name: actor.name,
|
|
20777
|
+
roles,
|
|
20778
|
+
kind: actor.kind
|
|
20779
|
+
});
|
|
20780
|
+
}
|
|
20781
|
+
return out;
|
|
20782
|
+
}
|
|
20783
|
+
async function plannerArtifactTypes(schema, registry2) {
|
|
20784
|
+
if (!registry2) {
|
|
20785
|
+
return schema.filter((def) => def.name !== "brief").map((def) => ({
|
|
20786
|
+
name: def.name,
|
|
20787
|
+
ownerRole: def.ownerRole,
|
|
20788
|
+
contentType: def.contentType
|
|
20789
|
+
}));
|
|
20790
|
+
}
|
|
20791
|
+
const actors = await plannerAvailableActors(registry2);
|
|
20792
|
+
const staffableRoles = new Set(actors.flatMap((actor) => actor.roles));
|
|
20793
|
+
return schema.filter((def) => def.name !== "brief" && staffableRoles.has(def.ownerRole)).map((def) => ({
|
|
20794
|
+
name: def.name,
|
|
20795
|
+
ownerRole: def.ownerRole,
|
|
20796
|
+
contentType: def.contentType
|
|
20797
|
+
}));
|
|
20798
|
+
}
|
|
20504
20799
|
function parseWorkorderSpec(body) {
|
|
20505
20800
|
const first = body.split("\n", 1)[0] ?? "";
|
|
20506
20801
|
if (!first.startsWith(WORKORDER_SPEC_PREFIX)) return null;
|
|
@@ -20527,6 +20822,8 @@ async function buildDynamicWorkorderPlan(input) {
|
|
|
20527
20822
|
const primaryType = schemaByName.has(input.requestedType) && input.requestedType !== "brief" ? input.requestedType : "prd";
|
|
20528
20823
|
const primaryId = input.requestedArtifactId && input.requestedType === primaryType ? input.requestedArtifactId : `artifact:${primaryType}:${slug4}`;
|
|
20529
20824
|
const issues = [...input.agentIssues ?? []];
|
|
20825
|
+
const enforceStaffing = Boolean(input.registry);
|
|
20826
|
+
const staffableTypes = new Set((await plannerArtifactTypes(schema, input.registry)).map((def) => def.name));
|
|
20530
20827
|
const roleCache = /* @__PURE__ */ new Map();
|
|
20531
20828
|
const resolveRole = async (role, artifactType) => {
|
|
20532
20829
|
const hit = roleCache.get(role);
|
|
@@ -20578,6 +20875,7 @@ async function buildDynamicWorkorderPlan(input) {
|
|
|
20578
20875
|
continue;
|
|
20579
20876
|
}
|
|
20580
20877
|
const owner = await resolveRole(def.ownerRole, node.type);
|
|
20878
|
+
if (enforceStaffing && (!owner.actor || !staffableTypes.has(node.type))) continue;
|
|
20581
20879
|
const ownerActor = owner.actor ?? `pending:role:${def.ownerRole}`;
|
|
20582
20880
|
availableNodes.push({
|
|
20583
20881
|
type: node.type,
|
|
@@ -20599,6 +20897,7 @@ async function buildDynamicWorkorderPlan(input) {
|
|
|
20599
20897
|
}
|
|
20600
20898
|
const owner = await resolveRole(def.ownerRole, type);
|
|
20601
20899
|
const ownerActor = owner.actor ?? (type === primaryType ? input.actor : `pending:role:${def.ownerRole}`);
|
|
20900
|
+
if (enforceStaffing && !owner.actor && type !== primaryType) continue;
|
|
20602
20901
|
const id = type === primaryType ? primaryId : `artifact:${type}:${slug4}`;
|
|
20603
20902
|
availableNodes.push({ type, id, role: def.ownerRole, owner: ownerActor });
|
|
20604
20903
|
}
|
|
@@ -20905,6 +21204,7 @@ var init_planner = __esm({
|
|
|
20905
21204
|
"../server/src/domains/collab/planner.ts"() {
|
|
20906
21205
|
"use strict";
|
|
20907
21206
|
init_src2();
|
|
21207
|
+
init_identity();
|
|
20908
21208
|
DEFAULT_TYPES = [
|
|
20909
21209
|
// brief 的 goal/acceptanceCriteria 现在是【自定义字段】(Phase 3,替代旧 OASIS_WORKORDER_SPEC 批注,铲 #1 死锁根)
|
|
20910
21210
|
{ name: "brief", contentType: "text", ownerRole: "pm", fieldDefs: [
|
|
@@ -21190,8 +21490,19 @@ async function runCommand(kernel, blobs, oplog, actor, command, args, ctx = {})
|
|
|
21190
21490
|
if (createdViaArg !== void 0 && createdViaArg !== "seed" && createdViaArg !== "manual-spawn") {
|
|
21191
21491
|
throw new Error(`createdVia \u4EC5\u63A5\u53D7 seed\uFF08\u53D1\u8D77\u5DE5\u5355\uFF09\u6216 manual-spawn\uFF1Bauto-trigger/prereq \u7531\u5185\u6838\u5185\u90E8\u8BBE\u7F6E`);
|
|
21192
21492
|
}
|
|
21193
|
-
|
|
21493
|
+
let workspace = optStr(args, "workspace");
|
|
21494
|
+
if (workspace === void 0) {
|
|
21495
|
+
workspace = createdViaArg === "seed" ? `ws:wo-${(await import("node:crypto")).randomUUID().slice(0, 8)}` : "ws:default";
|
|
21496
|
+
}
|
|
21194
21497
|
const projectId = optStr(args, "project_id") ?? optStr(args, "projectId");
|
|
21498
|
+
const schemaForSeed = (createdViaArg ?? "manual-spawn") === "seed" ? ctx.schema : void 0;
|
|
21499
|
+
let coordinatorRequirement;
|
|
21500
|
+
if (schemaForSeed) {
|
|
21501
|
+
coordinatorRequirement = await resolveCoordinatorRequirement(ctx.registry);
|
|
21502
|
+
if (!coordinatorRequirement.ok && args["allowMissingCoordinator"] !== true) {
|
|
21503
|
+
throw new CoordinatorRequiredError(coordinatorRequirement);
|
|
21504
|
+
}
|
|
21505
|
+
}
|
|
21195
21506
|
if (projectId !== void 0) {
|
|
21196
21507
|
if (!ctx.projectState || !ctx.artifactState) {
|
|
21197
21508
|
throw new Error("spawn \u5E26 project_id \u9700\u8981\u670D\u52A1\u7AEF\u6CE8\u5165\u9879\u76EE\u5B58\u50A8\u4E0E\u5DE5\u5355\u7ED1\u5B9A\u5B58\u50A8");
|
|
@@ -21200,7 +21511,7 @@ async function runCommand(kernel, blobs, oplog, actor, command, args, ctx = {})
|
|
|
21200
21511
|
if (!project) throw new Error(`project not found: ${projectId}`);
|
|
21201
21512
|
await ctx.artifactState.upsertWorkspaceBinding({ workOrderId: workspace, projectId });
|
|
21202
21513
|
}
|
|
21203
|
-
if (
|
|
21514
|
+
if (schemaForSeed) {
|
|
21204
21515
|
const title = optStr(args, "title") ?? optStr(args, "description") ?? workspace;
|
|
21205
21516
|
const brief = optStr(args, "brief") ?? optStr(args, "body") ?? optStr(args, "description") ?? workspace;
|
|
21206
21517
|
const goal = optStr(args, "goal");
|
|
@@ -21211,6 +21522,14 @@ async function runCommand(kernel, blobs, oplog, actor, command, args, ctx = {})
|
|
|
21211
21522
|
\u9A8C\u6536\u6807\u51C6\uFF1A
|
|
21212
21523
|
${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
|
|
21213
21524
|
const agentIssues = [];
|
|
21525
|
+
if (coordinatorRequirement && !coordinatorRequirement.ok) {
|
|
21526
|
+
agentIssues.push({
|
|
21527
|
+
code: "missing_coordinator",
|
|
21528
|
+
severity: "warning",
|
|
21529
|
+
role: "coordinator",
|
|
21530
|
+
message: `${coordinatorRequirement.message} \u7528\u6237\u5DF2\u786E\u8BA4\u7EE7\u7EED\u521B\u5EFA\uFF1B\u540E\u7EED gap / \u6B7B\u9501 / \u6539\u56FE\u6D41\u7A0B\u53EF\u80FD\u65E0\u6CD5\u81EA\u52A8\u5904\u7406\u3002`
|
|
21531
|
+
});
|
|
21532
|
+
}
|
|
21214
21533
|
let agentPlan = null;
|
|
21215
21534
|
const inlinePlan = args["plan"];
|
|
21216
21535
|
if (inlinePlan !== void 0 && inlinePlan !== null) {
|
|
@@ -21221,22 +21540,14 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
|
|
|
21221
21540
|
}
|
|
21222
21541
|
if (agentPlan === null && ctx.planWorkorderWithAgent) {
|
|
21223
21542
|
try {
|
|
21224
|
-
const availableActors =
|
|
21225
|
-
|
|
21226
|
-
name: a.name,
|
|
21227
|
-
roles: a.roles,
|
|
21228
|
-
kind: a.kind
|
|
21229
|
-
})) : [];
|
|
21543
|
+
const availableActors = await plannerAvailableActors(ctx.registry);
|
|
21544
|
+
const artifactTypes = await plannerArtifactTypes(schemaForSeed, ctx.registry);
|
|
21230
21545
|
const attempt = await ctx.planWorkorderWithAgent({
|
|
21231
21546
|
requestedType: type,
|
|
21232
21547
|
workspace,
|
|
21233
21548
|
title,
|
|
21234
21549
|
brief: briefForPlanner,
|
|
21235
|
-
artifactTypes
|
|
21236
|
-
name: def.name,
|
|
21237
|
-
ownerRole: def.ownerRole,
|
|
21238
|
-
contentType: def.contentType
|
|
21239
|
-
})),
|
|
21550
|
+
artifactTypes,
|
|
21240
21551
|
availableActors
|
|
21241
21552
|
});
|
|
21242
21553
|
if (attempt) {
|
|
@@ -21267,7 +21578,7 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
|
|
|
21267
21578
|
...goal !== void 0 ? { goal } : {},
|
|
21268
21579
|
...acceptanceCriteria && acceptanceCriteria.length > 0 ? { acceptanceCriteria } : {},
|
|
21269
21580
|
actor,
|
|
21270
|
-
schema:
|
|
21581
|
+
schema: schemaForSeed,
|
|
21271
21582
|
...optStr(args, "id") !== void 0 ? { requestedArtifactId: optStr(args, "id") } : {},
|
|
21272
21583
|
...ctx.registry ? { registry: ctx.registry } : {},
|
|
21273
21584
|
...agentPlan ? { agentPlan } : {},
|
|
@@ -21664,8 +21975,9 @@ async function runView(kernel, oplog, blobs, name, artifactId, params) {
|
|
|
21664
21975
|
case "ls":
|
|
21665
21976
|
return [...model.artifacts.values()].map((a) => {
|
|
21666
21977
|
const lifecycle = lifecycleOf(model, a.id);
|
|
21667
|
-
const
|
|
21668
|
-
|
|
21978
|
+
const queue = queueOf(model, a.id).length;
|
|
21979
|
+
const mark = lifecycle !== "active" ? lifecycle : pendingConcludeAttempt(model, a.id) ? "gate-pending" : blockedOf(model, a.id).blocked ? "blocked" : isConcluded(model, a.id) ? "concluded" : a.currentRev || queue > 0 ? "active" : "embryo";
|
|
21980
|
+
return { ...a, queue, mark };
|
|
21669
21981
|
});
|
|
21670
21982
|
case "show": {
|
|
21671
21983
|
const id = need2();
|
|
@@ -21767,6 +22079,7 @@ async function runView(kernel, oplog, blobs, name, artifactId, params) {
|
|
|
21767
22079
|
async function startOasisServer(opts) {
|
|
21768
22080
|
const resolveActor = opts.resolveActor ?? defaultResolveActor;
|
|
21769
22081
|
const stagingBuffers = /* @__PURE__ */ new Map();
|
|
22082
|
+
const stagingWorkspace = /* @__PURE__ */ new Map();
|
|
21770
22083
|
const resolveEngine = opts.resolveEngine ?? (async () => ({ kernel: opts.kernel, oplog: opts.oplog, blobs: opts.blobs, ...opts.registry ? { registry: opts.registry } : {} }));
|
|
21771
22084
|
const domainRouter = new Router();
|
|
21772
22085
|
for (const register of opts.domains ?? []) register(domainRouter);
|
|
@@ -21849,6 +22162,10 @@ async function startOasisServer(opts) {
|
|
|
21849
22162
|
}));
|
|
21850
22163
|
return;
|
|
21851
22164
|
}
|
|
22165
|
+
if (url.pathname === "/api/whoami" && req.method === "GET") {
|
|
22166
|
+
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ actor }));
|
|
22167
|
+
return;
|
|
22168
|
+
}
|
|
21852
22169
|
const sessionKey = (req.headers.authorization ?? "").startsWith("Bearer ") ? req.headers.authorization.slice("Bearer ".length) : actor;
|
|
21853
22170
|
const chunks = [];
|
|
21854
22171
|
for await (const chunk of req) chunks.push(chunk);
|
|
@@ -21870,8 +22187,38 @@ async function startOasisServer(opts) {
|
|
|
21870
22187
|
...opts.workorderDrafts ? { workorderDrafts: opts.workorderDrafts } : {},
|
|
21871
22188
|
...opts.gitEnv ? { gitEnv: opts.gitEnv } : {}
|
|
21872
22189
|
});
|
|
22190
|
+
if (body.command === "spawn" && opts.chatSession) {
|
|
22191
|
+
const workspace = body.args?.["workspace"];
|
|
22192
|
+
if (workspace && workspace !== "ws:default") {
|
|
22193
|
+
const chatSid = req.headers["x-chat-session-id"];
|
|
22194
|
+
if (typeof chatSid === "string") {
|
|
22195
|
+
await opts.chatSession.linkWorkOrder(chatSid, workspace).catch((err) => {
|
|
22196
|
+
console.error(`[chat-session] Failed to link work order ${workspace} to session ${chatSid}:`, err);
|
|
22197
|
+
});
|
|
22198
|
+
}
|
|
22199
|
+
}
|
|
22200
|
+
}
|
|
22201
|
+
if (body.command === "spawn" && opts.workorderDrafts) {
|
|
22202
|
+
const draftId = result?.data?.draftId;
|
|
22203
|
+
const chatSid = req.headers["x-chat-session-id"];
|
|
22204
|
+
if (draftId && typeof chatSid === "string") opts.workorderDrafts.tagSession(draftId, chatSid);
|
|
22205
|
+
}
|
|
21873
22206
|
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(result));
|
|
21874
22207
|
} catch (err) {
|
|
22208
|
+
if (err instanceof CoordinatorRequiredError) {
|
|
22209
|
+
res.writeHead(err.status, { "content-type": "application/json" }).end(JSON.stringify({
|
|
22210
|
+
error: {
|
|
22211
|
+
code: err.code,
|
|
22212
|
+
message: err.message,
|
|
22213
|
+
canContinueWithOverride: true,
|
|
22214
|
+
overrideField: "allowMissingCoordinator",
|
|
22215
|
+
overrideValue: true,
|
|
22216
|
+
reason: err.requirement.code,
|
|
22217
|
+
coordinatorActorId: err.requirement.actor?.id
|
|
22218
|
+
}
|
|
22219
|
+
}));
|
|
22220
|
+
return;
|
|
22221
|
+
}
|
|
21875
22222
|
const status = err instanceof FrozenError ? 409 : 400;
|
|
21876
22223
|
res.writeHead(status, { "content-type": "application/json" }).end(
|
|
21877
22224
|
JSON.stringify({
|
|
@@ -21908,7 +22255,8 @@ async function startOasisServer(opts) {
|
|
|
21908
22255
|
}
|
|
21909
22256
|
if (url.pathname === "/api/workorder/draft" && req.method === "GET" && opts.workorderDrafts) {
|
|
21910
22257
|
const workspace = url.searchParams.get("workspace");
|
|
21911
|
-
const
|
|
22258
|
+
const session = url.searchParams.get("session");
|
|
22259
|
+
const draft = workspace ? opts.workorderDrafts.forWorkspace(workspace) : session ? opts.workorderDrafts.forSession(session) : void 0;
|
|
21912
22260
|
if (!draft) {
|
|
21913
22261
|
res.writeHead(404, { "content-type": "application/json" }).end(JSON.stringify({ error: "no staged workorder draft" }));
|
|
21914
22262
|
return;
|
|
@@ -21930,6 +22278,11 @@ async function startOasisServer(opts) {
|
|
|
21930
22278
|
if (body.dispatch === false && brief?.workspace) {
|
|
21931
22279
|
await opts.artifactState?.setWorkspaceDispatchHold(brief.workspace, true);
|
|
21932
22280
|
}
|
|
22281
|
+
if (opts.chatSession && body.chatSessionId && brief?.workspace && brief.workspace !== "ws:default") {
|
|
22282
|
+
await opts.chatSession.linkWorkOrder(body.chatSessionId, brief.workspace).catch((err) => {
|
|
22283
|
+
console.error(`[chat-session] Failed to link work order ${brief.workspace} to session ${body.chatSessionId}:`, err);
|
|
22284
|
+
});
|
|
22285
|
+
}
|
|
21933
22286
|
res.writeHead(200, { "content-type": "application/json" }).end(
|
|
21934
22287
|
JSON.stringify({ ...result, briefArtifactId: body.briefArtifactId, workspace: brief?.workspace ?? null })
|
|
21935
22288
|
);
|
|
@@ -22014,11 +22367,29 @@ async function startOasisServer(opts) {
|
|
|
22014
22367
|
}
|
|
22015
22368
|
if (url.pathname === "/api/stage" && req.method === "POST") {
|
|
22016
22369
|
try {
|
|
22017
|
-
const { op } = JSON.parse(rawBody);
|
|
22370
|
+
const { op, workspace } = JSON.parse(rawBody);
|
|
22018
22371
|
if (!op || typeof op.action !== "string") throw new Error("\u7F3A\u5C11 op\uFF08{action,...}\uFF09");
|
|
22372
|
+
if (opts.workorderDrafts) {
|
|
22373
|
+
let ws = typeof workspace === "string" && workspace.trim() ? workspace.trim() : void 0;
|
|
22374
|
+
if (!ws) {
|
|
22375
|
+
const ref = op.artifactId;
|
|
22376
|
+
if (typeof ref === "string" && ref.startsWith("artifact:"))
|
|
22377
|
+
ws = opts.workorderDrafts.list().find((d) => d.plan.ops.some((o) => o.action === "spawn" && o.id === ref))?.workspace;
|
|
22378
|
+
}
|
|
22379
|
+
const draft = ws ? opts.workorderDrafts.forWorkspace(ws) : void 0;
|
|
22380
|
+
if (draft) {
|
|
22381
|
+
editDraftPlan(draft, op);
|
|
22382
|
+
draft.at = (/* @__PURE__ */ new Date()).toISOString();
|
|
22383
|
+
res.writeHead(200, { "content-type": "application/json" }).end(
|
|
22384
|
+
JSON.stringify({ draft: true, workspace: draft.workspace, nodes: draft.plan.ops.filter((o) => o.action === "spawn").length, staged: op })
|
|
22385
|
+
);
|
|
22386
|
+
return;
|
|
22387
|
+
}
|
|
22388
|
+
}
|
|
22019
22389
|
const buf = stagingBuffers.get(sessionKey) ?? [];
|
|
22020
22390
|
buf.push(op);
|
|
22021
22391
|
stagingBuffers.set(sessionKey, buf);
|
|
22392
|
+
if (typeof workspace === "string" && workspace.trim()) stagingWorkspace.set(sessionKey, workspace.trim());
|
|
22022
22393
|
const cascade = op.action === "seal" && op.reason === "cancelled" ? engine.kernel.cancelCascadeEdges(op.artifactId) : void 0;
|
|
22023
22394
|
res.writeHead(200, { "content-type": "application/json" }).end(
|
|
22024
22395
|
JSON.stringify({ count: buf.length, staged: op, ...cascade && cascade.length > 0 ? { cascade } : {} })
|
|
@@ -22032,10 +22403,11 @@ async function startOasisServer(opts) {
|
|
|
22032
22403
|
}
|
|
22033
22404
|
if (url.pathname === "/api/stage" && req.method === "GET") {
|
|
22034
22405
|
const ops = stagingBuffers.get(sessionKey) ?? [];
|
|
22406
|
+
const ws = stagingWorkspace.get(sessionKey);
|
|
22035
22407
|
let preview2 = null;
|
|
22036
22408
|
if (ops.length > 0) {
|
|
22037
22409
|
try {
|
|
22038
|
-
preview2 = await engine.kernel.previewIntervention({ reason: "(staged)", ops });
|
|
22410
|
+
preview2 = await engine.kernel.previewIntervention({ reason: "(staged)", ops, ...ws ? { workspace: ws } : {} });
|
|
22039
22411
|
} catch (err) {
|
|
22040
22412
|
preview2 = { error: err instanceof Error ? err.message : String(err) };
|
|
22041
22413
|
}
|
|
@@ -22045,6 +22417,7 @@ async function startOasisServer(opts) {
|
|
|
22045
22417
|
}
|
|
22046
22418
|
if (url.pathname === "/api/stage" && req.method === "DELETE") {
|
|
22047
22419
|
stagingBuffers.delete(sessionKey);
|
|
22420
|
+
stagingWorkspace.delete(sessionKey);
|
|
22048
22421
|
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ cleared: true }));
|
|
22049
22422
|
return;
|
|
22050
22423
|
}
|
|
@@ -22056,7 +22429,8 @@ async function startOasisServer(opts) {
|
|
|
22056
22429
|
return;
|
|
22057
22430
|
}
|
|
22058
22431
|
const { reason } = JSON.parse(rawBody || "{}");
|
|
22059
|
-
const
|
|
22432
|
+
const ws = stagingWorkspace.get(sessionKey);
|
|
22433
|
+
const plan = { reason: reason?.trim() || "(\u534F\u8C03\u8005 staged \u6539\u56FE)", ops, ...ws ? { workspace: ws } : {} };
|
|
22060
22434
|
const preview2 = await engine.kernel.previewIntervention(plan);
|
|
22061
22435
|
if (!preview2.endState.ok) {
|
|
22062
22436
|
throw new Error(`\u7EC8\u6001\u6821\u9A8C\u4E0D\u8FC7\uFF08\u673A\u68B0\u786C\u62E6\uFF09\uFF1A${preview2.endState.violations.join("\uFF1B")}`);
|
|
@@ -22064,6 +22438,7 @@ async function startOasisServer(opts) {
|
|
|
22064
22438
|
if (preview2.changeClass === "B") {
|
|
22065
22439
|
const result = await engine.kernel.applyIntervention(plan, preview2.baseSeqs, actor);
|
|
22066
22440
|
stagingBuffers.delete(sessionKey);
|
|
22441
|
+
stagingWorkspace.delete(sessionKey);
|
|
22067
22442
|
res.writeHead(200, { "content-type": "application/json" }).end(
|
|
22068
22443
|
JSON.stringify({ outcome: "applied", applied: result.applied, interventionId: result.interventionId })
|
|
22069
22444
|
);
|
|
@@ -22079,6 +22454,7 @@ async function startOasisServer(opts) {
|
|
|
22079
22454
|
downstreamClosure: preview2.blastRadius.downstreamClosure
|
|
22080
22455
|
});
|
|
22081
22456
|
stagingBuffers.delete(sessionKey);
|
|
22457
|
+
stagingWorkspace.delete(sessionKey);
|
|
22082
22458
|
res.writeHead(200, { "content-type": "application/json" }).end(
|
|
22083
22459
|
JSON.stringify({ outcome: "drafted", draftId: draft.id, changeClass: draft.changeClass, nonMonotonicOps: draft.nonMonotonicOps })
|
|
22084
22460
|
);
|
|
@@ -22174,10 +22550,33 @@ async function startOasisServer(opts) {
|
|
|
22174
22550
|
}
|
|
22175
22551
|
return;
|
|
22176
22552
|
}
|
|
22553
|
+
if (url.pathname === "/api/chat/attachments" && req.method === "POST" && opts.uploadChatAttachment) {
|
|
22554
|
+
try {
|
|
22555
|
+
const body = JSON.parse(rawBody);
|
|
22556
|
+
if (typeof body.contentBase64 !== "string" || !body.contentBase64) throw new Error("\u7F3A\u5C11 contentBase64");
|
|
22557
|
+
const out = await opts.uploadChatAttachment({
|
|
22558
|
+
contentBase64: body.contentBase64,
|
|
22559
|
+
...typeof body.contentType === "string" ? { contentType: body.contentType } : {},
|
|
22560
|
+
...typeof body.name === "string" ? { name: body.name } : {}
|
|
22561
|
+
});
|
|
22562
|
+
res.writeHead(201, { "content-type": "application/json" });
|
|
22563
|
+
res.end(JSON.stringify(out));
|
|
22564
|
+
} catch (err) {
|
|
22565
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
22566
|
+
res.end(JSON.stringify({ error: { code: "ATTACHMENT_UPLOAD_FAILED", message: err instanceof Error ? err.message : String(err) } }));
|
|
22567
|
+
}
|
|
22568
|
+
return;
|
|
22569
|
+
}
|
|
22177
22570
|
if (url.pathname === "/api/chat" && req.method === "POST") {
|
|
22178
22571
|
try {
|
|
22179
22572
|
const body = JSON.parse(rawBody);
|
|
22180
22573
|
if (!body.message) throw new Error("\u7F3A\u5C11 message");
|
|
22574
|
+
const attachments = Array.isArray(body.attachments) ? body.attachments.filter((a) => typeof a?.name === "string" && (typeof a?.text === "string" || typeof a?.blobRef === "string")).map((a) => ({
|
|
22575
|
+
name: a.name,
|
|
22576
|
+
...typeof a.text === "string" ? { text: a.text } : {},
|
|
22577
|
+
...typeof a.blobRef === "string" ? { blobRef: a.blobRef } : {},
|
|
22578
|
+
...typeof a.contentType === "string" ? { contentType: a.contentType } : {}
|
|
22579
|
+
})) : [];
|
|
22181
22580
|
const typedParts = wantsChatParts(req);
|
|
22182
22581
|
if (opts.dispatchChat) {
|
|
22183
22582
|
if (!body.actorId) {
|
|
@@ -22186,7 +22585,9 @@ async function startOasisServer(opts) {
|
|
|
22186
22585
|
const session = await opts.dispatchChat({
|
|
22187
22586
|
actorId: body.actorId,
|
|
22188
22587
|
message: body.message,
|
|
22189
|
-
...body.sessionId ? { sessionId: body.sessionId } : {}
|
|
22588
|
+
...body.sessionId ? { sessionId: body.sessionId } : {},
|
|
22589
|
+
...body.chatSessionId ? { chatSessionId: body.chatSessionId } : {},
|
|
22590
|
+
...attachments.length ? { attachments } : {}
|
|
22190
22591
|
});
|
|
22191
22592
|
let finished = false;
|
|
22192
22593
|
res.on("close", () => {
|
|
@@ -22202,8 +22603,9 @@ async function startOasisServer(opts) {
|
|
|
22202
22603
|
res.writeHead(200, {
|
|
22203
22604
|
"content-type": "application/x-ndjson; charset=utf-8",
|
|
22204
22605
|
"transfer-encoding": "chunked",
|
|
22205
|
-
"access-control-expose-headers": "X-Session-Id",
|
|
22206
|
-
"x-session-id": session.id
|
|
22606
|
+
"access-control-expose-headers": "X-Session-Id, X-Run-Id",
|
|
22607
|
+
"x-session-id": session.id,
|
|
22608
|
+
...session.runId ? { "x-run-id": session.runId } : {}
|
|
22207
22609
|
});
|
|
22208
22610
|
let sawTextOutput = false;
|
|
22209
22611
|
session.onOutput((chunk) => {
|
|
@@ -22307,7 +22709,7 @@ async function startOasisServer(opts) {
|
|
|
22307
22709
|
];
|
|
22308
22710
|
const child = spawn6(opts.claudeBin ?? "claude", args, {
|
|
22309
22711
|
...spawnCwd ? { cwd: spawnCwd } : {},
|
|
22310
|
-
env: { ...process.env, ...env },
|
|
22712
|
+
env: { ...process.env, ...env, ...body.chatSessionId ? { OASIS_CHAT_SESSION_ID: body.chatSessionId } : {} },
|
|
22311
22713
|
stdio: ["ignore", "pipe", "pipe"]
|
|
22312
22714
|
});
|
|
22313
22715
|
if (typedParts) {
|
|
@@ -22588,7 +22990,7 @@ async function startOasisServer(opts) {
|
|
|
22588
22990
|
})
|
|
22589
22991
|
};
|
|
22590
22992
|
}
|
|
22591
|
-
var http, defaultResolveActor, enc, CHAT_OUTPUT_LIMIT, GOVERNANCE_COMMANDS, isAgent;
|
|
22993
|
+
var http, CoordinatorRequiredError, defaultResolveActor, enc, CHAT_OUTPUT_LIMIT, GOVERNANCE_COMMANDS, isAgent;
|
|
22592
22994
|
var init_server3 = __esm({
|
|
22593
22995
|
"../server/src/server.ts"() {
|
|
22594
22996
|
"use strict";
|
|
@@ -22600,7 +23002,17 @@ var init_server3 = __esm({
|
|
|
22600
23002
|
init_router();
|
|
22601
23003
|
init_drafts();
|
|
22602
23004
|
init_workorder_drafts();
|
|
23005
|
+
init_draft_edit();
|
|
22603
23006
|
init_planner();
|
|
23007
|
+
init_identity();
|
|
23008
|
+
CoordinatorRequiredError = class extends Error {
|
|
23009
|
+
constructor(requirement) {
|
|
23010
|
+
super(requirement.message ?? "\u7CFB\u7EDF\u7F3A\u5C11\u534F\u8C03\u8005\u667A\u80FD\u4F53\u3002");
|
|
23011
|
+
this.requirement = requirement;
|
|
23012
|
+
}
|
|
23013
|
+
status = 409;
|
|
23014
|
+
code = "COORDINATOR_REQUIRED";
|
|
23015
|
+
};
|
|
22604
23016
|
defaultResolveActor = (token) => token.startsWith("token:") ? token.slice("token:".length) : null;
|
|
22605
23017
|
enc = (s2) => new TextEncoder().encode(s2);
|
|
22606
23018
|
CHAT_OUTPUT_LIMIT = 16e3;
|
|
@@ -22612,29 +23024,53 @@ var init_server3 = __esm({
|
|
|
22612
23024
|
// ../server/src/tokens.ts
|
|
22613
23025
|
function createTokenIssuer() {
|
|
22614
23026
|
const table = /* @__PURE__ */ new Map();
|
|
23027
|
+
const prune = () => {
|
|
23028
|
+
const now = Date.now();
|
|
23029
|
+
for (const [token, entry] of table) {
|
|
23030
|
+
if (entry.expiresAt !== void 0 && entry.expiresAt <= now) table.delete(token);
|
|
23031
|
+
}
|
|
23032
|
+
};
|
|
22615
23033
|
return {
|
|
22616
|
-
issue(actor) {
|
|
23034
|
+
issue(actor, opts = {}) {
|
|
23035
|
+
prune();
|
|
22617
23036
|
const token = `oat_${(0, import_node_crypto3.randomBytes)(24).toString("base64url")}`;
|
|
22618
|
-
|
|
23037
|
+
const issuedAt = Date.now();
|
|
23038
|
+
table.set(token, {
|
|
23039
|
+
actor,
|
|
23040
|
+
issuedAt,
|
|
23041
|
+
...opts.ttlMs !== void 0 ? { expiresAt: issuedAt + opts.ttlMs } : {},
|
|
23042
|
+
...opts.sessionId !== void 0 ? { sessionId: opts.sessionId } : {},
|
|
23043
|
+
...opts.artifactId !== void 0 ? { artifactId: opts.artifactId } : {},
|
|
23044
|
+
...opts.action !== void 0 ? { action: opts.action } : {},
|
|
23045
|
+
...opts.nodeId !== void 0 ? { nodeId: opts.nodeId } : {},
|
|
23046
|
+
...opts.runtimeKind !== void 0 ? { runtimeKind: opts.runtimeKind } : {},
|
|
23047
|
+
...opts.scopes !== void 0 ? { scopes: opts.scopes } : {}
|
|
23048
|
+
});
|
|
22619
23049
|
return token;
|
|
22620
23050
|
},
|
|
22621
23051
|
revoke(token) {
|
|
22622
23052
|
table.delete(token);
|
|
22623
23053
|
},
|
|
22624
23054
|
resolve(token) {
|
|
22625
|
-
|
|
23055
|
+
const entry = table.get(token);
|
|
23056
|
+
if (!entry) return null;
|
|
23057
|
+
if (entry.expiresAt !== void 0 && entry.expiresAt <= Date.now()) {
|
|
23058
|
+
table.delete(token);
|
|
23059
|
+
return null;
|
|
23060
|
+
}
|
|
23061
|
+
return entry.actor;
|
|
22626
23062
|
}
|
|
22627
23063
|
};
|
|
22628
23064
|
}
|
|
22629
23065
|
function createNodeTokenStore(file) {
|
|
22630
23066
|
const read = () => {
|
|
22631
23067
|
try {
|
|
22632
|
-
return JSON.parse(
|
|
23068
|
+
return JSON.parse(fs2.readFileSync(file, "utf8"));
|
|
22633
23069
|
} catch {
|
|
22634
23070
|
return {};
|
|
22635
23071
|
}
|
|
22636
23072
|
};
|
|
22637
|
-
const write = (table) =>
|
|
23073
|
+
const write = (table) => fs2.writeFileSync(file, JSON.stringify(table, null, 2), { mode: 384 });
|
|
22638
23074
|
return {
|
|
22639
23075
|
issue(nodeId) {
|
|
22640
23076
|
const table = read();
|
|
@@ -22660,14 +23096,14 @@ function createEnrollTokenStore(defaultTtlMs = 30 * 60 * 1e3, file) {
|
|
|
22660
23096
|
const read = () => {
|
|
22661
23097
|
if (!file) return table;
|
|
22662
23098
|
try {
|
|
22663
|
-
return new Map(Object.entries(JSON.parse(
|
|
23099
|
+
return new Map(Object.entries(JSON.parse(fs2.readFileSync(file, "utf8"))));
|
|
22664
23100
|
} catch {
|
|
22665
23101
|
return /* @__PURE__ */ new Map();
|
|
22666
23102
|
}
|
|
22667
23103
|
};
|
|
22668
23104
|
const write = (m2) => {
|
|
22669
23105
|
if (!file) return;
|
|
22670
|
-
|
|
23106
|
+
fs2.writeFileSync(file, JSON.stringify(Object.fromEntries(m2), null, 2), { mode: 384 });
|
|
22671
23107
|
};
|
|
22672
23108
|
const table = /* @__PURE__ */ new Map();
|
|
22673
23109
|
const prune = () => {
|
|
@@ -22710,12 +23146,12 @@ function createEnrollTokenStore(defaultTtlMs = 30 * 60 * 1e3, file) {
|
|
|
22710
23146
|
}
|
|
22711
23147
|
};
|
|
22712
23148
|
}
|
|
22713
|
-
var import_node_crypto3,
|
|
23149
|
+
var import_node_crypto3, fs2;
|
|
22714
23150
|
var init_tokens = __esm({
|
|
22715
23151
|
"../server/src/tokens.ts"() {
|
|
22716
23152
|
"use strict";
|
|
22717
23153
|
import_node_crypto3 = require("node:crypto");
|
|
22718
|
-
|
|
23154
|
+
fs2 = __toESM(require("node:fs"), 1);
|
|
22719
23155
|
}
|
|
22720
23156
|
});
|
|
22721
23157
|
|
|
@@ -23829,17 +24265,25 @@ function nodeInfo(model, a) {
|
|
|
23829
24265
|
const lifecycle = lifecycleOf(model, a.id);
|
|
23830
24266
|
const sealed = lifecycle !== "active";
|
|
23831
24267
|
const concluded = isConcluded(model, a.id);
|
|
23832
|
-
const
|
|
23833
|
-
const
|
|
23834
|
-
|
|
24268
|
+
const queueLen = queueOf(model, a.id).length;
|
|
24269
|
+
const mark = sealed ? lifecycle : pendingConcludeAttempt(model, a.id) ? "gate-pending" : blockedOf(model, a.id).blocked ? "blocked" : concluded ? "concluded" : (
|
|
24270
|
+
// embryo 只判"还没动工":无定稿版**且**队列里也没有待审版。智能体已交首版排队待审
|
|
24271
|
+
// (queueLen>0)即已产出,按 active 走,由 deriveNodeStage 落到 review,别误判成 embryo。
|
|
24272
|
+
a.currentRev || queueLen > 0 ? "active" : "embryo"
|
|
24273
|
+
);
|
|
24274
|
+
const revisions = revisionsOf(model, a.id);
|
|
24275
|
+
const hasWorking = revisions.some((r) => r.state === "working");
|
|
24276
|
+
const producedParts = new Set(revisions.filter((r) => r.part !== void 0 && r.state !== "rejected").map((r) => r.part));
|
|
24277
|
+
const hasUnproducedParts = (a.parts ?? []).some((p2) => !producedParts.has(p2.name));
|
|
24278
|
+
return { mark, queueLen, concluded, sealed, hasWorking, hasUnproducedParts };
|
|
23835
24279
|
}
|
|
23836
24280
|
function deriveStage(members) {
|
|
23837
24281
|
if (members.length > 0 && members.every((m2) => m2.sealed)) return "sealed";
|
|
23838
24282
|
const live = members.filter((m2) => !m2.sealed);
|
|
23839
24283
|
if (live.length > 0 && live.every((m2) => m2.concluded)) return "concluded";
|
|
23840
|
-
const reviewing = live.some((m2) => m2.mark === "gate-pending" || m2.mark === "active" && m2.queueLen > 0);
|
|
24284
|
+
const reviewing = live.some((m2) => m2.mark === "gate-pending" || m2.mark === "active" && m2.queueLen > 0 && !m2.hasUnproducedParts);
|
|
23841
24285
|
if (reviewing) return "review";
|
|
23842
|
-
const progressing = live.some((m2) => m2.mark === "active" || m2.mark === "embryo");
|
|
24286
|
+
const progressing = live.some((m2) => m2.mark === "active" || m2.mark === "embryo" || m2.hasUnproducedParts);
|
|
23843
24287
|
const blocked = live.some((m2) => m2.mark === "blocked");
|
|
23844
24288
|
if (blocked && !progressing) return "blocked";
|
|
23845
24289
|
return "executing";
|
|
@@ -23856,7 +24300,7 @@ function deriveNodeStage(info) {
|
|
|
23856
24300
|
case "embryo":
|
|
23857
24301
|
return "embryo";
|
|
23858
24302
|
default:
|
|
23859
|
-
if (info.hasWorking) return "editing";
|
|
24303
|
+
if (info.hasWorking || info.hasUnproducedParts) return "editing";
|
|
23860
24304
|
if (info.queueLen > 0) return "review";
|
|
23861
24305
|
return "merged";
|
|
23862
24306
|
}
|
|
@@ -23894,6 +24338,8 @@ function buildWorkorderSummaries(model, resolveActor, resolveProject, dispatchPa
|
|
|
23894
24338
|
const lastOps = arts.map((a) => model.lastOpAt.get(a.id)).filter((t) => Boolean(t)).sort();
|
|
23895
24339
|
const participantIds = [...new Set(arts.map((a) => a.owner))];
|
|
23896
24340
|
const woTitle = seed.title ?? seed.description ?? workspace;
|
|
24341
|
+
const stage = deriveStage(members);
|
|
24342
|
+
const dispatchPaused = dispatchPausedOf?.(workspace) ?? false;
|
|
23897
24343
|
out.push({
|
|
23898
24344
|
id: workspace,
|
|
23899
24345
|
title: woTitle,
|
|
@@ -23901,7 +24347,7 @@ function buildWorkorderSummaries(model, resolveActor, resolveProject, dispatchPa
|
|
|
23901
24347
|
...seed.description && seed.description !== woTitle ? { description: seed.description } : {},
|
|
23902
24348
|
// 一句话目标:优先取结构化 spec.goal;缺省时前端回退 description 首段(投影不替前端做回退,只给真值)
|
|
23903
24349
|
...spec?.goal ? { goal: spec.goal } : {},
|
|
23904
|
-
stage
|
|
24350
|
+
stage,
|
|
23905
24351
|
owner: ref(seed.owner),
|
|
23906
24352
|
participants: participantIds.map(ref),
|
|
23907
24353
|
artifactCount: arts.length,
|
|
@@ -23909,7 +24355,7 @@ function buildWorkorderSummaries(model, resolveActor, resolveProject, dispatchPa
|
|
|
23909
24355
|
updatedAt: lastOps[lastOps.length - 1] ?? "",
|
|
23910
24356
|
projectId: resolveProject?.(workspace) ?? null,
|
|
23911
24357
|
planning: planningStatusOf(model, arts),
|
|
23912
|
-
...
|
|
24358
|
+
...dispatchPaused ? { dispatchPaused: true } : {}
|
|
23913
24359
|
});
|
|
23914
24360
|
}
|
|
23915
24361
|
return out.sort((a, b2) => a.updatedAt < b2.updatedAt ? 1 : a.updatedAt > b2.updatedAt ? -1 : 0);
|
|
@@ -24783,9 +25229,9 @@ var init_service = __esm({
|
|
|
24783
25229
|
reason: input.revision.summary ?? input.revision.version
|
|
24784
25230
|
});
|
|
24785
25231
|
await this.syncProjection();
|
|
24786
|
-
const
|
|
24787
|
-
if (!
|
|
24788
|
-
return
|
|
25232
|
+
const record4 = await this.artifacts.getRevision(kernelRevision.id);
|
|
25233
|
+
if (!record4) throw new Error(`revision projection missing after register: ${kernelRevision.id}`);
|
|
25234
|
+
return record4;
|
|
24789
25235
|
}
|
|
24790
25236
|
async approveArtifactRevision(input) {
|
|
24791
25237
|
if (input.actorKind !== "human") throw namedError("AgentReviewForbidden", "agents cannot approve artifact revisions");
|
|
@@ -24880,9 +25326,9 @@ var init_service = __esm({
|
|
|
24880
25326
|
...input.revisionId ? { revisionId: input.revisionId } : {}
|
|
24881
25327
|
});
|
|
24882
25328
|
await this.syncProjection();
|
|
24883
|
-
const
|
|
24884
|
-
if (!
|
|
24885
|
-
return
|
|
25329
|
+
const record4 = await this.artifacts.getAnnotation(kernelAnnotation.id);
|
|
25330
|
+
if (!record4) throw new Error(`annotation projection missing after create: ${kernelAnnotation.id}`);
|
|
25331
|
+
return record4;
|
|
24886
25332
|
}
|
|
24887
25333
|
async resolveArtifactAnnotation(input) {
|
|
24888
25334
|
const artifact = await this.artifacts.getArtifact(input.artifactId);
|
|
@@ -24898,9 +25344,9 @@ var init_service = __esm({
|
|
|
24898
25344
|
...input.comment?.trim() ? { note: input.comment.trim() } : {}
|
|
24899
25345
|
});
|
|
24900
25346
|
await this.syncProjection();
|
|
24901
|
-
const
|
|
24902
|
-
if (!
|
|
24903
|
-
return
|
|
25347
|
+
const record4 = await this.artifacts.getAnnotation(input.annotationId);
|
|
25348
|
+
if (!record4) throw new Error(`annotation projection missing after resolve: ${input.annotationId}`);
|
|
25349
|
+
return record4;
|
|
24904
25350
|
}
|
|
24905
25351
|
async registerNodeOutput(input) {
|
|
24906
25352
|
await this.assertProjectExecutable(input.projectId);
|
|
@@ -25129,12 +25575,12 @@ var init_service = __esm({
|
|
|
25129
25575
|
});
|
|
25130
25576
|
|
|
25131
25577
|
// ../server/src/dev-store.ts
|
|
25132
|
-
var import_node_crypto4,
|
|
25578
|
+
var import_node_crypto4, fs3, path, NdjsonOplogStore, DirBlobStore, MUTATORS, FileTypeRegistryStore, FileRegistryStore, FileProjectStateStore, FileProjectDocumentStore, FileArtifactStateStore, FileTraceStore, MemoryChatSessionStore;
|
|
25133
25579
|
var init_dev_store = __esm({
|
|
25134
25580
|
"../server/src/dev-store.ts"() {
|
|
25135
25581
|
"use strict";
|
|
25136
25582
|
import_node_crypto4 = require("node:crypto");
|
|
25137
|
-
|
|
25583
|
+
fs3 = __toESM(require("node:fs"), 1);
|
|
25138
25584
|
path = __toESM(require("node:path"), 1);
|
|
25139
25585
|
init_src();
|
|
25140
25586
|
init_src3();
|
|
@@ -25147,8 +25593,8 @@ var init_dev_store = __esm({
|
|
|
25147
25593
|
}
|
|
25148
25594
|
static async open(file) {
|
|
25149
25595
|
const memory = new MemoryOplogStore();
|
|
25150
|
-
if (
|
|
25151
|
-
for (const line of
|
|
25596
|
+
if (fs3.existsSync(file)) {
|
|
25597
|
+
for (const line of fs3.readFileSync(file, "utf8").split("\n")) {
|
|
25152
25598
|
if (!line.trim()) continue;
|
|
25153
25599
|
const stored = JSON.parse(line);
|
|
25154
25600
|
const { artifactId, seq, ...op } = stored;
|
|
@@ -25156,15 +25602,15 @@ var init_dev_store = __esm({
|
|
|
25156
25602
|
if (assigned !== seq) throw new Error(`corrupt ndjson log: seq mismatch at ${line}`);
|
|
25157
25603
|
}
|
|
25158
25604
|
} else {
|
|
25159
|
-
|
|
25160
|
-
|
|
25605
|
+
fs3.mkdirSync(path.dirname(file), { recursive: true });
|
|
25606
|
+
fs3.writeFileSync(file, "");
|
|
25161
25607
|
}
|
|
25162
25608
|
return new _NdjsonOplogStore(file, memory);
|
|
25163
25609
|
}
|
|
25164
25610
|
async append(artifactId, op, expectedSeq) {
|
|
25165
25611
|
const seq = await this.memory.append(artifactId, op, expectedSeq);
|
|
25166
25612
|
const stored = { ...op, artifactId, seq };
|
|
25167
|
-
|
|
25613
|
+
fs3.appendFileSync(this.file, JSON.stringify(stored) + "\n");
|
|
25168
25614
|
return seq;
|
|
25169
25615
|
}
|
|
25170
25616
|
read(artifactId, fromSeq) {
|
|
@@ -25180,7 +25626,7 @@ var init_dev_store = __esm({
|
|
|
25180
25626
|
DirBlobStore = class {
|
|
25181
25627
|
constructor(dir) {
|
|
25182
25628
|
this.dir = dir;
|
|
25183
|
-
|
|
25629
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
25184
25630
|
}
|
|
25185
25631
|
fileOf(hash) {
|
|
25186
25632
|
return path.join(this.dir, hash);
|
|
@@ -25188,31 +25634,31 @@ var init_dev_store = __esm({
|
|
|
25188
25634
|
async put(bytes) {
|
|
25189
25635
|
const hash = (0, import_node_crypto4.createHash)("sha256").update(bytes).digest("hex");
|
|
25190
25636
|
const file = this.fileOf(hash);
|
|
25191
|
-
if (!
|
|
25637
|
+
if (!fs3.existsSync(file)) fs3.writeFileSync(file, bytes);
|
|
25192
25638
|
return hash;
|
|
25193
25639
|
}
|
|
25194
25640
|
async meta(hash) {
|
|
25195
25641
|
const file = this.fileOf(hash);
|
|
25196
|
-
if (!
|
|
25197
|
-
const size =
|
|
25198
|
-
const fd =
|
|
25642
|
+
if (!fs3.existsSync(file)) return null;
|
|
25643
|
+
const size = fs3.statSync(file).size;
|
|
25644
|
+
const fd = fs3.openSync(file, "r");
|
|
25199
25645
|
const head = Buffer.alloc(16);
|
|
25200
|
-
|
|
25201
|
-
|
|
25646
|
+
fs3.readSync(fd, head, 0, 16, 0);
|
|
25647
|
+
fs3.closeSync(fd);
|
|
25202
25648
|
const contentType = sniffContentType(new Uint8Array(head));
|
|
25203
25649
|
return { size, ...contentType ? { contentType } : {} };
|
|
25204
25650
|
}
|
|
25205
25651
|
async get(hash) {
|
|
25206
25652
|
const file = this.fileOf(hash);
|
|
25207
|
-
if (!
|
|
25208
|
-
return new Uint8Array(
|
|
25653
|
+
if (!fs3.existsSync(file)) throw new BlobNotFoundError(hash);
|
|
25654
|
+
return new Uint8Array(fs3.readFileSync(file));
|
|
25209
25655
|
}
|
|
25210
25656
|
async has(hash) {
|
|
25211
|
-
return
|
|
25657
|
+
return fs3.existsSync(this.fileOf(hash));
|
|
25212
25658
|
}
|
|
25213
25659
|
async sweep(reachable) {
|
|
25214
|
-
for (const name of
|
|
25215
|
-
if (!reachable.has(name))
|
|
25660
|
+
for (const name of fs3.readdirSync(this.dir)) {
|
|
25661
|
+
if (!reachable.has(name)) fs3.unlinkSync(this.fileOf(name));
|
|
25216
25662
|
}
|
|
25217
25663
|
}
|
|
25218
25664
|
};
|
|
@@ -25238,7 +25684,7 @@ var init_dev_store = __esm({
|
|
|
25238
25684
|
this.file = file;
|
|
25239
25685
|
}
|
|
25240
25686
|
static async open(file) {
|
|
25241
|
-
const seed =
|
|
25687
|
+
const seed = fs3.existsSync(file) ? JSON.parse(fs3.readFileSync(file, "utf8")) : [];
|
|
25242
25688
|
return new _FileTypeRegistryStore(file, seed);
|
|
25243
25689
|
}
|
|
25244
25690
|
async put(def) {
|
|
@@ -25251,8 +25697,8 @@ var init_dev_store = __esm({
|
|
|
25251
25697
|
}
|
|
25252
25698
|
async save() {
|
|
25253
25699
|
const tmp = `${this.file}.tmp`;
|
|
25254
|
-
|
|
25255
|
-
|
|
25700
|
+
fs3.writeFileSync(tmp, JSON.stringify(await this.list(), null, 2));
|
|
25701
|
+
fs3.renameSync(tmp, this.file);
|
|
25256
25702
|
}
|
|
25257
25703
|
};
|
|
25258
25704
|
FileRegistryStore = class _FileRegistryStore extends MemoryRegistryStore {
|
|
@@ -25270,8 +25716,8 @@ var init_dev_store = __esm({
|
|
|
25270
25716
|
}
|
|
25271
25717
|
static async open(file) {
|
|
25272
25718
|
const store = new _FileRegistryStore(file);
|
|
25273
|
-
if (
|
|
25274
|
-
const snap = JSON.parse(
|
|
25719
|
+
if (fs3.existsSync(file)) {
|
|
25720
|
+
const snap = JSON.parse(fs3.readFileSync(file, "utf8"));
|
|
25275
25721
|
for (const a of snap.actors) await MemoryRegistryStore.prototype.upsertActor.call(store, a);
|
|
25276
25722
|
for (const c of snap.configs) await MemoryRegistryStore.prototype.appendConfig.call(store, c);
|
|
25277
25723
|
for (const t of snap.teams) await MemoryRegistryStore.prototype.upsertTeam.call(store, t);
|
|
@@ -25308,7 +25754,7 @@ var init_dev_store = __esm({
|
|
|
25308
25754
|
installedSkills,
|
|
25309
25755
|
skillFiles: skillFilesEntries
|
|
25310
25756
|
};
|
|
25311
|
-
|
|
25757
|
+
fs3.writeFileSync(this.file, JSON.stringify(snap, null, 2));
|
|
25312
25758
|
}
|
|
25313
25759
|
};
|
|
25314
25760
|
FileProjectStateStore = class _FileProjectStateStore extends MemoryProjectStateStore {
|
|
@@ -25318,8 +25764,8 @@ var init_dev_store = __esm({
|
|
|
25318
25764
|
}
|
|
25319
25765
|
static async open(file) {
|
|
25320
25766
|
const store = new _FileProjectStateStore(file);
|
|
25321
|
-
if (
|
|
25322
|
-
const snap = JSON.parse(
|
|
25767
|
+
if (fs3.existsSync(file)) {
|
|
25768
|
+
const snap = JSON.parse(fs3.readFileSync(file, "utf8"));
|
|
25323
25769
|
for (const project of snap.projects ?? []) await MemoryProjectStateStore.prototype.upsertProject.call(store, project);
|
|
25324
25770
|
const membersByProject = /* @__PURE__ */ new Map();
|
|
25325
25771
|
for (const member of snap.members ?? []) {
|
|
@@ -25331,8 +25777,8 @@ var init_dev_store = __esm({
|
|
|
25331
25777
|
await MemoryProjectStateStore.prototype.replaceProjectMembers.call(store, projectId, members);
|
|
25332
25778
|
}
|
|
25333
25779
|
} else {
|
|
25334
|
-
|
|
25335
|
-
|
|
25780
|
+
fs3.mkdirSync(path.dirname(file), { recursive: true });
|
|
25781
|
+
fs3.writeFileSync(file, JSON.stringify({ projects: [], members: [] }, null, 2));
|
|
25336
25782
|
}
|
|
25337
25783
|
return store;
|
|
25338
25784
|
}
|
|
@@ -25351,7 +25797,7 @@ var init_dev_store = __esm({
|
|
|
25351
25797
|
async save() {
|
|
25352
25798
|
const projects = await this.listProjects();
|
|
25353
25799
|
const members = (await Promise.all(projects.map((project) => this.listProjectMembers(project.id)))).flat();
|
|
25354
|
-
|
|
25800
|
+
fs3.writeFileSync(this.file, JSON.stringify({ projects, members }, null, 2));
|
|
25355
25801
|
}
|
|
25356
25802
|
};
|
|
25357
25803
|
FileProjectDocumentStore = class _FileProjectDocumentStore extends MemoryProjectDocumentStore {
|
|
@@ -25361,8 +25807,8 @@ var init_dev_store = __esm({
|
|
|
25361
25807
|
}
|
|
25362
25808
|
static async open(file) {
|
|
25363
25809
|
const store = new _FileProjectDocumentStore(file);
|
|
25364
|
-
if (
|
|
25365
|
-
const snap = JSON.parse(
|
|
25810
|
+
if (fs3.existsSync(file)) {
|
|
25811
|
+
const snap = JSON.parse(fs3.readFileSync(file, "utf8"));
|
|
25366
25812
|
for (const [projectId, space] of Object.entries(snap.spaces ?? {})) {
|
|
25367
25813
|
store.spaces.set(projectId, {
|
|
25368
25814
|
rootRef: space.rootRef,
|
|
@@ -25376,8 +25822,8 @@ var init_dev_store = __esm({
|
|
|
25376
25822
|
});
|
|
25377
25823
|
}
|
|
25378
25824
|
} else {
|
|
25379
|
-
|
|
25380
|
-
|
|
25825
|
+
fs3.mkdirSync(path.dirname(file), { recursive: true });
|
|
25826
|
+
fs3.writeFileSync(file, JSON.stringify({ spaces: {} }, null, 2));
|
|
25381
25827
|
}
|
|
25382
25828
|
return store;
|
|
25383
25829
|
}
|
|
@@ -25410,7 +25856,7 @@ var init_dev_store = __esm({
|
|
|
25410
25856
|
runStates: [...space.runStates.values()]
|
|
25411
25857
|
};
|
|
25412
25858
|
}
|
|
25413
|
-
|
|
25859
|
+
fs3.writeFileSync(this.file, JSON.stringify({ spaces }, null, 2));
|
|
25414
25860
|
}
|
|
25415
25861
|
};
|
|
25416
25862
|
FileArtifactStateStore = class _FileArtifactStateStore extends MemoryArtifactStateStore {
|
|
@@ -25420,8 +25866,8 @@ var init_dev_store = __esm({
|
|
|
25420
25866
|
}
|
|
25421
25867
|
static async open(file) {
|
|
25422
25868
|
const store = new _FileArtifactStateStore(file);
|
|
25423
|
-
if (
|
|
25424
|
-
const snap = JSON.parse(
|
|
25869
|
+
if (fs3.existsSync(file)) {
|
|
25870
|
+
const snap = JSON.parse(fs3.readFileSync(file, "utf8"));
|
|
25425
25871
|
for (const artifact of snap.artifacts ?? []) await MemoryArtifactStateStore.prototype.upsertArtifact.call(store, artifact);
|
|
25426
25872
|
for (const revision of snap.revisions ?? []) await MemoryArtifactStateStore.prototype.upsertRevision.call(store, revision);
|
|
25427
25873
|
for (const event of snap.events ?? []) await MemoryArtifactStateStore.prototype.appendEvent.call(store, event);
|
|
@@ -25430,8 +25876,8 @@ var init_dev_store = __esm({
|
|
|
25430
25876
|
for (const binding of snap.workspaceBindings ?? []) await MemoryArtifactStateStore.prototype.upsertWorkspaceBinding.call(store, binding);
|
|
25431
25877
|
for (const ws of snap.workspaceDispatchHeld ?? []) await MemoryArtifactStateStore.prototype.setWorkspaceDispatchHold.call(store, ws, true);
|
|
25432
25878
|
} else {
|
|
25433
|
-
|
|
25434
|
-
|
|
25879
|
+
fs3.mkdirSync(path.dirname(file), { recursive: true });
|
|
25880
|
+
fs3.writeFileSync(file, JSON.stringify({ artifacts: [], revisions: [], events: [], annotations: [], nodeOutputs: [], workspaceBindings: [], workspaceDispatchHeld: [] }, null, 2));
|
|
25435
25881
|
}
|
|
25436
25882
|
return store;
|
|
25437
25883
|
}
|
|
@@ -25464,7 +25910,7 @@ var init_dev_store = __esm({
|
|
|
25464
25910
|
await this.save();
|
|
25465
25911
|
}
|
|
25466
25912
|
async save() {
|
|
25467
|
-
|
|
25913
|
+
fs3.writeFileSync(this.file, JSON.stringify(await this.listAll(), null, 2));
|
|
25468
25914
|
}
|
|
25469
25915
|
};
|
|
25470
25916
|
FileTraceStore = class _FileTraceStore extends MemoryTraceStore {
|
|
@@ -25491,20 +25937,20 @@ var init_dev_store = __esm({
|
|
|
25491
25937
|
return path.join(this.eventsDir, `${encodeURIComponent(runId)}.ndjson`);
|
|
25492
25938
|
}
|
|
25493
25939
|
appendLine(file, obj) {
|
|
25494
|
-
|
|
25940
|
+
fs3.appendFileSync(file, JSON.stringify(obj) + "\n");
|
|
25495
25941
|
}
|
|
25496
25942
|
static replay(file, fn) {
|
|
25497
|
-
if (!
|
|
25498
|
-
for (const line of
|
|
25943
|
+
if (!fs3.existsSync(file)) return;
|
|
25944
|
+
for (const line of fs3.readFileSync(file, "utf8").split("\n")) {
|
|
25499
25945
|
if (!line.trim()) continue;
|
|
25500
25946
|
fn(JSON.parse(line));
|
|
25501
25947
|
}
|
|
25502
25948
|
}
|
|
25503
25949
|
static async open(dir) {
|
|
25504
25950
|
const store = new _FileTraceStore(dir);
|
|
25505
|
-
|
|
25951
|
+
fs3.mkdirSync(store.eventsDir, { recursive: true });
|
|
25506
25952
|
_FileTraceStore.replay(store.runsFile, (r) => store.restoreRun(r));
|
|
25507
|
-
for (const name of
|
|
25953
|
+
for (const name of fs3.readdirSync(store.eventsDir)) {
|
|
25508
25954
|
_FileTraceStore.replay(path.join(store.eventsDir, name), (e) => store.restoreEvent(e));
|
|
25509
25955
|
}
|
|
25510
25956
|
_FileTraceStore.replay(store.toolCallsFile, (c) => store.restoreToolCall(c));
|
|
@@ -25549,6 +25995,7 @@ var init_dev_store = __esm({
|
|
|
25549
25995
|
MemoryChatSessionStore = class {
|
|
25550
25996
|
sessions = /* @__PURE__ */ new Map();
|
|
25551
25997
|
messages = /* @__PURE__ */ new Map();
|
|
25998
|
+
sessionWorkOrders = /* @__PURE__ */ new Map();
|
|
25552
25999
|
async createSession(s2) {
|
|
25553
26000
|
this.sessions.set(s2.id, s2);
|
|
25554
26001
|
}
|
|
@@ -25565,6 +26012,7 @@ var init_dev_store = __esm({
|
|
|
25565
26012
|
async deleteSession(id) {
|
|
25566
26013
|
this.sessions.delete(id);
|
|
25567
26014
|
this.messages.delete(id);
|
|
26015
|
+
this.sessionWorkOrders.delete(id);
|
|
25568
26016
|
}
|
|
25569
26017
|
async appendMessage(m2) {
|
|
25570
26018
|
const list = this.messages.get(m2.sessionId) ?? [];
|
|
@@ -25575,16 +26023,24 @@ var init_dev_store = __esm({
|
|
|
25575
26023
|
const list = (this.messages.get(sessionId) ?? []).sort((a, b2) => a.seq - b2.seq);
|
|
25576
26024
|
return limit ? list.slice(-limit) : list;
|
|
25577
26025
|
}
|
|
26026
|
+
async linkWorkOrder(sessionId, workOrderId) {
|
|
26027
|
+
const set = this.sessionWorkOrders.get(sessionId) ?? /* @__PURE__ */ new Set();
|
|
26028
|
+
set.add(workOrderId);
|
|
26029
|
+
this.sessionWorkOrders.set(sessionId, set);
|
|
26030
|
+
}
|
|
26031
|
+
async listWorkOrdersForSession(sessionId) {
|
|
26032
|
+
return [...this.sessionWorkOrders.get(sessionId) ?? []];
|
|
26033
|
+
}
|
|
25578
26034
|
};
|
|
25579
26035
|
}
|
|
25580
26036
|
});
|
|
25581
26037
|
|
|
25582
26038
|
// ../server/src/control-plane-file-store.ts
|
|
25583
|
-
var
|
|
26039
|
+
var fs4, path2, MUTATORS2, FileControlPlaneStore;
|
|
25584
26040
|
var init_control_plane_file_store = __esm({
|
|
25585
26041
|
"../server/src/control-plane-file-store.ts"() {
|
|
25586
26042
|
"use strict";
|
|
25587
|
-
|
|
26043
|
+
fs4 = __toESM(require("node:fs"), 1);
|
|
25588
26044
|
path2 = __toESM(require("node:path"), 1);
|
|
25589
26045
|
init_src3();
|
|
25590
26046
|
MUTATORS2 = [
|
|
@@ -25613,15 +26069,15 @@ var init_control_plane_file_store = __esm({
|
|
|
25613
26069
|
}
|
|
25614
26070
|
static async open(file) {
|
|
25615
26071
|
const store = new _FileControlPlaneStore(file);
|
|
25616
|
-
if (
|
|
25617
|
-
const snap = JSON.parse(
|
|
26072
|
+
if (fs4.existsSync(file)) {
|
|
26073
|
+
const snap = JSON.parse(fs4.readFileSync(file, "utf8"));
|
|
25618
26074
|
for (const c of snap.companies ?? []) await MemoryControlPlaneStore.prototype.createCompany.call(store, c);
|
|
25619
26075
|
for (const m2 of snap.members ?? []) await MemoryControlPlaneStore.prototype.upsertMember.call(store, m2);
|
|
25620
26076
|
for (const inv of snap.invitations ?? []) await MemoryControlPlaneStore.prototype.createInvitation.call(store, inv);
|
|
25621
26077
|
for (const a of snap.accounts ?? []) await MemoryControlPlaneStore.prototype.upsertAccount.call(store, a);
|
|
25622
26078
|
for (const e of snap.accountSecrets ?? []) store.accountSecrets.set(e.accountId, { ...e.secret });
|
|
25623
26079
|
} else {
|
|
25624
|
-
|
|
26080
|
+
fs4.mkdirSync(path2.dirname(file), { recursive: true });
|
|
25625
26081
|
}
|
|
25626
26082
|
return store;
|
|
25627
26083
|
}
|
|
@@ -25642,7 +26098,7 @@ var init_control_plane_file_store = __esm({
|
|
|
25642
26098
|
accounts,
|
|
25643
26099
|
accountSecrets
|
|
25644
26100
|
};
|
|
25645
|
-
|
|
26101
|
+
fs4.writeFileSync(this.file, JSON.stringify(snap, null, 2));
|
|
25646
26102
|
}
|
|
25647
26103
|
};
|
|
25648
26104
|
}
|
|
@@ -25916,7 +26372,7 @@ var init_liveness = __esm({
|
|
|
25916
26372
|
|
|
25917
26373
|
// ../server/src/file-engine.ts
|
|
25918
26374
|
async function openFileEngine(dir, seed = {}) {
|
|
25919
|
-
|
|
26375
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
25920
26376
|
const oplog = await NdjsonOplogStore.open(path3.join(dir, "oplog.ndjson"));
|
|
25921
26377
|
const blobs = new DirBlobStore(path3.join(dir, "blobs"));
|
|
25922
26378
|
const assets = new DirBlobStore(path3.join(dir, "assets"));
|
|
@@ -25937,11 +26393,11 @@ async function openFileEngine(dir, seed = {}) {
|
|
|
25937
26393
|
function companyEngineDirName(companyId) {
|
|
25938
26394
|
return companyId.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
25939
26395
|
}
|
|
25940
|
-
var
|
|
26396
|
+
var fs5, path3;
|
|
25941
26397
|
var init_file_engine = __esm({
|
|
25942
26398
|
"../server/src/file-engine.ts"() {
|
|
25943
26399
|
"use strict";
|
|
25944
|
-
|
|
26400
|
+
fs5 = __toESM(require("node:fs"), 1);
|
|
25945
26401
|
path3 = __toESM(require("node:path"), 1);
|
|
25946
26402
|
init_src2();
|
|
25947
26403
|
init_dev_store();
|
|
@@ -29994,6 +30450,7 @@ var init_service2 = __esm({
|
|
|
29994
30450
|
"use strict";
|
|
29995
30451
|
import_node_crypto5 = require("node:crypto");
|
|
29996
30452
|
init_skill_fetcher();
|
|
30453
|
+
init_identity();
|
|
29997
30454
|
REDACTED_MARKER = "[REDACTED]";
|
|
29998
30455
|
ActorsService = class {
|
|
29999
30456
|
constructor(opts) {
|
|
@@ -30009,11 +30466,19 @@ var init_service2 = __esm({
|
|
|
30009
30466
|
async upsertActor(a, by) {
|
|
30010
30467
|
const existing = await this.opts.store.getActor(a.id);
|
|
30011
30468
|
const roles = a.roles ?? existing?.roles ?? [];
|
|
30012
|
-
const
|
|
30013
|
-
|
|
30014
|
-
|
|
30469
|
+
const record4 = { ...a, roles, createdAt: existing?.createdAt ?? a.createdAt ?? this.now() };
|
|
30470
|
+
if (isActiveCoordinatorAgent(record4)) {
|
|
30471
|
+
const existingCoordinator = (await this.opts.store.listActors({ kind: "agent", status: "active" })).find((actor) => actor.id !== record4.id && isActiveCoordinatorAgent(actor));
|
|
30472
|
+
if (existingCoordinator) {
|
|
30473
|
+
throw new Error(
|
|
30474
|
+
`\u7CFB\u7EDF\u53EA\u80FD\u6709\u4E00\u4E2A\u534F\u8C03\u8005\u667A\u80FD\u4F53\uFF1A\u5DF2\u5B58\u5728 ${existingCoordinator.name}\uFF08${existingCoordinator.id}\uFF09\uFF0C\u4E0D\u80FD\u518D\u521B\u5EFA\u6216\u542F\u7528 ${record4.name}\uFF08${record4.id}\uFF09\u3002`
|
|
30475
|
+
);
|
|
30476
|
+
}
|
|
30477
|
+
}
|
|
30478
|
+
await this.opts.store.upsertActor(record4);
|
|
30479
|
+
this.opts.onRolesChanged?.(record4.id, record4.status === "active" ? roles : []);
|
|
30015
30480
|
await this.audit({ kind: "actor_upsert", actorId: a.id, by, at: this.now(), detail: { status: a.status, roles } });
|
|
30016
|
-
return
|
|
30481
|
+
return record4;
|
|
30017
30482
|
}
|
|
30018
30483
|
/** 删除即停用(2.1 完成标准) */
|
|
30019
30484
|
async disableActor(id, by) {
|
|
@@ -30029,10 +30494,10 @@ var init_service2 = __esm({
|
|
|
30029
30494
|
const a = await this.opts.store.getActor(id);
|
|
30030
30495
|
if (!a) throw new Error(`actor not found: ${id}`);
|
|
30031
30496
|
const { avatar: _drop, ...rest } = a;
|
|
30032
|
-
const
|
|
30033
|
-
await this.opts.store.upsertActor(
|
|
30497
|
+
const record4 = ref === null ? rest : { ...rest, avatar: ref };
|
|
30498
|
+
await this.opts.store.upsertActor(record4);
|
|
30034
30499
|
await this.audit({ kind: "actor_upsert", actorId: id, by, at: this.now(), detail: { avatar: ref ?? "cleared" } });
|
|
30035
|
-
return
|
|
30500
|
+
return record4;
|
|
30036
30501
|
}
|
|
30037
30502
|
/** 设置/清除团队图标引用(同头像约定;不接受 emoji,只接受 blob 引用) */
|
|
30038
30503
|
async setTeamIcon(teamId, ref) {
|
|
@@ -31806,8 +32271,8 @@ var DEFAULT_COMPANY_ID, DEFAULT_COMPANY_SLUG, DEFAULT_COMPANY_NAME;
|
|
|
31806
32271
|
var init_migrate = __esm({
|
|
31807
32272
|
"../server/src/domains/companies/migrate.ts"() {
|
|
31808
32273
|
"use strict";
|
|
31809
|
-
DEFAULT_COMPANY_ID = "company:
|
|
31810
|
-
DEFAULT_COMPANY_SLUG = "
|
|
32274
|
+
DEFAULT_COMPANY_ID = "company:oasis";
|
|
32275
|
+
DEFAULT_COMPANY_SLUG = "oasis";
|
|
31811
32276
|
DEFAULT_COMPANY_NAME = "Oasis \u603B\u90E8";
|
|
31812
32277
|
}
|
|
31813
32278
|
});
|
|
@@ -31833,9 +32298,13 @@ var init_service3 = __esm({
|
|
|
31833
32298
|
CompaniesService = class {
|
|
31834
32299
|
store;
|
|
31835
32300
|
now;
|
|
32301
|
+
sender;
|
|
32302
|
+
consoleUrl;
|
|
31836
32303
|
constructor(opts) {
|
|
31837
32304
|
this.store = opts.store;
|
|
31838
32305
|
this.now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
32306
|
+
this.sender = opts.sender;
|
|
32307
|
+
this.consoleUrl = opts.consoleUrl ?? "/";
|
|
31839
32308
|
}
|
|
31840
32309
|
/** 当前用户所属的公司(按 membership 聚合)。 */
|
|
31841
32310
|
async listForUser(accountId) {
|
|
@@ -31926,6 +32395,16 @@ var init_service3 = __esm({
|
|
|
31926
32395
|
await this.requireCompany(companyId);
|
|
31927
32396
|
return this.store.listMembers(companyId);
|
|
31928
32397
|
}
|
|
32398
|
+
/** 成员视图:每条成员关系附上其账号的 email/name(无账号成员如 agent 为 null)。 */
|
|
32399
|
+
async listMemberViews(companyId) {
|
|
32400
|
+
const members = await this.listMembers(companyId);
|
|
32401
|
+
const out = [];
|
|
32402
|
+
for (const m2 of members) {
|
|
32403
|
+
const acc = await this.store.getAccount(m2.accountId);
|
|
32404
|
+
out.push({ ...m2, email: acc?.email ?? null, name: acc?.name ?? null });
|
|
32405
|
+
}
|
|
32406
|
+
return out;
|
|
32407
|
+
}
|
|
31929
32408
|
/**
|
|
31930
32409
|
* 改成员角色。不变量「至少留 1 个 owner」:把唯一的 owner 降级会被拒。
|
|
31931
32410
|
* 公司/成员不存在则抛 404。
|
|
@@ -31961,7 +32440,7 @@ var init_service3 = __esm({
|
|
|
31961
32440
|
* 公司不存在则抛 404。
|
|
31962
32441
|
*/
|
|
31963
32442
|
async createInvitation(companyId, email2, role) {
|
|
31964
|
-
const mail = email2?.trim() ?? "";
|
|
32443
|
+
const mail = email2?.trim().toLowerCase() ?? "";
|
|
31965
32444
|
if (!mail) throw new CompanyError(400, "BAD_REQUEST", "\u7F3A\u5C11 email");
|
|
31966
32445
|
if (!INVITABLE_ROLES.includes(role)) throw new CompanyError(400, "BAD_ROLE", `role \u987B\u4E3A ${INVITABLE_ROLES.join(" | ")}\uFF08owner \u4E0D\u7ECF\u9080\u8BF7\u4EA7\u751F\uFF09`);
|
|
31967
32446
|
await this.requireCompany(companyId);
|
|
@@ -31979,11 +32458,16 @@ var init_service3 = __esm({
|
|
|
31979
32458
|
expiresAt: new Date(nowMs + INVITATION_TTL_MS).toISOString()
|
|
31980
32459
|
};
|
|
31981
32460
|
await this.store.createInvitation(invitation);
|
|
32461
|
+
if (this.sender) {
|
|
32462
|
+
const company = await this.store.getCompany(companyId);
|
|
32463
|
+
const companyName = company?.name ?? companyId;
|
|
32464
|
+
void this.sender.sendInvitation(mail, { companyName, consoleUrl: this.consoleUrl }).catch((e) => console.error(`[companies] \u9080\u8BF7\u90AE\u4EF6\u53D1\u9001\u5931\u8D25 ${mail}: ${e instanceof Error ? e.message : String(e)}`));
|
|
32465
|
+
}
|
|
31982
32466
|
return invitation;
|
|
31983
32467
|
}
|
|
31984
32468
|
/** 列出发给某邮箱的有效(pending 未过期)邀请;顺带把过期的 pending 落为 expired。 */
|
|
31985
32469
|
async listInvitationsForEmail(email2) {
|
|
31986
|
-
const all = await this.store.listInvitations({ email: email2 });
|
|
32470
|
+
const all = await this.store.listInvitations({ email: email2.trim().toLowerCase() });
|
|
31987
32471
|
const out = [];
|
|
31988
32472
|
for (const inv of all) {
|
|
31989
32473
|
if (inv.status !== "pending") continue;
|
|
@@ -32077,7 +32561,7 @@ function companiesDomain(opts) {
|
|
|
32077
32561
|
return { status: 200, body: { ok: true } };
|
|
32078
32562
|
});
|
|
32079
32563
|
router.get("/api/companies/:id/members", async (req) => {
|
|
32080
|
-
const items = await service.
|
|
32564
|
+
const items = await service.listMemberViews(req.params.id).catch(mapErr);
|
|
32081
32565
|
return { status: 200, body: { items } };
|
|
32082
32566
|
});
|
|
32083
32567
|
router.patch("/api/companies/:id/members/:accountId", async (req) => {
|
|
@@ -32124,7 +32608,12 @@ var init_routes3 = __esm({
|
|
|
32124
32608
|
|
|
32125
32609
|
// ../server/src/domains/companies/index.ts
|
|
32126
32610
|
function createCompaniesDomain(opts) {
|
|
32127
|
-
const service = new CompaniesService({
|
|
32611
|
+
const service = new CompaniesService({
|
|
32612
|
+
store: opts.store,
|
|
32613
|
+
...opts.now !== void 0 ? { now: opts.now } : {},
|
|
32614
|
+
...opts.sender !== void 0 ? { sender: opts.sender } : {},
|
|
32615
|
+
...opts.consoleUrl !== void 0 ? { consoleUrl: opts.consoleUrl } : {}
|
|
32616
|
+
});
|
|
32128
32617
|
const currentUser = opts.currentUser ?? makeCurrentUser(opts.store);
|
|
32129
32618
|
return { service, register: companiesDomain({ service, currentUser, ...opts.blobs !== void 0 ? { blobs: opts.blobs } : {} }), currentUser };
|
|
32130
32619
|
}
|
|
@@ -32227,6 +32716,14 @@ function parseCookies(header) {
|
|
|
32227
32716
|
}
|
|
32228
32717
|
return out;
|
|
32229
32718
|
}
|
|
32719
|
+
function looksLikeEmail(s2) {
|
|
32720
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s2);
|
|
32721
|
+
}
|
|
32722
|
+
function maskEmail(email2) {
|
|
32723
|
+
const at = email2.indexOf("@");
|
|
32724
|
+
if (at <= 0) return "***";
|
|
32725
|
+
return `${email2[0]}***${email2.slice(at)}`;
|
|
32726
|
+
}
|
|
32230
32727
|
function createAuth(opts) {
|
|
32231
32728
|
const now = opts.now ?? (() => Date.now());
|
|
32232
32729
|
const cookieName = opts.cookieName ?? "oasis_auth";
|
|
@@ -32235,7 +32732,6 @@ function createAuth(opts) {
|
|
|
32235
32732
|
const maxAttempts = opts.maxCodeAttempts ?? 5;
|
|
32236
32733
|
const resendThrottleMs = opts.resendThrottleMs ?? 30 * 1e3;
|
|
32237
32734
|
const { store, sender, jwtSecret } = opts;
|
|
32238
|
-
const hasPendingInvite = async (email2) => (await store.listInvitations({ email: email2, status: "pending" })).some((inv) => Date.parse(inv.expiresAt) > now());
|
|
32239
32735
|
const sessionCookie = (accountId, tokenVersion) => {
|
|
32240
32736
|
const t = now();
|
|
32241
32737
|
const jwt = signSession({ sub: accountId, tokenVersion, iat: t, exp: t + sessionTtlMs }, jwtSecret);
|
|
@@ -32292,26 +32788,23 @@ function createAuth(opts) {
|
|
|
32292
32788
|
if (method !== "POST") return { status: 405, body: { error: { code: "METHOD_NOT_ALLOWED", message: method } } };
|
|
32293
32789
|
if (route === "request-code") {
|
|
32294
32790
|
const email2 = typeof parsed.email === "string" ? parsed.email.trim().toLowerCase() : "";
|
|
32295
|
-
if (email2) {
|
|
32296
|
-
const
|
|
32297
|
-
const
|
|
32298
|
-
if (
|
|
32299
|
-
const
|
|
32300
|
-
const
|
|
32301
|
-
|
|
32302
|
-
|
|
32303
|
-
|
|
32304
|
-
|
|
32305
|
-
|
|
32306
|
-
|
|
32307
|
-
|
|
32308
|
-
|
|
32309
|
-
|
|
32310
|
-
|
|
32311
|
-
|
|
32312
|
-
await sender.sendCode(email2, code);
|
|
32313
|
-
} catch {
|
|
32314
|
-
}
|
|
32791
|
+
if (looksLikeEmail(email2)) {
|
|
32792
|
+
const existing = await store.getVerificationCode(email2);
|
|
32793
|
+
const fresh = existing && Date.parse(existing.createdAt) > now() - resendThrottleMs;
|
|
32794
|
+
if (!fresh) {
|
|
32795
|
+
const code = generateCode();
|
|
32796
|
+
const t = now();
|
|
32797
|
+
await store.putVerificationCode({
|
|
32798
|
+
email: email2,
|
|
32799
|
+
codeHash: hashCode(code, jwtSecret),
|
|
32800
|
+
createdAt: new Date(t).toISOString(),
|
|
32801
|
+
expiresAt: new Date(t + codeTtlMs).toISOString(),
|
|
32802
|
+
attempts: 0
|
|
32803
|
+
});
|
|
32804
|
+
try {
|
|
32805
|
+
await sender.sendCode(email2, code);
|
|
32806
|
+
} catch (e) {
|
|
32807
|
+
console.error(`[auth] \u9A8C\u8BC1\u7801\u53D1\u9001\u5931\u8D25 ${maskEmail(email2)}: ${e instanceof Error ? e.message : String(e)}`);
|
|
32315
32808
|
}
|
|
32316
32809
|
}
|
|
32317
32810
|
}
|
|
@@ -32334,7 +32827,7 @@ function createAuth(opts) {
|
|
|
32334
32827
|
}
|
|
32335
32828
|
await store.deleteVerificationCode(email2);
|
|
32336
32829
|
let account = await store.getAccountByEmail(email2);
|
|
32337
|
-
if (!account && opts.provisionAccount
|
|
32830
|
+
if (!account && opts.provisionAccount) {
|
|
32338
32831
|
account = await opts.provisionAccount(email2);
|
|
32339
32832
|
}
|
|
32340
32833
|
if (!account) return fail();
|
|
@@ -32381,6 +32874,9 @@ function consoleSender() {
|
|
|
32381
32874
|
return {
|
|
32382
32875
|
async sendCode(email2, code) {
|
|
32383
32876
|
console.log(`[auth] \u9A8C\u8BC1\u7801 \u2192 ${email2}: ${code}`);
|
|
32877
|
+
},
|
|
32878
|
+
async sendInvitation(email2, opts) {
|
|
32879
|
+
console.log(`[auth] \u9080\u8BF7 \u2192 ${email2}: \u52A0\u5165\u300C${opts.companyName}\u300D\uFF0C\u767B\u5F55 ${opts.consoleUrl}`);
|
|
32384
32880
|
}
|
|
32385
32881
|
};
|
|
32386
32882
|
}
|
|
@@ -32402,6 +32898,22 @@ function resendSender(opts) {
|
|
|
32402
32898
|
const detail = await res.text().catch(() => "");
|
|
32403
32899
|
throw new Error(`Resend \u53D1\u9001\u5931\u8D25 ${res.status}: ${detail.slice(0, 200)}`);
|
|
32404
32900
|
}
|
|
32901
|
+
},
|
|
32902
|
+
async sendInvitation(email2, { companyName, consoleUrl }) {
|
|
32903
|
+
const res = await doFetch("https://api.resend.com/emails", {
|
|
32904
|
+
method: "POST",
|
|
32905
|
+
headers: { authorization: `Bearer ${opts.apiKey}`, "content-type": "application/json" },
|
|
32906
|
+
body: JSON.stringify({
|
|
32907
|
+
from: opts.from,
|
|
32908
|
+
to: [email2],
|
|
32909
|
+
subject: `\u4F60\u88AB\u9080\u8BF7\u52A0\u5165\u300C${companyName}\u300D`,
|
|
32910
|
+
html: `<div style="font-family:sans-serif;max-width:440px;margin:0 auto"><h2>\u52A0\u5165\u300C${companyName}\u300D</h2><p>\u4F60\u88AB\u9080\u8BF7\u52A0\u5165 Oasis \u4E0A\u7684\u300C${companyName}\u300D\u3002</p><p style="margin:24px 0"><a href="${consoleUrl}" style="background:#111;color:#fff;padding:10px 20px;border-radius:8px;text-decoration:none">\u767B\u5F55\u5E76\u63A5\u53D7\u9080\u8BF7</a></p><p style="color:#666;font-size:14px">\u7528<strong>\u672C\u90AE\u7BB1</strong>\u767B\u5F55\uFF08\u90AE\u7BB1\u9A8C\u8BC1\u7801\uFF09\uFF0C\u767B\u5F55\u540E\u5728\u516C\u53F8\u5207\u6362\u5904\u5373\u53EF\u770B\u5230\u5E76\u63A5\u53D7\u8FD9\u6761\u9080\u8BF7\u3002\u9080\u8BF7 7 \u5929\u5185\u6709\u6548\u3002</p><p style="color:#666;font-size:13px">\u94FE\u63A5\u6253\u4E0D\u5F00\u5C31\u76F4\u63A5\u8BBF\u95EE\uFF1A${consoleUrl}</p></div>`
|
|
32911
|
+
})
|
|
32912
|
+
});
|
|
32913
|
+
if (!res.ok) {
|
|
32914
|
+
const detail = await res.text().catch(() => "");
|
|
32915
|
+
throw new Error(`Resend \u9080\u8BF7\u53D1\u9001\u5931\u8D25 ${res.status}: ${detail.slice(0, 200)}`);
|
|
32916
|
+
}
|
|
32405
32917
|
}
|
|
32406
32918
|
};
|
|
32407
32919
|
}
|
|
@@ -32484,6 +32996,7 @@ After=network.target
|
|
|
32484
32996
|
|
|
32485
32997
|
[Service]
|
|
32486
32998
|
ExecStart=$NODE_CMD node --server-ws ${p2.gatewayUrl} --dir $NODE_DIR
|
|
32999
|
+
Environment=PATH=$PATH:$HOME/.npm-global/bin:$HOME/.local/bin:/usr/local/bin
|
|
32487
33000
|
WorkingDirectory=$OASIS_DIR
|
|
32488
33001
|
Restart=on-failure
|
|
32489
33002
|
RestartSec=5
|
|
@@ -32876,11 +33389,11 @@ var init_types3 = __esm({
|
|
|
32876
33389
|
});
|
|
32877
33390
|
|
|
32878
33391
|
// ../server/src/node-store.ts
|
|
32879
|
-
var
|
|
33392
|
+
var fs6, MemoryNodeStore, FileNodeStore;
|
|
32880
33393
|
var init_node_store = __esm({
|
|
32881
33394
|
"../server/src/node-store.ts"() {
|
|
32882
33395
|
"use strict";
|
|
32883
|
-
|
|
33396
|
+
fs6 = __toESM(require("node:fs"), 1);
|
|
32884
33397
|
MemoryNodeStore = class {
|
|
32885
33398
|
nodes = /* @__PURE__ */ new Map();
|
|
32886
33399
|
runtimes = /* @__PURE__ */ new Map();
|
|
@@ -32961,16 +33474,16 @@ var init_node_store = __esm({
|
|
|
32961
33474
|
static async open(file) {
|
|
32962
33475
|
const s2 = new _FileNodeStore(file);
|
|
32963
33476
|
try {
|
|
32964
|
-
const snap = JSON.parse(
|
|
33477
|
+
const snap = JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
32965
33478
|
for (const n of snap.nodes ?? []) s2.nodes.set(n.id, n);
|
|
32966
33479
|
for (const r of snap.runtimes ?? []) s2.runtimes.set(r.id, r);
|
|
32967
33480
|
} catch {
|
|
32968
|
-
|
|
33481
|
+
fs6.writeFileSync(file, JSON.stringify({ nodes: [], runtimes: [] }, null, 2));
|
|
32969
33482
|
}
|
|
32970
33483
|
return s2;
|
|
32971
33484
|
}
|
|
32972
33485
|
flush() {
|
|
32973
|
-
|
|
33486
|
+
fs6.writeFileSync(this.file, JSON.stringify({
|
|
32974
33487
|
nodes: [...this.nodes.values()],
|
|
32975
33488
|
runtimes: [...this.runtimes.values()]
|
|
32976
33489
|
}, null, 2));
|
|
@@ -33018,7 +33531,7 @@ var init_node_store = __esm({
|
|
|
33018
33531
|
});
|
|
33019
33532
|
|
|
33020
33533
|
// ../server/src/domains/collab/workorder-detail.ts
|
|
33021
|
-
function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject, dispatchJournal = [], dispatchPausedOf) {
|
|
33534
|
+
function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject, dispatchJournal = [], dispatchPausedOf, ops = []) {
|
|
33022
33535
|
const arts = [...model.artifacts.values()].filter((a) => a.workspace === workspaceId);
|
|
33023
33536
|
if (arts.length === 0) return null;
|
|
33024
33537
|
const summary = buildWorkorderSummaries(model, resolveActor, resolveProject, dispatchPausedOf).find((w2) => w2.id === workspaceId);
|
|
@@ -33035,13 +33548,16 @@ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject,
|
|
|
33035
33548
|
const runtimeByArtifact = buildRuntimeByArtifact(dispatchJournal, ref);
|
|
33036
33549
|
const nodes = arts.map((a) => {
|
|
33037
33550
|
const info = nodeInfo(model, a);
|
|
33038
|
-
const
|
|
33551
|
+
const observedRuntime = runtimeByArtifact.get(a.id);
|
|
33552
|
+
const runtime = observedRuntime && shouldExposeRuntime(model, a.id, info, observedRuntime) ? observedRuntime : void 0;
|
|
33553
|
+
const baseStage = deriveNodeStage(info);
|
|
33554
|
+
const stage = runtime?.status === "running" && (runtime.action === "produce" || runtime.action === "integrate") && (baseStage === "embryo" || baseStage === "merged" || baseStage === "review") ? "editing" : baseStage;
|
|
33039
33555
|
const lifecycle = lifecycleOf(model, a.id);
|
|
33040
33556
|
return {
|
|
33041
33557
|
id: a.id,
|
|
33042
33558
|
type: a.type,
|
|
33043
33559
|
label: nodeLabel(a),
|
|
33044
|
-
stage
|
|
33560
|
+
stage,
|
|
33045
33561
|
currentRev: a.currentRev,
|
|
33046
33562
|
queue: info.queueLen,
|
|
33047
33563
|
blocked: blockedOf(model, a.id).blocked,
|
|
@@ -33055,6 +33571,7 @@ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject,
|
|
|
33055
33571
|
(a) => a.inputs.map((e) => ({ from: a.id, to: e.to, pinned: e.pinned, required: e.required }))
|
|
33056
33572
|
);
|
|
33057
33573
|
const activity = buildActivity(model, arts, ref);
|
|
33574
|
+
const coordinatorActivity = buildCoordinatorActivity(arts, ops, ref);
|
|
33058
33575
|
const acceptance = buildAcceptance(model, arts);
|
|
33059
33576
|
const planning = planningStatusOf(model, arts);
|
|
33060
33577
|
const spec = workorderSpecOf(model, arts);
|
|
@@ -33062,11 +33579,72 @@ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject,
|
|
|
33062
33579
|
summary,
|
|
33063
33580
|
graph: { nodes, edges },
|
|
33064
33581
|
activity,
|
|
33582
|
+
coordinatorActivity,
|
|
33065
33583
|
acceptance,
|
|
33066
33584
|
...spec?.acceptanceCriteria ? { acceptanceCriteria: spec.acceptanceCriteria } : {},
|
|
33067
33585
|
planning
|
|
33068
33586
|
};
|
|
33069
33587
|
}
|
|
33588
|
+
function buildWorkorderDraftDetail(draft, resolveActor) {
|
|
33589
|
+
const ref = (id) => {
|
|
33590
|
+
const extra = resolveActor?.(id);
|
|
33591
|
+
return { id, ...extra?.name ? { name: extra.name } : {}, ...extra?.role ? { role: extra.role } : {}, ...extra?.avatar ? { avatar: extra.avatar } : {} };
|
|
33592
|
+
};
|
|
33593
|
+
const spawns = draft.plan.ops.filter((op) => op.action === "spawn" && !!op.id);
|
|
33594
|
+
const nodes = spawns.map((op) => ({
|
|
33595
|
+
id: op.id,
|
|
33596
|
+
type: op.type,
|
|
33597
|
+
label: op.title ?? op.description ?? op.id,
|
|
33598
|
+
stage: "embryo",
|
|
33599
|
+
currentRev: null,
|
|
33600
|
+
queue: 0,
|
|
33601
|
+
blocked: false,
|
|
33602
|
+
owner: ref(op.owner ?? draft.draftedBy)
|
|
33603
|
+
}));
|
|
33604
|
+
const edges = [
|
|
33605
|
+
...spawns.flatMap((op) => (op.inputs ?? []).map((inp) => ({ from: op.id, to: inp.to, pinned: null, required: inp.required ?? true }))),
|
|
33606
|
+
...draft.plan.ops.filter((op) => op.action === "link").map((op) => ({ from: op.artifactId, to: op.to, pinned: null, required: op.required ?? true }))
|
|
33607
|
+
];
|
|
33608
|
+
const rootSpawn = spawns.find((op) => op.id === draft.rootArtifactId);
|
|
33609
|
+
const title = rootSpawn?.title ?? rootSpawn?.description ?? draft.workspace;
|
|
33610
|
+
const participantIds = [...new Set(nodes.map((n) => n.owner.id))];
|
|
33611
|
+
const summary = {
|
|
33612
|
+
id: draft.workspace,
|
|
33613
|
+
title,
|
|
33614
|
+
...draft.seed.brief && draft.seed.brief !== title ? { description: draft.seed.brief } : {},
|
|
33615
|
+
...draft.seed.goal ? { goal: draft.seed.goal } : {},
|
|
33616
|
+
stage: "draft",
|
|
33617
|
+
owner: ref(draft.draftedBy),
|
|
33618
|
+
participants: participantIds.map((id) => ref(id)),
|
|
33619
|
+
artifactCount: nodes.length,
|
|
33620
|
+
progress: { concluded: 0, total: nodes.length },
|
|
33621
|
+
updatedAt: draft.at,
|
|
33622
|
+
projectId: null,
|
|
33623
|
+
...draft.seed.dispatch === false ? { dispatchPaused: true } : {}
|
|
33624
|
+
};
|
|
33625
|
+
return {
|
|
33626
|
+
summary,
|
|
33627
|
+
graph: { nodes, edges },
|
|
33628
|
+
activity: [],
|
|
33629
|
+
coordinatorActivity: [],
|
|
33630
|
+
acceptance: [],
|
|
33631
|
+
...draft.seed.acceptanceCriteria ? { acceptanceCriteria: draft.seed.acceptanceCriteria } : {}
|
|
33632
|
+
};
|
|
33633
|
+
}
|
|
33634
|
+
function isRuntimeFailure(runtime) {
|
|
33635
|
+
return runtime.status === "failed" || runtime.status === "fused" || runtime.status === "escalated";
|
|
33636
|
+
}
|
|
33637
|
+
function shouldExposeRuntime(model, artifactId, info, runtime) {
|
|
33638
|
+
if (!isRuntimeFailure(runtime)) return true;
|
|
33639
|
+
if (info.concluded) return false;
|
|
33640
|
+
const lastOpAt = model.lastOpAt.get(artifactId);
|
|
33641
|
+
const runtimeAt = runtime.finishedAt ?? runtime.startedAt;
|
|
33642
|
+
if (!lastOpAt || !runtimeAt) return true;
|
|
33643
|
+
const lastOpTime = Date.parse(lastOpAt);
|
|
33644
|
+
const runtimeTime = Date.parse(runtimeAt);
|
|
33645
|
+
if (!Number.isFinite(lastOpTime) || !Number.isFinite(runtimeTime)) return true;
|
|
33646
|
+
return lastOpTime <= runtimeTime;
|
|
33647
|
+
}
|
|
33070
33648
|
function buildRuntimeByArtifact(entries, ref) {
|
|
33071
33649
|
const byJob = /* @__PURE__ */ new Map();
|
|
33072
33650
|
for (const entry of entries) {
|
|
@@ -33115,7 +33693,16 @@ function buildRuntimeByArtifact(entries, ref) {
|
|
|
33115
33693
|
const byArtifact = /* @__PURE__ */ new Map();
|
|
33116
33694
|
for (const value of byJob.values()) {
|
|
33117
33695
|
const old = byArtifact.get(value.artifactId);
|
|
33118
|
-
if (!old
|
|
33696
|
+
if (!old) {
|
|
33697
|
+
byArtifact.set(value.artifactId, value);
|
|
33698
|
+
continue;
|
|
33699
|
+
}
|
|
33700
|
+
if (old.status === "running" && value.status !== "running") continue;
|
|
33701
|
+
if (old.status !== "running" && value.status === "running") {
|
|
33702
|
+
byArtifact.set(value.artifactId, value);
|
|
33703
|
+
continue;
|
|
33704
|
+
}
|
|
33705
|
+
if (old.at < value.at) byArtifact.set(value.artifactId, value);
|
|
33119
33706
|
}
|
|
33120
33707
|
return new Map([...byArtifact].map(([id, value]) => {
|
|
33121
33708
|
const { at: _at, artifactId: _artifactId, ...runtime } = value;
|
|
@@ -33173,6 +33760,65 @@ function buildActivity(model, arts, ref) {
|
|
|
33173
33760
|
}
|
|
33174
33761
|
return events.sort((x2, y) => x2.at < y.at ? -1 : x2.at > y.at ? 1 : x2.seq - y.seq);
|
|
33175
33762
|
}
|
|
33763
|
+
function buildCoordinatorActivity(arts, ops, ref) {
|
|
33764
|
+
const inWorkspace = new Set(arts.map((a) => a.id));
|
|
33765
|
+
const firstRevisionAt = ops.filter((op) => op.kind === "propose_revision" && inWorkspace.has(op.artifactId)).map((op) => op.timestamp).sort()[0];
|
|
33766
|
+
const edits = [];
|
|
33767
|
+
for (const op of ops) {
|
|
33768
|
+
if (!inWorkspace.has(op.artifactId)) continue;
|
|
33769
|
+
if (op.actor !== COORDINATOR_ACTOR) continue;
|
|
33770
|
+
if (!COORDINATOR_EDIT_KINDS.has(op.kind)) continue;
|
|
33771
|
+
const kind = op.kind;
|
|
33772
|
+
if ((kind === "spawn_artifact" || kind === "link_input") && (!firstRevisionAt || op.timestamp < firstRevisionAt)) {
|
|
33773
|
+
continue;
|
|
33774
|
+
}
|
|
33775
|
+
const detail = coordinatorEditDetail(kind, op.payload, ref);
|
|
33776
|
+
edits.push({
|
|
33777
|
+
seq: op.seq,
|
|
33778
|
+
at: op.timestamp,
|
|
33779
|
+
actor: ref(op.actor),
|
|
33780
|
+
kind,
|
|
33781
|
+
artifactId: op.artifactId,
|
|
33782
|
+
label: COORDINATOR_EDIT_LABEL[kind],
|
|
33783
|
+
...detail ? { detail } : {}
|
|
33784
|
+
});
|
|
33785
|
+
}
|
|
33786
|
+
return edits.sort((x2, y) => x2.at < y.at ? -1 : x2.at > y.at ? 1 : x2.seq - y.seq);
|
|
33787
|
+
}
|
|
33788
|
+
function coordinatorEditDetail(kind, payload, ref) {
|
|
33789
|
+
const str2 = (v2) => typeof v2 === "string" && v2.trim() ? v2 : void 0;
|
|
33790
|
+
switch (kind) {
|
|
33791
|
+
case "link_input":
|
|
33792
|
+
case "unlink_input":
|
|
33793
|
+
return str2(payload.to);
|
|
33794
|
+
case "spawn_artifact":
|
|
33795
|
+
return str2(payload.title) ?? str2(payload.type);
|
|
33796
|
+
case "assign_owner": {
|
|
33797
|
+
const owner = str2(payload.owner);
|
|
33798
|
+
return owner ? ref(owner).name ?? owner : void 0;
|
|
33799
|
+
}
|
|
33800
|
+
case "bump_pin": {
|
|
33801
|
+
const to = str2(payload.to);
|
|
33802
|
+
const pinned = str2(payload.pinned);
|
|
33803
|
+
return to && pinned ? `${to} \u2192 ${pinned}` : to ?? pinned;
|
|
33804
|
+
}
|
|
33805
|
+
case "report_gap":
|
|
33806
|
+
return str2(payload.description);
|
|
33807
|
+
case "edit_artifact": {
|
|
33808
|
+
const changed = ["title", "description", "docType", "fields"].filter((k2) => payload[k2] !== void 0);
|
|
33809
|
+
return changed.length ? changed.join("\u3001") : void 0;
|
|
33810
|
+
}
|
|
33811
|
+
case "seal": {
|
|
33812
|
+
const reason = str2(payload.reason);
|
|
33813
|
+
const note = str2(payload.note);
|
|
33814
|
+
return [reason, note].filter(Boolean).join("\uFF1A") || void 0;
|
|
33815
|
+
}
|
|
33816
|
+
case "reopen":
|
|
33817
|
+
return str2(payload.note);
|
|
33818
|
+
default:
|
|
33819
|
+
return void 0;
|
|
33820
|
+
}
|
|
33821
|
+
}
|
|
33176
33822
|
function buildAcceptance(model, arts) {
|
|
33177
33823
|
return arts.map((a) => ({
|
|
33178
33824
|
artifactId: a.id,
|
|
@@ -33180,12 +33826,36 @@ function buildAcceptance(model, arts) {
|
|
|
33180
33826
|
state: isConcluded(model, a.id) ? "done" : a.currentRev ? "active" : "pending"
|
|
33181
33827
|
}));
|
|
33182
33828
|
}
|
|
33829
|
+
var COORDINATOR_EDIT_KINDS, COORDINATOR_EDIT_LABEL, COORDINATOR_ACTOR;
|
|
33183
33830
|
var init_workorder_detail = __esm({
|
|
33184
33831
|
"../server/src/domains/collab/workorder-detail.ts"() {
|
|
33185
33832
|
"use strict";
|
|
33186
33833
|
init_src2();
|
|
33187
33834
|
init_node_state();
|
|
33188
33835
|
init_workorders();
|
|
33836
|
+
COORDINATOR_EDIT_KINDS = /* @__PURE__ */ new Set([
|
|
33837
|
+
"link_input",
|
|
33838
|
+
"unlink_input",
|
|
33839
|
+
"spawn_artifact",
|
|
33840
|
+
"assign_owner",
|
|
33841
|
+
"bump_pin",
|
|
33842
|
+
"report_gap",
|
|
33843
|
+
"edit_artifact",
|
|
33844
|
+
"seal",
|
|
33845
|
+
"reopen"
|
|
33846
|
+
]);
|
|
33847
|
+
COORDINATOR_EDIT_LABEL = {
|
|
33848
|
+
link_input: "\u63A5\u5165\u4F9D\u8D56",
|
|
33849
|
+
unlink_input: "\u79FB\u9664\u4F9D\u8D56",
|
|
33850
|
+
spawn_artifact: "\u65B0\u5EFA\u4EA4\u4ED8\u7269",
|
|
33851
|
+
assign_owner: "\u6539\u6D3E",
|
|
33852
|
+
bump_pin: "\u89E6\u53D1\u8FD4\u5DE5",
|
|
33853
|
+
report_gap: "\u7559\u7F3A\u53E3",
|
|
33854
|
+
edit_artifact: "\u6539\u4EA4\u4ED8\u7269\u4FE1\u606F",
|
|
33855
|
+
seal: "\u5C01\u5B58",
|
|
33856
|
+
reopen: "\u91CD\u542F"
|
|
33857
|
+
};
|
|
33858
|
+
COORDINATOR_ACTOR = "actor:agent:coordinator";
|
|
33189
33859
|
}
|
|
33190
33860
|
});
|
|
33191
33861
|
|
|
@@ -33235,7 +33905,7 @@ function buildInbox(model, me) {
|
|
|
33235
33905
|
if (gate?.gate && gate.gate.eligible.includes(me)) {
|
|
33236
33906
|
const voted = (model.reviews.get(gate.head) ?? []).some((v2) => v2.author === me);
|
|
33237
33907
|
if (!voted) {
|
|
33238
|
-
out.push({ kind: "review-request", artifactId: a.id, workspace: ws,
|
|
33908
|
+
out.push({ kind: "review-request", artifactId: a.id, workspace: ws, since: gate.at, actionRequired: true, summary: `\u5F85\u4F60\u5BA1\u6279\uFF1A${label}\uFF08quorum=${gate.gate.quorum}\uFF09` });
|
|
33239
33909
|
continue;
|
|
33240
33910
|
}
|
|
33241
33911
|
}
|
|
@@ -33442,15 +34112,17 @@ function collabDomain(opts) {
|
|
|
33442
34112
|
return { status: 200, body: { items: buildWorkorderSummaries(kernel.model, resolve3, resolveProject, dispatchPausedOf) } };
|
|
33443
34113
|
});
|
|
33444
34114
|
router.get("/api/workorders/:id", async (req) => {
|
|
33445
|
-
const { kernel, registry: registry2, artifacts } = await resolveCtx(req.auth.companyId);
|
|
34115
|
+
const { kernel, oplog, registry: registry2, artifacts } = await resolveCtx(req.auth.companyId);
|
|
33446
34116
|
const resolve3 = await buildResolver(registry2);
|
|
33447
34117
|
const resolveProject = await buildProjectResolver(artifacts);
|
|
33448
34118
|
const dispatchPausedOf = await buildDispatchPausedResolver(artifacts);
|
|
33449
|
-
const
|
|
33450
|
-
|
|
33451
|
-
|
|
33452
|
-
}
|
|
33453
|
-
|
|
34119
|
+
const wsArts = [...kernel.model.artifacts.values()].filter((a) => a.workspace === req.params.id);
|
|
34120
|
+
const ops = (await Promise.all(wsArts.map((a) => oplog.read(a.id)))).flat();
|
|
34121
|
+
const detail = buildWorkorderDetail(kernel.model, req.params.id, resolve3, resolveProject, opts.dispatchJournal?.() ?? [], dispatchPausedOf, ops);
|
|
34122
|
+
if (detail) return { status: 200, body: detail };
|
|
34123
|
+
const draft = opts.workorderDrafts?.forWorkspace(req.params.id);
|
|
34124
|
+
if (draft) return { status: 200, body: buildWorkorderDraftDetail(draft, resolve3) };
|
|
34125
|
+
return { status: 404, body: { error: { code: "not_found", message: `\u5DE5\u5355\u4E0D\u5B58\u5728: ${req.params.id}` } } };
|
|
33454
34126
|
});
|
|
33455
34127
|
router.post("/api/workorders/:id/dispatch", async (req) => {
|
|
33456
34128
|
const { kernel, artifacts } = await resolveCtx(req.auth.companyId);
|
|
@@ -33473,7 +34145,7 @@ function collabDomain(opts) {
|
|
|
33473
34145
|
const q = req.query;
|
|
33474
34146
|
const audit = {};
|
|
33475
34147
|
const actor = q.get("actor");
|
|
33476
|
-
|
|
34148
|
+
audit.actor = actor && actor !== "me" ? actor : req.auth.actor;
|
|
33477
34149
|
const kind = q.get("kind");
|
|
33478
34150
|
if (kind) audit.kind = kind;
|
|
33479
34151
|
const from = q.get("from");
|
|
@@ -33537,12 +34209,75 @@ var init_collab = __esm({
|
|
|
33537
34209
|
});
|
|
33538
34210
|
|
|
33539
34211
|
// ../server/src/domains/trace/service.ts
|
|
33540
|
-
|
|
34212
|
+
function rawObj(event) {
|
|
34213
|
+
const payload = event.payload;
|
|
34214
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
|
|
34215
|
+
const raw = payload["raw"];
|
|
34216
|
+
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
|
|
34217
|
+
}
|
|
34218
|
+
function payloadObj(event) {
|
|
34219
|
+
const payload = event.payload;
|
|
34220
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : null;
|
|
34221
|
+
}
|
|
34222
|
+
function modelChunkKind(event) {
|
|
34223
|
+
if (event.stream !== "model") return null;
|
|
34224
|
+
const payload = payloadObj(event);
|
|
34225
|
+
const kind = payload?.["kind"] ?? rawObj(event)?.["kind"];
|
|
34226
|
+
if (kind === "message" || kind === "thought") return kind;
|
|
34227
|
+
return null;
|
|
34228
|
+
}
|
|
34229
|
+
function modelText(event) {
|
|
34230
|
+
const raw = rawObj(event);
|
|
34231
|
+
const text = raw?.["text"];
|
|
34232
|
+
if (typeof text === "string") return text;
|
|
34233
|
+
return typeof event.message === "string" ? event.message : null;
|
|
34234
|
+
}
|
|
34235
|
+
function eventTimeMs(event) {
|
|
34236
|
+
const t = Date.parse(event.startedAt ?? event.createdAt);
|
|
34237
|
+
return Number.isFinite(t) ? t : null;
|
|
34238
|
+
}
|
|
34239
|
+
function eventEndTimeMs(event) {
|
|
34240
|
+
const t = Date.parse(event.endedAt ?? event.startedAt ?? event.createdAt);
|
|
34241
|
+
return Number.isFinite(t) ? t : null;
|
|
34242
|
+
}
|
|
34243
|
+
function canCompactModelChunk(prev, next) {
|
|
34244
|
+
const kind = modelChunkKind(prev);
|
|
34245
|
+
if (!kind || kind !== modelChunkKind(next)) return false;
|
|
34246
|
+
const a = eventEndTimeMs(prev);
|
|
34247
|
+
const b2 = eventTimeMs(next);
|
|
34248
|
+
if (a === null || b2 === null) return false;
|
|
34249
|
+
return Math.abs(b2 - a) <= MODEL_CHUNK_COMPACT_WINDOW_MS;
|
|
34250
|
+
}
|
|
34251
|
+
function compactPair(prev, next) {
|
|
34252
|
+
const text = `${modelText(prev) ?? ""}${modelText(next) ?? ""}`;
|
|
34253
|
+
const prevPayload = payloadObj(prev) ?? {};
|
|
34254
|
+
const prevRaw = rawObj(prev) ?? {};
|
|
34255
|
+
return {
|
|
34256
|
+
...prev,
|
|
34257
|
+
id: next.id,
|
|
34258
|
+
seq: next.seq,
|
|
34259
|
+
message: text.slice(0, 500),
|
|
34260
|
+
payload: { ...prevPayload, raw: { ...prevRaw, text } },
|
|
34261
|
+
endedAt: next.endedAt ?? next.startedAt ?? prev.endedAt
|
|
34262
|
+
};
|
|
34263
|
+
}
|
|
34264
|
+
function compactModelChunks(rows) {
|
|
34265
|
+
const out = [];
|
|
34266
|
+
for (const row of rows) {
|
|
34267
|
+
const prev = out[out.length - 1];
|
|
34268
|
+
if (prev && canCompactModelChunk(prev, row)) out[out.length - 1] = compactPair(prev, row);
|
|
34269
|
+
else out.push(row);
|
|
34270
|
+
}
|
|
34271
|
+
return out;
|
|
34272
|
+
}
|
|
34273
|
+
var EVENT_PAGE_LIMIT, MODEL_CHUNK_COMPACT_WINDOW_MS, MODEL_CHUNK_RAW_MULTIPLIER, TraceService;
|
|
33541
34274
|
var init_service4 = __esm({
|
|
33542
34275
|
"../server/src/domains/trace/service.ts"() {
|
|
33543
34276
|
"use strict";
|
|
33544
34277
|
init_src();
|
|
33545
34278
|
EVENT_PAGE_LIMIT = 200;
|
|
34279
|
+
MODEL_CHUNK_COMPACT_WINDOW_MS = 1500;
|
|
34280
|
+
MODEL_CHUNK_RAW_MULTIPLIER = 20;
|
|
33546
34281
|
TraceService = class {
|
|
33547
34282
|
store;
|
|
33548
34283
|
constructor(opts) {
|
|
@@ -33619,12 +34354,14 @@ var init_service4 = __esm({
|
|
|
33619
34354
|
}
|
|
33620
34355
|
async listEvents(runId, opts) {
|
|
33621
34356
|
const limit = Math.min(opts.limit ?? EVENT_PAGE_LIMIT, EVENT_PAGE_LIMIT);
|
|
34357
|
+
const rawLimit = Math.min(limit * MODEL_CHUNK_RAW_MULTIPLIER + 1, 5e3);
|
|
33622
34358
|
const rows = await this.store.listEvents(runId, {
|
|
33623
34359
|
...opts.afterSeq !== void 0 ? { afterSeq: opts.afterSeq } : {},
|
|
33624
|
-
limit:
|
|
34360
|
+
limit: rawLimit
|
|
33625
34361
|
});
|
|
33626
|
-
const
|
|
33627
|
-
const
|
|
34362
|
+
const compacted = compactModelChunks(rows);
|
|
34363
|
+
const hasMore = rows.length > rawLimit - 1 || compacted.length > limit;
|
|
34364
|
+
const items = compacted.length > limit ? compacted.slice(0, limit) : compacted;
|
|
33628
34365
|
const nextCursor = hasMore ? String(items[items.length - 1].seq) : null;
|
|
33629
34366
|
return { items, nextCursor };
|
|
33630
34367
|
}
|
|
@@ -34190,6 +34927,18 @@ var init_sink = __esm({
|
|
|
34190
34927
|
this.onError = opts.onError ?? (() => {
|
|
34191
34928
|
});
|
|
34192
34929
|
}
|
|
34930
|
+
runtimeKindFor(session) {
|
|
34931
|
+
if (this.runtimeKind !== "auto") return this.runtimeKind;
|
|
34932
|
+
const runtimeKind = session.runtimeKind?.toLowerCase();
|
|
34933
|
+
if (runtimeKind === "codex") return "codex";
|
|
34934
|
+
if (runtimeKind === "hermes") return "hermes";
|
|
34935
|
+
if (runtimeKind === "claude" || runtimeKind === "claude-code") return "claude-code";
|
|
34936
|
+
const id = session.sessionId.toLowerCase();
|
|
34937
|
+
if (id.startsWith("codex-")) return "codex";
|
|
34938
|
+
if (id.startsWith("hermes-")) return "hermes";
|
|
34939
|
+
if (id.startsWith("claude-") || id.startsWith("claudecode-")) return "claude-code";
|
|
34940
|
+
return "custom";
|
|
34941
|
+
}
|
|
34193
34942
|
enqueue(sessionId, step) {
|
|
34194
34943
|
const st = this.sessions.get(sessionId);
|
|
34195
34944
|
if (!st) return;
|
|
@@ -34202,7 +34951,7 @@ var init_sink = __esm({
|
|
|
34202
34951
|
await this.store.createRun({
|
|
34203
34952
|
id: session.sessionId,
|
|
34204
34953
|
actorId: session.actor,
|
|
34205
|
-
runtimeKind: this.
|
|
34954
|
+
runtimeKind: this.runtimeKindFor(session),
|
|
34206
34955
|
triggerKind: "dispatch",
|
|
34207
34956
|
triggerRef: session.jobKey,
|
|
34208
34957
|
jobKey: session.jobKey,
|
|
@@ -34319,6 +35068,69 @@ var init_sink = __esm({
|
|
|
34319
35068
|
}
|
|
34320
35069
|
});
|
|
34321
35070
|
|
|
35071
|
+
// ../server/src/domains/trace/chat-parts.ts
|
|
35072
|
+
function asObj2(v2) {
|
|
35073
|
+
return v2 && typeof v2 === "object" && !Array.isArray(v2) ? v2 : void 0;
|
|
35074
|
+
}
|
|
35075
|
+
function textOf2(v2) {
|
|
35076
|
+
if (typeof v2 === "string") return v2;
|
|
35077
|
+
if (Array.isArray(v2)) return v2.map(textOf2).filter(Boolean).join("\n") || void 0;
|
|
35078
|
+
const o = asObj2(v2);
|
|
35079
|
+
if (o) {
|
|
35080
|
+
if (typeof o.text === "string") return o.text;
|
|
35081
|
+
if (typeof o.content === "string") return o.content;
|
|
35082
|
+
if (typeof o.thinking === "string") return o.thinking;
|
|
35083
|
+
}
|
|
35084
|
+
return void 0;
|
|
35085
|
+
}
|
|
35086
|
+
function truncate(s2, limit = OUTPUT_PREVIEW_LIMIT) {
|
|
35087
|
+
return s2.length > limit ? `${s2.slice(0, limit)}\u2026` : s2;
|
|
35088
|
+
}
|
|
35089
|
+
function deriveChatPartsFromEvents(events) {
|
|
35090
|
+
const parts = [];
|
|
35091
|
+
for (const ev of [...events].sort((a, b2) => a.seq - b2.seq)) {
|
|
35092
|
+
const p2 = asObj2(ev.payload);
|
|
35093
|
+
const kind = p2 && typeof p2.kind === "string" ? p2.kind : void 0;
|
|
35094
|
+
const raw = p2?.raw;
|
|
35095
|
+
if (kind === "thought") {
|
|
35096
|
+
const text = textOf2(raw);
|
|
35097
|
+
if (text) parts.push({ type: "thinking", seq: ev.seq, text });
|
|
35098
|
+
continue;
|
|
35099
|
+
}
|
|
35100
|
+
if (kind === "tool_use") {
|
|
35101
|
+
const ro = asObj2(raw);
|
|
35102
|
+
parts.push({
|
|
35103
|
+
type: "tool_use",
|
|
35104
|
+
seq: ev.seq,
|
|
35105
|
+
toolName: ro && typeof ro.name === "string" ? ro.name : "tool",
|
|
35106
|
+
...ro && ro.input !== void 0 ? { input: ro.input } : {},
|
|
35107
|
+
...ro && typeof ro.id === "string" ? { toolCallId: ro.id } : {}
|
|
35108
|
+
});
|
|
35109
|
+
continue;
|
|
35110
|
+
}
|
|
35111
|
+
if (kind === "tool_result") {
|
|
35112
|
+
const ro = asObj2(raw);
|
|
35113
|
+
const output = textOf2(raw) ?? "";
|
|
35114
|
+
parts.push({
|
|
35115
|
+
type: "tool_result",
|
|
35116
|
+
seq: ev.seq,
|
|
35117
|
+
output: truncate(output),
|
|
35118
|
+
isError: ro?.is_error === true || ro?.isError === true,
|
|
35119
|
+
...ro && typeof ro.tool_use_id === "string" ? { toolCallId: ro.tool_use_id } : {}
|
|
35120
|
+
});
|
|
35121
|
+
continue;
|
|
35122
|
+
}
|
|
35123
|
+
}
|
|
35124
|
+
return parts;
|
|
35125
|
+
}
|
|
35126
|
+
var OUTPUT_PREVIEW_LIMIT;
|
|
35127
|
+
var init_chat_parts = __esm({
|
|
35128
|
+
"../server/src/domains/trace/chat-parts.ts"() {
|
|
35129
|
+
"use strict";
|
|
35130
|
+
OUTPUT_PREVIEW_LIMIT = 4e3;
|
|
35131
|
+
}
|
|
35132
|
+
});
|
|
35133
|
+
|
|
34322
35134
|
// ../server/src/domains/trace/index.ts
|
|
34323
35135
|
function createTraceDomain(opts) {
|
|
34324
35136
|
const service = new TraceService({ store: opts.store });
|
|
@@ -34332,6 +35144,7 @@ var init_trace2 = __esm({
|
|
|
34332
35144
|
init_service4();
|
|
34333
35145
|
init_routes5();
|
|
34334
35146
|
init_sink();
|
|
35147
|
+
init_chat_parts();
|
|
34335
35148
|
}
|
|
34336
35149
|
});
|
|
34337
35150
|
|
|
@@ -34342,6 +35155,15 @@ function createChatSessionsDomain(opts) {
|
|
|
34342
35155
|
const binding = await registry2.getBinding(aiActorId).catch(() => null);
|
|
34343
35156
|
return binding?.nodeId ?? null;
|
|
34344
35157
|
}
|
|
35158
|
+
async function enrichMessages(messages) {
|
|
35159
|
+
if (!opts.trace) return messages;
|
|
35160
|
+
return Promise.all(messages.map(async (m2) => {
|
|
35161
|
+
if (!m2.runId) return m2;
|
|
35162
|
+
const events = await opts.trace.listEvents(m2.runId, {}).catch(() => []);
|
|
35163
|
+
const parts = deriveChatPartsFromEvents(events);
|
|
35164
|
+
return parts.length ? { ...m2, parts } : m2;
|
|
35165
|
+
}));
|
|
35166
|
+
}
|
|
34345
35167
|
return (router) => {
|
|
34346
35168
|
router.get("/api/chat-sessions", async (req) => {
|
|
34347
35169
|
const sessions = await store.listSessions(req.auth.actor);
|
|
@@ -34368,10 +35190,11 @@ function createChatSessionsDomain(opts) {
|
|
|
34368
35190
|
router.get("/api/chat-sessions/:id", async (req) => {
|
|
34369
35191
|
const session = await store.getSession(req.params.id);
|
|
34370
35192
|
if (!session || session.humanActorId !== req.auth.actor) throw new ApiError(404, "NOT_FOUND", "session not found");
|
|
34371
|
-
const [
|
|
35193
|
+
const [rawMessages, curRuntimeId] = await Promise.all([
|
|
34372
35194
|
store.listMessages(session.id),
|
|
34373
35195
|
currentRuntimeId(session.aiActorId)
|
|
34374
35196
|
]);
|
|
35197
|
+
const messages = await enrichMessages(rawMessages);
|
|
34375
35198
|
const detail = { ...session, currentRuntimeId: curRuntimeId, messages };
|
|
34376
35199
|
return { status: 200, body: detail };
|
|
34377
35200
|
});
|
|
@@ -34406,12 +35229,30 @@ function createChatSessionsDomain(opts) {
|
|
|
34406
35229
|
role: body.role,
|
|
34407
35230
|
content: body.content,
|
|
34408
35231
|
seq: existing.length + 1,
|
|
34409
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
35232
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35233
|
+
...body.runId ? { runId: body.runId } : {}
|
|
34410
35234
|
};
|
|
34411
35235
|
await store.appendMessage(msg);
|
|
34412
35236
|
await store.updateSession(req.params.id, { touchedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
34413
35237
|
return { status: 201, body: msg };
|
|
34414
35238
|
});
|
|
35239
|
+
router.get("/api/chat-sessions/:id/work-orders", async (req) => {
|
|
35240
|
+
const session = await store.getSession(req.params.id);
|
|
35241
|
+
if (!session || session.humanActorId !== req.auth.actor) throw new ApiError(404, "NOT_FOUND", "session not found");
|
|
35242
|
+
const workOrderIds = await store.listWorkOrdersForSession(req.params.id);
|
|
35243
|
+
if (!opts.kernel || workOrderIds.length === 0) return { status: 200, body: { items: [] } };
|
|
35244
|
+
const bindings = opts.artifactState ? await opts.artifactState.listWorkspaceBindings() : [];
|
|
35245
|
+
const byWo = new Map(bindings.map((b2) => [b2.workOrderId, b2.projectId]));
|
|
35246
|
+
const actors = opts.kernel ? await registry2.listActors().catch(() => []) : [];
|
|
35247
|
+
const byId = new Map(actors.map((a) => [a.id, a]));
|
|
35248
|
+
const resolver = (id) => {
|
|
35249
|
+
const a = byId.get(id);
|
|
35250
|
+
return a ? { name: a.name } : void 0;
|
|
35251
|
+
};
|
|
35252
|
+
const allSummaries = buildWorkorderSummaries(opts.kernel.model, resolver, (wo) => byWo.get(wo) ?? null);
|
|
35253
|
+
const items = allSummaries.filter((s2) => workOrderIds.includes(s2.id));
|
|
35254
|
+
return { status: 200, body: { items } };
|
|
35255
|
+
});
|
|
34415
35256
|
};
|
|
34416
35257
|
}
|
|
34417
35258
|
var import_node_crypto10;
|
|
@@ -34420,6 +35261,8 @@ var init_chat_sessions = __esm({
|
|
|
34420
35261
|
"use strict";
|
|
34421
35262
|
import_node_crypto10 = require("node:crypto");
|
|
34422
35263
|
init_router();
|
|
35264
|
+
init_workorders();
|
|
35265
|
+
init_chat_parts();
|
|
34423
35266
|
}
|
|
34424
35267
|
});
|
|
34425
35268
|
|
|
@@ -35750,22 +36593,22 @@ var init_engine = __esm({
|
|
|
35750
36593
|
});
|
|
35751
36594
|
|
|
35752
36595
|
// ../server/src/mirror/fs-state.ts
|
|
35753
|
-
var
|
|
36596
|
+
var fs7, FsMirrorStateStore;
|
|
35754
36597
|
var init_fs_state = __esm({
|
|
35755
36598
|
"../server/src/mirror/fs-state.ts"() {
|
|
35756
36599
|
"use strict";
|
|
35757
|
-
|
|
36600
|
+
fs7 = __toESM(require("node:fs"), 1);
|
|
35758
36601
|
FsMirrorStateStore = class {
|
|
35759
36602
|
constructor(file) {
|
|
35760
36603
|
this.file = file;
|
|
35761
|
-
if (
|
|
35762
|
-
const obj = JSON.parse(
|
|
36604
|
+
if (fs7.existsSync(file)) {
|
|
36605
|
+
const obj = JSON.parse(fs7.readFileSync(file, "utf8"));
|
|
35763
36606
|
for (const [k2, v2] of Object.entries(obj)) this.map.set(k2, v2);
|
|
35764
36607
|
}
|
|
35765
36608
|
}
|
|
35766
36609
|
map = /* @__PURE__ */ new Map();
|
|
35767
36610
|
flush() {
|
|
35768
|
-
|
|
36611
|
+
fs7.writeFileSync(this.file, JSON.stringify(Object.fromEntries(this.map), null, 2));
|
|
35769
36612
|
}
|
|
35770
36613
|
async get(artifactId) {
|
|
35771
36614
|
return this.map.get(artifactId) ?? null;
|
|
@@ -35782,12 +36625,12 @@ var init_fs_state = __esm({
|
|
|
35782
36625
|
});
|
|
35783
36626
|
|
|
35784
36627
|
// ../server/src/git/integrate.ts
|
|
35785
|
-
var import_node_child_process2,
|
|
36628
|
+
var import_node_child_process2, fs8, path4, GitRemoteIntegrator;
|
|
35786
36629
|
var init_integrate = __esm({
|
|
35787
36630
|
"../server/src/git/integrate.ts"() {
|
|
35788
36631
|
"use strict";
|
|
35789
36632
|
import_node_child_process2 = require("node:child_process");
|
|
35790
|
-
|
|
36633
|
+
fs8 = __toESM(require("node:fs"), 1);
|
|
35791
36634
|
path4 = __toESM(require("node:path"), 1);
|
|
35792
36635
|
GitRemoteIntegrator = class {
|
|
35793
36636
|
constructor(remote, cloneDir, develop = "develop") {
|
|
@@ -35805,8 +36648,8 @@ var init_integrate = __esm({
|
|
|
35805
36648
|
}
|
|
35806
36649
|
/** 确保本地工作克隆存在并拉到最新远程态。幂等。 */
|
|
35807
36650
|
sync() {
|
|
35808
|
-
if (!
|
|
35809
|
-
|
|
36651
|
+
if (!fs8.existsSync(path4.join(this.cloneDir, ".git"))) {
|
|
36652
|
+
fs8.mkdirSync(path4.dirname(this.cloneDir), { recursive: true });
|
|
35810
36653
|
(0, import_node_child_process2.execFileSync)("git", ["clone", "--quiet", this.remote, this.cloneDir], { env: this.env, maxBuffer: 256 * 1024 * 1024 });
|
|
35811
36654
|
this.git(["config", "user.email", "oasis@local"]);
|
|
35812
36655
|
this.git(["config", "user.name", "oasis"]);
|
|
@@ -35945,13 +36788,14 @@ var init_resolve = __esm({
|
|
|
35945
36788
|
});
|
|
35946
36789
|
|
|
35947
36790
|
// ../server/src/coordinator/worker.ts
|
|
35948
|
-
var import_node_crypto13,
|
|
36791
|
+
var import_node_crypto13, COORDINATOR_ACTOR2, COORDINATOR_SYSTEM_PROMPT, CoordinatorWorker;
|
|
35949
36792
|
var init_worker = __esm({
|
|
35950
36793
|
"../server/src/coordinator/worker.ts"() {
|
|
35951
36794
|
"use strict";
|
|
35952
36795
|
import_node_crypto13 = require("node:crypto");
|
|
35953
36796
|
init_src2();
|
|
35954
|
-
|
|
36797
|
+
init_identity();
|
|
36798
|
+
COORDINATOR_ACTOR2 = LEGACY_COORDINATOR_ACTOR;
|
|
35955
36799
|
COORDINATOR_SYSTEM_PROMPT = `\u4F60\u662F Oasis \u534F\u4F5C\u56FE\u7684**\u534F\u8C03\u8005**\u3002\u4F60\u53EA\u7BA1**\u56FE\u7684\u7ED3\u6784**\uFF08\u8FDE\u8FB9 / \u65B0\u589E / \u53BB\u91CD / \u91CD\u6784 / \u6539\u8282\u70B9\u7684\u6807\u9898\u4E0E brief\uFF09\uFF0C**\u7EDD\u4E0D\u78B0\u5185\u5BB9**\uFF08\u4EA7\u7269\u6B63\u6587\u600E\u4E48\u5199\u662F\u5404 owner \u7684\u6D3B\uFF09\u3002
|
|
35956
36800
|
\u539F\u5219\uFF1A\u2460 **\u53BB\u91CD\u4F18\u5148**\uFF0C\u80FD\u8FDE\u65E2\u6709\u5C31\u522B\u65B0\u5EFA\uFF1B\u2461 \u4F60\u505A\u7ED3\u6784 wiring\u3001\u4E0D\u505A\u5185\u5BB9\u8BC4\u5BA1\uFF1B\u2462 **\u62FF\u4E0D\u51C6\u5C31\u522B\u52A8\u3001\u522B\u786C\u8FDE**\uFF0C\u7ED3\u675F\u65F6\u4E00\u53E5\u8BDD\u8BF4\u660E\u7591\u8651\u3002
|
|
35957
36801
|
|
|
@@ -35961,8 +36805,12 @@ var init_worker = __esm({
|
|
|
35961
36805
|
\u2462 **\u62FF\u4E0D\u51C6\u3001\u6216\u6839\u56E0\u4E0D\u5728\u56FE\u4E0A**\uFF08\u50CF\u8FD0\u884C\u65F6\u5361\u6B7B\u3001\u5185\u5BB9\u5199\u4E0D\u51FA\u3001\u9700\u6C42\u672C\u8EAB\u77DB\u76FE\uFF09\u2192 **\u522B\u52A8\u56FE**\uFF0C\u4E00\u53E5\u8BDD\u8BF4\u6E05\u4F60\u5224\u65AD\u7684\u6839\u56E0 + \u5EFA\u8BAE\u8C01\u5904\u7F6E\uFF0C\u6536\u5DE5\u3002**\u8BEF\u5220\u4E00\u6761\u672C\u8BE5\u7B49\u7684\u6D41\u7A0B\uFF0C\u6BD4\u665A\u6551\u4E00\u4F1A\u513F\u66F4\u7CDF\u3002**
|
|
35962
36806
|
|
|
35963
36807
|
\u3010\u6539\u56FE\uFF1A\u6682\u5B58 \u2192 \u63D0\u4EA4\u3011\u4F60\u80FD\u7528\u7684\u6539\u56FE\u547D\u4EE4\uFF08**\u90FD\u5148\u8FDB\u7F16\u8F91\u7F13\u51B2\u3001\u4E0D\u7ACB\u5373\u6539\u771F\u56FE**\uFF1B\u6539\u5B8C\u7528 \`oasis apply\` \u4E00\u6B21\u6027\u63D0\u4EA4\uFF09\uFF1A
|
|
35964
|
-
|
|
35965
|
-
|
|
36808
|
+
|
|
36809
|
+
> **\u600E\u4E48\u6307\u4E00\u4E2A\u8282\u70B9\uFF08\u5F15\u7528\u5199\u6CD5\uFF09**\uFF1A\u4E0B\u9762\u547D\u4EE4\u91CC\u51E1\u662F\u6307\u8282\u70B9\u7684\u53C2\u6570\uFF08\`<consumer>\`/\`<up>\`/\`<id>\`\uFF09\uFF0C\u4E09\u79CD\u5199\u6CD5\u90FD\u884C\u2014\u2014
|
|
36810
|
+
> **\u88F8 type**\uFF08\u5982 \`dev\`,\u8BE5 type \u53EA\u6709\u4E00\u4E2A\u8282\u70B9\u65F6\u6700\u7701\u4E8B\uFF09/ **\`type:title\`**\uFF08\u5982 \`prd:\u540E\u7AEF\u9700\u6C42\`,\u4E00\u4E2A type \u6709\u591A\u4E2A\u8282\u70B9\u65F6\u7528 title \u533A\u5206\uFF09/ **\u5B8C\u6574 id**\u3002\u7CFB\u7EDF\u81EA\u52A8\u89E3\u6790;**\u5199\u5F97\u592A\u6CDB\u3001\u649E\u5230\u591A\u4E2A**\u4F1A\u628A\u5019\u9009\u5217\u7ED9\u4F60,\u6311\u4E2A\u66F4\u5177\u4F53\u7684\u91CD\u53D1\u5373\u53EF\u3002\u5DE5\u5355\u6839\u7528 \`brief\`\u3002**\u4F60\u4E0D\u7528\u81EA\u5DF1\u7F16 id\u3002**
|
|
36811
|
+
|
|
36812
|
+
- \`oasis link <consumer> --to <up> [--required true]\` \u2014\u2014 \u7ED9 consumer \u52A0\u4E00\u6761\u6307\u5411\u4E0A\u6E38 up \u7684\u4F9D\u8D56\u8FB9\u3002\`<consumer>\` = \u8981\u52A0\u4F9D\u8D56\u7684\u4E0B\u6E38\u8282\u70B9\uFF08\u5F15\u7528,\u89C1\u4E0A\uFF09\uFF1B\`--to <up>\` = \u88AB\u4F9D\u8D56\u7684\u4E0A\u6E38\uFF1B\`--required true\` = \u8FD9\u6761\u662F**\u963B\u585E\u6027**\u4F9D\u8D56\uFF08up \u6CA1 conclude\uFF0Cconsumer \u5C31\u52A8\u4E0D\u4E86\uFF09\uFF0C**\u7701\u7565\u5219\u662F\u8F6F\u4F9D\u8D56**\uFF08\u53EA\u8BB0\u5173\u7CFB\u3001\u4E0D\u963B\u585E\uFF09\u3002\u53BB\u91CD\u65F6\u7528\u5B83\u628A"\u8BE5\u8FDE\u65E2\u6709"\u7684\u8FB9\u8865\u4E0A\u3002
|
|
36813
|
+
- \`oasis spawn --type <t> --title "<\u77ED\u540D>" [--brief "<\u89C4\u683C>"] [--input <ref>[,\u2026]]\` \u2014\u2014 \u65B0\u5EFA\u4E00\u4E2A\u8282\u70B9\u3002**id \u4E0D\u7528\u4F60\u7ED9,\u7CFB\u7EDF\u636E (type,title) \u81EA\u52A8\u6D3E\u751F**\u3002\`--type <t>\` = \u7C7B\u578B\u540D\uFF08**\u5FC5\u586B**\uFF0C\u5148 \`oasis artifact-types\` \u770B\u53EF\u9009\u7C7B\u578B\u4E0E\u7528\u9014\uFF09\uFF1B\`--title\` = \u77ED\u540D\uFF08**\u5FC5\u586B\u3001\u540C type \u4E0B\u552F\u4E00**,\u649E\u540D\u4F1A\u8BA9\u4F60\u6539\uFF09\uFF1B\`--brief\` = \u7ED9\u5B83\u7684\u4EA7\u51FA\u89C4\u683C\uFF08\u5199\u6E05\u8981\u505A\u6210\u4EC0\u4E48\uFF0C\u4F1A\u6210\u4E3A\u8BE5\u8282\u70B9 owner \u7684\u4EFB\u52A1\u4E66\uFF09\uFF1B\`--input\` = \u5B83\u4F9D\u8D56\u54EA\u4E9B\u4E0A\u6E38\uFF08**\u9017\u53F7\u5206\u9694**\u591A\u4E2A,\u6BCF\u4E2A\u6309\u4E0A\u9762\u5F15\u7528\u5199\u6CD5\uFF09\u3002**\u786E\u65E0\u65E2\u6709\u53EF\u8FDE\u3001\u662F\u771F\u65B0\u9700\u6C42\u624D\u7528**\u3002
|
|
35966
36814
|
- \`oasis edit <id> [--title "<\u65B0\u77ED\u540D>"] [--brief "<\u65B0\u89C4\u683C>"]\` \u2014\u2014 \u6539\u4E00\u4E2A**\u5DF2\u5B58\u5728**\u8282\u70B9\u7684\u6807\u9898 / brief\uFF08**\u53EA\u6539\u8FD9\u4E24\u6837\u5143\u6570\u636E\uFF0C\u7EDD\u4E0D\u78B0\u6B63\u6587**\uFF09\u3002\`<id>\` = \u88AB\u6539\u8282\u70B9\uFF1B\`--title\` / \`--brief\` \u7ED9\u54EA\u4E2A\u4F20\u54EA\u4E2A\u3002\u5408\u5E76\u53BB\u91CD\u65F6\u7528\u5B83\u628A\u4FDD\u7559\u4EF6\u7684 brief \u6269\u5199\u3002
|
|
35967
36815
|
- \`oasis unlink <consumer> --to <up>\` \u2014\u2014 \u5220\u6389 consumer \u6307\u5411 up \u7684\u90A3\u6761\u4F9D\u8D56\u8FB9\u3002\`<consumer>\` = \u4E0B\u6E38\uFF1B\`--to <up>\` = \u4E0D\u518D\u4F9D\u8D56\u7684\u4E0A\u6E38\u3002\u6539\u8FDE\uFF08\u65AD\u9519\u8FB9 / \u6539\u8FDE\u5230\u6B63\u786E\u4E0A\u6E38\uFF09\u65F6\u7528\u3002
|
|
35968
36816
|
- \`oasis cancel <id> [--note "<\u539F\u56E0>"]\` \u2014\u2014 **\u5F7B\u5E95\u5E9F\u5F03**\u4E00\u4E2A\u8282\u70B9\uFF1A\u5B83\u4E0D\u518D\u7B97\u6570\uFF0C\u7CFB\u7EDF**\u81EA\u52A8\u7EA7\u8054**\u65AD\u6389\u5B83\u6240\u6709\u4F9D\u8D56\u8FB9\u3001\u5E76\u91CD\u5524\u4E0B\u6E38\u8BA9\u5B83\u4EEC\u53BB\u6389\u5BF9\u5B83\u7684\u9002\u914D\u3002\`<id>\`\uFF08\u4F4D\u7F6E\u53C2\u6570\uFF09= \u88AB\u5E9F\u8282\u70B9\uFF1B\`--note\` = \u4E3A\u4EC0\u4E48\u5E9F\uFF08\u7559\u75D5\uFF09\u3002\u5EFA\u5728\u5DF2\u5E9F\u5730\u57FA\u4E0A\u3001\u6216\u8FD9\u8282\u70B9\u6839\u672C\u4E0D\u8BE5\u5B58\u5728\u65F6\u7528\u3002\u5E9F\u5F03\u540E\u8BE5\u8282\u70B9\u5728 \`oasis status\` \u91CC\u663E\u793A lifecycle=sealed:cancelled\u3002**\u9500\u6BC1\u6027**\u2014\u2014apply \u65F6\u6253\u5305\u6210\u63D0\u6848\u4EA4\u4EBA\u786E\u8BA4\u3002
|
|
@@ -36161,7 +37009,7 @@ var init_worker = __esm({
|
|
|
36161
37009
|
``,
|
|
36162
37010
|
`## \u4F60\u7684\u52A8\u4F5C\uFF08\u6682\u5B58 \u2192 apply\uFF09`,
|
|
36163
37011
|
`- \u67D0\u65E2\u6709\u6EE1\u8DB3\u8FD9\u9700\u6C42 \u2192 \`oasis link ${gap.artifactId} --to <\u65E2\u6709id>\`\uFF0C\`oasis apply --reason "\u2026"\`\uFF08apply \u540E\u81EA\u52A8\u89E3\u8FD9\u6761 gap\uFF09\u3002`,
|
|
36164
|
-
`- \u786E\u65E0\u5339\u914D\u3001\u65B0\u9700\u6C42 \u2192 \`oasis spawn
|
|
37012
|
+
`- \u786E\u65E0\u5339\u914D\u3001\u65B0\u9700\u6C42 \u2192 \`oasis spawn --type <\u7C7B\u578B> --title "<\u77ED\u540D>" --brief "${gap.description}"\`\uFF08\u4E0D\u7528\u7ED9 id\uFF09+ \`oasis link ${gap.artifactId} --to <\u7C7B\u578B>:<\u77ED\u540D>\`\uFF08\u6309 type:title \u5F15\u521A\u5EFA\u7684\uFF09\u2192 apply\u3002`,
|
|
36165
37013
|
`- \u987A\u624B\u53D1\u73B0\u4E24\u4E2A\u4EA7\u7269\u91CD\u590D \u2192 **\u5148\u628A\u4E0A\u9762\u89E3 gap \u90A3\u7B14\u5355\u72EC apply \u6389**\uFF0C\u518D\u53E6\u8D77\u4E00\u7B14\uFF1A\`oasis edit\` \u6269\u4FDD\u7559\u4EF6 brief + \`oasis link\` \u628A\u6D88\u8D39\u8005\u6539\u8FDE\u5230\u4FDD\u7559\u4EF6 + \`oasis cancel\` \u5E9F\u5F03\u4EF6\uFF08\u81EA\u52A8\u65AD\u5B83\u7684\u8FB9\u3001\u91CD\u5524\u88AB\u6539\u8FDE\u7684\u6D88\u8D39\u8005\uFF09\uFF0C\u4E00\u8D77\u6682\u5B58\u518D apply\u3002`,
|
|
36166
37014
|
`- \u62FF\u4E0D\u51C6 \u2192 \u522B\u6682\u5B58\uFF0C\u4E00\u53E5\u8BDD\u8BF4\u660E\u7591\u8651\u6536\u5DE5\u3002`
|
|
36167
37015
|
].join("\n");
|
|
@@ -36221,7 +37069,7 @@ var init_worker = __esm({
|
|
|
36221
37069
|
``,
|
|
36222
37070
|
`## \u4F60\u7684\u52A8\u4F5C\uFF08\u6682\u5B58 \u2192 apply\uFF09`,
|
|
36223
37071
|
`- \u5728**\u7B49\u4E00\u4E2A active\u3001\u6709 head/\u5728\u4EA7\u7684\u4E0A\u6E38** \u2192 **\u4E0D\u662F\u6B7B\u9501\uFF0C\u522B\u52A8**\uFF0C\u6536\u5DE5\uFF08\u53EF \`oasis status <\u4E0A\u6E38>\` \u786E\u8BA4\uFF09\u3002`,
|
|
36224
|
-
`- **\u5B83\u81EA\u5DF1\u7684 gap \u6CA1\u89E3**\u3001\u4F60\u80FD\u5224 \u2192 \`oasis link ${p2.id} --to <\u65E2\u6709>\`\uFF0C\u6216 \`oasis spawn
|
|
37072
|
+
`- **\u5B83\u81EA\u5DF1\u7684 gap \u6CA1\u89E3**\u3001\u4F60\u80FD\u5224 \u2192 \`oasis link ${p2.id} --to <\u65E2\u6709>\`\uFF0C\u6216 \`oasis spawn --type <\u7C7B\u578B> --title "<\u77ED\u540D>" --brief \u2026\`\uFF08\u4E0D\u7ED9 id\uFF09+ \`oasis link ${p2.id} --to <\u7C7B\u578B>:<\u77ED\u540D>\` \u2192 apply\u3002`,
|
|
36225
37073
|
`- **required \u4E0A\u6E38\u5DF2\u5E9F\u5F03 / \u51BB\u7ED3\u3001\u7B49\u4E0D\u5230\u4E86** \u2192 \`oasis link ${p2.id} --to <\u5B58\u6D3B\u4E0A\u6E38>\` \u6539\u8FDE\uFF0C\u6216 \`oasis cancel ${p2.id}\`\uFF08\u8F6C\u4EBA\u5BA1\uFF09\u3002`,
|
|
36226
37074
|
`- **\u67D0 part \u6C38\u4E45\u4EA4\u4ED8\u4E0D\u4E86\u3001\u5361\u6B7B\u6574\u6761\u96C6\u6210** \u2192 \`oasis cancel-part ${p2.id} --part <\u5361\u4F4F\u7684part>\` \u2192 apply\uFF08\u9500\u6BC1\u6027\uFF0C\u8F6C\u4EBA\u5BA1\uFF09\u3002`,
|
|
36227
37075
|
`- \u62FF\u4E0D\u51C6\u3001\u6216\u6839\u56E0\u4E0D\u5728\u56FE\u4E0A \u2192 \u522B\u52A8\uFF0C\u4E00\u53E5\u8BDD\u8BF4\u6E05\u6839\u56E0 + \u5EFA\u8BAE\u8C01\u5904\u7F6E\uFF0C\u6536\u5DE5\u3002`
|
|
@@ -36230,30 +37078,49 @@ var init_worker = __esm({
|
|
|
36230
37078
|
}
|
|
36231
37079
|
/** 唤起一个 bounded 协调者会话跑给定任务(reconcile / 死锁评估共用)。 */
|
|
36232
37080
|
async runCoordinator(artifactId, task, logMsg) {
|
|
36233
|
-
const
|
|
36234
|
-
const
|
|
37081
|
+
const resolved = this.deps.resolveCoordinator ? await this.deps.resolveCoordinator() : null;
|
|
37082
|
+
const actorId = this.deps.resolveCoordinator ? resolved?.actor.id : COORDINATOR_ACTOR2;
|
|
37083
|
+
const binding = resolved?.binding;
|
|
37084
|
+
if (!actorId) {
|
|
37085
|
+
this.deps.log?.(`[coordinator] ${logMsg} \u8DF3\u8FC7\uFF1A\u7CFB\u7EDF\u7F3A\u5C11\u540D\u79F0\u5305\u542B\u201C\u534F\u8C03\u8005\u201D\u6216\u201Ccoordinator\u201D\u7684 active \u667A\u80FD\u4F53\uFF0C\u6216\u7F3A\u5C11 active runtime \u7ED1\u5B9A`);
|
|
37086
|
+
return;
|
|
37087
|
+
}
|
|
37088
|
+
const prov = this.deps.provision ? await this.deps.provision(actorId) : void 0;
|
|
37089
|
+
const limits = { wallClockMs: 5 * 6e4 };
|
|
37090
|
+
const ctx = {
|
|
37091
|
+
actor: actorId,
|
|
37092
|
+
artifactId,
|
|
37093
|
+
action: "coordinate",
|
|
37094
|
+
...binding !== void 0 ? { binding } : {},
|
|
37095
|
+
limits
|
|
37096
|
+
};
|
|
37097
|
+
const token = this.deps.tokenFor(actorId, ctx);
|
|
37098
|
+
const serverUrl = this.deps.serverUrlFor ? await this.deps.serverUrlFor(ctx) : this.deps.serverUrl;
|
|
37099
|
+
const workspace = this.deps.kernel.model.artifacts.get(artifactId)?.workspace;
|
|
36235
37100
|
const job = {
|
|
36236
|
-
actor:
|
|
37101
|
+
actor: actorId,
|
|
36237
37102
|
actorToken: token,
|
|
36238
37103
|
artifactId,
|
|
36239
37104
|
bundle: { files: { "TASK.md": task } },
|
|
36240
37105
|
systemPrompt: COORDINATOR_SYSTEM_PROMPT,
|
|
36241
|
-
server: { url:
|
|
36242
|
-
|
|
37106
|
+
server: { url: serverUrl },
|
|
37107
|
+
...binding ? { binding } : {},
|
|
37108
|
+
limits,
|
|
36243
37109
|
// OASIS_STAGE=1:本会话里 link/spawn/unlink/seal/cancel-part 进编辑缓冲、apply 才提交(react 攒事务)。
|
|
36244
|
-
env: { ...prov?.env ?? {}, OASIS_STAGE: "1" },
|
|
37110
|
+
env: { ...prov?.env ?? {}, OASIS_STAGE: "1", ...workspace ? { OASIS_WORKSPACE: workspace } : {} },
|
|
36245
37111
|
...prov?.wrapperPaths && prov.wrapperPaths.length > 0 ? { wrapperPaths: prov.wrapperPaths } : {}
|
|
36246
37112
|
};
|
|
36247
37113
|
this.deps.log?.(`[coordinator] ${logMsg}`);
|
|
36248
37114
|
const handle = await this.deps.adapter.spawn(job);
|
|
36249
37115
|
await new Promise((resolve3) => handle.onExit(() => resolve3()));
|
|
36250
|
-
|
|
37116
|
+
void this.finalizeStage(token);
|
|
36251
37117
|
}
|
|
36252
37118
|
/** 会话结束兜底:flush 该会话(按 token 隔离)的编辑缓冲;空则 no-op。 */
|
|
36253
37119
|
async finalizeStage(token) {
|
|
36254
37120
|
try {
|
|
36255
37121
|
const res = await fetch(`${this.deps.serverUrl}/api/stage/apply`, {
|
|
36256
37122
|
method: "POST",
|
|
37123
|
+
signal: AbortSignal.timeout(2e3),
|
|
36257
37124
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
36258
37125
|
body: JSON.stringify({ reason: "\uFF08\u534F\u8C03\u8005\u4F1A\u8BDD\u6536\u5DE5\u81EA\u52A8\u63D0\u4EA4\uFF09" })
|
|
36259
37126
|
});
|
|
@@ -36268,6 +37135,115 @@ var init_worker = __esm({
|
|
|
36268
37135
|
}
|
|
36269
37136
|
});
|
|
36270
37137
|
|
|
37138
|
+
// ../server/src/governance/build-workorder-skill.ts
|
|
37139
|
+
async function bootstrapBuildWorkorderSkill(service) {
|
|
37140
|
+
const SYSTEM = "actor:system:bootstrap";
|
|
37141
|
+
const installed = await service.listInstalledSkills();
|
|
37142
|
+
if (!installed.some((s2) => s2.id === BUILD_WORKORDER_SKILL_ID)) {
|
|
37143
|
+
await service.installCustomSkill(
|
|
37144
|
+
{ id: BUILD_WORKORDER_SKILL_ID, name: BUILD_WORKORDER_SKILL_ID, description: DESCRIPTION, source: "custom" },
|
|
37145
|
+
SYSTEM
|
|
37146
|
+
);
|
|
37147
|
+
}
|
|
37148
|
+
await service.putSkillFile(BUILD_WORKORDER_SKILL_ID, "SKILL.md", BUILD_WORKORDER_SKILL_MD);
|
|
37149
|
+
}
|
|
37150
|
+
var BUILD_WORKORDER_SKILL_ID, DESCRIPTION, BUILD_WORKORDER_SKILL_MD;
|
|
37151
|
+
var init_build_workorder_skill = __esm({
|
|
37152
|
+
"../server/src/governance/build-workorder-skill.ts"() {
|
|
37153
|
+
"use strict";
|
|
37154
|
+
BUILD_WORKORDER_SKILL_ID = "build-workorder";
|
|
37155
|
+
DESCRIPTION = "\u628A\u804A\u6E05\u695A\u7684\u9700\u6C42\u843D\u6210\u4E00\u5F20\u5DE5\u5355\uFF08\u534F\u4F5C\u56FE\uFF09\uFF0C\u4EE5\u53CA\u5728\u4EBA\u786E\u8BA4\u524D\u7EE7\u7EED\u6539\u8FD9\u4EFD\u8349\u6848\u3002\u5F53\u7528\u6237\u8981\u5EFA\u5355/\u7ACB\u9879\uFF0C\u6216\u8981\u8C03\u6574\u521A\u751F\u6210\u7684\u5DE5\u5355\u8349\u6848\u65F6\u7528\u672C\u6280\u80FD\uFF1A\u67E5\u91CD oasis workorders \u2192 \u5EFA\u5355 oasis create-workorder \u2192 \u6539\u8349\u6848 oasis spawn/edit/link/cancel\u3002";
|
|
37156
|
+
BUILD_WORKORDER_SKILL_MD = String.raw`---
|
|
37157
|
+
name: build-workorder
|
|
37158
|
+
description: ${DESCRIPTION}
|
|
37159
|
+
---
|
|
37160
|
+
|
|
37161
|
+
# 建单:把对话落成一张工单
|
|
37162
|
+
|
|
37163
|
+
工单 = 一张以产物为中心的协作图:一个根 ´brief´(工单总规格)+ 若干分解节点(prd/research/design/dev/qa…),节点间连依赖边。你把对话敲定的内容**结构化暂存**成一份草案,人在编辑器里看、确认才真创建。
|
|
37164
|
+
|
|
37165
|
+
## 流程
|
|
37166
|
+
1. **查重** ´oasis workorders --search <词>´:有没有类似工单在做/做过,别重复立项。
|
|
37167
|
+
2. **建单** ´oasis create-workorder …´:暂存**草案**(stage=draft,人确认才真创建),命令**直接打印** workspace(工单 id)+ 各节点 id。
|
|
37168
|
+
3. **改草案** ´oasis spawn/edit/link/cancel …´:人确认前,随时调这份草案(加/删/改节点、连边)。
|
|
37169
|
+
4. 人在编辑器确认 → 工单创建。**你不用 apply**,确认是人的事。
|
|
37170
|
+
|
|
37171
|
+
## 一、查重(建单前必做)
|
|
37172
|
+
- ´oasis workorders --search <关键词>´:列/搜工单。命中类似的 → ´oasis workorder <工单id>´ 看详情,已覆盖就别再建、跟用户说明。
|
|
37173
|
+
|
|
37174
|
+
## 二、建单
|
|
37175
|
+
信息不足、关键决策没定**别硬建**,用文字问用户;一个工单一个目标,多件独立的事拆成多张。
|
|
37176
|
+
|
|
37177
|
+
´´´bash
|
|
37178
|
+
oasis create-workorder \
|
|
37179
|
+
--type <工单主产物类型,如 prd;根 brief 自动建> \
|
|
37180
|
+
--title "工单名,名词短语,≤20字" \
|
|
37181
|
+
--goal "一句话目标,≤50字" \
|
|
37182
|
+
--brief "完整规格(markdown):背景、关键决策、约束、范围、不做什么" \
|
|
37183
|
+
--acceptance '["逐条可判定的验收条件","没聊到给 []"]' \
|
|
37184
|
+
--plan '<节点分解 JSON,见下>'
|
|
37185
|
+
´´´
|
|
37186
|
+
**不用填 workspace——系统自动生成工单 id。** 命令**直接打印 workspace(工单 id)和每个节点的 id**,记住它们、改草案要用——**不必再去 query**。它产出的是**草案**(stage=draft):人在编辑器确认才真创建,你别拿它当活工单使。
|
|
37187
|
+
|
|
37188
|
+
### --plan 结构
|
|
37189
|
+
´´´json
|
|
37190
|
+
{
|
|
37191
|
+
"nodes": [
|
|
37192
|
+
{ "type": "节点类型", "title": "≤20字,同 type 内唯一",
|
|
37193
|
+
"dependsOn": ["上游节点写 type:title,如 prd:需求;无上游写 []"],
|
|
37194
|
+
"brief": "这个节点具体要产出什么的规格,别套模板、别只写类型名" }
|
|
37195
|
+
],
|
|
37196
|
+
"notes": ["可选:分解理由/缺口;没有给 []"]
|
|
37197
|
+
}
|
|
37198
|
+
´´´
|
|
37199
|
+
|
|
37200
|
+
## 三、改草案(人确认前随时改)
|
|
37201
|
+
建单后草案在编辑器里等人确认。用户要调整就用下面的命令改这份草案(**改完即时生效、人在编辑器里能看到**,你不用 apply):
|
|
37202
|
+
|
|
37203
|
+
**节点引用 = id 或 type:title,分清:**
|
|
37204
|
+
- **填完整 id**(´oasis workorder´ / create 返回的,如 ´artifact:prd:x7k2-需求´)→ **不用带 workspace**。推荐。
|
|
37205
|
+
- **填 type:title**(简写,如 ´prd:需求´)→ **必须加 ´--workspace <工单id>´**(create 返回的那个)。
|
|
37206
|
+
- ´spawn´ 是新建节点、没有现成 id → **一律带 ´--workspace´**。
|
|
37207
|
+
|
|
37208
|
+
´´´bash
|
|
37209
|
+
# 加节点(带 workspace;--input 上游可用 type:title)
|
|
37210
|
+
oasis spawn --type qa --title 验收用例 --brief "覆盖登录成功/失败" --input dev:实现 --workspace ws:wo-x7k2
|
|
37211
|
+
# 改节点标题/规格(用 id,免 workspace)
|
|
37212
|
+
oasis edit artifact:prd:x7k2-需求 --title "需求与范围"
|
|
37213
|
+
# 连边:消费者依赖上游
|
|
37214
|
+
oasis link artifact:qa:x7k2-验收 --to artifact:dev:x7k2-实现
|
|
37215
|
+
# 删节点(从草案删掉,连它的边一起摘)
|
|
37216
|
+
oasis cancel prd:需求 --workspace ws:wo-x7k2
|
|
37217
|
+
´´´
|
|
37218
|
+
|
|
37219
|
+
# 命令参考
|
|
37220
|
+
|
|
37221
|
+
## 查询
|
|
37222
|
+
- ´oasis workorders [--search <词>]´ —— 列/搜工单(id/名/阶段/进度)。无参列全部;´--search´ 端侧过滤。
|
|
37223
|
+
- ´oasis workorder <工单id>´ —— 工单详情:**stage**(draft 草案 / executing 进行中 / review 评审 / blocked 卡住 / concluded 已完成 / sealed 已封存)+ 目标/验收 + 节点(id/type)+ 依赖边。**刚建的草案这里 ´stage=draft´、也查得到(含各节点 id)**;真查不到才报不存在。
|
|
37224
|
+
- ´oasis artifact-types list | search <词> | show <type>´ —— 看可用节点类型(´show´ 看该类型可填的自定义字段)。
|
|
37225
|
+
- ´oasis projects [--search <词>]´ —— 列/搜项目(建单 ´--project´ 用)。
|
|
37226
|
+
|
|
37227
|
+
## 建单
|
|
37228
|
+
- ´oasis create-workorder --type <t> --title <s> [--goal <s>] [--brief <s>] [--acceptance '<json string[]>'] [--plan '<json>'] [--project <id>]´
|
|
37229
|
+
暂存建单草案。´--type´=主产物类型(必填,根 brief 自动建);´--title´ 必填。**不填 workspace(系统生成)**。返回 ´workspace´ + 各节点 ´id´。
|
|
37230
|
+
|
|
37231
|
+
## 改草案(引用 id 不带 workspace;引用 type:title 或 spawn 带 ´--workspace <工单id>´)
|
|
37232
|
+
- ´oasis spawn --type <t> --title <s> [--brief <s>] [--input <ref>,<ref>…] --workspace <工单id>´ —— 加节点,id 据 (type,title) 自动派生。´--type´/´--title´ 必填,title 同 type 唯一;´--input´=上游依赖(逗号分隔)。
|
|
37233
|
+
- ´oasis edit <ref> [--title <s>] [--brief <s>]´ —— 改节点标题/规格。
|
|
37234
|
+
- ´oasis link <consumer-ref> --to <upstream-ref> [--required true]´ —— 加依赖边。´--required true´=阻塞性(上游没完成下游动不了),省=软依赖。
|
|
37235
|
+
- ´oasis unlink <consumer-ref> --to <upstream-ref>´ —— 删依赖边。
|
|
37236
|
+
- ´oasis cancel <ref> [--note <s>]´ —— 从草案删节点(连它的边一起摘)。
|
|
37237
|
+
|
|
37238
|
+
## 规则
|
|
37239
|
+
- **节点类型**:先 ´oasis artifact-types list/search/show´ 挑最贴合的;都不贴合用 ´oasis artifact-types add <name> --content-type <text|code|…> --owner-role <角色>´ 新建再用。常见:prd、research、design、adr、dev、qa。
|
|
37240
|
+
- **title**:≤20字、同 type 内唯一、别用半角冒号 ´:´(与 type:title 分隔冲突,要冒号用全角 ´:´)。一个工单可多个同 type 节点,靠 title 区分。
|
|
37241
|
+
- **项目**:归某项目就 ´oasis projects --search <词>´ 找 id 再加 ´--project´。
|
|
37242
|
+
- **边界**:你只管图的结构与规格,不替用户拍产品决策——拿不准就问。改**已建好的活工单**是协调者的活,不在本技能范围。
|
|
37243
|
+
`.replace(/´/g, "`");
|
|
37244
|
+
}
|
|
37245
|
+
});
|
|
37246
|
+
|
|
36271
37247
|
// ../server/src/index.ts
|
|
36272
37248
|
var init_src4 = __esm({
|
|
36273
37249
|
"../server/src/index.ts"() {
|
|
@@ -36303,9 +37279,11 @@ var init_src4 = __esm({
|
|
|
36303
37279
|
init_integrate();
|
|
36304
37280
|
init_sync();
|
|
36305
37281
|
init_resolve();
|
|
37282
|
+
init_identity();
|
|
36306
37283
|
init_worker();
|
|
36307
37284
|
init_drafts();
|
|
36308
37285
|
init_workorder_drafts();
|
|
37286
|
+
init_build_workorder_skill();
|
|
36309
37287
|
}
|
|
36310
37288
|
});
|
|
36311
37289
|
|
|
@@ -37897,15 +38875,15 @@ var require_pg_connection_string = __commonJS({
|
|
|
37897
38875
|
if (config2.sslcert || config2.sslkey || config2.sslrootcert || config2.sslmode) {
|
|
37898
38876
|
config2.ssl = {};
|
|
37899
38877
|
}
|
|
37900
|
-
const
|
|
38878
|
+
const fs17 = config2.sslcert || config2.sslkey || config2.sslrootcert ? require("fs") : null;
|
|
37901
38879
|
if (config2.sslcert) {
|
|
37902
|
-
config2.ssl.cert =
|
|
38880
|
+
config2.ssl.cert = fs17.readFileSync(config2.sslcert).toString();
|
|
37903
38881
|
}
|
|
37904
38882
|
if (config2.sslkey) {
|
|
37905
|
-
config2.ssl.key =
|
|
38883
|
+
config2.ssl.key = fs17.readFileSync(config2.sslkey).toString();
|
|
37906
38884
|
}
|
|
37907
38885
|
if (config2.sslrootcert) {
|
|
37908
|
-
config2.ssl.ca =
|
|
38886
|
+
config2.ssl.ca = fs17.readFileSync(config2.sslrootcert).toString();
|
|
37909
38887
|
}
|
|
37910
38888
|
if (options.useLibpqCompat && config2.uselibpqcompat) {
|
|
37911
38889
|
throw new Error("Both useLibpqCompat and uselibpqcompat are set. Please use only one of them.");
|
|
@@ -39842,15 +40820,15 @@ var require_lib = __commonJS({
|
|
|
39842
40820
|
"../../node_modules/.pnpm/pgpass@1.0.5/node_modules/pgpass/lib/index.js"(exports2, module2) {
|
|
39843
40821
|
"use strict";
|
|
39844
40822
|
var path14 = require("path");
|
|
39845
|
-
var
|
|
40823
|
+
var fs17 = require("fs");
|
|
39846
40824
|
var helper = require_helper();
|
|
39847
40825
|
module2.exports = function(connInfo, cb) {
|
|
39848
40826
|
var file = helper.getFileName();
|
|
39849
|
-
|
|
40827
|
+
fs17.stat(file, function(err, stat) {
|
|
39850
40828
|
if (err || !helper.usePgPass(stat, file)) {
|
|
39851
40829
|
return cb(void 0);
|
|
39852
40830
|
}
|
|
39853
|
-
var st =
|
|
40831
|
+
var st = fs17.createReadStream(file);
|
|
39854
40832
|
helper.getPassword(connInfo, st, cb);
|
|
39855
40833
|
});
|
|
39856
40834
|
};
|
|
@@ -41404,37 +42382,37 @@ __export(trajectory_exports, {
|
|
|
41404
42382
|
});
|
|
41405
42383
|
async function exportTrajectories(dataDir) {
|
|
41406
42384
|
const root = path10.join(dataDir, "trajectories");
|
|
41407
|
-
if (!
|
|
42385
|
+
if (!fs13.existsSync(root)) return [];
|
|
41408
42386
|
const model = emptyReadModel();
|
|
41409
42387
|
const oplogFile = path10.join(dataDir, "oplog.ndjson");
|
|
41410
|
-
if (
|
|
42388
|
+
if (fs13.existsSync(oplogFile)) {
|
|
41411
42389
|
const oplog = await NdjsonOplogStore.open(oplogFile);
|
|
41412
42390
|
for await (const entry of oplog.readAll()) fold(model, entry.op);
|
|
41413
42391
|
}
|
|
41414
42392
|
const samples = [];
|
|
41415
|
-
for (const sessionId of
|
|
42393
|
+
for (const sessionId of fs13.readdirSync(root).sort()) {
|
|
41416
42394
|
const dir = path10.join(root, sessionId);
|
|
41417
42395
|
const sessionFile = path10.join(dir, "session.json");
|
|
41418
|
-
if (!
|
|
41419
|
-
const session = JSON.parse(
|
|
42396
|
+
if (!fs13.existsSync(sessionFile)) continue;
|
|
42397
|
+
const session = JSON.parse(fs13.readFileSync(sessionFile, "utf8"));
|
|
41420
42398
|
const events = [];
|
|
41421
42399
|
const eventsFile = path10.join(dir, "events.ndjson");
|
|
41422
|
-
if (
|
|
41423
|
-
for (const line of
|
|
42400
|
+
if (fs13.existsSync(eventsFile)) {
|
|
42401
|
+
for (const line of fs13.readFileSync(eventsFile, "utf8").split("\n")) {
|
|
41424
42402
|
if (line.trim()) events.push(JSON.parse(line));
|
|
41425
42403
|
}
|
|
41426
42404
|
}
|
|
41427
42405
|
const bundle = {};
|
|
41428
42406
|
const bundleDir = path10.join(dir, "bundle");
|
|
41429
42407
|
const walk = (sub) => {
|
|
41430
|
-
for (const name of
|
|
42408
|
+
for (const name of fs13.readdirSync(path10.join(bundleDir, sub))) {
|
|
41431
42409
|
const rel = sub ? `${sub}/${name}` : name;
|
|
41432
42410
|
const full = path10.join(bundleDir, rel);
|
|
41433
|
-
if (
|
|
41434
|
-
else bundle[rel] =
|
|
42411
|
+
if (fs13.statSync(full).isDirectory()) walk(rel);
|
|
42412
|
+
else bundle[rel] = fs13.readFileSync(full, "utf8");
|
|
41435
42413
|
}
|
|
41436
42414
|
};
|
|
41437
|
-
if (
|
|
42415
|
+
if (fs13.existsSync(bundleDir)) walk("");
|
|
41438
42416
|
const proposed = (session.opRefs ?? []).filter((r) => r.kind === "propose_revision").map((r) => r.target);
|
|
41439
42417
|
const verdicts = proposed.flatMap(
|
|
41440
42418
|
(rev) => (model.reviews.get(rev) ?? []).map((v2) => ({ author: v2.author, verdict: v2.verdict, ...v2.note !== void 0 ? { note: v2.note } : {} }))
|
|
@@ -41446,11 +42424,11 @@ async function exportTrajectories(dataDir) {
|
|
|
41446
42424
|
}
|
|
41447
42425
|
return samples;
|
|
41448
42426
|
}
|
|
41449
|
-
var
|
|
42427
|
+
var fs13, path10, FsTrajectorySink;
|
|
41450
42428
|
var init_trajectory = __esm({
|
|
41451
42429
|
"../cli/src/trajectory.ts"() {
|
|
41452
42430
|
"use strict";
|
|
41453
|
-
|
|
42431
|
+
fs13 = __toESM(require("node:fs"), 1);
|
|
41454
42432
|
path10 = __toESM(require("node:path"), 1);
|
|
41455
42433
|
init_src2();
|
|
41456
42434
|
init_src4();
|
|
@@ -41465,25 +42443,25 @@ var init_trajectory = __esm({
|
|
|
41465
42443
|
}
|
|
41466
42444
|
begin(session, bundleFiles) {
|
|
41467
42445
|
const dir = this.dir(session.sessionId);
|
|
41468
|
-
|
|
41469
|
-
|
|
42446
|
+
fs13.mkdirSync(path10.join(dir, "bundle"), { recursive: true });
|
|
42447
|
+
fs13.writeFileSync(path10.join(dir, "session.json"), JSON.stringify(session, null, 2));
|
|
41470
42448
|
for (const [rel, content] of Object.entries(bundleFiles)) {
|
|
41471
42449
|
const file = path10.join(dir, "bundle", rel);
|
|
41472
|
-
|
|
41473
|
-
|
|
42450
|
+
fs13.mkdirSync(path10.dirname(file), { recursive: true });
|
|
42451
|
+
fs13.writeFileSync(file, content);
|
|
41474
42452
|
}
|
|
41475
42453
|
this.broadcast?.({ type: "begin", sessionId: session.sessionId, data: session });
|
|
41476
42454
|
}
|
|
41477
42455
|
event(sessionId, event) {
|
|
41478
|
-
|
|
42456
|
+
fs13.appendFileSync(path10.join(this.dir(sessionId), "events.ndjson"), JSON.stringify(event) + "\n");
|
|
41479
42457
|
this.broadcast?.({ type: "event", sessionId, data: event });
|
|
41480
42458
|
}
|
|
41481
42459
|
end(sessionId, update) {
|
|
41482
42460
|
const file = path10.join(this.dir(sessionId), "session.json");
|
|
41483
|
-
const session = JSON.parse(
|
|
42461
|
+
const session = JSON.parse(fs13.readFileSync(file, "utf8"));
|
|
41484
42462
|
session.exit = update.exit;
|
|
41485
42463
|
session.opRefs = update.opRefs;
|
|
41486
|
-
|
|
42464
|
+
fs13.writeFileSync(file, JSON.stringify(session, null, 2));
|
|
41487
42465
|
this.broadcast?.({ type: "end", sessionId, data: session });
|
|
41488
42466
|
}
|
|
41489
42467
|
};
|
|
@@ -41491,19 +42469,19 @@ var init_trajectory = __esm({
|
|
|
41491
42469
|
});
|
|
41492
42470
|
|
|
41493
42471
|
// src/index.ts
|
|
41494
|
-
var
|
|
42472
|
+
var fs16 = __toESM(require("node:fs"));
|
|
41495
42473
|
var os8 = __toESM(require("node:os"));
|
|
41496
42474
|
var path13 = __toESM(require("node:path"));
|
|
41497
42475
|
var import_node_child_process10 = require("node:child_process");
|
|
41498
42476
|
|
|
41499
42477
|
// ../cli/src/cli.ts
|
|
41500
|
-
var
|
|
42478
|
+
var fs15 = __toESM(require("node:fs"), 1);
|
|
41501
42479
|
var os7 = __toESM(require("node:os"), 1);
|
|
41502
42480
|
var path12 = __toESM(require("node:path"), 1);
|
|
41503
42481
|
var import_node_crypto21 = require("node:crypto");
|
|
41504
42482
|
|
|
41505
42483
|
// ../cli/src/serve.ts
|
|
41506
|
-
var
|
|
42484
|
+
var fs14 = __toESM(require("node:fs"), 1);
|
|
41507
42485
|
var os6 = __toESM(require("node:os"), 1);
|
|
41508
42486
|
var path11 = __toESM(require("node:path"), 1);
|
|
41509
42487
|
var import_node_crypto20 = require("node:crypto");
|
|
@@ -41712,7 +42690,7 @@ var import_node_path3 = require("node:path");
|
|
|
41712
42690
|
// ../adapters/src/claude-code/index.ts
|
|
41713
42691
|
var import_node_child_process5 = require("node:child_process");
|
|
41714
42692
|
var import_node_crypto14 = require("node:crypto");
|
|
41715
|
-
var
|
|
42693
|
+
var fs9 = __toESM(require("node:fs"), 1);
|
|
41716
42694
|
var os2 = __toESM(require("node:os"), 1);
|
|
41717
42695
|
var path6 = __toESM(require("node:path"), 1);
|
|
41718
42696
|
var readline = __toESM(require("node:readline"), 1);
|
|
@@ -41833,12 +42811,17 @@ var ClaudeCodeAdapter = class {
|
|
|
41833
42811
|
async spawn(job) {
|
|
41834
42812
|
const slug4 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
|
|
41835
42813
|
const id = `claude-${slug4}-${(0, import_node_crypto14.randomUUID)().slice(0, 8)}`;
|
|
41836
|
-
const dir = job.runtimeSessionId ? path6.join(this.opts.workRoot ?? os2.tmpdir(), "oasis-chat-sessions", job.runtimeSessionId.replace(/[^a-zA-Z0-9_-]+/g, "_")) :
|
|
41837
|
-
|
|
42814
|
+
const dir = job.runtimeSessionId ? path6.join(this.opts.workRoot ?? os2.tmpdir(), "oasis-chat-sessions", job.runtimeSessionId.replace(/[^a-zA-Z0-9_-]+/g, "_")) : fs9.mkdtempSync(path6.join(this.opts.workRoot ?? os2.tmpdir(), "oasis-session-"));
|
|
42815
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
41838
42816
|
for (const [rel, content] of Object.entries(job.bundle.files)) {
|
|
41839
42817
|
const file = path6.join(dir, rel);
|
|
41840
|
-
|
|
41841
|
-
|
|
42818
|
+
fs9.mkdirSync(path6.dirname(file), { recursive: true });
|
|
42819
|
+
fs9.writeFileSync(file, content);
|
|
42820
|
+
}
|
|
42821
|
+
for (const [rel, b64] of Object.entries(job.bundle.binaryFiles ?? {})) {
|
|
42822
|
+
const file = path6.join(dir, rel);
|
|
42823
|
+
fs9.mkdirSync(path6.dirname(file), { recursive: true });
|
|
42824
|
+
fs9.writeFileSync(file, Buffer.from(b64, "base64"));
|
|
41842
42825
|
}
|
|
41843
42826
|
const prompt = job.bundle.files["TASK.md"];
|
|
41844
42827
|
if (!prompt) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
|
|
@@ -41877,7 +42860,7 @@ var ClaudeCodeAdapter = class {
|
|
|
41877
42860
|
stdio: ["ignore", "pipe", "pipe"]
|
|
41878
42861
|
});
|
|
41879
42862
|
let transcriptRef = path6.join(dir, "transcript.txt");
|
|
41880
|
-
const out =
|
|
42863
|
+
const out = fs9.createWriteStream(transcriptRef);
|
|
41881
42864
|
const telemetryCbs = [];
|
|
41882
42865
|
let eventSeq = 0;
|
|
41883
42866
|
let lastResult;
|
|
@@ -41940,13 +42923,13 @@ var ClaudeCodeAdapter = class {
|
|
|
41940
42923
|
if (this.opts.cleanupWorkdir && !job.runtimeSessionId) {
|
|
41941
42924
|
try {
|
|
41942
42925
|
const keepDir = path6.join(this.opts.workRoot ?? os2.tmpdir(), "oasis-transcripts");
|
|
41943
|
-
|
|
42926
|
+
fs9.mkdirSync(keepDir, { recursive: true });
|
|
41944
42927
|
const kept = path6.join(keepDir, `${id}.txt`);
|
|
41945
42928
|
const src = transcriptRef;
|
|
41946
42929
|
out.end(() => {
|
|
41947
42930
|
try {
|
|
41948
|
-
|
|
41949
|
-
|
|
42931
|
+
fs9.renameSync(src, kept);
|
|
42932
|
+
fs9.rmSync(dir, { recursive: true, force: true });
|
|
41950
42933
|
} catch {
|
|
41951
42934
|
}
|
|
41952
42935
|
});
|
|
@@ -41981,7 +42964,7 @@ var ClaudeCodeAdapter = class {
|
|
|
41981
42964
|
// ../adapters/src/codex/index.ts
|
|
41982
42965
|
var import_node_child_process6 = require("node:child_process");
|
|
41983
42966
|
var import_node_crypto15 = require("node:crypto");
|
|
41984
|
-
var
|
|
42967
|
+
var fs10 = __toESM(require("node:fs"), 1);
|
|
41985
42968
|
var os3 = __toESM(require("node:os"), 1);
|
|
41986
42969
|
var path7 = __toESM(require("node:path"), 1);
|
|
41987
42970
|
var readline2 = __toESM(require("node:readline"), 1);
|
|
@@ -42026,7 +43009,7 @@ function codexSessionsRoot() {
|
|
|
42026
43009
|
if (home) candidates.push(path7.join(home, ".codex", "sessions"));
|
|
42027
43010
|
for (const c of candidates) {
|
|
42028
43011
|
try {
|
|
42029
|
-
if (
|
|
43012
|
+
if (fs10.statSync(c).isDirectory()) return c;
|
|
42030
43013
|
} catch {
|
|
42031
43014
|
}
|
|
42032
43015
|
}
|
|
@@ -42038,7 +43021,7 @@ function listRecentRollouts(root, sinceMs) {
|
|
|
42038
43021
|
const walk = (d) => {
|
|
42039
43022
|
let ents;
|
|
42040
43023
|
try {
|
|
42041
|
-
ents =
|
|
43024
|
+
ents = fs10.readdirSync(d, { withFileTypes: true });
|
|
42042
43025
|
} catch {
|
|
42043
43026
|
return;
|
|
42044
43027
|
}
|
|
@@ -42048,7 +43031,7 @@ function listRecentRollouts(root, sinceMs) {
|
|
|
42048
43031
|
else if (ent.isFile() && ent.name.startsWith("rollout-") && ent.name.endsWith(".jsonl")) {
|
|
42049
43032
|
let m2 = 0;
|
|
42050
43033
|
try {
|
|
42051
|
-
m2 =
|
|
43034
|
+
m2 = fs10.statSync(p2).mtimeMs;
|
|
42052
43035
|
} catch {
|
|
42053
43036
|
}
|
|
42054
43037
|
if (m2 >= margin) found.push({ f: p2, m: m2 });
|
|
@@ -42063,7 +43046,7 @@ function scanCodexUsage(workdir, sinceMs, sessionsRoot = codexSessionsRoot()) {
|
|
|
42063
43046
|
for (const f2 of listRecentRollouts(sessionsRoot, sinceMs)) {
|
|
42064
43047
|
let lines;
|
|
42065
43048
|
try {
|
|
42066
|
-
lines =
|
|
43049
|
+
lines = fs10.readFileSync(f2, "utf8").split("\n");
|
|
42067
43050
|
} catch {
|
|
42068
43051
|
continue;
|
|
42069
43052
|
}
|
|
@@ -42087,9 +43070,9 @@ function createCodexNormalizeState() {
|
|
|
42087
43070
|
function record2(value) {
|
|
42088
43071
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
42089
43072
|
}
|
|
42090
|
-
function
|
|
43073
|
+
function textOf3(value) {
|
|
42091
43074
|
if (typeof value === "string") return value;
|
|
42092
|
-
if (Array.isArray(value)) return value.map(
|
|
43075
|
+
if (Array.isArray(value)) return value.map(textOf3).filter(Boolean).join("\n");
|
|
42093
43076
|
const obj = record2(value);
|
|
42094
43077
|
if (!obj) return void 0;
|
|
42095
43078
|
if (typeof obj.text === "string") return obj.text;
|
|
@@ -42098,7 +43081,7 @@ function textOf2(value) {
|
|
|
42098
43081
|
return void 0;
|
|
42099
43082
|
}
|
|
42100
43083
|
function stringifyContent(value) {
|
|
42101
|
-
const text =
|
|
43084
|
+
const text = textOf3(value);
|
|
42102
43085
|
if (text !== void 0) return text;
|
|
42103
43086
|
if (value === void 0 || value === null) return "";
|
|
42104
43087
|
try {
|
|
@@ -42129,7 +43112,7 @@ ${text}` : text;
|
|
|
42129
43112
|
return [{ kind: "message", payload: { text: delta }, outputText: delta }];
|
|
42130
43113
|
}
|
|
42131
43114
|
if (type === "reasoning") {
|
|
42132
|
-
const text =
|
|
43115
|
+
const text = textOf3(item.text ?? item.summary ?? item.content ?? item);
|
|
42133
43116
|
return text ? [{ kind: "thought", payload: { text } }] : [];
|
|
42134
43117
|
}
|
|
42135
43118
|
if (type === "command_execution" && typeof item.id === "string") {
|
|
@@ -42255,11 +43238,11 @@ var CodexAdapter = class {
|
|
|
42255
43238
|
const slug4 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
|
|
42256
43239
|
const id = `codex-${slug4}-${(0, import_node_crypto15.randomUUID)().slice(0, 8)}`;
|
|
42257
43240
|
const startedAtMs = Date.now();
|
|
42258
|
-
const dir =
|
|
43241
|
+
const dir = fs10.mkdtempSync(path7.join(this.opts.workRoot ?? os3.tmpdir(), "oasis-session-"));
|
|
42259
43242
|
for (const [rel, content] of Object.entries(job.bundle.files)) {
|
|
42260
43243
|
const file = path7.join(dir, rel);
|
|
42261
|
-
|
|
42262
|
-
|
|
43244
|
+
fs10.mkdirSync(path7.dirname(file), { recursive: true });
|
|
43245
|
+
fs10.writeFileSync(file, content);
|
|
42263
43246
|
}
|
|
42264
43247
|
const task = job.bundle.files["TASK.md"];
|
|
42265
43248
|
if (!task) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
|
|
@@ -42291,7 +43274,7 @@ var CodexAdapter = class {
|
|
|
42291
43274
|
stdio: ["ignore", "pipe", "pipe"]
|
|
42292
43275
|
});
|
|
42293
43276
|
let transcriptRef = path7.join(dir, "transcript.txt");
|
|
42294
|
-
const out =
|
|
43277
|
+
const out = fs10.createWriteStream(transcriptRef);
|
|
42295
43278
|
const outputCbs = [];
|
|
42296
43279
|
const telemetryCbs = [];
|
|
42297
43280
|
const normalizeState = createCodexNormalizeState();
|
|
@@ -42322,13 +43305,13 @@ var CodexAdapter = class {
|
|
|
42322
43305
|
if (this.opts.cleanupWorkdir) {
|
|
42323
43306
|
try {
|
|
42324
43307
|
const keepDir = path7.join(this.opts.workRoot ?? os3.tmpdir(), "oasis-transcripts");
|
|
42325
|
-
|
|
43308
|
+
fs10.mkdirSync(keepDir, { recursive: true });
|
|
42326
43309
|
const kept = path7.join(keepDir, `${id}.txt`);
|
|
42327
43310
|
const src = transcriptRef;
|
|
42328
43311
|
out.end(() => {
|
|
42329
43312
|
try {
|
|
42330
|
-
|
|
42331
|
-
|
|
43313
|
+
fs10.renameSync(src, kept);
|
|
43314
|
+
fs10.rmSync(dir, { recursive: true, force: true });
|
|
42332
43315
|
} catch {
|
|
42333
43316
|
}
|
|
42334
43317
|
});
|
|
@@ -42381,7 +43364,7 @@ var CodexAdapter = class {
|
|
|
42381
43364
|
// ../adapters/src/_core/acp.ts
|
|
42382
43365
|
var import_node_child_process7 = require("node:child_process");
|
|
42383
43366
|
var import_node_crypto16 = require("node:crypto");
|
|
42384
|
-
var
|
|
43367
|
+
var fs11 = __toESM(require("node:fs"), 1);
|
|
42385
43368
|
var os4 = __toESM(require("node:os"), 1);
|
|
42386
43369
|
var path8 = __toESM(require("node:path"), 1);
|
|
42387
43370
|
var readline3 = __toESM(require("node:readline"), 1);
|
|
@@ -42396,6 +43379,7 @@ var ACPClient = class {
|
|
|
42396
43379
|
nextId = 0;
|
|
42397
43380
|
pending = /* @__PURE__ */ new Map();
|
|
42398
43381
|
pendingTools = /* @__PURE__ */ new Map();
|
|
43382
|
+
textBuffers = /* @__PURE__ */ new Map();
|
|
42399
43383
|
seq = 0;
|
|
42400
43384
|
request(method, params) {
|
|
42401
43385
|
const id = this.nextId++;
|
|
@@ -42405,6 +43389,7 @@ var ACPClient = class {
|
|
|
42405
43389
|
});
|
|
42406
43390
|
}
|
|
42407
43391
|
closeAllPending(err) {
|
|
43392
|
+
this.flushTextBuffers();
|
|
42408
43393
|
for (const p2 of this.pending.values()) p2.reject(err);
|
|
42409
43394
|
this.pending.clear();
|
|
42410
43395
|
}
|
|
@@ -42463,17 +43448,22 @@ var ACPClient = class {
|
|
|
42463
43448
|
this.handleAgentThought(updateData);
|
|
42464
43449
|
break;
|
|
42465
43450
|
case "tool_call":
|
|
43451
|
+
this.flushTextBuffers();
|
|
42466
43452
|
this.handleToolCallStart(updateData);
|
|
43453
|
+
this.handleToolCallUpdate(updateData);
|
|
42467
43454
|
break;
|
|
42468
43455
|
case "tool_call_update":
|
|
43456
|
+
this.flushTextBuffers();
|
|
42469
43457
|
this.handleToolCallUpdate(updateData);
|
|
42470
43458
|
break;
|
|
42471
43459
|
case "turn_end":
|
|
43460
|
+
this.flushTextBuffers();
|
|
42472
43461
|
this.extractStopReason(updateData);
|
|
42473
43462
|
break;
|
|
42474
43463
|
}
|
|
42475
43464
|
}
|
|
42476
43465
|
extractStopReason(data) {
|
|
43466
|
+
this.flushTextBuffers();
|
|
42477
43467
|
const r = data;
|
|
42478
43468
|
if (r?.stopReason) this.onStop(r.stopReason);
|
|
42479
43469
|
}
|
|
@@ -42481,45 +43471,235 @@ var ACPClient = class {
|
|
|
42481
43471
|
if (outputText) this.onOutput(outputText);
|
|
42482
43472
|
this.onTelemetry({ kind, payload, seq: ++this.seq, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
42483
43473
|
}
|
|
43474
|
+
appendText(kind, text) {
|
|
43475
|
+
if (kind === "message") this.onOutput(text);
|
|
43476
|
+
const other = kind === "message" ? "thought" : "message";
|
|
43477
|
+
this.flushTextBuffer(other);
|
|
43478
|
+
const existing = this.textBuffers.get(kind);
|
|
43479
|
+
if (existing) existing.text += text;
|
|
43480
|
+
else this.textBuffers.set(kind, { text, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
43481
|
+
}
|
|
43482
|
+
flushTextBuffer(kind) {
|
|
43483
|
+
const buffered = this.textBuffers.get(kind);
|
|
43484
|
+
if (!buffered) return;
|
|
43485
|
+
this.textBuffers.delete(kind);
|
|
43486
|
+
const text = buffered.text;
|
|
43487
|
+
if (!text) return;
|
|
43488
|
+
this.onTelemetry({ kind, payload: { text }, seq: ++this.seq, ts: buffered.startedAt });
|
|
43489
|
+
}
|
|
43490
|
+
flushTextBuffers() {
|
|
43491
|
+
this.flushTextBuffer("thought");
|
|
43492
|
+
this.flushTextBuffer("message");
|
|
43493
|
+
}
|
|
42484
43494
|
handleAgentMessage(data) {
|
|
42485
|
-
const text = data?.content
|
|
42486
|
-
if (text) this.
|
|
43495
|
+
const text = textFromUnknown(record3(data)?.["content"] ?? data);
|
|
43496
|
+
if (text) this.appendText("message", text);
|
|
42487
43497
|
}
|
|
42488
43498
|
handleAgentThought(data) {
|
|
42489
|
-
const text = data?.content
|
|
42490
|
-
if (text) this.
|
|
43499
|
+
const text = textFromUnknown(record3(data)?.["content"] ?? data);
|
|
43500
|
+
if (text) this.appendText("thought", text);
|
|
42491
43501
|
}
|
|
42492
43502
|
handleToolCallStart(data) {
|
|
42493
|
-
const d = data;
|
|
42494
|
-
|
|
42495
|
-
|
|
42496
|
-
const
|
|
42497
|
-
|
|
42498
|
-
|
|
42499
|
-
this.
|
|
43503
|
+
const d = toolPayload(data);
|
|
43504
|
+
const toolCallId = toolCallIdOf(d);
|
|
43505
|
+
if (!toolCallId) return;
|
|
43506
|
+
const toolName = toolNameOf(d);
|
|
43507
|
+
const input = toolInputOf(d);
|
|
43508
|
+
if (input !== void 0) {
|
|
43509
|
+
this.pendingTools.set(toolCallId, { toolName, input, argsText: "", outputParts: [], emitted: true });
|
|
43510
|
+
this.emit("tool_use", { id: toolCallId, name: toolName, input });
|
|
42500
43511
|
} else {
|
|
42501
|
-
this.pendingTools.set(
|
|
43512
|
+
this.pendingTools.set(toolCallId, {
|
|
43513
|
+
toolName,
|
|
43514
|
+
argsText: argsTextOf(d) ?? "",
|
|
43515
|
+
outputParts: [],
|
|
43516
|
+
emitted: false
|
|
43517
|
+
});
|
|
42502
43518
|
}
|
|
42503
43519
|
}
|
|
42504
43520
|
handleToolCallUpdate(data) {
|
|
42505
|
-
const d = data;
|
|
42506
|
-
|
|
42507
|
-
|
|
42508
|
-
|
|
42509
|
-
|
|
42510
|
-
|
|
43521
|
+
const d = toolPayload(data);
|
|
43522
|
+
const toolCallId = toolCallIdOf(d);
|
|
43523
|
+
if (!toolCallId) return;
|
|
43524
|
+
let pending = this.pendingTools.get(toolCallId);
|
|
43525
|
+
if (!pending) {
|
|
43526
|
+
pending = {
|
|
43527
|
+
toolName: toolNameOf(d),
|
|
43528
|
+
input: toolInputOf(d),
|
|
43529
|
+
argsText: argsTextOf(d) ?? "",
|
|
43530
|
+
outputParts: [],
|
|
43531
|
+
emitted: false
|
|
43532
|
+
};
|
|
43533
|
+
this.pendingTools.set(toolCallId, pending);
|
|
43534
|
+
}
|
|
43535
|
+
const input = toolInputOf(d);
|
|
43536
|
+
if (input !== void 0) pending.input = input;
|
|
43537
|
+
const argsText = argsTextOf(d);
|
|
43538
|
+
if (argsText) pending.argsText += argsText;
|
|
43539
|
+
const output = toolOutputOf(d);
|
|
43540
|
+
if (output !== void 0) pending.outputParts.push(output);
|
|
43541
|
+
if (!pending.emitted && pending.input !== void 0) {
|
|
43542
|
+
pending.emitted = true;
|
|
43543
|
+
this.emit("tool_use", { id: toolCallId, name: pending.toolName, input: pending.input });
|
|
43544
|
+
}
|
|
43545
|
+
const status = statusOf(d);
|
|
43546
|
+
if (!isFinalToolStatus(status, d)) return;
|
|
43547
|
+
this.pendingTools.delete(toolCallId);
|
|
42511
43548
|
if (!pending?.emitted) {
|
|
42512
|
-
const
|
|
42513
|
-
|
|
42514
|
-
this.emit("tool_use", { id: d.toolCallId, name: toolName, input });
|
|
43549
|
+
const input2 = pending.input ?? parseArgsJSON(pending.argsText);
|
|
43550
|
+
this.emit("tool_use", { id: toolCallId, name: pending.toolName, input: input2 ?? {} });
|
|
42515
43551
|
}
|
|
42516
43552
|
this.emit("tool_result", {
|
|
42517
|
-
tool_use_id:
|
|
42518
|
-
content:
|
|
42519
|
-
is_error: status
|
|
43553
|
+
tool_use_id: toolCallId,
|
|
43554
|
+
content: output ?? joinOutputParts(pending.outputParts),
|
|
43555
|
+
is_error: isErrorToolStatus(status, d)
|
|
42520
43556
|
});
|
|
42521
43557
|
}
|
|
42522
43558
|
};
|
|
43559
|
+
function record3(value) {
|
|
43560
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
43561
|
+
}
|
|
43562
|
+
function firstString(...values) {
|
|
43563
|
+
for (const value of values) {
|
|
43564
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
43565
|
+
}
|
|
43566
|
+
return void 0;
|
|
43567
|
+
}
|
|
43568
|
+
function textFromUnknown(value) {
|
|
43569
|
+
if (typeof value === "string") return value;
|
|
43570
|
+
if (Array.isArray(value)) {
|
|
43571
|
+
const parts = value.map(textFromUnknown).filter((x2) => Boolean(x2));
|
|
43572
|
+
return parts.length ? parts.join("") : void 0;
|
|
43573
|
+
}
|
|
43574
|
+
const obj = record3(value);
|
|
43575
|
+
if (!obj) return void 0;
|
|
43576
|
+
const direct = firstString(obj["text"], obj["delta"], obj["message"], record3(obj["error"])?.["message"]);
|
|
43577
|
+
if (direct) return direct;
|
|
43578
|
+
return textFromUnknown(obj["content"]) ?? textFromUnknown(record3(obj["result"])?.["content"]) ?? textFromUnknown(obj["result"]);
|
|
43579
|
+
}
|
|
43580
|
+
function jsonLike(value) {
|
|
43581
|
+
if (typeof value !== "string") return value;
|
|
43582
|
+
const t = value.trim();
|
|
43583
|
+
if (!t) return void 0;
|
|
43584
|
+
if (t.startsWith("{") && t.endsWith("}") || t.startsWith("[") && t.endsWith("]")) {
|
|
43585
|
+
try {
|
|
43586
|
+
return JSON.parse(t);
|
|
43587
|
+
} catch {
|
|
43588
|
+
}
|
|
43589
|
+
}
|
|
43590
|
+
return { text: value };
|
|
43591
|
+
}
|
|
43592
|
+
function toolPayload(data) {
|
|
43593
|
+
const d = record3(data) ?? {};
|
|
43594
|
+
const nested = record3(d["toolCall"]) ?? record3(d["tool_call"]) ?? record3(d["call"]) ?? record3(d["tool"]) ?? record3(d["data"]) ?? {};
|
|
43595
|
+
return { ...nested, ...d };
|
|
43596
|
+
}
|
|
43597
|
+
function toolCallIdOf(d) {
|
|
43598
|
+
return firstString(d["toolCallId"], d["tool_call_id"], d["callId"], d["call_id"], d["id"]);
|
|
43599
|
+
}
|
|
43600
|
+
function toolNameOf(d) {
|
|
43601
|
+
return firstString(d["name"], d["toolName"], d["tool_name"], record3(d["tool"])?.["name"]) ?? toolNameFromTitle(firstString(d["title"]) ?? "", firstString(d["kind"]) ?? "");
|
|
43602
|
+
}
|
|
43603
|
+
function inputFromTitle(title, toolName) {
|
|
43604
|
+
const colon = title.indexOf(":");
|
|
43605
|
+
if (colon < 0) return void 0;
|
|
43606
|
+
const tail = title.slice(colon + 1).trim();
|
|
43607
|
+
if (!tail) return void 0;
|
|
43608
|
+
if (toolName === "terminal" || toolName === "execute_code" || toolName === "python") return { command: tail };
|
|
43609
|
+
if (toolName.includes("read") || toolName.includes("write")) return { path: tail };
|
|
43610
|
+
if (toolName.includes("search")) return { query: tail };
|
|
43611
|
+
return { text: tail };
|
|
43612
|
+
}
|
|
43613
|
+
function toolInputOf(d) {
|
|
43614
|
+
const direct = d["rawInput"] ?? d["raw_input"] ?? d["input"] ?? d["arguments"] ?? d["args"] ?? d["parameters"] ?? d["params"];
|
|
43615
|
+
const parsed = jsonLike(direct);
|
|
43616
|
+
if (parsed !== void 0) return parsed;
|
|
43617
|
+
const command = firstString(d["command"], d["cmd"]);
|
|
43618
|
+
if (command) return { command };
|
|
43619
|
+
const common = compactRecord({
|
|
43620
|
+
query: d["query"] ?? d["q"] ?? d["searchQuery"] ?? d["search_query"],
|
|
43621
|
+
queries: d["queries"],
|
|
43622
|
+
url: d["url"] ?? d["uri"],
|
|
43623
|
+
urls: d["urls"],
|
|
43624
|
+
path: d["path"] ?? d["filePath"] ?? d["file_path"],
|
|
43625
|
+
paths: d["paths"],
|
|
43626
|
+
pattern: d["pattern"] ?? d["regex"],
|
|
43627
|
+
prompt: d["prompt"],
|
|
43628
|
+
description: d["description"]
|
|
43629
|
+
});
|
|
43630
|
+
if (common) return common;
|
|
43631
|
+
const fromTitle = inputFromTitle(firstString(d["title"]) ?? "", toolNameOf(d));
|
|
43632
|
+
const locations = locationsOf(d);
|
|
43633
|
+
if (fromTitle && locations) return { ...fromTitle, locations };
|
|
43634
|
+
if (fromTitle) return fromTitle;
|
|
43635
|
+
if (locations) return { locations };
|
|
43636
|
+
return void 0;
|
|
43637
|
+
}
|
|
43638
|
+
function argsTextOf(d) {
|
|
43639
|
+
const value = d["argsText"] ?? d["args_text"] ?? d["argumentsText"] ?? d["arguments_text"];
|
|
43640
|
+
return typeof value === "string" ? value : void 0;
|
|
43641
|
+
}
|
|
43642
|
+
function toolOutputOf(d) {
|
|
43643
|
+
const error2 = record3(d["error"]);
|
|
43644
|
+
if (error2) return error2["message"] ?? error2;
|
|
43645
|
+
const raw = d["rawOutput"] ?? d["raw_output"] ?? d["output"] ?? d["result"] ?? d["results"] ?? d["response"] ?? d["data"] ?? d["content"];
|
|
43646
|
+
const stream = compactRecord({
|
|
43647
|
+
stdout: d["stdout"],
|
|
43648
|
+
stderr: d["stderr"],
|
|
43649
|
+
exit_code: d["exit_code"] ?? d["exitCode"]
|
|
43650
|
+
});
|
|
43651
|
+
if (raw === void 0) return stream;
|
|
43652
|
+
const text = textFromUnknown(raw);
|
|
43653
|
+
const primary = text ?? raw;
|
|
43654
|
+
if (stream && typeof primary === "string" && primary.trim()) return { output: primary, ...stream };
|
|
43655
|
+
return stream ?? primary;
|
|
43656
|
+
}
|
|
43657
|
+
function compactRecord(input) {
|
|
43658
|
+
const out = {};
|
|
43659
|
+
for (const [key, value] of Object.entries(input)) {
|
|
43660
|
+
if (value === void 0 || value === null) continue;
|
|
43661
|
+
if (typeof value === "string" && !value.trim()) continue;
|
|
43662
|
+
if (Array.isArray(value) && value.length === 0) continue;
|
|
43663
|
+
out[key] = parseJsonString(value);
|
|
43664
|
+
}
|
|
43665
|
+
return Object.keys(out).length ? out : void 0;
|
|
43666
|
+
}
|
|
43667
|
+
function parseJsonString(value) {
|
|
43668
|
+
if (typeof value !== "string") return value;
|
|
43669
|
+
const t = value.trim();
|
|
43670
|
+
if (t.startsWith("{") && t.endsWith("}") || t.startsWith("[") && t.endsWith("]")) {
|
|
43671
|
+
try {
|
|
43672
|
+
return JSON.parse(t);
|
|
43673
|
+
} catch {
|
|
43674
|
+
}
|
|
43675
|
+
}
|
|
43676
|
+
return value;
|
|
43677
|
+
}
|
|
43678
|
+
function locationsOf(d) {
|
|
43679
|
+
const locations = d["locations"];
|
|
43680
|
+
if (!Array.isArray(locations)) return void 0;
|
|
43681
|
+
const out = locations.filter((item) => Boolean(record3(item)));
|
|
43682
|
+
return out.length ? out : void 0;
|
|
43683
|
+
}
|
|
43684
|
+
function statusOf(d) {
|
|
43685
|
+
const status = firstString(d["status"], d["state"]);
|
|
43686
|
+
if (status) return status.toLowerCase();
|
|
43687
|
+
if (d["success"] === true) return "succeeded";
|
|
43688
|
+
if (d["success"] === false || d["isError"] === true || d["is_error"] === true || d["error"]) return "failed";
|
|
43689
|
+
return void 0;
|
|
43690
|
+
}
|
|
43691
|
+
function isFinalToolStatus(status, d) {
|
|
43692
|
+
if (d["done"] === true || d["final"] === true) return true;
|
|
43693
|
+
return status === "completed" || status === "complete" || status === "succeeded" || status === "success" || status === "failed" || status === "error" || status === "cancelled" || status === "canceled" || status === "done" || status === "finished";
|
|
43694
|
+
}
|
|
43695
|
+
function isErrorToolStatus(status, d) {
|
|
43696
|
+
return status === "failed" || status === "error" || status === "cancelled" || status === "canceled" || d["success"] === false || d["isError"] === true || d["is_error"] === true || Boolean(d["error"]);
|
|
43697
|
+
}
|
|
43698
|
+
function joinOutputParts(parts) {
|
|
43699
|
+
if (parts.length === 0) return "";
|
|
43700
|
+
if (parts.every((p2) => typeof p2 === "string")) return parts.join("");
|
|
43701
|
+
return parts;
|
|
43702
|
+
}
|
|
42523
43703
|
function normalizeACPUpdate(data) {
|
|
42524
43704
|
const d = data;
|
|
42525
43705
|
if (!d) return ["", data];
|
|
@@ -42537,7 +43717,17 @@ function normalizeUpdateType(t) {
|
|
|
42537
43717
|
agentmessagechunk: "agent_message_chunk",
|
|
42538
43718
|
agentthoughtchunk: "agent_thought_chunk",
|
|
42539
43719
|
toolcall: "tool_call",
|
|
43720
|
+
toolcallstart: "tool_call",
|
|
43721
|
+
toolcallstarted: "tool_call",
|
|
43722
|
+
toolcallbegin: "tool_call",
|
|
43723
|
+
toolcallcreated: "tool_call",
|
|
42540
43724
|
toolcallupdate: "tool_call_update",
|
|
43725
|
+
toolcallupdated: "tool_call_update",
|
|
43726
|
+
toolcallprogress: "tool_call_update",
|
|
43727
|
+
toolcallstatus: "tool_call_update",
|
|
43728
|
+
toolcalldelta: "tool_call_update",
|
|
43729
|
+
toolcallfinished: "tool_call_update",
|
|
43730
|
+
toolcallcompleted: "tool_call_update",
|
|
42541
43731
|
usageupdate: "usage_update",
|
|
42542
43732
|
turnend: "turn_end",
|
|
42543
43733
|
endturn: "turn_end"
|
|
@@ -42587,11 +43777,11 @@ async function runACPSession(job, cfg) {
|
|
|
42587
43777
|
const slug4 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
|
|
42588
43778
|
const binTag = path8.basename(cfg.bin).replace(/[^a-zA-Z0-9]/g, "").slice(0, 12) || "acp";
|
|
42589
43779
|
const id = `${binTag}-${slug4}-${(0, import_node_crypto16.randomUUID)().slice(0, 8)}`;
|
|
42590
|
-
const dir =
|
|
43780
|
+
const dir = fs11.mkdtempSync(path8.join(cfg.workRoot ?? os4.tmpdir(), "oasis-session-"));
|
|
42591
43781
|
for (const [rel, content] of Object.entries(job.bundle.files)) {
|
|
42592
43782
|
const file = path8.join(dir, rel);
|
|
42593
|
-
|
|
42594
|
-
|
|
43783
|
+
fs11.mkdirSync(path8.dirname(file), { recursive: true });
|
|
43784
|
+
fs11.writeFileSync(file, content);
|
|
42595
43785
|
}
|
|
42596
43786
|
const task = job.bundle.files["TASK.md"];
|
|
42597
43787
|
if (!task) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
|
|
@@ -42609,7 +43799,7 @@ async function runACPSession(job, cfg) {
|
|
|
42609
43799
|
stdio: ["pipe", "pipe", "pipe"]
|
|
42610
43800
|
});
|
|
42611
43801
|
let transcriptRef = path8.join(dir, "transcript.txt");
|
|
42612
|
-
const transcriptOut =
|
|
43802
|
+
const transcriptOut = fs11.createWriteStream(transcriptRef);
|
|
42613
43803
|
const outputCbs = [];
|
|
42614
43804
|
const telemetryCbs = [];
|
|
42615
43805
|
const exitCbs = [];
|
|
@@ -42620,13 +43810,13 @@ async function runACPSession(job, cfg) {
|
|
|
42620
43810
|
if (cfg.cleanupWorkdir) {
|
|
42621
43811
|
try {
|
|
42622
43812
|
const keepDir = path8.join(cfg.workRoot ?? os4.tmpdir(), "oasis-transcripts");
|
|
42623
|
-
|
|
43813
|
+
fs11.mkdirSync(keepDir, { recursive: true });
|
|
42624
43814
|
const kept = path8.join(keepDir, `${id}.txt`);
|
|
42625
43815
|
const src = transcriptRef;
|
|
42626
43816
|
transcriptOut.end(() => {
|
|
42627
43817
|
try {
|
|
42628
|
-
|
|
42629
|
-
|
|
43818
|
+
fs11.renameSync(src, kept);
|
|
43819
|
+
fs11.rmSync(dir, { recursive: true, force: true });
|
|
42630
43820
|
} catch {
|
|
42631
43821
|
}
|
|
42632
43822
|
});
|
|
@@ -42803,18 +43993,18 @@ var KiroAdapter = class {
|
|
|
42803
43993
|
// ../adapters/src/_core/subprocess.ts
|
|
42804
43994
|
var import_node_child_process8 = require("node:child_process");
|
|
42805
43995
|
var import_node_crypto17 = require("node:crypto");
|
|
42806
|
-
var
|
|
43996
|
+
var fs12 = __toESM(require("node:fs"), 1);
|
|
42807
43997
|
var os5 = __toESM(require("node:os"), 1);
|
|
42808
43998
|
var path9 = __toESM(require("node:path"), 1);
|
|
42809
43999
|
var readline4 = __toESM(require("node:readline"), 1);
|
|
42810
44000
|
function materialize(job, workRoot) {
|
|
42811
44001
|
const slug4 = job.artifactId.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32);
|
|
42812
44002
|
const id = `${slug4}-${(0, import_node_crypto17.randomUUID)().slice(0, 8)}`;
|
|
42813
|
-
const dir =
|
|
44003
|
+
const dir = fs12.mkdtempSync(path9.join(workRoot ?? os5.tmpdir(), "oasis-session-"));
|
|
42814
44004
|
for (const [rel, content] of Object.entries(job.bundle.files)) {
|
|
42815
44005
|
const file = path9.join(dir, rel);
|
|
42816
|
-
|
|
42817
|
-
|
|
44006
|
+
fs12.mkdirSync(path9.dirname(file), { recursive: true });
|
|
44007
|
+
fs12.writeFileSync(file, content);
|
|
42818
44008
|
}
|
|
42819
44009
|
const task = job.bundle.files["TASK.md"];
|
|
42820
44010
|
if (!task) throw new Error("bundle \u7F3A TASK.md\uFF08\u88C5\u914D\u5668\u5951\u7EA6\uFF09");
|
|
@@ -42837,13 +44027,13 @@ function makeFinish(id, cfg, transcriptRefPtr, transcriptOut, dir, exitCbs) {
|
|
|
42837
44027
|
if (cfg.cleanupWorkdir) {
|
|
42838
44028
|
try {
|
|
42839
44029
|
const keepDir = path9.join(cfg.workRoot ?? os5.tmpdir(), "oasis-transcripts");
|
|
42840
|
-
|
|
44030
|
+
fs12.mkdirSync(keepDir, { recursive: true });
|
|
42841
44031
|
const kept = path9.join(keepDir, `${id}.txt`);
|
|
42842
44032
|
const src = transcriptRefPtr.value;
|
|
42843
44033
|
transcriptOut.end(() => {
|
|
42844
44034
|
try {
|
|
42845
|
-
|
|
42846
|
-
|
|
44035
|
+
fs12.renameSync(src, kept);
|
|
44036
|
+
fs12.rmSync(dir, { recursive: true, force: true });
|
|
42847
44037
|
} catch {
|
|
42848
44038
|
}
|
|
42849
44039
|
});
|
|
@@ -42866,7 +44056,7 @@ function runStreamJson(job, cfg) {
|
|
|
42866
44056
|
stdio: ["ignore", "pipe", "pipe"]
|
|
42867
44057
|
});
|
|
42868
44058
|
const transcriptRefPtr = { value: path9.join(dir, "transcript.txt") };
|
|
42869
|
-
const transcriptOut =
|
|
44059
|
+
const transcriptOut = fs12.createWriteStream(transcriptRefPtr.value);
|
|
42870
44060
|
const outputCbs = [];
|
|
42871
44061
|
const telemetryCbs = [];
|
|
42872
44062
|
const exitCbs = [];
|
|
@@ -42924,7 +44114,7 @@ function runOneShotText(job, cfg) {
|
|
|
42924
44114
|
stdio: ["ignore", "pipe", "pipe"]
|
|
42925
44115
|
});
|
|
42926
44116
|
const transcriptRefPtr = { value: path9.join(dir, "transcript.txt") };
|
|
42927
|
-
const transcriptOut =
|
|
44117
|
+
const transcriptOut = fs12.createWriteStream(transcriptRefPtr.value);
|
|
42928
44118
|
const outputCbs = [];
|
|
42929
44119
|
const exitCbs = [];
|
|
42930
44120
|
const { finish, exited } = makeFinish(id, cfg, transcriptRefPtr, transcriptOut, dir, exitCbs);
|
|
@@ -45667,9 +46857,18 @@ var PostgresChatSessionStore = class _PostgresChatSessionStore {
|
|
|
45667
46857
|
role text NOT NULL,
|
|
45668
46858
|
content text NOT NULL,
|
|
45669
46859
|
seq integer NOT NULL,
|
|
45670
|
-
created_at timestamptz NOT NULL
|
|
46860
|
+
created_at timestamptz NOT NULL,
|
|
46861
|
+
run_id text
|
|
45671
46862
|
)`);
|
|
45672
46863
|
await pool.query(`CREATE INDEX IF NOT EXISTS chat_messages_session_idx ON "${s2}".chat_messages (session_id, seq)`);
|
|
46864
|
+
await pool.query(`ALTER TABLE "${s2}".chat_messages ADD COLUMN IF NOT EXISTS run_id text`);
|
|
46865
|
+
await pool.query(`
|
|
46866
|
+
CREATE TABLE IF NOT EXISTS "${s2}".chat_session_work_orders (
|
|
46867
|
+
session_id text NOT NULL,
|
|
46868
|
+
work_order_id text NOT NULL,
|
|
46869
|
+
linked_at timestamptz NOT NULL DEFAULT now(),
|
|
46870
|
+
PRIMARY KEY (session_id, work_order_id)
|
|
46871
|
+
)`);
|
|
45673
46872
|
return new _PostgresChatSessionStore(pool, schema);
|
|
45674
46873
|
}
|
|
45675
46874
|
async createSession(s2) {
|
|
@@ -45719,9 +46918,9 @@ var PostgresChatSessionStore = class _PostgresChatSessionStore {
|
|
|
45719
46918
|
}
|
|
45720
46919
|
async appendMessage(m2) {
|
|
45721
46920
|
await this.pool.query(
|
|
45722
|
-
`INSERT INTO ${this.s}.chat_messages (id, session_id, role, content, seq, created_at)
|
|
45723
|
-
VALUES ($1,$2,$3,$4,$5,$6)`,
|
|
45724
|
-
[m2.id, m2.sessionId, m2.role, m2.content, m2.seq, m2.createdAt]
|
|
46921
|
+
`INSERT INTO ${this.s}.chat_messages (id, session_id, role, content, seq, created_at, run_id)
|
|
46922
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7)`,
|
|
46923
|
+
[m2.id, m2.sessionId, m2.role, m2.content, m2.seq, m2.createdAt, m2.runId ?? null]
|
|
45725
46924
|
);
|
|
45726
46925
|
}
|
|
45727
46926
|
async listMessages(sessionId, limit) {
|
|
@@ -45733,6 +46932,19 @@ var PostgresChatSessionStore = class _PostgresChatSessionStore {
|
|
|
45733
46932
|
const rows = r.rows.map(rowToMessage);
|
|
45734
46933
|
return limit ? rows.reverse() : rows;
|
|
45735
46934
|
}
|
|
46935
|
+
async linkWorkOrder(sessionId, workOrderId) {
|
|
46936
|
+
await this.pool.query(
|
|
46937
|
+
`INSERT INTO ${this.s}.chat_session_work_orders (session_id, work_order_id) VALUES ($1,$2) ON CONFLICT DO NOTHING`,
|
|
46938
|
+
[sessionId, workOrderId]
|
|
46939
|
+
);
|
|
46940
|
+
}
|
|
46941
|
+
async listWorkOrdersForSession(sessionId) {
|
|
46942
|
+
const r = await this.pool.query(
|
|
46943
|
+
`SELECT work_order_id FROM ${this.s}.chat_session_work_orders WHERE session_id = $1 ORDER BY linked_at`,
|
|
46944
|
+
[sessionId]
|
|
46945
|
+
);
|
|
46946
|
+
return r.rows.map((row) => row.work_order_id);
|
|
46947
|
+
}
|
|
45736
46948
|
};
|
|
45737
46949
|
var rowToSession = (row) => ({
|
|
45738
46950
|
id: row.id,
|
|
@@ -45750,7 +46962,8 @@ var rowToMessage = (row) => ({
|
|
|
45750
46962
|
role: row.role,
|
|
45751
46963
|
content: row.content,
|
|
45752
46964
|
seq: Number(row.seq),
|
|
45753
|
-
createdAt: new Date(row.created_at).toISOString()
|
|
46965
|
+
createdAt: new Date(row.created_at).toISOString(),
|
|
46966
|
+
...row.run_id != null ? { runId: row.run_id } : {}
|
|
45754
46967
|
});
|
|
45755
46968
|
|
|
45756
46969
|
// ../storage/src/postgres-nodes.ts
|
|
@@ -45927,18 +47140,42 @@ var rowToRuntime = (row) => ({
|
|
|
45927
47140
|
init_src3();
|
|
45928
47141
|
init_trajectory();
|
|
45929
47142
|
var DEFAULT_CHAT_SKILLS = ["build-workorder"];
|
|
47143
|
+
var SESSION_TOKEN_GRACE_MS = 15 * 6e4;
|
|
47144
|
+
function normalizeHttpBaseUrl(raw) {
|
|
47145
|
+
const url = new URL(raw);
|
|
47146
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
47147
|
+
throw new Error(`public URL \u5FC5\u987B\u662F http(s): ${raw}`);
|
|
47148
|
+
}
|
|
47149
|
+
url.hash = "";
|
|
47150
|
+
url.search = "";
|
|
47151
|
+
const out = url.toString();
|
|
47152
|
+
return out.endsWith("/") ? out.slice(0, -1) : out;
|
|
47153
|
+
}
|
|
47154
|
+
function apiUrlFromGatewayUrl(raw) {
|
|
47155
|
+
if (!raw) return void 0;
|
|
47156
|
+
try {
|
|
47157
|
+
const url = new URL(raw);
|
|
47158
|
+
if (url.protocol === "wss:") url.protocol = "https:";
|
|
47159
|
+
else if (url.protocol === "ws:") url.protocol = "http:";
|
|
47160
|
+
else if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
|
|
47161
|
+
url.pathname = url.pathname.replace(/\/node-gateway\/?$/, "") || "/";
|
|
47162
|
+
return normalizeHttpBaseUrl(url.toString());
|
|
47163
|
+
} catch {
|
|
47164
|
+
return void 0;
|
|
47165
|
+
}
|
|
47166
|
+
}
|
|
45930
47167
|
var oasisWrapperCache;
|
|
45931
47168
|
function oasisWrapperScript() {
|
|
45932
|
-
if (oasisWrapperCache &&
|
|
47169
|
+
if (oasisWrapperCache && fs14.existsSync(oasisWrapperCache)) return oasisWrapperCache;
|
|
45933
47170
|
const repoRoot = (0, import_node_url3.fileURLToPath)(new URL("../../../", __esm_import_meta_url));
|
|
45934
47171
|
const tsx = path11.join(repoRoot, "node_modules/.bin/tsx");
|
|
45935
47172
|
const main = path11.join(repoRoot, "packages/cli/src/main.ts");
|
|
45936
|
-
const dir =
|
|
47173
|
+
const dir = fs14.mkdtempSync(path11.join(os6.tmpdir(), "oasis-cli-"));
|
|
45937
47174
|
const wrapper = path11.join(dir, "oasis");
|
|
45938
|
-
|
|
47175
|
+
fs14.writeFileSync(wrapper, `#!/usr/bin/env bash
|
|
45939
47176
|
exec ${JSON.stringify(tsx)} ${JSON.stringify(main)} "$@"
|
|
45940
47177
|
`);
|
|
45941
|
-
|
|
47178
|
+
fs14.chmodSync(wrapper, 493);
|
|
45942
47179
|
oasisWrapperCache = wrapper;
|
|
45943
47180
|
return wrapper;
|
|
45944
47181
|
}
|
|
@@ -45995,13 +47232,14 @@ async function buildActorContext(service, actorId) {
|
|
|
45995
47232
|
function traceRuntimeKind(runtimeKind) {
|
|
45996
47233
|
if (runtimeKind === "claude" || runtimeKind === "claude-code") return "claude-code";
|
|
45997
47234
|
if (runtimeKind === "codex") return "codex";
|
|
47235
|
+
if (runtimeKind === "hermes") return "hermes";
|
|
45998
47236
|
return "custom";
|
|
45999
47237
|
}
|
|
46000
47238
|
async function startServe(opts) {
|
|
46001
|
-
|
|
47239
|
+
fs14.mkdirSync(opts.dir, { recursive: true });
|
|
46002
47240
|
const lock = path11.join(opts.dir, "serve.lock");
|
|
46003
|
-
if (
|
|
46004
|
-
const pid = Number(
|
|
47241
|
+
if (fs14.existsSync(lock)) {
|
|
47242
|
+
const pid = Number(fs14.readFileSync(lock, "utf8"));
|
|
46005
47243
|
let alive = false;
|
|
46006
47244
|
try {
|
|
46007
47245
|
process.kill(pid, 0);
|
|
@@ -46009,12 +47247,12 @@ async function startServe(opts) {
|
|
|
46009
47247
|
} catch {
|
|
46010
47248
|
}
|
|
46011
47249
|
if (alive) throw new Error(`\u8BE5\u6570\u636E\u76EE\u5F55\u5DF2\u6709 serve \u5728\u8DD1\uFF08pid=${pid}\uFF09\u2014\u2014\u5355\u5199\u8005\u8FDB\u7A0B\uFF0C\u4E0D\u80FD\u53CC\u5F00`);
|
|
46012
|
-
|
|
47250
|
+
fs14.unlinkSync(lock);
|
|
46013
47251
|
}
|
|
46014
|
-
|
|
47252
|
+
fs14.writeFileSync(lock, String(process.pid));
|
|
46015
47253
|
const loadJson = (name) => {
|
|
46016
47254
|
const file = path11.join(opts.dir, name);
|
|
46017
|
-
return
|
|
47255
|
+
return fs14.existsSync(file) ? JSON.parse(fs14.readFileSync(file, "utf8")) : void 0;
|
|
46018
47256
|
};
|
|
46019
47257
|
const oplog = await NdjsonOplogStore.open(path11.join(opts.dir, "oplog.ndjson"));
|
|
46020
47258
|
const blobs = new DirBlobStore(path11.join(opts.dir, "blobs"));
|
|
@@ -46059,16 +47297,16 @@ async function startServe(opts) {
|
|
|
46059
47297
|
const issuer = createTokenIssuer();
|
|
46060
47298
|
const operatorActor = `actor:human:${os6.userInfo().username}`;
|
|
46061
47299
|
const operatorToken = issuer.issue(operatorActor);
|
|
46062
|
-
|
|
47300
|
+
fs14.writeFileSync(path11.join(opts.dir, "operator-token"), operatorToken, { mode: 384 });
|
|
46063
47301
|
let nodeTokens = createNodeTokenStore(path11.join(opts.dir, "node-tokens.json"));
|
|
46064
47302
|
const enrollTokens = createEnrollTokenStore(30 * 60 * 1e3, path11.join(opts.dir, "enroll-tokens.json"));
|
|
46065
47303
|
let nodeStore = await FileNodeStore.open(path11.join(opts.dir, "nodes.json"));
|
|
46066
47304
|
const registryAuditFile = path11.join(opts.dir, "registry-audit.ndjson");
|
|
46067
47305
|
const registryListeners = /* @__PURE__ */ new Set();
|
|
46068
|
-
const registryAudit = (
|
|
46069
|
-
|
|
47306
|
+
const registryAudit = (record4) => {
|
|
47307
|
+
fs14.appendFile(registryAuditFile, JSON.stringify(record4) + "\n", () => {
|
|
46070
47308
|
});
|
|
46071
|
-
for (const l of registryListeners) l({ kind:
|
|
47309
|
+
for (const l of registryListeners) l({ kind: record4.kind, actor: record4.actor, target: record4.target, timestamp: record4.timestamp });
|
|
46072
47310
|
};
|
|
46073
47311
|
await syncRegistryRolesToKernel(registryStore, kernel);
|
|
46074
47312
|
const actors = createActorsDomain({
|
|
@@ -46150,20 +47388,22 @@ async function startServe(opts) {
|
|
|
46150
47388
|
console.log("[serve] \u8FD0\u884C\u8F68\u8FF9\u4F53\u7CFB\uFF1A\u672C\u5730 ndjson dev store");
|
|
46151
47389
|
}
|
|
46152
47390
|
const trace = createTraceDomain({ store: traceStore });
|
|
46153
|
-
const traceStoreSink = new TraceStoreSink({ store: traceStore, runtimeKind: "
|
|
47391
|
+
const traceStoreSink = new TraceStoreSink({ store: traceStore, runtimeKind: "auto" });
|
|
46154
47392
|
const automationStore = pgPool ? await PostgresAutomationStore.open(pgPool, pgSchema) : new MemoryAutomationStore();
|
|
46155
47393
|
console.log(`[serve] \u81EA\u52A8\u5316\u4F53\u7CFB\uFF1A${pgPool ? `Postgres schema=${pgSchema}` : "\u5185\u5B58 dev store"}`);
|
|
46156
47394
|
const automations = createAutomationsDomain({ store: automationStore, kernel });
|
|
46157
47395
|
const controlPlaneStore = await FileControlPlaneStore.open(path11.join(opts.dir, "control-plane.json"));
|
|
46158
|
-
const companies = createCompaniesDomain({ store: controlPlaneStore, blobs });
|
|
46159
|
-
const jwtSecret = process.env["OASIS_JWT_SECRET"];
|
|
46160
|
-
if (!jwtSecret) console.warn("[serve] OASIS_JWT_SECRET \u672A\u8BBE\u2014\u2014\u7528 dev \u9ED8\u8BA4\u5BC6\u94A5\uFF0C\u751F\u4EA7\u52A1\u5FC5\u914D\u7F6E\uFF08\u5426\u5219\u4F1A\u8BDD\u53EF\u4F2A\u9020\uFF09");
|
|
46161
47396
|
const resendKey = process.env["RESEND_API_KEY"];
|
|
46162
47397
|
const mailFrom = process.env["RESEND_FROM_EMAIL"] ?? process.env["OASIS_MAIL_FROM"] ?? "noreply@open-friday.com";
|
|
46163
|
-
if (!resendKey) console.warn("[serve] RESEND_API_KEY \u672A\u8BBE\u2014\u2014\u9A8C\u8BC1\u7801\u6253\u5370\u5230\u65E5\u5FD7\uFF08dev\uFF09\uFF1B\u751F\u4EA7\u8BF7\u914D RESEND_API_KEY + RESEND_FROM_EMAIL\uFF08Resend \u5DF2\u9A8C\u8BC1\u57DF\u540D\uFF09");
|
|
47398
|
+
if (!resendKey) console.warn("[serve] RESEND_API_KEY \u672A\u8BBE\u2014\u2014\u9A8C\u8BC1\u7801/\u9080\u8BF7\u6253\u5370\u5230\u65E5\u5FD7\uFF08dev\uFF09\uFF1B\u751F\u4EA7\u8BF7\u914D RESEND_API_KEY + RESEND_FROM_EMAIL\uFF08Resend \u5DF2\u9A8C\u8BC1\u57DF\u540D\uFF09");
|
|
47399
|
+
const mailSender = resendKey ? resendSender({ apiKey: resendKey, from: mailFrom }) : consoleSender();
|
|
47400
|
+
const consoleUrl = process.env["OASIS_CONSOLE_URL"] ?? "https://design.open-friday.com/oasis-preview/";
|
|
47401
|
+
const companies = createCompaniesDomain({ store: controlPlaneStore, blobs, sender: mailSender, consoleUrl });
|
|
47402
|
+
const jwtSecret = process.env["OASIS_JWT_SECRET"];
|
|
47403
|
+
if (!jwtSecret) console.warn("[serve] OASIS_JWT_SECRET \u672A\u8BBE\u2014\u2014\u7528 dev \u9ED8\u8BA4\u5BC6\u94A5\uFF0C\u751F\u4EA7\u52A1\u5FC5\u914D\u7F6E\uFF08\u5426\u5219\u4F1A\u8BDD\u53EF\u4F2A\u9020\uFF09");
|
|
46164
47404
|
const auth = createAuth({
|
|
46165
47405
|
store: controlPlaneStore,
|
|
46166
|
-
sender:
|
|
47406
|
+
sender: mailSender,
|
|
46167
47407
|
jwtSecret: jwtSecret ?? "oasis-dev-secret-change-me",
|
|
46168
47408
|
// dev 经 http/vite 反代,置 Secure 会丢 cookie;生产经 https 应由部署设 OASIS_SECURE_COOKIE=1
|
|
46169
47409
|
secureCookie: process.env["OASIS_SECURE_COOKIE"] === "1",
|
|
@@ -46171,11 +47411,7 @@ async function startServe(opts) {
|
|
|
46171
47411
|
provisionAccount: (email2) => companies.service.ensureAccount(email2)
|
|
46172
47412
|
});
|
|
46173
47413
|
await migrateDefaultCompany(controlPlaneStore, await actors.service.listActors());
|
|
46174
|
-
|
|
46175
|
-
if (!await controlPlaneStore.getMember(DEFAULT_COMPANY_ID, devActor)) {
|
|
46176
|
-
await controlPlaneStore.upsertMember({ companyId: DEFAULT_COMPANY_ID, accountId: devActor, actorId: devActor, role: "member" });
|
|
46177
|
-
}
|
|
46178
|
-
}
|
|
47414
|
+
await bootstrapBuildWorkorderSkill(actors.service).catch((err) => console.warn(`[serve] build-workorder skill bootstrap failed: ${String(err)}`));
|
|
46179
47415
|
if (!await controlPlaneStore.getMember(DEFAULT_COMPANY_ID, operatorActor)) {
|
|
46180
47416
|
await controlPlaneStore.upsertMember({ companyId: DEFAULT_COMPANY_ID, accountId: operatorActor, actorId: operatorActor, role: "owner" });
|
|
46181
47417
|
}
|
|
@@ -46207,12 +47443,12 @@ async function startServe(opts) {
|
|
|
46207
47443
|
return auth.sessionAccountId(headers);
|
|
46208
47444
|
};
|
|
46209
47445
|
const interventionDrafts = new InterventionDraftStore();
|
|
46210
|
-
const workorderDrafts = new WorkorderDraftStore();
|
|
47446
|
+
const workorderDrafts = new WorkorderDraftStore(path11.join(opts.dir, "workorder-drafts.json"));
|
|
46211
47447
|
const journalFile = path11.join(opts.dir, "dispatch-journal.ndjson");
|
|
46212
47448
|
const journalRing = [];
|
|
46213
|
-
if (
|
|
47449
|
+
if (fs14.existsSync(journalFile)) {
|
|
46214
47450
|
try {
|
|
46215
|
-
const lines =
|
|
47451
|
+
const lines = fs14.readFileSync(journalFile, "utf8").split(/\r?\n/).filter(Boolean);
|
|
46216
47452
|
for (const line of lines.slice(-500)) {
|
|
46217
47453
|
try {
|
|
46218
47454
|
journalRing.push(JSON.parse(line));
|
|
@@ -46225,9 +47461,43 @@ async function startServe(opts) {
|
|
|
46225
47461
|
const journal = (entry) => {
|
|
46226
47462
|
journalRing.push(entry);
|
|
46227
47463
|
if (journalRing.length > 500) journalRing.shift();
|
|
46228
|
-
|
|
47464
|
+
fs14.appendFile(journalFile, JSON.stringify(entry) + "\n", () => {
|
|
46229
47465
|
});
|
|
46230
47466
|
};
|
|
47467
|
+
const closeOrphanedDispatches = () => {
|
|
47468
|
+
const open = /* @__PURE__ */ new Map();
|
|
47469
|
+
for (const entry of journalRing) {
|
|
47470
|
+
if (entry.kind === "dispatched") {
|
|
47471
|
+
open.set(entry.jobKey, entry);
|
|
47472
|
+
} else if (entry.kind === "exited" || entry.kind === "fused") {
|
|
47473
|
+
open.delete(entry.jobKey);
|
|
47474
|
+
}
|
|
47475
|
+
}
|
|
47476
|
+
if (open.size === 0) return;
|
|
47477
|
+
const now = Date.now();
|
|
47478
|
+
const at = new Date(now).toISOString();
|
|
47479
|
+
for (const entry of open.values()) {
|
|
47480
|
+
const startedAt = Date.parse(entry.at);
|
|
47481
|
+
const lastOpAt = kernel.model.lastOpAt.get(entry.artifactId);
|
|
47482
|
+
const lastOpTime = lastOpAt ? Date.parse(lastOpAt) : NaN;
|
|
47483
|
+
const progressed = Number.isFinite(startedAt) && Number.isFinite(lastOpTime) && lastOpTime > startedAt;
|
|
47484
|
+
journal({
|
|
47485
|
+
kind: "exited",
|
|
47486
|
+
at,
|
|
47487
|
+
jobKey: entry.jobKey,
|
|
47488
|
+
artifactId: entry.artifactId,
|
|
47489
|
+
actor: entry.actor,
|
|
47490
|
+
action: entry.action,
|
|
47491
|
+
code: null,
|
|
47492
|
+
reason: "timeout",
|
|
47493
|
+
durationMs: Number.isFinite(startedAt) ? Math.max(0, now - startedAt) : 0,
|
|
47494
|
+
progressed,
|
|
47495
|
+
consecutiveFailures: progressed ? 0 : 1
|
|
47496
|
+
});
|
|
47497
|
+
}
|
|
47498
|
+
console.warn(`[dispatch] closed ${open.size} orphaned dispatch journal entr${open.size === 1 ? "y" : "ies"} from a previous server process`);
|
|
47499
|
+
};
|
|
47500
|
+
if (opts.dispatch) closeOrphanedDispatches();
|
|
46231
47501
|
let dispatcher;
|
|
46232
47502
|
const trajListeners = /* @__PURE__ */ new Set();
|
|
46233
47503
|
const trajectorySink = new FsTrajectorySink(opts.dir, (msg) => {
|
|
@@ -46250,6 +47520,27 @@ async function startServe(opts) {
|
|
|
46250
47520
|
let hub = null;
|
|
46251
47521
|
let chatRemoteAdapter = null;
|
|
46252
47522
|
let localUrl = "";
|
|
47523
|
+
const remoteApiUrl = opts.publicUrl ? normalizeHttpBaseUrl(opts.publicUrl) : apiUrlFromGatewayUrl(opts.gatewayUrl);
|
|
47524
|
+
const serverUrlForBinding = (binding) => {
|
|
47525
|
+
if (!binding?.nodeId) return localUrl;
|
|
47526
|
+
if (remoteApiUrl) return remoteApiUrl;
|
|
47527
|
+
throw new Error(`\u8FDC\u7A0B\u8282\u70B9 ${binding.nodeId} \u9700\u8981\u53EF\u8FBE\u7684 Oasis API \u5730\u5740\uFF1A\u914D\u7F6E --public-url / $OASIS_PUBLIC_URL\uFF0C\u6216\u4F7F\u7528\u53EF\u63A8\u5BFC\u7684 --gateway-url`);
|
|
47528
|
+
};
|
|
47529
|
+
const issueSessionToken = (actorId, ctx) => {
|
|
47530
|
+
const token = issuer.issue(actorId, {
|
|
47531
|
+
ttlMs: ctx.limits.wallClockMs + SESSION_TOKEN_GRACE_MS,
|
|
47532
|
+
artifactId: ctx.artifactId,
|
|
47533
|
+
action: ctx.action,
|
|
47534
|
+
...ctx.sessionId !== void 0 ? { sessionId: ctx.sessionId } : {},
|
|
47535
|
+
...ctx.binding?.nodeId !== void 0 ? { nodeId: ctx.binding.nodeId } : {},
|
|
47536
|
+
...ctx.binding?.runtimeKind !== void 0 ? { runtimeKind: ctx.binding.runtimeKind } : {},
|
|
47537
|
+
scopes: ctx.scopes ?? ["oasis:api"]
|
|
47538
|
+
});
|
|
47539
|
+
if (issuer.resolve(token) !== actorId) {
|
|
47540
|
+
throw new Error(`\u521A\u7B7E\u53D1\u7684 OASIS_TOKEN \u65E0\u6CD5\u89E3\u6790\u4E3A ${actorId}`);
|
|
47541
|
+
}
|
|
47542
|
+
return token;
|
|
47543
|
+
};
|
|
46253
47544
|
let resolveCodeRepoRef;
|
|
46254
47545
|
const server = await startOasisServer({
|
|
46255
47546
|
kernel: engine.kernel,
|
|
@@ -46297,15 +47588,17 @@ async function startServe(opts) {
|
|
|
46297
47588
|
console.warn(`[planner] skill materialize failed for ${planner.id}: ${String(err)}`);
|
|
46298
47589
|
return {};
|
|
46299
47590
|
}));
|
|
47591
|
+
const limits = { wallClockMs: 5 * 6e4 };
|
|
47592
|
+
const artifactId = `artifact:planner:${(0, import_node_crypto20.randomUUID)()}`;
|
|
46300
47593
|
const handle = await chatRemoteAdapter.spawn({
|
|
46301
47594
|
actor: planner.id,
|
|
46302
|
-
actorToken:
|
|
46303
|
-
artifactId
|
|
47595
|
+
actorToken: issueSessionToken(planner.id, { artifactId, action: "plan-workorder", limits, binding }),
|
|
47596
|
+
artifactId,
|
|
46304
47597
|
bundle: { files },
|
|
46305
47598
|
...actorCtx?.config?.prompt ? { systemPrompt: actorCtx.config.prompt } : {},
|
|
46306
|
-
server: { url:
|
|
47599
|
+
server: { url: serverUrlForBinding(binding) },
|
|
46307
47600
|
binding,
|
|
46308
|
-
limits
|
|
47601
|
+
limits,
|
|
46309
47602
|
env: provision.env,
|
|
46310
47603
|
wrapperPaths: provision.wrapperPaths
|
|
46311
47604
|
});
|
|
@@ -46339,15 +47632,18 @@ async function startServe(opts) {
|
|
|
46339
47632
|
registry: registryStore,
|
|
46340
47633
|
artifacts: artifactStateStore,
|
|
46341
47634
|
dispatchJournal: () => journalRing.slice(-200),
|
|
47635
|
+
workorderDrafts,
|
|
47636
|
+
// 活工单查不到时回退查建单草案(status="draft")
|
|
46342
47637
|
...opts.sla ? { sla: opts.sla } : {},
|
|
46343
47638
|
resolveEngine: (cid) => engineRouter.getEngine(cid ?? DEFAULT_COMPANY_ID)
|
|
46344
47639
|
}),
|
|
46345
47640
|
trace.register,
|
|
46346
47641
|
automations.register,
|
|
46347
|
-
...chatSessionStore ? [createChatSessionsDomain({ store: chatSessionStore, registry: registryStore })] : []
|
|
47642
|
+
...chatSessionStore ? [createChatSessionsDomain({ store: chatSessionStore, registry: registryStore, trace: traceStore, kernel, artifactState: artifactStateStore })] : []
|
|
46348
47643
|
],
|
|
46349
47644
|
drafts: interventionDrafts,
|
|
46350
47645
|
workorderDrafts,
|
|
47646
|
+
...chatSessionStore ? { chatSession: chatSessionStore } : {},
|
|
46351
47647
|
// 暴露注册的 artifact 类型,给前端「发起工单」动态拉取根产物可选类型——schema.json 增改即自动扩展,
|
|
46352
47648
|
// 前端不再写死枚举。走通用 /api/view(generic data),非域路由契约,不进 gen:api。
|
|
46353
47649
|
views: {
|
|
@@ -46367,6 +47663,7 @@ async function startServe(opts) {
|
|
|
46367
47663
|
resolveActorEnv: async (actorId) => (await buildActorProvision(actors.service, actorId)).env,
|
|
46368
47664
|
resolveActorContext: (actorId) => buildActorContext(actors.service, actorId),
|
|
46369
47665
|
materializeSkills: (actorId, runtimeKind) => materializeSkillFiles({ service: actors.service, blobs: assets, actorId, runtimeKind }),
|
|
47666
|
+
uploadChatAttachment: async ({ contentBase64 }) => ({ blobRef: await assets.put(new Uint8Array(Buffer.from(contentBase64, "base64"))) }),
|
|
46370
47667
|
dispatchProduce: async ({ artifactId, actorId, part }) => {
|
|
46371
47668
|
if (!dispatcher) {
|
|
46372
47669
|
throw new ApiError(503, "DISPATCH_NOT_ENABLED", "\u670D\u52A1\u7AEF\u672A\u542F\u7528 --dispatch true\uFF0C\u4E0D\u80FD\u62C9\u8D77\u771F\u5B9E\u667A\u80FD\u4F53\u4FEE\u6539");
|
|
@@ -46377,7 +47674,7 @@ async function startServe(opts) {
|
|
|
46377
47674
|
...part !== void 0 ? { part } : {}
|
|
46378
47675
|
});
|
|
46379
47676
|
},
|
|
46380
|
-
dispatchChat: async ({ actorId, message, sessionId }) => {
|
|
47677
|
+
dispatchChat: async ({ actorId, message, sessionId, chatSessionId, attachments }) => {
|
|
46381
47678
|
const binding = await actors.service.resolveBinding()(actorId);
|
|
46382
47679
|
if (!binding) {
|
|
46383
47680
|
throw new ApiError(409, "ACTOR_BINDING_MISSING", `\u6570\u5B57\u5458\u5DE5 ${actorId} \u6CA1\u6709 active runtime \u7ED1\u5B9A`);
|
|
@@ -46450,6 +47747,29 @@ async function startServe(opts) {
|
|
|
46450
47747
|
};
|
|
46451
47748
|
const provision = await buildActorProvision(actors.service, actorId);
|
|
46452
47749
|
const actorCtx = await buildActorContext(actors.service, actorId).catch(() => null);
|
|
47750
|
+
const attachmentFiles = {};
|
|
47751
|
+
const attachmentBinary = {};
|
|
47752
|
+
const attachmentPaths = [];
|
|
47753
|
+
const usedRel = /* @__PURE__ */ new Set();
|
|
47754
|
+
for (const att of attachments ?? []) {
|
|
47755
|
+
const safe = (att.name || "attachment").replace(/[^\w.\-]+/g, "_").replace(/^\.+/, "").slice(0, 80) || "attachment";
|
|
47756
|
+
let rel = `inputs/attachments/${safe}`;
|
|
47757
|
+
let n = 1;
|
|
47758
|
+
while (usedRel.has(rel)) rel = `inputs/attachments/${n++}-${safe}`;
|
|
47759
|
+
usedRel.add(rel);
|
|
47760
|
+
if (typeof att.text === "string") {
|
|
47761
|
+
attachmentFiles[rel] = att.text;
|
|
47762
|
+
attachmentPaths.push(rel);
|
|
47763
|
+
} else if (att.blobRef) {
|
|
47764
|
+
try {
|
|
47765
|
+
const bytes = await assets.get(att.blobRef);
|
|
47766
|
+
attachmentBinary[rel] = Buffer.from(bytes).toString("base64");
|
|
47767
|
+
attachmentPaths.push(rel);
|
|
47768
|
+
} catch (err) {
|
|
47769
|
+
console.warn(`[chat] \u9644\u4EF6 blob ${att.blobRef} \u8BFB\u53D6\u5931\u8D25: ${String(err)}`);
|
|
47770
|
+
}
|
|
47771
|
+
}
|
|
47772
|
+
}
|
|
46453
47773
|
const files = {
|
|
46454
47774
|
"TASK.md": [
|
|
46455
47775
|
"# \u5373\u65F6\u8111\u66B4\u5BF9\u8BDD",
|
|
@@ -46458,12 +47778,14 @@ async function startServe(opts) {
|
|
|
46458
47778
|
`actor_id: ${actorId}`,
|
|
46459
47779
|
"",
|
|
46460
47780
|
"\u8BF7\u76F4\u63A5\u56DE\u7B54\u7528\u6237\u95EE\u9898\u3002\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\u5199\u5165\u7CFB\u7EDF\uFF0C\u5426\u5219\u4E0D\u8981\u521B\u5EFA\u6216\u4FEE\u6539 artifact\u3002",
|
|
47781
|
+
...attachmentPaths.length ? ["", "## \u7528\u6237\u9644\u4EF6", "", "\u7528\u6237\u968F\u6D88\u606F\u9644\u5E26\u4E86\u4EE5\u4E0B\u6587\u4EF6\uFF08\u5728\u4F60\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF0C\u53EF\u76F4\u63A5\u8BFB\u53D6\uFF09\uFF1A", ...attachmentPaths.map((p2) => `- ${p2}`)] : [],
|
|
46461
47782
|
"",
|
|
46462
47783
|
"## \u7528\u6237\u6D88\u606F",
|
|
46463
47784
|
"",
|
|
46464
47785
|
message
|
|
46465
47786
|
].join("\n")
|
|
46466
47787
|
};
|
|
47788
|
+
Object.assign(files, attachmentFiles);
|
|
46467
47789
|
if (actorCtx?.config?.prompt) {
|
|
46468
47790
|
Object.assign(files, splitIdentityFiles3(actorCtx.config.prompt));
|
|
46469
47791
|
}
|
|
@@ -46477,18 +47799,23 @@ async function startServe(opts) {
|
|
|
46477
47799
|
console.warn(`[chat] skill materialize failed for ${actorId}: ${String(err)}`);
|
|
46478
47800
|
return {};
|
|
46479
47801
|
}));
|
|
47802
|
+
const limits = { wallClockMs: 10 * 6e4 };
|
|
47803
|
+
const artifactId = `artifact:chat:${(0, import_node_crypto20.randomUUID)()}`;
|
|
46480
47804
|
const handle = await chatRemoteAdapter.spawn({
|
|
46481
47805
|
actor: actorId,
|
|
46482
|
-
actorToken:
|
|
46483
|
-
artifactId
|
|
46484
|
-
bundle: { files },
|
|
47806
|
+
actorToken: issueSessionToken(actorId, { artifactId, action: "chat", limits, binding, sessionId: runtimeSessionId }),
|
|
47807
|
+
artifactId,
|
|
47808
|
+
bundle: { files, ...Object.keys(attachmentBinary).length ? { binaryFiles: attachmentBinary } : {} },
|
|
46485
47809
|
...actorCtx?.config?.prompt ? { systemPrompt: actorCtx.config.prompt } : {},
|
|
46486
47810
|
runtimeSessionId,
|
|
46487
47811
|
resumeRuntimeSession,
|
|
46488
|
-
server: { url:
|
|
47812
|
+
server: { url: serverUrlForBinding(binding) },
|
|
46489
47813
|
binding,
|
|
46490
|
-
limits
|
|
46491
|
-
|
|
47814
|
+
limits,
|
|
47815
|
+
// OASIS_STAGE=1:chat agent 的改图命令(edit/link/spawn/cancel)经 /api/stage → 草案路由
|
|
47816
|
+
// (建单/改图暂存统一,见 docs/proposals/draft-edit-unification.md)。不设 OASIS_WORKSPACE——
|
|
47817
|
+
// chat agent 不绑单一工单,按命令 --workspace / id 定位是哪份草案。
|
|
47818
|
+
env: { ...provision.env, OASIS_STAGE: "1", ...chatSessionId ? { OASIS_CHAT_SESSION_ID: chatSessionId } : {} },
|
|
46492
47819
|
wrapperPaths: provision.wrapperPaths
|
|
46493
47820
|
});
|
|
46494
47821
|
const buffered = [];
|
|
@@ -46531,6 +47858,7 @@ async function startServe(opts) {
|
|
|
46531
47858
|
});
|
|
46532
47859
|
return {
|
|
46533
47860
|
id: runtimeSessionId,
|
|
47861
|
+
runId: traceRunId,
|
|
46534
47862
|
onOutput(cb) {
|
|
46535
47863
|
for (const chunk of buffered.splice(0)) cb(chunk);
|
|
46536
47864
|
outputCbs.push(cb);
|
|
@@ -46561,6 +47889,7 @@ async function startServe(opts) {
|
|
|
46561
47889
|
chatRemoteAdapter = new DaemonHubAdapter(hub);
|
|
46562
47890
|
const port = new URL(server.baseUrl).port;
|
|
46563
47891
|
localUrl = `http://127.0.0.1:${port}`;
|
|
47892
|
+
console.log(`[serve] \u8FDC\u7A0B agent API URL\uFF1A${remoteApiUrl ?? "\u672A\u914D\u7F6E\uFF08\u4EC5\u652F\u6301\u672C\u673A runtime\uFF09"}`);
|
|
46564
47893
|
const projectsCfg = loadJson("projects.json");
|
|
46565
47894
|
const seedProjects = (projectsCfg?.projects ?? []).map(
|
|
46566
47895
|
(p2) => p2.repos?.length ? { ...p2, repos: p2.repos.map((r) => r.repoDir ? { ...r, repoDir: path11.resolve(opts.dir, r.repoDir) } : r) } : p2
|
|
@@ -46608,6 +47937,7 @@ async function startServe(opts) {
|
|
|
46608
47937
|
remote: new DaemonHubAdapter(hub)
|
|
46609
47938
|
}),
|
|
46610
47939
|
serverUrl: localUrl,
|
|
47940
|
+
serverUrlFor: (ctx) => serverUrlForBinding(ctx.binding),
|
|
46611
47941
|
resolveBinding: actors.service.resolveBinding(),
|
|
46612
47942
|
provision: (actorId) => buildActorProvision(actors.service, actorId),
|
|
46613
47943
|
resolveActorContext: (actorId) => buildActorContext(actors.service, actorId),
|
|
@@ -46617,7 +47947,13 @@ async function startServe(opts) {
|
|
|
46617
47947
|
// §4/C:异步从权威源(store)解析;dispatcher 装配前 await
|
|
46618
47948
|
// #2 派单开关:工单"待派发"(控制面旁存标 held)时不派其活。held 集通常空、表极小,每次查可接受。
|
|
46619
47949
|
resolveDispatchHold: async (ws) => (await artifactStateStore.listWorkspaceDispatchHeld()).includes(ws),
|
|
46620
|
-
tokenFor: (actor) =>
|
|
47950
|
+
tokenFor: (actor, ctx) => issueSessionToken(actor, {
|
|
47951
|
+
artifactId: ctx.artifactId,
|
|
47952
|
+
action: ctx.action,
|
|
47953
|
+
limits: ctx.limits,
|
|
47954
|
+
...ctx.binding !== void 0 ? { binding: ctx.binding } : {},
|
|
47955
|
+
...ctx.part !== void 0 ? { sessionId: `${ctx.action}:${ctx.artifactId}:${ctx.part}` } : {}
|
|
47956
|
+
}),
|
|
46621
47957
|
journal,
|
|
46622
47958
|
// TrajectoryEvent 是唯一事件模型;trace 旁账与 M7 蒸馏库并行消费同一流(§16.D 收口)。
|
|
46623
47959
|
trajectory: combineTrajectorySinks([trajectorySink, traceStoreSink]),
|
|
@@ -46758,14 +48094,24 @@ async function startServe(opts) {
|
|
|
46758
48094
|
const coordinator = new CoordinatorWorker({
|
|
46759
48095
|
kernel,
|
|
46760
48096
|
oplog,
|
|
46761
|
-
adapter: new
|
|
46762
|
-
|
|
46763
|
-
|
|
46764
|
-
|
|
46765
|
-
|
|
48097
|
+
adapter: new BindingRouterAdapter({
|
|
48098
|
+
local: new ConnectorAwareAdapter({
|
|
48099
|
+
streamJson: true,
|
|
48100
|
+
maxOutputTokens: opts.maxOutputTokens ?? 64e3,
|
|
48101
|
+
cleanupWorkdir: true,
|
|
48102
|
+
...opts.model ? { model: opts.model } : {}
|
|
48103
|
+
}),
|
|
48104
|
+
remote: new DaemonHubAdapter(hub)
|
|
46766
48105
|
}),
|
|
46767
48106
|
serverUrl: localUrl,
|
|
46768
|
-
|
|
48107
|
+
serverUrlFor: (ctx) => serverUrlForBinding(ctx.binding),
|
|
48108
|
+
tokenFor: (actor, ctx) => issueSessionToken(actor, {
|
|
48109
|
+
artifactId: ctx?.artifactId ?? "artifact:coordinator",
|
|
48110
|
+
action: ctx?.action ?? "coordinate",
|
|
48111
|
+
limits: ctx?.limits ?? { wallClockMs: 5 * 6e4 },
|
|
48112
|
+
...ctx?.binding !== void 0 ? { binding: ctx.binding } : {}
|
|
48113
|
+
}),
|
|
48114
|
+
resolveCoordinator: () => resolveCoordinator(registryStore),
|
|
46769
48115
|
schemaTypes: schema.map((d) => d.name),
|
|
46770
48116
|
schemaTypeDescriptions: Object.fromEntries(schema.filter((d) => d.description).map((d) => [d.name, d.description])),
|
|
46771
48117
|
provision: (actorId) => buildActorProvision(actors.service, actorId),
|
|
@@ -46821,7 +48167,7 @@ async function startServe(opts) {
|
|
|
46821
48167
|
await server.close();
|
|
46822
48168
|
if (pgPool) await pgPool.end().catch(() => {
|
|
46823
48169
|
});
|
|
46824
|
-
|
|
48170
|
+
fs14.rmSync(lock, { force: true });
|
|
46825
48171
|
}
|
|
46826
48172
|
};
|
|
46827
48173
|
}
|
|
@@ -46869,8 +48215,22 @@ function log2(tag, msg, extra) {
|
|
|
46869
48215
|
// ../cli/src/daemon/ws-client.ts
|
|
46870
48216
|
var RECONNECT_DELAY_MS = 3e3;
|
|
46871
48217
|
var PING_INTERVAL_MS = 2e4;
|
|
48218
|
+
async function preflightOasisAccess(job) {
|
|
48219
|
+
const base = job.server.url.replace(/\/+$/, "");
|
|
48220
|
+
try {
|
|
48221
|
+
const res = await fetch(`${base}/api/whoami`, {
|
|
48222
|
+
headers: { authorization: `Bearer ${job.actorToken}` },
|
|
48223
|
+
signal: AbortSignal.timeout(5e3)
|
|
48224
|
+
});
|
|
48225
|
+
if (res.status === 401 || res.status === 403) return { code: null, reason: "auth-error" };
|
|
48226
|
+
if (!res.ok) return { code: null, reason: "server-unreachable" };
|
|
48227
|
+
return null;
|
|
48228
|
+
} catch {
|
|
48229
|
+
return { code: null, reason: "server-unreachable" };
|
|
48230
|
+
}
|
|
48231
|
+
}
|
|
46872
48232
|
var DaemonWsClient = class {
|
|
46873
|
-
constructor(serverUrl, daemonId, sessions, adapters = ["claude-code"], token, runtimes = adapters.map((kind) => ({ kind })), nodeName, reportRuntimes =
|
|
48233
|
+
constructor(serverUrl, daemonId, sessions, adapters = ["claude-code"], token, runtimes = adapters.map((kind) => ({ kind })), nodeName, reportRuntimes = true, preflight = true) {
|
|
46874
48234
|
this.serverUrl = serverUrl;
|
|
46875
48235
|
this.daemonId = daemonId;
|
|
46876
48236
|
this.sessions = sessions;
|
|
@@ -46879,6 +48239,7 @@ var DaemonWsClient = class {
|
|
|
46879
48239
|
this.runtimes = runtimes;
|
|
46880
48240
|
this.nodeName = nodeName;
|
|
46881
48241
|
this.reportRuntimes = reportRuntimes;
|
|
48242
|
+
this.preflight = preflight;
|
|
46882
48243
|
}
|
|
46883
48244
|
ws = null;
|
|
46884
48245
|
pingTimer = null;
|
|
@@ -46935,6 +48296,14 @@ var DaemonWsClient = class {
|
|
|
46935
48296
|
const t0 = Date.now();
|
|
46936
48297
|
log2("[node-cli]", `\u2190 dispatch ${dispatchId} artifact=${job.artifactId}`);
|
|
46937
48298
|
try {
|
|
48299
|
+
if (this.preflight) {
|
|
48300
|
+
const preflightFailure = await preflightOasisAccess(job);
|
|
48301
|
+
if (preflightFailure) {
|
|
48302
|
+
log2("[node-cli]", ` preflight failed: ${preflightFailure.reason}`);
|
|
48303
|
+
this.send({ type: "session_exited", dispatchId, sessionId: "preflight-failed", info: preflightFailure });
|
|
48304
|
+
return;
|
|
48305
|
+
}
|
|
48306
|
+
}
|
|
46938
48307
|
const handle = await this.sessions.getAdapter().spawn(job);
|
|
46939
48308
|
this.sessions.register(dispatchId, handle);
|
|
46940
48309
|
log2("[node-cli]", ` \u2192 session_started id=${handle.id} setup=${Date.now() - t0}ms`);
|
|
@@ -46954,7 +48323,10 @@ var DaemonWsClient = class {
|
|
|
46954
48323
|
}
|
|
46955
48324
|
case "kill": {
|
|
46956
48325
|
log2("[node-cli]", `\u2190 kill dispatch=${msg.dispatchId}`);
|
|
46957
|
-
await this.sessions.kill(msg.dispatchId);
|
|
48326
|
+
const killed = await this.sessions.kill(msg.dispatchId);
|
|
48327
|
+
if (!killed) {
|
|
48328
|
+
this.send({ type: "session_exited", dispatchId: msg.dispatchId, sessionId: "missing-session", info: { code: null, reason: "timeout" } });
|
|
48329
|
+
}
|
|
46958
48330
|
break;
|
|
46959
48331
|
}
|
|
46960
48332
|
case "ping":
|
|
@@ -47087,8 +48459,9 @@ async function startNode(opts) {
|
|
|
47087
48459
|
init_src4();
|
|
47088
48460
|
var USAGE = `oasis \u2014\u2014 artifact-centric \u534F\u4F5C\u5185\u6838 CLI\uFF08\u670D\u52A1\u7AEF\u7626\u5BA2\u6237\u7AEF\uFF09
|
|
47089
48461
|
|
|
47090
|
-
oasis serve [--dir .oasis] [--port 7320] [--dispatch true [--model haiku] [--max-produce-per-agent 5] [--max-concurrent-produce N]] [--allow-simple-tokens true] [--gateway-url wss://host/oasis-api/node-gateway]
|
|
48462
|
+
oasis serve [--dir .oasis] [--port 7320] [--dispatch true [--model haiku] [--max-produce-per-agent 5] [--max-concurrent-produce N]] [--allow-simple-tokens true] [--public-url https://host/oasis-api] [--gateway-url wss://host/oasis-api/node-gateway]
|
|
47091
48463
|
# --gateway-url\uFF08\u6216 $OASIS_GATEWAY_URL\uFF09\uFF1A\u8282\u70B9\u5165\u7F51\u811A\u672C\u91CC\u5B88\u62A4\u8FDB\u7A0B\u8981\u8FDE\u7684\u7F51\u5173 URL\uFF1B\u516C\u7F51\u7ECF nginx \u5FC5\u8BBE\uFF0C\u5426\u5219\u9ED8\u8BA4\u672C\u673A ws://127.0.0.1:<port>\u3002
|
|
48464
|
+
# --public-url\uFF08\u6216 $OASIS_PUBLIC_URL\uFF09\uFF1A\u8FDC\u7A0B node \u4E0A agent \u56DE\u8FDE\u7684 HTTP API \u5730\u5740\uFF1B\u7F3A\u7701\u4ECE gateway-url \u63A8\u5BFC\u3002
|
|
47092
48465
|
oasis export-trajectories [--dir d] [--out samples.jsonl] \u5BFC\u51FA\u84B8\u998F\u6837\u672C\uFF08\u8F93\u5165\u5305+\u4E8B\u4EF6\u6D41+\u7ED3\u679C\u5F15\u7528+gate \u6807\u7B7E\uFF09
|
|
47093
48466
|
oasis serve install [--dir d] [--port p] [--dispatch true --model m] [--bin ~/.local/bin/oasis] [--unit-dir ~/.config/systemd/user] \u751F\u6210 systemd \u7528\u6237\u5355\u5143\uFF08D2 \u5B88\u62A4\u5316\uFF09
|
|
47094
48467
|
\u5E38\u9A7B\u670D\u52A1\uFF08\u552F\u4E00\u5199\u8005\u8FDB\u7A0B\uFF09\uFF1A/api \u7ED9 CLI\u3001\u524D\u7AEF\u4E0E agent\uFF08agent \u901A\u8FC7 oasis CLI \u8C03\u7528\uFF09\u3002
|
|
@@ -47204,7 +48577,10 @@ function projectSlug(value) {
|
|
|
47204
48577
|
return value.trim().toLowerCase().replace(/[^\w\u4e00-\u9fa5\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 32);
|
|
47205
48578
|
}
|
|
47206
48579
|
function makeClient(base, token) {
|
|
47207
|
-
const
|
|
48580
|
+
const baseHeaders = { authorization: `Bearer ${token}`, "content-type": "application/json" };
|
|
48581
|
+
const chatSessionId = process.env["OASIS_CHAT_SESSION_ID"];
|
|
48582
|
+
if (chatSessionId) baseHeaders["x-chat-session-id"] = chatSessionId;
|
|
48583
|
+
const headers = baseHeaders;
|
|
47208
48584
|
const fail = async (res) => {
|
|
47209
48585
|
const body = await res.json().catch(() => ({}));
|
|
47210
48586
|
const err = body.error;
|
|
@@ -47260,9 +48636,9 @@ async function runCli(argv, println = console.log) {
|
|
|
47260
48636
|
const nodeTokenFile = path12.join(dir, "node-token");
|
|
47261
48637
|
const enrollTokenFile = path12.join(dir, "enroll-token");
|
|
47262
48638
|
const reportMarkerFile = path12.join(dir, "report-runtimes");
|
|
47263
|
-
let nodeId =
|
|
47264
|
-
let token2 = flags.get("token") ?? process.env["OASIS_NODE_TOKEN"] ?? (
|
|
47265
|
-
const enrollToken = flags.get("enroll-token") ?? process.env["OASIS_ENROLL_TOKEN"] ?? (
|
|
48639
|
+
let nodeId = fs15.existsSync(nodeIdFile) ? fs15.readFileSync(nodeIdFile, "utf8").trim() : null;
|
|
48640
|
+
let token2 = flags.get("token") ?? process.env["OASIS_NODE_TOKEN"] ?? (fs15.existsSync(nodeTokenFile) ? fs15.readFileSync(nodeTokenFile, "utf8").trim() : void 0);
|
|
48641
|
+
const enrollToken = flags.get("enroll-token") ?? process.env["OASIS_ENROLL_TOKEN"] ?? (fs15.existsSync(enrollTokenFile) ? fs15.readFileSync(enrollTokenFile, "utf8").trim() : void 0);
|
|
47266
48642
|
let freshEnrollment = false;
|
|
47267
48643
|
if ((!nodeId || !token2) && enrollToken) {
|
|
47268
48644
|
const httpBase2 = serverUrl.replace(/^ws(s?):\/\//, "http$1://").replace(/\/node-gateway.*$/, "");
|
|
@@ -47275,26 +48651,26 @@ async function runCli(argv, println = console.log) {
|
|
|
47275
48651
|
const body = await res.json();
|
|
47276
48652
|
nodeId = body.nodeId;
|
|
47277
48653
|
token2 = body.token;
|
|
47278
|
-
|
|
47279
|
-
|
|
47280
|
-
|
|
48654
|
+
fs15.mkdirSync(dir, { recursive: true });
|
|
48655
|
+
fs15.writeFileSync(nodeIdFile, nodeId);
|
|
48656
|
+
fs15.writeFileSync(nodeTokenFile, token2, { mode: 384 });
|
|
47281
48657
|
try {
|
|
47282
|
-
|
|
48658
|
+
fs15.unlinkSync(enrollTokenFile);
|
|
47283
48659
|
} catch {
|
|
47284
48660
|
}
|
|
47285
48661
|
freshEnrollment = true;
|
|
47286
48662
|
}
|
|
47287
48663
|
if (!nodeId) throw new Error("no nodeId \u2014 provide --enroll-token <ent_...> on first run");
|
|
47288
|
-
const scriptRun = freshEnrollment ||
|
|
48664
|
+
const scriptRun = freshEnrollment || fs15.existsSync(reportMarkerFile);
|
|
47289
48665
|
if (flags.has("service")) {
|
|
47290
48666
|
const { spawnSync, execSync: execSync2 } = await import("node:child_process");
|
|
47291
48667
|
if (token2) {
|
|
47292
|
-
|
|
47293
|
-
|
|
48668
|
+
fs15.mkdirSync(dir, { recursive: true });
|
|
48669
|
+
fs15.writeFileSync(nodeTokenFile, token2, { mode: 384 });
|
|
47294
48670
|
}
|
|
47295
48671
|
if (scriptRun) {
|
|
47296
|
-
|
|
47297
|
-
|
|
48672
|
+
fs15.mkdirSync(dir, { recursive: true });
|
|
48673
|
+
fs15.writeFileSync(reportMarkerFile, "");
|
|
47298
48674
|
}
|
|
47299
48675
|
println("\u2192 Installing oasis_test globally...");
|
|
47300
48676
|
spawnSync("npm", ["install", "-g", "oasis_test@latest"], { stdio: "inherit" });
|
|
@@ -47303,16 +48679,19 @@ async function runCli(argv, println = console.log) {
|
|
|
47303
48679
|
const svcArgs = `node --server-ws ${serverUrl} --dir ${dir}${nodeName ? ` --name ${nodeName}` : ""}`;
|
|
47304
48680
|
if (process.platform === "linux") {
|
|
47305
48681
|
const svcFile = path12.join(os7.homedir(), ".config/systemd/user/oasis-node.service");
|
|
47306
|
-
|
|
48682
|
+
fs15.mkdirSync(path12.dirname(svcFile), { recursive: true });
|
|
47307
48683
|
if (spawnSync("systemctl", ["--user", "is-active", "--quiet", "oasis-node"], {}).status === 0)
|
|
47308
48684
|
spawnSync("systemctl", ["--user", "stop", "oasis-node"], { stdio: "inherit" });
|
|
47309
|
-
|
|
48685
|
+
fs15.writeFileSync(svcFile, [
|
|
47310
48686
|
"[Unit]",
|
|
47311
48687
|
"Description=Oasis Node Daemon",
|
|
47312
48688
|
"After=network.target",
|
|
47313
48689
|
"",
|
|
47314
48690
|
"[Service]",
|
|
47315
48691
|
`ExecStart=${binPath} ${svcArgs}`,
|
|
48692
|
+
// Carry the install-time PATH so the detached daemon detects CLIs in user dirs
|
|
48693
|
+
// (~/.npm-global/bin, ~/.local/bin) — systemd --user otherwise gives a minimal PATH.
|
|
48694
|
+
`Environment=PATH=${process.env["PATH"] ?? ""}:${path12.join(os7.homedir(), ".npm-global/bin")}:${path12.join(os7.homedir(), ".local/bin")}:/usr/local/bin`,
|
|
47316
48695
|
"Restart=on-failure",
|
|
47317
48696
|
"RestartSec=5",
|
|
47318
48697
|
"StandardOutput=journal",
|
|
@@ -47331,10 +48710,10 @@ async function runCli(argv, println = console.log) {
|
|
|
47331
48710
|
println(" Stop : systemctl --user stop oasis-node");
|
|
47332
48711
|
} else if (process.platform === "darwin") {
|
|
47333
48712
|
const plist = path12.join(os7.homedir(), "Library/LaunchAgents/com.oasis.node.plist");
|
|
47334
|
-
|
|
48713
|
+
fs15.mkdirSync(path12.dirname(plist), { recursive: true });
|
|
47335
48714
|
spawnSync("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
47336
48715
|
const argXml = [binPath, "node", "--server-ws", serverUrl, "--dir", dir, ...nodeName ? ["--name", nodeName] : []].map((a) => `<string>${a}</string>`).join("");
|
|
47337
|
-
|
|
48716
|
+
fs15.writeFileSync(plist, `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>com.oasis.node</string><key>ProgramArguments</key><array>${argXml}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>StandardOutPath</key><string>${os7.homedir()}/.oasis-node.log</string><key>StandardErrorPath</key><string>${os7.homedir()}/.oasis-node.err</string></dict></plist>`);
|
|
47338
48717
|
spawnSync("launchctl", ["load", plist], { stdio: "inherit" });
|
|
47339
48718
|
println("\u2713 Oasis node service started (macOS LaunchAgent com.oasis.node)");
|
|
47340
48719
|
println(` Logs : tail -f ${os7.homedir()}/.oasis-node.log`);
|
|
@@ -47346,9 +48725,9 @@ async function runCli(argv, println = console.log) {
|
|
|
47346
48725
|
}
|
|
47347
48726
|
return;
|
|
47348
48727
|
}
|
|
47349
|
-
if (
|
|
48728
|
+
if (fs15.existsSync(reportMarkerFile)) {
|
|
47350
48729
|
try {
|
|
47351
|
-
|
|
48730
|
+
fs15.unlinkSync(reportMarkerFile);
|
|
47352
48731
|
} catch {
|
|
47353
48732
|
}
|
|
47354
48733
|
}
|
|
@@ -47400,7 +48779,7 @@ async function runCli(argv, println = console.log) {
|
|
|
47400
48779
|
const outFile = flags.get("out");
|
|
47401
48780
|
const lines = samples.map((sample) => JSON.stringify(sample)).join("\n");
|
|
47402
48781
|
if (outFile !== void 0) {
|
|
47403
|
-
|
|
48782
|
+
fs15.writeFileSync(outFile, lines + (lines ? "\n" : ""));
|
|
47404
48783
|
println(`\u5DF2\u5BFC\u51FA ${samples.length} \u6761\u8F68\u8FF9\u6837\u672C \u2192 ${outFile}\uFF08approved/rejected \u6210\u5BF9\u5373\u504F\u597D\u5BF9\uFF09`);
|
|
47405
48784
|
} else {
|
|
47406
48785
|
println(lines);
|
|
@@ -47428,9 +48807,9 @@ async function runCli(argv, println = console.log) {
|
|
|
47428
48807
|
`WantedBy=default.target`,
|
|
47429
48808
|
``
|
|
47430
48809
|
].join("\n");
|
|
47431
|
-
|
|
48810
|
+
fs15.mkdirSync(unitDir, { recursive: true });
|
|
47432
48811
|
const unitPath = path12.join(unitDir, `${name}.service`);
|
|
47433
|
-
|
|
48812
|
+
fs15.writeFileSync(unitPath, unit);
|
|
47434
48813
|
println(`\u5DF2\u5199\u5165 ${unitPath}`);
|
|
47435
48814
|
println(`\u542F\u7528\uFF1Asystemctl --user daemon-reload && systemctl --user enable --now ${name}`);
|
|
47436
48815
|
println(`\u5F00\u673A\u81EA\u542F\uFF08\u542B\u672A\u767B\u5F55\uFF09\uFF1Aloginctl enable-linger $USER`);
|
|
@@ -47447,6 +48826,7 @@ async function runCli(argv, println = console.log) {
|
|
|
47447
48826
|
...flags.has("max-produce-per-agent") ? { maxConcurrentProducePerActor: Number(flags.get("max-produce-per-agent")) } : {},
|
|
47448
48827
|
...flags.has("model") ? { model: flags.get("model") } : {},
|
|
47449
48828
|
allowSimpleTokens: flags.get("allow-simple-tokens") === "true",
|
|
48829
|
+
...flags.get("public-url") ?? process.env["OASIS_PUBLIC_URL"] ? { publicUrl: flags.get("public-url") ?? process.env["OASIS_PUBLIC_URL"] } : {},
|
|
47450
48830
|
...flags.get("gateway-url") ?? process.env["OASIS_GATEWAY_URL"] ? { gatewayUrl: flags.get("gateway-url") ?? process.env["OASIS_GATEWAY_URL"] } : {}
|
|
47451
48831
|
});
|
|
47452
48832
|
const port2 = new URL(handle.baseUrl).port;
|
|
@@ -47459,12 +48839,12 @@ async function runCli(argv, println = console.log) {
|
|
|
47459
48839
|
}
|
|
47460
48840
|
const base = flags.get("server") ?? process.env["OASIS_SERVER"] ?? "http://127.0.0.1:7320";
|
|
47461
48841
|
const tokenFile = path12.join(dir, "operator-token");
|
|
47462
|
-
const token = flags.get("token") ?? process.env["OASIS_TOKEN"] ?? (
|
|
48842
|
+
const token = flags.get("token") ?? process.env["OASIS_TOKEN"] ?? (fs15.existsSync(tokenFile) ? fs15.readFileSync(tokenFile, "utf8").trim() : void 0);
|
|
47463
48843
|
if (!token) {
|
|
47464
48844
|
throw new Error(`\u65E0 token\uFF1A\u5148 oasis serve\uFF08\u4F1A\u751F\u6210 ${tokenFile}\uFF09\uFF0C\u6216\u663E\u5F0F --token / $OASIS_TOKEN`);
|
|
47465
48845
|
}
|
|
47466
48846
|
const api = makeClient(base, token);
|
|
47467
|
-
const readFileArg = (file) =>
|
|
48847
|
+
const readFileArg = (file) => fs15.readFileSync(file, "utf8");
|
|
47468
48848
|
const parseJsonFlag = (value, name) => {
|
|
47469
48849
|
const parsed = JSON.parse(value);
|
|
47470
48850
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
@@ -47482,11 +48862,11 @@ async function runCli(argv, println = console.log) {
|
|
|
47482
48862
|
const readDirFiles = (root) => {
|
|
47483
48863
|
const out = [];
|
|
47484
48864
|
const walk = (abs, rel) => {
|
|
47485
|
-
for (const name of
|
|
48865
|
+
for (const name of fs15.readdirSync(abs)) {
|
|
47486
48866
|
const childAbs = path12.join(abs, name);
|
|
47487
48867
|
const childRel = rel ? `${rel}/${name}` : name;
|
|
47488
|
-
if (
|
|
47489
|
-
else out.push({ path: childRel, contentBase64:
|
|
48868
|
+
if (fs15.statSync(childAbs).isDirectory()) walk(childAbs, childRel);
|
|
48869
|
+
else out.push({ path: childRel, contentBase64: fs15.readFileSync(childAbs).toString("base64") });
|
|
47490
48870
|
}
|
|
47491
48871
|
};
|
|
47492
48872
|
walk(root, "");
|
|
@@ -47529,9 +48909,10 @@ async function runCli(argv, println = console.log) {
|
|
|
47529
48909
|
};
|
|
47530
48910
|
case "spawn": {
|
|
47531
48911
|
const fields = fieldsFromFlags(SPAWN_OWN_FLAGS);
|
|
48912
|
+
const spawnId = positional[0];
|
|
47532
48913
|
return {
|
|
47533
48914
|
action: "spawn",
|
|
47534
|
-
|
|
48915
|
+
...spawnId ? { id: spawnId } : {},
|
|
47535
48916
|
type: need(flags, "type"),
|
|
47536
48917
|
...flags.get("title") !== void 0 ? { title: flags.get("title") } : {},
|
|
47537
48918
|
...(flags.get("brief") ?? flags.get("description")) !== void 0 ? { description: flags.get("brief") ?? flags.get("description") } : {},
|
|
@@ -47585,7 +48966,12 @@ async function runCli(argv, println = console.log) {
|
|
|
47585
48966
|
};
|
|
47586
48967
|
if (process.env["OASIS_STAGE"] === "1" && STAGE_COMMANDS.has(command)) {
|
|
47587
48968
|
const op = buildStageOp(command);
|
|
47588
|
-
const
|
|
48969
|
+
const editWs = flags.get("workspace") ?? process.env["OASIS_WORKSPACE"];
|
|
48970
|
+
const r = await api.post("/api/stage", { op, ...editWs ? { workspace: editWs } : {} });
|
|
48971
|
+
if (r.draft) {
|
|
48972
|
+
println(`\u5DF2\u6539\u8349\u6848\uFF1A${describeStageOp(op)}\uFF08\u5DE5\u5355 ${r.workspace}\uFF0C\u73B0 ${r.nodes} \u4E2A\u8282\u70B9\uFF1B\u4EBA\u5728\u7F16\u8F91\u5668\u786E\u8BA4\u5373\u521B\u5EFA\uFF09`);
|
|
48973
|
+
return;
|
|
48974
|
+
}
|
|
47589
48975
|
let msg = `\u5DF2\u6682\u5B58\uFF1A${describeStageOp(op)}\uFF08\u7F13\u51B2 ${r.count} \u9879\uFF1B\u6539\u5B8C \`oasis apply --reason "\u2026"\` \u63D0\u4EA4\u3001\`oasis staged\` \u770B\u5168\u90E8\uFF09`;
|
|
47590
48976
|
if (r.cascade && r.cascade.length > 0)
|
|
47591
48977
|
msg += `
|
|
@@ -47595,27 +48981,69 @@ async function runCli(argv, println = console.log) {
|
|
|
47595
48981
|
}
|
|
47596
48982
|
switch (command) {
|
|
47597
48983
|
case "create-workorder": {
|
|
47598
|
-
const planRaw = flags.get("plan-file") !== void 0 ?
|
|
48984
|
+
const planRaw = flags.get("plan-file") !== void 0 ? fs15.readFileSync(flags.get("plan-file"), "utf8") : flags.get("plan");
|
|
47599
48985
|
const acceptanceFlag = flags.get("acceptance");
|
|
47600
48986
|
const args = {
|
|
47601
48987
|
createdVia: "seed",
|
|
47602
48988
|
stage: true,
|
|
47603
48989
|
type: need(flags, "type"),
|
|
47604
|
-
workspace
|
|
48990
|
+
// workspace 可选:不给则服务端自动生成唯一工单 id(见 build-workorder skill:建单不必自带 ws)。
|
|
48991
|
+
...flags.get("workspace") !== void 0 ? { workspace: flags.get("workspace") } : {},
|
|
47605
48992
|
title: need(flags, "title"),
|
|
47606
48993
|
...flags.get("brief") !== void 0 ? { brief: flags.get("brief") } : {},
|
|
47607
48994
|
...flags.get("goal") !== void 0 ? { goal: flags.get("goal") } : {},
|
|
47608
48995
|
...acceptanceFlag !== void 0 ? { acceptance_criteria: parseStringArrayFlag(acceptanceFlag, "acceptance") } : {},
|
|
47609
48996
|
...planRaw !== void 0 ? { plan: parseJsonFlag(planRaw, "plan") } : {},
|
|
47610
48997
|
...flags.get("project") !== void 0 ? { project_id: flags.get("project") } : {},
|
|
47611
|
-
...flags.get("dispatch") === "false" ? { dispatch: false } : {}
|
|
48998
|
+
...flags.get("dispatch") === "false" ? { dispatch: false } : {},
|
|
48999
|
+
...flags.get("allow-missing-coordinator") === "true" ? { allowMissingCoordinator: true } : {}
|
|
47612
49000
|
};
|
|
47613
49001
|
const res = await api.cmd("spawn", args);
|
|
47614
49002
|
println(res.message);
|
|
47615
49003
|
const data = res.data;
|
|
47616
49004
|
if (data?.draftId) {
|
|
47617
|
-
println(`draftId=${data.draftId} workspace=${data.workspace} \u8282\u70B9=${data.spawned?.length ?? 0} \u89C4\u5212=${data.planning?.status ?? "?"}`);
|
|
49005
|
+
println(`draftId=${data.draftId} workspace=${data.workspace} \u72B6\u6001=draft\uFF08\u8349\u6848\uFF0C\u5F85\u4EBA\u786E\u8BA4\uFF09 \u8282\u70B9=${data.spawned?.length ?? 0} \u89C4\u5212=${data.planning?.status ?? "?"}`);
|
|
49006
|
+
for (const n of data.spawned ?? []) println(` ${n.id} (${n.type})`);
|
|
49007
|
+
}
|
|
49008
|
+
break;
|
|
49009
|
+
}
|
|
49010
|
+
case "workorders": {
|
|
49011
|
+
const sessionId = flags.get("session");
|
|
49012
|
+
if (sessionId) {
|
|
49013
|
+
const res = await api.request(
|
|
49014
|
+
"GET",
|
|
49015
|
+
`/api/chat-sessions/${encodeURIComponent(sessionId)}/work-orders`
|
|
49016
|
+
);
|
|
49017
|
+
println(`\u4F1A\u8BDD ${sessionId} \u7684\u5DE5\u5355\uFF08${res.items.length}\uFF09\uFF1A`);
|
|
49018
|
+
for (const w2 of res.items) {
|
|
49019
|
+
const p2 = w2.progress ? ` [${w2.progress.concluded}/${w2.progress.total}]` : "";
|
|
49020
|
+
println(` ${w2.id} ${w2.title} [${w2.stage}]${p2}`);
|
|
49021
|
+
}
|
|
49022
|
+
} else {
|
|
49023
|
+
const res = await api.request("GET", "/api/workorders");
|
|
49024
|
+
const q = flags.get("search")?.trim().toLowerCase();
|
|
49025
|
+
const items = q ? res.items.filter((w2) => `${w2.id} ${w2.title} ${w2.goal ?? ""} ${w2.description ?? ""}`.toLowerCase().includes(q)) : res.items;
|
|
49026
|
+
println(`\u5DE5\u5355(${items.length}${q ? ` / \u641C\u300C${q}\u300D` : ""})\u2014\u2014\u8BE6\u60C5/\u6539\u56FE\u524D\u5148 \`oasis workorder <id>\`:`);
|
|
49027
|
+
for (const w2 of items) {
|
|
49028
|
+
println(`- ${w2.id} ${w2.title}${w2.dispatchPaused ? " [\u5F85\u6D3E\u53D1]" : ""}`);
|
|
49029
|
+
println(` \u9636\u6BB5=${w2.stage} \u8FDB\u5EA6=${w2.progress.concluded}/${w2.progress.total} \u8282\u70B9=${w2.artifactCount} owner=${w2.owner.name ?? w2.owner.id}${w2.goal ? ` \u76EE\u6807: ${w2.goal}` : ""}`);
|
|
49030
|
+
}
|
|
49031
|
+
if (items.length === 0) println("\uFF08\u65E0\u5339\u914D\u5DE5\u5355\uFF09");
|
|
49032
|
+
}
|
|
49033
|
+
break;
|
|
49034
|
+
}
|
|
49035
|
+
case "artifacts": {
|
|
49036
|
+
const workOrderId = positional[0];
|
|
49037
|
+
if (!workOrderId) {
|
|
49038
|
+
println("\u7528\u6CD5: oasis artifacts <workOrderId>");
|
|
49039
|
+
break;
|
|
47618
49040
|
}
|
|
49041
|
+
const res = await api.request(
|
|
49042
|
+
"GET",
|
|
49043
|
+
`/api/workorders/${encodeURIComponent(workOrderId)}`
|
|
49044
|
+
);
|
|
49045
|
+
println(`\u5DE5\u5355 ${res.summary.title} \u7684\u4EA7\u7269\uFF08${res.graph.nodes.length}\uFF09\uFF1A`);
|
|
49046
|
+
for (const n of res.graph.nodes) println(` ${n.id} [${n.type}] ${n.label} stage=${n.stage}`);
|
|
47619
49047
|
break;
|
|
47620
49048
|
}
|
|
47621
49049
|
case "projects": {
|
|
@@ -47628,6 +49056,29 @@ async function runCli(argv, println = console.log) {
|
|
|
47628
49056
|
if (res.next_cursor) println("\uFF08\u8FD8\u6709\u66F4\u591A\u9879\u76EE\u672A\u5217\u5168\uFF0C\u7528 --search \u7F29\u5C0F\u8303\u56F4\uFF09");
|
|
47629
49057
|
break;
|
|
47630
49058
|
}
|
|
49059
|
+
case "workorder": {
|
|
49060
|
+
const woId = needPos(positional, 0, "oasis workorder <\u5DE5\u5355id\uFF0C\u5982 ws:wo-abc>");
|
|
49061
|
+
const d = await api.request("GET", `/api/workorders/${encodeURIComponent(woId)}`);
|
|
49062
|
+
const s2 = d.summary;
|
|
49063
|
+
const draftNote = s2.stage === "draft" ? "\uFF08\u8349\u6848\u2014\u2014\u5F85\u4EBA\u5728\u7F16\u8F91\u5668\u786E\u8BA4\u624D\u771F\u521B\u5EFA\uFF1B\u4E0B\u9762\u7684\u8282\u70B9 id \u5DF2\u53EF\u7528\u4E8E\u6539\u8349\u6848\uFF09" : "";
|
|
49064
|
+
println(`${s2.id} ${s2.title} \u72B6\u6001=${s2.stage}${draftNote}${s2.dispatchPaused ? " [\u672A\u6D3E\u53D1]" : ""}`);
|
|
49065
|
+
if (s2.goal) println(`\u76EE\u6807: ${s2.goal}`);
|
|
49066
|
+
println(`\u8FDB\u5EA6=${s2.progress.concluded}/${s2.progress.total} owner=${s2.owner.name ?? s2.owner.id}`);
|
|
49067
|
+
if (d.acceptanceCriteria && d.acceptanceCriteria.length > 0) {
|
|
49068
|
+
println("\u9A8C\u6536\u6807\u51C6:");
|
|
49069
|
+
for (const a of d.acceptanceCriteria) println(` - ${a}`);
|
|
49070
|
+
}
|
|
49071
|
+
println("\n\u8282\u70B9\uFF08\u6539\u56FE\u5F15\u7528\u7528\u8FD9\u4E9B id\uFF09:");
|
|
49072
|
+
for (const n of d.graph.nodes) {
|
|
49073
|
+
println(` ${n.id}`);
|
|
49074
|
+
println(` type=${n.type} \u9636\u6BB5=${n.stage}${n.blocked ? " [\u5361\u4F4F]" : ""} owner=${n.owner.name ?? n.owner.id}${n.label ? ` ${n.label}` : ""}`);
|
|
49075
|
+
}
|
|
49076
|
+
if (d.graph.edges.length > 0) {
|
|
49077
|
+
println("\n\u4F9D\u8D56\u8FB9\uFF08\u6D88\u8D39\u8005 \u2192 \u4E0A\u6E38\uFF09:");
|
|
49078
|
+
for (const e of d.graph.edges) println(` ${e.from} \u2192 ${e.to}${e.required ? "" : "\uFF08\u53EF\u9009\uFF09"}`);
|
|
49079
|
+
}
|
|
49080
|
+
break;
|
|
49081
|
+
}
|
|
47631
49082
|
case "project": {
|
|
47632
49083
|
const projSub = positional[0];
|
|
47633
49084
|
if (projSub && !["create", "docs", "progress", "run-state"].includes(projSub)) {
|
|
@@ -48381,18 +49832,18 @@ var parseFlags = (argv) => {
|
|
|
48381
49832
|
};
|
|
48382
49833
|
var readConfig = () => {
|
|
48383
49834
|
try {
|
|
48384
|
-
return JSON.parse(
|
|
49835
|
+
return JSON.parse(fs16.readFileSync(CONFIG_FILE, "utf8"));
|
|
48385
49836
|
} catch {
|
|
48386
49837
|
return null;
|
|
48387
49838
|
}
|
|
48388
49839
|
};
|
|
48389
49840
|
var saveConfig = (c) => {
|
|
48390
|
-
|
|
48391
|
-
|
|
49841
|
+
fs16.mkdirSync(OASIS_DIR, { recursive: true });
|
|
49842
|
+
fs16.writeFileSync(CONFIG_FILE, JSON.stringify(c), { mode: 384 });
|
|
48392
49843
|
};
|
|
48393
49844
|
var readPid = () => {
|
|
48394
49845
|
try {
|
|
48395
|
-
const p2 = parseInt(
|
|
49846
|
+
const p2 = parseInt(fs16.readFileSync(PID_FILE, "utf8").trim(), 10);
|
|
48396
49847
|
return isNaN(p2) ? null : p2;
|
|
48397
49848
|
} catch {
|
|
48398
49849
|
return null;
|
|
@@ -48408,25 +49859,33 @@ var isRunning = (pid) => {
|
|
|
48408
49859
|
};
|
|
48409
49860
|
var httpBase = (wsUrl) => wsUrl.replace(/^ws(s?):\/\//, "http$1://").replace(/\/node-gateway.*$/, "");
|
|
48410
49861
|
function installBinary() {
|
|
48411
|
-
|
|
48412
|
-
|
|
48413
|
-
|
|
48414
|
-
|
|
49862
|
+
fs16.mkdirSync(path13.dirname(BIN_FILE), { recursive: true });
|
|
49863
|
+
fs16.copyFileSync(process.argv[1], BIN_FILE);
|
|
49864
|
+
fs16.mkdirSync(path13.dirname(LOCAL_BIN), { recursive: true });
|
|
49865
|
+
fs16.writeFileSync(LOCAL_BIN, `#!/bin/sh
|
|
48415
49866
|
exec node "${BIN_FILE}" "$@"
|
|
48416
49867
|
`, { mode: 493 });
|
|
48417
49868
|
}
|
|
48418
49869
|
function setupAutostart() {
|
|
48419
49870
|
const cmd = `${process.execPath} "${BIN_FILE}" --_daemon`;
|
|
49871
|
+
const home = os8.homedir();
|
|
49872
|
+
const extraBins = [
|
|
49873
|
+
path13.join(home, ".npm-global", "bin"),
|
|
49874
|
+
path13.join(home, ".local", "bin"),
|
|
49875
|
+
"/usr/local/bin"
|
|
49876
|
+
];
|
|
49877
|
+
const daemonPath = [process.env["PATH"] ?? "", ...extraBins].filter(Boolean).join(":");
|
|
48420
49878
|
if (process.platform === "linux") {
|
|
48421
49879
|
const unitDir = path13.join(os8.homedir(), ".config", "systemd", "user");
|
|
48422
|
-
|
|
48423
|
-
|
|
49880
|
+
fs16.mkdirSync(unitDir, { recursive: true });
|
|
49881
|
+
fs16.writeFileSync(path13.join(unitDir, "oasis-node.service"), [
|
|
48424
49882
|
"[Unit]",
|
|
48425
49883
|
"Description=Oasis Node Daemon",
|
|
48426
49884
|
"After=network.target",
|
|
48427
49885
|
"",
|
|
48428
49886
|
"[Service]",
|
|
48429
49887
|
`ExecStart=${cmd}`,
|
|
49888
|
+
`Environment=PATH=${daemonPath}`,
|
|
48430
49889
|
`StandardOutput=append:${LOG_FILE}`,
|
|
48431
49890
|
`StandardError=append:${LOG_FILE}`,
|
|
48432
49891
|
"Restart=on-failure",
|
|
@@ -48442,12 +49901,13 @@ function setupAutostart() {
|
|
|
48442
49901
|
}
|
|
48443
49902
|
} else if (process.platform === "darwin") {
|
|
48444
49903
|
const plist = path13.join(os8.homedir(), "Library", "LaunchAgents", "com.oasis.node.plist");
|
|
48445
|
-
|
|
48446
|
-
|
|
49904
|
+
fs16.mkdirSync(path13.dirname(plist), { recursive: true });
|
|
49905
|
+
fs16.writeFileSync(plist, `<?xml version="1.0" encoding="UTF-8"?>
|
|
48447
49906
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
48448
49907
|
<plist version="1.0"><dict>
|
|
48449
49908
|
<key>Label</key><string>com.oasis.node</string>
|
|
48450
49909
|
<key>ProgramArguments</key><array><string>/bin/sh</string><string>-c</string><string>${cmd}</string></array>
|
|
49910
|
+
<key>EnvironmentVariables</key><dict><key>PATH</key><string>${daemonPath}</string></dict>
|
|
48451
49911
|
<key>RunAtLoad</key><true/><key>KeepAlive</key><true/>
|
|
48452
49912
|
<key>StandardOutPath</key><string>${LOG_FILE}</string>
|
|
48453
49913
|
<key>StandardErrorPath</key><string>${LOG_FILE}</string>
|
|
@@ -48459,14 +49919,14 @@ function setupAutostart() {
|
|
|
48459
49919
|
}
|
|
48460
49920
|
}
|
|
48461
49921
|
function spawnDaemon() {
|
|
48462
|
-
const log3 =
|
|
49922
|
+
const log3 = fs16.openSync(LOG_FILE, "a");
|
|
48463
49923
|
const child = (0, import_node_child_process10.spawn)(process.execPath, [BIN_FILE, "--_daemon"], {
|
|
48464
49924
|
detached: true,
|
|
48465
49925
|
stdio: ["ignore", log3, log3]
|
|
48466
49926
|
});
|
|
48467
49927
|
child.unref();
|
|
48468
49928
|
const pid = child.pid ?? 0;
|
|
48469
|
-
|
|
49929
|
+
fs16.writeFileSync(PID_FILE, String(pid));
|
|
48470
49930
|
return pid;
|
|
48471
49931
|
}
|
|
48472
49932
|
function stopDaemon() {
|
|
@@ -48478,7 +49938,7 @@ function stopDaemon() {
|
|
|
48478
49938
|
}
|
|
48479
49939
|
}
|
|
48480
49940
|
try {
|
|
48481
|
-
|
|
49941
|
+
fs16.unlinkSync(PID_FILE);
|
|
48482
49942
|
} catch {
|
|
48483
49943
|
}
|
|
48484
49944
|
try {
|
|
@@ -48499,10 +49959,10 @@ void (async () => {
|
|
|
48499
49959
|
console.error("[oasis] no config \u2014 run `oasis start` first");
|
|
48500
49960
|
process.exit(1);
|
|
48501
49961
|
}
|
|
48502
|
-
|
|
49962
|
+
fs16.writeFileSync(PID_FILE, String(process.pid));
|
|
48503
49963
|
const cleanup = () => {
|
|
48504
49964
|
try {
|
|
48505
|
-
|
|
49965
|
+
fs16.unlinkSync(PID_FILE);
|
|
48506
49966
|
} catch {
|
|
48507
49967
|
}
|
|
48508
49968
|
};
|