@nextclaw/x-harness 0.0.0-stage → 0.1.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.
Files changed (110) hide show
  1. package/LICENSE +10 -0
  2. package/README.md +41 -2
  3. package/cli/main.js +219 -0
  4. package/dist/api.d.ts +10 -0
  5. package/dist/api.js +308 -0
  6. package/dist/boundary.d.ts +2 -0
  7. package/dist/browser-common.d.ts +4 -0
  8. package/dist/browser-common.js +29 -0
  9. package/dist/browser-dom-capabilities.d.ts +4 -0
  10. package/dist/browser-dom-capabilities.js +110 -0
  11. package/dist/browser-dom-dependencies.d.ts +16 -0
  12. package/dist/browser-dom-editing.d.ts +148 -0
  13. package/dist/browser-dom-editing.js +386 -0
  14. package/dist/browser-dom-navigation.d.ts +9 -0
  15. package/dist/browser-dom-navigation.js +165 -0
  16. package/dist/browser-dom-network.d.ts +1 -0
  17. package/dist/browser-dom-network.js +128 -0
  18. package/dist/browser-dom-submission.d.ts +2 -0
  19. package/dist/browser-dom-submission.js +82 -0
  20. package/dist/browser-dom.d.ts +2 -0
  21. package/dist/browser-dom.js +103 -0
  22. package/dist/browser-native.d.ts +1 -0
  23. package/dist/browser-native.js +215 -0
  24. package/dist/browser.d.ts +4 -0
  25. package/dist/browser.js +147 -0
  26. package/dist/creator.d.ts +29 -0
  27. package/dist/creator.js +164 -0
  28. package/dist/growth-batch.d.ts +5 -0
  29. package/dist/growth-batch.js +168 -0
  30. package/dist/growth-constants.d.ts +5 -0
  31. package/dist/growth-constants.js +13 -0
  32. package/dist/growth-dependencies.d.ts +33 -0
  33. package/dist/growth-execution.d.ts +11 -0
  34. package/dist/growth-execution.js +315 -0
  35. package/dist/growth-parallel.d.ts +4 -0
  36. package/dist/growth-parallel.js +301 -0
  37. package/dist/growth-plan.d.ts +4 -0
  38. package/dist/growth-plan.js +207 -0
  39. package/dist/growth-policy.d.ts +9 -0
  40. package/dist/growth-policy.js +181 -0
  41. package/dist/growth-publishing.d.ts +5 -0
  42. package/dist/growth-publishing.js +240 -0
  43. package/dist/growth-reading.d.ts +4 -0
  44. package/dist/growth-reading.js +135 -0
  45. package/dist/growth-recovery.d.ts +6 -0
  46. package/dist/growth-recovery.js +158 -0
  47. package/dist/growth-state.d.ts +8 -0
  48. package/dist/growth-state.js +183 -0
  49. package/dist/growth-tasks.d.ts +17 -0
  50. package/dist/growth-tasks.js +101 -0
  51. package/dist/growth.d.ts +2 -0
  52. package/dist/growth.js +138 -0
  53. package/dist/index.d.ts +4 -0
  54. package/dist/index.js +22 -0
  55. package/dist/observation.d.ts +1 -0
  56. package/dist/observation.js +2 -0
  57. package/dist/platform-capabilities.d.ts +4 -0
  58. package/dist/platform-capabilities.js +19 -0
  59. package/dist/platform-collections.d.ts +7 -0
  60. package/dist/platform-collections.js +223 -0
  61. package/dist/platform-dependencies.d.ts +43 -0
  62. package/dist/platform-diagnostics.d.ts +9 -0
  63. package/dist/platform-diagnostics.js +51 -0
  64. package/dist/platform-observation.d.ts +8 -0
  65. package/dist/platform-observation.js +75 -0
  66. package/dist/platform-rules.d.ts +17 -0
  67. package/dist/platform-rules.js +41 -0
  68. package/dist/platform-targets.d.ts +7 -0
  69. package/dist/platform-targets.js +129 -0
  70. package/dist/platform-writing.d.ts +8 -0
  71. package/dist/platform-writing.js +171 -0
  72. package/dist/platform.d.ts +2 -0
  73. package/dist/platform.js +60 -0
  74. package/dist/readers-actions.d.ts +2 -0
  75. package/dist/readers-actions.js +66 -0
  76. package/dist/readers-ax-host.d.ts +2 -0
  77. package/dist/readers-ax-host.js +125 -0
  78. package/dist/readers-ax-parser.d.ts +1 -0
  79. package/dist/readers-ax-parser.js +69 -0
  80. package/dist/readers-ax-program.d.ts +1 -0
  81. package/dist/readers-ax-tree.d.ts +19 -0
  82. package/dist/readers-ax-tree.js +110 -0
  83. package/dist/readers-ax-views.d.ts +87 -0
  84. package/dist/readers-ax-views.js +247 -0
  85. package/dist/readers-cli.d.ts +6 -0
  86. package/dist/readers-composer.d.ts +2 -0
  87. package/dist/readers-composer.js +131 -0
  88. package/dist/readers-navigation.d.ts +2 -0
  89. package/dist/readers-navigation.js +66 -0
  90. package/dist/readers-posts.d.ts +2 -0
  91. package/dist/readers-posts.js +141 -0
  92. package/dist/readers-profile.d.ts +3 -0
  93. package/dist/readers-profile.js +157 -0
  94. package/dist/readers.d.ts +9 -0
  95. package/dist/results.d.ts +6 -0
  96. package/dist/results.js +233 -0
  97. package/dist/round-state.d.ts +3 -0
  98. package/dist/round-state.js +155 -0
  99. package/dist/store.d.ts +30 -0
  100. package/dist/store.js +175 -0
  101. package/dist/types.d.ts +407 -0
  102. package/generated/observation.d.mts +1 -0
  103. package/generated/observation.mjs +515 -0
  104. package/package.json +29 -3
  105. package/skill/SKILL.md +33 -0
  106. package/skill/references/api.md +10 -0
  107. package/skill/references/reads.md +23 -0
  108. package/skill/references/recovery.md +25 -0
  109. package/skill/references/setup.md +23 -0
  110. package/skill/references/writes.md +51 -0
