@nextclaw/x-harness 0.1.0 → 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 (94) hide show
  1. package/README.md +2 -2
  2. package/cli/main.js +23 -11
  3. package/dist/api.js +53 -1
  4. package/dist/browser-dom-capabilities.d.ts +4 -0
  5. package/dist/browser-dom-capabilities.js +110 -0
  6. package/dist/browser-dom-dependencies.d.ts +16 -0
  7. package/dist/browser-dom-editing.d.ts +148 -0
  8. package/dist/browser-dom-editing.js +386 -0
  9. package/dist/browser-dom-navigation.d.ts +9 -0
  10. package/dist/browser-dom-navigation.js +165 -0
  11. package/dist/browser-dom-network.d.ts +1 -0
  12. package/dist/browser-dom-network.js +128 -0
  13. package/dist/browser-dom-submission.d.ts +2 -0
  14. package/dist/browser-dom-submission.js +82 -0
  15. package/dist/browser-dom.d.ts +1 -1
  16. package/dist/browser-dom.js +51 -802
  17. package/dist/browser-native.js +32 -28
  18. package/dist/browser.js +6 -2
  19. package/dist/creator.js +1 -1
  20. package/dist/growth-batch.d.ts +5 -0
  21. package/dist/growth-batch.js +168 -0
  22. package/dist/growth-constants.d.ts +5 -0
  23. package/dist/growth-constants.js +13 -0
  24. package/dist/growth-dependencies.d.ts +33 -0
  25. package/dist/growth-execution.d.ts +11 -0
  26. package/dist/growth-execution.js +315 -0
  27. package/dist/growth-parallel.d.ts +4 -0
  28. package/dist/growth-parallel.js +301 -0
  29. package/dist/growth-plan.d.ts +4 -0
  30. package/dist/growth-plan.js +207 -0
  31. package/dist/growth-policy.d.ts +9 -0
  32. package/dist/growth-policy.js +181 -0
  33. package/dist/growth-publishing.d.ts +5 -0
  34. package/dist/growth-publishing.js +240 -0
  35. package/dist/growth-reading.d.ts +4 -0
  36. package/dist/growth-reading.js +135 -0
  37. package/dist/growth-recovery.d.ts +6 -0
  38. package/dist/growth-recovery.js +158 -0
  39. package/dist/growth-state.d.ts +8 -0
  40. package/dist/growth-state.js +183 -0
  41. package/dist/growth-tasks.d.ts +17 -0
  42. package/dist/growth-tasks.js +101 -0
  43. package/dist/growth.d.ts +1 -0
  44. package/dist/growth.js +122 -1902
  45. package/dist/platform-capabilities.d.ts +4 -0
  46. package/dist/platform-capabilities.js +19 -0
  47. package/dist/platform-collections.d.ts +7 -0
  48. package/dist/platform-collections.js +223 -0
  49. package/dist/platform-dependencies.d.ts +43 -0
  50. package/dist/platform-diagnostics.d.ts +9 -0
  51. package/dist/platform-diagnostics.js +51 -0
  52. package/dist/platform-observation.d.ts +8 -0
  53. package/dist/platform-observation.js +75 -0
  54. package/dist/platform-rules.d.ts +17 -0
  55. package/dist/platform-rules.js +41 -0
  56. package/dist/platform-targets.d.ts +7 -0
  57. package/dist/platform-targets.js +129 -0
  58. package/dist/platform-writing.d.ts +8 -0
  59. package/dist/platform-writing.js +171 -0
  60. package/dist/platform.d.ts +1 -1
  61. package/dist/platform.js +56 -677
  62. package/dist/readers-actions.d.ts +2 -0
  63. package/dist/readers-actions.js +66 -0
  64. package/dist/readers-ax-host.d.ts +2 -0
  65. package/dist/readers-ax-host.js +125 -0
  66. package/dist/readers-ax-parser.d.ts +1 -0
  67. package/dist/readers-ax-parser.js +69 -0
  68. package/dist/readers-ax-program.d.ts +1 -0
  69. package/dist/readers-ax-tree.d.ts +19 -0
  70. package/dist/readers-ax-tree.js +110 -0
  71. package/dist/readers-ax-views.d.ts +87 -0
  72. package/dist/readers-ax-views.js +247 -0
  73. package/dist/readers-cli.d.ts +6 -0
  74. package/dist/readers-composer.d.ts +2 -0
  75. package/dist/readers-composer.js +131 -0
  76. package/dist/readers-navigation.d.ts +2 -0
  77. package/dist/readers-navigation.js +66 -0
  78. package/dist/readers-posts.d.ts +2 -0
  79. package/dist/readers-posts.js +141 -0
  80. package/dist/readers-profile.d.ts +3 -0
  81. package/dist/readers-profile.js +157 -0
  82. package/dist/readers.d.ts +9 -20
  83. package/dist/results.js +1 -1
  84. package/dist/round-state.js +2 -2
  85. package/dist/types.d.ts +10 -0
  86. package/generated/observation.mjs +2 -1
  87. package/package.json +1 -1
  88. package/skill/SKILL.md +17 -36
  89. package/skill/references/api.md +7 -34
  90. package/skill/references/reads.md +23 -0
  91. package/skill/references/recovery.md +25 -0
  92. package/skill/references/setup.md +23 -0
  93. package/skill/references/writes.md +51 -0
  94. package/dist/readers.js +0 -1045
@@ -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
+ };