oasis_test 0.1.103 → 0.1.104
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 +143 -33
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -7364,9 +7364,9 @@ function checkTerminated(state) {
|
|
|
7364
7364
|
if (!isBusinessDependencyEdge(state, edge)) continue;
|
|
7365
7365
|
if (isEdgeInputUnobtainable(state, edge)) continue;
|
|
7366
7366
|
const upstream = state.node(edge.fromNodeId);
|
|
7367
|
-
if (!upstream?.
|
|
7367
|
+
if (!upstream?.latestAcceptId || !businessHandoffBarrierSatisfied(state, upstream.id, upstream.latestAcceptId)) return false;
|
|
7368
7368
|
}
|
|
7369
|
-
return live.every((n) => n.
|
|
7369
|
+
return live.every((n) => n.latestAcceptId !== null && (n.latestAcceptId === n.latestWorkId || n.latestAcceptId === n.latestSuccessWorkId));
|
|
7370
7370
|
}
|
|
7371
7371
|
function isEdgeInputUnobtainable(state, edge) {
|
|
7372
7372
|
const upstream = state.node(edge.fromNodeId);
|
|
@@ -7394,10 +7394,17 @@ function acceptBlock(state, workId) {
|
|
|
7394
7394
|
}
|
|
7395
7395
|
const node = state.node(w2.nodeId);
|
|
7396
7396
|
if (!node) return { code: "node-missing", detail: `\u8282\u70B9\u4E0D\u5728\u518C\uFF1A${w2.nodeId}`, overridable: false };
|
|
7397
|
-
if (!
|
|
7397
|
+
if (!hasOutput(w2, state.artifactsOf(w2.id).length)) {
|
|
7398
|
+
if (!w2.endedAt) {
|
|
7399
|
+
return {
|
|
7400
|
+
code: "work-in-flight",
|
|
7401
|
+
detail: `\u8FD9\u4E00\u7248\u8FD8\u5728\u8DD1\u3001\u4E14\u8FD8\u6CA1\u6709\u4EFB\u4F55\u4EA7\u51FA\u2014\u2014\u7B49\u5B83\u4EA4\u7A3F\u540E\u518D\u6536\u53E3\uFF0C\u6216\u5148\u505C\u6389\uFF08hold\uFF09/ \u91CD\u8DD1`,
|
|
7402
|
+
overridable: false
|
|
7403
|
+
};
|
|
7404
|
+
}
|
|
7398
7405
|
return {
|
|
7399
|
-
code: "
|
|
7400
|
-
detail: `\u8FD9\u4E00\u7248\
|
|
7406
|
+
code: "no-output",
|
|
7407
|
+
detail: `\u8FD9\u4E00\u7248\u5DF2\u7ED3\u675F\u4F46\u6CA1\u6709\u4EFB\u4F55\u4EA7\u51FA\uFF08\u7A7A\u58F3\uFF09\u2014\u2014\u4E0D\u80FD\u628A\u6CA1\u6709\u5185\u5BB9\u7684\u7248\u672C\u6536\u53E3\u6210\u4EA4\u4ED8\u7248\u672C\uFF1B\u6539\u5BF9\u6700\u8FD1\u4E00\u7248\u6709\u4EA7\u51FA\u7684\u7248\u672C\u64CD\u4F5C\uFF0C\u6216\u91CD\u8DD1\u672C\u8282\u70B9`,
|
|
7401
7408
|
overridable: false
|
|
7402
7409
|
};
|
|
7403
7410
|
}
|
|
@@ -7408,10 +7415,10 @@ function handoffBlock(state, nodeId, workId) {
|
|
|
7408
7415
|
if (!requiresBusinessHandoff(state, nodeId, work)) return null;
|
|
7409
7416
|
if (work?.acceptanceState === "accepted" && work.overrideBy) return null;
|
|
7410
7417
|
const node = state.node(nodeId);
|
|
7411
|
-
if (node?.latestWorkId !==
|
|
7418
|
+
if (workId !== node?.latestWorkId && workId !== node?.latestSuccessWorkId) {
|
|
7412
7419
|
return {
|
|
7413
7420
|
code: "not-latest-work",
|
|
7414
|
-
detail: `\u4E0D\u662F\u672C\u8282\u70B9\u6700\u65B0\
|
|
7421
|
+
detail: `\u4E0D\u662F\u672C\u8282\u70B9\u6700\u65B0\u7248\uFF08\u6700\u65B0\u5C1D\u8BD5\uFF1A${node?.latestWorkId ?? "\u65E0"}\uFF1B\u6700\u65B0\u6709\u6548\u4EA7\u51FA\uFF1A${node?.latestSuccessWorkId ?? "\u65E0"}\uFF09\u2014\u2014\u6536\u53E3\u53EA\u8BA4\u6700\u65B0\u4E00\u7248\u6216\u6700\u65B0\u6709\u6548\u4EA7\u51FA`,
|
|
7415
7422
|
overridable: false
|
|
7416
7423
|
};
|
|
7417
7424
|
}
|
|
@@ -7504,6 +7511,7 @@ CREATE TABLE IF NOT EXISTS ${schema}.workflow_nodes (
|
|
|
7504
7511
|
assignee_actor_id text,
|
|
7505
7512
|
assignee_role text,
|
|
7506
7513
|
latest_work_id text,
|
|
7514
|
+
latest_success_work_id text,
|
|
7507
7515
|
latest_accept_id text,
|
|
7508
7516
|
is_final_output boolean NOT NULL DEFAULT false,
|
|
7509
7517
|
fields jsonb,
|
|
@@ -7765,6 +7773,9 @@ CREATE INDEX IF NOT EXISTS issues_workorder_alive_idx ON ${schema}.issues (worko
|
|
|
7765
7773
|
CREATE INDEX IF NOT EXISTS issues_about_alive_idx ON ${schema}.issues (about_node_id) WHERE resolved_at IS NULL;
|
|
7766
7774
|
CREATE INDEX IF NOT EXISTS issues_raised_alive_idx ON ${schema}.issues (raised_by_node_id) WHERE resolved_at IS NULL;
|
|
7767
7775
|
ALTER TABLE ${schema}.workflow_nodes ADD COLUMN IF NOT EXISTS fields jsonb;
|
|
7776
|
+
-- \u2605 \u6709\u6548\u4EA7\u51FA\u6307\u9488\uFF08ADR 0148\uFF09\uFF1Anode \u4E0A\u300C\u6700\u8FD1\u4E00\u6B21\u5C1D\u8BD5\u300D\uFF08latest_work_id\uFF09\u4E4B\u5916\u518D\u5B58\u300C\u6700\u8FD1\u4E00\u6B21\u6709\u6548\u4EA7\u51FA\u300D\uFF0C
|
|
7777
|
+
-- \u5931\u8D25\u7A7A\u6D3E\u53D1\u4E0D\u518D\u9876\u6389\u6210\u54C1\u3002\u56DE\u586B\u5728\u4E0B\u65B9 works.status \u56DE\u586B\u4E4B\u540E\u6267\u884C\uFF08\u4F9D\u8D56 status \u5DF2\u843D\u5E93\uFF09\u3002
|
|
7778
|
+
ALTER TABLE ${schema}.workflow_nodes ADD COLUMN IF NOT EXISTS latest_success_work_id text;
|
|
7768
7779
|
-- \u2605 \u5148\u5220\u89C6\u56FE\u518D\u5220\u5217\uFF1Awork_view/node_view/workorder_view \u7528 SELECT *\uFF0C\u5BF9\u57FA\u8868\u5217\u6709\u4F9D\u8D56\u2014\u2014\u5B58\u91CF\u5E93\u4E0A
|
|
7769
7780
|
-- DROP COLUMN \u4F1A\u62A5 "cannot drop column ... because other objects depend on it"\uFF0C\u4E14\u672C\u6BB5\u662F\u5355\u6761
|
|
7770
7781
|
-- \u591A\u8BED\u53E5 Query\uFF08\u4E00\u4E2A\u9690\u5F0F\u4E8B\u52A1\uFF09\uFF0C\u4E00\u5904\u5931\u8D25\u5168\u6BB5\u56DE\u6EDA\u3001migrate \u629B\u9519 \u2192 serve \u8D77\u4E0D\u6765\u3002\u89C6\u56FE\u968F\u540E\u7531
|
|
@@ -7820,6 +7831,16 @@ UPDATE ${schema}.reviews SET status = CASE
|
|
|
7820
7831
|
WHEN ended_at IS NOT NULL THEN 'failed'
|
|
7821
7832
|
ELSE 'running' END
|
|
7822
7833
|
WHERE status IS NULL;
|
|
7834
|
+
-- \u2605 \u6709\u6548\u4EA7\u51FA\u6307\u9488\u56DE\u586B\uFF08ADR 0148\uFF09\uFF1Alatest_success_work_id = \u8BE5\u8282\u70B9\u6700\u8FD1\u4E00\u4E2A status=success \u7684**\u4E3B\u94FE** work
|
|
7835
|
+
-- \uFF08\u6392\u9664\u56DE\u4FE1 work reply_to_issue_id\uFF09\u3002\u5FC5\u987B\u5728\u4E0A\u9762 works.status \u56DE\u586B\u4E4B\u540E\uFF08\u4F9D\u8D56 status \u5DF2\u843D\u5E93\uFF09\u3002
|
|
7836
|
+
-- \u53EA\u586B\u5F53\u524D\u4E3A NULL \u7684\u884C \u2192 \u5E42\u7B49\uFF1A\u91CD\u542F\u65F6\u4E0D\u8986\u76D6 handler \u5DF2\u7EF4\u62A4\u7684\u503C\uFF1B\u4ECE\u672A\u6210\u529F\u8FC7\u7684\u8282\u70B9\u4FDD\u6301 NULL\u3002
|
|
7837
|
+
UPDATE ${schema}.workflow_nodes n SET latest_success_work_id = (
|
|
7838
|
+
SELECT w.id FROM ${schema}.works w
|
|
7839
|
+
WHERE w.node_id = n.id AND w.status = 'success' AND w.reply_to_issue_id IS NULL
|
|
7840
|
+
ORDER BY w.created_at DESC, w.id DESC
|
|
7841
|
+
LIMIT 1
|
|
7842
|
+
)
|
|
7843
|
+
WHERE n.latest_success_work_id IS NULL;
|
|
7823
7844
|
`;
|
|
7824
7845
|
}
|
|
7825
7846
|
function ENGINE_VIEWS(schema) {
|
|
@@ -7835,20 +7856,25 @@ SELECT r.*, r.status AS state
|
|
|
7835
7856
|
FROM ${schema}.reviews r;
|
|
7836
7857
|
|
|
7837
7858
|
DROP VIEW IF EXISTS ${schema}.node_view;
|
|
7859
|
+
-- \u2605 ADR 0148\uFF1A\u4E3B\u6001\u6309 latest_success\uFF08\u6700\u8FD1\u4E00\u4E2A\u6709\u6548\u4EA7\u51FA\uFF09\u8D70\u2014\u2014\u300C\u5DF2\u4EA4\u4ED8 v3\u3001\u672B\u6B21\u5C1D\u8BD5 v4 \u5D29\u4E86\u300D\u663E\u793A\u5DF2\u5B8C\u6210\uFF0C
|
|
7860
|
+
-- \u672B\u6B21\u5931\u8D25\u53EA\u4F5C\u526F\u6807\u8BB0\uFF08\u7248\u672C\u5217\u8868\u91CC\u90A3\u6761 failed \u4ECD\u5728\uFF09\uFF0C\u4E0D\u518D\u51FA\u73B0\u300C\u56FE\u4E0A\u7EA2\u3001\u4E0B\u6E38\u5728\u8DD1\u300D\u7684\u5272\u88C2\u3002
|
|
7861
|
+
-- \u4E0E views.ts nodeState \u540C\u6E90\uFF1A\u5728\u8DD1\uFF08latest_work running\uFF09\u4F18\u5148\uFF1B\u5426\u5219\u6709 latest_success \u5C31\u6309\u5176\u9A8C\u6536\u4E0E\u5426\u5206
|
|
7862
|
+
-- \u5DF2\u5B8C\u6210/\u8BC4\u5BA1\u4E2D\uFF1B\u518D\u5426\u5219\u672B\u6B21\u5C1D\u8BD5\u5931\u8D25/dead \u2192 \u5361\u4F4F\u3002
|
|
7838
7863
|
CREATE VIEW ${schema}.node_view AS
|
|
7839
7864
|
SELECT n.*,
|
|
7840
7865
|
CASE
|
|
7841
7866
|
WHEN n.cancelled_at IS NOT NULL THEN 'dead' -- \u5DF2\u4F5C\u5E9F\uFF08\u79FB\u51FA\u8BA1\u5212\uFF09
|
|
7842
7867
|
WHEN lw.id IS NULL THEN 'waiting' -- \u672A\u8FD0\u884C\uFF08latest_work \u4E3A\u7A7A\uFF09
|
|
7843
7868
|
WHEN lw.status = 'retry' THEN 'waiting' -- \u672A\u8FD0\u884C\uFF08\u7528\u6237\u4ECB\u5165\u91CD\u8BD5\uFF0C\u7B49 try_to_run\uFF09
|
|
7844
|
-
WHEN lw.status = 'running' THEN 'running' -- \u5728\u8FDB\u884C
|
|
7845
|
-
WHEN
|
|
7846
|
-
WHEN
|
|
7847
|
-
WHEN lw.status IN ('failed','dead') THEN 'dead' -- \u5361\u4F4F\u4E86
|
|
7869
|
+
WHEN lw.status = 'running' THEN 'running' -- \u5728\u8FDB\u884C\uFF08\u91CD\u8DD1\u8FDB\u884C\u4E2D\u4E5F\u4F18\u5148\u663E\u793A\u5728\u8FDB\u884C\uFF09
|
|
7870
|
+
WHEN ls.id IS NOT NULL AND n.latest_accept_id = ls.id THEN 'done' -- \u5DF2\u5B8C\u6210\uFF08\u6709\u6548\u4EA7\u51FA\u5DF2\u9A8C\u6536\uFF09
|
|
7871
|
+
WHEN ls.id IS NOT NULL AND n.latest_accept_id IS DISTINCT FROM ls.id THEN 'reviewing' -- \u8BC4\u5BA1\u4E2D\uFF08\u6709\u6548\u4EA7\u51FA\u672A\u9A8C\u6536\uFF09
|
|
7872
|
+
WHEN lw.status IN ('failed','dead') THEN 'dead' -- \u4ECE\u672A\u4EA7\u51FA\u4E14\u672B\u6B21\u5C1D\u8BD5\u5931\u8D25 \u2192 \u5361\u4F4F\u4E86
|
|
7848
7873
|
ELSE 'waiting'
|
|
7849
7874
|
END AS state
|
|
7850
7875
|
FROM ${schema}.workflow_nodes n
|
|
7851
|
-
LEFT JOIN ${schema}.works lw ON lw.id = n.latest_work_id
|
|
7876
|
+
LEFT JOIN ${schema}.works lw ON lw.id = n.latest_work_id
|
|
7877
|
+
LEFT JOIN ${schema}.works ls ON ls.id = n.latest_success_work_id;
|
|
7852
7878
|
|
|
7853
7879
|
DROP VIEW IF EXISTS ${schema}.workorder_view;
|
|
7854
7880
|
CREATE VIEW ${schema}.workorder_view AS
|
|
@@ -13128,6 +13154,7 @@ var init_store_postgres = __esm({
|
|
|
13128
13154
|
assigneeActorId: n.assignee_actor_id,
|
|
13129
13155
|
assigneeRole: n.assignee_role,
|
|
13130
13156
|
latestWorkId: n.latest_work_id,
|
|
13157
|
+
latestSuccessWorkId: n.latest_success_work_id ?? null,
|
|
13131
13158
|
latestAcceptId: n.latest_accept_id,
|
|
13132
13159
|
isFinalOutput: n.is_final_output,
|
|
13133
13160
|
cancelledAt: iso(n.cancelled_at),
|
|
@@ -13304,9 +13331,9 @@ var init_store_postgres = __esm({
|
|
|
13304
13331
|
await this.c.query(
|
|
13305
13332
|
`INSERT INTO ${this.s}.workflow_nodes
|
|
13306
13333
|
(id,workorder_id,type,title,spec,version,assignee_actor_id,assignee_role,
|
|
13307
|
-
latest_work_id,latest_accept_id,is_final_output,
|
|
13334
|
+
latest_work_id,latest_success_work_id,latest_accept_id,is_final_output,
|
|
13308
13335
|
fields,cancelled_at,row_version,created_at,updated_at)
|
|
13309
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)
|
|
13336
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17)
|
|
13310
13337
|
ON CONFLICT (id) DO NOTHING`,
|
|
13311
13338
|
[
|
|
13312
13339
|
n.id,
|
|
@@ -13318,6 +13345,7 @@ var init_store_postgres = __esm({
|
|
|
13318
13345
|
n.assigneeActorId,
|
|
13319
13346
|
n.assigneeRole,
|
|
13320
13347
|
n.latestWorkId,
|
|
13348
|
+
n.latestSuccessWorkId,
|
|
13321
13349
|
n.latestAcceptId,
|
|
13322
13350
|
n.isFinalOutput,
|
|
13323
13351
|
n.fields ? JSON.stringify(n.fields) : null,
|
|
@@ -13586,11 +13614,14 @@ var init_store_postgres = __esm({
|
|
|
13586
13614
|
* `NOT EXISTS` 那一段是"我前面还有没跑完的兄弟就别动我";`SKIP LOCKED` 是
|
|
13587
13615
|
* "别的 worker 正在处理这张工单就跳过整张"。两者合起来 = 按工单分区的串行队列。
|
|
13588
13616
|
*/
|
|
13589
|
-
async claimNextEvent(now) {
|
|
13617
|
+
async claimNextEvent(now, recoverAppliedBefore) {
|
|
13590
13618
|
const res = await this.c.query(
|
|
13591
13619
|
`SELECT e.* FROM ${this.s}.events e
|
|
13592
|
-
WHERE
|
|
13593
|
-
|
|
13620
|
+
WHERE (
|
|
13621
|
+
(e.status = 'pending' AND (e.available_at IS NULL OR e.available_at <= $1))
|
|
13622
|
+
-- \u5D29\u6E83\u81EA\u6108\uFF1A\u5361\u5728 applied \u592A\u4E45\u7684\uFF08\u8FDB\u7A0B\u6B7B\u5728\u300C\u843D\u72B6\u6001\u300D\u4E0E\u300C\u6536\u5C3E\u300D\u4E4B\u95F4\uFF09\u4E5F\u56DE\u6536\u91CD\u8DD1
|
|
13623
|
+
OR ($2::timestamptz IS NOT NULL AND e.status = 'applied' AND e.applied_at < $2::timestamptz)
|
|
13624
|
+
)
|
|
13594
13625
|
AND NOT EXISTS (
|
|
13595
13626
|
SELECT 1 FROM ${this.s}.events p
|
|
13596
13627
|
WHERE p.workorder_id = e.workorder_id
|
|
@@ -13599,7 +13630,7 @@ var init_store_postgres = __esm({
|
|
|
13599
13630
|
ORDER BY e.seq
|
|
13600
13631
|
FOR UPDATE OF e SKIP LOCKED
|
|
13601
13632
|
LIMIT 1`,
|
|
13602
|
-
[now]
|
|
13633
|
+
[now, recoverAppliedBefore ?? null]
|
|
13603
13634
|
);
|
|
13604
13635
|
return res.rows[0] ? toRecord(res.rows[0]) : null;
|
|
13605
13636
|
}
|
|
@@ -13640,10 +13671,14 @@ var init_store_postgres = __esm({
|
|
|
13640
13671
|
);
|
|
13641
13672
|
return (res.rowCount ?? 0) > 0;
|
|
13642
13673
|
}
|
|
13643
|
-
async claimAllPending(now) {
|
|
13674
|
+
async claimAllPending(now, recoverAppliedBefore) {
|
|
13644
13675
|
const res = await this.c.query(
|
|
13645
13676
|
`SELECT e.* FROM ${this.s}.events e
|
|
13646
|
-
WHERE
|
|
13677
|
+
WHERE (
|
|
13678
|
+
(e.status = 'pending' AND (e.available_at IS NULL OR e.available_at <= $1))
|
|
13679
|
+
-- \u5D29\u6E83\u81EA\u6108\uFF1A\u89C1 claimNextEvent \u7684\u6CE8\u91CA
|
|
13680
|
+
OR ($2::timestamptz IS NOT NULL AND e.status = 'applied' AND e.applied_at < $2::timestamptz)
|
|
13681
|
+
)
|
|
13647
13682
|
AND NOT EXISTS (SELECT 1 FROM ${this.s}.events p WHERE p.workorder_id = e.workorder_id
|
|
13648
13683
|
AND p.status IN ('pending','applied') AND p.seq < e.seq)
|
|
13649
13684
|
-- \u2605 scan \u4E8B\u4EF6\u6392\u524D\u9762\uFF1Aorigin='apply' \u4E14\u65E0\u4E0A\u6E38\u4E8B\u4EF6\uFF08\u4E0D\u662F\u88AB\u5176\u4ED6\u4E8B\u4EF6\u89E6\u53D1\u7684\uFF09
|
|
@@ -13653,7 +13688,7 @@ var init_store_postgres = __esm({
|
|
|
13653
13688
|
CASE WHEN e.origin = 'apply' AND e.caused_by_seq IS NULL THEN 0 ELSE 1 END,
|
|
13654
13689
|
e.seq
|
|
13655
13690
|
FOR UPDATE OF e SKIP LOCKED`,
|
|
13656
|
-
[now]
|
|
13691
|
+
[now, recoverAppliedBefore ?? null]
|
|
13657
13692
|
);
|
|
13658
13693
|
return res.rows.map(toRecord);
|
|
13659
13694
|
}
|
|
@@ -13810,6 +13845,7 @@ function newNode(id, workorderId, at, version2) {
|
|
|
13810
13845
|
assigneeActorId: null,
|
|
13811
13846
|
assigneeRole: null,
|
|
13812
13847
|
latestWorkId: null,
|
|
13848
|
+
latestSuccessWorkId: null,
|
|
13813
13849
|
latestAcceptId: null,
|
|
13814
13850
|
isFinalOutput: false,
|
|
13815
13851
|
cancelledAt: null,
|
|
@@ -14138,6 +14174,10 @@ var init_workorder = __esm({
|
|
|
14138
14174
|
});
|
|
14139
14175
|
|
|
14140
14176
|
// ../engine/src/handlers/work.ts
|
|
14177
|
+
function latestSuccessOf(state, nodeId) {
|
|
14178
|
+
const successes = state.worksOf(nodeId).filter((w2) => !w2.replyToIssueId && w2.status === "success").sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
14179
|
+
return successes.length > 0 ? successes[successes.length - 1].id : null;
|
|
14180
|
+
}
|
|
14141
14181
|
var workCreate, workKill, workResponse, workHandoffRecorded, workStart, workSubmitOutput, workTimeout;
|
|
14142
14182
|
var init_work = __esm({
|
|
14143
14183
|
"../engine/src/handlers/work.ts"() {
|
|
@@ -14267,6 +14307,12 @@ var init_work = __esm({
|
|
|
14267
14307
|
...w2.status !== "dead" ? { status: "dead" } : {}
|
|
14268
14308
|
});
|
|
14269
14309
|
}
|
|
14310
|
+
if (node.latestSuccessWorkId) {
|
|
14311
|
+
const ls = state.work(node.latestSuccessWorkId);
|
|
14312
|
+
if (!ls || ls.status !== "success") {
|
|
14313
|
+
state.updateNode(e.nodeId, { latestSuccessWorkId: latestSuccessOf(state, e.nodeId) });
|
|
14314
|
+
}
|
|
14315
|
+
}
|
|
14270
14316
|
killNodeReviews(state, e.nodeId, ctx.at);
|
|
14271
14317
|
},
|
|
14272
14318
|
async effect(e, ctx) {
|
|
@@ -14300,6 +14346,9 @@ var init_work = __esm({
|
|
|
14300
14346
|
outcomeDetail: businessFailed ? { ...e.detail ?? {}, reason: "agent business handoff not delivered" } : e.detail,
|
|
14301
14347
|
status
|
|
14302
14348
|
});
|
|
14349
|
+
if (status === "success") {
|
|
14350
|
+
state.updateNode(w2.nodeId, { latestSuccessWorkId: e.workId });
|
|
14351
|
+
}
|
|
14303
14352
|
}
|
|
14304
14353
|
};
|
|
14305
14354
|
workHandoffRecorded = {
|
|
@@ -14309,17 +14358,20 @@ var init_work = __esm({
|
|
|
14309
14358
|
const w2 = state.work(e.workId);
|
|
14310
14359
|
if (!w2 || e.producedBy !== "agent") return;
|
|
14311
14360
|
if (w2.agentHandoffAttemptId && w2.agentHandoffAttemptId !== e.attemptId) return;
|
|
14361
|
+
const flipsToFailed = e.status === "not_delivered" && w2.endedAt && !w2.deadAt;
|
|
14312
14362
|
state.updateWork(e.workId, {
|
|
14313
14363
|
agentHandoffAttemptId: e.attemptId,
|
|
14314
14364
|
businessHandoffStatus: e.status,
|
|
14315
14365
|
businessHandoffRecordedAt: ctx.at,
|
|
14316
|
-
|
|
14317
|
-
...e.status === "not_delivered" && w2.endedAt && !w2.deadAt ? {
|
|
14366
|
+
...flipsToFailed ? {
|
|
14318
14367
|
status: "failed",
|
|
14319
14368
|
outcome: "failed",
|
|
14320
14369
|
outcomeDetail: { ...w2.outcomeDetail ?? {}, reason: "agent business handoff not delivered" }
|
|
14321
14370
|
} : {}
|
|
14322
14371
|
});
|
|
14372
|
+
if (flipsToFailed && state.node(w2.nodeId)?.latestSuccessWorkId === e.workId) {
|
|
14373
|
+
state.updateNode(w2.nodeId, { latestSuccessWorkId: latestSuccessOf(state, w2.nodeId) });
|
|
14374
|
+
}
|
|
14323
14375
|
}
|
|
14324
14376
|
};
|
|
14325
14377
|
workStart = {
|
|
@@ -14865,7 +14917,7 @@ function emptySnapshotPlaceholder(record8) {
|
|
|
14865
14917
|
reviews: []
|
|
14866
14918
|
};
|
|
14867
14919
|
}
|
|
14868
|
-
var SYSTEM_ACTOR2, NOOP_IO, SYSTEM_CLOCK, HandlerRegistry, EngineBus, ApplyFailure, sleep;
|
|
14920
|
+
var SYSTEM_ACTOR2, NOOP_IO, SYSTEM_CLOCK, HandlerRegistry, DEFAULT_STUCK_APPLIED_RECOVERY_MS, EngineBus, ApplyFailure, sleep;
|
|
14869
14921
|
var init_bus = __esm({
|
|
14870
14922
|
"../engine/src/bus.ts"() {
|
|
14871
14923
|
"use strict";
|
|
@@ -14903,6 +14955,7 @@ var init_bus = __esm({
|
|
|
14903
14955
|
return [...this.byKind.values()].flat();
|
|
14904
14956
|
}
|
|
14905
14957
|
};
|
|
14958
|
+
DEFAULT_STUCK_APPLIED_RECOVERY_MS = 5 * 6e4;
|
|
14906
14959
|
EngineBus = class {
|
|
14907
14960
|
store;
|
|
14908
14961
|
io;
|
|
@@ -14911,6 +14964,7 @@ var init_bus = __esm({
|
|
|
14911
14964
|
policy;
|
|
14912
14965
|
registry;
|
|
14913
14966
|
maxEffectAttempts;
|
|
14967
|
+
stuckAppliedRecoveryMs;
|
|
14914
14968
|
running = false;
|
|
14915
14969
|
/** 事件监听器(内存 pubsub):每批事件处理完调用,供 /api/events SSE 推送(替代旧 oplog.subscribe)。 */
|
|
14916
14970
|
eventListener = null;
|
|
@@ -14922,10 +14976,19 @@ var init_bus = __esm({
|
|
|
14922
14976
|
this.policy = opts.policy ?? CODE_DEFAULT_POLICY;
|
|
14923
14977
|
this.registry = new HandlerRegistry(opts.handlers);
|
|
14924
14978
|
this.maxEffectAttempts = opts.maxEffectAttempts ?? 3;
|
|
14979
|
+
this.stuckAppliedRecoveryMs = opts.stuckAppliedRecoveryMs ?? DEFAULT_STUCK_APPLIED_RECOVERY_MS;
|
|
14925
14980
|
}
|
|
14926
14981
|
setIO(io) {
|
|
14927
14982
|
this.io = io;
|
|
14928
14983
|
}
|
|
14984
|
+
/**
|
|
14985
|
+
* 崩溃自愈水位线:早于此刻仍停在 `applied` 的事件,认为是「进程死在落状态与收尾之间」的残留,
|
|
14986
|
+
* 回收重跑。`stuckAppliedRecoveryMs <= 0` 时返回 undefined(= 关闭自愈,维持旧行为)。
|
|
14987
|
+
*/
|
|
14988
|
+
recoverAppliedBefore() {
|
|
14989
|
+
if (this.stuckAppliedRecoveryMs <= 0) return void 0;
|
|
14990
|
+
return new Date(Date.parse(this.clock.now()) - this.stuckAppliedRecoveryMs).toISOString();
|
|
14991
|
+
}
|
|
14929
14992
|
/** 注册事件监听器——新事件处理完即回调(serve 桥接 SSE 实时推送,替代 oplog.subscribe)。返回退订。 */
|
|
14930
14993
|
onEvent(fn) {
|
|
14931
14994
|
this.eventListener = fn;
|
|
@@ -15014,7 +15077,7 @@ var init_bus = __esm({
|
|
|
15014
15077
|
let lastFullScan = 0;
|
|
15015
15078
|
while (true) {
|
|
15016
15079
|
try {
|
|
15017
|
-
const batch = await this.store.transaction((tx) => tx.claimAllPending(this.clock.now()));
|
|
15080
|
+
const batch = await this.store.transaction((tx) => tx.claimAllPending(this.clock.now(), this.recoverAppliedBefore()));
|
|
15018
15081
|
const touched = /* @__PURE__ */ new Set();
|
|
15019
15082
|
if (batch.length > 0) {
|
|
15020
15083
|
for (const record8 of batch) {
|
|
@@ -15247,6 +15310,10 @@ var init_bus = __esm({
|
|
|
15247
15310
|
};
|
|
15248
15311
|
let ok = true;
|
|
15249
15312
|
for (const h of handlers) {
|
|
15313
|
+
const effectKey = `${h.name}#effect`;
|
|
15314
|
+
if (record8.appliedAt && await this.store.transaction((tx) => tx.isHandlerDone(record8.seq, effectKey))) {
|
|
15315
|
+
continue;
|
|
15316
|
+
}
|
|
15250
15317
|
let lastErr = "";
|
|
15251
15318
|
for (let attempt = 1; attempt <= this.maxEffectAttempts; attempt++) {
|
|
15252
15319
|
try {
|
|
@@ -15259,6 +15326,8 @@ var init_bus = __esm({
|
|
|
15259
15326
|
}
|
|
15260
15327
|
if (lastErr) {
|
|
15261
15328
|
ok = false;
|
|
15329
|
+
} else {
|
|
15330
|
+
await this.store.transaction((tx) => tx.markHandlerDone(record8.seq, effectKey));
|
|
15262
15331
|
}
|
|
15263
15332
|
}
|
|
15264
15333
|
await this.store.transaction(async (tx) => {
|
|
@@ -15317,7 +15386,7 @@ var init_bus = __esm({
|
|
|
15317
15386
|
/* ═══════════════════════ 内部:旧 applyPhase(claim + apply 一体) ═══════════════════════ */
|
|
15318
15387
|
async applyPhase() {
|
|
15319
15388
|
return this.store.transaction(async (tx) => {
|
|
15320
|
-
const record8 = await tx.claimNextEvent(this.clock.now());
|
|
15389
|
+
const record8 = await tx.claimNextEvent(this.clock.now(), this.recoverAppliedBefore());
|
|
15321
15390
|
if (!record8) return null;
|
|
15322
15391
|
const snapshot = await tx.loadWorkorder(record8.workorderId);
|
|
15323
15392
|
if (snapshot?.workorder.doneAt) {
|
|
@@ -15442,6 +15511,10 @@ function mapWorkStateToRevisionState(w2) {
|
|
|
15442
15511
|
if (w2.acceptanceState === "rejected") return "rejected";
|
|
15443
15512
|
return "proposed";
|
|
15444
15513
|
// success but not yet judged
|
|
15514
|
+
// ★ ADR 0148:failed 尝试没有对应的 RevisionState(枚举只有 working/proposed/merged/rejected/
|
|
15515
|
+
// superseded,改枚举会牵动契约与前端,见红线 4)——仍投影成 superseded 保持生命周期语义,但**内容
|
|
15516
|
+
// 如实为 empty**(workToRevision 对无产出 work 给 contentKind="empty")。读面(CLI 版本列表)据此把
|
|
15517
|
+
// 「superseded + empty」如实标成「未产出」,不再让「这次失败了」被读成「被更好的版本取代」。
|
|
15445
15518
|
case "failed":
|
|
15446
15519
|
return "superseded";
|
|
15447
15520
|
case "retry":
|
|
@@ -15711,8 +15784,16 @@ function bridgeLoadReadModel(input) {
|
|
|
15711
15784
|
const lw = node.latestWorkId ? workById.get(node.latestWorkId) : void 0;
|
|
15712
15785
|
if (lw) {
|
|
15713
15786
|
const st = lw.status ?? (lw.retryAt ? "retry" : lw.deadAt ? "dead" : lw.endedAt ? lw.outcome === "failed" ? "failed" : "success" : "running");
|
|
15714
|
-
|
|
15715
|
-
|
|
15787
|
+
let uiSt;
|
|
15788
|
+
if (st === "retry") uiSt = null;
|
|
15789
|
+
else if (st === "running") uiSt = "running";
|
|
15790
|
+
else if (node.latestSuccessWorkId) uiSt = "success";
|
|
15791
|
+
else uiSt = st;
|
|
15792
|
+
art.latestWorkState = uiSt;
|
|
15793
|
+
art.latestAccepted = node.latestSuccessWorkId !== null && node.latestAcceptId === node.latestSuccessWorkId;
|
|
15794
|
+
if ((st === "failed" || st === "dead") && node.latestSuccessWorkId) {
|
|
15795
|
+
art.currentRev = node.latestSuccessWorkId;
|
|
15796
|
+
}
|
|
15716
15797
|
} else {
|
|
15717
15798
|
art.latestWorkState = null;
|
|
15718
15799
|
art.latestAccepted = false;
|
|
@@ -15902,6 +15983,26 @@ var init_bridge_readmodel = __esm({
|
|
|
15902
15983
|
}
|
|
15903
15984
|
});
|
|
15904
15985
|
|
|
15986
|
+
// ../core/src/propose-guard.ts
|
|
15987
|
+
function proposeBlock(head, headArtifactCount) {
|
|
15988
|
+
if (!head) return null;
|
|
15989
|
+
const state = workState(head, hasOutput(head, headArtifactCount));
|
|
15990
|
+
if (state === "dead") {
|
|
15991
|
+
const why = head.rejectedReason ? `\uFF1A${head.rejectedReason}` : "";
|
|
15992
|
+
return {
|
|
15993
|
+
code: "head-dead",
|
|
15994
|
+
detail: `\u8FD9\u4E00\u7248\uFF08${head.id}\uFF09\u5DF2\u88AB\u505C\u6389\uFF08${head.deadAt ?? "?"}${why}\uFF09\u2014\u2014\u4F5C\u5E9F\u7248\u4E4B\u4E0A\u4E0D\u80FD\u518D\u4EA4\u7A3F\u3002\u8981\u7EE7\u7EED\uFF0C\u5148\u91CD\u8DD1\u672C\u8282\u70B9\uFF08\u5728\u63A7\u5236\u53F0\u70B9\u300C\u91CD\u65B0\u8FD0\u884C\u300D\uFF0C\u6216\u8BA9\u7BA1\u7406\u8005\u91CD\u6D3E\uFF09\u62FF\u5230\u4E00\u4E2A\u53EF\u5199\u7684\u65B0\u7248\u672C\uFF0C\u518D propose\u3002`
|
|
15995
|
+
};
|
|
15996
|
+
}
|
|
15997
|
+
return null;
|
|
15998
|
+
}
|
|
15999
|
+
var init_propose_guard = __esm({
|
|
16000
|
+
"../core/src/propose-guard.ts"() {
|
|
16001
|
+
"use strict";
|
|
16002
|
+
init_src3();
|
|
16003
|
+
}
|
|
16004
|
+
});
|
|
16005
|
+
|
|
15905
16006
|
// ../core/src/preview-intervention.ts
|
|
15906
16007
|
function resolveOwner(type, typeDef, roleIndex, schemaSize) {
|
|
15907
16008
|
if (!typeDef) {
|
|
@@ -16367,6 +16468,7 @@ var init_kernel_bridge = __esm({
|
|
|
16367
16468
|
init_closure_gate();
|
|
16368
16469
|
init_kernel();
|
|
16369
16470
|
init_bridge_readmodel();
|
|
16471
|
+
init_propose_guard();
|
|
16370
16472
|
init_src3();
|
|
16371
16473
|
init_src3();
|
|
16372
16474
|
init_preview_intervention();
|
|
@@ -16697,8 +16799,14 @@ var init_kernel_bridge = __esm({
|
|
|
16697
16799
|
}
|
|
16698
16800
|
}
|
|
16699
16801
|
if (!workId) {
|
|
16700
|
-
const
|
|
16701
|
-
|
|
16802
|
+
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
16803
|
+
const headId = snap?.nodes.find((n) => n.id === args.artifactId)?.latestWorkId ?? null;
|
|
16804
|
+
if (headId) {
|
|
16805
|
+
const headWork = snap?.works.find((w2) => w2.id === headId);
|
|
16806
|
+
const headOutputs = snap?.artifacts.filter((a) => a.workId === headId).length ?? 0;
|
|
16807
|
+
const block = proposeBlock(headWork, headOutputs);
|
|
16808
|
+
if (block) throw new KernelError(block.detail, block.code);
|
|
16809
|
+
}
|
|
16702
16810
|
const worksCount = [...this.model.revisions.values()].filter((r) => r.artifactId === args.artifactId).length;
|
|
16703
16811
|
const revisionId = `wk:${args.artifactId}:${worksCount + 1}`;
|
|
16704
16812
|
await this.commit({
|
|
@@ -16974,7 +17082,8 @@ var init_kernel_bridge = __esm({
|
|
|
16974
17082
|
const artifact = this.model.artifacts.get(args.artifactId);
|
|
16975
17083
|
const currentRev = artifact?.currentRev ? this.model.revisions.get(artifact.currentRev) : void 0;
|
|
16976
17084
|
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
16977
|
-
const
|
|
17085
|
+
const targetNode = snap?.nodes.find((n) => n.id === args.artifactId);
|
|
17086
|
+
const head = (currentRev && currentRev.state === "proposed" ? currentRev.id : null) ?? concludedHead(this.model, args.artifactId) ?? (targetNode?.latestSuccessWorkId ?? targetNode?.latestWorkId ?? null);
|
|
16978
17087
|
if (!head) throw new KernelError(`force-conclude \u65E0\u4ECE\u4E0B\u624B\uFF08${args.artifactId}\uFF09\uFF1A\u672C\u8282\u70B9\u8FD8\u6CA1\u6709\u4EFB\u4F55\u4E00\u7248\u4EA7\u51FA`, "no-head");
|
|
16979
17088
|
const block = snap ? acceptBlock(new WorkorderState(snap), head) : null;
|
|
16980
17089
|
if (block?.code === "already-accepted") return;
|
|
@@ -201490,7 +201599,8 @@ ${res.warning}`);
|
|
|
201490
201599
|
r.id === v2.artifact.currentRev ? "\u2190 current head" : "",
|
|
201491
201600
|
r.id === v2.concludedHead ? "\u2190 \u5DF2\u4EA4\u4ED8" : ""
|
|
201492
201601
|
].filter(Boolean).join(" ");
|
|
201493
|
-
|
|
201602
|
+
const stateLabel = r.state === "superseded" && r.contentKind === "empty" ? "\u672A\u4EA7\u51FA" : r.state;
|
|
201603
|
+
println(` ${r.id} ${stateLabel} by ${r.author} "${r.reason}"${marks ? ` ${marks}` : ""}`);
|
|
201494
201604
|
}
|
|
201495
201605
|
const NOTE_ABSTRACT = 150;
|
|
201496
201606
|
const voteRows = [];
|
|
@@ -201779,7 +201889,7 @@ function syncRuntimeAssets(candidateRoots, binDir) {
|
|
|
201779
201889
|
}
|
|
201780
201890
|
|
|
201781
201891
|
// src/index.ts
|
|
201782
|
-
var PKG_VERSION = true ? "0.1.
|
|
201892
|
+
var PKG_VERSION = true ? "0.1.104" : "dev";
|
|
201783
201893
|
var OASIS_DIR = path26.join(os10.homedir(), ".oasis");
|
|
201784
201894
|
var CONFIG_FILE = path26.join(OASIS_DIR, "node-config.json");
|
|
201785
201895
|
var PID_FILE = path26.join(OASIS_DIR, "node.pid");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oasis_test",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.104",
|
|
4
4
|
"description": "Oasis node daemon + CLI — background daemon, auto-start, full server CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"oasis": "./dist/index.js"
|
|
@@ -26,6 +26,6 @@
|
|
|
26
26
|
"node": ">=20"
|
|
27
27
|
},
|
|
28
28
|
"oasisRelease": {
|
|
29
|
-
"sourceHead": "
|
|
29
|
+
"sourceHead": "2b9f5db18e1ad2cf93a2e1c7c49aa47765d228c8"
|
|
30
30
|
}
|
|
31
31
|
}
|