@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.
- package/README.md +2 -2
- package/cli/main.js +23 -11
- package/dist/api.js +53 -1
- package/dist/browser-dom-capabilities.d.ts +4 -0
- package/dist/browser-dom-capabilities.js +110 -0
- package/dist/browser-dom-dependencies.d.ts +16 -0
- package/dist/browser-dom-editing.d.ts +148 -0
- package/dist/browser-dom-editing.js +386 -0
- package/dist/browser-dom-navigation.d.ts +9 -0
- package/dist/browser-dom-navigation.js +165 -0
- package/dist/browser-dom-network.d.ts +1 -0
- package/dist/browser-dom-network.js +128 -0
- package/dist/browser-dom-submission.d.ts +2 -0
- package/dist/browser-dom-submission.js +82 -0
- package/dist/browser-dom.d.ts +1 -1
- package/dist/browser-dom.js +51 -802
- package/dist/browser-native.js +32 -28
- package/dist/browser.js +6 -2
- package/dist/creator.js +1 -1
- package/dist/growth-batch.d.ts +5 -0
- package/dist/growth-batch.js +168 -0
- package/dist/growth-constants.d.ts +5 -0
- package/dist/growth-constants.js +13 -0
- package/dist/growth-dependencies.d.ts +33 -0
- package/dist/growth-execution.d.ts +11 -0
- package/dist/growth-execution.js +315 -0
- package/dist/growth-parallel.d.ts +4 -0
- package/dist/growth-parallel.js +301 -0
- package/dist/growth-plan.d.ts +4 -0
- package/dist/growth-plan.js +207 -0
- package/dist/growth-policy.d.ts +9 -0
- package/dist/growth-policy.js +181 -0
- package/dist/growth-publishing.d.ts +5 -0
- package/dist/growth-publishing.js +240 -0
- package/dist/growth-reading.d.ts +4 -0
- package/dist/growth-reading.js +135 -0
- package/dist/growth-recovery.d.ts +6 -0
- package/dist/growth-recovery.js +158 -0
- package/dist/growth-state.d.ts +8 -0
- package/dist/growth-state.js +183 -0
- package/dist/growth-tasks.d.ts +17 -0
- package/dist/growth-tasks.js +101 -0
- package/dist/growth.d.ts +1 -0
- package/dist/growth.js +122 -1902
- package/dist/platform-capabilities.d.ts +4 -0
- package/dist/platform-capabilities.js +19 -0
- package/dist/platform-collections.d.ts +7 -0
- package/dist/platform-collections.js +223 -0
- package/dist/platform-dependencies.d.ts +43 -0
- package/dist/platform-diagnostics.d.ts +9 -0
- package/dist/platform-diagnostics.js +51 -0
- package/dist/platform-observation.d.ts +8 -0
- package/dist/platform-observation.js +75 -0
- package/dist/platform-rules.d.ts +17 -0
- package/dist/platform-rules.js +41 -0
- package/dist/platform-targets.d.ts +7 -0
- package/dist/platform-targets.js +129 -0
- package/dist/platform-writing.d.ts +8 -0
- package/dist/platform-writing.js +171 -0
- package/dist/platform.d.ts +1 -1
- package/dist/platform.js +56 -677
- package/dist/readers-actions.d.ts +2 -0
- package/dist/readers-actions.js +66 -0
- package/dist/readers-ax-host.d.ts +2 -0
- package/dist/readers-ax-host.js +125 -0
- package/dist/readers-ax-parser.d.ts +1 -0
- package/dist/readers-ax-parser.js +69 -0
- package/dist/readers-ax-program.d.ts +1 -0
- package/dist/readers-ax-tree.d.ts +19 -0
- package/dist/readers-ax-tree.js +110 -0
- package/dist/readers-ax-views.d.ts +87 -0
- package/dist/readers-ax-views.js +247 -0
- package/dist/readers-cli.d.ts +6 -0
- package/dist/readers-composer.d.ts +2 -0
- package/dist/readers-composer.js +131 -0
- package/dist/readers-navigation.d.ts +2 -0
- package/dist/readers-navigation.js +66 -0
- package/dist/readers-posts.d.ts +2 -0
- package/dist/readers-posts.js +141 -0
- package/dist/readers-profile.d.ts +3 -0
- package/dist/readers-profile.js +157 -0
- package/dist/readers.d.ts +9 -20
- package/dist/results.js +1 -1
- package/dist/round-state.js +2 -2
- package/dist/types.d.ts +10 -0
- package/generated/observation.mjs +2 -1
- package/package.json +1 -1
- package/skill/SKILL.md +17 -36
- package/skill/references/api.md +7 -34
- package/skill/references/reads.md +23 -0
- package/skill/references/recovery.md +25 -0
- package/skill/references/setup.md +23 -0
- package/skill/references/writes.md +51 -0
- 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