@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.
Files changed (80) hide show
  1. package/LICENSE +231 -6
  2. package/README.md +15 -1
  3. package/SOURCE.md +9 -0
  4. package/cli/main.js +232 -200
  5. package/dist/api.d.ts +56 -2
  6. package/dist/api.js +130 -79
  7. package/dist/boundary.js +1 -0
  8. package/dist/browser-common.d.ts +11 -0
  9. package/dist/browser-common.js +34 -18
  10. package/dist/browser-dom-capabilities.d.ts +7 -2
  11. package/dist/browser-dom-capabilities.js +123 -102
  12. package/dist/browser-dom-dependencies.js +1 -0
  13. package/dist/browser-dom-editing.d.ts +20 -2
  14. package/dist/browser-dom-editing.js +148 -116
  15. package/dist/browser-dom-navigation.d.ts +11 -2
  16. package/dist/browser-dom-navigation.js +66 -50
  17. package/dist/browser-dom-submission.d.ts +6 -1
  18. package/dist/browser-dom-submission.js +37 -18
  19. package/dist/browser-dom.d.ts +13 -1
  20. package/dist/browser-dom.js +113 -66
  21. package/dist/browser-native.d.ts +14 -1
  22. package/dist/browser-native.js +161 -128
  23. package/dist/creator-native.d.ts +3 -0
  24. package/dist/creator-native.js +113 -0
  25. package/dist/diagnostics.d.ts +11 -0
  26. package/dist/diagnostics.js +10 -0
  27. package/dist/growth-batch.d.ts +6 -3
  28. package/dist/growth-batch.js +157 -135
  29. package/dist/growth-dependencies.js +1 -0
  30. package/dist/growth-execution.d.ts +8 -2
  31. package/dist/growth-execution.js +68 -37
  32. package/dist/growth-parallel.d.ts +6 -2
  33. package/dist/growth-parallel.js +78 -47
  34. package/dist/growth-plan.d.ts +8 -3
  35. package/dist/growth-plan.js +45 -23
  36. package/dist/growth-policy.d.ts +11 -2
  37. package/dist/growth-policy.js +80 -53
  38. package/dist/growth-publishing.d.ts +7 -2
  39. package/dist/growth-publishing.js +72 -46
  40. package/dist/growth-reading.d.ts +6 -2
  41. package/dist/growth-reading.js +47 -25
  42. package/dist/growth-recovery.d.ts +7 -4
  43. package/dist/growth-recovery.js +177 -148
  44. package/dist/growth-state.d.ts +10 -2
  45. package/dist/growth-state.js +67 -30
  46. package/dist/growth-tasks.d.ts +19 -16
  47. package/dist/growth-tasks.js +153 -90
  48. package/dist/growth.d.ts +25 -1
  49. package/dist/growth.js +207 -124
  50. package/dist/platform-capabilities.d.ts +6 -2
  51. package/dist/platform-capabilities.js +18 -8
  52. package/dist/platform-collections.d.ts +10 -3
  53. package/dist/platform-collections.js +62 -35
  54. package/dist/platform-dependencies.js +1 -0
  55. package/dist/platform-diagnostics.d.ts +7 -2
  56. package/dist/platform-diagnostics.js +31 -19
  57. package/dist/platform-observation.d.ts +10 -2
  58. package/dist/platform-observation.js +44 -23
  59. package/dist/platform-targets.d.ts +10 -3
  60. package/dist/platform-targets.js +39 -14
  61. package/dist/platform-writing.d.ts +10 -2
  62. package/dist/platform-writing.js +63 -32
  63. package/dist/platform.d.ts +22 -1
  64. package/dist/platform.js +120 -52
  65. package/dist/readers-ax-host.d.ts +11 -1
  66. package/dist/readers-ax-host.js +108 -99
  67. package/dist/readers-ax-program.js +5 -0
  68. package/dist/readers-ax-tree.js +1 -0
  69. package/dist/readers-cli.js +79 -0
  70. package/dist/readers.js +9 -0
  71. package/dist/round-state.js +18 -1
  72. package/dist/store.d.ts +22 -4
  73. package/dist/store.js +173 -151
  74. package/dist/types.js +1 -0
  75. package/generated/observation.mjs +1 -1
  76. package/package.json +23 -9
  77. package/skill/SKILL.md +2 -2
  78. package/skill/references/reads.md +1 -1
  79. package/skill/references/recovery.md +4 -4
  80. package/skill/references/setup.md +1 -1
