@holoscript/holoscript-agent 2.0.1 → 2.0.3
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 +117 -0
- package/bin/holoscript-agent.cjs +3 -1
- package/dist/ablation.js +4 -1
- package/dist/ablation.js.map +1 -1
- package/dist/brain.js +73 -3
- package/dist/brain.js.map +1 -1
- package/dist/commit-hook.js +6 -2
- package/dist/commit-hook.js.map +1 -1
- package/dist/cost-guard.js +2 -0
- package/dist/cost-guard.js.map +1 -1
- package/dist/holomesh-client.d.ts +8 -1
- package/dist/holomesh-client.js +24 -25
- package/dist/holomesh-client.js.map +1 -1
- package/dist/identity.js +2 -2
- package/dist/identity.js.map +1 -1
- package/dist/index.js +593 -94
- package/dist/index.js.map +1 -1
- package/dist/provision.js +39 -22
- package/dist/provision.js.map +1 -1
- package/dist/runner.js +289 -20
- package/dist/runner.js.map +1 -1
- package/dist/supervisor-config.js +3 -1
- package/dist/supervisor-config.js.map +1 -1
- package/dist/supervisor.js +401 -55
- package/dist/supervisor.js.map +1 -1
- package/dist/types.d.ts +30 -2
- package/package.json +14 -11
- package/LICENSE +0 -21
package/dist/provision.js
CHANGED
|
@@ -14,9 +14,14 @@ async function provisionAgent(req, opts = { execute: false }) {
|
|
|
14
14
|
throw new Error(`handle "${req.handle}" must match ${HANDLE_PATTERN}`);
|
|
15
15
|
}
|
|
16
16
|
if (!req.founderBearer || req.founderBearer.trim().length === 0) {
|
|
17
|
-
throw new Error(
|
|
17
|
+
throw new Error(
|
|
18
|
+
"founderBearer is required (HOLOMESH_API_KEY of an agent that can call /register)"
|
|
19
|
+
);
|
|
18
20
|
}
|
|
19
|
-
const meshApiBase = (req.meshApiBase ?? "https://mcp.holoscript.net/api/holomesh").replace(
|
|
21
|
+
const meshApiBase = (req.meshApiBase ?? "https://mcp.holoscript.net/api/holomesh").replace(
|
|
22
|
+
/\/$/,
|
|
23
|
+
""
|
|
24
|
+
);
|
|
20
25
|
const seatsRoot = req.seatsRoot ?? defaultSeatsRoot();
|
|
21
26
|
const surface = req.handle;
|
|
22
27
|
const seatId = makeSeatId(surface);
|
|
@@ -31,10 +36,7 @@ async function provisionAgent(req, opts = { execute: false }) {
|
|
|
31
36
|
seatId,
|
|
32
37
|
seatDir,
|
|
33
38
|
willGenerateWallet: !existsSync(walletPath),
|
|
34
|
-
willCallEndpoints: [
|
|
35
|
-
`POST ${meshApiBase}/register/challenge`,
|
|
36
|
-
`POST ${meshApiBase}/register`
|
|
37
|
-
]
|
|
39
|
+
willCallEndpoints: [`POST ${meshApiBase}/register/challenge`, `POST ${meshApiBase}/register`]
|
|
38
40
|
};
|
|
39
41
|
}
|
|
40
42
|
if (existsSync(walletPath) && !opts.force) {
|
|
@@ -77,30 +79,40 @@ async function provisionAgent(req, opts = { execute: false }) {
|
|
|
77
79
|
if (!challenge.nonce) {
|
|
78
80
|
throw new Error(`/register/challenge returned no nonce: ${JSON.stringify(challenge)}`);
|
|
79
81
|
}
|
|
80
|
-
const signature = await wallet.signTypedData(EIP712_DOMAIN, EIP712_TYPES, {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
req.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
signature
|
|
90
|
-
}
|
|
91
|
-
);
|
|
82
|
+
const signature = await wallet.signTypedData(EIP712_DOMAIN, EIP712_TYPES, {
|
|
83
|
+
nonce: challenge.nonce
|
|
84
|
+
});
|
|
85
|
+
const registration = await postJson(fetchImpl, `${meshApiBase}/register`, req.founderBearer, {
|
|
86
|
+
name: req.handle,
|
|
87
|
+
wallet_address: wallet.address,
|
|
88
|
+
nonce: challenge.nonce,
|
|
89
|
+
signature
|
|
90
|
+
});
|
|
92
91
|
writeFileSync(
|
|
93
92
|
regPath,
|
|
94
|
-
JSON.stringify(
|
|
93
|
+
JSON.stringify(
|
|
94
|
+
{
|
|
95
|
+
status: 201,
|
|
96
|
+
response: registration,
|
|
97
|
+
registered_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
98
|
+
flow: "x402"
|
|
99
|
+
},
|
|
100
|
+
null,
|
|
101
|
+
2
|
|
102
|
+
),
|
|
95
103
|
"utf8"
|
|
96
104
|
);
|
|
97
105
|
const agentId = registration.agent?.id;
|
|
98
106
|
const bearer = registration.agent?.api_key;
|
|
99
107
|
if (!agentId || !bearer) {
|
|
100
|
-
throw new Error(
|
|
108
|
+
throw new Error(
|
|
109
|
+
`/register did not return agent.id + agent.api_key: ${JSON.stringify(registration).slice(0, 400)}`
|
|
110
|
+
);
|
|
101
111
|
}
|
|
102
112
|
if (registration.wallet?.private_key) {
|
|
103
|
-
console.warn(
|
|
113
|
+
console.warn(
|
|
114
|
+
"[provision] WARN \u2014 server returned private_key despite x402 flow; ignoring (using local key)."
|
|
115
|
+
);
|
|
104
116
|
}
|
|
105
117
|
let joinedTeam;
|
|
106
118
|
if (req.autoJoinTeamId) {
|
|
@@ -160,7 +172,12 @@ function encryptPrivateKey(privKey, masterKey) {
|
|
|
160
172
|
const iv = randomBytes(12);
|
|
161
173
|
const cipher = createCipheriv("aes-256-gcm", masterKey, iv);
|
|
162
174
|
const ct = Buffer.concat([cipher.update(privKey, "utf8"), cipher.final()]);
|
|
163
|
-
return {
|
|
175
|
+
return {
|
|
176
|
+
iv: iv.toString("base64"),
|
|
177
|
+
ct: ct.toString("base64"),
|
|
178
|
+
tag: cipher.getAuthTag().toString("base64"),
|
|
179
|
+
alg: "aes-256-gcm"
|
|
180
|
+
};
|
|
164
181
|
}
|
|
165
182
|
async function postJson(fetchImpl, url, bearer, body) {
|
|
166
183
|
const res = await fetchImpl(url, {
|
package/dist/provision.js.map
CHANGED
|
@@ -1 +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":";AAAA,SAAS,WAAW,cAAc,eAAe,YAAY,iBAAiB;AAC9E,SAAS,YAAY;AACrB,SAAS,SAAS,gBAAgB;AAClC,SAAS,aAAa,gBAAgB,kBAAkB;AACxD,SAAS,cAAc;AAEvB,IAAM,iBAAiB;AAoCvB,IAAM,gBAAgB,EAAE,MAAM,YAAY,SAAS,IAAI;AACvD,IAAM,eAAsE;AAAA,EAC1E,cAAc,CAAC,EAAE,MAAM,SAAS,MAAM,SAAS,CAAC;AAClD;AAEA,eAAsB,eACpB,KACA,OAA8C,EAAE,SAAS,MAAM,GACrC;AAC1B,MAAI,CAAC,eAAe,KAAK,IAAI,MAAM,GAAG;AACpC,UAAM,IAAI,MAAM,WAAW,IAAI,MAAM,gBAAgB,cAAc,EAAE;AAAA,EACvE;AACA,MAAI,CAAC,IAAI,iBAAiB,IAAI,cAAc,KAAK,EAAE,WAAW,GAAG;AAC/D,UAAM,IAAI,MAAM,kFAAkF;AAAA,EACpG;AAEA,QAAM,eAAe,IAAI,eAAe,2CAA2C,QAAQ,OAAO,EAAE;AACpG,QAAM,YAAY,IAAI,aAAa,iBAAiB;AACpD,QAAM,UAAU,IAAI;AACpB,QAAM,SAAS,WAAW,OAAO;AACjC,QAAM,UAAU,KAAK,WAAW,MAAM;AACtC,QAAM,aAAa,KAAK,SAAS,YAAY;AAC7C,QAAM,UAAU,KAAK,SAAS,mBAAmB;AAEjD,MAAI,CAAC,KAAK,SAAS;AACjB,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ,IAAI;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA,oBAAoB,CAAC,WAAW,UAAU;AAAA,MAC1C,mBAAmB;AAAA,QACjB,QAAQ,WAAW;AAAA,QACnB,QAAQ,WAAW;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,WAAW,UAAU,KAAK,CAAC,KAAK,OAAO;AACzC,UAAM,OAAO,KAAK,MAAM,aAAa,YAAY,MAAM,CAAC;AACxD,UAAM,SAA4B;AAAA,MAChC,QAAQ;AAAA,MACR,QAAQ,IAAI;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA,eAAe,KAAK;AAAA,MACpB,aAAa,eAAe,IAAI,QAAQ,KAAK,SAAS,MAAS;AAAA,IACjE;AACA,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,OAAO,aAAa;AACnC,YAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAEtC,QAAM,YAAY,gBAAgB,SAAS;AAC3C,QAAM,gBAAgB;AAAA,IACpB,SAAS;AAAA,IACT;AAAA,IACA,QAAQ,IAAI;AAAA,IACZ,SAAS,OAAO;AAAA,IAChB,mBAAmB,kBAAkB,OAAO,YAAY,SAAS;AAAA,IACjE,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC,QAAQ;AAAA,EACV;AACA,gBAAc,YAAY,KAAK,UAAU,eAAe,MAAM,CAAC,GAAG,MAAM;AACxE,MAAI;AAAE,cAAU,YAAY,GAAK;AAAA,EAAG,QAAQ;AAAA,EAAC;AAE7C,QAAM,YAAY,IAAI,aAAa;AAEnC,QAAM,YAAY,MAAM;AAAA,IACtB;AAAA,IACA,GAAG,WAAW;AAAA,IACd,IAAI;AAAA,IACJ,EAAE,gBAAgB,OAAO,QAAQ;AAAA,EACnC;AACA,MAAI,CAAC,UAAU,OAAO;AACpB,UAAM,IAAI,MAAM,0CAA0C,KAAK,UAAU,SAAS,CAAC,EAAE;AAAA,EACvF;AAEA,QAAM,YAAY,MAAM,OAAO,cAAc,eAAe,cAAc,EAAE,OAAO,UAAU,MAAM,CAAC;AAEpG,QAAM,eAAe,MAAM;AAAA,IAIzB;AAAA,IACA,GAAG,WAAW;AAAA,IACd,IAAI;AAAA,IACJ;AAAA,MACE,MAAM,IAAI;AAAA,MACV,gBAAgB,OAAO;AAAA,MACvB,OAAO,UAAU;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AACA;AAAA,IACE;AAAA,IACA,KAAK,UAAU,EAAE,QAAQ,KAAK,UAAU,cAAc,gBAAe,oBAAI,KAAK,GAAE,YAAY,GAAG,MAAM,OAAO,GAAG,MAAM,CAAC;AAAA,IACtH;AAAA,EACF;AAEA,QAAM,UAAU,aAAa,OAAO;AACpC,QAAM,SAAS,aAAa,OAAO;AACnC,MAAI,CAAC,WAAW,CAAC,QAAQ;AACvB,UAAM,IAAI,MAAM,sDAAsD,KAAK,UAAU,YAAY,EAAE,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EACpH;AACA,MAAI,aAAa,QAAQ,aAAa;AACpC,YAAQ,KAAK,oGAA+F;AAAA,EAC9G;AAEA,MAAI;AACJ,MAAI,IAAI,gBAAgB;AACtB,QAAI;AACF,YAAM,UAAU,MAAM;AAAA,QACpB;AAAA,QACA,GAAG,WAAW,SAAS,IAAI,cAAc;AAAA,QACzC;AAAA,QACA,CAAC;AAAA,MACH;AACA,mBAAa;AAAA,QACX,QAAQ,IAAI;AAAA,QACZ,MAAM,QAAQ,QAAQ;AAAA,QACtB,SAAS,QAAQ,WAAW;AAAA,MAC9B;AAAA,IACF,SAAS,KAAK;AACZ,mBAAa;AAAA,QACX,QAAQ,IAAI;AAAA,QACZ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ,IAAI;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe,OAAO;AAAA,IACtB;AAAA,IACA;AAAA,IACA,aAAa,eAAe,IAAI,QAAQ,OAAO,SAAS,MAAM;AAAA,IAC9D;AAAA,EACF;AACF;AAEA,SAAS,mBAA2B;AAClC,SAAO,QAAQ,IAAI,+BACd,KAAK,QAAQ,GAAG,qBAAqB,OAAO;AACnD;AAEA,SAAS,WAAW,SAAyB;AAC3C,QAAM,KAAK,WAAW,QAAQ,EAAE,OAAO,SAAS,IAAI,QAAQ,CAAC,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,CAAC;AACvF,SAAO,cAAc,OAAO,IAAI,EAAE;AACpC;AAEA,SAAS,gBAAgB,WAA2B;AAClD,QAAM,UAAU,KAAK,WAAW,aAAa;AAC7C,MAAI,CAAC,WAAW,SAAS,EAAG,WAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AACpE,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,UAAM,IAAI,YAAY,EAAE;AACxB,kBAAc,SAAS,CAAC;AACxB,QAAI;AAAE,gBAAU,SAAS,GAAK;AAAA,IAAG,QAAQ;AAAA,IAAC;AAAA,EAC5C;AACA,SAAO,aAAa,OAAO;AAC7B;AAEA,SAAS,kBACP,SACA,WAC6D;AAC7D,QAAM,KAAK,YAAY,EAAE;AACzB,QAAM,SAAS,eAAe,eAAe,WAAW,EAAE;AAC1D,QAAM,KAAK,OAAO,OAAO,CAAC,OAAO,OAAO,SAAS,MAAM,GAAG,OAAO,MAAM,CAAC,CAAC;AACzE,SAAO,EAAE,IAAI,GAAG,SAAS,QAAQ,GAAG,IAAI,GAAG,SAAS,QAAQ,GAAG,KAAK,OAAO,WAAW,EAAE,SAAS,QAAQ,GAAG,KAAK,cAAc;AACjI;AAEA,eAAe,SACb,WACA,KACA,QACA,MACY;AACZ,QAAM,MAAM,MAAM,UAAU,KAAK;AAAA,IAC/B,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,eAAe,UAAU,MAAM;AAAA,MAC/B,gBAAgB;AAAA,IAClB;AAAA,IACA,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B,CAAC;AACD,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,QAAQ,GAAG,IAAI,IAAI,MAAM,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EACpE;AACA,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,UAAM,IAAI,MAAM,QAAQ,GAAG,uBAAuB,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EACxE;AACF;AAEA,SAAS,eAAe,QAAgB,eAAuB,QAA2B;AACxF,QAAM,SAAS,OAAO,YAAY,EAAE,QAAQ,MAAM,GAAG;AACrD,QAAM,QAAQ,CAAC,2BAA2B,MAAM,IAAI,aAAa,EAAE;AACnE,MAAI,QAAQ;AACV,UAAM,KAAK,oBAAoB,MAAM,SAAS,MAAM,EAAE;AAAA,EACxD;AACA,SAAO;AACT;","names":[]}
|
|
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?:\n | { teamId: string; role: string; members: number }\n | { 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(\n 'founderBearer is required (HOLOMESH_API_KEY of an agent that can call /register)'\n );\n }\n\n const meshApiBase = (req.meshApiBase ?? 'https://mcp.holoscript.net/api/holomesh').replace(\n /\\/$/,\n ''\n );\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: [`POST ${meshApiBase}/register/challenge`, `POST ${meshApiBase}/register`],\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 {\n chmodSync(walletPath, 0o600);\n } 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, {\n nonce: challenge.nonce,\n });\n\n const registration = await postJson<{\n agent?: { id: string; api_key: string };\n wallet?: { private_key?: string };\n }>(fetchImpl, `${meshApiBase}/register`, req.founderBearer, {\n name: req.handle,\n wallet_address: wallet.address,\n nonce: challenge.nonce,\n signature,\n });\n writeFileSync(\n regPath,\n JSON.stringify(\n {\n status: 201,\n response: registration,\n registered_at: new Date().toISOString(),\n flow: 'x402',\n },\n null,\n 2\n ),\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(\n `/register did not return agent.id + agent.api_key: ${JSON.stringify(registration).slice(0, 400)}`\n );\n }\n if (registration.wallet?.private_key) {\n console.warn(\n '[provision] WARN — server returned private_key despite x402 flow; ignoring (using local key).'\n );\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 ?? join(homedir(), '.holoscript-agent', 'seats');\n}\n\nfunction makeSeatId(surface: string): string {\n const fp = createHash('sha256')\n .update(hostname() + homedir())\n .digest('hex')\n .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 {\n chmodSync(keyPath, 0o600);\n } 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 {\n iv: iv.toString('base64'),\n ct: ct.toString('base64'),\n tag: cipher.getAuthTag().toString('base64'),\n alg: 'aes-256-gcm',\n };\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":";AAAA,SAAS,WAAW,cAAc,eAAe,YAAY,iBAAiB;AAC9E,SAAS,YAAY;AACrB,SAAS,SAAS,gBAAgB;AAClC,SAAS,aAAa,gBAAgB,kBAAkB;AACxD,SAAS,cAAc;AAEvB,IAAM,iBAAiB;AAsCvB,IAAM,gBAAgB,EAAE,MAAM,YAAY,SAAS,IAAI;AACvD,IAAM,eAAsE;AAAA,EAC1E,cAAc,CAAC,EAAE,MAAM,SAAS,MAAM,SAAS,CAAC;AAClD;AAEA,eAAsB,eACpB,KACA,OAA8C,EAAE,SAAS,MAAM,GACrC;AAC1B,MAAI,CAAC,eAAe,KAAK,IAAI,MAAM,GAAG;AACpC,UAAM,IAAI,MAAM,WAAW,IAAI,MAAM,gBAAgB,cAAc,EAAE;AAAA,EACvE;AACA,MAAI,CAAC,IAAI,iBAAiB,IAAI,cAAc,KAAK,EAAE,WAAW,GAAG;AAC/D,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe,IAAI,eAAe,2CAA2C;AAAA,IACjF;AAAA,IACA;AAAA,EACF;AACA,QAAM,YAAY,IAAI,aAAa,iBAAiB;AACpD,QAAM,UAAU,IAAI;AACpB,QAAM,SAAS,WAAW,OAAO;AACjC,QAAM,UAAU,KAAK,WAAW,MAAM;AACtC,QAAM,aAAa,KAAK,SAAS,YAAY;AAC7C,QAAM,UAAU,KAAK,SAAS,mBAAmB;AAEjD,MAAI,CAAC,KAAK,SAAS;AACjB,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ,IAAI;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA,oBAAoB,CAAC,WAAW,UAAU;AAAA,MAC1C,mBAAmB,CAAC,QAAQ,WAAW,uBAAuB,QAAQ,WAAW,WAAW;AAAA,IAC9F;AAAA,EACF;AAEA,MAAI,WAAW,UAAU,KAAK,CAAC,KAAK,OAAO;AACzC,UAAM,OAAO,KAAK,MAAM,aAAa,YAAY,MAAM,CAAC;AACxD,UAAM,SAA4B;AAAA,MAChC,QAAQ;AAAA,MACR,QAAQ,IAAI;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA,eAAe,KAAK;AAAA,MACpB,aAAa,eAAe,IAAI,QAAQ,KAAK,SAAS,MAAS;AAAA,IACjE;AACA,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,OAAO,aAAa;AACnC,YAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAEtC,QAAM,YAAY,gBAAgB,SAAS;AAC3C,QAAM,gBAAgB;AAAA,IACpB,SAAS;AAAA,IACT;AAAA,IACA,QAAQ,IAAI;AAAA,IACZ,SAAS,OAAO;AAAA,IAChB,mBAAmB,kBAAkB,OAAO,YAAY,SAAS;AAAA,IACjE,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC,QAAQ;AAAA,EACV;AACA,gBAAc,YAAY,KAAK,UAAU,eAAe,MAAM,CAAC,GAAG,MAAM;AACxE,MAAI;AACF,cAAU,YAAY,GAAK;AAAA,EAC7B,QAAQ;AAAA,EAAC;AAET,QAAM,YAAY,IAAI,aAAa;AAEnC,QAAM,YAAY,MAAM;AAAA,IACtB;AAAA,IACA,GAAG,WAAW;AAAA,IACd,IAAI;AAAA,IACJ,EAAE,gBAAgB,OAAO,QAAQ;AAAA,EACnC;AACA,MAAI,CAAC,UAAU,OAAO;AACpB,UAAM,IAAI,MAAM,0CAA0C,KAAK,UAAU,SAAS,CAAC,EAAE;AAAA,EACvF;AAEA,QAAM,YAAY,MAAM,OAAO,cAAc,eAAe,cAAc;AAAA,IACxE,OAAO,UAAU;AAAA,EACnB,CAAC;AAED,QAAM,eAAe,MAAM,SAGxB,WAAW,GAAG,WAAW,aAAa,IAAI,eAAe;AAAA,IAC1D,MAAM,IAAI;AAAA,IACV,gBAAgB,OAAO;AAAA,IACvB,OAAO,UAAU;AAAA,IACjB;AAAA,EACF,CAAC;AACD;AAAA,IACE;AAAA,IACA,KAAK;AAAA,MACH;AAAA,QACE,QAAQ;AAAA,QACR,UAAU;AAAA,QACV,gBAAe,oBAAI,KAAK,GAAE,YAAY;AAAA,QACtC,MAAM;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,EACF;AAEA,QAAM,UAAU,aAAa,OAAO;AACpC,QAAM,SAAS,aAAa,OAAO;AACnC,MAAI,CAAC,WAAW,CAAC,QAAQ;AACvB,UAAM,IAAI;AAAA,MACR,sDAAsD,KAAK,UAAU,YAAY,EAAE,MAAM,GAAG,GAAG,CAAC;AAAA,IAClG;AAAA,EACF;AACA,MAAI,aAAa,QAAQ,aAAa;AACpC,YAAQ;AAAA,MACN;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACJ,MAAI,IAAI,gBAAgB;AACtB,QAAI;AACF,YAAM,UAAU,MAAM;AAAA,QACpB;AAAA,QACA,GAAG,WAAW,SAAS,IAAI,cAAc;AAAA,QACzC;AAAA,QACA,CAAC;AAAA,MACH;AACA,mBAAa;AAAA,QACX,QAAQ,IAAI;AAAA,QACZ,MAAM,QAAQ,QAAQ;AAAA,QACtB,SAAS,QAAQ,WAAW;AAAA,MAC9B;AAAA,IACF,SAAS,KAAK;AACZ,mBAAa;AAAA,QACX,QAAQ,IAAI;AAAA,QACZ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,QAAQ,IAAI;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe,OAAO;AAAA,IACtB;AAAA,IACA;AAAA,IACA,aAAa,eAAe,IAAI,QAAQ,OAAO,SAAS,MAAM;AAAA,IAC9D;AAAA,EACF;AACF;AAEA,SAAS,mBAA2B;AAClC,SAAO,QAAQ,IAAI,+BAA+B,KAAK,QAAQ,GAAG,qBAAqB,OAAO;AAChG;AAEA,SAAS,WAAW,SAAyB;AAC3C,QAAM,KAAK,WAAW,QAAQ,EAC3B,OAAO,SAAS,IAAI,QAAQ,CAAC,EAC7B,OAAO,KAAK,EACZ,MAAM,GAAG,CAAC;AACb,SAAO,cAAc,OAAO,IAAI,EAAE;AACpC;AAEA,SAAS,gBAAgB,WAA2B;AAClD,QAAM,UAAU,KAAK,WAAW,aAAa;AAC7C,MAAI,CAAC,WAAW,SAAS,EAAG,WAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AACpE,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,UAAM,IAAI,YAAY,EAAE;AACxB,kBAAc,SAAS,CAAC;AACxB,QAAI;AACF,gBAAU,SAAS,GAAK;AAAA,IAC1B,QAAQ;AAAA,IAAC;AAAA,EACX;AACA,SAAO,aAAa,OAAO;AAC7B;AAEA,SAAS,kBACP,SACA,WAC6D;AAC7D,QAAM,KAAK,YAAY,EAAE;AACzB,QAAM,SAAS,eAAe,eAAe,WAAW,EAAE;AAC1D,QAAM,KAAK,OAAO,OAAO,CAAC,OAAO,OAAO,SAAS,MAAM,GAAG,OAAO,MAAM,CAAC,CAAC;AACzE,SAAO;AAAA,IACL,IAAI,GAAG,SAAS,QAAQ;AAAA,IACxB,IAAI,GAAG,SAAS,QAAQ;AAAA,IACxB,KAAK,OAAO,WAAW,EAAE,SAAS,QAAQ;AAAA,IAC1C,KAAK;AAAA,EACP;AACF;AAEA,eAAe,SACb,WACA,KACA,QACA,MACY;AACZ,QAAM,MAAM,MAAM,UAAU,KAAK;AAAA,IAC/B,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,eAAe,UAAU,MAAM;AAAA,MAC/B,gBAAgB;AAAA,IAClB;AAAA,IACA,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B,CAAC;AACD,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,QAAQ,GAAG,IAAI,IAAI,MAAM,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EACpE;AACA,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,UAAM,IAAI,MAAM,QAAQ,GAAG,uBAAuB,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EACxE;AACF;AAEA,SAAS,eAAe,QAAgB,eAAuB,QAA2B;AACxF,QAAM,SAAS,OAAO,YAAY,EAAE,QAAQ,MAAM,GAAG;AACrD,QAAM,QAAQ,CAAC,2BAA2B,MAAM,IAAI,aAAa,EAAE;AACnE,MAAI,QAAQ;AACV,UAAM,KAAK,oBAAoB,MAAM,SAAS,MAAM,EAAE;AAAA,EACxD;AACA,SAAO;AACT;","names":[]}
|
package/dist/runner.js
CHANGED
|
@@ -37,7 +37,18 @@ function brainClassOf(brain) {
|
|
|
37
37
|
return "unknown";
|
|
38
38
|
}
|
|
39
39
|
function buildCaelRecord(input) {
|
|
40
|
-
const {
|
|
40
|
+
const {
|
|
41
|
+
identity,
|
|
42
|
+
brain,
|
|
43
|
+
task,
|
|
44
|
+
messages,
|
|
45
|
+
finalText,
|
|
46
|
+
usage,
|
|
47
|
+
costUsd,
|
|
48
|
+
spentUsd,
|
|
49
|
+
prevChain,
|
|
50
|
+
runtimeVersion
|
|
51
|
+
} = input;
|
|
41
52
|
const l0 = sha(brain.systemPrompt);
|
|
42
53
|
const l1 = sha(`${task.id}|${task.title}|${task.description ?? ""}`);
|
|
43
54
|
const l2 = sha(JSON.stringify(messages));
|
|
@@ -60,9 +71,9 @@ function buildCaelRecord(input) {
|
|
|
60
71
|
|
|
61
72
|
// src/tools.ts
|
|
62
73
|
import { readFile, writeFile, readdir, mkdir, stat } from "fs/promises";
|
|
63
|
-
import { resolve, dirname } from "path";
|
|
74
|
+
import { resolve, dirname, delimiter, isAbsolute, sep } from "path";
|
|
64
75
|
import { spawn } from "child_process";
|
|
65
|
-
var
|
|
76
|
+
var FLEET_READ_ROOTS = [
|
|
66
77
|
"/root/msc-paper-22",
|
|
67
78
|
// Paper 22 mechanization inputs (scp'd by deploy)
|
|
68
79
|
"/root/holoscript-mesh",
|
|
@@ -70,10 +81,23 @@ var ALLOWED_READ_ROOTS = [
|
|
|
70
81
|
"/root/agent-output"
|
|
71
82
|
// Read back what we wrote
|
|
72
83
|
];
|
|
73
|
-
var
|
|
84
|
+
var FLEET_WRITE_ROOTS = [
|
|
74
85
|
"/root/agent-output"
|
|
75
86
|
// Single write sink — keeps deliverables in one place
|
|
76
87
|
];
|
|
88
|
+
function parseRootsEnv(raw, fallback) {
|
|
89
|
+
if (!raw) return fallback;
|
|
90
|
+
const roots = raw.split(delimiter).map((r) => r.trim()).filter((r) => r.length > 0 && isAbsolute(r));
|
|
91
|
+
return roots.length > 0 ? roots : fallback;
|
|
92
|
+
}
|
|
93
|
+
var ALLOWED_READ_ROOTS = parseRootsEnv(
|
|
94
|
+
process.env.HOLOSCRIPT_AGENT_READ_ROOTS,
|
|
95
|
+
FLEET_READ_ROOTS
|
|
96
|
+
);
|
|
97
|
+
var ALLOWED_WRITE_ROOTS = parseRootsEnv(
|
|
98
|
+
process.env.HOLOSCRIPT_AGENT_WRITE_ROOTS,
|
|
99
|
+
FLEET_WRITE_ROOTS
|
|
100
|
+
);
|
|
77
101
|
var BASH_READ_ONLY_PREFIXES = [
|
|
78
102
|
"ls ",
|
|
79
103
|
"ls\n",
|
|
@@ -99,7 +123,15 @@ var BASH_PRODUCTIVE_PREFIXES = [
|
|
|
99
123
|
"lean ",
|
|
100
124
|
"pnpm --filter",
|
|
101
125
|
"pnpm vitest",
|
|
102
|
-
"vitest run"
|
|
126
|
+
"vitest run",
|
|
127
|
+
// Robotics / edge-node (Jetson) productive commands — without these, every
|
|
128
|
+
// ros2/colcon/tegrastats task fails the W.107 artifact gate and is abandoned
|
|
129
|
+
// as no-artifact. (jetson-orin-01 lane.)
|
|
130
|
+
"ros2 launch",
|
|
131
|
+
"ros2 topic pub",
|
|
132
|
+
"ros2 service call",
|
|
133
|
+
"colcon build",
|
|
134
|
+
"tegrastats"
|
|
103
135
|
];
|
|
104
136
|
var BASH_WHITELIST = [...BASH_READ_ONLY_PREFIXES, ...BASH_PRODUCTIVE_PREFIXES];
|
|
105
137
|
function isProductiveBashCommand(cmd) {
|
|
@@ -110,7 +142,7 @@ function isProductiveBashCommand(cmd) {
|
|
|
110
142
|
var MESH_TOOLS = [
|
|
111
143
|
{
|
|
112
144
|
name: "read_file",
|
|
113
|
-
description:
|
|
145
|
+
description: `Read a file from the agent sandbox. Allowed roots: ${ALLOWED_READ_ROOTS.join(", ")}. Returns the file content as text. Use this to inspect task inputs and the read-only repo view.`,
|
|
114
146
|
input_schema: {
|
|
115
147
|
type: "object",
|
|
116
148
|
properties: {
|
|
@@ -132,11 +164,11 @@ var MESH_TOOLS = [
|
|
|
132
164
|
},
|
|
133
165
|
{
|
|
134
166
|
name: "write_file",
|
|
135
|
-
description:
|
|
167
|
+
description: `Write a file to the deliverable sink (write roots: ${ALLOWED_WRITE_ROOTS.join(", ")}). Anything you want to emit as task output (a Lean proof, a markdown report, a JSON dataset, a .holo scene) goes here. Creates parent directories. Will refuse paths outside the write root(s).`,
|
|
136
168
|
input_schema: {
|
|
137
169
|
type: "object",
|
|
138
170
|
properties: {
|
|
139
|
-
path: { type: "string", description:
|
|
171
|
+
path: { type: "string", description: `Absolute path under a write root: ${ALLOWED_WRITE_ROOTS.join(", ")}` },
|
|
140
172
|
content: { type: "string", description: "File content to write (UTF-8)" }
|
|
141
173
|
},
|
|
142
174
|
required: ["path", "content"]
|
|
@@ -144,7 +176,7 @@ var MESH_TOOLS = [
|
|
|
144
176
|
},
|
|
145
177
|
{
|
|
146
178
|
name: "bash",
|
|
147
|
-
description: "Run a shell command. Whitelisted prefixes only: lake build, lean, ls, cat, grep, find, wc, head, tail, git status/log/diff/show, pnpm --filter, vitest run, pwd, echo. Hard 60s wall timeout, 1MB stdout cap. Use for
|
|
179
|
+
description: "Run a shell command. Whitelisted prefixes only: lake build, lean, ls, cat, grep, find, wc, head, tail, git status/log/diff/show, pnpm --filter, vitest run, pwd, echo, ros2 launch/topic/service, colcon build, tegrastats. Hard 60s wall timeout, 1MB stdout cap. Use for builds, tests, hardware probes. Refuses rm, curl, ssh, sudo, eval.",
|
|
148
180
|
input_schema: {
|
|
149
181
|
type: "object",
|
|
150
182
|
properties: {
|
|
@@ -153,22 +185,52 @@ var MESH_TOOLS = [
|
|
|
153
185
|
},
|
|
154
186
|
required: ["cmd"]
|
|
155
187
|
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "emit_hardware_receipt",
|
|
191
|
+
description: "Emit a portable hardware receipt (PortableHardwareReceiptMetadata v1) capturing device identity, runtime, and measured performance. Writes a JSON receipt to the agent output dir. Use after running tegrastats or colcon build to record hardware evidence for the CAEL audit chain. Accepts either pre-parsed measurements or raw tegrastats output (the tool parses it automatically).",
|
|
192
|
+
input_schema: {
|
|
193
|
+
type: "object",
|
|
194
|
+
properties: {
|
|
195
|
+
device_kind: {
|
|
196
|
+
type: "string",
|
|
197
|
+
description: 'Device identifier, e.g. "jetson-orin-nano-super", "raspberry-pi-5"'
|
|
198
|
+
},
|
|
199
|
+
accelerator: {
|
|
200
|
+
description: 'Accelerator string, e.g. "NVIDIA CUDA 8.7", or null for CPU-only'
|
|
201
|
+
},
|
|
202
|
+
runtime_name: { type: "string", description: 'Inference runtime, e.g. "Ollama", "llama.cpp"' },
|
|
203
|
+
runtime_version: { type: "string", description: 'Runtime version, e.g. "0.30.8"' },
|
|
204
|
+
host_os: { type: "string", description: 'OS + firmware, e.g. "JetPack 6.2.1 / Ubuntu 22.04"' },
|
|
205
|
+
composition_id: { type: "string", description: 'Brain composition reference, e.g. "jetson-orin-brain"' },
|
|
206
|
+
measurements: {
|
|
207
|
+
type: "array",
|
|
208
|
+
description: "Pre-parsed measurements. Each item: {metric: string, value: number, unit: string}",
|
|
209
|
+
items: { type: "object" }
|
|
210
|
+
},
|
|
211
|
+
tegrastats_output: {
|
|
212
|
+
type: "string",
|
|
213
|
+
description: "Raw tegrastats output line(s) \u2014 tool auto-parses GPU%, RAM, temp, power"
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
required: ["device_kind", "runtime_name", "runtime_version", "host_os"]
|
|
217
|
+
}
|
|
156
218
|
}
|
|
157
219
|
];
|
|
158
220
|
function isUnderRoot(absPath, root) {
|
|
159
221
|
const resolved = resolve(absPath);
|
|
160
222
|
const rootResolved = resolve(root);
|
|
161
|
-
return resolved === rootResolved || resolved.startsWith(rootResolved +
|
|
223
|
+
return resolved === rootResolved || resolved.startsWith(rootResolved + sep);
|
|
162
224
|
}
|
|
163
225
|
function checkReadAllowed(path) {
|
|
164
|
-
if (!path
|
|
226
|
+
if (!isAbsolute(path)) return `path must be absolute, got "${path}"`;
|
|
165
227
|
for (const root of ALLOWED_READ_ROOTS) {
|
|
166
228
|
if (isUnderRoot(path, root)) return null;
|
|
167
229
|
}
|
|
168
230
|
return `read denied \u2014 path "${path}" not under allowed roots: ${ALLOWED_READ_ROOTS.join(", ")}`;
|
|
169
231
|
}
|
|
170
232
|
function checkWriteAllowed(path) {
|
|
171
|
-
if (!path
|
|
233
|
+
if (!isAbsolute(path)) return `path must be absolute, got "${path}"`;
|
|
172
234
|
for (const root of ALLOWED_WRITE_ROOTS) {
|
|
173
235
|
if (isUnderRoot(path, root)) return null;
|
|
174
236
|
}
|
|
@@ -223,11 +285,105 @@ async function runTool(use) {
|
|
|
223
285
|
return result.code === 0 ? okResult(use.id, result.stdout) : errResult(use.id, `exit=${result.code}
|
|
224
286
|
${result.stderr || result.stdout}`);
|
|
225
287
|
}
|
|
288
|
+
if (use.name === "emit_hardware_receipt") {
|
|
289
|
+
const deviceKind = String(use.input.device_kind ?? "unknown-device");
|
|
290
|
+
const accelerator = use.input.accelerator === null || use.input.accelerator === "null" ? null : String(use.input.accelerator ?? "").trim() || null;
|
|
291
|
+
const runtimeName = String(use.input.runtime_name ?? "Ollama");
|
|
292
|
+
const runtimeVersion = String(use.input.runtime_version ?? "unknown");
|
|
293
|
+
const hostOs = String(use.input.host_os ?? "unknown");
|
|
294
|
+
const compositionId = String(use.input.composition_id ?? "unknown");
|
|
295
|
+
let measurements = [];
|
|
296
|
+
if (Array.isArray(use.input.measurements)) {
|
|
297
|
+
for (const m of use.input.measurements) {
|
|
298
|
+
const metric = String(m.metric ?? "");
|
|
299
|
+
const value = Number(m.value ?? 0);
|
|
300
|
+
const unit = String(m.unit ?? "");
|
|
301
|
+
if (metric && Number.isFinite(value)) {
|
|
302
|
+
measurements.push({ metric, value, unit, method: "measured" });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (typeof use.input.tegrastats_output === "string" && use.input.tegrastats_output.length > 0) {
|
|
307
|
+
measurements = [...measurements, ...parseTegrastats(use.input.tegrastats_output)];
|
|
308
|
+
}
|
|
309
|
+
if (measurements.length === 0) {
|
|
310
|
+
measurements.push({ metric: "agent-tick", value: 1, unit: "count", method: "presence" });
|
|
311
|
+
}
|
|
312
|
+
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
313
|
+
const receipt = {
|
|
314
|
+
schemaVersion: "holoscript.hardware-receipt-metadata.v1",
|
|
315
|
+
target: {
|
|
316
|
+
id: `${deviceKind}-${Date.now()}`,
|
|
317
|
+
kind: deviceKind,
|
|
318
|
+
architecture: /jetson|orin|nano|agx|xavier/i.test(deviceKind) ? "arm64" : "unknown",
|
|
319
|
+
artifactKind: "measurement-trace"
|
|
320
|
+
},
|
|
321
|
+
device: {
|
|
322
|
+
vendor: /jetson|orin|nvidia/i.test(deviceKind) ? "nvidia" : "unknown",
|
|
323
|
+
model: deviceKind,
|
|
324
|
+
accelerator
|
|
325
|
+
},
|
|
326
|
+
runtime: { name: runtimeName, version: runtimeVersion, hostOS: hostOs },
|
|
327
|
+
compilerVersion: "holoscript-agent-1.0.0",
|
|
328
|
+
constraints: [],
|
|
329
|
+
measuredResults: measurements,
|
|
330
|
+
replayInputs: [
|
|
331
|
+
{ kind: "composition-ref", uri: `compositions/${compositionId}`, sha256: "unknown" }
|
|
332
|
+
],
|
|
333
|
+
provenance: {
|
|
334
|
+
capturedAt,
|
|
335
|
+
sourceCompositionHash: compositionId
|
|
336
|
+
},
|
|
337
|
+
owner: {
|
|
338
|
+
agent: process.env.HOLOSCRIPT_AGENT_HANDLE ?? "unknown",
|
|
339
|
+
...process.env.HOLOMESH_TEAM_ID ? { team: process.env.HOLOMESH_TEAM_ID } : {}
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
const ts = capturedAt.replace(/[:.]/g, "-");
|
|
343
|
+
const outPath = resolve(ALLOWED_WRITE_ROOTS[0], `hardware-receipt-${ts}.json`);
|
|
344
|
+
const denied = checkWriteAllowed(outPath);
|
|
345
|
+
if (denied) return errResult(use.id, `Cannot write receipt: ${denied}`);
|
|
346
|
+
await mkdir(dirname(outPath), { recursive: true });
|
|
347
|
+
await writeFile(outPath, JSON.stringify(receipt, null, 2), "utf8");
|
|
348
|
+
return okResult(
|
|
349
|
+
use.id,
|
|
350
|
+
`Hardware receipt written to ${outPath} \u2014 ${measurements.length} measurements, accelerator=${accelerator ?? "none"}`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
226
353
|
return errResult(use.id, `unknown tool: ${use.name}`);
|
|
227
354
|
} catch (err) {
|
|
228
355
|
return errResult(use.id, err instanceof Error ? err.message : String(err));
|
|
229
356
|
}
|
|
230
357
|
}
|
|
358
|
+
function parseTegrastats(raw) {
|
|
359
|
+
const results = [];
|
|
360
|
+
const m = (pattern, metric, unit, transform) => {
|
|
361
|
+
const match = raw.match(pattern);
|
|
362
|
+
if (match?.[1]) {
|
|
363
|
+
const value = transform ? transform(match[1]) : Number(match[1]);
|
|
364
|
+
if (Number.isFinite(value)) results.push({ metric, value, unit, method: "tegrastats" });
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
const ram = raw.match(/RAM\s+(\d+)\/(\d+)MB/);
|
|
368
|
+
if (ram) {
|
|
369
|
+
const used = Number(ram[1]);
|
|
370
|
+
const total = Number(ram[2]);
|
|
371
|
+
results.push({ metric: "ram-used", value: used, unit: "MB", method: "tegrastats" });
|
|
372
|
+
results.push({ metric: "ram-total", value: total, unit: "MB", method: "tegrastats" });
|
|
373
|
+
if (total > 0)
|
|
374
|
+
results.push({ metric: "ram-pct", value: Math.round(used / total * 100), unit: "%", method: "tegrastats" });
|
|
375
|
+
}
|
|
376
|
+
m(/GR3D_FREQ\s+(\d+)%/, "gpu-util", "%");
|
|
377
|
+
m(/EMC_FREQ\s+(\d+)%/, "emc-freq-pct", "%");
|
|
378
|
+
m(/tj@([\d.]+)C/, "temp-tj", "C", parseFloat);
|
|
379
|
+
m(/cpu@([\d.]+)C/, "temp-cpu", "C", parseFloat);
|
|
380
|
+
m(/gpu@([\d.]+)C/, "temp-gpu", "C", parseFloat);
|
|
381
|
+
m(/VDD_SOC\s+(\d+)mW/, "power-soc", "mW");
|
|
382
|
+
m(/VDD_CPU_CV\s+(\d+)mW/, "power-cpu-cv", "mW");
|
|
383
|
+
m(/VDD_IN\s+(\d+)mW/, "power-total", "mW");
|
|
384
|
+
m(/CPU\s+\[(\d+)%/, "cpu-util-core0", "%");
|
|
385
|
+
return results;
|
|
386
|
+
}
|
|
231
387
|
function runBash(cmd, cwd) {
|
|
232
388
|
if (process.env.VITEST === "true" || process.env.NODE_ENV === "test") {
|
|
233
389
|
return Promise.resolve({
|
|
@@ -356,8 +512,28 @@ var AgentRunner = class {
|
|
|
356
512
|
log({ ev: "claim", taskId: target.id, title: target.title });
|
|
357
513
|
await mesh.claim(target.id);
|
|
358
514
|
const start = Date.now();
|
|
515
|
+
let systemContent = brain.systemPrompt;
|
|
516
|
+
if (brain.onTaskActions && brain.onTaskActions.length > 0) {
|
|
517
|
+
const llmCallAction = brain.onTaskActions.find((a) => a.verb === "llm_call");
|
|
518
|
+
const deferredVerbs = brain.onTaskActions.filter((a) => a.verb === "recall" || a.verb === "rag_query" || a.verb === "plan").map((a) => a.verb);
|
|
519
|
+
if (deferredVerbs.length > 0) {
|
|
520
|
+
log({
|
|
521
|
+
ev: "on-task-deferred",
|
|
522
|
+
taskId: target.id,
|
|
523
|
+
verbs: deferredVerbs,
|
|
524
|
+
note: "trait-backed dispatch deferred to Phase 2.2 (idea-seeds.md)"
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
if (llmCallAction && typeof llmCallAction.config.prompt === "string" && llmCallAction.config.prompt.length > 0) {
|
|
528
|
+
systemContent = `${brain.systemPrompt}
|
|
529
|
+
|
|
530
|
+
[Brain on_task directive]
|
|
531
|
+
${llmCallAction.config.prompt}`;
|
|
532
|
+
log({ ev: "on-task-llm-call", taskId: target.id, promptLen: llmCallAction.config.prompt.length });
|
|
533
|
+
}
|
|
534
|
+
}
|
|
359
535
|
const messages = [
|
|
360
|
-
{ role: "system", content:
|
|
536
|
+
{ role: "system", content: systemContent },
|
|
361
537
|
{ role: "user", content: buildTaskPrompt(target) }
|
|
362
538
|
];
|
|
363
539
|
let aggUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
@@ -375,12 +551,16 @@ var AgentRunner = class {
|
|
|
375
551
|
finalText = finalText || `[tool-loop hit ${MAX_TOOL_ITERS}-iter cap before final text]`;
|
|
376
552
|
break;
|
|
377
553
|
}
|
|
554
|
+
const activeTools = brain.requires.includes("local-llm") ? MESH_TOOLS.filter((t) => t.name === "write_file") : MESH_TOOLS;
|
|
378
555
|
const resp = await provider.complete(
|
|
379
556
|
{
|
|
380
557
|
messages,
|
|
381
|
-
|
|
558
|
+
// 8192 for local thinking models (qwen3:4b uses ~3800 tokens on thinking
|
|
559
|
+
// before the tool-call JSON; 4096 cuts off mid-generation). Frontier
|
|
560
|
+
// models ignore this ceiling and stop naturally earlier.
|
|
561
|
+
maxTokens: 8192,
|
|
382
562
|
temperature: 0.4,
|
|
383
|
-
tools:
|
|
563
|
+
tools: activeTools
|
|
384
564
|
},
|
|
385
565
|
identity.llmModel
|
|
386
566
|
);
|
|
@@ -391,7 +571,12 @@ var AgentRunner = class {
|
|
|
391
571
|
totalTokens: aggUsage.totalTokens + resp.usage.totalTokens
|
|
392
572
|
};
|
|
393
573
|
if (resp.finishReason === "tool_use" && resp.toolUses && resp.toolUses.length > 0) {
|
|
394
|
-
log({
|
|
574
|
+
log({
|
|
575
|
+
ev: "tool-call",
|
|
576
|
+
taskId: target.id,
|
|
577
|
+
iter: iters,
|
|
578
|
+
tools: resp.toolUses.map((t) => t.name)
|
|
579
|
+
});
|
|
395
580
|
for (const u of resp.toolUses) {
|
|
396
581
|
toolsCalled.add(u.name);
|
|
397
582
|
if (u.name === "write_file") {
|
|
@@ -400,6 +585,8 @@ var AgentRunner = class {
|
|
|
400
585
|
} else if (u.name === "bash") {
|
|
401
586
|
const cmd = String(u.input?.cmd ?? "");
|
|
402
587
|
if (isProductiveBashCommand(cmd)) productiveCallCount++;
|
|
588
|
+
} else if (u.name === "emit_hardware_receipt") {
|
|
589
|
+
productiveCallCount++;
|
|
403
590
|
}
|
|
404
591
|
}
|
|
405
592
|
messages.push({
|
|
@@ -444,6 +631,58 @@ var AgentRunner = class {
|
|
|
444
631
|
message: `no productive tool call observed (toolsCalled=[${[...toolsCalled].join(",")}], productiveCallCount=${productiveCallCount}, iters=${iters})`
|
|
445
632
|
};
|
|
446
633
|
}
|
|
634
|
+
let reflectVerdict;
|
|
635
|
+
if (brain.reflect) {
|
|
636
|
+
try {
|
|
637
|
+
const reflectResp = await provider.complete(
|
|
638
|
+
{
|
|
639
|
+
messages: [
|
|
640
|
+
{
|
|
641
|
+
role: "system",
|
|
642
|
+
content: "You are a strict reviewer. Evaluate the work against the criteria; do not rewrite it."
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
role: "user",
|
|
646
|
+
content: `Reflect on the artifact produced for this task. Evaluate it for: ${brain.reflect.criteria}.
|
|
647
|
+
|
|
648
|
+
--- artifact / final response ---
|
|
649
|
+
${finalText.slice(0, 4e3)}
|
|
650
|
+
--- end ---
|
|
651
|
+
|
|
652
|
+
Give a one-line reason, then end with exactly "VERDICT: PASS" or "VERDICT: FAIL".`
|
|
653
|
+
}
|
|
654
|
+
],
|
|
655
|
+
maxTokens: 512,
|
|
656
|
+
temperature: 0.1
|
|
657
|
+
},
|
|
658
|
+
identity.llmModel
|
|
659
|
+
);
|
|
660
|
+
aggUsage = {
|
|
661
|
+
promptTokens: aggUsage.promptTokens + reflectResp.usage.promptTokens,
|
|
662
|
+
completionTokens: aggUsage.completionTokens + reflectResp.usage.completionTokens,
|
|
663
|
+
totalTokens: aggUsage.totalTokens + reflectResp.usage.totalTokens
|
|
664
|
+
};
|
|
665
|
+
const verdictMatch = /VERDICT:\s*(PASS|FAIL)/i.exec(reflectResp.content);
|
|
666
|
+
const pass = verdictMatch ? verdictMatch[1].toUpperCase() === "PASS" : true;
|
|
667
|
+
reflectVerdict = {
|
|
668
|
+
pass,
|
|
669
|
+
reason: reflectResp.content.replace(/VERDICT:\s*(PASS|FAIL)/i, "").trim().slice(0, 300)
|
|
670
|
+
};
|
|
671
|
+
log({
|
|
672
|
+
ev: "reflect",
|
|
673
|
+
taskId: target.id,
|
|
674
|
+
pass,
|
|
675
|
+
escalateOnFail: brain.reflect.escalateOnFail,
|
|
676
|
+
reason: reflectVerdict.reason.slice(0, 120)
|
|
677
|
+
});
|
|
678
|
+
} catch (err) {
|
|
679
|
+
log({
|
|
680
|
+
ev: "reflect-error",
|
|
681
|
+
taskId: target.id,
|
|
682
|
+
message: err instanceof Error ? err.message : String(err)
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
}
|
|
447
686
|
const cost = costGuard.recordUsage(identity.llmModel, aggUsage);
|
|
448
687
|
log({
|
|
449
688
|
ev: "executed",
|
|
@@ -453,7 +692,11 @@ var AgentRunner = class {
|
|
|
453
692
|
tokens: aggUsage.totalTokens,
|
|
454
693
|
tool_iters: iters
|
|
455
694
|
});
|
|
456
|
-
const response = {
|
|
695
|
+
const response = {
|
|
696
|
+
...lastResponse ?? { content: finalText, usage: aggUsage },
|
|
697
|
+
content: finalText,
|
|
698
|
+
usage: aggUsage
|
|
699
|
+
};
|
|
457
700
|
const execResult = {
|
|
458
701
|
taskId: target.id,
|
|
459
702
|
responseText: response.content,
|
|
@@ -487,10 +730,32 @@ var AgentRunner = class {
|
|
|
487
730
|
});
|
|
488
731
|
const posted = await mesh.postAuditRecords(identity.handle, [caelRecord]);
|
|
489
732
|
this.prevCaelChain = caelRecord.fnv1a_chain;
|
|
490
|
-
log({
|
|
733
|
+
log({
|
|
734
|
+
ev: "cael-posted",
|
|
735
|
+
taskId: target.id,
|
|
736
|
+
appended: posted.appended,
|
|
737
|
+
rejected: posted.rejected
|
|
738
|
+
});
|
|
491
739
|
} catch (err) {
|
|
492
740
|
log({ ev: "cael-post-error", message: err instanceof Error ? err.message : String(err) });
|
|
493
741
|
}
|
|
742
|
+
if (reflectVerdict && !reflectVerdict.pass && brain.reflect?.escalateOnFail) {
|
|
743
|
+
try {
|
|
744
|
+
await mesh.sendMessageOnTask(
|
|
745
|
+
target.id,
|
|
746
|
+
`[${identity.handle}] reflect gate FAILED \u2014 escalating to the fleet instead of marking done. Reason: ${reflectVerdict.reason}`
|
|
747
|
+
);
|
|
748
|
+
} catch {
|
|
749
|
+
}
|
|
750
|
+
log({ ev: "reflect-escalate", taskId: target.id, reason: reflectVerdict.reason.slice(0, 120) });
|
|
751
|
+
return {
|
|
752
|
+
action: "reflect-escalate",
|
|
753
|
+
taskId: target.id,
|
|
754
|
+
spentUsd: costGuard.getState().spentUsd,
|
|
755
|
+
remainingUsd: costGuard.getRemainingUsd(),
|
|
756
|
+
message: `reflect self-evaluation failed; escalated to fleet (reason: ${reflectVerdict.reason.slice(0, 120)})`
|
|
757
|
+
};
|
|
758
|
+
}
|
|
494
759
|
if (this.opts.onTaskExecuted) {
|
|
495
760
|
await this.opts.onTaskExecuted(execResult, target);
|
|
496
761
|
} else {
|
|
@@ -505,7 +770,11 @@ ${response.content}`
|
|
|
505
770
|
await mesh.markDone(target.id, finalText.slice(0, 500), lastCommitHash);
|
|
506
771
|
log({ ev: "mark-done", taskId: target.id, commitHash: lastCommitHash });
|
|
507
772
|
} catch (err) {
|
|
508
|
-
log({
|
|
773
|
+
log({
|
|
774
|
+
ev: "mark-done-error",
|
|
775
|
+
taskId: target.id,
|
|
776
|
+
message: err instanceof Error ? err.message : String(err)
|
|
777
|
+
});
|
|
509
778
|
}
|
|
510
779
|
return {
|
|
511
780
|
action: "executed",
|
|
@@ -599,7 +868,7 @@ function buildTaskPrompt(task) {
|
|
|
599
868
|
"Description:",
|
|
600
869
|
task.description ?? "(no description)",
|
|
601
870
|
"",
|
|
602
|
-
"Produce the deliverable
|
|
871
|
+
"Produce the deliverable: call write_file (or bash with a build command) to create all required output files FIRST. Apply your brain composition rules \u2014 anti-patterns, decision loop, and scope tier all bind. After calling the tool(s), return a short plain-text summary of what you did for posting to /room."
|
|
603
872
|
].join("\n");
|
|
604
873
|
}
|
|
605
874
|
function sleep(ms) {
|