brainchain-worker 0.12.0 → 0.12.2
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/dist/cli.js +11 -3
- package/dist/state.js +16 -0
- package/dist/watch.js +7 -2
- package/dist/work.js +14 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ BRAINCHAIN_NETWORK=mainnet npx brainchain-worker claim # pays it in one
|
|
|
83
83
|
Any agent can watch BrainChain for the rest, with no stake. `watch` judges every change Jev queues against the [constitution](https://docs.usebrainchain.com/constitution) and re-checks every new decision the way `brainchain-verify` does: the published record, its hashes and its confidence against what the chain recorded.
|
|
84
84
|
|
|
85
85
|
```sh
|
|
86
|
-
npx brainchain-worker watch --once # one round: queued changes judged,
|
|
86
|
+
npx brainchain-worker watch --once # one round: queued changes judged, decisions since the last --once verified (the latest 10 on the first), exit 1 on a finding
|
|
87
87
|
npx brainchain-worker watch # keeps watching, a round every 30 seconds
|
|
88
88
|
npx brainchain-worker watch --veto # also vetoes a change that crosses a floor, through the guardian gate
|
|
89
89
|
```
|
|
@@ -166,7 +166,7 @@ Briefs are written by strangers, and some will try to get your agent to do somet
|
|
|
166
166
|
| `claim [--dry]` | Claim the ETH due on the worker's Proofs on Robinhood Chain, in one transaction. `--dry` lists each Proof and what it is due without sending anything. |
|
|
167
167
|
| `bridge --eth <ETH> \| --stake <amount> \| --bounty <amount> [--dry]` | Move ETH, the stake token or the bounty token from Robinhood Chain to the worker's address on BrainChain. |
|
|
168
168
|
| `credit --agent-id <id> [--premium <bps>]` | Sign Priors' consent for BrainChain to back a credit line for the worker's ERC-8004 agent, with the key that owns the agent, and send it to the service. The line opens once the worker is proven on the board. `--premium` is the most the sponsor may charge per 30 days (default 0, at most 200). Mainnet only. |
|
|
169
|
-
| `watch [--veto] [--once]` | Judge every queued change against the constitution and re-verify every new decision, reporting what disagrees. With the worker's key set, each round is reported to the service toward the worker's watchtower mark. `--veto` vetoes a change that crosses a floor through the guardian gate, when the worker's key is the gate's watcher. `--once` reads one round and exits 1 on a finding. |
|
|
169
|
+
| `watch [--veto] [--once]` | Judge every queued change against the constitution and re-verify every new decision, reporting what disagrees. With the worker's key set, each round is reported to the service toward the worker's watchtower mark. `--veto` vetoes a change that crosses a floor through the guardian gate, when the worker's key is the gate's watcher. `--once` reads one round, checks the decisions added since the last `--once` (the latest 10 on the first) and exits 1 on a finding. |
|
|
170
170
|
| `propose --target <contract> --call <function> --args <JSON> --title <text> --rationale <text> [--current <text>] [--proposed <text>] [--data <hex>] [--dry]` | Bond the stake token and propose a change for Jev to decide. See [Propose a change](#propose-a-change). |
|
|
171
171
|
| `proposal <id>` | Where a proposal stands: waiting for Jev and until when, adopted and when its change runs, or kept with the bond burned. Needs no key. |
|
|
172
172
|
| `dispute <task>` | Dispute the grade on a task the worker delivered, inside the dispute window. Jev rules; a confident ruling decides, otherwise the grade stands. |
|
package/dist/cli.js
CHANGED
|
@@ -10,11 +10,13 @@ import { createProofMinter, createProofsChain, MAX_MINT, mint } from "./mint.js"
|
|
|
10
10
|
import { chainOf, resolveNetwork } from "./network.js";
|
|
11
11
|
import { buildCall, caseURI, createProposals, proposal, propose } from "./propose.js";
|
|
12
12
|
import { hireable, shortOfStake, snapshot, stake, usdg, wallet, when } from "./standing.js";
|
|
13
|
-
import { openStore } from "./state.js";
|
|
13
|
+
import { openStore, readWatched, writeWatched } from "./state.js";
|
|
14
14
|
import { createCreditChain, credit } from "./credit.js";
|
|
15
15
|
import { appeal, createDisputeChain, dispute } from "./dispute.js";
|
|
16
16
|
import { createReporter, createWatch, createWatchChain } from "./watch.js";
|
|
17
|
-
import { createWork, shellCommand, stopLeftover, stopper } from "./work.js";
|
|
17
|
+
import { createWork, firstProgram, programFound, shellCommand, stopLeftover, stopper } from "./work.js";
|
|
18
|
+
/** How many of the latest decisions a first watch --once checks. */
|
|
19
|
+
const FIRST_RUN_DECISIONS = 10n;
|
|
18
20
|
/** A published record's bytes as the service serves them, capped at a megabyte. */
|
|
19
21
|
async function fetchRecord(uri) {
|
|
20
22
|
const response = await fetch(uri, { signal: AbortSignal.timeout(20_000) });
|
|
@@ -65,7 +67,8 @@ Usage:
|
|
|
65
67
|
worker's key set, each round is reported to the service toward the
|
|
66
68
|
worker's watchtower mark. --veto vetoes a change that crosses a floor
|
|
67
69
|
through the guardian gate, when the worker's key is the gate's
|
|
68
|
-
watcher. --once reads one round
|
|
70
|
+
watcher. --once reads one round, checks the decisions added since
|
|
71
|
+
the last --once (the latest 10 on the first) and exits 1 on a finding.
|
|
69
72
|
brainchain-worker credit --agent-id <id> [--premium <bps>]
|
|
70
73
|
Ask BrainChain to back a credit line for the worker's ERC-8004 agent
|
|
71
74
|
on Priors (priors.trade): signs Priors' consent with the agent owner's
|
|
@@ -307,6 +310,9 @@ async function run(network, { key, account }, values, once, mints) {
|
|
|
307
310
|
throw new UsageError("--timeout is the most minutes the command may spend on one task: more than 0, at most 1440, and 20 by default");
|
|
308
311
|
}
|
|
309
312
|
const log = (message) => console.log(`${new Date().toISOString().slice(0, 19)}Z ${message}`);
|
|
313
|
+
const program = firstProgram(exec);
|
|
314
|
+
if (program && !programFound(program))
|
|
315
|
+
log(`warning: the shell cannot find ${program}, so --exec will fail when the worker is hired`);
|
|
310
316
|
const store = openStore(account.address, network, log);
|
|
311
317
|
// The lock rules out a live run, so a command still in the state was left by one that died.
|
|
312
318
|
await stopLeftover(store, log);
|
|
@@ -455,6 +461,8 @@ export async function main(argv) {
|
|
|
455
461
|
...(keyed ? { report: createReporter(network, account) } : {}),
|
|
456
462
|
veto,
|
|
457
463
|
log: console.log,
|
|
464
|
+
// Each run of --once is a new process, so it keeps its place on disk; a first run checks the latest few.
|
|
465
|
+
...(once ? { seenDecision: readWatched(log), recent: FIRST_RUN_DECISIONS, saw: (count) => writeWatched(log, count) } : {}),
|
|
458
466
|
});
|
|
459
467
|
if (veto && !network.contracts.gate)
|
|
460
468
|
throw new UsageError(`${network.name} has no guardian gate yet, so there is nothing to veto through`);
|
package/dist/state.js
CHANGED
|
@@ -2,6 +2,22 @@ import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, re
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
export const stateDir = () => join(homedir(), ".config", "brainchain-worker");
|
|
5
|
+
/** The decision count a one-shot watch run stopped at, kept per decision log; undefined when there is none or it cannot be read. */
|
|
6
|
+
export function readWatched(log, dir = stateDir()) {
|
|
7
|
+
try {
|
|
8
|
+
const { seen } = JSON.parse(readFileSync(join(dir, `watch.${log.toLowerCase()}.json`), "utf8"));
|
|
9
|
+
return typeof seen === "string" && /^\d+$/.test(seen) ? BigInt(seen) : undefined;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function writeWatched(log, seen, dir = stateDir()) {
|
|
16
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
17
|
+
const file = join(dir, `watch.${log.toLowerCase()}.json`);
|
|
18
|
+
writeFileSync(`${file}.tmp`, JSON.stringify({ seen: seen.toString() }), { mode: 0o600 });
|
|
19
|
+
renameSync(`${file}.tmp`, file);
|
|
20
|
+
}
|
|
5
21
|
const empty = (board) => ({ board: board.toLowerCase(), tasks: {}, done: [] });
|
|
6
22
|
export function memoryStore(board = "") {
|
|
7
23
|
return { state: empty(board), save: () => undefined };
|
package/dist/watch.js
CHANGED
|
@@ -139,7 +139,7 @@ export function createReporter(network, account, fetcher = fetch) {
|
|
|
139
139
|
export function createWatch(options) {
|
|
140
140
|
const { network, chain, verify, log } = options;
|
|
141
141
|
let nextBlock = options.fromBlock ?? network.fromBlock;
|
|
142
|
-
let seenDecision;
|
|
142
|
+
let seenDecision = options.seenDecision;
|
|
143
143
|
const judged = new Set();
|
|
144
144
|
let findings = 0;
|
|
145
145
|
async function changes() {
|
|
@@ -188,7 +188,11 @@ export function createWatch(options) {
|
|
|
188
188
|
if (!verify)
|
|
189
189
|
return;
|
|
190
190
|
const count = await chain.decisions();
|
|
191
|
-
|
|
191
|
+
if (seenDecision === undefined && options.recent !== undefined)
|
|
192
|
+
seenDecision = count > options.recent ? count - options.recent : 0n;
|
|
193
|
+
// A log restarted under a new set of contracts has fewer decisions than the count kept from the old one.
|
|
194
|
+
if (seenDecision !== undefined && seenDecision > count)
|
|
195
|
+
seenDecision = options.recent === undefined ? undefined : count > options.recent ? count - options.recent : 0n;
|
|
192
196
|
if (seenDecision === undefined) {
|
|
193
197
|
seenDecision = count;
|
|
194
198
|
log(`${count} decisions recorded so far; new ones are checked from here`);
|
|
@@ -215,6 +219,7 @@ export function createWatch(options) {
|
|
|
215
219
|
}
|
|
216
220
|
}
|
|
217
221
|
seenDecision = count;
|
|
222
|
+
options.saw?.(count);
|
|
218
223
|
if (!options.report || (checked.length === 0 && found.length === 0))
|
|
219
224
|
return;
|
|
220
225
|
try {
|
package/dist/work.js
CHANGED
|
@@ -70,12 +70,23 @@ export function childEnv(extra) {
|
|
|
70
70
|
env[name] = value;
|
|
71
71
|
return { ...env, ...extra };
|
|
72
72
|
}
|
|
73
|
+
/** The program a shell command starts with, when it plainly starts with one; undefined for anything a shell has to interpret first. */
|
|
74
|
+
export function firstProgram(command) {
|
|
75
|
+
const word = /^\s*([^\s]+)/.exec(command)?.[1];
|
|
76
|
+
return word !== undefined && /^[\w./+-]+$/.test(word) ? word : undefined;
|
|
77
|
+
}
|
|
78
|
+
/** Whether the shell can find a program, for a warning at start; a typo otherwise only shows at the first hire. */
|
|
79
|
+
export function programFound(program) {
|
|
80
|
+
return spawnSync("sh", ["-c", 'command -v -- "$0" >/dev/null', program], { stdio: "ignore" }).status === 0;
|
|
81
|
+
}
|
|
73
82
|
function signalGroup(pid, signal) {
|
|
74
83
|
try {
|
|
75
84
|
process.kill(-pid, signal);
|
|
76
85
|
}
|
|
77
86
|
catch (error) {
|
|
78
|
-
|
|
87
|
+
// macOS answers EPERM for a group whose members are already exiting; the group is the worker's own child either way.
|
|
88
|
+
const { code } = error;
|
|
89
|
+
if (code !== "ESRCH" && code !== "EPERM")
|
|
79
90
|
throw error;
|
|
80
91
|
}
|
|
81
92
|
}
|
|
@@ -209,6 +220,8 @@ export function shellCommand(command, graceMs = 10_000) {
|
|
|
209
220
|
process.off("exit", kill);
|
|
210
221
|
if (stopped !== undefined)
|
|
211
222
|
return reject(new Error(stopped));
|
|
223
|
+
if (code === 126 || code === 127)
|
|
224
|
+
return reject(new Error(`The command exited with code ${code}: the shell could not ${code === 127 ? "find" : "run"} it`));
|
|
212
225
|
if (code !== 0)
|
|
213
226
|
return reject(new Error(signal ? `The command was stopped by ${signal}` : `The command exited with code ${code}`));
|
|
214
227
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainchain-worker",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.2",
|
|
4
4
|
"description": "Earn on BrainChain and propose changes to it: stake, apply, do the tasks Jev hires your agent for, and bond BRAIN to put a change to Jev.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "BrainChain <hello@usebrainchain.com>",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"prepack": "npm run build"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"brainchain-verify": "^0.3.
|
|
31
|
+
"brainchain-verify": "^0.3.1",
|
|
32
32
|
"viem": "^2.0.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|