@pome-sh/cli 0.42.3 → 0.43.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/dist/build-info.json +3 -3
- package/dist/{checks-BKMTBJSO.js → checks-QYUPKBPT.js} +9 -14
- package/dist/{chunk-VPXIGCZW.js → chunk-3YCX3KUL.js} +1 -1
- package/dist/{chunk-5NPGY73F.js → chunk-5A6HHA54.js} +1 -1
- package/dist/{chunk-6KJC4BTO.js → chunk-5KFDRR53.js} +2 -2
- package/dist/{chunk-NNXVR46L.js → chunk-CS7O2ZXB.js} +114 -2
- package/dist/{chunk-RPPF2KDQ.js → chunk-ELIEDNF3.js} +7 -7
- package/dist/{chunk-STMGQ7GU.js → chunk-OBFHOACQ.js} +1404 -171
- package/dist/{runTrialGroup-BP446HIE.js → runTrialGroup-4QL6S7QO.js} +19 -9
- package/dist/{server-GDK4TBTL.js → server-KC57K5AC.js} +1 -1
- package/dist/src/cli/main.js +511 -235
- package/dist/{src-FUPOFZI4.js → src-5F5OTVYT.js} +1 -1
- package/dist/{src-SNSK5ENY.js → src-7X62AGW7.js} +1 -1
- package/dist/{src-UY4Y75BD.js → src-E7NTZL2F.js} +1 -1
- package/dist/{src-V3RUKDMQ.js → src-ESRY2MC7.js} +1 -1
- package/dist/{src-Z4C4SSDR.js → src-Z63IOSCJ.js} +1 -1
- package/dist/twinHarness-WJFDLEAO.js +6 -0
- package/dist/{twinSeed-X6VHBOLM.js → twinSeed-4VQ7RRCL.js} +1 -1
- package/dist/{twinStart-QQU6OIO4.js → twinStart-QQ64EV3P.js} +4 -4
- package/package.json +1 -2
- package/dist/agent-Q2UTRJYL.js +0 -408
- package/dist/chunk-46POYRLY.js +0 -1240
- package/dist/chunk-DFOQGAKS.js +0 -116
- package/dist/chunk-JNXZBK3O.js +0 -17
- package/dist/chunk-WERG4AUH.js +0 -462
- package/dist/chunk-ZX4WNSZ5.js +0 -71
- package/dist/runDemo-DRDMCG25.js +0 -408
- package/dist/twinHarness-ATYQTDA5.js +0 -6
package/dist/src/cli/main.js
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DEFAULT_CONTROL_PLANE_URL, DEFAULT_DASHBOARD_URL, clearLocalCredentials, friendlyHostedError, SESSION_TWIN_NAMES, runSessionCreate, runSessionList, runSessionStop, resolveCredentials, runTaskHosted, discoverRunSet, persistCredentialsAfterLogin, DEFAULT_DOCS_SITE_ORIGIN, resolveSeams, resolveCachedAgentId, readLinkCache, postAgentResolver, writeLinkCache, ensurePomeGitignored } from '../../chunk-
|
|
3
|
-
import
|
|
4
|
-
import { parseTaskFile, scoreStatus, runScoreLine, narratorReadingLines, readLatestRun, readMetaSummary, outcomeOf, readConfigTwins, scoreCountsSummary, criterionRowLine, readCodeCriteria, createHostedClient, redactJsonl, scoreFromFinalizeResponse, parseGitHubSeedState, uploadRunBlobs, isPreSatisfied } from '../../chunk-STMGQ7GU.js';
|
|
5
|
-
import '../../chunk-VPXIGCZW.js';
|
|
2
|
+
import { DEFAULT_CONTROL_PLANE_URL, DEFAULT_DASHBOARD_URL, clearLocalCredentials, friendlyHostedError, SESSION_TWIN_NAMES, runSessionCreate, runSessionList, runSessionStop, resolveCredentials, parseTaskFile, runTaskHosted, scoreStatus, runScoreLine, narratorReadingLines, readLatestRun, readMetaSummary, discoverRunSet, outcomeOf, persistCredentialsAfterLogin, DEFAULT_DOCS_SITE_ORIGIN, resolveSeams, readConfigTwins, resolveCachedAgentId, readLinkCache, seedStateForTwin, runAgentCommand, scoreCountsSummary, criterionRowLine, readCodeCriteria, postAgentResolver, writeLinkCache, ensurePomeGitignored, writeRunArtifactsCore, createHostedClient, redactJsonl, scoreFromFinalizeResponse, parseGitHubSeedState, uploadRunBlobs, isPreSatisfied } from '../../chunk-OBFHOACQ.js';
|
|
3
|
+
import '../../chunk-3YCX3KUL.js';
|
|
6
4
|
import '../../chunk-NW7HGA2K.js';
|
|
7
5
|
import { GMAIL_CHECKS } from '../../chunk-JM6VS62R.js';
|
|
8
6
|
import '../../chunk-PASFBRK4.js';
|
|
9
7
|
import { LINEAR_CHECKS } from '../../chunk-TSOSDKXP.js';
|
|
10
8
|
import '../../chunk-3FZY376K.js';
|
|
11
|
-
import '../../chunk-
|
|
12
|
-
import { TWIN_NAME_LIST, TWIN_REGISTRY, createGitHubSmokeApp } from '../../chunk-
|
|
13
|
-
import {
|
|
14
|
-
import '../../chunk-
|
|
15
|
-
import {
|
|
16
|
-
import '../../chunk-CBFKZZBR.js';
|
|
9
|
+
import { createRecorder, bootTwin } from '../../chunk-5A6HHA54.js';
|
|
10
|
+
import { TWIN_NAME_LIST, TWIN_REGISTRY, createGitHubSmokeApp } from '../../chunk-ELIEDNF3.js';
|
|
11
|
+
import { getAvailablePort } from '../../chunk-XDU6TD4O.js';
|
|
12
|
+
import { MOUNTED_TWINS, readManifest, deriveAgentSlug, writeManifest, MANIFEST_JSON, exitCodeFor, readRequiredManifest, HostedUsageError, HostedOrchError, normalizeManifestTwins } from '../../chunk-CS7O2ZXB.js';
|
|
13
|
+
import { buildEgressAllowlist, readBlockedEgress } from '../../chunk-CBFKZZBR.js';
|
|
17
14
|
import { TAPE_ASSERTABLE_TOOLS } from '../../chunk-C4BVTUA3.js';
|
|
18
15
|
import { seedSchema } from '../../chunk-NBOQN5VX.js';
|
|
19
16
|
import { SLACK_CHECKS } from '../../chunk-2ZGVDTJC.js';
|
|
20
17
|
import { oneOf, defineCheck, repoRef, VACUITY_SENTINEL_NUMBER, childStatePath, VACUITY_SENTINEL, statePath, templateSlots, renderCheck, checksDigest, checkPattern, checkNearMissPattern } from '../../chunk-JWJYNAWI.js';
|
|
21
18
|
import '../../chunk-YBWG5JK2.js';
|
|
22
19
|
import '../../chunk-HRAD7MRX.js';
|
|
23
|
-
import { eventSchema, isLegacyEventRow } from '../../chunk-
|
|
20
|
+
import { eventSchema, isLegacyEventRow } from '../../chunk-5KFDRR53.js';
|
|
24
21
|
import { redactSecrets, redactEvent } from '../../chunk-SG6ZTIMT.js';
|
|
25
22
|
import '../../chunk-2K6BJ3PI.js';
|
|
26
23
|
import '../../chunk-FBSA5L36.js';
|
|
27
24
|
import { Command } from 'commander';
|
|
28
25
|
import { existsSync, readFileSync, realpathSync, statSync, constants } from 'node:fs';
|
|
29
26
|
import { mkdir, readFile, writeFile, readdir, copyFile, stat, cp } from 'node:fs/promises';
|
|
30
|
-
import { dirname, resolve, join, basename, relative, extname } from 'node:path';
|
|
27
|
+
import { dirname, resolve, join, basename, relative, sep, extname } from 'node:path';
|
|
31
28
|
import { fileURLToPath } from 'node:url';
|
|
32
|
-
import {
|
|
33
|
-
import { randomBytes, createHash } from 'node:crypto';
|
|
29
|
+
import { serve } from '@hono/node-server';
|
|
30
|
+
import { randomBytes, randomUUID, createHash } from 'node:crypto';
|
|
31
|
+
import { sign } from 'hono/jwt';
|
|
32
|
+
import { execFile, spawn } from 'node:child_process';
|
|
34
33
|
import { createServer } from 'node:http';
|
|
35
34
|
import { createInterface } from 'node:readline';
|
|
36
35
|
import { z } from 'zod';
|
|
37
36
|
import Anthropic from '@anthropic-ai/sdk';
|
|
37
|
+
import { createRequire } from 'node:module';
|
|
38
38
|
|
|
39
39
|
async function readEventsJsonl(runDir) {
|
|
40
40
|
const file = resolve(runDir, "events.jsonl");
|
|
@@ -202,6 +202,419 @@ function renderLlmTurn(event2) {
|
|
|
202
202
|
` cache read/create=${cache}${finish}`
|
|
203
203
|
];
|
|
204
204
|
}
|
|
205
|
+
var LISTENING_REGEX = /listening on 127\.0\.0\.1:(\d+)/;
|
|
206
|
+
async function spawnCaptureServerChild(options) {
|
|
207
|
+
const execPath = options.execPath ?? process.execPath;
|
|
208
|
+
const binArgs = options.binArgs ?? defaultBinArgs(options.eventsOut, options.allowHosts, options.egressOut);
|
|
209
|
+
const readyTimeoutMs = options.readyTimeoutMs ?? 5e3;
|
|
210
|
+
const shutdownGraceMs = options.shutdownGraceMs ?? 5e3;
|
|
211
|
+
const child = spawn(execPath, binArgs, {
|
|
212
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
213
|
+
env: process.env
|
|
214
|
+
});
|
|
215
|
+
let stderrBuf = "";
|
|
216
|
+
let resolved = false;
|
|
217
|
+
const readyPromise = new Promise((resolve7, reject) => {
|
|
218
|
+
const onData = (chunk) => {
|
|
219
|
+
stderrBuf += chunk.toString("utf8");
|
|
220
|
+
if (stderrBuf.length > 8192) stderrBuf = stderrBuf.slice(-8192);
|
|
221
|
+
const match = LISTENING_REGEX.exec(stderrBuf);
|
|
222
|
+
if (match) {
|
|
223
|
+
resolved = true;
|
|
224
|
+
cleanup();
|
|
225
|
+
resolve7(Number(match[1]));
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const onExit = (code, signal) => {
|
|
229
|
+
if (resolved) return;
|
|
230
|
+
cleanup();
|
|
231
|
+
const detail = stderrBuf.trim().slice(0, 2048);
|
|
232
|
+
reject(
|
|
233
|
+
new Error(
|
|
234
|
+
`pome capture-server exited (code=${code} signal=${signal}) before printing the listening line.${detail ? `
|
|
235
|
+
stderr: ${detail}` : ""}`
|
|
236
|
+
)
|
|
237
|
+
);
|
|
238
|
+
};
|
|
239
|
+
const timer = setTimeout(() => {
|
|
240
|
+
if (resolved) return;
|
|
241
|
+
cleanup();
|
|
242
|
+
reject(
|
|
243
|
+
new Error(
|
|
244
|
+
`pome capture-server did not start listening within ${readyTimeoutMs}ms.${stderrBuf ? `
|
|
245
|
+
stderr: ${stderrBuf.trim().slice(0, 2048)}` : ""}`
|
|
246
|
+
)
|
|
247
|
+
);
|
|
248
|
+
}, readyTimeoutMs);
|
|
249
|
+
const cleanup = () => {
|
|
250
|
+
clearTimeout(timer);
|
|
251
|
+
child.stderr?.off("data", onData);
|
|
252
|
+
child.off("exit", onExit);
|
|
253
|
+
};
|
|
254
|
+
child.stderr?.on("data", onData);
|
|
255
|
+
child.once("exit", onExit);
|
|
256
|
+
});
|
|
257
|
+
let port;
|
|
258
|
+
try {
|
|
259
|
+
port = await readyPromise;
|
|
260
|
+
} catch (err) {
|
|
261
|
+
if (!child.killed) child.kill("SIGKILL");
|
|
262
|
+
throw err;
|
|
263
|
+
}
|
|
264
|
+
child.stderr?.on("data", () => {
|
|
265
|
+
});
|
|
266
|
+
const pid = child.pid;
|
|
267
|
+
if (pid === void 0) {
|
|
268
|
+
child.kill("SIGKILL");
|
|
269
|
+
throw new Error("pome capture-server child has no pid after boot");
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
port,
|
|
273
|
+
pid,
|
|
274
|
+
shutdown: makeShutdown(child, shutdownGraceMs)
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function defaultBinArgs(eventsOut, allowHosts, egressOut) {
|
|
278
|
+
const script = process.argv[1];
|
|
279
|
+
if (typeof script !== "string" || script.length === 0) {
|
|
280
|
+
throw new Error(
|
|
281
|
+
"Cannot determine pome binary path (process.argv[1] is empty). Pass `binArgs` explicitly."
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
return [script, "capture-server", "--port", "0", "--events-out", eventsOut, ...egressArgs(allowHosts, egressOut)];
|
|
285
|
+
}
|
|
286
|
+
function egressArgs(allowHosts, egressOut) {
|
|
287
|
+
const args = [];
|
|
288
|
+
if (allowHosts && allowHosts.length > 0) args.push("--allow", allowHosts.join(","));
|
|
289
|
+
if (egressOut) args.push("--egress-out", egressOut);
|
|
290
|
+
return args;
|
|
291
|
+
}
|
|
292
|
+
function makeShutdown(child, graceMs) {
|
|
293
|
+
let done = null;
|
|
294
|
+
return () => {
|
|
295
|
+
if (done) return done;
|
|
296
|
+
done = new Promise((resolve7) => {
|
|
297
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
298
|
+
resolve7();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const onExit = () => {
|
|
302
|
+
clearTimeout(killTimer);
|
|
303
|
+
resolve7();
|
|
304
|
+
};
|
|
305
|
+
child.once("exit", onExit);
|
|
306
|
+
try {
|
|
307
|
+
child.kill("SIGTERM");
|
|
308
|
+
} catch {
|
|
309
|
+
child.off("exit", onExit);
|
|
310
|
+
resolve7();
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const killTimer = setTimeout(() => {
|
|
314
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
315
|
+
try {
|
|
316
|
+
child.kill("SIGKILL");
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}, graceMs);
|
|
321
|
+
});
|
|
322
|
+
return done;
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function resolveTwinHttpParents(rows) {
|
|
326
|
+
const eventIdByToolUseId = /* @__PURE__ */ new Map();
|
|
327
|
+
for (const row of rows) {
|
|
328
|
+
if (row.kind === "ToolUseEvent") eventIdByToolUseId.set(row.tool_use_id, row.event_id);
|
|
329
|
+
}
|
|
330
|
+
if (eventIdByToolUseId.size === 0) return rows;
|
|
331
|
+
return rows.map((row) => {
|
|
332
|
+
if (row.kind !== "TwinHttpEvent") return row;
|
|
333
|
+
if (row.parent_event_id != null) return row;
|
|
334
|
+
const causingToolUseId = row.tool_call_id ?? row.correlation_id ?? null;
|
|
335
|
+
if (causingToolUseId === null) return row;
|
|
336
|
+
const parentEventId = eventIdByToolUseId.get(causingToolUseId);
|
|
337
|
+
return parentEventId === void 0 ? row : { ...row, parent_event_id: parentEventId };
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
async function mergeAdapterSignalsIntoEvents(signalsPath, eventsJsonlPath) {
|
|
341
|
+
let rawSignals;
|
|
342
|
+
try {
|
|
343
|
+
rawSignals = await readFile(signalsPath, "utf8");
|
|
344
|
+
} catch {
|
|
345
|
+
return { appended: 0, dropped: 0 };
|
|
346
|
+
}
|
|
347
|
+
let dropped = 0;
|
|
348
|
+
const signalRows = [];
|
|
349
|
+
for (const line of rawSignals.split("\n")) {
|
|
350
|
+
if (line.length === 0) continue;
|
|
351
|
+
let parsed;
|
|
352
|
+
try {
|
|
353
|
+
parsed = JSON.parse(line);
|
|
354
|
+
} catch {
|
|
355
|
+
dropped += 1;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const result = eventSchema.safeParse(parsed);
|
|
359
|
+
if (!result.success) {
|
|
360
|
+
dropped += 1;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
signalRows.push(redactEvent(result.data));
|
|
364
|
+
}
|
|
365
|
+
if (signalRows.length === 0) return { appended: 0, dropped };
|
|
366
|
+
let rawEvents;
|
|
367
|
+
try {
|
|
368
|
+
rawEvents = await readFile(eventsJsonlPath, "utf8");
|
|
369
|
+
} catch {
|
|
370
|
+
rawEvents = "";
|
|
371
|
+
}
|
|
372
|
+
const eventRows = [];
|
|
373
|
+
const unparseablePassthrough = [];
|
|
374
|
+
for (const line of rawEvents.split("\n")) {
|
|
375
|
+
if (line.length === 0) continue;
|
|
376
|
+
let parsed;
|
|
377
|
+
try {
|
|
378
|
+
parsed = JSON.parse(line);
|
|
379
|
+
} catch {
|
|
380
|
+
unparseablePassthrough.push(line);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
const result = eventSchema.safeParse(parsed);
|
|
384
|
+
if (result.success) {
|
|
385
|
+
eventRows.push(redactEvent(result.data));
|
|
386
|
+
} else {
|
|
387
|
+
unparseablePassthrough.push(line);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const merged = resolveTwinHttpParents(eventRows.concat(signalRows));
|
|
391
|
+
merged.sort((a, b) => a.ts < b.ts ? -1 : a.ts > b.ts ? 1 : 0);
|
|
392
|
+
const sortedJsonl = merged.map((r) => JSON.stringify(r)).join("\n");
|
|
393
|
+
const head = unparseablePassthrough.length > 0 ? unparseablePassthrough.join("\n") + "\n" : "";
|
|
394
|
+
await writeFile(eventsJsonlPath, head + sortedJsonl + "\n");
|
|
395
|
+
return { appended: signalRows.length, dropped };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/runner/runTaskCore.ts
|
|
399
|
+
async function writeRunNoScore(input) {
|
|
400
|
+
const artifacts = await writeRunArtifactsCore({
|
|
401
|
+
artifactsDir: input.artifactsDir,
|
|
402
|
+
runId: input.runId,
|
|
403
|
+
scenario: input.scenario,
|
|
404
|
+
startedAt: input.startedAt,
|
|
405
|
+
completedAt: input.completedAt,
|
|
406
|
+
stdout: input.agentStdout,
|
|
407
|
+
stderr: input.agentStderr,
|
|
408
|
+
exitCode: input.agentExitCode,
|
|
409
|
+
events: input.events,
|
|
410
|
+
stateInitial: input.stateInitial,
|
|
411
|
+
stateFinal: input.stateFinal
|
|
412
|
+
});
|
|
413
|
+
const exitCode = input.agentExitCode !== 0 || input.agentTimedOut ? 3 : 0;
|
|
414
|
+
return { artifacts, exitCode };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/runner/runTask.ts
|
|
418
|
+
function closeServer(server) {
|
|
419
|
+
return new Promise((resolvePromise, reject) => {
|
|
420
|
+
server.close((err) => err ? reject(err) : resolvePromise());
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
async function runTask(options) {
|
|
424
|
+
const scenario = await parseTaskFile(options.taskPath);
|
|
425
|
+
const runId = `run_${randomUUID()}`;
|
|
426
|
+
const artifactsDir = options.artifactsDir ?? "runs";
|
|
427
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
428
|
+
const writeRun = writeRunNoScore;
|
|
429
|
+
const runDir = join(artifactsDir, scenario.slug, runId);
|
|
430
|
+
await mkdir(runDir, { recursive: true });
|
|
431
|
+
const signalsPath = join(runDir, "signals.jsonl");
|
|
432
|
+
await writeFile(signalsPath, "");
|
|
433
|
+
const signalsPathForEnv = sep === "\\" ? signalsPath.replace(/\\/g, "/") : signalsPath;
|
|
434
|
+
const eventsJsonlPath = join(runDir, "events.jsonl");
|
|
435
|
+
await writeFile(eventsJsonlPath, "");
|
|
436
|
+
const egressAllowHosts = buildEgressAllowlist(process.env, {
|
|
437
|
+
extraHosts: options.egressExtraHosts
|
|
438
|
+
});
|
|
439
|
+
const egressJsonlPath = join(runDir, "egress.jsonl");
|
|
440
|
+
const captureServer = options.noCapture ? null : await spawnCaptureServerChild({
|
|
441
|
+
eventsOut: resolve(eventsJsonlPath),
|
|
442
|
+
allowHosts: egressAllowHosts,
|
|
443
|
+
egressOut: resolve(egressJsonlPath),
|
|
444
|
+
execPath: options.captureServerCommand?.execPath,
|
|
445
|
+
binArgs: options.captureServerCommand ? [
|
|
446
|
+
...options.captureServerCommand.prefixArgs,
|
|
447
|
+
"capture-server",
|
|
448
|
+
"--port",
|
|
449
|
+
"0",
|
|
450
|
+
"--events-out",
|
|
451
|
+
resolve(eventsJsonlPath),
|
|
452
|
+
...egressArgs(egressAllowHosts, resolve(egressJsonlPath))
|
|
453
|
+
] : void 0
|
|
454
|
+
});
|
|
455
|
+
if (captureServer) options.onCaptureServerSpawned?.(captureServer.pid);
|
|
456
|
+
const collectBlockedEgress = async () => {
|
|
457
|
+
if (!captureServer) return [];
|
|
458
|
+
await captureServer.shutdown();
|
|
459
|
+
return readBlockedEgress(egressJsonlPath);
|
|
460
|
+
};
|
|
461
|
+
const twins = scenario.config.twins.length ? scenario.config.twins : ["github"];
|
|
462
|
+
const primaryTwin2 = twins[0];
|
|
463
|
+
const isMultiTwin = twins.length > 1;
|
|
464
|
+
const sid = runId;
|
|
465
|
+
const authSecret = process.env.TWIN_AUTH_SECRET ?? randomBytes(32).toString("hex");
|
|
466
|
+
process.env.TWIN_AUTH_SECRET = authSecret;
|
|
467
|
+
const recorder = createRecorder({ eventsPath: eventsJsonlPath });
|
|
468
|
+
const booted = [];
|
|
469
|
+
const twinEnv = {};
|
|
470
|
+
const tokenEnvNames = /* @__PURE__ */ new Set();
|
|
471
|
+
const stateInitialByTwin = {};
|
|
472
|
+
let mergedClaims = {};
|
|
473
|
+
for (const twin of twins) {
|
|
474
|
+
const port = await getAvailablePort();
|
|
475
|
+
const baseUrl = `http://127.0.0.1:${port}`;
|
|
476
|
+
const harness = await bootTwin({
|
|
477
|
+
twin,
|
|
478
|
+
seedState: seedStateForTwin(scenario, twin),
|
|
479
|
+
runId,
|
|
480
|
+
twinBaseUrl: baseUrl,
|
|
481
|
+
recorder
|
|
482
|
+
});
|
|
483
|
+
stateInitialByTwin[twin] = await harness.exportState();
|
|
484
|
+
const sessionBase = `${baseUrl}/s/${sid}`;
|
|
485
|
+
twinEnv[`POME_${harness.envName}_REST_URL`] = sessionBase;
|
|
486
|
+
twinEnv[`POME_${harness.envName}_MCP_URL`] = `${sessionBase}/mcp`;
|
|
487
|
+
if (harness.tokenEnvName) tokenEnvNames.add(harness.tokenEnvName);
|
|
488
|
+
mergedClaims = { ...mergedClaims, ...harness.extraClaims ?? {} };
|
|
489
|
+
const server = serve({ fetch: harness.app.fetch, port, hostname: "127.0.0.1" });
|
|
490
|
+
booted.push({ twin, harness, server });
|
|
491
|
+
}
|
|
492
|
+
const stateInitial = stateInitialByTwin[primaryTwin2];
|
|
493
|
+
const token = await sign(
|
|
494
|
+
{
|
|
495
|
+
sid,
|
|
496
|
+
team_id: "tm_local",
|
|
497
|
+
// The self-host agent acts as `pome-agent`, the collaborator the twin
|
|
498
|
+
// seeds onto every repo. Without a login claim the REST merge gate
|
|
499
|
+
// (which checks the authenticated user has push access) rejects merges —
|
|
500
|
+
// the MCP merge path calls the domain directly and never hit this, so it
|
|
501
|
+
// was invisible until a scripted REST agent needed to merge.
|
|
502
|
+
login: "pome-agent",
|
|
503
|
+
// Twin-supplied claims (e.g. Stripe's `account_id`, so the token resolves
|
|
504
|
+
// to the account the seed data lives in) — merged across all twins.
|
|
505
|
+
// github/slack supply none.
|
|
506
|
+
...mergedClaims,
|
|
507
|
+
exp: Math.floor(Date.now() / 1e3) + Math.max(scenario.config.timeout * 2, 600)
|
|
508
|
+
},
|
|
509
|
+
authSecret
|
|
510
|
+
);
|
|
511
|
+
const proxyEnv = captureServer ? {
|
|
512
|
+
// Agent's outbound traffic flows through the capture-server.
|
|
513
|
+
// Twin traffic is localhost, NO_PROXY excludes it so it stays a
|
|
514
|
+
// TwinHttpEvent (recorded in-process) and isn't double-counted as a
|
|
515
|
+
// proxy-captured LlmCallEvent.
|
|
516
|
+
HTTP_PROXY: `http://127.0.0.1:${captureServer.port}`,
|
|
517
|
+
HTTPS_PROXY: `http://127.0.0.1:${captureServer.port}`,
|
|
518
|
+
NO_PROXY: "127.0.0.1,localhost"
|
|
519
|
+
} : {};
|
|
520
|
+
for (const name of tokenEnvNames) twinEnv[name] = token;
|
|
521
|
+
const env = {
|
|
522
|
+
POME_TASK: scenario.prompt,
|
|
523
|
+
POME_TWIN_NAMES: twins.join(","),
|
|
524
|
+
...twinEnv,
|
|
525
|
+
POME_AUTH_TOKEN: token,
|
|
526
|
+
POME_RUN_ID: runId,
|
|
527
|
+
POME_ARTIFACTS_DIR: runDir,
|
|
528
|
+
POME_ADAPTER_SIGNALS_PATH: signalsPathForEnv,
|
|
529
|
+
...options.extraAgentEnv ?? {},
|
|
530
|
+
...proxyEnv
|
|
531
|
+
};
|
|
532
|
+
const captureFinalStates = async () => {
|
|
533
|
+
const finals = {};
|
|
534
|
+
for (const { twin, harness } of booted) {
|
|
535
|
+
finals[twin] = await harness.exportState();
|
|
536
|
+
}
|
|
537
|
+
if (isMultiTwin) {
|
|
538
|
+
for (const { twin } of booted) {
|
|
539
|
+
if (twin === primaryTwin2) continue;
|
|
540
|
+
try {
|
|
541
|
+
await writeFile(
|
|
542
|
+
join(runDir, `state_final.${twin}.json`),
|
|
543
|
+
`${JSON.stringify(redactSecrets(finals[twin]), null, 2)}
|
|
544
|
+
`
|
|
545
|
+
);
|
|
546
|
+
} catch {
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return finals[primaryTwin2];
|
|
551
|
+
};
|
|
552
|
+
try {
|
|
553
|
+
const preflight = await runAgentCommand({
|
|
554
|
+
command: options.agentCommand,
|
|
555
|
+
env,
|
|
556
|
+
// Cold agent startup can exceed 10s on CI; cap preflight below full scenario
|
|
557
|
+
// timeout but leave enough headroom for toolchain startup.
|
|
558
|
+
timeoutSeconds: Math.min(60, scenario.config.timeout),
|
|
559
|
+
preflight: true
|
|
560
|
+
});
|
|
561
|
+
if (preflight.exitCode !== 0) {
|
|
562
|
+
await recorder.flush?.();
|
|
563
|
+
const stateFinal2 = await captureFinalStates();
|
|
564
|
+
const { artifacts: artifacts2, exitCode: exitCode2 } = await writeRun({
|
|
565
|
+
artifactsDir,
|
|
566
|
+
runId,
|
|
567
|
+
scenario,
|
|
568
|
+
startedAt,
|
|
569
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
570
|
+
agentStdout: preflight.stdout,
|
|
571
|
+
agentStderr: preflight.stderr,
|
|
572
|
+
agentExitCode: preflight.exitCode,
|
|
573
|
+
agentTimedOut: preflight.timedOut ?? false,
|
|
574
|
+
events: recorder.events(),
|
|
575
|
+
stateInitial,
|
|
576
|
+
stateFinal: stateFinal2
|
|
577
|
+
});
|
|
578
|
+
await mergeAdapterSignalsIntoEvents(signalsPath, eventsJsonlPath);
|
|
579
|
+
void exitCode2;
|
|
580
|
+
const blockedEgress2 = await collectBlockedEgress();
|
|
581
|
+
return { scenario, runId, artifacts: artifacts2, agent: preflight, exitCode: 3, blockedEgress: blockedEgress2 };
|
|
582
|
+
}
|
|
583
|
+
const agent = await runAgentCommand({
|
|
584
|
+
command: options.agentCommand,
|
|
585
|
+
env,
|
|
586
|
+
timeoutSeconds: scenario.config.timeout
|
|
587
|
+
});
|
|
588
|
+
await recorder.flush?.();
|
|
589
|
+
const stateFinal = await captureFinalStates();
|
|
590
|
+
const { artifacts, exitCode } = await writeRun({
|
|
591
|
+
artifactsDir,
|
|
592
|
+
runId,
|
|
593
|
+
scenario,
|
|
594
|
+
startedAt,
|
|
595
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
596
|
+
agentStdout: agent.stdout,
|
|
597
|
+
agentStderr: agent.stderr,
|
|
598
|
+
agentExitCode: agent.exitCode,
|
|
599
|
+
agentTimedOut: agent.timedOut ?? false,
|
|
600
|
+
events: recorder.events(),
|
|
601
|
+
stateInitial,
|
|
602
|
+
stateFinal
|
|
603
|
+
});
|
|
604
|
+
await mergeAdapterSignalsIntoEvents(signalsPath, eventsJsonlPath);
|
|
605
|
+
const blockedEgress = await collectBlockedEgress();
|
|
606
|
+
return { scenario, runId, artifacts, agent, exitCode, blockedEgress };
|
|
607
|
+
} finally {
|
|
608
|
+
if (captureServer) await captureServer.shutdown();
|
|
609
|
+
await Promise.all(
|
|
610
|
+
booted.map(async ({ server, harness }) => {
|
|
611
|
+
await closeServer(server);
|
|
612
|
+
await harness.close();
|
|
613
|
+
})
|
|
614
|
+
);
|
|
615
|
+
await recorder.close?.();
|
|
616
|
+
}
|
|
617
|
+
}
|
|
205
618
|
|
|
206
619
|
// src/runner/trialCount.ts
|
|
207
620
|
var MAX_TRIALS = 20;
|
|
@@ -309,8 +722,8 @@ async function startCallbackServer(expectedState) {
|
|
|
309
722
|
let rejectCode = null;
|
|
310
723
|
let settled = false;
|
|
311
724
|
let deliveredCode = false;
|
|
312
|
-
const codePromise = new Promise((
|
|
313
|
-
resolveCode =
|
|
725
|
+
const codePromise = new Promise((resolve7, reject) => {
|
|
726
|
+
resolveCode = resolve7;
|
|
314
727
|
rejectCode = reject;
|
|
315
728
|
});
|
|
316
729
|
const fail = (err) => {
|
|
@@ -334,7 +747,7 @@ async function startCallbackServer(expectedState) {
|
|
|
334
747
|
res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
|
|
335
748
|
res.end("<h1>Pome login failed</h1><p>You can close this tab.</p>");
|
|
336
749
|
fail(new Error(`Browser reported error: ${error}`));
|
|
337
|
-
void
|
|
750
|
+
void closeServer2(server);
|
|
338
751
|
return;
|
|
339
752
|
}
|
|
340
753
|
if (!code || state !== expectedState) {
|
|
@@ -352,11 +765,11 @@ async function startCallbackServer(expectedState) {
|
|
|
352
765
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
353
766
|
res.end("<h1>Pome login complete</h1><p>You can close this tab.</p>");
|
|
354
767
|
succeed(code);
|
|
355
|
-
void
|
|
768
|
+
void closeServer2(server);
|
|
356
769
|
});
|
|
357
|
-
await new Promise((
|
|
770
|
+
await new Promise((resolve7, reject) => {
|
|
358
771
|
server?.once("error", reject);
|
|
359
|
-
server?.listen(0, "127.0.0.1",
|
|
772
|
+
server?.listen(0, "127.0.0.1", resolve7);
|
|
360
773
|
});
|
|
361
774
|
const address = server.address();
|
|
362
775
|
if (!address || typeof address === "string") {
|
|
@@ -368,12 +781,12 @@ async function startCallbackServer(expectedState) {
|
|
|
368
781
|
"Timed out waiting for browser login (5 min). Run `pome login` again."
|
|
369
782
|
)
|
|
370
783
|
);
|
|
371
|
-
void
|
|
784
|
+
void closeServer2(server);
|
|
372
785
|
}, 5 * 60 * 1e3);
|
|
373
786
|
timeout.unref();
|
|
374
787
|
const onInt = () => {
|
|
375
788
|
fail(new Error("Login cancelled (Ctrl+C). No credentials were changed."));
|
|
376
|
-
void
|
|
789
|
+
void closeServer2(server);
|
|
377
790
|
};
|
|
378
791
|
process.once("SIGINT", onInt);
|
|
379
792
|
codePromise.finally(() => {
|
|
@@ -388,26 +801,26 @@ async function startCallbackServer(expectedState) {
|
|
|
388
801
|
async close() {
|
|
389
802
|
clearTimeout(timeout);
|
|
390
803
|
process.off("SIGINT", onInt);
|
|
391
|
-
await
|
|
804
|
+
await closeServer2(server);
|
|
392
805
|
}
|
|
393
806
|
};
|
|
394
807
|
}
|
|
395
|
-
function
|
|
396
|
-
return new Promise((
|
|
397
|
-
server?.close(() =>
|
|
808
|
+
function closeServer2(server) {
|
|
809
|
+
return new Promise((resolve7) => {
|
|
810
|
+
server?.close(() => resolve7());
|
|
398
811
|
});
|
|
399
812
|
}
|
|
400
813
|
async function openBrowser(url) {
|
|
401
814
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "powershell.exe" : "xdg-open";
|
|
402
815
|
const args = process.platform === "win32" ? ["-NoProfile", "-Command", "Start-Process", url] : [url];
|
|
403
|
-
await new Promise((
|
|
816
|
+
await new Promise((resolve7) => {
|
|
404
817
|
execFile(command, args, (error) => {
|
|
405
818
|
if (error) {
|
|
406
819
|
console.error(
|
|
407
820
|
"Could not open a browser automatically \u2014 copy the URL above into a browser."
|
|
408
821
|
);
|
|
409
822
|
}
|
|
410
|
-
|
|
823
|
+
resolve7();
|
|
411
824
|
});
|
|
412
825
|
});
|
|
413
826
|
}
|
|
@@ -616,7 +1029,7 @@ function topicMatchesFilter(t, filter) {
|
|
|
616
1029
|
return t.id.includes(f) || t.title.toLowerCase().includes(f) || t.keywords.some((k) => k.toLowerCase().includes(f)) || t.path.toLowerCase().includes(f);
|
|
617
1030
|
}
|
|
618
1031
|
async function promptLine(rl, q) {
|
|
619
|
-
return await new Promise((
|
|
1032
|
+
return await new Promise((resolve7) => rl.question(q, resolve7));
|
|
620
1033
|
}
|
|
621
1034
|
function printTopicUrl(topic, site) {
|
|
622
1035
|
console.log(`${site}${topic.path}`);
|
|
@@ -774,7 +1187,7 @@ function extractJsonPayload(text) {
|
|
|
774
1187
|
|
|
775
1188
|
// src/task/seed-verifier.ts
|
|
776
1189
|
async function verifySeedWithTwin(seed) {
|
|
777
|
-
const { GitHubDomain, openGitHubCloneDatabase } = await import('../../src-
|
|
1190
|
+
const { GitHubDomain, openGitHubCloneDatabase } = await import('../../src-E7NTZL2F.js');
|
|
778
1191
|
const db = openGitHubCloneDatabase(":memory:");
|
|
779
1192
|
try {
|
|
780
1193
|
new GitHubDomain(db).seed(seed);
|
|
@@ -975,6 +1388,16 @@ function statusStamp(status) {
|
|
|
975
1388
|
return "FAIL";
|
|
976
1389
|
}
|
|
977
1390
|
}
|
|
1391
|
+
function resolvePackageRoot(importMetaUrl) {
|
|
1392
|
+
let dir = dirname(fileURLToPath(importMetaUrl));
|
|
1393
|
+
for (let i = 0; i < 6; i++) {
|
|
1394
|
+
if (existsSync(join(dir, "package.json"))) return dir;
|
|
1395
|
+
const parent = resolve(dir, "..");
|
|
1396
|
+
if (parent === dir) break;
|
|
1397
|
+
dir = parent;
|
|
1398
|
+
}
|
|
1399
|
+
return void 0;
|
|
1400
|
+
}
|
|
978
1401
|
|
|
979
1402
|
// src/cli/tasks-catalog.ts
|
|
980
1403
|
var TASK_TWINS = [
|
|
@@ -2970,15 +3393,27 @@ function displaySentence(def) {
|
|
|
2970
3393
|
function localDigest(twin) {
|
|
2971
3394
|
return checksDigest(checksFor(twin));
|
|
2972
3395
|
}
|
|
3396
|
+
function bakedVersions() {
|
|
3397
|
+
try {
|
|
3398
|
+
return JSON.parse('{"@pome-sh/sdk":"0.11.6","@pome-sh/wire":"0.4.1","@pome-sh/twin-github":"0.12.0","@pome-sh/twin-gmail":"0.4.0","@pome-sh/twin-linear":"0.4.1","@pome-sh/twin-slack":"0.4.1","@pome-sh/twin-stripe":"0.4.7"}');
|
|
3399
|
+
} catch {
|
|
3400
|
+
return {};
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
2973
3403
|
function pinnedVersion(pkg) {
|
|
2974
|
-
const
|
|
2975
|
-
if (
|
|
3404
|
+
const baked = bakedVersions()[pkg];
|
|
3405
|
+
if (baked) return baked;
|
|
2976
3406
|
try {
|
|
2977
|
-
const
|
|
2978
|
-
|
|
3407
|
+
const require2 = createRequire(import.meta.url);
|
|
3408
|
+
const manifest = JSON.parse(readFileSync(require2.resolve(`${pkg}/package.json`), "utf8"));
|
|
3409
|
+
if (manifest.name === pkg && typeof manifest.version === "string") return manifest.version;
|
|
2979
3410
|
} catch {
|
|
2980
|
-
return "unknown";
|
|
2981
3411
|
}
|
|
3412
|
+
return void 0;
|
|
3413
|
+
}
|
|
3414
|
+
function pinLabel(pkg) {
|
|
3415
|
+
const version = pinnedVersion(pkg);
|
|
3416
|
+
return version === void 0 ? pkg : `${pkg} ${version}`;
|
|
2982
3417
|
}
|
|
2983
3418
|
var SUBSTRATE_HELP = {
|
|
2984
3419
|
final: "the final state",
|
|
@@ -2997,6 +3432,10 @@ function bold3(s) {
|
|
|
2997
3432
|
function argFlagsFor(def) {
|
|
2998
3433
|
return templateSlots(def.template).params.map((name) => `--arg ${name}=${def.params[name].example}`).join(" ");
|
|
2999
3434
|
}
|
|
3435
|
+
function checksHeader(twin, count2, version) {
|
|
3436
|
+
const label = bold3(`${twin} \u2014 ${count2} declared check${count2 === 1 ? "" : "s"}`);
|
|
3437
|
+
return version === void 0 ? label : `${label} ${dim3(`(@pome-sh/twin-${twin} ${version})`)}`;
|
|
3438
|
+
}
|
|
3000
3439
|
function jsonView(twin) {
|
|
3001
3440
|
return {
|
|
3002
3441
|
twin,
|
|
@@ -3050,9 +3489,7 @@ async function runChecksCommand(twinArg, opts) {
|
|
|
3050
3489
|
);
|
|
3051
3490
|
return;
|
|
3052
3491
|
}
|
|
3053
|
-
console.log(
|
|
3054
|
-
`${bold3(`${twin} \u2014 ${checks.length} declared check${checks.length === 1 ? "" : "s"}`)} ` + dim3(`(@pome-sh/twin-${twin} ${pinnedVersion(`@pome-sh/twin-${twin}`)})`)
|
|
3055
|
-
);
|
|
3492
|
+
console.log(checksHeader(twin, checks.length, pinnedVersion(`@pome-sh/twin-${twin}`)));
|
|
3056
3493
|
console.log("");
|
|
3057
3494
|
for (const def of checks) {
|
|
3058
3495
|
console.log(` ${bold3(def.id)}`);
|
|
@@ -3343,7 +3780,7 @@ function formatSkewRefusal(twin, findings) {
|
|
|
3343
3780
|
`Refusing to write: this CLI and the cloud disagree about ${twin}'s vocabulary, so a sentence written here might not be graded there.`,
|
|
3344
3781
|
...findings.flatMap(bullet),
|
|
3345
3782
|
"",
|
|
3346
|
-
` local
|
|
3783
|
+
` local ${pinLabel(`@pome-sh/twin-${twin}`)}` + (sdkImplicated ? `, ${pinLabel("@pome-sh/sdk")}` : ""),
|
|
3347
3784
|
...sdkImplicated ? [` The cloud publishes no sdk version here, so that is the only pin this CLI can name.`] : [],
|
|
3348
3785
|
` Update with \`npm i -g @pome-sh/cli@latest\`. If this CLI is already current, the cloud is behind \u2014 that is a deploy, not something you can fix here.`
|
|
3349
3786
|
].join("\n");
|
|
@@ -3368,14 +3805,13 @@ function parseArgFlags(pairs) {
|
|
|
3368
3805
|
return args;
|
|
3369
3806
|
}
|
|
3370
3807
|
async function handshake(twin, fetchRemote) {
|
|
3371
|
-
const pin = pinnedVersion(`@pome-sh/twin-${twin}`);
|
|
3372
3808
|
let remote;
|
|
3373
3809
|
try {
|
|
3374
3810
|
remote = await fetchRemote(twin);
|
|
3375
3811
|
} catch (err) {
|
|
3376
3812
|
return {
|
|
3377
3813
|
kind: "unverified",
|
|
3378
|
-
note: `Not verified against the cloud (${err instanceof Error ? err.message : "unreachable"}). Writing from the local pin:
|
|
3814
|
+
note: `Not verified against the cloud (${err instanceof Error ? err.message : "unreachable"}). Writing from the local pin: ${pinLabel(`@pome-sh/twin-${twin}`)}.`
|
|
3379
3815
|
};
|
|
3380
3816
|
}
|
|
3381
3817
|
if (localDigest(twin) === remote.digest) return { kind: "match" };
|
|
@@ -3949,6 +4385,29 @@ async function runEvalCommand(runDirArg, opts) {
|
|
|
3949
4385
|
process.exitCode = code;
|
|
3950
4386
|
}
|
|
3951
4387
|
}
|
|
4388
|
+
function assetPath(...segments) {
|
|
4389
|
+
const root = resolvePackageRoot(import.meta.url);
|
|
4390
|
+
if (!root) {
|
|
4391
|
+
throw new Error(
|
|
4392
|
+
`Could not locate the @pome-sh/cli package root (no enclosing package.json) while resolving the packaged asset ${segments.join("/")}.`
|
|
4393
|
+
);
|
|
4394
|
+
}
|
|
4395
|
+
const candidate = join(root, "assets", ...segments);
|
|
4396
|
+
if (!existsSync(candidate)) {
|
|
4397
|
+
throw new Error(
|
|
4398
|
+
`Packaged asset not found at ${candidate}. This is a packaging bug \u2014 \`assets/\` must be listed in cli/package.json \`files\`.`
|
|
4399
|
+
);
|
|
4400
|
+
}
|
|
4401
|
+
return candidate;
|
|
4402
|
+
}
|
|
4403
|
+
|
|
4404
|
+
// src/demo/task.ts
|
|
4405
|
+
var DEMO_TASK_NAME = "first-run-demo";
|
|
4406
|
+
function demoTaskPath() {
|
|
4407
|
+
return assetPath("demo", "first-run-demo.md");
|
|
4408
|
+
}
|
|
4409
|
+
|
|
4410
|
+
// src/cli/default-task.ts
|
|
3952
4411
|
var DEFAULT_TASK_TRIALS = 5;
|
|
3953
4412
|
var DEFAULT_TASK_DIR = "tasks";
|
|
3954
4413
|
var DEFAULT_TASK_FILENAME = `${DEMO_TASK_NAME}.md`;
|
|
@@ -4203,126 +4662,6 @@ async function runRegisterAgent(opts) {
|
|
|
4203
4662
|
function stripControlCharacters(value) {
|
|
4204
4663
|
return value.replace(/[\u0000-\u001f\u007f]/g, "");
|
|
4205
4664
|
}
|
|
4206
|
-
var CLAUDE_SDK_AGENT_RELATIVE = "examples/agents/claude-sdk-agent.ts";
|
|
4207
|
-
var CLAUDE_SDK_AGENT_SOURCE = `// SPDX-License-Identifier: Apache-2.0
|
|
4208
|
-
//
|
|
4209
|
-
// Scaffolded by \`pome init --sdk claude\`. Minimal Claude Agent SDK starter
|
|
4210
|
-
// wired through @pome-sh/adapter-claude-sdk so \`pome run\` captures adapter
|
|
4211
|
-
// signals. Swap the tool body for your own agent logic.
|
|
4212
|
-
|
|
4213
|
-
import { createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
|
|
4214
|
-
import { query, tool, withPome } from "@pome-sh/adapter-claude-sdk";
|
|
4215
|
-
import { z } from "zod";
|
|
4216
|
-
|
|
4217
|
-
withPome();
|
|
4218
|
-
|
|
4219
|
-
const restUrl = process.env.POME_GITHUB_REST_URL;
|
|
4220
|
-
const authToken = process.env.POME_AUTH_TOKEN;
|
|
4221
|
-
const task =
|
|
4222
|
-
process.env.POME_TASK?.trim() ||
|
|
4223
|
-
"List open issues in acme/api and summarize their titles.";
|
|
4224
|
-
|
|
4225
|
-
if (!restUrl) {
|
|
4226
|
-
throw new Error(
|
|
4227
|
-
"POME_GITHUB_REST_URL is required (injected by \`pome run\`, or set it when driving a twin yourself).",
|
|
4228
|
-
);
|
|
4229
|
-
}
|
|
4230
|
-
|
|
4231
|
-
if (process.env.POME_PREFLIGHT === "1") {
|
|
4232
|
-
const res = await fetch(\`\${restUrl.replace(/\\/+$/, "")}/rate_limit\`, {
|
|
4233
|
-
headers: {
|
|
4234
|
-
accept: "application/vnd.github+json",
|
|
4235
|
-
...(authToken ? { authorization: \`Bearer \${authToken}\` } : {}),
|
|
4236
|
-
},
|
|
4237
|
-
});
|
|
4238
|
-
if (!res.ok) {
|
|
4239
|
-
throw new Error(\`preflight failed: HTTP \${res.status}\`);
|
|
4240
|
-
}
|
|
4241
|
-
console.log("preflight ok");
|
|
4242
|
-
process.exit(0);
|
|
4243
|
-
}
|
|
4244
|
-
|
|
4245
|
-
const listOpenIssues = tool(
|
|
4246
|
-
"list_open_issues",
|
|
4247
|
-
"List open issues on the GitHub twin for owner/repo.",
|
|
4248
|
-
{
|
|
4249
|
-
owner: z.string(),
|
|
4250
|
-
repo: z.string(),
|
|
4251
|
-
},
|
|
4252
|
-
async ({ owner, repo }) => {
|
|
4253
|
-
const res = await fetch(
|
|
4254
|
-
\`\${restUrl.replace(/\\/+$/, "")}/repos/\${owner}/\${repo}/issues?state=open\`,
|
|
4255
|
-
{
|
|
4256
|
-
headers: {
|
|
4257
|
-
accept: "application/vnd.github+json",
|
|
4258
|
-
...(authToken ? { authorization: \`Bearer \${authToken}\` } : {}),
|
|
4259
|
-
},
|
|
4260
|
-
},
|
|
4261
|
-
);
|
|
4262
|
-
const body = await res.text();
|
|
4263
|
-
if (!res.ok) {
|
|
4264
|
-
return {
|
|
4265
|
-
content: [{ type: "text" as const, text: \`HTTP \${res.status}: \${body}\` }],
|
|
4266
|
-
isError: true,
|
|
4267
|
-
};
|
|
4268
|
-
}
|
|
4269
|
-
return { content: [{ type: "text" as const, text: body }] };
|
|
4270
|
-
},
|
|
4271
|
-
);
|
|
4272
|
-
|
|
4273
|
-
const server = createSdkMcpServer({
|
|
4274
|
-
name: "github-twin",
|
|
4275
|
-
version: "0.1.0",
|
|
4276
|
-
tools: [listOpenIssues],
|
|
4277
|
-
});
|
|
4278
|
-
|
|
4279
|
-
for await (const message of query({
|
|
4280
|
-
prompt: task,
|
|
4281
|
-
options: {
|
|
4282
|
-
permissionMode: "bypassPermissions",
|
|
4283
|
-
maxTurns: 10,
|
|
4284
|
-
allowedTools: ["list_open_issues"],
|
|
4285
|
-
mcpServers: {
|
|
4286
|
-
"github-twin": {
|
|
4287
|
-
type: "sdk",
|
|
4288
|
-
name: "github-twin",
|
|
4289
|
-
instance: server.instance,
|
|
4290
|
-
},
|
|
4291
|
-
},
|
|
4292
|
-
},
|
|
4293
|
-
})) {
|
|
4294
|
-
if (message.type === "result" && message.subtype === "success" && message.result) {
|
|
4295
|
-
console.log(message.result);
|
|
4296
|
-
}
|
|
4297
|
-
}
|
|
4298
|
-
`;
|
|
4299
|
-
async function writeSdkScaffold(sdk) {
|
|
4300
|
-
switch (sdk) {
|
|
4301
|
-
case "claude":
|
|
4302
|
-
return writeClaudeSdkScaffold();
|
|
4303
|
-
case "claude-managed":
|
|
4304
|
-
throw new ClaudeManagedDeferredError();
|
|
4305
|
-
}
|
|
4306
|
-
}
|
|
4307
|
-
async function writeClaudeSdkScaffold() {
|
|
4308
|
-
const path = join(process.cwd(), CLAUDE_SDK_AGENT_RELATIVE);
|
|
4309
|
-
await mkdir(dirname(path), { recursive: true });
|
|
4310
|
-
await writeFile(path, CLAUDE_SDK_AGENT_SOURCE, "utf8");
|
|
4311
|
-
return {
|
|
4312
|
-
agentSdkValue: "claude-agent-sdk",
|
|
4313
|
-
agentCommand: `npx tsx ${CLAUDE_SDK_AGENT_RELATIVE}`,
|
|
4314
|
-
exampleAgentRelativePath: CLAUDE_SDK_AGENT_RELATIVE,
|
|
4315
|
-
postInstallHint: "Next steps:\n 1. npm install @pome-sh/adapter-claude-sdk @anthropic-ai/claude-agent-sdk zod\n 2. pome login\n 3. pome register agent <name>\n 4. pome run tasks/01-bug-happy-path.md\n\nSee `pome docs getting-started` for a narrative walkthrough."
|
|
4316
|
-
};
|
|
4317
|
-
}
|
|
4318
|
-
var ClaudeManagedDeferredError = class extends Error {
|
|
4319
|
-
constructor() {
|
|
4320
|
-
super(
|
|
4321
|
-
"Claude Managed Agent is not yet supported \u2014 three blockers (no injection point inside Anthropic-hosted runtime, no sidechannel for adapter signals, Managed Agents API still moving). Follow the tracking issue for v2 timing. For now, use `pome init` (without --sdk) or `pome init --sdk claude`."
|
|
4322
|
-
);
|
|
4323
|
-
this.name = "ClaudeManagedDeferredError";
|
|
4324
|
-
}
|
|
4325
|
-
};
|
|
4326
4665
|
|
|
4327
4666
|
// src/cli/example-catalog.ts
|
|
4328
4667
|
var CATALOG_EXAMPLES = [
|
|
@@ -4622,7 +4961,7 @@ function firstSentence(description) {
|
|
|
4622
4961
|
function resolveExampleRef(env = process.env) {
|
|
4623
4962
|
const override = env.POME_EXAMPLE_REF?.trim();
|
|
4624
4963
|
if (override) return override;
|
|
4625
|
-
const baked = "
|
|
4964
|
+
const baked = "589885b59adab9a5fded977620a6adc25125da85".trim() ;
|
|
4626
4965
|
return FULL_SHA.test(baked) ? baked : "main";
|
|
4627
4966
|
}
|
|
4628
4967
|
function rawUrlFor(example, file, ref) {
|
|
@@ -4985,7 +5324,7 @@ var DEFAULT_AGENT_COMMAND = `node ${DEFAULT_AGENT_FILE}`;
|
|
|
4985
5324
|
var MANIFEST_SCHEMA_URL = "https://pome.sh/schemas/v1/pome.json";
|
|
4986
5325
|
var MAX_UNREADABLE_PATHS_SHOWN = 5;
|
|
4987
5326
|
function readPackageVersion() {
|
|
4988
|
-
if ("0.
|
|
5327
|
+
if ("0.43.0".length > 0) return "0.43.0";
|
|
4989
5328
|
try {
|
|
4990
5329
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
4991
5330
|
const candidates = [
|
|
@@ -5014,13 +5353,10 @@ function createProgram() {
|
|
|
5014
5353
|
process.env.POME_API_URL ?? DEFAULT_CONTROL_PLANE_URL
|
|
5015
5354
|
).option("--artifacts-dir <dir>", "Directory for run artifacts", "runs");
|
|
5016
5355
|
program.name("pome").description(
|
|
5017
|
-
"Test AI agents against digital twins of real SaaS APIs. Runs are recorded to app.pome.sh. Start with `pome
|
|
5356
|
+
"Test AI agents against digital twins of real SaaS APIs. Runs are recorded to app.pome.sh. Start with `pome init`."
|
|
5018
5357
|
).version(PACKAGE_VERSION).showHelpAfterError("(add --help for usage)");
|
|
5019
5358
|
program.command("init").summary("Set up Pome in this project").description(
|
|
5020
5359
|
"Create pome.json, plus starter files in a new project \u2014 or, with --example <id>, fetch a complete runnable example into ./<id>"
|
|
5021
|
-
).option(
|
|
5022
|
-
"--sdk <name>",
|
|
5023
|
-
"Scaffold for a specific agent SDK (claude | claude-managed). Adds the SDK-specific example file and pre-fills agent.framework so the dashboard badges runs correctly."
|
|
5024
5360
|
).option(
|
|
5025
5361
|
"--bare",
|
|
5026
5362
|
"Write only the manifest \u2014 skip the starter task library and sample agents. Auto-selected when the current directory already has a package.json (the bring-your-own-agent case)."
|
|
@@ -5031,10 +5367,9 @@ function createProgram() {
|
|
|
5031
5367
|
"--example <id>",
|
|
5032
5368
|
`Scaffold a complete bundled example into ./<id> instead of a starter project. Ids: ${exampleIds().join(", ")}.`
|
|
5033
5369
|
).action(async (opts) => {
|
|
5034
|
-
const sdk = opts.sdk?.trim();
|
|
5035
5370
|
const exampleId = opts.example?.trim();
|
|
5036
5371
|
if (exampleId !== void 0) {
|
|
5037
|
-
const conflicting = ["
|
|
5372
|
+
const conflicting = ["bare", "starter"].filter(
|
|
5038
5373
|
(flag) => opts[flag] !== void 0 && opts[flag] !== false
|
|
5039
5374
|
);
|
|
5040
5375
|
if (conflicting.length > 0) {
|
|
@@ -5059,18 +5394,6 @@ function createProgram() {
|
|
|
5059
5394
|
}
|
|
5060
5395
|
return;
|
|
5061
5396
|
}
|
|
5062
|
-
if (sdk !== void 0 && sdk !== "claude" && sdk !== "claude-managed") {
|
|
5063
|
-
console.error(
|
|
5064
|
-
`Unknown --sdk value "${opts.sdk}". Supported: claude, claude-managed.`
|
|
5065
|
-
);
|
|
5066
|
-
process.exitCode = 2;
|
|
5067
|
-
return;
|
|
5068
|
-
}
|
|
5069
|
-
if (sdk === "claude-managed") {
|
|
5070
|
-
console.error(new ClaudeManagedDeferredError().message);
|
|
5071
|
-
process.exitCode = 2;
|
|
5072
|
-
return;
|
|
5073
|
-
}
|
|
5074
5397
|
if (opts.bare && opts.starter) {
|
|
5075
5398
|
console.error("Pass at most one of --bare / --starter.");
|
|
5076
5399
|
process.exitCode = 2;
|
|
@@ -5086,22 +5409,13 @@ function createProgram() {
|
|
|
5086
5409
|
await mkdir("runs", { recursive: true });
|
|
5087
5410
|
await copyStarterFiles();
|
|
5088
5411
|
}
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
let postInitMessage = bare ? 'Pome initialized (existing project \u2014 starter library skipped).\nNext steps:\n 1. Set "command" in pome.json to your agent\'s launch command\n 2. pome login # one-time, opens the dashboard to sign in\n 3. pome register agent <name> # scopes runs to this project (writes agent.slug to pome.json)\n 4. pome run <your-task>.md\n\nOptional follow-ups:\n - pome tasks github --copy # drop the starter task library into tasks/\n - pome init --sdk claude # scaffold a Claude Agent SDK starter\n\nSee `pome docs getting-started` for a narrative walkthrough.' : "Pome initialized.\nNext steps:\n 1. pome login # one-time, opens the dashboard to sign in\n 2. pome register agent <name> # scopes runs to this project (writes agent.slug to pome.json)\n 3. pome run tasks/01-bug-happy-path.md\n\nOptional follow-ups:\n - pome init --sdk claude # scaffold a Claude Agent SDK starter\n - pome tasks stripe --copy # add Stripe payment tasks when needed\n\nSee `pome docs getting-started` for a narrative walkthrough.";
|
|
5092
|
-
if (sdk) {
|
|
5093
|
-
const scaffold = await writeSdkScaffold(sdk);
|
|
5094
|
-
command = scaffold.agentCommand;
|
|
5095
|
-
framework = scaffold.agentSdkValue;
|
|
5096
|
-
postInitMessage = `Pome initialized with --sdk ${sdk}. Scaffolded ${scaffold.exampleAgentRelativePath}.
|
|
5097
|
-
` + scaffold.postInstallHint;
|
|
5098
|
-
}
|
|
5412
|
+
const command = bare ? void 0 : DEFAULT_AGENT_COMMAND;
|
|
5413
|
+
const postInitMessage = bare ? 'Pome initialized (existing project \u2014 starter library skipped).\nNext steps:\n 1. Set "command" in pome.json to your agent\'s launch command\n 2. pome login # one-time, opens the dashboard to sign in\n 3. pome register agent <name> # scopes runs to this project (writes agent.slug to pome.json)\n 4. pome run <your-task>.md\n\nOptional follow-ups:\n - pome tasks github --copy # drop the starter task library into tasks/\n\nSee `pome docs getting-started` for a narrative walkthrough.' : "Pome initialized.\nNext steps:\n 1. pome login # one-time, opens the dashboard to sign in\n 2. pome register agent <name> # scopes runs to this project (writes agent.slug to pome.json)\n 3. pome run tasks/01-bug-happy-path.md\n\nOptional follow-ups:\n - pome tasks stripe --copy # add Stripe payment tasks when needed\n\nSee `pome docs getting-started` for a narrative walkthrough.";
|
|
5099
5414
|
const tasksDir = !bare || await hasMarkdownTasks("tasks") ? "tasks" : void 0;
|
|
5100
5415
|
const existing = await readManifest(process.cwd());
|
|
5101
5416
|
if (!existing) {
|
|
5102
5417
|
const slug = deriveAgentSlug(basename(process.cwd())) || "my-agent";
|
|
5103
5418
|
const agent = { slug };
|
|
5104
|
-
if (framework) agent.framework = framework;
|
|
5105
5419
|
const manifest = {
|
|
5106
5420
|
$schema: MANIFEST_SCHEMA_URL,
|
|
5107
5421
|
agent
|
|
@@ -5111,15 +5425,6 @@ function createProgram() {
|
|
|
5111
5425
|
manifest.artifacts_dir = "runs";
|
|
5112
5426
|
manifest.pass_threshold = 100;
|
|
5113
5427
|
await writeManifest(join(process.cwd(), MANIFEST_JSON), "json", manifest);
|
|
5114
|
-
} else if (sdk) {
|
|
5115
|
-
const priorAgent = typeof existing.raw.agent === "object" && existing.raw.agent !== null ? existing.raw.agent : {};
|
|
5116
|
-
const nextAgent = { ...priorAgent };
|
|
5117
|
-
if (framework) nextAgent.framework = framework;
|
|
5118
|
-
await writeManifest(existing.path, existing.format, {
|
|
5119
|
-
...existing.raw,
|
|
5120
|
-
agent: nextAgent,
|
|
5121
|
-
command
|
|
5122
|
-
});
|
|
5123
5428
|
}
|
|
5124
5429
|
console.error(postInitMessage);
|
|
5125
5430
|
});
|
|
@@ -5423,7 +5728,7 @@ function createProgram() {
|
|
|
5423
5728
|
return;
|
|
5424
5729
|
}
|
|
5425
5730
|
{
|
|
5426
|
-
const { runDoctorChecks } = await import('../../checks-
|
|
5731
|
+
const { runDoctorChecks } = await import('../../checks-QYUPKBPT.js');
|
|
5427
5732
|
const { renderDoctorReport } = await import('../../render-ZQQ4UMNO.js');
|
|
5428
5733
|
const doctorReport = await runDoctorChecks({ mode: useLocal ? "full" : "hosted" });
|
|
5429
5734
|
if (!doctorReport.ok) {
|
|
@@ -5461,7 +5766,7 @@ function createProgram() {
|
|
|
5461
5766
|
taskForRuns.config.runs
|
|
5462
5767
|
);
|
|
5463
5768
|
if (k > 1) {
|
|
5464
|
-
const { runTrialGroup } = await import('../../runTrialGroup-
|
|
5769
|
+
const { runTrialGroup } = await import('../../runTrialGroup-4QL6S7QO.js');
|
|
5465
5770
|
const fileForRerun = relative(process.cwd(), file);
|
|
5466
5771
|
const rerunCommand = defaultTask ? options.trials !== void 0 ? `pome run -n ${k}` : "pome run" : `pome run ${fileForRerun && !fileForRerun.startsWith("..") ? fileForRerun : file} -n ${k}`;
|
|
5467
5772
|
const groupResult = await runTrialGroup({
|
|
@@ -5540,39 +5845,10 @@ function createProgram() {
|
|
|
5540
5845
|
process.exitCode = worstExit;
|
|
5541
5846
|
}
|
|
5542
5847
|
);
|
|
5543
|
-
program.command("demo").summary("Run a sample task, no account needed").description(
|
|
5544
|
-
"Zero-auth first-run demo: boots a local GitHub twin, runs the bundled demo agent for 5 isolated trials (model calls via pome's anonymous demo gateway), and prints per-trial verdicts evaluated in Pome cloud. No signup, no API keys; ends with a no-login preview link."
|
|
5545
|
-
).option(
|
|
5546
|
-
"--trials <n>",
|
|
5547
|
-
"Number of trials to run, 1 to 10",
|
|
5548
|
-
"5"
|
|
5549
|
-
).action(
|
|
5550
|
-
async (opts, cmd) => {
|
|
5551
|
-
const trials = Number.parseInt(opts.trials, 10);
|
|
5552
|
-
if (!Number.isInteger(trials) || trials < 1 || trials > 10) {
|
|
5553
|
-
console.error(`Invalid --trials "${opts.trials}" (expected 1-10).`);
|
|
5554
|
-
process.exitCode = 5;
|
|
5555
|
-
return;
|
|
5556
|
-
}
|
|
5557
|
-
const { runDemo } = await import('../../runDemo-DRDMCG25.js');
|
|
5558
|
-
const result = await runDemo({
|
|
5559
|
-
apiBase: globals(cmd).apiUrl.replace(/\/$/, ""),
|
|
5560
|
-
dashboardBase: process.env.POME_DASHBOARD_URL ?? DEFAULT_DASHBOARD_URL,
|
|
5561
|
-
trials,
|
|
5562
|
-
artifactsDir: globals(cmd).artifactsDir
|
|
5563
|
-
});
|
|
5564
|
-
process.exitCode = result.exitCode;
|
|
5565
|
-
}
|
|
5566
|
-
);
|
|
5567
|
-
program.command("demo-agent", { hidden: true }).description("Internal: the bundled demo agent process (spawned by `pome demo`).").action(async () => {
|
|
5568
|
-
const { runDemoAgentCommand } = await import('../../agent-Q2UTRJYL.js');
|
|
5569
|
-
const code = await runDemoAgentCommand();
|
|
5570
|
-
if (code !== 0) process.exitCode = code;
|
|
5571
|
-
});
|
|
5572
5848
|
program.command("doctor").summary("Check the agent and twin wiring").description(
|
|
5573
5849
|
"Check the agent\u2194twin wiring: pome.json (or pome.yaml) present + valid, the local twin boots + serves, requests routed to the twin (not a hardcoded production host), egress floor active. On failure prints one named cause (file:line where knowable) + one concrete fix and exits non-zero."
|
|
5574
5850
|
).action(async () => {
|
|
5575
|
-
const { runDoctorChecks } = await import('../../checks-
|
|
5851
|
+
const { runDoctorChecks } = await import('../../checks-QYUPKBPT.js');
|
|
5576
5852
|
const { renderDoctorReport } = await import('../../render-ZQQ4UMNO.js');
|
|
5577
5853
|
const report = await runDoctorChecks();
|
|
5578
5854
|
for (const line of renderDoctorReport(report, { passNote: true })) console.error(line);
|
|
@@ -5740,13 +6016,13 @@ function createProgram() {
|
|
|
5740
6016
|
).description(
|
|
5741
6017
|
"Start a standalone twin as a long-lived foreground server (Ctrl-C to stop)"
|
|
5742
6018
|
).action(async (name, options) => {
|
|
5743
|
-
const { runTwinStartCommand } = await import('../../twinStart-
|
|
6019
|
+
const { runTwinStartCommand } = await import('../../twinStart-QQ64EV3P.js');
|
|
5744
6020
|
await runTwinStartCommand(name, options);
|
|
5745
6021
|
});
|
|
5746
6022
|
twin.command("new-seed").argument("<name...>", `Twin name (${TWIN_NAME_LIST.join(" | ")}). Repeat for one file covering several.`).option("--out <path>", "Write to this file instead of stdout. Refuses to overwrite.").summary("Print a new starter seed file for a twin").description(
|
|
5747
6023
|
"Print a new starter seed file for a twin, generated from the twin's own starting state. One twin is flat, several are the per-twin envelope. Boot it with `twin start <twin> --seed`, seed a sandbox with `sandbox create --twin <twin> --seed`, or drop it beside a task as <task>.seed.json"
|
|
5748
6024
|
).action(async (names, options) => {
|
|
5749
|
-
const { runTwinSeedCommand } = await import('../../twinSeed-
|
|
6025
|
+
const { runTwinSeedCommand } = await import('../../twinSeed-4VQ7RRCL.js');
|
|
5750
6026
|
await runTwinSeedCommand(names, options);
|
|
5751
6027
|
});
|
|
5752
6028
|
twin.command("status").summary("Say whether the local twin is running").description(
|