@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,158 @@
1
+ import { xURL } from "./platform-rules.js";
2
+ import { same } from "./growth-constants.js";
3
+ export function createGrowthRecovery(dependencies) {
4
+ const { capability, platform, load, evidenceExists, clock, store, settleProof, executor, parallelRound, round, readTask, } = dependencies;
5
+ return {
6
+ async verifyPublishedPost({ taskId, draftPath, publicURL, evidenceRef, sourceLink, }) {
7
+ // Read-only recovery of a single submitted original; never opens a composer.
8
+ const caps = capability("read");
9
+ if (!caps.covered || typeof platform.readPost !== "function")
10
+ return {
11
+ status: "needsCapability",
12
+ missing: ["platform.readPost", ...caps.missing],
13
+ };
14
+ const initial = await load(taskId);
15
+ const action = initial.state.actions.find((a) => a.kind === "original" && a.draftPath === draftPath);
16
+ if (!action ||
17
+ !["intent", "unknown"].includes(action.status) ||
18
+ !evidenceExists(evidenceRef))
19
+ throw new Error("submitted-original-and-public-proof-required");
20
+ const url = xURL(publicURL), postId = url.pathname.match(/\/status\/(\d+)$/)?.[1];
21
+ if (!postId ||
22
+ url.pathname.toLowerCase() !==
23
+ `/${action.accountHandle}/status/${postId}`.toLowerCase())
24
+ throw new Error("public-account-mismatch");
25
+ const observation = await platform.readPost({
26
+ postId,
27
+ url: publicURL,
28
+ evidenceRef,
29
+ });
30
+ const trailing = action.text.match(/\n\n(https:\/\/\S+)$/);
31
+ const matches = (observation.posts ?? []).filter((p) => {
32
+ const exact = p.body === action.text;
33
+ const card = trailing &&
34
+ sourceLink &&
35
+ evidenceExists(sourceLink.evidenceRef) &&
36
+ sourceLink.resolvedURL === trailing[1] &&
37
+ p.links?.includes(sourceLink.href) &&
38
+ p.body === action.text.slice(0, -trailing[0].length);
39
+ let renderedSource = false;
40
+ if (trailing &&
41
+ sourceLink?.renderedText &&
42
+ evidenceExists(sourceLink.evidenceRef) &&
43
+ sourceLink.resolvedURL === trailing[1] &&
44
+ p.links?.includes(sourceLink.href)) {
45
+ try {
46
+ renderedSource =
47
+ new URL(sourceLink.renderedText.replace(/\s/g, "")).href ===
48
+ sourceLink.resolvedURL &&
49
+ p.body?.endsWith(sourceLink.renderedText) &&
50
+ p.body.slice(0, -sourceLink.renderedText.length).trimEnd() ===
51
+ action.text.slice(0, -trailing[0].length);
52
+ }
53
+ catch {
54
+ /* Invalid rendered URL cannot verify publication. */
55
+ }
56
+ }
57
+ return (p.id === postId &&
58
+ same(p.author, action.accountHandle) &&
59
+ (exact || card || renderedSource) &&
60
+ !p.needsExpansion &&
61
+ !(action.beforeIds ?? []).includes(p.id) &&
62
+ Date.parse(p.datetime) >= Date.parse(action.intentAt) &&
63
+ Date.parse(p.datetime) <= clock() + 5000 &&
64
+ (!action.assets?.length ||
65
+ action.assets.every((a) => p.images?.some((i) => i.alt === a.alt))));
66
+ });
67
+ Object.assign(observation, {
68
+ verified: !observation.needsStateCheck &&
69
+ !observation.alerts?.length &&
70
+ matches.length === 1,
71
+ publicId: matches.length === 1 ? postId : null,
72
+ sourceLink,
73
+ });
74
+ return store.withLock(async () => {
75
+ const latest = await load(taskId), target = latest.state.actions.find((a) => a.id === action.id);
76
+ if (!["intent", "unknown"].includes(target?.status) ||
77
+ target.intentAt !== action.intentAt ||
78
+ target.text !== action.text)
79
+ throw new Error("recovery-action-changed");
80
+ await settleProof(latest, target, observation, {
81
+ preserveStatus: true,
82
+ });
83
+ return {
84
+ status: observation.verified ? "verified" : "unknown",
85
+ publicId: observation.publicId,
86
+ taskId,
87
+ actionId: action.id,
88
+ mayEnd: false,
89
+ };
90
+ });
91
+ },
92
+ verifySubmission: async ({ taskId, actionId }) => {
93
+ const snapshot = await load(taskId), action = snapshot.state.actions?.find((a) => a.id === actionId);
94
+ if (!action || !["intent", "unknown"].includes(action.status))
95
+ return {
96
+ status: "needsDecision",
97
+ reason: "verification-requires-original-unknown-or-intent",
98
+ mayEnd: false,
99
+ };
100
+ return (executor ? parallelRound : round)({
101
+ taskId,
102
+ requestedKind: action.kind,
103
+ selector: (a) => a.id === actionId,
104
+ });
105
+ },
106
+ verifyFollow: async (target) => {
107
+ const mode = target.mode ?? "receipt";
108
+ if (!["receipt", "decision"].includes(mode))
109
+ throw new Error("关注核验模式须为receipt/decision");
110
+ const caps = platform.preflight({ operation: "read" });
111
+ if (!caps.covered)
112
+ return {
113
+ handle: target.handle,
114
+ verified: false,
115
+ status: "needsCapability",
116
+ ...caps,
117
+ };
118
+ if (!target.url || !target.evidenceRef) {
119
+ const original = (await store.load()).state.actions?.find((a) => a.kind === "follow" && same(a.handle, target.handle));
120
+ if (!original)
121
+ return {
122
+ handle: target.handle,
123
+ verified: false,
124
+ status: "needsDecision",
125
+ reason: "original-person-observation-required",
126
+ };
127
+ target = original;
128
+ }
129
+ const value = await readTask("readPerson", {
130
+ ...target,
131
+ mode: "decision",
132
+ });
133
+ return {
134
+ handle: target.handle,
135
+ status: value.status,
136
+ fresh: value.fresh,
137
+ verified: value.status === "observed" &&
138
+ !value.needsStateCheck &&
139
+ !value.alerts?.length &&
140
+ value.relationship === "following" &&
141
+ (!target.accountHandle ||
142
+ same(value.accountHandle, target.accountHandle)),
143
+ ...(value.evidenceRef ? { evidenceRef: value.evidenceRef } : {}),
144
+ ...(value.missing?.length ? { missing: value.missing } : {}),
145
+ ...(value.diagnostic
146
+ ? {
147
+ diagnostic: {
148
+ operation: value.diagnostic.operation,
149
+ category: value.diagnostic.category,
150
+ code: value.diagnostic.code,
151
+ },
152
+ }
153
+ : {}),
154
+ ...(mode === "decision" ? { observation: value } : {}),
155
+ };
156
+ },
157
+ };
158
+ }
@@ -0,0 +1,8 @@
1
+ import type { GrowthDependencies } from "./growth-dependencies.js";
2
+ export declare function createGrowthState(dependencies: Pick<GrowthDependencies, "store" | "project" | "clock" | "evidenceExists" | "stamp" | "executor" | "waitBudget" | "platform" | "runtimeId" | "session">): {
3
+ load: (taskId: any) => Promise<any>;
4
+ save: (snapshot: any, state: any) => Promise<any>;
5
+ result: (snapshot: any, needs?: any) => any;
6
+ settleProof: (snapshot: any, action: any, observation: any, { preserveStatus }?: any) => Promise<any>;
7
+ releaseSession: (taskId?: any) => Promise<any>;
8
+ };
@@ -0,0 +1,183 @@
1
+ import { checkRound } from "./round-state.js";
2
+ import { kinds } from "./growth-constants.js";
3
+ export function createGrowthState(dependencies) {
4
+ const { store, project, clock, evidenceExists, stamp, executor, waitBudget, platform, runtimeId, session, } = dependencies;
5
+ async function load(taskId) {
6
+ const snapshot = await store.load();
7
+ if (snapshot.state.batchId !== taskId)
8
+ throw new Error("任务ID不是原台账当前批次");
9
+ // Expired WAITING is actionable, not a malformed ledger. Validate structure separately from stopping.
10
+ const gate = checkRound({ ...project(snapshot.state), status: "RUNNING" }, { now: clock(), evidenceExists });
11
+ if (!gate.valid)
12
+ throw new Error(`原轮次标记无效:${gate.errors.join(";")}`);
13
+ const actions = snapshot.state.actions ?? [];
14
+ if (new Set(actions.map((a) => a.id)).size !== actions.length)
15
+ throw new Error("原队列动作ID重复");
16
+ for (const a of actions) {
17
+ if (!a.id ||
18
+ !kinds.includes(a.kind) ||
19
+ ![
20
+ "prepared",
21
+ "intent",
22
+ "unknown",
23
+ "verified",
24
+ "preexisting",
25
+ "skipped",
26
+ ].includes(a.status))
27
+ throw new Error("原队列动作结构无效");
28
+ }
29
+ return snapshot;
30
+ }
31
+ async function save(snapshot, state) {
32
+ state.updatedAt = stamp();
33
+ if (!executor)
34
+ return store.save(snapshot, state);
35
+ // Batch metadata still uses a short withLock transaction; it never contains browser I/O.
36
+ if (!session.claim)
37
+ return store.save(snapshot, state);
38
+ return store.transaction((latest) => {
39
+ const next = structuredClone(latest.state), before = snapshot.state.actions.find((a) => a.id === session.claim.actionId);
40
+ const entry = next.actions.find((a) => a.id === session.claim.actionId), after = state.actions.find((a) => a.id === session.claim.actionId);
41
+ if (next.batchId !== snapshot.state.batchId ||
42
+ entry?.claim?.token !== session.claim.token ||
43
+ JSON.stringify({ ...entry, claim: undefined }) !==
44
+ JSON.stringify({ ...before, claim: undefined }))
45
+ throw new Error("action-claim-or-version-conflict");
46
+ const oldItem = snapshot.state.items.find((i) => i.kind === entry.kind), newItem = state.items.find((i) => i.kind === entry.kind);
47
+ const item = next.items.find((i) => i.kind === entry.kind);
48
+ const reservation = before.status === "prepared" && after.status === "intent";
49
+ if (reservation) {
50
+ if (!item.actionable || item.verified + item.unknown >= item.planned)
51
+ throw new Error("shared-target-capacity-exhausted");
52
+ if (entry.kind === "follow" && waitBudget(next))
53
+ throw new Error("shared-follow-budget-exhausted");
54
+ }
55
+ if (after.status === "verified" &&
56
+ entry.kind !== "follow" &&
57
+ next.actions.some((a) => a.id !== entry.id &&
58
+ a.status === "verified" &&
59
+ a.publicId === after.publicId))
60
+ throw new Error("同一公开结果不能重复结算");
61
+ Object.assign(entry, after, { claim: entry.claim });
62
+ item.verified += newItem.verified - oldItem.verified;
63
+ item.unknown += newItem.unknown - oldItem.unknown;
64
+ if (item.unknown < 0 || item.verified + item.unknown > item.planned)
65
+ throw new Error("并行结算越界");
66
+ item.resultEvidence = [
67
+ ...new Set([
68
+ ...(item.resultEvidence ?? []),
69
+ ...(newItem.resultEvidence ?? []),
70
+ ]),
71
+ ];
72
+ for (const key of [
73
+ "actionable",
74
+ "reason",
75
+ "resumeAt",
76
+ "resumeWhen",
77
+ "blockEvidence",
78
+ ]) {
79
+ if (JSON.stringify(oldItem[key]) !== JSON.stringify(newItem[key])) {
80
+ if (key in newItem)
81
+ item[key] = newItem[key];
82
+ else
83
+ delete item[key];
84
+ }
85
+ }
86
+ if (reservation) {
87
+ const attempt = state.attempts.at(-1);
88
+ next.attempts ??= [];
89
+ next.attempts.push(attempt);
90
+ if (entry.kind === "follow") {
91
+ next.execution ??= {};
92
+ const old = next.execution.followGroup;
93
+ next.execution.followGroup = {
94
+ attempts: old?.attempts < 10 && clock() < Date.parse(old.lastAt) + 180000
95
+ ? old.attempts + 1
96
+ : 1,
97
+ lastAt: attempt.at,
98
+ };
99
+ }
100
+ }
101
+ next.status = "RUNNING";
102
+ next.updatedAt = stamp();
103
+ return next;
104
+ });
105
+ }
106
+ function result(snapshot, needs = []) {
107
+ const gate = checkRound(project(snapshot.state), {
108
+ now: clock(),
109
+ evidenceExists,
110
+ });
111
+ return {
112
+ status: gate.mayEnd
113
+ ? gate.status === "COMPLETE"
114
+ ? "complete"
115
+ : "waiting"
116
+ : needs.some((n) => n.status === "needsCapability")
117
+ ? "needsCapability"
118
+ : "needsDecision",
119
+ taskId: snapshot.state.batchId,
120
+ mayEnd: gate.mayEnd,
121
+ scopeKinds: gate.scopeKinds,
122
+ actual: Object.fromEntries(snapshot.state.items.map((i) => [
123
+ i.kind,
124
+ { verified: i.verified, unknown: i.unknown },
125
+ ])),
126
+ gaps: gate.pending,
127
+ ...(needs.length ? { decision: needs } : {}),
128
+ evidenceRef: snapshot.path ?? store.path,
129
+ ...(gate.errors.length ? { settlementErrors: gate.errors } : {}),
130
+ };
131
+ }
132
+ async function settleProof(snapshot, action, observation, { preserveStatus = false } = {}) {
133
+ const proof = await store.recordProof(await platform.captureProof({ observation }));
134
+ const state = structuredClone(snapshot.state);
135
+ const entry = state.actions.find((a) => a.id === action.id);
136
+ const item = state.items.find((i) => i.kind === action.kind);
137
+ if (observation.verified) {
138
+ if (action.kind !== "follow" &&
139
+ (!/^\d+$/.test(observation.publicId ?? "") ||
140
+ state.actions.some((a) => a.id !== action.id &&
141
+ a.status === "verified" &&
142
+ a.publicId === observation.publicId)))
143
+ throw new Error("同一公开结果不能重复结算");
144
+ if (["intent", "unknown"].includes(entry.status))
145
+ item.unknown -= 1;
146
+ if (entry.status !== "verified")
147
+ item.verified += 1;
148
+ entry.status = "verified";
149
+ entry.publicId = observation.publicId;
150
+ entry.resultEvidence = proof;
151
+ item.resultEvidence = [
152
+ ...new Set([...(item.resultEvidence ?? []), proof]),
153
+ ];
154
+ }
155
+ else {
156
+ if (!["intent", "unknown"].includes(entry.status))
157
+ item.unknown += 1;
158
+ entry.status = "unknown";
159
+ entry.resultEvidence = proof;
160
+ }
161
+ if (item.verified > item.planned || item.unknown < 0)
162
+ throw new Error("结算越界:不覆盖原目标或重复计数");
163
+ if (!preserveStatus)
164
+ state.status = "RUNNING";
165
+ return save(snapshot, state);
166
+ }
167
+ async function releaseSession(taskId = undefined) {
168
+ if (!executor)
169
+ return;
170
+ await store.transaction((latest) => {
171
+ const own = latest.state.executions?.[executor.id];
172
+ if ((taskId && latest.state.batchId !== taskId) ||
173
+ own?.runtimeId !== runtimeId ||
174
+ !own.activity)
175
+ return null;
176
+ const next = structuredClone(latest.state);
177
+ delete next.executions[executor.id].activity;
178
+ next.updatedAt = stamp();
179
+ return next;
180
+ });
181
+ }
182
+ return { load, save, result, settleProof, releaseSession };
183
+ }
@@ -0,0 +1,17 @@
1
+ import type { GrowthDependencies } from "./growth-dependencies.js";
2
+ export declare function createGrowthTasks(dependencies: Pick<GrowthDependencies, "platform" | "capability" | "executor" | "readTask" | "parallelRound" | "round" | "load" | "result" | "publishIndependent" | "prepareActionsForCall">): {
3
+ preflight: ({ operation, input }: any) => any;
4
+ readCreator: (args: any) => Promise<any>;
5
+ inspectAccount: (args: any) => Promise<any>;
6
+ readPost: (args: any) => Promise<any>;
7
+ readPerson: (args: any) => Promise<any>;
8
+ readBookmarks: (args: any) => Promise<any>;
9
+ searchSources: (args: any) => Promise<any>;
10
+ readFeedback: (args: any) => Promise<any>;
11
+ runGrowthRound: (args: any) => Promise<any>;
12
+ resumeGrowthRound: (args: any) => Promise<any>;
13
+ inspectRound: ({ taskId }: any) => Promise<any>;
14
+ followOne: ({ taskId, handle }: any) => Promise<any>;
15
+ publishPost: (input: any) => Promise<any>;
16
+ replyToPost: ({ taskId, postId, text, preparedAction }: any) => Promise<any>;
17
+ };
@@ -0,0 +1,101 @@
1
+ import { kinds, operations, same } from "./growth-constants.js";
2
+ export function createGrowthTasks(dependencies) {
3
+ const { platform, capability, executor, readTask, parallelRound, round, load, result, publishIndependent, prepareActionsForCall, } = dependencies;
4
+ return {
5
+ preflight: ({ operation, input = {} }) => {
6
+ if (["readBookmarks", "searchSources", "readFeedback"].includes(operation))
7
+ return platform.preflight({ operation, input });
8
+ if (["inspectRound", "verifySubmission"].includes(operation))
9
+ return capability("read", input);
10
+ if (["inspectAccount", "verifyFollow"].includes(operation)) {
11
+ const name = operation === "verifyFollow" ? "readPerson" : "readAccount";
12
+ if (typeof platform?.preflight !== "function" ||
13
+ typeof platform?.[name] !== "function")
14
+ return {
15
+ covered: false,
16
+ missing: [name],
17
+ owner: "scripts/x-harness-platform.mjs",
18
+ };
19
+ return platform.preflight({ operation: "read", input });
20
+ }
21
+ if (["runGrowthRound", "resumeGrowthRound"].includes(operation)) {
22
+ const scoped = executor?.scopeKinds ?? input.scopeKinds ?? kinds;
23
+ if (!Array.isArray(scoped) ||
24
+ !scoped.length ||
25
+ scoped.some((k) => !kinds.includes(k)))
26
+ return {
27
+ covered: false,
28
+ missing: ["scopeKinds"],
29
+ owner: "scripts/x-growth-harness.mjs",
30
+ };
31
+ const all = scoped.map((kind) => capability(operations[kind], { ...input, kind }));
32
+ return {
33
+ covered: all.every((c) => c.covered),
34
+ missing: [...new Set(all.flatMap((c) => c.missing))],
35
+ owners: [
36
+ ...new Set(all.filter((c) => !c.covered).map((c) => c.owner)),
37
+ ],
38
+ };
39
+ }
40
+ if (![
41
+ "read",
42
+ "followOne",
43
+ "followBatch",
44
+ "publishPost",
45
+ "replyToPost",
46
+ ...Object.values(operations),
47
+ ].includes(operation))
48
+ return {
49
+ covered: false,
50
+ missing: [`operation.${operation}`],
51
+ owner: "scripts/x-growth-harness.mjs",
52
+ };
53
+ return capability(operation, input);
54
+ },
55
+ readCreator: (args) => readTask("readCreator", args),
56
+ inspectAccount: (args) => readTask("readAccount", args),
57
+ readPost: (args) => readTask("readPost", { ...args, expand: true }),
58
+ readPerson: (args) => readTask("readPerson", args),
59
+ readBookmarks: (args) => readTask("readBookmarks", args),
60
+ searchSources: (args) => readTask("searchSources", args),
61
+ readFeedback: (args) => readTask("readFeedback", args),
62
+ runGrowthRound: (args) => executor ? parallelRound(args) : round(args),
63
+ resumeGrowthRound: (args) => executor ? parallelRound(args) : round(args),
64
+ inspectRound: async ({ taskId }) => {
65
+ const snapshot = await load(taskId);
66
+ return result(snapshot);
67
+ },
68
+ followOne: ({ taskId, handle }) => (executor ? parallelRound : round)({
69
+ taskId,
70
+ requestedKind: "follow",
71
+ selector: (a) => a.kind === "follow" && same(a.handle, handle),
72
+ }),
73
+ publishPost: async (input) => {
74
+ if (!executor && input.independentExecution)
75
+ return publishIndependent(input);
76
+ if (input.preparedAction &&
77
+ (input.preparedAction.kind !== "original" ||
78
+ input.preparedAction.draftPath !== input.draftPath))
79
+ throw new Error("prepared-original-target-mismatch");
80
+ await prepareActionsForCall(input.taskId, input.preparedAction ? [input.preparedAction] : []);
81
+ return (executor ? parallelRound : round)({
82
+ taskId: input.taskId,
83
+ requestedKind: "original",
84
+ selector: (a) => a.kind === "original" && a.draftPath === input.draftPath,
85
+ });
86
+ },
87
+ replyToPost: async ({ taskId, postId, text, preparedAction }) => {
88
+ if (preparedAction &&
89
+ (preparedAction.kind !== "reply" ||
90
+ preparedAction.postId !== postId ||
91
+ preparedAction.text !== text))
92
+ throw new Error("prepared-reply-target-mismatch");
93
+ await prepareActionsForCall(taskId, preparedAction ? [preparedAction] : []);
94
+ return (executor ? parallelRound : round)({
95
+ taskId,
96
+ requestedKind: "reply",
97
+ selector: (a) => a.kind === "reply" && a.postId === postId && a.text === text,
98
+ });
99
+ },
100
+ };
101
+ }
package/dist/growth.d.ts CHANGED
@@ -1 +1,2 @@
1
+ /** Synchronous composition only; owners retain one original ledger and settlement path. */
1
2
  export declare function createGrowthHarness({ platform, store, clock, executor, }?: any): any;