@holoscript/holoscript-agent 2.0.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 +21 -0
- package/dist/ablation.d.ts +56 -0
- package/dist/ablation.js +183 -0
- package/dist/ablation.js.map +1 -0
- package/dist/audit-log.d.ts +99 -0
- package/dist/audit-log.js +123 -0
- package/dist/audit-log.js.map +1 -0
- package/dist/brain.d.ts +6 -0
- package/dist/brain.js +66 -0
- package/dist/brain.js.map +1 -0
- package/dist/commit-hook.d.ts +22 -0
- package/dist/commit-hook.js +103 -0
- package/dist/commit-hook.js.map +1 -0
- package/dist/cost-guard.d.ts +54 -0
- package/dist/cost-guard.js +92 -0
- package/dist/cost-guard.js.map +1 -0
- package/dist/holomesh-client.d.ts +63 -0
- package/dist/holomesh-client.js +117 -0
- package/dist/holomesh-client.js.map +1 -0
- package/dist/identity.d.ts +7 -0
- package/dist/identity.js +64 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2101 -0
- package/dist/index.js.map +1 -0
- package/dist/provision.d.ts +43 -0
- package/dist/provision.js +195 -0
- package/dist/provision.js.map +1 -0
- package/dist/runner.d.ts +62 -0
- package/dist/runner.js +543 -0
- package/dist/runner.js.map +1 -0
- package/dist/supervisor-config.d.ts +26 -0
- package/dist/supervisor-config.js +109 -0
- package/dist/supervisor-config.js.map +1 -0
- package/dist/supervisor.d.ts +53 -0
- package/dist/supervisor.js +1167 -0
- package/dist/supervisor.js.map +1 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +99 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// src/provision.ts
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync, chmodSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir, hostname } from "os";
|
|
5
|
+
import { randomBytes, createCipheriv, createHash } from "crypto";
|
|
6
|
+
import { Wallet } from "ethers";
|
|
7
|
+
var HANDLE_PATTERN = /^[a-z0-9_-]{1,64}$/i;
|
|
8
|
+
var EIP712_DOMAIN = { name: "HoloMesh", version: "1" };
|
|
9
|
+
var EIP712_TYPES = {
|
|
10
|
+
Registration: [{ name: "nonce", type: "string" }]
|
|
11
|
+
};
|
|
12
|
+
async function provisionAgent(req, opts = { execute: false }) {
|
|
13
|
+
if (!HANDLE_PATTERN.test(req.handle)) {
|
|
14
|
+
throw new Error(`handle "${req.handle}" must match ${HANDLE_PATTERN}`);
|
|
15
|
+
}
|
|
16
|
+
if (!req.founderBearer || req.founderBearer.trim().length === 0) {
|
|
17
|
+
throw new Error("founderBearer is required (HOLOMESH_API_KEY of an agent that can call /register)");
|
|
18
|
+
}
|
|
19
|
+
const meshApiBase = (req.meshApiBase ?? "https://mcp.holoscript.net/api/holomesh").replace(/\/$/, "");
|
|
20
|
+
const seatsRoot = req.seatsRoot ?? defaultSeatsRoot();
|
|
21
|
+
const surface = req.handle;
|
|
22
|
+
const seatId = makeSeatId(surface);
|
|
23
|
+
const seatDir = join(seatsRoot, seatId);
|
|
24
|
+
const walletPath = join(seatDir, "wallet.enc");
|
|
25
|
+
const regPath = join(seatDir, "registration.json");
|
|
26
|
+
if (!opts.execute) {
|
|
27
|
+
return {
|
|
28
|
+
status: "dry-run",
|
|
29
|
+
handle: req.handle,
|
|
30
|
+
surface,
|
|
31
|
+
seatId,
|
|
32
|
+
seatDir,
|
|
33
|
+
willGenerateWallet: !existsSync(walletPath),
|
|
34
|
+
willCallEndpoints: [
|
|
35
|
+
`POST ${meshApiBase}/register/challenge`,
|
|
36
|
+
`POST ${meshApiBase}/register`
|
|
37
|
+
]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (existsSync(walletPath) && !opts.force) {
|
|
41
|
+
const blob = JSON.parse(readFileSync(walletPath, "utf8"));
|
|
42
|
+
const reused = {
|
|
43
|
+
status: "reused",
|
|
44
|
+
handle: req.handle,
|
|
45
|
+
surface,
|
|
46
|
+
seatId,
|
|
47
|
+
seatDir,
|
|
48
|
+
walletAddress: blob.address,
|
|
49
|
+
envVarLines: envVarLinesFor(req.handle, blob.address, void 0)
|
|
50
|
+
};
|
|
51
|
+
return reused;
|
|
52
|
+
}
|
|
53
|
+
const wallet = Wallet.createRandom();
|
|
54
|
+
mkdirSync(seatDir, { recursive: true });
|
|
55
|
+
const masterKey = ensureMasterKey(seatsRoot);
|
|
56
|
+
const encryptedBlob = {
|
|
57
|
+
seat_id: seatId,
|
|
58
|
+
surface,
|
|
59
|
+
handle: req.handle,
|
|
60
|
+
address: wallet.address,
|
|
61
|
+
encrypted_privkey: encryptPrivateKey(wallet.privateKey, masterKey),
|
|
62
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
63
|
+
source: "holoscript-agent.provision"
|
|
64
|
+
};
|
|
65
|
+
writeFileSync(walletPath, JSON.stringify(encryptedBlob, null, 2), "utf8");
|
|
66
|
+
try {
|
|
67
|
+
chmodSync(walletPath, 384);
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
const fetchImpl = req.fetchImpl ?? fetch;
|
|
71
|
+
const challenge = await postJson(
|
|
72
|
+
fetchImpl,
|
|
73
|
+
`${meshApiBase}/register/challenge`,
|
|
74
|
+
req.founderBearer,
|
|
75
|
+
{ wallet_address: wallet.address }
|
|
76
|
+
);
|
|
77
|
+
if (!challenge.nonce) {
|
|
78
|
+
throw new Error(`/register/challenge returned no nonce: ${JSON.stringify(challenge)}`);
|
|
79
|
+
}
|
|
80
|
+
const signature = await wallet.signTypedData(EIP712_DOMAIN, EIP712_TYPES, { nonce: challenge.nonce });
|
|
81
|
+
const registration = await postJson(
|
|
82
|
+
fetchImpl,
|
|
83
|
+
`${meshApiBase}/register`,
|
|
84
|
+
req.founderBearer,
|
|
85
|
+
{
|
|
86
|
+
name: req.handle,
|
|
87
|
+
wallet_address: wallet.address,
|
|
88
|
+
nonce: challenge.nonce,
|
|
89
|
+
signature
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
writeFileSync(
|
|
93
|
+
regPath,
|
|
94
|
+
JSON.stringify({ status: 201, response: registration, registered_at: (/* @__PURE__ */ new Date()).toISOString(), flow: "x402" }, null, 2),
|
|
95
|
+
"utf8"
|
|
96
|
+
);
|
|
97
|
+
const agentId = registration.agent?.id;
|
|
98
|
+
const bearer = registration.agent?.api_key;
|
|
99
|
+
if (!agentId || !bearer) {
|
|
100
|
+
throw new Error(`/register did not return agent.id + agent.api_key: ${JSON.stringify(registration).slice(0, 400)}`);
|
|
101
|
+
}
|
|
102
|
+
if (registration.wallet?.private_key) {
|
|
103
|
+
console.warn("[provision] WARN \u2014 server returned private_key despite x402 flow; ignoring (using local key).");
|
|
104
|
+
}
|
|
105
|
+
let joinedTeam;
|
|
106
|
+
if (req.autoJoinTeamId) {
|
|
107
|
+
try {
|
|
108
|
+
const joinRes = await postJson(
|
|
109
|
+
fetchImpl,
|
|
110
|
+
`${meshApiBase}/team/${req.autoJoinTeamId}/join`,
|
|
111
|
+
bearer,
|
|
112
|
+
{}
|
|
113
|
+
);
|
|
114
|
+
joinedTeam = {
|
|
115
|
+
teamId: req.autoJoinTeamId,
|
|
116
|
+
role: joinRes.role ?? "member",
|
|
117
|
+
members: joinRes.members ?? 0
|
|
118
|
+
};
|
|
119
|
+
} catch (err) {
|
|
120
|
+
joinedTeam = {
|
|
121
|
+
teamId: req.autoJoinTeamId,
|
|
122
|
+
error: err instanceof Error ? err.message : String(err)
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
status: "executed",
|
|
128
|
+
handle: req.handle,
|
|
129
|
+
surface,
|
|
130
|
+
seatId,
|
|
131
|
+
seatDir,
|
|
132
|
+
walletAddress: wallet.address,
|
|
133
|
+
bearer,
|
|
134
|
+
agentId,
|
|
135
|
+
envVarLines: envVarLinesFor(req.handle, wallet.address, bearer),
|
|
136
|
+
joinedTeam
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function defaultSeatsRoot() {
|
|
140
|
+
return process.env.HOLOSCRIPT_AGENT_SEATS_ROOT ?? join(homedir(), ".holoscript-agent", "seats");
|
|
141
|
+
}
|
|
142
|
+
function makeSeatId(surface) {
|
|
143
|
+
const fp = createHash("sha256").update(hostname() + homedir()).digest("hex").slice(0, 8);
|
|
144
|
+
return `holoscript-${surface}-${fp}-x402`;
|
|
145
|
+
}
|
|
146
|
+
function ensureMasterKey(seatsRoot) {
|
|
147
|
+
const keyPath = join(seatsRoot, ".master-key");
|
|
148
|
+
if (!existsSync(seatsRoot)) mkdirSync(seatsRoot, { recursive: true });
|
|
149
|
+
if (!existsSync(keyPath)) {
|
|
150
|
+
const k = randomBytes(32);
|
|
151
|
+
writeFileSync(keyPath, k);
|
|
152
|
+
try {
|
|
153
|
+
chmodSync(keyPath, 384);
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return readFileSync(keyPath);
|
|
158
|
+
}
|
|
159
|
+
function encryptPrivateKey(privKey, masterKey) {
|
|
160
|
+
const iv = randomBytes(12);
|
|
161
|
+
const cipher = createCipheriv("aes-256-gcm", masterKey, iv);
|
|
162
|
+
const ct = Buffer.concat([cipher.update(privKey, "utf8"), cipher.final()]);
|
|
163
|
+
return { iv: iv.toString("base64"), ct: ct.toString("base64"), tag: cipher.getAuthTag().toString("base64"), alg: "aes-256-gcm" };
|
|
164
|
+
}
|
|
165
|
+
async function postJson(fetchImpl, url, bearer, body) {
|
|
166
|
+
const res = await fetchImpl(url, {
|
|
167
|
+
method: "POST",
|
|
168
|
+
headers: {
|
|
169
|
+
Authorization: `Bearer ${bearer}`,
|
|
170
|
+
"Content-Type": "application/json"
|
|
171
|
+
},
|
|
172
|
+
body: JSON.stringify(body)
|
|
173
|
+
});
|
|
174
|
+
const text = await res.text();
|
|
175
|
+
if (!res.ok) {
|
|
176
|
+
throw new Error(`POST ${url} ${res.status}: ${text.slice(0, 400)}`);
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
return JSON.parse(text);
|
|
180
|
+
} catch {
|
|
181
|
+
throw new Error(`POST ${url} returned non-JSON: ${text.slice(0, 200)}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function envVarLinesFor(handle, walletAddress, bearer) {
|
|
185
|
+
const suffix = handle.toUpperCase().replace(/-/g, "_");
|
|
186
|
+
const lines = [`HOLOSCRIPT_AGENT_WALLET_${suffix}=${walletAddress}`];
|
|
187
|
+
if (bearer) {
|
|
188
|
+
lines.push(`HOLOMESH_API_KEY_${suffix}_X402=${bearer}`);
|
|
189
|
+
}
|
|
190
|
+
return lines;
|
|
191
|
+
}
|
|
192
|
+
export {
|
|
193
|
+
provisionAgent
|
|
194
|
+
};
|
|
195
|
+
//# sourceMappingURL=provision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/provision.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync, existsSync, chmodSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir, hostname } from 'node:os';\nimport { randomBytes, createCipheriv, createHash } from 'node:crypto';\nimport { Wallet } from 'ethers';\n\nconst HANDLE_PATTERN = /^[a-z0-9_-]{1,64}$/i;\n\nexport interface ProvisionRequest {\n handle: string;\n meshApiBase?: string;\n founderBearer: string;\n seatsRoot?: string;\n fetchImpl?: typeof fetch;\n autoJoinTeamId?: string;\n}\n\nexport interface ProvisionDryRun {\n status: 'dry-run';\n handle: string;\n surface: string;\n seatId: string;\n seatDir: string;\n willGenerateWallet: boolean;\n willCallEndpoints: string[];\n}\n\nexport interface ProvisionExecuted {\n status: 'executed' | 'reused';\n handle: string;\n surface: string;\n seatId: string;\n seatDir: string;\n walletAddress: string;\n bearer?: string;\n agentId?: string;\n envVarLines: string[];\n joinedTeam?: { teamId: string; role: string; members: number } | { teamId: string; error: string };\n}\n\nexport type ProvisionResult = ProvisionDryRun | ProvisionExecuted;\n\nconst EIP712_DOMAIN = { name: 'HoloMesh', version: '1' };\nconst EIP712_TYPES: Record<string, Array<{ name: string; type: string }>> = {\n Registration: [{ name: 'nonce', type: 'string' }],\n};\n\nexport async function provisionAgent(\n req: ProvisionRequest,\n opts: { execute: boolean; force?: boolean } = { execute: false }\n): Promise<ProvisionResult> {\n if (!HANDLE_PATTERN.test(req.handle)) {\n throw new Error(`handle \"${req.handle}\" must match ${HANDLE_PATTERN}`);\n }\n if (!req.founderBearer || req.founderBearer.trim().length === 0) {\n throw new Error('founderBearer is required (HOLOMESH_API_KEY of an agent that can call /register)');\n }\n\n const meshApiBase = (req.meshApiBase ?? 'https://mcp.holoscript.net/api/holomesh').replace(/\\/$/, '');\n const seatsRoot = req.seatsRoot ?? defaultSeatsRoot();\n const surface = req.handle;\n const seatId = makeSeatId(surface);\n const seatDir = join(seatsRoot, seatId);\n const walletPath = join(seatDir, 'wallet.enc');\n const regPath = join(seatDir, 'registration.json');\n\n if (!opts.execute) {\n return {\n status: 'dry-run',\n handle: req.handle,\n surface,\n seatId,\n seatDir,\n willGenerateWallet: !existsSync(walletPath),\n willCallEndpoints: [\n `POST ${meshApiBase}/register/challenge`,\n `POST ${meshApiBase}/register`,\n ],\n };\n }\n\n if (existsSync(walletPath) && !opts.force) {\n const blob = JSON.parse(readFileSync(walletPath, 'utf8')) as { address: string };\n const reused: ProvisionExecuted = {\n status: 'reused',\n handle: req.handle,\n surface,\n seatId,\n seatDir,\n walletAddress: blob.address,\n envVarLines: envVarLinesFor(req.handle, blob.address, undefined),\n };\n return reused;\n }\n\n const wallet = Wallet.createRandom();\n mkdirSync(seatDir, { recursive: true });\n\n const masterKey = ensureMasterKey(seatsRoot);\n const encryptedBlob = {\n seat_id: seatId,\n surface,\n handle: req.handle,\n address: wallet.address,\n encrypted_privkey: encryptPrivateKey(wallet.privateKey, masterKey),\n created_at: new Date().toISOString(),\n source: 'holoscript-agent.provision',\n };\n writeFileSync(walletPath, JSON.stringify(encryptedBlob, null, 2), 'utf8');\n try { chmodSync(walletPath, 0o600); } catch {}\n\n const fetchImpl = req.fetchImpl ?? fetch;\n\n const challenge = await postJson<{ nonce: string }>(\n fetchImpl,\n `${meshApiBase}/register/challenge`,\n req.founderBearer,\n { wallet_address: wallet.address }\n );\n if (!challenge.nonce) {\n throw new Error(`/register/challenge returned no nonce: ${JSON.stringify(challenge)}`);\n }\n\n const signature = await wallet.signTypedData(EIP712_DOMAIN, EIP712_TYPES, { nonce: challenge.nonce });\n\n const registration = await postJson<{\n agent?: { id: string; api_key: string };\n wallet?: { private_key?: string };\n }>(\n fetchImpl,\n `${meshApiBase}/register`,\n req.founderBearer,\n {\n name: req.handle,\n wallet_address: wallet.address,\n nonce: challenge.nonce,\n signature,\n }\n );\n writeFileSync(\n regPath,\n JSON.stringify({ status: 201, response: registration, registered_at: new Date().toISOString(), flow: 'x402' }, null, 2),\n 'utf8'\n );\n\n const agentId = registration.agent?.id;\n const bearer = registration.agent?.api_key;\n if (!agentId || !bearer) {\n throw new Error(`/register did not return agent.id + agent.api_key: ${JSON.stringify(registration).slice(0, 400)}`);\n }\n if (registration.wallet?.private_key) {\n console.warn('[provision] WARN — server returned private_key despite x402 flow; ignoring (using local key).');\n }\n\n let joinedTeam: ProvisionExecuted['joinedTeam'];\n if (req.autoJoinTeamId) {\n try {\n const joinRes = await postJson<{ success?: boolean; role?: string; members?: number }>(\n fetchImpl,\n `${meshApiBase}/team/${req.autoJoinTeamId}/join`,\n bearer,\n {}\n );\n joinedTeam = {\n teamId: req.autoJoinTeamId,\n role: joinRes.role ?? 'member',\n members: joinRes.members ?? 0,\n };\n } catch (err) {\n joinedTeam = {\n teamId: req.autoJoinTeamId,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n return {\n status: 'executed',\n handle: req.handle,\n surface,\n seatId,\n seatDir,\n walletAddress: wallet.address,\n bearer,\n agentId,\n envVarLines: envVarLinesFor(req.handle, wallet.address, bearer),\n joinedTeam,\n };\n}\n\nfunction defaultSeatsRoot(): string {\n return process.env.HOLOSCRIPT_AGENT_SEATS_ROOT\n ?? join(homedir(), '.holoscript-agent', 'seats');\n}\n\nfunction makeSeatId(surface: string): string {\n const fp = createHash('sha256').update(hostname() + homedir()).digest('hex').slice(0, 8);\n return `holoscript-${surface}-${fp}-x402`;\n}\n\nfunction ensureMasterKey(seatsRoot: string): Buffer {\n const keyPath = join(seatsRoot, '.master-key');\n if (!existsSync(seatsRoot)) mkdirSync(seatsRoot, { recursive: true });\n if (!existsSync(keyPath)) {\n const k = randomBytes(32);\n writeFileSync(keyPath, k);\n try { chmodSync(keyPath, 0o600); } catch {}\n }\n return readFileSync(keyPath);\n}\n\nfunction encryptPrivateKey(\n privKey: string,\n masterKey: Buffer\n): { iv: string; ct: string; tag: string; alg: 'aes-256-gcm' } {\n const iv = randomBytes(12);\n const cipher = createCipheriv('aes-256-gcm', masterKey, iv);\n const ct = Buffer.concat([cipher.update(privKey, 'utf8'), cipher.final()]);\n return { iv: iv.toString('base64'), ct: ct.toString('base64'), tag: cipher.getAuthTag().toString('base64'), alg: 'aes-256-gcm' };\n}\n\nasync function postJson<T>(\n fetchImpl: typeof fetch,\n url: string,\n bearer: string,\n body: unknown\n): Promise<T> {\n const res = await fetchImpl(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${bearer}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n });\n const text = await res.text();\n if (!res.ok) {\n throw new Error(`POST ${url} ${res.status}: ${text.slice(0, 400)}`);\n }\n try {\n return JSON.parse(text) as T;\n } catch {\n throw new Error(`POST ${url} returned non-JSON: ${text.slice(0, 200)}`);\n }\n}\n\nfunction envVarLinesFor(handle: string, walletAddress: string, bearer?: string): string[] {\n const suffix = handle.toUpperCase().replace(/-/g, '_');\n const lines = [`HOLOSCRIPT_AGENT_WALLET_${suffix}=${walletAddress}`];\n if (bearer) {\n lines.push(`HOLOMESH_API_KEY_${suffix}_X402=${bearer}`);\n }\n return lines;\n}\n"],"mappings":"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","names":[]}
|
package/dist/runner.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ILLMProvider } from '@holoscript/llm-provider';
|
|
2
|
+
import { CostGuard } from './cost-guard.js';
|
|
3
|
+
import { HolomeshClient } from './holomesh-client.js';
|
|
4
|
+
import { AuditLog } from './audit-log.js';
|
|
5
|
+
import { AgentIdentity, RuntimeBrainConfig, ExecutionResult, BoardTask, TickResult } from './types.js';
|
|
6
|
+
|
|
7
|
+
interface AgentRunnerOptions {
|
|
8
|
+
identity: AgentIdentity;
|
|
9
|
+
brain: RuntimeBrainConfig;
|
|
10
|
+
provider: ILLMProvider;
|
|
11
|
+
costGuard: CostGuard;
|
|
12
|
+
mesh: HolomeshClient;
|
|
13
|
+
logger?: (event: Record<string, unknown>) => void;
|
|
14
|
+
onTaskExecuted?: (result: ExecutionResult, task: BoardTask) => Promise<void>;
|
|
15
|
+
auditLog?: AuditLog;
|
|
16
|
+
}
|
|
17
|
+
declare class AgentRunner {
|
|
18
|
+
private readonly opts;
|
|
19
|
+
private stopped;
|
|
20
|
+
private prevCaelChain;
|
|
21
|
+
private joinedThisProcess;
|
|
22
|
+
constructor(opts: AgentRunnerOptions);
|
|
23
|
+
tick(): Promise<TickResult>;
|
|
24
|
+
runForever(opts?: {
|
|
25
|
+
tickIntervalMs?: number;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
stop(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Heartbeat with one-shot self-rejoin on 403 "Not a member of this team".
|
|
30
|
+
*
|
|
31
|
+
* Pairs with task_1777112258989_eeyp: fresh-deploy fleet workers whose
|
|
32
|
+
* provisioning didn't atomically call /join (or whose membership was
|
|
33
|
+
* reaped) hit 403 every tick and never recover. We detect the specific
|
|
34
|
+
* server error string (see packages/mcp-server/src/holomesh/routes/
|
|
35
|
+
* team-routes.ts:903 → `{ error: 'Not a member' }` for /presence), call
|
|
36
|
+
* mesh.joinTeam() ONCE per runner process, and retry the heartbeat.
|
|
37
|
+
*
|
|
38
|
+
* Strict scope:
|
|
39
|
+
* - Only retries on 403 + "Not a member" body. Any other 403 (insufficient
|
|
40
|
+
* permissions, signing failure) re-throws unchanged.
|
|
41
|
+
* - Only retries ONCE per process. If we already rejoined this process and
|
|
42
|
+
* the heartbeat is *still* 403, the team is rejecting us for a reason
|
|
43
|
+
* /join can't fix (e.g. capacity, ban) — surface the error.
|
|
44
|
+
* - If joinTeam() itself throws, we DO mark joinedThisProcess=true before
|
|
45
|
+
* re-throwing so we don't slam the join endpoint on every subsequent
|
|
46
|
+
* tick. The next tick will surface the same heartbeat 403 and the
|
|
47
|
+
* runner-level catch in runForever logs tick-error and sleeps. Operator
|
|
48
|
+
* inspection (SSH/log) is the recovery path at that point.
|
|
49
|
+
*/
|
|
50
|
+
private heartbeatWithAutoRejoin;
|
|
51
|
+
/**
|
|
52
|
+
* Detect the server's "Not a member" 403 error from HolomeshClient.req().
|
|
53
|
+
* The error message format is: `HoloMesh POST /team/<id>/presence 403: <body>`
|
|
54
|
+
* where body contains `{"error":"Not a member"}` (or "Not a member of this team").
|
|
55
|
+
* Match conservatively: BOTH a "403" status marker AND the "Not a member"
|
|
56
|
+
* substring must appear, so unrelated 403s (insufficient permissions,
|
|
57
|
+
* signing failures) do NOT trigger a rejoin.
|
|
58
|
+
*/
|
|
59
|
+
private isNotAMemberError;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { AgentRunner, type AgentRunnerOptions };
|