@@ -1,144 +1,166 @@
1
1
  import { followWindows, same } from "./growth-constants.js";
2
- export function createFollowBatch(dependencies) {
3
- const { prepareActionsForCall, store, scope, stamp, clock, evidenceExists, executor, parallelRound, round, } = dependencies;
4
- return {
5
- followBatch: async ({ taskId, handles, preparedActions } = {}) => {
6
- if (!Array.isArray(handles) ||
7
- handles.length < 5 ||
8
- handles.length > 10 ||
9
- handles.some((h) => !/^[A-Za-z0-9_]{1,15}$/.test(h)) ||
10
- new Set(handles.map((h) => h.toLowerCase())).size !==
11
- handles.length)
12
- throw new Error("正常关注子批需5~10位不同的已准备对象");
13
- if (preparedActions?.some((a) => a.kind !== "follow" ||
14
- !handles.some((h) => same(h, a.handle))))
15
- throw new Error("batch-action-target-mismatch");
16
- await prepareActionsForCall(taskId, preparedActions);
17
- let batchId, resumedBatch = false;
18
- const original = await store.transaction(async (snapshot) => {
19
- if (!scope(snapshot.state).includes("follow"))
20
- throw new Error("kind-outside-round-scope");
21
- if (snapshot.state.batchId !== taskId ||
22
- handles.some((h) => !snapshot.state.actions?.some((a) => a.kind === "follow" && same(a.handle, h))))
23
- throw new Error("子批名单必须归原任务队列");
24
- const next = structuredClone(snapshot.state);
25
- next.followBatches ??= [];
26
- const previous = next.followBatches.findLast((b) => b.status !== "SETTLED" &&
27
- b.handles.length === handles.length &&
28
- b.handles.every((h, i) => same(h, handles[i])));
29
- resumedBatch = Boolean(previous);
30
- batchId = previous?.id ?? `${taskId}-FOLLOW-${stamp()}`;
31
- if (previous)
32
- Object.assign(previous, {
33
- status: "RUNNING",
34
- lastResumedAt: stamp(),
35
- });
36
- else
37
- next.followBatches.push({
38
- id: batchId,
39
- handles,
40
- planned: handles.length,
41
- startedAt: stamp(),
42
- status: "RUNNING",
43
- });
44
- next.updatedAt = stamp();
45
- return next;
46
- });
47
- const preparedCount = original.state.actions.filter((a) => a.kind === "follow" &&
48
- a.status === "prepared" &&
49
- handles.some((h) => same(h, a.handle))).length;
50
- let eligibleAt = clock();
51
- const previousGroup = original.state.execution?.followGroup;
52
- const lastAttempt = (original.state.attempts ?? [])
53
- .filter((a) => a.kind === "follow")
54
- .sort((a, b) => Date.parse(a.at) - Date.parse(b.at))
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
- return {
80
- taskId,
81
- batchId,
56
+ else
57
+ next.followBatches.push({
58
+ id: batchId,
59
+ handles,
82
60
  planned: handles.length,
83
- attempted: 0,
84
- verifiedThisCall: 0,
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
- next.updatedAt = stamp();
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 summary;
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 function createGrowthExecution(dependencies: Pick<GrowthDependencies, "capability" | "platform" | "settleProof" | "clock" | "save" | "guardInput" | "waitBudget" | "authorizedWindow" | "stamp" | "store" | "executor" | "session">): {
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 {};
@@ -1,11 +1,39 @@
1
1
  import { operations, same } from "./growth-constants.js";
2
- export function createGrowthExecution(dependencies) {
3
- const { capability, platform, settleProof, clock, save, guardInput, waitBudget, authorizedWindow, stamp, store, executor, session, } = dependencies;
4
- async function checkExecutionIdentity(snapshot, action) {
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 function admitAction(snapshot, action) {
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 function execute(snapshot, action) {
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
- return { execute };
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 function createParallelRound(dependencies: Pick<GrowthDependencies, "load" | "scope" | "result" | "store" | "executor" | "runtimeId" | "stamp" | "evidenceExists" | "execute" | "clock" | "releaseSession" | "session">): {
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;