@nextclaw/x-harness 0.1.1 → 0.2.1
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/LICENSE +231 -6
- package/README.md +15 -1
- package/SOURCE.md +9 -0
- package/cli/main.js +232 -200
- package/dist/api.d.ts +56 -2
- package/dist/api.js +130 -79
- package/dist/boundary.js +1 -0
- package/dist/browser-common.d.ts +11 -0
- package/dist/browser-common.js +34 -18
- package/dist/browser-dom-capabilities.d.ts +7 -2
- package/dist/browser-dom-capabilities.js +123 -102
- package/dist/browser-dom-dependencies.js +1 -0
- package/dist/browser-dom-editing.d.ts +20 -2
- package/dist/browser-dom-editing.js +148 -116
- package/dist/browser-dom-navigation.d.ts +11 -2
- package/dist/browser-dom-navigation.js +66 -50
- package/dist/browser-dom-submission.d.ts +6 -1
- package/dist/browser-dom-submission.js +37 -18
- package/dist/browser-dom.d.ts +13 -1
- package/dist/browser-dom.js +113 -66
- package/dist/browser-native.d.ts +14 -1
- package/dist/browser-native.js +161 -128
- package/dist/creator-native.d.ts +3 -0
- package/dist/creator-native.js +113 -0
- package/dist/diagnostics.d.ts +11 -0
- package/dist/diagnostics.js +10 -0
- package/dist/growth-batch.d.ts +6 -3
- package/dist/growth-batch.js +157 -135
- package/dist/growth-dependencies.js +1 -0
- package/dist/growth-execution.d.ts +8 -2
- package/dist/growth-execution.js +68 -37
- package/dist/growth-parallel.d.ts +6 -2
- package/dist/growth-parallel.js +78 -47
- package/dist/growth-plan.d.ts +8 -3
- package/dist/growth-plan.js +45 -23
- package/dist/growth-policy.d.ts +11 -2
- package/dist/growth-policy.js +80 -53
- package/dist/growth-publishing.d.ts +7 -2
- package/dist/growth-publishing.js +72 -46
- package/dist/growth-reading.d.ts +6 -2
- package/dist/growth-reading.js +47 -25
- package/dist/growth-recovery.d.ts +7 -4
- package/dist/growth-recovery.js +177 -148
- package/dist/growth-state.d.ts +10 -2
- package/dist/growth-state.js +67 -30
- package/dist/growth-tasks.d.ts +19 -16
- package/dist/growth-tasks.js +153 -90
- package/dist/growth.d.ts +25 -1
- package/dist/growth.js +207 -124
- package/dist/platform-capabilities.d.ts +6 -2
- package/dist/platform-capabilities.js +18 -8
- package/dist/platform-collections.d.ts +10 -3
- package/dist/platform-collections.js +62 -35
- package/dist/platform-dependencies.js +1 -0
- package/dist/platform-diagnostics.d.ts +7 -2
- package/dist/platform-diagnostics.js +31 -19
- package/dist/platform-observation.d.ts +10 -2
- package/dist/platform-observation.js +44 -23
- package/dist/platform-targets.d.ts +10 -3
- package/dist/platform-targets.js +39 -14
- package/dist/platform-writing.d.ts +10 -2
- package/dist/platform-writing.js +63 -32
- package/dist/platform.d.ts +22 -1
- package/dist/platform.js +120 -52
- package/dist/readers-ax-host.d.ts +11 -1
- package/dist/readers-ax-host.js +108 -99
- package/dist/readers-ax-program.js +5 -0
- package/dist/readers-ax-tree.js +1 -0
- package/dist/readers-cli.js +79 -0
- package/dist/readers.js +9 -0
- package/dist/round-state.js +18 -1
- package/dist/store.d.ts +22 -4
- package/dist/store.js +173 -151
- package/dist/types.js +1 -0
- package/generated/observation.mjs +1 -1
- package/package.json +23 -9
- package/skill/SKILL.md +2 -2
- package/skill/references/reads.md +1 -1
- package/skill/references/recovery.md +4 -4
- package/skill/references/setup.md +1 -1
package/dist/growth-batch.js
CHANGED
|
@@ -1,144 +1,166 @@
|
|
|
1
1
|
import { followWindows, same } from "./growth-constants.js";
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
.at(-1);
|
|
56
|
-
const lastAction = original.state.actions.find((a) => a.id === lastAttempt?.actionId);
|
|
57
|
-
const sameBatchRecovery = resumedBatch &&
|
|
58
|
-
lastAction?.status === "verified" &&
|
|
59
|
-
evidenceExists(lastAction.resultEvidence) &&
|
|
60
|
-
handles.some((h) => same(h, lastAction.handle));
|
|
61
|
-
if (preparedCount && previousGroup?.attempts && !sameBatchRecovery)
|
|
62
|
-
eligibleAt = Math.max(eligibleAt, Date.parse(previousGroup.lastAt) + 180000);
|
|
63
|
-
for (const [duration, limit] of followWindows) {
|
|
64
|
-
const recent = (original.state.attempts ?? [])
|
|
65
|
-
.filter((a) => a.kind === "follow" && Date.parse(a.at) > clock() - duration)
|
|
66
|
-
.sort((a, b) => Date.parse(a.at) - Date.parse(b.at));
|
|
67
|
-
if (preparedCount > limit - recent.length)
|
|
68
|
-
eligibleAt = Math.max(eligibleAt, Date.parse(recent[recent.length - limit + preparedCount - 1].at) +
|
|
69
|
-
duration);
|
|
70
|
-
}
|
|
71
|
-
if (eligibleAt > clock()) {
|
|
72
|
-
const resumeAt = new Date(eligibleAt).toISOString();
|
|
73
|
-
await store.transaction((snapshot) => {
|
|
74
|
-
const next = structuredClone(snapshot.state);
|
|
75
|
-
Object.assign(next.followBatches.find((b) => b.id === batchId), { status: "WAITING", remaining: preparedCount, resumeAt });
|
|
76
|
-
next.updatedAt = stamp();
|
|
77
|
-
return next;
|
|
2
|
+
export class FollowBatch {
|
|
3
|
+
#prepareActionsForCall;
|
|
4
|
+
#store;
|
|
5
|
+
#scope;
|
|
6
|
+
#stamp;
|
|
7
|
+
#clock;
|
|
8
|
+
#evidenceExists;
|
|
9
|
+
#executor;
|
|
10
|
+
#parallelRound;
|
|
11
|
+
#round;
|
|
12
|
+
constructor(dependencies) {
|
|
13
|
+
this.followBatch = this.followBatch.bind(this);
|
|
14
|
+
const { prepareActionsForCall, store, scope, stamp, clock, evidenceExists, executor, parallelRound, round, } = dependencies;
|
|
15
|
+
this.#prepareActionsForCall = prepareActionsForCall;
|
|
16
|
+
this.#store = store;
|
|
17
|
+
this.#scope = scope;
|
|
18
|
+
this.#stamp = stamp;
|
|
19
|
+
this.#clock = clock;
|
|
20
|
+
this.#evidenceExists = evidenceExists;
|
|
21
|
+
this.#executor = executor;
|
|
22
|
+
this.#parallelRound = parallelRound;
|
|
23
|
+
this.#round = round;
|
|
24
|
+
}
|
|
25
|
+
async followBatch({ taskId, handles, preparedActions } = {}) {
|
|
26
|
+
const executor = this.#executor;
|
|
27
|
+
if (!Array.isArray(handles) ||
|
|
28
|
+
handles.length < 5 ||
|
|
29
|
+
handles.length > 10 ||
|
|
30
|
+
handles.some((h) => !/^[A-Za-z0-9_]{1,15}$/.test(h)) ||
|
|
31
|
+
new Set(handles.map((h) => h.toLowerCase())).size !== handles.length)
|
|
32
|
+
throw new Error("正常关注子批需5~10位不同的已准备对象");
|
|
33
|
+
if (preparedActions?.some((a) => a.kind !== "follow" ||
|
|
34
|
+
!handles.some((h) => same(h, a.handle))))
|
|
35
|
+
throw new Error("batch-action-target-mismatch");
|
|
36
|
+
await this.#prepareActionsForCall(taskId, preparedActions);
|
|
37
|
+
let batchId, resumedBatch = false;
|
|
38
|
+
const original = await this.#store.transaction(async (snapshot) => {
|
|
39
|
+
if (!this.#scope(snapshot.state).includes("follow"))
|
|
40
|
+
throw new Error("kind-outside-round-scope");
|
|
41
|
+
if (snapshot.state.batchId !== taskId ||
|
|
42
|
+
handles.some((h) => !snapshot.state.actions?.some((a) => a.kind === "follow" && same(a.handle, h))))
|
|
43
|
+
throw new Error("子批名单必须归原任务队列");
|
|
44
|
+
const next = structuredClone(snapshot.state);
|
|
45
|
+
next.followBatches ??= [];
|
|
46
|
+
const previous = next.followBatches.findLast((b) => b.status !== "SETTLED" &&
|
|
47
|
+
b.handles.length === handles.length &&
|
|
48
|
+
b.handles.every((h, i) => same(h, handles[i])));
|
|
49
|
+
resumedBatch = Boolean(previous);
|
|
50
|
+
batchId = previous?.id ?? `${taskId}-FOLLOW-${this.#stamp()}`;
|
|
51
|
+
if (previous)
|
|
52
|
+
Object.assign(previous, {
|
|
53
|
+
status: "RUNNING",
|
|
54
|
+
lastResumedAt: this.#stamp(),
|
|
78
55
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
batchId,
|
|
56
|
+
else
|
|
57
|
+
next.followBatches.push({
|
|
58
|
+
id: batchId,
|
|
59
|
+
handles,
|
|
82
60
|
planned: handles.length,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
unknownInBatch: original.state.actions.filter((a) => handles.some((h) => same(h, a.handle)) &&
|
|
86
|
-
["unknown", "intent"].includes(a.status)).length,
|
|
87
|
-
remaining: preparedCount,
|
|
88
|
-
mayEnd: false,
|
|
89
|
-
decision: [
|
|
90
|
-
{
|
|
91
|
-
kind: "follow",
|
|
92
|
-
status: "waiting",
|
|
93
|
-
reason: "normal-batch-capacity-or-cooldown",
|
|
94
|
-
resumeAt,
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
evidenceRef: store.path,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
let result;
|
|
101
|
-
const exceptional = [];
|
|
102
|
-
for (const handle of handles) {
|
|
103
|
-
// A resumed batch submits only its remaining prepared members. Recovery
|
|
104
|
-
// stays in the read-only API; a frozen member must not disturb that path.
|
|
105
|
-
if (!original.state.actions.some((a) => a.kind === "follow" &&
|
|
106
|
-
same(a.handle, handle) &&
|
|
107
|
-
a.status === "prepared"))
|
|
108
|
-
continue;
|
|
109
|
-
result = await (executor ? parallelRound : round)({
|
|
110
|
-
taskId,
|
|
111
|
-
requestedKind: "follow",
|
|
112
|
-
selector: (a) => a.kind === "follow" && same(a.handle, handle),
|
|
113
|
-
});
|
|
114
|
-
const relevant = (result.decision ?? []).filter((d) => d.kind === "follow" ||
|
|
115
|
-
["harness-boundary-error", "original-ledger-conflict"].includes(d.reason) ||
|
|
116
|
-
original.state.actions.some((a) => a.id === d.actionId && a.kind === "follow"));
|
|
117
|
-
exceptional.push(...relevant);
|
|
118
|
-
// A new unconfirmed submission is a diagnostic boundary, not permission
|
|
119
|
-
// to consume the rest of the batch on the same failing write path.
|
|
120
|
-
if (relevant.length)
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
const after = await store.load();
|
|
124
|
-
const summary = summarizeFollowBatch(taskId, batchId, handles, after, original, result, exceptional, store.path);
|
|
125
|
-
await store.transaction((snapshot) => {
|
|
126
|
-
const next = structuredClone(snapshot.state), batch = next.followBatches.find((b) => b.id === batchId);
|
|
127
|
-
Object.assign(batch, {
|
|
128
|
-
attempted: (batch.attempted ?? 0) + summary.attempted,
|
|
129
|
-
verifiedTotal: (batch.verifiedTotal ?? 0) + summary.verifiedThisCall,
|
|
130
|
-
verifiedThisCall: summary.verifiedThisCall,
|
|
131
|
-
unknown: summary.unknownInBatch,
|
|
132
|
-
remaining: summary.remaining,
|
|
133
|
-
finishedAt: stamp(),
|
|
134
|
-
status: summary.remaining || summary.unknownInBatch ? "PARTIAL" : "SETTLED",
|
|
61
|
+
startedAt: this.#stamp(),
|
|
62
|
+
status: "RUNNING",
|
|
135
63
|
});
|
|
136
|
-
|
|
64
|
+
next.updatedAt = this.#stamp();
|
|
65
|
+
return next;
|
|
66
|
+
});
|
|
67
|
+
const preparedCount = original.state.actions.filter((a) => a.kind === "follow" &&
|
|
68
|
+
a.status === "prepared" &&
|
|
69
|
+
handles.some((h) => same(h, a.handle))).length;
|
|
70
|
+
let eligibleAt = this.#clock();
|
|
71
|
+
const previousGroup = original.state.execution?.followGroup;
|
|
72
|
+
const lastAttempt = (original.state.attempts ?? [])
|
|
73
|
+
.filter((a) => a.kind === "follow")
|
|
74
|
+
.sort((a, b) => Date.parse(a.at) - Date.parse(b.at))
|
|
75
|
+
.at(-1);
|
|
76
|
+
const lastAction = original.state.actions.find((a) => a.id === lastAttempt?.actionId);
|
|
77
|
+
const sameBatchRecovery = resumedBatch &&
|
|
78
|
+
lastAction?.status === "verified" &&
|
|
79
|
+
this.#evidenceExists(lastAction.resultEvidence) &&
|
|
80
|
+
handles.some((h) => same(h, lastAction.handle));
|
|
81
|
+
if (preparedCount && previousGroup?.attempts && !sameBatchRecovery)
|
|
82
|
+
eligibleAt = Math.max(eligibleAt, Date.parse(previousGroup.lastAt) + 180000);
|
|
83
|
+
for (const [duration, limit] of followWindows) {
|
|
84
|
+
const recent = (original.state.attempts ?? [])
|
|
85
|
+
.filter((a) => a.kind === "follow" && Date.parse(a.at) > this.#clock() - duration)
|
|
86
|
+
.sort((a, b) => Date.parse(a.at) - Date.parse(b.at));
|
|
87
|
+
if (preparedCount > limit - recent.length)
|
|
88
|
+
eligibleAt = Math.max(eligibleAt, Date.parse(recent[recent.length - limit + preparedCount - 1].at) +
|
|
89
|
+
duration);
|
|
90
|
+
}
|
|
91
|
+
if (eligibleAt > this.#clock()) {
|
|
92
|
+
const resumeAt = new Date(eligibleAt).toISOString();
|
|
93
|
+
await this.#store.transaction((snapshot) => {
|
|
94
|
+
const next = structuredClone(snapshot.state);
|
|
95
|
+
Object.assign(next.followBatches.find((b) => b.id === batchId), { status: "WAITING", remaining: preparedCount, resumeAt });
|
|
96
|
+
next.updatedAt = this.#stamp();
|
|
137
97
|
return next;
|
|
138
98
|
});
|
|
139
|
-
return
|
|
140
|
-
|
|
141
|
-
|
|
99
|
+
return {
|
|
100
|
+
taskId,
|
|
101
|
+
batchId,
|
|
102
|
+
planned: handles.length,
|
|
103
|
+
attempted: 0,
|
|
104
|
+
verifiedThisCall: 0,
|
|
105
|
+
unknownInBatch: original.state.actions.filter((a) => handles.some((h) => same(h, a.handle)) &&
|
|
106
|
+
["unknown", "intent"].includes(a.status)).length,
|
|
107
|
+
remaining: preparedCount,
|
|
108
|
+
mayEnd: false,
|
|
109
|
+
decision: [
|
|
110
|
+
{
|
|
111
|
+
kind: "follow",
|
|
112
|
+
status: "waiting",
|
|
113
|
+
reason: "normal-batch-capacity-or-cooldown",
|
|
114
|
+
resumeAt,
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
evidenceRef: this.#store.path,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
let result;
|
|
121
|
+
const exceptional = [];
|
|
122
|
+
for (const handle of handles) {
|
|
123
|
+
// A resumed batch submits only its remaining prepared members. Recovery
|
|
124
|
+
// stays in the read-only API; a frozen member must not disturb that path.
|
|
125
|
+
if (!original.state.actions.some((a) => a.kind === "follow" &&
|
|
126
|
+
same(a.handle, handle) &&
|
|
127
|
+
a.status === "prepared"))
|
|
128
|
+
continue;
|
|
129
|
+
result = await (executor ? this.#parallelRound : this.#round)({
|
|
130
|
+
taskId,
|
|
131
|
+
requestedKind: "follow",
|
|
132
|
+
selector: (a) => a.kind === "follow" && same(a.handle, handle),
|
|
133
|
+
});
|
|
134
|
+
const relevant = (result.decision ?? []).filter((d) => d.kind === "follow" ||
|
|
135
|
+
["harness-boundary-error", "original-ledger-conflict"].includes(d.reason) ||
|
|
136
|
+
original.state.actions.some((a) => a.id === d.actionId && a.kind === "follow"));
|
|
137
|
+
exceptional.push(...relevant);
|
|
138
|
+
// A new unconfirmed submission is a diagnostic boundary, not permission
|
|
139
|
+
// to consume the rest of the batch on the same failing write path.
|
|
140
|
+
if (relevant.length)
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
const after = await this.#store.load();
|
|
144
|
+
const summary = summarizeFollowBatch(taskId, batchId, handles, after, original, result, exceptional, this.#store.path);
|
|
145
|
+
await this.#store.transaction((snapshot) => {
|
|
146
|
+
const next = structuredClone(snapshot.state), batch = next.followBatches.find((b) => b.id === batchId);
|
|
147
|
+
Object.assign(batch, {
|
|
148
|
+
attempted: (batch.attempted ?? 0) + summary.attempted,
|
|
149
|
+
verifiedTotal: (batch.verifiedTotal ?? 0) + summary.verifiedThisCall,
|
|
150
|
+
verifiedThisCall: summary.verifiedThisCall,
|
|
151
|
+
unknown: summary.unknownInBatch,
|
|
152
|
+
remaining: summary.remaining,
|
|
153
|
+
finishedAt: this.#stamp(),
|
|
154
|
+
status: summary.remaining || summary.unknownInBatch ? "PARTIAL" : "SETTLED",
|
|
155
|
+
});
|
|
156
|
+
next.updatedAt = this.#stamp();
|
|
157
|
+
return next;
|
|
158
|
+
});
|
|
159
|
+
return summary;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
export function createFollowBatch(...args) {
|
|
163
|
+
return new FollowBatch(...args);
|
|
142
164
|
}
|
|
143
165
|
function summarizeFollowBatch(taskId, batchId, handles, after, original, result, exceptional, evidenceRef) {
|
|
144
166
|
return {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -5,7 +5,13 @@ interface ExecutionStep {
|
|
|
5
5
|
need: BoundaryRecord | null;
|
|
6
6
|
}
|
|
7
7
|
import type { GrowthDependencies } from "./growth-dependencies.js";
|
|
8
|
-
export declare
|
|
8
|
+
export declare class GrowthExecution {
|
|
9
|
+
#private;
|
|
9
10
|
execute: (snapshot: any, action: any) => Promise<ExecutionStep>;
|
|
10
|
-
|
|
11
|
+
constructor(dependencies: Pick<GrowthDependencies, "capability" | "platform" | "settleProof" | "clock" | "save" | "guardInput" | "waitBudget" | "authorizedWindow" | "stamp" | "store" | "executor" | "session">);
|
|
12
|
+
private checkExecutionIdentity;
|
|
13
|
+
private admitAction;
|
|
14
|
+
private handleExecute;
|
|
15
|
+
}
|
|
16
|
+
export declare function createGrowthExecution(...args: ConstructorParameters<typeof GrowthExecution>): GrowthExecution;
|
|
11
17
|
export {};
|
package/dist/growth-execution.js
CHANGED
|
@@ -1,11 +1,39 @@
|
|
|
1
1
|
import { operations, same } from "./growth-constants.js";
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
export class GrowthExecution {
|
|
3
|
+
#capability;
|
|
4
|
+
#platform;
|
|
5
|
+
#settleProof;
|
|
6
|
+
#clock;
|
|
7
|
+
#save;
|
|
8
|
+
#guardInput;
|
|
9
|
+
#waitBudget;
|
|
10
|
+
#authorizedWindow;
|
|
11
|
+
#stamp;
|
|
12
|
+
#store;
|
|
13
|
+
#executor;
|
|
14
|
+
#session;
|
|
15
|
+
execute;
|
|
16
|
+
constructor(dependencies) {
|
|
17
|
+
const { capability, platform, settleProof, clock, save, guardInput, waitBudget, authorizedWindow, stamp, store, executor, session, } = dependencies;
|
|
18
|
+
this.#capability = capability;
|
|
19
|
+
this.#platform = platform;
|
|
20
|
+
this.#settleProof = settleProof;
|
|
21
|
+
this.#clock = clock;
|
|
22
|
+
this.#save = save;
|
|
23
|
+
this.#guardInput = guardInput;
|
|
24
|
+
this.#waitBudget = waitBudget;
|
|
25
|
+
this.#authorizedWindow = authorizedWindow;
|
|
26
|
+
this.#stamp = stamp;
|
|
27
|
+
this.#store = store;
|
|
28
|
+
this.#executor = executor;
|
|
29
|
+
this.#session = session;
|
|
30
|
+
this.execute = this.handleExecute.bind(this);
|
|
31
|
+
}
|
|
32
|
+
async checkExecutionIdentity(snapshot, action) {
|
|
5
33
|
const state = snapshot.state;
|
|
6
34
|
let own;
|
|
7
35
|
try {
|
|
8
|
-
own = await platform.readAccount();
|
|
36
|
+
own = await this.#platform.readAccount();
|
|
9
37
|
}
|
|
10
38
|
catch (error) {
|
|
11
39
|
return {
|
|
@@ -31,7 +59,7 @@ export function createGrowthExecution(dependencies) {
|
|
|
31
59
|
};
|
|
32
60
|
return null;
|
|
33
61
|
}
|
|
34
|
-
async
|
|
62
|
+
async admitAction(snapshot, action) {
|
|
35
63
|
const state = snapshot.state;
|
|
36
64
|
const item = state.items.find((i) => i.kind === action.kind);
|
|
37
65
|
if (!item.actionable)
|
|
@@ -43,7 +71,7 @@ export function createGrowthExecution(dependencies) {
|
|
|
43
71
|
reason: item.reason ?? "original-kind-not-actionable",
|
|
44
72
|
},
|
|
45
73
|
};
|
|
46
|
-
const inputError = guardInput(state, action);
|
|
74
|
+
const inputError = this.#guardInput(state, action);
|
|
47
75
|
if (inputError)
|
|
48
76
|
return {
|
|
49
77
|
snapshot,
|
|
@@ -53,16 +81,16 @@ export function createGrowthExecution(dependencies) {
|
|
|
53
81
|
reason: inputError,
|
|
54
82
|
},
|
|
55
83
|
};
|
|
56
|
-
const identity = await checkExecutionIdentity(snapshot, action);
|
|
84
|
+
const identity = await this.checkExecutionIdentity(snapshot, action);
|
|
57
85
|
if (identity)
|
|
58
86
|
return identity;
|
|
59
87
|
if (action.kind === "follow") {
|
|
60
|
-
const budget = waitBudget(state);
|
|
88
|
+
const budget = this.#waitBudget(state);
|
|
61
89
|
if (budget)
|
|
62
90
|
return { snapshot, need: { ...budget, actionId: action.id } };
|
|
63
91
|
let value;
|
|
64
92
|
try {
|
|
65
|
-
value = await platform.readPerson(action);
|
|
93
|
+
value = await this.#platform.readPerson(action);
|
|
66
94
|
}
|
|
67
95
|
catch (error) {
|
|
68
96
|
return {
|
|
@@ -100,7 +128,7 @@ export function createGrowthExecution(dependencies) {
|
|
|
100
128
|
const next = structuredClone(state);
|
|
101
129
|
next.actions.find((a) => a.id === action.id).status =
|
|
102
130
|
"preexisting";
|
|
103
|
-
return { snapshot: await save(snapshot, next), need: null };
|
|
131
|
+
return { snapshot: await this.#save(snapshot, next), need: null };
|
|
104
132
|
}
|
|
105
133
|
}
|
|
106
134
|
else {
|
|
@@ -108,9 +136,9 @@ export function createGrowthExecution(dependencies) {
|
|
|
108
136
|
timeZone: "Asia/Shanghai",
|
|
109
137
|
hour: "2-digit",
|
|
110
138
|
hourCycle: "h23",
|
|
111
|
-
}).format(clock()));
|
|
139
|
+
}).format(this.#clock()));
|
|
112
140
|
if ((hour < 7 || hour >= (action.kind === "reply" ? 24 : 22)) &&
|
|
113
|
-
!authorizedWindow(state, action.kind))
|
|
141
|
+
!this.#authorizedWindow(state, action.kind))
|
|
114
142
|
return {
|
|
115
143
|
snapshot,
|
|
116
144
|
need: {
|
|
@@ -124,9 +152,9 @@ export function createGrowthExecution(dependencies) {
|
|
|
124
152
|
if (action.kind !== "follow") {
|
|
125
153
|
let baseline;
|
|
126
154
|
try {
|
|
127
|
-
baseline = await platform.findSubmission({
|
|
155
|
+
baseline = await this.#platform.findSubmission({
|
|
128
156
|
...action,
|
|
129
|
-
intentAt: stamp(),
|
|
157
|
+
intentAt: this.#stamp(),
|
|
130
158
|
});
|
|
131
159
|
}
|
|
132
160
|
catch (error) {
|
|
@@ -153,9 +181,10 @@ export function createGrowthExecution(dependencies) {
|
|
|
153
181
|
}
|
|
154
182
|
return { beforeIds };
|
|
155
183
|
}
|
|
156
|
-
async
|
|
184
|
+
async handleExecute(snapshot, action) {
|
|
185
|
+
const executor = this.#executor;
|
|
157
186
|
const recovering = ["intent", "unknown"].includes(action.status);
|
|
158
|
-
const caps = capability(recovering ? "read" : operations[action.kind], action);
|
|
187
|
+
const caps = this.#capability(recovering ? "read" : operations[action.kind], action);
|
|
159
188
|
if (!caps.covered)
|
|
160
189
|
return {
|
|
161
190
|
snapshot,
|
|
@@ -165,7 +194,7 @@ export function createGrowthExecution(dependencies) {
|
|
|
165
194
|
if (recovering) {
|
|
166
195
|
let observation;
|
|
167
196
|
try {
|
|
168
|
-
observation = await platform.findSubmission(action);
|
|
197
|
+
observation = await this.#platform.findSubmission(action);
|
|
169
198
|
}
|
|
170
199
|
catch (error) {
|
|
171
200
|
observation = {
|
|
@@ -174,7 +203,7 @@ export function createGrowthExecution(dependencies) {
|
|
|
174
203
|
error: error.message.slice(0, 240),
|
|
175
204
|
};
|
|
176
205
|
}
|
|
177
|
-
snapshot = await settleProof(snapshot, action, observation);
|
|
206
|
+
snapshot = await this.#settleProof(snapshot, action, observation);
|
|
178
207
|
return {
|
|
179
208
|
snapshot,
|
|
180
209
|
need: observation.verified
|
|
@@ -190,7 +219,7 @@ export function createGrowthExecution(dependencies) {
|
|
|
190
219
|
if (snapshot.state.items
|
|
191
220
|
.find((i) => i.kind === action.kind)
|
|
192
221
|
.reason?.startsWith("harness:") &&
|
|
193
|
-
Date.parse(snapshot.state.items.find((i) => i.kind === action.kind).resumeAt) <= clock()) {
|
|
222
|
+
Date.parse(snapshot.state.items.find((i) => i.kind === action.kind).resumeAt) <= this.#clock()) {
|
|
194
223
|
const next = structuredClone(snapshot.state), pending = next.items.find((i) => i.kind === action.kind);
|
|
195
224
|
pending.actionable = true;
|
|
196
225
|
delete pending.reason;
|
|
@@ -198,9 +227,9 @@ export function createGrowthExecution(dependencies) {
|
|
|
198
227
|
delete pending.resumeWhen;
|
|
199
228
|
delete pending.blockEvidence;
|
|
200
229
|
next.status = "RUNNING";
|
|
201
|
-
snapshot = await save(snapshot, next);
|
|
230
|
+
snapshot = await this.#save(snapshot, next);
|
|
202
231
|
}
|
|
203
|
-
const admission = await admitAction(snapshot, action);
|
|
232
|
+
const admission = await this.admitAction(snapshot, action);
|
|
204
233
|
if ("snapshot" in admission)
|
|
205
234
|
return admission;
|
|
206
235
|
const { beforeIds } = admission;
|
|
@@ -209,7 +238,7 @@ export function createGrowthExecution(dependencies) {
|
|
|
209
238
|
const next = structuredClone(state);
|
|
210
239
|
const entry = next.actions.find((a) => a.id === action.id);
|
|
211
240
|
entry.status = "intent";
|
|
212
|
-
entry.intentAt = stamp();
|
|
241
|
+
entry.intentAt = this.#stamp();
|
|
213
242
|
entry.beforeIds = beforeIds;
|
|
214
243
|
next.items.find((i) => i.kind === action.kind).unknown += 1;
|
|
215
244
|
next.attempts ??= [];
|
|
@@ -222,57 +251,57 @@ export function createGrowthExecution(dependencies) {
|
|
|
222
251
|
next.execution ??= {};
|
|
223
252
|
const old = next.execution.followGroup;
|
|
224
253
|
next.execution.followGroup = {
|
|
225
|
-
attempts: old?.attempts < 10 && clock() < Date.parse(old.lastAt) + 180000
|
|
254
|
+
attempts: old?.attempts < 10 && this.#clock() < Date.parse(old.lastAt) + 180000
|
|
226
255
|
? old.attempts + 1
|
|
227
256
|
: 1,
|
|
228
257
|
lastAt: entry.intentAt,
|
|
229
258
|
};
|
|
230
259
|
}
|
|
231
|
-
snapshot = await save(snapshot, next);
|
|
260
|
+
snapshot = await this.#save(snapshot, next);
|
|
232
261
|
let observation, submissionMayHaveStarted = false;
|
|
233
262
|
try {
|
|
234
|
-
await platform[operations[action.kind]]({
|
|
263
|
+
await this.#platform[operations[action.kind]]({
|
|
235
264
|
...entry,
|
|
236
265
|
beforeSubmit: async () => {
|
|
237
|
-
const latest = await store.load();
|
|
266
|
+
const latest = await this.#store.load();
|
|
238
267
|
if (executor) {
|
|
239
268
|
const current = latest.state.actions.find((a) => a.id === entry.id);
|
|
240
269
|
if (latest.state.batchId !== snapshot.state.batchId ||
|
|
241
|
-
current?.claim?.token !== session.claim.token ||
|
|
270
|
+
current?.claim?.token !== this.#session.claim.token ||
|
|
242
271
|
JSON.stringify(current) !==
|
|
243
272
|
JSON.stringify(snapshot.state.actions.find((a) => a.id === entry.id)))
|
|
244
273
|
throw new Error("提交前本动作或claim变化");
|
|
245
274
|
}
|
|
246
275
|
else if (latest.revision !== snapshot.revision)
|
|
247
276
|
throw new Error("提交前原台账变化,保留intent并重核执行权");
|
|
248
|
-
const inputError = guardInput(latest.state, latest.state.actions.find((a) => a.id === entry.id));
|
|
277
|
+
const inputError = this.#guardInput(latest.state, latest.state.actions.find((a) => a.id === entry.id));
|
|
249
278
|
if (inputError)
|
|
250
279
|
throw new Error(inputError);
|
|
251
280
|
const hour = Number(new Intl.DateTimeFormat("en-GB", {
|
|
252
281
|
timeZone: "Asia/Shanghai",
|
|
253
282
|
hour: "2-digit",
|
|
254
283
|
hourCycle: "h23",
|
|
255
|
-
}).format(clock()));
|
|
284
|
+
}).format(this.#clock()));
|
|
256
285
|
if ((hour < 7 || hour >= (action.kind === "reply" ? 24 : 22)) &&
|
|
257
|
-
!authorizedWindow(latest.state, action.kind))
|
|
286
|
+
!this.#authorizedWindow(latest.state, action.kind))
|
|
258
287
|
throw new Error("实际提交时已越过运营窗口");
|
|
259
288
|
if (action.kind === "follow") {
|
|
260
289
|
const check = structuredClone(latest.state);
|
|
261
290
|
check.attempts = check.attempts.filter((a) => a.actionId !== entry.id);
|
|
262
291
|
if (check.execution.followGroup)
|
|
263
292
|
check.execution.followGroup.attempts -= 1;
|
|
264
|
-
if (waitBudget(check))
|
|
293
|
+
if (this.#waitBudget(check))
|
|
265
294
|
throw new Error("实际提交前关注预算不再可用");
|
|
266
295
|
}
|
|
267
296
|
submissionMayHaveStarted = true;
|
|
268
297
|
},
|
|
269
298
|
});
|
|
270
|
-
observation = await platform.findSubmission(entry);
|
|
299
|
+
observation = await this.#platform.findSubmission(entry);
|
|
271
300
|
}
|
|
272
301
|
catch (error) {
|
|
273
302
|
if (!submissionMayHaveStarted) {
|
|
274
|
-
const proof = await store.recordProof({
|
|
275
|
-
capturedAt: stamp(),
|
|
303
|
+
const proof = await this.#store.recordProof({
|
|
304
|
+
capturedAt: this.#stamp(),
|
|
276
305
|
submissionStarted: false,
|
|
277
306
|
error: error.message.slice(0, 240),
|
|
278
307
|
});
|
|
@@ -280,7 +309,7 @@ export function createGrowthExecution(dependencies) {
|
|
|
280
309
|
target.status = "prepared";
|
|
281
310
|
target.preparationEvidence = proof;
|
|
282
311
|
unsubmitted.items.find((i) => i.kind === action.kind).unknown -= 1;
|
|
283
|
-
snapshot = await save(snapshot, unsubmitted);
|
|
312
|
+
snapshot = await this.#save(snapshot, unsubmitted);
|
|
284
313
|
return {
|
|
285
314
|
snapshot,
|
|
286
315
|
need: {
|
|
@@ -298,7 +327,7 @@ export function createGrowthExecution(dependencies) {
|
|
|
298
327
|
error: error.message.slice(0, 240),
|
|
299
328
|
};
|
|
300
329
|
}
|
|
301
|
-
snapshot = await settleProof(snapshot, entry, observation);
|
|
330
|
+
snapshot = await this.#settleProof(snapshot, entry, observation);
|
|
302
331
|
return {
|
|
303
332
|
snapshot,
|
|
304
333
|
need: observation.verified
|
|
@@ -311,5 +340,7 @@ export function createGrowthExecution(dependencies) {
|
|
|
311
340
|
},
|
|
312
341
|
};
|
|
313
342
|
}
|
|
314
|
-
|
|
343
|
+
}
|
|
344
|
+
export function createGrowthExecution(...args) {
|
|
345
|
+
return new GrowthExecution(...args);
|
|
315
346
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { GrowthDependencies } from "./growth-dependencies.js";
|
|
2
|
-
export declare
|
|
2
|
+
export declare class ParallelRound {
|
|
3
|
+
#private;
|
|
3
4
|
parallelRound: ({ taskId, decision, selector, requestedKind, maxActions, }?: any) => Promise<any>;
|
|
4
|
-
|
|
5
|
+
constructor(dependencies: Pick<GrowthDependencies, "load" | "scope" | "result" | "store" | "executor" | "runtimeId" | "stamp" | "evidenceExists" | "execute" | "clock" | "releaseSession" | "session">);
|
|
6
|
+
private handleParallelRound;
|
|
7
|
+
}
|
|
8
|
+
export declare function createParallelRound(...args: ConstructorParameters<typeof ParallelRound>): ParallelRound;
|