@@ -0,0 +1,301 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { same } from "./growth-constants.js";
3
+ export function createParallelRound(dependencies) {
4
+ const { load, scope, result, store, executor, runtimeId, stamp, evidenceExists, execute, clock, releaseSession, session, } = dependencies;
5
+ async function parallelRound({ taskId, decision, selector, requestedKind, maxActions = 10, } = {}) {
6
+ if (!executor)
7
+ throw new Error("parallel-executor-required");
8
+ if (session.running)
9
+ return {
10
+ status: "needsDecision",
11
+ mayEnd: false,
12
+ taskId,
13
+ decision: [{ reason: "same-tab-operation-running" }],
14
+ };
15
+ session.running = true;
16
+ let completed = 0;
17
+ const needs = [];
18
+ try {
19
+ if (!Number.isInteger(maxActions) || maxActions < 1 || maxActions > 100)
20
+ throw new Error("maxActions须为1–100");
21
+ let snapshot = await load(taskId);
22
+ const activeKinds = scope(snapshot.state);
23
+ if (requestedKind && !activeKinds.includes(requestedKind))
24
+ return {
25
+ ...result(snapshot),
26
+ status: "needsDecision",
27
+ decision: [{ reason: "kind-outside-round-scope" }],
28
+ };
29
+ snapshot = await store.transaction((latest) => {
30
+ if (latest.state.batchId !== taskId)
31
+ throw new Error("original-task-changed");
32
+ const next = structuredClone(latest.state);
33
+ next.executions ??= {};
34
+ if (Object.values(next.executions).some((e) => (e.tabId === executor.tabId || e.id === executor.id) &&
35
+ e.runtimeId !== runtimeId &&
36
+ (e.activity || e.actionId) &&
37
+ store.ownerAlive(e.pid)))
38
+ throw new Error("same-tab-or-executor-claimed");
39
+ next.executions[executor.id] = {
40
+ ...next.executions[executor.id],
41
+ ...executor,
42
+ runtimeId,
43
+ pid: store.processId,
44
+ activity: "round",
45
+ status: "RUNNING",
46
+ updatedAt: stamp(),
47
+ };
48
+ if (decision?.type === "prepareActions") {
49
+ if (!Array.isArray(decision.actions) ||
50
+ !same(next.accountHandle, decision.accountHandle) ||
51
+ !evidenceExists(decision.ownershipEvidenceRef))
52
+ throw new Error("需原账号核验和完整动作队列");
53
+ next.actions ??= [];
54
+ for (const action of decision.actions) {
55
+ if (!action.id ||
56
+ !activeKinds.includes(action.kind) ||
57
+ action.status !== "prepared" ||
58
+ next.actions.some((a) => a.id === action.id))
59
+ throw new Error("原prepared动作ID无效");
60
+ next.actions.push(structuredClone(action));
61
+ }
62
+ if (next.items.some((i) => next.actions.filter((a) => a.kind === i.kind && a.status === "prepared").length >
63
+ i.planned - i.verified - i.unknown))
64
+ throw new Error("队列超过原目标");
65
+ if (decision.sharedAttempts) {
66
+ if (!Array.isArray(decision.sharedAttempts))
67
+ throw new Error("共享尝试须为数组");
68
+ next.attempts = [
69
+ ...(next.attempts ?? []),
70
+ ...structuredClone(decision.sharedAttempts),
71
+ ];
72
+ }
73
+ if (decision.attemptsCoverage) {
74
+ if (!decision.attemptsCoverage.complete ||
75
+ !evidenceExists(decision.attemptsCoverage.evidenceRef))
76
+ throw new Error("共享历史缺证据");
77
+ next.attemptsCoverage = structuredClone(decision.attemptsCoverage);
78
+ }
79
+ if (decision.nextScheduledCheckAt)
80
+ next.executions[executor.id].nextScheduledCheckAt =
81
+ decision.nextScheduledCheckAt;
82
+ }
83
+ else if (decision) {
84
+ const action = next.actions?.find((a) => a.id === decision.actionId);
85
+ if (!action ||
86
+ !activeKinds.includes(action.kind) ||
87
+ action.status !== "prepared" ||
88
+ action.claim ||
89
+ typeof decision.approved !== "boolean" ||
90
+ !evidenceExists(decision.evidenceRef))
91
+ throw new Error("审查只绑定职责内未领取的原prepared动作");
92
+ if (!decision.approved)
93
+ action.status = "skipped";
94
+ else
95
+ action.quality = {
96
+ approved: true,
97
+ evidenceRef: decision.evidenceRef,
98
+ ...(action.kind === "follow"
99
+ ? {
100
+ handle: action.handle,
101
+ blue: decision.blue === true,
102
+ chinese: decision.chinese === true,
103
+ }
104
+ : {
105
+ approvedText: decision.approvedText,
106
+ ...(action.kind === "original"
107
+ ? { draftFingerprint: decision.draftFingerprint }
108
+ : {}),
109
+ }),
110
+ };
111
+ }
112
+ next.updatedAt = stamp();
113
+ return next;
114
+ });
115
+ for (const kind of activeKinds) {
116
+ const queue = (snapshot.state.actions ?? [])
117
+ .filter((a) => a.kind === kind && (!selector || selector(a)))
118
+ .sort((a, b) => Number(["intent", "unknown"].includes(b.status)) -
119
+ Number(["intent", "unknown"].includes(a.status)));
120
+ if (snapshot.state.items.find((i) => i.kind === kind).unknown >
121
+ snapshot.state.actions.filter((a) => a.kind === kind && ["intent", "unknown"].includes(a.status)).length) {
122
+ needs.push({
123
+ status: "needsDecision",
124
+ reason: "original-unmapped-unknown-needs-dedup",
125
+ kind,
126
+ });
127
+ continue;
128
+ }
129
+ for (const original of queue) {
130
+ if (completed >= maxActions)
131
+ break;
132
+ if (["verified", "preexisting", "skipped"].includes(original.status))
133
+ continue;
134
+ try {
135
+ snapshot = await store.transaction((latest) => {
136
+ const next = structuredClone(latest.state), action = next.actions.find((a) => a.id === original.id);
137
+ if (next.batchId !== taskId)
138
+ throw new Error("original-task-changed");
139
+ if (["verified", "preexisting", "skipped"].includes(action.status))
140
+ return null;
141
+ const sameTarget = (other) => other.kind === action.kind &&
142
+ (kind === "follow"
143
+ ? same(other.handle, action.handle)
144
+ : kind === "original"
145
+ ? other.draftPath === action.draftPath
146
+ : other.postId === action.postId &&
147
+ other.text === action.text);
148
+ if (next.actions.some((a) => a.id !== action.id &&
149
+ sameTarget(a) &&
150
+ (a.claim ||
151
+ ["intent", "unknown", "verified"].includes(a.status))))
152
+ throw new Error("same-target-already-claimed-or-submitted");
153
+ if (action.claim && store.ownerAlive(action.claim.pid))
154
+ throw new Error("same-action-claimed");
155
+ if (Object.values(next.executions).some((e) => e.tabId === executor.tabId &&
156
+ e.actionId &&
157
+ e.runtimeId !== runtimeId &&
158
+ store.ownerAlive(e.pid)))
159
+ throw new Error("same-tab-claimed");
160
+ session.claim = {
161
+ actionId: action.id,
162
+ token: randomUUID(),
163
+ executorId: executor.id,
164
+ runtimeId,
165
+ pid: store.processId,
166
+ acquiredAt: stamp(),
167
+ };
168
+ action.claim = session.claim;
169
+ next.executions[executor.id].actionId = action.id;
170
+ next.updatedAt = stamp();
171
+ return next;
172
+ });
173
+ if (!session.claim)
174
+ continue;
175
+ const outcome = await execute(snapshot, snapshot.state.actions.find((a) => a.id === original.id));
176
+ snapshot = outcome.snapshot;
177
+ completed++;
178
+ if (outcome.need) {
179
+ needs.push(outcome.need);
180
+ break;
181
+ }
182
+ }
183
+ catch (error) {
184
+ needs.push({
185
+ status: "needsDecision",
186
+ kind,
187
+ actionId: original.id,
188
+ reason: /claimed|same-target/.test(error.message)
189
+ ? "action-or-tab-claimed"
190
+ : /budget|capacity/.test(error.message)
191
+ ? "shared-reservation-unavailable"
192
+ : "harness-boundary-error",
193
+ error: error.message.slice(0, 240),
194
+ });
195
+ break;
196
+ }
197
+ finally {
198
+ if (session.claim) {
199
+ const owned = session.claim;
200
+ session.claim = null;
201
+ snapshot = await store.transaction((latest) => {
202
+ const next = structuredClone(latest.state), action = next.actions?.find((a) => a.id === owned.actionId);
203
+ if (action?.claim?.token !== owned.token)
204
+ return null;
205
+ delete action.claim;
206
+ if (next.executions[executor.id]?.runtimeId === runtimeId)
207
+ delete next.executions[executor.id].actionId;
208
+ next.updatedAt = stamp();
209
+ return next;
210
+ });
211
+ }
212
+ }
213
+ }
214
+ }
215
+ snapshot = await store.transaction(async (latest) => {
216
+ const next = structuredClone(latest.state), own = next.executions[executor.id];
217
+ const pending = next.items.filter((i) => activeKinds.includes(i.kind) &&
218
+ (i.verified < i.planned || i.unknown));
219
+ own.status = pending.length ? "RUNNING" : "COMPLETE";
220
+ const scheduled = own.nextScheduledCheckAt ??
221
+ next.execution?.nextScheduledCheckAt ??
222
+ next.stop?.nextScheduledCheckAt;
223
+ const waits = pending.map((i) => needs.find((n) => n.status === "waiting" &&
224
+ next.actions.find((a) => a.id === n.actionId)?.kind ===
225
+ i.kind));
226
+ if (pending.length &&
227
+ pending.every((i) => !i.unknown &&
228
+ next.actions.filter((a) => a.kind === i.kind && a.status === "prepared").length >=
229
+ i.planned - i.verified) &&
230
+ waits.every((w) => w && Date.parse(w.resumeAt) > clock()) &&
231
+ Date.parse(scheduled) > clock()) {
232
+ for (let i = 0; i < pending.length; i++) {
233
+ const proof = await store.recordProof({
234
+ observedAt: stamp(),
235
+ ...waits[i],
236
+ attempts: next.attempts,
237
+ });
238
+ Object.assign(pending[i], {
239
+ actionable: false,
240
+ reason: `harness:${waits[i].reason}`,
241
+ resumeAt: waits[i].resumeAt,
242
+ resumeWhen: `不早于${waits[i].resumeAt}重核`,
243
+ blockEvidence: [proof],
244
+ });
245
+ }
246
+ own.status = "WAITING";
247
+ own.stop = {
248
+ independentActionsHandled: true,
249
+ nextScheduledCheckAt: scheduled,
250
+ nextEligibleCheckAt: waits.map((w) => w.resumeAt).sort()[0],
251
+ };
252
+ }
253
+ next.status = next.items.every((i) => i.verified === i.planned && !i.unknown)
254
+ ? "COMPLETE"
255
+ : "RUNNING";
256
+ own.updatedAt = next.updatedAt = stamp();
257
+ return next;
258
+ });
259
+ const out = result(snapshot, needs);
260
+ if (!out.mayEnd && !needs.length && completed >= maxActions)
261
+ return {
262
+ ...out,
263
+ status: "checkpoint",
264
+ checkpoint: {
265
+ taskId,
266
+ executorId: executor.id,
267
+ completed,
268
+ resume: "resumeGrowthRound",
269
+ },
270
+ };
271
+ if (!out.mayEnd && !needs.length)
272
+ out.decision = [
273
+ { reason: "prepared-queue-does-not-cover-original-targets" },
274
+ ];
275
+ return out;
276
+ }
277
+ catch (error) {
278
+ return {
279
+ status: "needsDecision",
280
+ mayEnd: false,
281
+ taskId,
282
+ evidenceRef: store.path,
283
+ decision: [
284
+ {
285
+ reason: "harness-boundary-error",
286
+ error: error.message.slice(0, 240),
287
+ },
288
+ ],
289
+ };
290
+ }
291
+ finally {
292
+ try {
293
+ await releaseSession(taskId);
294
+ }
295
+ finally {
296
+ session.running = false;
297
+ }
298
+ }
299
+ }
300
+ return { parallelRound };
301
+ }
@@ -0,0 +1,4 @@
1
+ import type { GrowthDependencies } from "./growth-dependencies.js";
2
+ export declare function createPlannedRound(dependencies: Pick<GrowthDependencies, "store" | "load" | "result" | "evidenceExists" | "save" | "execute" | "clock" | "stamp">): {
3
+ round: ({ taskId, decision, selector, requestedKind, }?: any) => Promise<any>;
4
+ };
@@ -0,0 +1,207 @@
1
+ import { kinds, same } from "./growth-constants.js";
2
+ export function createPlannedRound(dependencies) {
3
+ const { store, load, result, evidenceExists, save, execute, clock, stamp } = dependencies;
4
+ async function applyRoundDecision(snapshot, decision, activeKinds) {
5
+ if (decision?.type === "prepareActions") {
6
+ if (!Array.isArray(decision.actions) ||
7
+ !/^[A-Za-z0-9_]{1,15}$/.test(decision.accountHandle ?? "") ||
8
+ !evidenceExists(decision.ownershipEvidenceRef) ||
9
+ (snapshot.state.execution?.owner &&
10
+ snapshot.state.execution.owner !== "harness"))
11
+ throw new Error("需原账号执行权核验和完整动作队列;不接管已有执行者");
12
+ if (snapshot.state.accountHandle &&
13
+ !same(snapshot.state.accountHandle, decision.accountHandle))
14
+ throw new Error("不能变更原账号");
15
+ const next = structuredClone(snapshot.state);
16
+ next.actions ??= [];
17
+ for (const action of decision.actions) {
18
+ if (!action.id ||
19
+ !activeKinds.includes(action.kind) ||
20
+ action.status !== "prepared" ||
21
+ next.actions.some((a) => a.id === action.id))
22
+ throw new Error("仅添加职责范围内唯一ID的prepared原动作");
23
+ next.actions.push(structuredClone(action));
24
+ }
25
+ if (next.items.some((item) => next.actions.filter((a) => a.kind === item.kind && a.status === "prepared").length >
26
+ item.planned - item.verified - item.unknown))
27
+ throw new Error("prepared队列不能越过原批次未决目标");
28
+ next.accountHandle = decision.accountHandle;
29
+ next.execution = {
30
+ ...next.execution,
31
+ owner: "harness",
32
+ exclusive: true,
33
+ ownershipEvidenceRef: decision.ownershipEvidenceRef,
34
+ ...(decision.nextScheduledCheckAt
35
+ ? { nextScheduledCheckAt: decision.nextScheduledCheckAt }
36
+ : {}),
37
+ };
38
+ if (decision.attemptsCoverage) {
39
+ if (!decision.attemptsCoverage.complete ||
40
+ !evidenceExists(decision.attemptsCoverage.evidenceRef))
41
+ throw new Error("共享历史须完整取证;不能默认为零");
42
+ next.attemptsCoverage = structuredClone(decision.attemptsCoverage);
43
+ }
44
+ if (decision.sharedAttempts) {
45
+ if (!Array.isArray(decision.sharedAttempts))
46
+ throw new Error("共享尝试记录必须为数组");
47
+ next.attempts = [
48
+ ...(next.attempts ?? []),
49
+ ...structuredClone(decision.sharedAttempts),
50
+ ];
51
+ }
52
+ snapshot = await save(snapshot, next);
53
+ }
54
+ else if (decision) {
55
+ const action = snapshot.state.actions?.find((a) => a.id === decision.actionId);
56
+ if (action && !activeKinds.includes(action.kind))
57
+ throw new Error("kind-outside-round-scope");
58
+ if (!action ||
59
+ action.status !== "prepared" ||
60
+ typeof decision.approved !== "boolean" ||
61
+ !evidenceExists(decision.evidenceRef))
62
+ throw new Error("决策仅能绑定原prepared动作及真实审查证据");
63
+ const next = structuredClone(snapshot.state), target = next.actions.find((a) => a.id === action.id);
64
+ if (!decision.approved)
65
+ target.status = "skipped";
66
+ else
67
+ target.quality = {
68
+ approved: true,
69
+ evidenceRef: decision.evidenceRef,
70
+ ...(action.kind === "follow"
71
+ ? {
72
+ handle: action.handle,
73
+ blue: decision.blue === true,
74
+ chinese: decision.chinese === true,
75
+ }
76
+ : {
77
+ approvedText: decision.approvedText,
78
+ ...(action.kind === "original"
79
+ ? { draftFingerprint: decision.draftFingerprint }
80
+ : {}),
81
+ }),
82
+ };
83
+ snapshot = await save(snapshot, next);
84
+ }
85
+ return snapshot;
86
+ }
87
+ async function round({ taskId, decision, selector, requestedKind, } = {}) {
88
+ try {
89
+ return await store.withLock(async () => {
90
+ let snapshot = await load(taskId);
91
+ const activeKinds = snapshot.state.scopeKinds ?? kinds;
92
+ if (requestedKind && !activeKinds.includes(requestedKind))
93
+ return {
94
+ ...result(snapshot),
95
+ status: "needsDecision",
96
+ mayEnd: false,
97
+ decision: [
98
+ { kind: requestedKind, reason: "kind-outside-round-scope" },
99
+ ],
100
+ };
101
+ snapshot = await applyRoundDecision(snapshot, decision, activeKinds);
102
+ const needs = [];
103
+ for (const kind of activeKinds) {
104
+ const queue = (snapshot.state.actions ?? [])
105
+ .filter((a) => a.kind === kind && (!selector || selector(a)))
106
+ .sort((a, b) => Number(["intent", "unknown"].includes(b.status)) -
107
+ Number(["intent", "unknown"].includes(a.status)));
108
+ // A selector narrows this invocation, not the original unresolved ledger.
109
+ const unresolved = (snapshot.state.actions ?? []).filter((a) => a.kind === kind && ["intent", "unknown"].includes(a.status)).length;
110
+ if (snapshot.state.items.find((i) => i.kind === kind).unknown >
111
+ unresolved) {
112
+ needs.push({
113
+ status: "needsDecision",
114
+ reason: "original-unmapped-unknown-needs-dedup",
115
+ kind,
116
+ });
117
+ continue;
118
+ }
119
+ for (const original of queue) {
120
+ const item = snapshot.state.items.find((i) => i.kind === kind);
121
+ const action = snapshot.state.actions.find((a) => a.id === original.id);
122
+ if (["verified", "preexisting", "skipped"].includes(action.status))
123
+ continue;
124
+ if (item.verified >= item.planned && action.status === "prepared")
125
+ continue;
126
+ const outcome = await execute(snapshot, action);
127
+ snapshot = outcome.snapshot;
128
+ if (outcome.need) {
129
+ needs.push(outcome.need);
130
+ break;
131
+ } // Stop only this kind; continue independent kinds.
132
+ }
133
+ }
134
+ if (snapshot.state.items
135
+ .filter((i) => activeKinds.includes(i.kind))
136
+ .every((i) => i.verified === i.planned && i.unknown === 0)) {
137
+ const next = structuredClone(snapshot.state);
138
+ next.status = "COMPLETE";
139
+ snapshot = await save(snapshot, next);
140
+ }
141
+ else {
142
+ const pending = snapshot.state.items.filter((i) => activeKinds.includes(i.kind) &&
143
+ (i.verified < i.planned || i.unknown > 0));
144
+ const waits = pending.map((item) => needs.find((n) => n.status === "waiting" &&
145
+ snapshot.state.actions.find((a) => a.id === n.actionId)
146
+ ?.kind === item.kind));
147
+ const scheduled = snapshot.state.execution?.nextScheduledCheckAt ??
148
+ snapshot.state.stop?.nextScheduledCheckAt;
149
+ const covered = pending.every((item) => item.unknown === 0 &&
150
+ (snapshot.state.actions ?? []).filter((a) => a.kind === item.kind && a.status === "prepared").length >=
151
+ item.planned - item.verified);
152
+ if (covered &&
153
+ waits.every((w) => w && Date.parse(w.resumeAt) > clock()) &&
154
+ Date.parse(scheduled) > clock()) {
155
+ const next = structuredClone(snapshot.state);
156
+ for (let i = 0; i < pending.length; i++) {
157
+ const item = next.items.find((a) => a.kind === pending[i].kind), wait = waits[i];
158
+ const proof = await store.recordProof({
159
+ observedAt: stamp(),
160
+ reason: wait.reason,
161
+ resumeAt: wait.resumeAt,
162
+ attempts: next.attempts,
163
+ });
164
+ Object.assign(item, {
165
+ actionable: false,
166
+ reason: `harness:${wait.reason}`,
167
+ resumeAt: wait.resumeAt,
168
+ resumeWhen: `不早于${wait.resumeAt}重核预算及fresh状态`,
169
+ blockEvidence: [proof],
170
+ });
171
+ }
172
+ next.status = "WAITING";
173
+ next.stop = {
174
+ independentActionsHandled: true,
175
+ nextScheduledCheckAt: scheduled,
176
+ nextEligibleCheckAt: waits.map((w) => w.resumeAt).sort()[0],
177
+ };
178
+ snapshot = await save(snapshot, next);
179
+ }
180
+ else if (!needs.length)
181
+ needs.push({
182
+ status: "needsDecision",
183
+ reason: "prepared-queue-does-not-cover-original-targets",
184
+ });
185
+ }
186
+ return result(snapshot, needs);
187
+ });
188
+ }
189
+ catch (error) {
190
+ return {
191
+ status: "needsDecision",
192
+ mayEnd: false,
193
+ taskId,
194
+ evidenceRef: store.path,
195
+ decision: [
196
+ {
197
+ reason: error.code === "CONFLICT"
198
+ ? "original-ledger-conflict"
199
+ : "harness-boundary-error",
200
+ error: error.message.slice(0, 240),
201
+ },
202
+ ],
203
+ };
204
+ }
205
+ }
206
+ return { round };
207
+ }
@@ -0,0 +1,9 @@
1
+ import type { GrowthDependencies } from "./growth-dependencies.js";
2
+ export declare function createGrowthPolicy(dependencies: Pick<GrowthDependencies, "executor" | "clock" | "evidenceExists" | "platform" | "store">): {
3
+ authorizedWindow: (state: any, kind: string) => boolean;
4
+ scope: (state: any) => any;
5
+ project: (state: any) => any;
6
+ capability: (operation: any, input?: any) => any;
7
+ waitBudget: (state: any) => any;
8
+ guardInput: (state: any, action: any, independentExecution?: any) => any;
9
+ };