@avow-finance/sdk 0.0.0-stage → 0.2.0
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/LICENSE +201 -0
- package/README.md +122 -2
- package/dist/agent-entry.d.ts +15 -0
- package/dist/agent-entry.d.ts.map +1 -0
- package/dist/agent-entry.js +11 -0
- package/dist/agent-entry.js.map +1 -0
- package/dist/agent-key.d.ts +34 -0
- package/dist/agent-key.d.ts.map +1 -0
- package/dist/agent-key.js +33 -0
- package/dist/agent-key.js.map +1 -0
- package/dist/agent.d.ts +261 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +493 -0
- package/dist/agent.js.map +1 -0
- package/dist/avow-api.d.ts +78 -0
- package/dist/avow-api.d.ts.map +1 -0
- package/dist/avow-api.js +2 -0
- package/dist/avow-api.js.map +1 -0
- package/dist/contest.d.ts +135 -0
- package/dist/contest.d.ts.map +1 -0
- package/dist/contest.js +186 -0
- package/dist/contest.js.map +1 -0
- package/dist/http.d.ts +161 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +311 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/job/accept.d.ts +84 -0
- package/dist/job/accept.d.ts.map +1 -0
- package/dist/job/accept.js +136 -0
- package/dist/job/accept.js.map +1 -0
- package/dist/job/customer.d.ts +43 -0
- package/dist/job/customer.d.ts.map +1 -0
- package/dist/job/customer.js +83 -0
- package/dist/job/customer.js.map +1 -0
- package/dist/job/quote.d.ts +34 -0
- package/dist/job/quote.d.ts.map +1 -0
- package/dist/job/quote.js +37 -0
- package/dist/job/quote.js.map +1 -0
- package/dist/lock.d.ts +269 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lock.js +283 -0
- package/dist/lock.js.map +1 -0
- package/dist/moderato.d.ts +27 -0
- package/dist/moderato.d.ts.map +1 -0
- package/dist/moderato.js +34 -0
- package/dist/moderato.js.map +1 -0
- package/dist/node/cli.d.ts +3 -0
- package/dist/node/cli.d.ts.map +1 -0
- package/dist/node/cli.js +346 -0
- package/dist/node/cli.js.map +1 -0
- package/dist/node/connect.d.ts +15 -0
- package/dist/node/connect.d.ts.map +1 -0
- package/dist/node/connect.js +91 -0
- package/dist/node/connect.js.map +1 -0
- package/dist/node/eval.d.ts +3 -0
- package/dist/node/eval.d.ts.map +1 -0
- package/dist/node/eval.js +67 -0
- package/dist/node/eval.js.map +1 -0
- package/dist/node/first-run.d.ts +50 -0
- package/dist/node/first-run.d.ts.map +1 -0
- package/dist/node/first-run.js +149 -0
- package/dist/node/first-run.js.map +1 -0
- package/dist/node/job.d.ts +50 -0
- package/dist/node/job.d.ts.map +1 -0
- package/dist/node/job.js +176 -0
- package/dist/node/job.js.map +1 -0
- package/dist/node/mailbox.d.ts +19 -0
- package/dist/node/mailbox.d.ts.map +1 -0
- package/dist/node/mailbox.js +81 -0
- package/dist/node/mailbox.js.map +1 -0
- package/dist/node/mcp.d.ts +74 -0
- package/dist/node/mcp.d.ts.map +1 -0
- package/dist/node/mcp.js +367 -0
- package/dist/node/mcp.js.map +1 -0
- package/dist/node/platform.d.ts +35 -0
- package/dist/node/platform.d.ts.map +1 -0
- package/dist/node/platform.js +102 -0
- package/dist/node/platform.js.map +1 -0
- package/dist/node/runner-session.d.ts +41 -0
- package/dist/node/runner-session.d.ts.map +1 -0
- package/dist/node/runner-session.js +100 -0
- package/dist/node/runner-session.js.map +1 -0
- package/dist/node/runner.d.ts +63 -0
- package/dist/node/runner.d.ts.map +1 -0
- package/dist/node/runner.js +341 -0
- package/dist/node/runner.js.map +1 -0
- package/dist/node/service-child.d.ts +2 -0
- package/dist/node/service-child.d.ts.map +1 -0
- package/dist/node/service-child.js +40 -0
- package/dist/node/service-child.js.map +1 -0
- package/dist/node/service-excerpt.d.ts +21 -0
- package/dist/node/service-excerpt.d.ts.map +1 -0
- package/dist/node/service-excerpt.js +32 -0
- package/dist/node/service-excerpt.js.map +1 -0
- package/dist/node/service-process.d.ts +12 -0
- package/dist/node/service-process.d.ts.map +1 -0
- package/dist/node/service-process.js +132 -0
- package/dist/node/service-process.js.map +1 -0
- package/dist/node/session-input.d.ts +12 -0
- package/dist/node/session-input.d.ts.map +1 -0
- package/dist/node/session-input.js +42 -0
- package/dist/node/session-input.js.map +1 -0
- package/dist/node/session-view.d.ts +20 -0
- package/dist/node/session-view.d.ts.map +1 -0
- package/dist/node/session-view.js +198 -0
- package/dist/node/session-view.js.map +1 -0
- package/dist/node-agent.d.ts +2 -0
- package/dist/node-agent.d.ts.map +1 -0
- package/dist/node-agent.js +6 -0
- package/dist/node-agent.js.map +1 -0
- package/dist/node.d.ts +2 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +9 -0
- package/dist/node.js.map +1 -0
- package/dist/sandbox/agent.d.ts +187 -0
- package/dist/sandbox/agent.d.ts.map +1 -0
- package/dist/sandbox/agent.js +468 -0
- package/dist/sandbox/agent.js.map +1 -0
- package/dist/sandbox/business-store.d.ts +19 -0
- package/dist/sandbox/business-store.d.ts.map +1 -0
- package/dist/sandbox/business-store.js +19 -0
- package/dist/sandbox/business-store.js.map +1 -0
- package/dist/sandbox/business.d.ts +70 -0
- package/dist/sandbox/business.d.ts.map +1 -0
- package/dist/sandbox/business.js +115 -0
- package/dist/sandbox/business.js.map +1 -0
- package/dist/sandbox/connect.d.ts +64 -0
- package/dist/sandbox/connect.d.ts.map +1 -0
- package/dist/sandbox/connect.js +182 -0
- package/dist/sandbox/connect.js.map +1 -0
- package/dist/sandbox/deployment.d.ts +14 -0
- package/dist/sandbox/deployment.d.ts.map +1 -0
- package/dist/sandbox/deployment.js +38 -0
- package/dist/sandbox/deployment.js.map +1 -0
- package/dist/sandbox/keys.d.ts +45 -0
- package/dist/sandbox/keys.d.ts.map +1 -0
- package/dist/sandbox/keys.js +132 -0
- package/dist/sandbox/keys.js.map +1 -0
- package/dist/sandbox/mailbox.d.ts +90 -0
- package/dist/sandbox/mailbox.d.ts.map +1 -0
- package/dist/sandbox/mailbox.js +400 -0
- package/dist/sandbox/mailbox.js.map +1 -0
- package/dist/sandbox/mandate.d.ts +84 -0
- package/dist/sandbox/mandate.d.ts.map +1 -0
- package/dist/sandbox/mandate.js +107 -0
- package/dist/sandbox/mandate.js.map +1 -0
- package/dist/sandbox/namespace.d.ts +16 -0
- package/dist/sandbox/namespace.d.ts.map +1 -0
- package/dist/sandbox/namespace.js +16 -0
- package/dist/sandbox/namespace.js.map +1 -0
- package/dist/sandbox/session.d.ts +60 -0
- package/dist/sandbox/session.d.ts.map +1 -0
- package/dist/sandbox/session.js +94 -0
- package/dist/sandbox/session.js.map +1 -0
- package/dist/sandbox/setup.d.ts +136 -0
- package/dist/sandbox/setup.d.ts.map +1 -0
- package/dist/sandbox/setup.js +490 -0
- package/dist/sandbox/setup.js.map +1 -0
- package/dist/sandbox/store.d.ts +69 -0
- package/dist/sandbox/store.d.ts.map +1 -0
- package/dist/sandbox/store.js +115 -0
- package/dist/sandbox/store.js.map +1 -0
- package/dist/sandbox/supplier-key.d.ts +73 -0
- package/dist/sandbox/supplier-key.d.ts.map +1 -0
- package/dist/sandbox/supplier-key.js +180 -0
- package/dist/sandbox/supplier-key.js.map +1 -0
- package/dist/sandbox/suppliers.d.ts +7 -0
- package/dist/sandbox/suppliers.d.ts.map +1 -0
- package/dist/sandbox/suppliers.js +29 -0
- package/dist/sandbox/suppliers.js.map +1 -0
- package/dist/sandbox/terms.d.ts +32 -0
- package/dist/sandbox/terms.d.ts.map +1 -0
- package/dist/sandbox/terms.js +58 -0
- package/dist/sandbox/terms.js.map +1 -0
- package/dist/sandbox/vault.d.ts +24 -0
- package/dist/sandbox/vault.d.ts.map +1 -0
- package/dist/sandbox/vault.js +54 -0
- package/dist/sandbox/vault.js.map +1 -0
- package/dist/send.d.ts +122 -0
- package/dist/send.d.ts.map +1 -0
- package/dist/send.js +328 -0
- package/dist/send.js.map +1 -0
- package/dist/service-entry.d.ts +6 -0
- package/dist/service-entry.d.ts.map +1 -0
- package/dist/service-entry.js +6 -0
- package/dist/service-entry.js.map +1 -0
- package/dist/session/anthropic.d.ts +17 -0
- package/dist/session/anthropic.d.ts.map +1 -0
- package/dist/session/anthropic.js +215 -0
- package/dist/session/anthropic.js.map +1 -0
- package/dist/session/charge.d.ts +13 -0
- package/dist/session/charge.d.ts.map +1 -0
- package/dist/session/charge.js +33 -0
- package/dist/session/charge.js.map +1 -0
- package/dist/session/data.d.ts +8 -0
- package/dist/session/data.d.ts.map +1 -0
- package/dist/session/data.js +20 -0
- package/dist/session/data.js.map +1 -0
- package/dist/session/eval.d.ts +41 -0
- package/dist/session/eval.d.ts.map +1 -0
- package/dist/session/eval.js +281 -0
- package/dist/session/eval.js.map +1 -0
- package/dist/session/executor.d.ts +80 -0
- package/dist/session/executor.d.ts.map +1 -0
- package/dist/session/executor.js +381 -0
- package/dist/session/executor.js.map +1 -0
- package/dist/session/fixture-world.d.ts +29 -0
- package/dist/session/fixture-world.d.ts.map +1 -0
- package/dist/session/fixture-world.js +708 -0
- package/dist/session/fixture-world.js.map +1 -0
- package/dist/session/live-world.d.ts +22 -0
- package/dist/session/live-world.d.ts.map +1 -0
- package/dist/session/live-world.js +329 -0
- package/dist/session/live-world.js.map +1 -0
- package/dist/session/ollama.d.ts +29 -0
- package/dist/session/ollama.d.ts.map +1 -0
- package/dist/session/ollama.js +177 -0
- package/dist/session/ollama.js.map +1 -0
- package/dist/session/provider.d.ts +79 -0
- package/dist/session/provider.d.ts.map +1 -0
- package/dist/session/provider.js +19 -0
- package/dist/session/provider.js.map +1 -0
- package/dist/session/scripted.d.ts +45 -0
- package/dist/session/scripted.d.ts.map +1 -0
- package/dist/session/scripted.js +236 -0
- package/dist/session/scripted.js.map +1 -0
- package/dist/session/selection.d.ts +37 -0
- package/dist/session/selection.d.ts.map +1 -0
- package/dist/session/selection.js +104 -0
- package/dist/session/selection.js.map +1 -0
- package/dist/session/service-check.d.ts +48 -0
- package/dist/session/service-check.d.ts.map +1 -0
- package/dist/session/service-check.js +86 -0
- package/dist/session/service-check.js.map +1 -0
- package/dist/session/service.d.ts +128 -0
- package/dist/session/service.d.ts.map +1 -0
- package/dist/session/service.js +150 -0
- package/dist/session/service.js.map +1 -0
- package/dist/session/session.d.ts +95 -0
- package/dist/session/session.d.ts.map +1 -0
- package/dist/session/session.js +312 -0
- package/dist/session/session.js.map +1 -0
- package/dist/session/toolbox.d.ts +69 -0
- package/dist/session/toolbox.d.ts.map +1 -0
- package/dist/session/toolbox.js +391 -0
- package/dist/session/toolbox.js.map +1 -0
- package/dist/session/tools.d.ts +45 -0
- package/dist/session/tools.d.ts.map +1 -0
- package/dist/session/tools.js +137 -0
- package/dist/session/tools.js.map +1 -0
- package/dist/session/world.d.ts +188 -0
- package/dist/session/world.d.ts.map +1 -0
- package/dist/session/world.js +2 -0
- package/dist/session/world.js.map +1 -0
- package/dist/sign.d.ts +19 -0
- package/dist/sign.d.ts.map +1 -0
- package/dist/sign.js +33 -0
- package/dist/sign.js.map +1 -0
- package/dist/supplier.d.ts +41 -0
- package/dist/supplier.d.ts.map +1 -0
- package/dist/supplier.js +57 -0
- package/dist/supplier.js.map +1 -0
- package/package.json +56 -3
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { isSessionId, newSessionId } from '@avow-finance/spec';
|
|
4
|
+
import { isAddressEqual } from 'viem';
|
|
5
|
+
import { readSessionStanding } from '../sandbox/session.js';
|
|
6
|
+
import { takenUpTo } from './platform.js';
|
|
7
|
+
/** How long before Avow's relay closes a session the runner moves to a new one, in seconds. */
|
|
8
|
+
export const SESSION_MARGIN = 600n;
|
|
9
|
+
const systemClock = () => BigInt(Math.floor(Date.now() / 1000));
|
|
10
|
+
/** The session id kept in the sandbox folder, or null when there's none in the form it's kept. */
|
|
11
|
+
function keptId(dir) {
|
|
12
|
+
try {
|
|
13
|
+
const { id } = JSON.parse(readFileSync(join(dir, 'session.json'), 'utf8'));
|
|
14
|
+
return typeof id === 'string' && isSessionId(id) ? id : null;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Keeps a session in the sandbox folder, with the runner's process id for the MCP server to find,
|
|
22
|
+
* clears the run numbers of every other session, and goes on from the relay's last run.
|
|
23
|
+
*/
|
|
24
|
+
function useSession(dir, id, lastRun) {
|
|
25
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
26
|
+
writeFileSync(join(dir, 'session.json'), `${JSON.stringify({ id, pid: process.pid })}\n`, {
|
|
27
|
+
mode: 0o600,
|
|
28
|
+
});
|
|
29
|
+
try {
|
|
30
|
+
for (const name of readdirSync(join(dir, 'runs'))) {
|
|
31
|
+
if (name !== id)
|
|
32
|
+
rmSync(join(dir, 'runs', name), { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// No run has been numbered yet.
|
|
37
|
+
}
|
|
38
|
+
takenUpTo(dir, id, lastRun);
|
|
39
|
+
}
|
|
40
|
+
function newSession(dir) {
|
|
41
|
+
const id = newSessionId();
|
|
42
|
+
useSession(dir, id, 0);
|
|
43
|
+
return { id, fresh: true, endsAt: null };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The session the runner reports to, kept in the sandbox folder with the business, so the screen
|
|
47
|
+
* a developer has open keeps working across restarts. A restart keeps it while Avow's relay holds
|
|
48
|
+
* it for the business's account and takes its events for more than `SESSION_MARGIN`. On a first
|
|
49
|
+
* run, after `npm run fresh`, or once a session has closed, it's a new session, and the runner
|
|
50
|
+
* announces the agent's authority to it.
|
|
51
|
+
*
|
|
52
|
+
* The account comes from business.json, and nothing checks it here, since that takes the
|
|
53
|
+
* passphrase. The sealed key opens only for the account it was sealed for, so a file that names
|
|
54
|
+
* another business's account can make the runner keep that business's session, but nothing the
|
|
55
|
+
* runner signs can reach it, and Avow's relay refuses this business's agent's events there.
|
|
56
|
+
*/
|
|
57
|
+
export async function keepSession(m) {
|
|
58
|
+
const now = m.now ?? systemClock;
|
|
59
|
+
const kept = keptId(m.dir);
|
|
60
|
+
if (!kept || !m.account)
|
|
61
|
+
return newSession(m.dir);
|
|
62
|
+
const standing = await readSessionStanding({
|
|
63
|
+
apiUrl: m.apiUrl,
|
|
64
|
+
sessionId: kept,
|
|
65
|
+
...(m.fetch ? { fetch: m.fetch } : {}),
|
|
66
|
+
}).catch(() => undefined);
|
|
67
|
+
if (standing === undefined) {
|
|
68
|
+
m.log?.("Avow's relay didn't say whether your session is still open, so the runner keeps it.");
|
|
69
|
+
useSession(m.dir, kept, 0);
|
|
70
|
+
return { id: kept, fresh: false, endsAt: null };
|
|
71
|
+
}
|
|
72
|
+
const ours = standing.business !== null &&
|
|
73
|
+
isAddressEqual(standing.business.account, m.account) &&
|
|
74
|
+
standing.endsAt !== null &&
|
|
75
|
+
standing.endsAt > now() + SESSION_MARGIN;
|
|
76
|
+
if (!ours)
|
|
77
|
+
return newSession(m.dir);
|
|
78
|
+
useSession(m.dir, kept, standing.lastRun);
|
|
79
|
+
return { id: kept, fresh: false, endsAt: standing.endsAt };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The session for the next run, the same one until Avow's relay is about to stop taking its
|
|
83
|
+
* events. Then it's a new one, which the runner announces the authority to again.
|
|
84
|
+
*/
|
|
85
|
+
export async function sessionForNextRun(session, m) {
|
|
86
|
+
const now = m.now ?? systemClock;
|
|
87
|
+
let { endsAt } = session;
|
|
88
|
+
if (endsAt === null) {
|
|
89
|
+
const standing = await readSessionStanding({
|
|
90
|
+
apiUrl: m.apiUrl,
|
|
91
|
+
sessionId: session.id,
|
|
92
|
+
...(m.fetch ? { fetch: m.fetch } : {}),
|
|
93
|
+
}).catch(() => undefined);
|
|
94
|
+
endsAt = standing?.endsAt ?? null;
|
|
95
|
+
}
|
|
96
|
+
if (endsAt !== null && endsAt <= now() + SESSION_MARGIN)
|
|
97
|
+
return newSession(m.dir);
|
|
98
|
+
return { ...session, endsAt };
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=runner-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner-session.js","sourceRoot":"","sources":["../../src/node/runner-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { SandboxSession } from '../sandbox/agent.js';
|
|
2
|
+
import type { SandboxBusinessConfig } from '../sandbox/setup.js';
|
|
3
|
+
/** How to run a file: TypeScript through the project's own tsx, JavaScript through Node. */
|
|
4
|
+
export declare function command(file: string, cwd?: string): string[];
|
|
5
|
+
/**
|
|
6
|
+
* The environment a child process gets. It's the runner's own, with every `AVOW_` variable taken
|
|
7
|
+
* out and only the session and run put back. The business's passphrase and key are never in it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function childEnv(session: SandboxSession | null): NodeJS.ProcessEnv;
|
|
10
|
+
/** Whether any process the runner started, or one of theirs, is still alive. */
|
|
11
|
+
export declare function childrenAlive(): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Waits for every process the runner started to be gone, and refuses when one outlives the wait.
|
|
14
|
+
* The runner asks for the passphrase only after this. A process that left the group it was started
|
|
15
|
+
* in isn't seen here, so `checkTerminal` looks for anything holding the terminal too.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ensureChildrenGone(waitMs?: number): Promise<void>;
|
|
18
|
+
/** A process that has the runner's terminal open. */
|
|
19
|
+
export interface TerminalHolder {
|
|
20
|
+
pid: number;
|
|
21
|
+
command: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Every process apart from the runner and the ones that started it, such as npm and the shell, that
|
|
25
|
+
* has the runner's terminal open. Any of them could read what's typed there. It's null when this
|
|
26
|
+
* machine gives no way to tell, as on Windows, without a terminal, or without `lsof` outside Linux.
|
|
27
|
+
*/
|
|
28
|
+
export declare function otherTerminalHolders(): TerminalHolder[] | null;
|
|
29
|
+
/**
|
|
30
|
+
* Checks the terminal before the runner asks for the passphrase. It refuses when it finds a process,
|
|
31
|
+
* apart from the runner and the ones that started it, holding the terminal open, and says so plainly
|
|
32
|
+
* where it can't look. It skips processes whose open files it can't read, and nothing stops a program
|
|
33
|
+
* running as the developer from opening the terminal later, so this closes what it can, and the
|
|
34
|
+
* business's key stays off the agent's machine in production.
|
|
35
|
+
*/
|
|
36
|
+
export declare function checkTerminal(log?: (line: string) => void): void;
|
|
37
|
+
/**
|
|
38
|
+
* Runs the agent's file once, isolated as `runIsolated` runs it, with only the session in its
|
|
39
|
+
* environment. It reads the agent's item, and it gets no input, so it can't be asked for anything.
|
|
40
|
+
*/
|
|
41
|
+
export declare function runAgent(file: string, session: SandboxSession | null, options?: {
|
|
42
|
+
cwd?: string;
|
|
43
|
+
capture?: boolean;
|
|
44
|
+
}): Promise<{
|
|
45
|
+
code: number;
|
|
46
|
+
output: string;
|
|
47
|
+
}>;
|
|
48
|
+
/**
|
|
49
|
+
* Prompts on the terminal, a line at a time. A passphrase is typed with nothing shown, and no line
|
|
50
|
+
* is kept in history, so no later prompt can bring one back.
|
|
51
|
+
*/
|
|
52
|
+
export declare function terminalPrompts(input?: NodeJS.ReadableStream, output?: NodeJS.WritableStream, tty?: boolean): {
|
|
53
|
+
ask: (prompt: string, hidden?: boolean) => Promise<string | null>;
|
|
54
|
+
onLine: (listener: (line: string) => void) => void;
|
|
55
|
+
print: (text: string) => void;
|
|
56
|
+
history: () => string[];
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Reads the business's config from its file, isolated as the agent runs, so nothing in the file
|
|
60
|
+
* runs where the business's key is ever opened. The file's default export is the config.
|
|
61
|
+
*/
|
|
62
|
+
export declare function readBusinessConfig(file: string, cwd?: string): Promise<SandboxBusinessConfig>;
|
|
63
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/node/runner.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAGhE,4FAA4F;AAC5F,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAsB,GAAG,MAAM,EAAE,CAO3E;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,GAAG,MAAM,CAAC,UAAU,CAU1E;AAkBD,gFAAgF;AAChF,wBAAgB,aAAa,IAAI,OAAO,CAUvC;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrE;AAED,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;CAChB;AAmDD;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,cAAc,EAAE,GAAG,IAAI,CA8B9D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAkB,GAAG,IAAI,CAc7E;AAyCD;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,cAAc,GAAG,IAAI,EAC9B,OAAO,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAChD,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAO3C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,GAAE,MAAM,CAAC,cAA8B,EAC5C,MAAM,GAAE,MAAM,CAAC,cAA+B,EAC9C,GAAG,GAAE,OAAsC;kBAkBtB,MAAM;uBAsCD,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI;kBAX3B,MAAM;;EAoB5B;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,qBAAqB,CAAC,CAsBhC"}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { mkdtempSync, readdirSync, readFileSync, readlinkSync, rmSync, writeFileSync, } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
import { createInterface } from 'node:readline';
|
|
7
|
+
import { Writable } from 'node:stream';
|
|
8
|
+
import { pathToFileURL } from 'node:url';
|
|
9
|
+
import { RUNNER_ENV } from './platform.js';
|
|
10
|
+
/** How to run a file: TypeScript through the project's own tsx, JavaScript through Node. */
|
|
11
|
+
export function command(file, cwd = process.cwd()) {
|
|
12
|
+
if (!/\.[cm]?tsx?$/.test(file))
|
|
13
|
+
return [file];
|
|
14
|
+
try {
|
|
15
|
+
return [createRequire(join(cwd, 'package.json')).resolve('tsx/cli'), file];
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new Error(`Running ${file} needs tsx. Install it with npm install tsx.`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The environment a child process gets. It's the runner's own, with every `AVOW_` variable taken
|
|
23
|
+
* out and only the session and run put back. The business's passphrase and key are never in it.
|
|
24
|
+
*/
|
|
25
|
+
export function childEnv(session) {
|
|
26
|
+
const env = {};
|
|
27
|
+
for (const [name, value] of Object.entries(process.env)) {
|
|
28
|
+
if (!name.startsWith('AVOW_'))
|
|
29
|
+
env[name] = value;
|
|
30
|
+
}
|
|
31
|
+
if (session) {
|
|
32
|
+
env[RUNNER_ENV.session] = session.id;
|
|
33
|
+
env[RUNNER_ENV.run] = String(session.run);
|
|
34
|
+
}
|
|
35
|
+
return env;
|
|
36
|
+
}
|
|
37
|
+
/** The process groups the runner started, until each is gone. */
|
|
38
|
+
const groups = new Set();
|
|
39
|
+
/** Ends a child's whole process group, so nothing it left behind keeps running. */
|
|
40
|
+
function killGroup(pid) {
|
|
41
|
+
if (process.platform === 'win32') {
|
|
42
|
+
spawnSync('taskkill', ['/pid', String(pid), '/T', '/F'], { stdio: 'ignore' });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
process.kill(-pid, 'SIGKILL');
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// The group is already gone.
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Whether any process the runner started, or one of theirs, is still alive. */
|
|
53
|
+
export function childrenAlive() {
|
|
54
|
+
for (const pid of groups) {
|
|
55
|
+
try {
|
|
56
|
+
process.kill(process.platform === 'win32' ? pid : -pid, 0);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
groups.delete(pid);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Waits for every process the runner started to be gone, and refuses when one outlives the wait.
|
|
67
|
+
* The runner asks for the passphrase only after this. A process that left the group it was started
|
|
68
|
+
* in isn't seen here, so `checkTerminal` looks for anything holding the terminal too.
|
|
69
|
+
*/
|
|
70
|
+
export async function ensureChildrenGone(waitMs = 2000) {
|
|
71
|
+
const deadline = Date.now() + waitMs;
|
|
72
|
+
while (childrenAlive()) {
|
|
73
|
+
for (const pid of groups)
|
|
74
|
+
killGroup(pid);
|
|
75
|
+
if (Date.now() > deadline) {
|
|
76
|
+
throw new Error("A process the agent started is still running, so the runner won't ask for the passphrase. Stop it, then press Enter again.");
|
|
77
|
+
}
|
|
78
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** The device of the terminal the runner reads from, or null when it reads from something else. */
|
|
82
|
+
function terminalDevice() {
|
|
83
|
+
if (!process.stdin.isTTY)
|
|
84
|
+
return null;
|
|
85
|
+
const found = spawnSync('tty', [], { stdio: [0, 'pipe', 'ignore'], encoding: 'utf8' });
|
|
86
|
+
const device = found.status === 0 ? found.stdout.trim() : '';
|
|
87
|
+
return device.startsWith('/dev/') ? device : null;
|
|
88
|
+
}
|
|
89
|
+
/** Each process on Linux this user can see, with its parent, its command and the files it holds. */
|
|
90
|
+
function linuxProcesses() {
|
|
91
|
+
return readdirSync('/proc')
|
|
92
|
+
.filter((name) => /^\d+$/.test(name))
|
|
93
|
+
.flatMap((name) => {
|
|
94
|
+
try {
|
|
95
|
+
const stat = readFileSync(`/proc/${name}/stat`, 'utf8');
|
|
96
|
+
const ppid = Number(stat.slice(stat.lastIndexOf(')') + 2).split(' ')[1]);
|
|
97
|
+
const command = readFileSync(`/proc/${name}/comm`, 'utf8').trim();
|
|
98
|
+
let files = [];
|
|
99
|
+
try {
|
|
100
|
+
files = readdirSync(`/proc/${name}/fd`).flatMap((fd) => {
|
|
101
|
+
try {
|
|
102
|
+
return [readlinkSync(`/proc/${name}/fd/${fd}`)];
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Another user's process, whose files this user can't see.
|
|
111
|
+
}
|
|
112
|
+
return [{ pid: Number(name), ppid, command, files }];
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/** Each process `ps` lists, with its parent and its command. */
|
|
120
|
+
function psProcesses() {
|
|
121
|
+
const listed = spawnSync('ps', ['-A', '-o', 'pid=,ppid=,comm='], {
|
|
122
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
123
|
+
encoding: 'utf8',
|
|
124
|
+
});
|
|
125
|
+
if (listed.status !== 0)
|
|
126
|
+
return null;
|
|
127
|
+
return listed.stdout.split('\n').flatMap((line) => {
|
|
128
|
+
const m = /^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(line);
|
|
129
|
+
return m ? [{ pid: Number(m[1]), ppid: Number(m[2]), command: (m[3] ?? '').trim() }] : [];
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Every process apart from the runner and the ones that started it, such as npm and the shell, that
|
|
134
|
+
* has the runner's terminal open. Any of them could read what's typed there. It's null when this
|
|
135
|
+
* machine gives no way to tell, as on Windows, without a terminal, or without `lsof` outside Linux.
|
|
136
|
+
*/
|
|
137
|
+
export function otherTerminalHolders() {
|
|
138
|
+
if (process.platform === 'win32')
|
|
139
|
+
return null;
|
|
140
|
+
const device = terminalDevice();
|
|
141
|
+
if (!device)
|
|
142
|
+
return null;
|
|
143
|
+
let processes;
|
|
144
|
+
let holders;
|
|
145
|
+
if (process.platform === 'linux') {
|
|
146
|
+
const seen = linuxProcesses();
|
|
147
|
+
processes = seen;
|
|
148
|
+
holders = seen.filter((p) => p.files.includes(device)).map((p) => p.pid);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const listed = psProcesses();
|
|
152
|
+
const open = spawnSync('lsof', ['-t', device], {
|
|
153
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
154
|
+
encoding: 'utf8',
|
|
155
|
+
});
|
|
156
|
+
// lsof answers 1 when nothing has the file open.
|
|
157
|
+
if (!listed || open.error || (open.status !== 0 && open.status !== 1))
|
|
158
|
+
return null;
|
|
159
|
+
processes = listed;
|
|
160
|
+
holders = open.stdout.split('\n').flatMap((l) => (/^\d+$/.test(l.trim()) ? [Number(l)] : []));
|
|
161
|
+
}
|
|
162
|
+
const parent = new Map(processes.map((p) => [p.pid, p.ppid]));
|
|
163
|
+
const allowed = new Set();
|
|
164
|
+
for (let pid = process.pid; pid && !allowed.has(pid); pid = parent.get(pid)) {
|
|
165
|
+
allowed.add(pid);
|
|
166
|
+
}
|
|
167
|
+
const command = new Map(processes.map((p) => [p.pid, p.command]));
|
|
168
|
+
return [...new Set(holders)]
|
|
169
|
+
.filter((pid) => !allowed.has(pid))
|
|
170
|
+
.map((pid) => ({ pid, command: command.get(pid) ?? 'unknown' }));
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Checks the terminal before the runner asks for the passphrase. It refuses when it finds a process,
|
|
174
|
+
* apart from the runner and the ones that started it, holding the terminal open, and says so plainly
|
|
175
|
+
* where it can't look. It skips processes whose open files it can't read, and nothing stops a program
|
|
176
|
+
* running as the developer from opening the terminal later, so this closes what it can, and the
|
|
177
|
+
* business's key stays off the agent's machine in production.
|
|
178
|
+
*/
|
|
179
|
+
export function checkTerminal(log = console.log) {
|
|
180
|
+
const others = otherTerminalHolders();
|
|
181
|
+
if (others === null) {
|
|
182
|
+
log("The runner can't see what else has this terminal open on this machine. Anything running as you can read what you type, so make sure nothing your agent started is still running.");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (others.length > 0) {
|
|
186
|
+
const named = others.map((p) => `${p.command} (process ${p.pid})`).join(', ');
|
|
187
|
+
throw new Error(`${named} ${others.length === 1 ? 'has' : 'have'} this terminal open and could read what you type, so the runner won't ask for the passphrase. Stop ${others.length === 1 ? 'it' : 'them'}, or run npm start in a new terminal, then press Enter.`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Runs a file in a session of its own with no controlling terminal, its input closed and its output
|
|
192
|
+
* passed through pipes, so the runner hands it no way to the terminal the passphrase is typed in.
|
|
193
|
+
* Running as the developer, it could still open the terminal's device itself, which `checkTerminal`
|
|
194
|
+
* looks for. Once it exits, its whole process group is ended.
|
|
195
|
+
*/
|
|
196
|
+
function runIsolated(args, options) {
|
|
197
|
+
return new Promise((done) => {
|
|
198
|
+
const child = spawn(process.execPath, args, {
|
|
199
|
+
cwd: options.cwd,
|
|
200
|
+
env: options.env,
|
|
201
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
202
|
+
detached: true,
|
|
203
|
+
windowsHide: true,
|
|
204
|
+
});
|
|
205
|
+
if (child.pid)
|
|
206
|
+
groups.add(child.pid);
|
|
207
|
+
let output = '';
|
|
208
|
+
let code = 1;
|
|
209
|
+
child.stdout.on('data', (chunk) => {
|
|
210
|
+
if (options.capture)
|
|
211
|
+
output += chunk.toString();
|
|
212
|
+
else
|
|
213
|
+
process.stdout.write(chunk);
|
|
214
|
+
});
|
|
215
|
+
child.stderr.on('data', (chunk) => {
|
|
216
|
+
process.stderr.write(chunk);
|
|
217
|
+
});
|
|
218
|
+
child.on('error', (error) => {
|
|
219
|
+
process.stderr.write(`${error.message}\n`);
|
|
220
|
+
});
|
|
221
|
+
child.on('exit', (exitCode) => {
|
|
222
|
+
code = exitCode ?? 1;
|
|
223
|
+
if (child.pid)
|
|
224
|
+
killGroup(child.pid);
|
|
225
|
+
});
|
|
226
|
+
child.on('close', () => done({ code, output }));
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Runs the agent's file once, isolated as `runIsolated` runs it, with only the session in its
|
|
231
|
+
* environment. It reads the agent's item, and it gets no input, so it can't be asked for anything.
|
|
232
|
+
*/
|
|
233
|
+
export function runAgent(file, session, options = {}) {
|
|
234
|
+
const cwd = options.cwd ?? process.cwd();
|
|
235
|
+
return runIsolated(command(file, cwd), {
|
|
236
|
+
cwd,
|
|
237
|
+
env: childEnv(session),
|
|
238
|
+
capture: options.capture ?? false,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Prompts on the terminal, a line at a time. A passphrase is typed with nothing shown, and no line
|
|
243
|
+
* is kept in history, so no later prompt can bring one back.
|
|
244
|
+
*/
|
|
245
|
+
export function terminalPrompts(input = process.stdin, output = process.stdout, tty = Boolean(process.stdin.isTTY)) {
|
|
246
|
+
let muted = false;
|
|
247
|
+
const shown = new Writable({
|
|
248
|
+
write(chunk, encoding, done) {
|
|
249
|
+
if (!muted)
|
|
250
|
+
output.write(chunk, encoding);
|
|
251
|
+
done();
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
const lines = createInterface({ input, output: shown, terminal: tty, historySize: 0 });
|
|
255
|
+
let closed = false;
|
|
256
|
+
/** Whether a visible question's prompt is on the screen, waiting for its answer. */
|
|
257
|
+
let asking = false;
|
|
258
|
+
const waiting = new Set();
|
|
259
|
+
lines.on('close', () => {
|
|
260
|
+
closed = true;
|
|
261
|
+
for (const resolve of waiting)
|
|
262
|
+
resolve(null);
|
|
263
|
+
});
|
|
264
|
+
const ask = (prompt, hidden = false) => new Promise((resolve) => {
|
|
265
|
+
if (closed)
|
|
266
|
+
return resolve(null);
|
|
267
|
+
waiting.add(resolve);
|
|
268
|
+
const answered = (answer) => {
|
|
269
|
+
muted = false;
|
|
270
|
+
asking = false;
|
|
271
|
+
waiting.delete(resolve);
|
|
272
|
+
if (hidden && tty)
|
|
273
|
+
output.write('\n');
|
|
274
|
+
resolve(answer);
|
|
275
|
+
};
|
|
276
|
+
// A hidden answer's prompt is written before the output is muted, so nothing typed shows. A
|
|
277
|
+
// visible one's goes to readline, which draws it again whenever it redraws the line.
|
|
278
|
+
if (hidden) {
|
|
279
|
+
output.write(prompt);
|
|
280
|
+
muted = true;
|
|
281
|
+
lines.question('', answered);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
asking = true;
|
|
285
|
+
lines.question(prompt, answered);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
/**
|
|
289
|
+
* Writes output that arrives while a question may be waiting, such as the customer's reply while
|
|
290
|
+
* the developer types to the agent. The prompt's row is cleared first and drawn again under the
|
|
291
|
+
* output with whatever was typed so far, so output and typing never share a row.
|
|
292
|
+
*/
|
|
293
|
+
const print = (text) => {
|
|
294
|
+
if (!asking || !tty || closed) {
|
|
295
|
+
output.write(text);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
output.write(`\r\u001b[2K${text}`);
|
|
299
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: the rail's autowrap switches
|
|
300
|
+
const bare = text.replace(/\u001b\[\?7[lh]/g, '');
|
|
301
|
+
if (bare.endsWith('\n') || bare === '\r\u001b[2K')
|
|
302
|
+
lines.prompt(true);
|
|
303
|
+
};
|
|
304
|
+
/** Each line typed while no question waits for it, such as one typed to a busy agent. */
|
|
305
|
+
const onLine = (listener) => {
|
|
306
|
+
lines.on('line', listener);
|
|
307
|
+
};
|
|
308
|
+
return {
|
|
309
|
+
ask,
|
|
310
|
+
onLine,
|
|
311
|
+
print,
|
|
312
|
+
history: () => [...(lines.history ?? [])],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Reads the business's config from its file, isolated as the agent runs, so nothing in the file
|
|
317
|
+
* runs where the business's key is ever opened. The file's default export is the config.
|
|
318
|
+
*/
|
|
319
|
+
export async function readBusinessConfig(file, cwd = process.cwd()) {
|
|
320
|
+
const dir = mkdtempSync(join(tmpdir(), 'avow-business-'));
|
|
321
|
+
const loader = join(dir, 'read.mjs');
|
|
322
|
+
writeFileSync(loader, 'const m = await import(process.argv[2]); process.stdout.write(JSON.stringify(m.default ?? null))\n');
|
|
323
|
+
const url = pathToFileURL(resolve(cwd, file)).href;
|
|
324
|
+
const [first, ...rest] = command(file, cwd);
|
|
325
|
+
const args = first === file ? [loader, url] : [first, loader, url, ...rest.slice(1)];
|
|
326
|
+
try {
|
|
327
|
+
const { code, output } = await runIsolated(args, { cwd, env: childEnv(null), capture: true });
|
|
328
|
+
if (code !== 0)
|
|
329
|
+
throw new Error(`${file} didn't load, so nothing was signed.`);
|
|
330
|
+
const text = output;
|
|
331
|
+
const config = JSON.parse(text);
|
|
332
|
+
if (typeof config !== 'object' || config === null) {
|
|
333
|
+
throw new Error(`${file} has to export its business as its default export.`);
|
|
334
|
+
}
|
|
335
|
+
return config;
|
|
336
|
+
}
|
|
337
|
+
finally {
|
|
338
|
+
rmSync(dir, { recursive: true, force: true });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/node/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-child.d.ts","sourceRoot":"","sources":["../../src/node/service-child.ts"],"names":[],"mappings":"AA6BA,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB1F"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { toWire } from '@avow-finance/spec';
|
|
3
|
+
import { readBacking, tempoLockReader } from '@avow-finance/verifier';
|
|
4
|
+
import { createPublicClient, http } from 'viem';
|
|
5
|
+
import { decodeData } from '../session/data.js';
|
|
6
|
+
import { isService } from '../session/service.js';
|
|
7
|
+
import { serviceChecks, snapshotChain } from '../session/service-check.js';
|
|
8
|
+
/**
|
|
9
|
+
* What runs in the developer's service process, which holds no key. It loads `service.ts`, then
|
|
10
|
+
* either writes its budget and switch, or runs `checkBeforeWork` on the job it was handed, with
|
|
11
|
+
* checks that read the chain and nothing else, and writes the verdict. Each answer is one line of
|
|
12
|
+
* JSON on its output.
|
|
13
|
+
*/
|
|
14
|
+
const systemClock = () => BigInt(Math.floor(Date.now() / 1000));
|
|
15
|
+
/** The chain the checks read: Tempo's RPC for a live run, or what the fixture world handed over. */
|
|
16
|
+
function chainOf(chain) {
|
|
17
|
+
if (chain.kind === 'snapshot')
|
|
18
|
+
return snapshotChain(chain.snapshot);
|
|
19
|
+
const client = createPublicClient({ transport: http(chain.url) });
|
|
20
|
+
return {
|
|
21
|
+
readers: [tempoLockReader(client, { factory: chain.factory, chainId: chain.chainId })],
|
|
22
|
+
backing: (lock, commitmentId, side, at) => readBacking(client, lock, commitmentId, side, { blockNumber: at }),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export async function runServiceChild(serviceUrl, inputPath) {
|
|
26
|
+
const input = decodeData(readFileSync(inputPath, 'utf8'));
|
|
27
|
+
const loaded = (await import(serviceUrl));
|
|
28
|
+
const service = loaded.default;
|
|
29
|
+
if (!isService(service)) {
|
|
30
|
+
throw new Error('service.ts has to export the service defineService makes as its default export');
|
|
31
|
+
}
|
|
32
|
+
if (input.mode === 'policy') {
|
|
33
|
+
const { budgetPerGpuHour, computeSwitch } = service.policy;
|
|
34
|
+
process.stdout.write(`${JSON.stringify({ budgetPerGpuHour: budgetPerGpuHour.toString(), computeSwitch })}\n`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const verdict = await service.checkBeforeWork(input.job, serviceChecks(chainOf(input.chain), systemClock));
|
|
38
|
+
process.stdout.write(`${JSON.stringify(toWire(verdict))}\n`);
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=service-child.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-child.js","sourceRoot":"","sources":["../../src/node/service-child.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AACrE,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAoB,MAAM,uBAAuB,CAAA;AACnE,OAAO,EAAwB,aAAa,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAGhG;;;;;GAKG;AAEH,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAA;AAE/D,oGAAoG;AACpG,SAAS,OAAO,CAAC,KAAiB;IAChC,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IACnE,MAAM,MAAM,GAAG,kBAAkB,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACjE,OAAO;QACL,OAAO,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACtF,OAAO,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,CACxC,WAAW,CAAC,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;KACrE,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,UAAkB,EAAE,SAAiB;IACzE,MAAM,KAAK,GAAG,UAAU,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAEE,CAAA;IAC1D,MAAM,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,CAA0B,CAAA;IAClE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAA;IAC9B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,gFAAgF,CACjF,CAAA;IACH,CAAC;IACD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,EAAE,gBAAgB,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,MAAM,CAAA;QAC1D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,gBAAgB,CAAC,QAAQ,EAAE,EAAE,aAAa,EAAE,CAAC,IAAI,CACxF,CAAA;QACD,OAAM;IACR,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,eAAe,CAC3C,KAAK,CAAC,GAAG,EACT,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,CACjD,CAAA;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAA;AAC9D,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lines of a developer's service file that decided a first run: the compute budget, the
|
|
3
|
+
* compute switch, and `checkBeforeWork`, numbered as the file numbers them. The first run ends on
|
|
4
|
+
* these, so a developer sees the code that held their agent to its rules and where to change it.
|
|
5
|
+
*/
|
|
6
|
+
export interface ExcerptLine {
|
|
7
|
+
/** The line's number in the file, from 1. */
|
|
8
|
+
line: number;
|
|
9
|
+
text: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ServiceExcerpt {
|
|
12
|
+
lines: ExcerptLine[];
|
|
13
|
+
/** The budget the file sets, in dollars per GPU-hour, when it sets one as a plain number. */
|
|
14
|
+
budget: number | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The deciding lines of a service file's source, or null when the file doesn't have the shape the
|
|
18
|
+
* starter wrote, since a developer who reshaped it knows where their rules are.
|
|
19
|
+
*/
|
|
20
|
+
export declare function serviceExcerpt(source: string): ServiceExcerpt | null;
|
|
21
|
+
//# sourceMappingURL=service-excerpt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-excerpt.d.ts","sourceRoot":"","sources":["../../src/node/service-excerpt.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,WAAW;IAC1B,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,6FAA6F;IAC7F,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAkBpE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lines of a developer's service file that decided a first run: the compute budget, the
|
|
3
|
+
* compute switch, and `checkBeforeWork`, numbered as the file numbers them. The first run ends on
|
|
4
|
+
* these, so a developer sees the code that held their agent to its rules and where to change it.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The deciding lines of a service file's source, or null when the file doesn't have the shape the
|
|
8
|
+
* starter wrote, since a developer who reshaped it knows where their rules are.
|
|
9
|
+
*/
|
|
10
|
+
export function serviceExcerpt(source) {
|
|
11
|
+
const all = source.split('\n');
|
|
12
|
+
const find = (pattern) => all.findIndex((text) => pattern.test(text));
|
|
13
|
+
const budgetAt = find(/^\s*computeBudgetPerGpuHour\s*:/);
|
|
14
|
+
const switchAt = find(/^\s*bookCompute\s*:/);
|
|
15
|
+
const checkAt = find(/^\s*(async\s+)?checkBeforeWork\s*\(/);
|
|
16
|
+
if (checkAt === -1)
|
|
17
|
+
return null;
|
|
18
|
+
// The function ends at the first line back at its own indent that closes it.
|
|
19
|
+
const indent = /^\s*/.exec(all[checkAt] ?? '')?.[0] ?? '';
|
|
20
|
+
const endAt = all.findIndex((text, i) => i > checkAt && text.startsWith(`${indent}}`));
|
|
21
|
+
if (endAt === -1 || endAt - checkAt > 20)
|
|
22
|
+
return null;
|
|
23
|
+
const picked = [budgetAt, switchAt].filter((i) => i !== -1);
|
|
24
|
+
for (let i = checkAt; i <= endAt; i++)
|
|
25
|
+
picked.push(i);
|
|
26
|
+
const budget = budgetAt === -1 ? null : /:\s*(\d+(\.\d+)?)\s*,?\s*$/.exec(all[budgetAt] ?? '');
|
|
27
|
+
return {
|
|
28
|
+
lines: picked.map((i) => ({ line: i + 1, text: (all[i] ?? '').trimEnd() })),
|
|
29
|
+
budget: budget ? Number(budget[1]) : null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=service-excerpt.js.map
|