@quireco/cli 0.0.10 → 0.0.12
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/{cli-CqxFgCaa.mjs → cli-CItUDOhU.mjs} +882 -95
- package/dist/index.mjs +1 -1
- package/dist/quire.mjs +1 -1
- package/package.json +1 -1
|
@@ -8,10 +8,10 @@ import { exec, execFile, execFileSync, spawn } from "node:child_process";
|
|
|
8
8
|
import pc from "picocolors";
|
|
9
9
|
import { calculateCost, createAssistantMessageEventStream } from "@earendil-works/pi-ai";
|
|
10
10
|
import { convertMessages } from "@earendil-works/pi-ai/openai-completions";
|
|
11
|
-
import { access, constants, mkdir, mkdtemp, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
11
|
+
import { access, constants, mkdir, mkdtemp, readFile, readdir, rename, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
12
12
|
import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
13
13
|
import { homedir, networkInterfaces, tmpdir } from "node:os";
|
|
14
|
-
import { closeSync, constants as constants$1, copyFileSync, existsSync, fstatSync, fsyncSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
|
|
14
|
+
import { closeSync, constants as constants$1, copyFileSync, existsSync, fstatSync, fsyncSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
16
16
|
import { customAlphabet } from "nanoid";
|
|
17
17
|
import { Type } from "@sinclair/typebox";
|
|
@@ -1375,21 +1375,28 @@ const FINDING_RELATIONSHIPS_TO_REQUEST = [
|
|
|
1375
1375
|
];
|
|
1376
1376
|
function createRunDir(repoPath, options = {}) {
|
|
1377
1377
|
const workspaceKey = workspaceKeyForRepoPath(resolve(repoPath));
|
|
1378
|
-
const runsRoot
|
|
1379
|
-
|
|
1378
|
+
const { runsRoot, runId, root, temporaryRunsRoot } = allocateRunRoot({
|
|
1379
|
+
runsRoot: resolve(options.runsRoot ?? defaultRunsRoot()),
|
|
1380
|
+
workspaceKey,
|
|
1380
1381
|
runId: options.runId,
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
const paths = runDirPaths(root);
|
|
1384
|
-
mkdirSync(paths.evidence, {
|
|
1385
|
-
recursive: true,
|
|
1386
|
-
mode: 448
|
|
1382
|
+
runIdFactory: options.createRunId ?? createRunId,
|
|
1383
|
+
allowTemporaryFallback: options.runsRoot === void 0 && (process.env.QUIRE_RUNS_DIR?.trim().length ?? 0) === 0
|
|
1387
1384
|
});
|
|
1385
|
+
const paths = runDirPaths(root);
|
|
1386
|
+
try {
|
|
1387
|
+
mkdirSync(paths.evidence, {
|
|
1388
|
+
recursive: true,
|
|
1389
|
+
mode: 448
|
|
1390
|
+
});
|
|
1391
|
+
} catch (error) {
|
|
1392
|
+
throw writableRunsRootError(error, runsRoot);
|
|
1393
|
+
}
|
|
1388
1394
|
return {
|
|
1389
1395
|
runId,
|
|
1390
1396
|
workspaceKey,
|
|
1391
1397
|
runsRoot,
|
|
1392
1398
|
root,
|
|
1399
|
+
...temporaryRunsRoot ? { temporaryRunsRoot: true } : {},
|
|
1393
1400
|
paths
|
|
1394
1401
|
};
|
|
1395
1402
|
}
|
|
@@ -1416,6 +1423,9 @@ function runPlanPath(runPath) {
|
|
|
1416
1423
|
function runHandoffPath(runPath) {
|
|
1417
1424
|
return join(resolve(runPath), "handoff.md");
|
|
1418
1425
|
}
|
|
1426
|
+
function runPendingHandoffPath(runPath) {
|
|
1427
|
+
return join(resolve(runPath), ".pending-handoff.json");
|
|
1428
|
+
}
|
|
1419
1429
|
function runDirPaths(root) {
|
|
1420
1430
|
const evidence = join(root, "evidence");
|
|
1421
1431
|
return {
|
|
@@ -1432,6 +1442,51 @@ function runDirPaths(root) {
|
|
|
1432
1442
|
harnessSessions: join(root, ".harness-sessions")
|
|
1433
1443
|
};
|
|
1434
1444
|
}
|
|
1445
|
+
function allocateRunRoot(input) {
|
|
1446
|
+
try {
|
|
1447
|
+
return {
|
|
1448
|
+
...createRunRoot(input.runsRoot, input.workspaceKey, input.runId, input.runIdFactory),
|
|
1449
|
+
runsRoot: input.runsRoot,
|
|
1450
|
+
temporaryRunsRoot: false
|
|
1451
|
+
};
|
|
1452
|
+
} catch (error) {
|
|
1453
|
+
if (!isWriteAccessError$2(error)) throw error;
|
|
1454
|
+
if (!input.allowTemporaryFallback) throw writableRunsRootError(error, input.runsRoot);
|
|
1455
|
+
const runsRoot = mkdtempSync(join(tmpdir(), "quire-runs-"));
|
|
1456
|
+
return {
|
|
1457
|
+
...createRunRoot(runsRoot, input.workspaceKey, input.runId, input.runIdFactory),
|
|
1458
|
+
runsRoot,
|
|
1459
|
+
temporaryRunsRoot: true
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
function createRunRoot(runsRoot, workspaceKey, runId, runIdFactory) {
|
|
1464
|
+
return runId === void 0 ? createUniqueRunRoot(runsRoot, workspaceKey, runIdFactory) : createNamedRunRoot(runsRoot, workspaceKey, runId);
|
|
1465
|
+
}
|
|
1466
|
+
function createNamedRunRoot(runsRoot, workspaceKey, runId) {
|
|
1467
|
+
const root = join(runsRoot, workspaceKey, runId);
|
|
1468
|
+
mkdirSync(join(root, "evidence"), {
|
|
1469
|
+
recursive: true,
|
|
1470
|
+
mode: 448
|
|
1471
|
+
});
|
|
1472
|
+
return {
|
|
1473
|
+
runId,
|
|
1474
|
+
root
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
function writableRunsRootError(error, runsRoot) {
|
|
1478
|
+
if (!isWriteAccessError$2(error)) return error;
|
|
1479
|
+
return new CliError$1([
|
|
1480
|
+
`Quire could not write run data at ${runsRoot}: ${error.message}`,
|
|
1481
|
+
"In sandboxed coding-agent environments, retry with a temporary run directory:",
|
|
1482
|
+
" quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)",
|
|
1483
|
+
" QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json <your request>",
|
|
1484
|
+
"Use the same QUIRE_RUNS_DIR value for follow-up `quire wait`, `quire status`, `quire watch`, or `quire cancel` commands."
|
|
1485
|
+
].join("\n"), ExitCode.InvalidInput);
|
|
1486
|
+
}
|
|
1487
|
+
function isWriteAccessError$2(error) {
|
|
1488
|
+
return error instanceof Error && "code" in error && (error.code === "EROFS" || error.code === "EACCES" || error.code === "EPERM");
|
|
1489
|
+
}
|
|
1435
1490
|
function defaultRunsRoot(env = process.env) {
|
|
1436
1491
|
return defaultRunsRoot$1(env);
|
|
1437
1492
|
}
|
|
@@ -1483,6 +1538,20 @@ function writeRunFinalHandoff(runDir, final) {
|
|
|
1483
1538
|
finalResult: final.result
|
|
1484
1539
|
});
|
|
1485
1540
|
}
|
|
1541
|
+
function writeRunPendingHandoff(runDir, handoff) {
|
|
1542
|
+
const writtenAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1543
|
+
writeFileAtomically(runPendingHandoffPath(runDir.root), `${stableJson({
|
|
1544
|
+
version: 1,
|
|
1545
|
+
writtenAt,
|
|
1546
|
+
handoff
|
|
1547
|
+
})}\n`, 384);
|
|
1548
|
+
writeFileAtomically(runDir.paths.handoff, `${handoff.handoffMarkdown.trimEnd()}\n`, 384);
|
|
1549
|
+
}
|
|
1550
|
+
function clearRunPendingHandoff(runDir) {
|
|
1551
|
+
try {
|
|
1552
|
+
unlinkSync(runPendingHandoffPath(runDir.root));
|
|
1553
|
+
} catch {}
|
|
1554
|
+
}
|
|
1486
1555
|
function patchRunStatusJson(runDir, patch) {
|
|
1487
1556
|
if (!existsSync(runDir.paths.status)) return;
|
|
1488
1557
|
let existing;
|
|
@@ -1752,18 +1821,32 @@ function readFreshRunStatus(runPath) {
|
|
|
1752
1821
|
if (existsSync(runHandoffPath(runPath))) return updateRunStatus(runPath, { status: "completed" });
|
|
1753
1822
|
return updateRunStatus(runPath, {
|
|
1754
1823
|
status: "stale",
|
|
1755
|
-
error: "Worker process exited before marking the run as running."
|
|
1824
|
+
error: staleWorkerError(runPath, "Worker process exited before marking the run as running.")
|
|
1756
1825
|
});
|
|
1757
1826
|
}
|
|
1758
1827
|
if ((status.status === "running" || status.status === "canceling") && !isPidAlive(status.pid)) {
|
|
1759
1828
|
if (existsSync(runHandoffPath(runPath))) return updateRunStatus(runPath, { status: "completed" });
|
|
1760
1829
|
return updateRunStatus(runPath, {
|
|
1761
1830
|
status: status.status === "canceling" ? "canceled" : "stale",
|
|
1762
|
-
error: status.status === "canceling" ? void 0 : "Worker process is no longer running."
|
|
1831
|
+
error: status.status === "canceling" ? void 0 : staleWorkerError(runPath, "Worker process is no longer running.")
|
|
1763
1832
|
});
|
|
1764
1833
|
}
|
|
1765
1834
|
return status;
|
|
1766
1835
|
}
|
|
1836
|
+
function staleWorkerError(runPath, message) {
|
|
1837
|
+
const workerLogPath = join(runPath, "worker.log");
|
|
1838
|
+
if (!existsSync(workerLogPath)) return message;
|
|
1839
|
+
const tail = readWorkerLogTail$1(workerLogPath);
|
|
1840
|
+
if (tail.length === 0) return `${message}\n\nWorker log: ${workerLogPath} (empty)`;
|
|
1841
|
+
return `${message}\n\nWorker log: ${workerLogPath}\n\nLast worker output:\n${tail}`;
|
|
1842
|
+
}
|
|
1843
|
+
function readWorkerLogTail$1(workerLogPath) {
|
|
1844
|
+
try {
|
|
1845
|
+
return readFileSync(workerLogPath, "utf8").slice(-12e3).trim();
|
|
1846
|
+
} catch {
|
|
1847
|
+
return "";
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1767
1850
|
function readRequiredRunStatus(runPath) {
|
|
1768
1851
|
const status = readRunStatus(runPath);
|
|
1769
1852
|
if (status === void 0) throw new CliError$1(`Run status not found: ${runPath}`, ExitCode.InvalidInput);
|
|
@@ -1799,10 +1882,27 @@ function writeStatus(filePath, status) {
|
|
|
1799
1882
|
mode: 448
|
|
1800
1883
|
});
|
|
1801
1884
|
const tempPath = join(dirname(filePath), `.${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.status.tmp`);
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1885
|
+
try {
|
|
1886
|
+
writeFileSync(tempPath, `${JSON.stringify(status, null, 2)}\n`, { mode: 384 });
|
|
1887
|
+
fsyncFile(tempPath);
|
|
1888
|
+
renameSync(tempPath, filePath);
|
|
1889
|
+
fsyncDirectory(dirname(filePath));
|
|
1890
|
+
} catch (error) {
|
|
1891
|
+
throw writableRunStatusError(error, status.runPath);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
function writableRunStatusError(error, runPath) {
|
|
1895
|
+
if (!isWriteAccessError$1(error)) return error;
|
|
1896
|
+
return new CliError$1([
|
|
1897
|
+
`Quire could not update run status at ${runPath}: ${error.message}`,
|
|
1898
|
+
"In sandboxed coding-agent environments, put Quire run data in a temporary directory and reuse it for every lifecycle command:",
|
|
1899
|
+
" quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)",
|
|
1900
|
+
" QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json <your request>",
|
|
1901
|
+
" QUIRE_RUNS_DIR=$quire_runs_dir quire wait <run-id> --json"
|
|
1902
|
+
].join("\n"), ExitCode.InvalidInput);
|
|
1903
|
+
}
|
|
1904
|
+
function isWriteAccessError$1(error) {
|
|
1905
|
+
return error instanceof Error && "code" in error && (error.code === "EROFS" || error.code === "EACCES" || error.code === "EPERM");
|
|
1806
1906
|
}
|
|
1807
1907
|
function fsyncFile(filePath) {
|
|
1808
1908
|
const file = openSync(filePath, "r");
|
|
@@ -2552,14 +2652,6 @@ async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, hea
|
|
|
2552
2652
|
for (const command of commands) {
|
|
2553
2653
|
const args = applyBrowserLaunchArgs(applyRunDefaults(applyHeadedMode(command.args, headed), runDir));
|
|
2554
2654
|
const commandLabel = browserCommandLabel(args);
|
|
2555
|
-
emitBrowserProgress(runDir, onProgressEvent, {
|
|
2556
|
-
kind: "action",
|
|
2557
|
-
status: "running",
|
|
2558
|
-
title: `Browser: ${commandLabel}`,
|
|
2559
|
-
summary: "agent-browser is acting on the target application.",
|
|
2560
|
-
importance: "normal",
|
|
2561
|
-
source: "runtime"
|
|
2562
|
-
});
|
|
2563
2655
|
const result = await spawnAgentBrowser({
|
|
2564
2656
|
args,
|
|
2565
2657
|
sessionId,
|
|
@@ -2604,14 +2696,6 @@ async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, hea
|
|
|
2604
2696
|
stderr: stderr.join("")
|
|
2605
2697
|
};
|
|
2606
2698
|
}
|
|
2607
|
-
emitBrowserProgress(runDir, onProgressEvent, {
|
|
2608
|
-
kind: "action",
|
|
2609
|
-
status: "passed",
|
|
2610
|
-
title: `Browser: ${commandLabel}`,
|
|
2611
|
-
summary: "agent-browser completed this target step.",
|
|
2612
|
-
importance: "normal",
|
|
2613
|
-
source: "runtime"
|
|
2614
|
-
});
|
|
2615
2699
|
}
|
|
2616
2700
|
return {
|
|
2617
2701
|
ok: true,
|
|
@@ -2817,6 +2901,7 @@ function materializeHandoff({ handoff, runDir, continuation, modelUsage, metadat
|
|
|
2817
2901
|
result: materializedResult
|
|
2818
2902
|
});
|
|
2819
2903
|
writeHandoffArtifact(runDir, materializedHandoff);
|
|
2904
|
+
clearRunPendingHandoff(runDir);
|
|
2820
2905
|
return {
|
|
2821
2906
|
result: materializedResult,
|
|
2822
2907
|
metadata: nextMetadata
|
|
@@ -45331,6 +45416,23 @@ const writeHandoffToolDefinition = {
|
|
|
45331
45416
|
};
|
|
45332
45417
|
}
|
|
45333
45418
|
};
|
|
45419
|
+
function makeWriteHandoffTool(options = {}) {
|
|
45420
|
+
return defineTool({
|
|
45421
|
+
...writeHandoffToolDefinition,
|
|
45422
|
+
async execute(_toolCallId, params, _signal) {
|
|
45423
|
+
const handoff = normalizeWriteHandoffInput(params);
|
|
45424
|
+
await options.onHandoff?.(handoff);
|
|
45425
|
+
return {
|
|
45426
|
+
content: [{
|
|
45427
|
+
type: "text",
|
|
45428
|
+
text: JSON.stringify(handoff, null, 2)
|
|
45429
|
+
}],
|
|
45430
|
+
details: handoff,
|
|
45431
|
+
terminate: true
|
|
45432
|
+
};
|
|
45433
|
+
}
|
|
45434
|
+
});
|
|
45435
|
+
}
|
|
45334
45436
|
function normalizeWriteHandoffInput(params) {
|
|
45335
45437
|
const { status, ...rest } = params;
|
|
45336
45438
|
return {
|
|
@@ -45343,7 +45445,7 @@ function normalizeHandoffStatus(status) {
|
|
|
45343
45445
|
if (status === "no_issue_found" || status === "not_a_bug" || status === "verified_no_bug_found" || status === "verified_no_issue_found") return "not_reproduced";
|
|
45344
45446
|
return status;
|
|
45345
45447
|
}
|
|
45346
|
-
|
|
45448
|
+
makeWriteHandoffTool();
|
|
45347
45449
|
//#endregion
|
|
45348
45450
|
//#region src/utils/schema.ts
|
|
45349
45451
|
function stringEnumSchema(values) {
|
|
@@ -46169,7 +46271,11 @@ async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons,
|
|
|
46169
46271
|
followup,
|
|
46170
46272
|
skillPack,
|
|
46171
46273
|
mcpGateway,
|
|
46172
|
-
intent
|
|
46274
|
+
intent,
|
|
46275
|
+
onHandoff: (handoff) => {
|
|
46276
|
+
emittedHandoff ??= handoff;
|
|
46277
|
+
writeRunPendingHandoff(runDir, handoff);
|
|
46278
|
+
}
|
|
46173
46279
|
});
|
|
46174
46280
|
const { session, logAgentProgress, registeredTargetTools } = runtime;
|
|
46175
46281
|
onResolvedModel?.({
|
|
@@ -46177,18 +46283,31 @@ async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons,
|
|
|
46177
46283
|
provider: runtime.model.provider,
|
|
46178
46284
|
modelId: runtime.model.id
|
|
46179
46285
|
});
|
|
46286
|
+
let rejectCurrentPromptOnModelError;
|
|
46180
46287
|
const unsubscribe = session.subscribe((event) => {
|
|
46181
46288
|
touchRunActivity(runDir.root, { minIntervalMs: RUN_ACTIVITY_HEARTBEAT_INTERVAL_MS });
|
|
46182
46289
|
logAgentProgress(event);
|
|
46290
|
+
const sessionError = modelErrorFromSessionEvent(event);
|
|
46291
|
+
if (sessionError !== void 0) rejectCurrentPromptOnModelError?.(sessionError);
|
|
46183
46292
|
emittedHandoff ??= extractWriteHandoff(event);
|
|
46184
46293
|
rememberTargetToolCallArgs(event, registeredTargetTools, targetToolCallArgs);
|
|
46185
46294
|
const attemptsObserved = countNewTargetAttempts(event, registeredTargetTools, observedTargetToolCallIds, targetToolCallArgs);
|
|
46186
46295
|
if (attemptsObserved > 0) observedToolAttempts += attemptsObserved;
|
|
46187
46296
|
});
|
|
46188
46297
|
try {
|
|
46189
|
-
|
|
46190
|
-
|
|
46298
|
+
try {
|
|
46299
|
+
await promptOrModelError(session.prompt(runBrief, { expandPromptTemplates: false }), (reject) => {
|
|
46300
|
+
rejectCurrentPromptOnModelError = reject;
|
|
46301
|
+
});
|
|
46302
|
+
if (emittedHandoff === void 0) await promptOrModelError(session.prompt(buildMissingRunHandoffPrompt(observedToolAttempts), { expandPromptTemplates: false }), (reject) => {
|
|
46303
|
+
rejectCurrentPromptOnModelError = reject;
|
|
46304
|
+
});
|
|
46305
|
+
} catch (error) {
|
|
46306
|
+
if (emittedHandoff === void 0) throw error;
|
|
46307
|
+
progress?.("Recovered terminal write_handoff after the agent session stopped.");
|
|
46308
|
+
}
|
|
46191
46309
|
} finally {
|
|
46310
|
+
rejectCurrentPromptOnModelError = void 0;
|
|
46192
46311
|
runtime.writeSessionState();
|
|
46193
46312
|
unsubscribe();
|
|
46194
46313
|
runtime.dispose();
|
|
@@ -46198,7 +46317,23 @@ async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons,
|
|
|
46198
46317
|
runDir
|
|
46199
46318
|
});
|
|
46200
46319
|
}
|
|
46201
|
-
async function
|
|
46320
|
+
async function promptOrModelError(prompt, setRejectCurrentPromptOnModelError) {
|
|
46321
|
+
try {
|
|
46322
|
+
return await Promise.race([prompt, new Promise((_resolve, reject) => {
|
|
46323
|
+
setRejectCurrentPromptOnModelError(reject);
|
|
46324
|
+
})]);
|
|
46325
|
+
} finally {
|
|
46326
|
+
setRejectCurrentPromptOnModelError(void 0);
|
|
46327
|
+
}
|
|
46328
|
+
}
|
|
46329
|
+
function modelErrorFromSessionEvent(event) {
|
|
46330
|
+
if (!isRecord$14(event) || event.type !== "message" || !isRecord$14(event.message)) return;
|
|
46331
|
+
const message = event.message;
|
|
46332
|
+
if (message.role !== "assistant" || message.stopReason !== "error") return;
|
|
46333
|
+
const errorMessage = typeof message.errorMessage === "string" && message.errorMessage.trim().length > 0 ? message.errorMessage.trim() : "model request failed";
|
|
46334
|
+
return /* @__PURE__ */ new Error(`Quire model request failed: ${errorMessage}`);
|
|
46335
|
+
}
|
|
46336
|
+
async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed, intent, progress, onProgressEvent, runInstructions, remoteAddons, runtimeCapabilities, followup, skillPack, mcpGateway, onHandoff }) {
|
|
46202
46337
|
const logAgentProgress = createAgentProgressLogger({
|
|
46203
46338
|
runDir,
|
|
46204
46339
|
write: progress
|
|
@@ -46228,6 +46363,7 @@ async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed
|
|
|
46228
46363
|
gateway: mcpGateway,
|
|
46229
46364
|
runId: runDir.runId
|
|
46230
46365
|
});
|
|
46366
|
+
const writeHandoffTool = makeWriteHandoffTool({ onHandoff });
|
|
46231
46367
|
const registeredTargetTools = [...browserTool === void 0 ? [] : [{
|
|
46232
46368
|
toolName: "agent-browser",
|
|
46233
46369
|
target: "web"
|
|
@@ -48199,6 +48335,8 @@ function runPath(runDir) {
|
|
|
48199
48335
|
}
|
|
48200
48336
|
//#endregion
|
|
48201
48337
|
//#region src/pipeline/background-investigation.ts
|
|
48338
|
+
const WORKER_BOOTSTRAP_TIMEOUT_MS = 2e3;
|
|
48339
|
+
const WORKER_LOG_FILE = "worker.log";
|
|
48202
48340
|
async function startInvestigationRun(options) {
|
|
48203
48341
|
assertHasInvestigationInput(options.query, options.inputText);
|
|
48204
48342
|
const repoPath = resolve(options.cwd ?? process.cwd());
|
|
@@ -48254,6 +48392,8 @@ async function startInvestigationRun(options) {
|
|
|
48254
48392
|
runId: runDir.runId,
|
|
48255
48393
|
workspaceKey: runDir.workspaceKey,
|
|
48256
48394
|
runPath: runDir.root,
|
|
48395
|
+
runsRoot: runDir.runsRoot,
|
|
48396
|
+
temporaryRunsRoot: runDir.temporaryRunsRoot === true,
|
|
48257
48397
|
pid: worker.pid
|
|
48258
48398
|
});
|
|
48259
48399
|
}
|
|
@@ -48331,6 +48471,8 @@ function workerStartedBrowserSessionIds(runDir) {
|
|
|
48331
48471
|
function spawnInvestigationWorker({ runPath }) {
|
|
48332
48472
|
const entryPoint = process.argv[1];
|
|
48333
48473
|
if (entryPoint === void 0 || entryPoint.length === 0) throw new CliError$1("Unable to locate Quire CLI entrypoint for background worker.", ExitCode.InternalError);
|
|
48474
|
+
const workerLogPath = `${runPath}/${WORKER_LOG_FILE}`;
|
|
48475
|
+
const workerLogFd = openSync(workerLogPath, "a", 384);
|
|
48334
48476
|
const child = spawn(process.execPath, [
|
|
48335
48477
|
...process.execArgv,
|
|
48336
48478
|
entryPoint,
|
|
@@ -48343,23 +48485,89 @@ function spawnInvestigationWorker({ runPath }) {
|
|
|
48343
48485
|
env: process.env,
|
|
48344
48486
|
stdio: [
|
|
48345
48487
|
"ignore",
|
|
48346
|
-
|
|
48347
|
-
|
|
48488
|
+
workerLogFd,
|
|
48489
|
+
workerLogFd
|
|
48348
48490
|
]
|
|
48349
48491
|
});
|
|
48350
|
-
|
|
48351
|
-
return
|
|
48492
|
+
closeSync(workerLogFd);
|
|
48493
|
+
return waitForWorkerBootstrap({
|
|
48494
|
+
child,
|
|
48495
|
+
runPath,
|
|
48496
|
+
workerLogPath
|
|
48497
|
+
}).finally(() => {
|
|
48498
|
+
child.unref();
|
|
48499
|
+
});
|
|
48500
|
+
}
|
|
48501
|
+
async function waitForWorkerBootstrap({ child, runPath, workerLogPath }) {
|
|
48502
|
+
const exit = new Promise((resolveExit) => {
|
|
48503
|
+
child.once("exit", (code, signal) => resolveExit({
|
|
48504
|
+
code,
|
|
48505
|
+
signal
|
|
48506
|
+
}));
|
|
48507
|
+
});
|
|
48508
|
+
const timeout = new Promise((resolveTimeout) => {
|
|
48509
|
+
setTimeout(() => resolveTimeout("timeout"), WORKER_BOOTSTRAP_TIMEOUT_MS).unref();
|
|
48510
|
+
});
|
|
48511
|
+
const started = waitForRunToLeaveQueued(runPath);
|
|
48512
|
+
const result = await Promise.race([
|
|
48513
|
+
exit,
|
|
48514
|
+
timeout,
|
|
48515
|
+
started.promise
|
|
48516
|
+
]);
|
|
48517
|
+
started.stop();
|
|
48518
|
+
if (result === "started") return { pid: child.pid };
|
|
48519
|
+
const status = readRunStatus(runPath);
|
|
48520
|
+
if (result !== "timeout" && status?.status === "queued") throw new CliError$1(formatEarlyWorkerExitError({
|
|
48521
|
+
workerLogPath,
|
|
48522
|
+
code: result.code,
|
|
48523
|
+
signal: result.signal
|
|
48524
|
+
}), ExitCode.InternalError);
|
|
48525
|
+
return { pid: child.pid };
|
|
48526
|
+
}
|
|
48527
|
+
function waitForRunToLeaveQueued(runPath) {
|
|
48528
|
+
let stopped = false;
|
|
48529
|
+
return {
|
|
48530
|
+
promise: new Promise((resolveStarted) => {
|
|
48531
|
+
const checkStatus = () => {
|
|
48532
|
+
if (stopped) return;
|
|
48533
|
+
const status = readRunStatus(runPath);
|
|
48534
|
+
if (status !== void 0 && status.status !== "queued") {
|
|
48535
|
+
resolveStarted("started");
|
|
48536
|
+
return;
|
|
48537
|
+
}
|
|
48538
|
+
setTimeout(checkStatus, 25).unref();
|
|
48539
|
+
};
|
|
48540
|
+
checkStatus();
|
|
48541
|
+
}),
|
|
48542
|
+
stop: () => {
|
|
48543
|
+
stopped = true;
|
|
48544
|
+
}
|
|
48545
|
+
};
|
|
48546
|
+
}
|
|
48547
|
+
function formatEarlyWorkerExitError({ workerLogPath, code, signal }) {
|
|
48548
|
+
const exit = signal === null ? `exit code ${code ?? "unknown"}` : `signal ${signal}`;
|
|
48549
|
+
const tail = readWorkerLogTail(workerLogPath);
|
|
48550
|
+
return `Worker process exited before marking the run as running (${exit}).\n\n${tail.length === 0 ? `Worker log: ${workerLogPath} (empty)` : `Worker log: ${workerLogPath}\n\nLast worker output:\n${tail}`}`;
|
|
48551
|
+
}
|
|
48552
|
+
function readWorkerLogTail(workerLogPath) {
|
|
48553
|
+
try {
|
|
48554
|
+
return readFileSync(workerLogPath, "utf8").slice(-12e3).trim();
|
|
48555
|
+
} catch {
|
|
48556
|
+
return "";
|
|
48557
|
+
}
|
|
48352
48558
|
}
|
|
48353
48559
|
function runHandle(input) {
|
|
48560
|
+
const commandPrefix = input.temporaryRunsRoot ? `QUIRE_RUNS_DIR=${shellQuote(input.runsRoot)} ` : "";
|
|
48354
48561
|
return {
|
|
48355
48562
|
status: "queued",
|
|
48356
48563
|
runId: input.runId,
|
|
48357
48564
|
workspaceKey: input.workspaceKey,
|
|
48358
48565
|
runPath: toJsonPath(input.runPath),
|
|
48359
48566
|
...input.pid === void 0 ? {} : { pid: input.pid },
|
|
48360
|
-
statusCommand:
|
|
48361
|
-
|
|
48362
|
-
|
|
48567
|
+
statusCommand: `${commandPrefix}quire status ${shellQuote(input.runId)} --json`,
|
|
48568
|
+
waitCommand: `${commandPrefix}quire wait ${shellQuote(input.runId)} --json`,
|
|
48569
|
+
watchCommand: `${commandPrefix}quire watch ${shellQuote(input.runId)}`,
|
|
48570
|
+
cancelCommand: `${commandPrefix}quire cancel ${shellQuote(input.runId)}`
|
|
48363
48571
|
};
|
|
48364
48572
|
}
|
|
48365
48573
|
function readWorkerRequest(runPath) {
|
|
@@ -48544,6 +48752,38 @@ const watchCommand = defineCommand({
|
|
|
48544
48752
|
await watchRun(readRequiredString(args.run, "run"));
|
|
48545
48753
|
}
|
|
48546
48754
|
});
|
|
48755
|
+
const waitCommand = defineCommand({
|
|
48756
|
+
meta: {
|
|
48757
|
+
name: "wait",
|
|
48758
|
+
description: "Wait for a Quire investigation run to reach a terminal status."
|
|
48759
|
+
},
|
|
48760
|
+
args: {
|
|
48761
|
+
run: {
|
|
48762
|
+
type: "positional",
|
|
48763
|
+
description: "Run id or run directory.",
|
|
48764
|
+
required: true
|
|
48765
|
+
},
|
|
48766
|
+
json: {
|
|
48767
|
+
type: "boolean",
|
|
48768
|
+
description: "Emit terminal status JSON."
|
|
48769
|
+
},
|
|
48770
|
+
"timeout-ms": {
|
|
48771
|
+
type: "string",
|
|
48772
|
+
description: "Maximum time to wait in milliseconds. Defaults to 600000."
|
|
48773
|
+
},
|
|
48774
|
+
"interval-ms": {
|
|
48775
|
+
type: "string",
|
|
48776
|
+
description: "Polling interval in milliseconds. Defaults to 5000."
|
|
48777
|
+
}
|
|
48778
|
+
},
|
|
48779
|
+
async run({ args }) {
|
|
48780
|
+
await waitRun(readRequiredString(args.run, "run"), {
|
|
48781
|
+
json: args.json === true,
|
|
48782
|
+
timeoutMs: readPositiveIntegerOption(args["timeout-ms"], "--timeout-ms") ?? 6e5,
|
|
48783
|
+
intervalMs: readPositiveIntegerOption(args["interval-ms"], "--interval-ms") ?? 5e3
|
|
48784
|
+
});
|
|
48785
|
+
}
|
|
48786
|
+
});
|
|
48547
48787
|
const cancelCommand = defineCommand({
|
|
48548
48788
|
meta: {
|
|
48549
48789
|
name: "cancel",
|
|
@@ -48669,6 +48909,28 @@ async function watchRun(run, options = {}) {
|
|
|
48669
48909
|
if (sigintHandler !== void 0) process.off("SIGINT", sigintHandler);
|
|
48670
48910
|
}
|
|
48671
48911
|
}
|
|
48912
|
+
async function waitRun(run, options = {}) {
|
|
48913
|
+
const stdout = options.io?.stdout ?? process.stdout;
|
|
48914
|
+
const runPath = resolveRunPath(run, options);
|
|
48915
|
+
const sleep = options.sleep ?? defaultSleep$1;
|
|
48916
|
+
const intervalMs = options.intervalMs ?? 5e3;
|
|
48917
|
+
const timeoutMs = options.timeoutMs ?? 6e5;
|
|
48918
|
+
const now = options.now ?? Date.now;
|
|
48919
|
+
const deadline = now() + timeoutMs;
|
|
48920
|
+
while (true) {
|
|
48921
|
+
const status = withSyncStatus(readFreshRunStatus(runPath));
|
|
48922
|
+
if (isTerminalRunStatus(status.status)) {
|
|
48923
|
+
if (options.json === true) writeJson(stdout, status);
|
|
48924
|
+
else {
|
|
48925
|
+
writeLine(stdout, renderFinalStatus(status));
|
|
48926
|
+
writeHandoffSummary(stdout, runDirFromStatus(status).paths.handoff);
|
|
48927
|
+
}
|
|
48928
|
+
return status;
|
|
48929
|
+
}
|
|
48930
|
+
if (now() >= deadline) throw new CliError$1(`Timed out waiting for run ${status.runId} to finish; latest status is ${status.status}. Use \`quire status ${status.runId} --json\` or \`quire watch ${status.runId}\` to inspect progress.`, ExitCode.InvalidInput);
|
|
48931
|
+
await sleep(Math.min(intervalMs, Math.max(0, deadline - now())));
|
|
48932
|
+
}
|
|
48933
|
+
}
|
|
48672
48934
|
async function cancelRun(run, options = {}) {
|
|
48673
48935
|
const stdout = options.io?.stdout ?? process.stdout;
|
|
48674
48936
|
const runPath = resolveRunPath(run, options);
|
|
@@ -48729,9 +48991,15 @@ async function syncRun(run, options = {}) {
|
|
|
48729
48991
|
function withSyncStatus(status) {
|
|
48730
48992
|
const sync = readInvestigationSyncStatus(runDirFromStatus(status));
|
|
48731
48993
|
const { sync: _storedSync, ...baseStatus } = status;
|
|
48732
|
-
|
|
48994
|
+
if (sync === void 0) return baseStatus;
|
|
48995
|
+
return sync.caseUrl === void 0 ? {
|
|
48733
48996
|
...baseStatus,
|
|
48734
48997
|
sync
|
|
48998
|
+
} : {
|
|
48999
|
+
...baseStatus,
|
|
49000
|
+
sync,
|
|
49001
|
+
webUrl: sync.caseUrl,
|
|
49002
|
+
caseUrl: sync.caseUrl
|
|
48735
49003
|
};
|
|
48736
49004
|
}
|
|
48737
49005
|
function signalRunProcess(pid, signal) {
|
|
@@ -48754,6 +49022,13 @@ function statusColor(status) {
|
|
|
48754
49022
|
function formatNumber(value) {
|
|
48755
49023
|
return new Intl.NumberFormat("en-US").format(value);
|
|
48756
49024
|
}
|
|
49025
|
+
function readPositiveIntegerOption(value, name) {
|
|
49026
|
+
if (value === void 0 || value === null || value === false) return;
|
|
49027
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new CliError$1(`${name} must be a positive integer.`, ExitCode.InvalidInput);
|
|
49028
|
+
const parsed = Number(value);
|
|
49029
|
+
if (!Number.isInteger(parsed) || parsed <= 0) throw new CliError$1(`${name} must be a positive integer.`, ExitCode.InvalidInput);
|
|
49030
|
+
return parsed;
|
|
49031
|
+
}
|
|
48757
49032
|
function defaultSleep$1(ms) {
|
|
48758
49033
|
return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
|
|
48759
49034
|
}
|
|
@@ -48813,6 +49088,7 @@ const investigationCommandSchema = {
|
|
|
48813
49088
|
"workspaceKey",
|
|
48814
49089
|
"runPath",
|
|
48815
49090
|
"statusCommand",
|
|
49091
|
+
"waitCommand",
|
|
48816
49092
|
"watchCommand",
|
|
48817
49093
|
"cancelCommand"
|
|
48818
49094
|
],
|
|
@@ -48826,6 +49102,7 @@ const investigationCommandSchema = {
|
|
|
48826
49102
|
optional: true
|
|
48827
49103
|
},
|
|
48828
49104
|
statusCommand: { type: "string" },
|
|
49105
|
+
waitCommand: { type: "string" },
|
|
48829
49106
|
watchCommand: { type: "string" },
|
|
48830
49107
|
cancelCommand: { type: "string" }
|
|
48831
49108
|
}
|
|
@@ -48833,6 +49110,7 @@ const investigationCommandSchema = {
|
|
|
48833
49110
|
followUpCommands: {
|
|
48834
49111
|
continue: "quire continue <run-id> \"additional context or instruction\"",
|
|
48835
49112
|
status: "quire status <run-id> --json",
|
|
49113
|
+
wait: "quire wait <run-id> --json",
|
|
48836
49114
|
watch: "quire watch <run-id>",
|
|
48837
49115
|
cancel: "quire cancel <run-id>"
|
|
48838
49116
|
},
|
|
@@ -48867,6 +49145,7 @@ const investigationCommandSchema = {
|
|
|
48867
49145
|
"quire \"verify checkout before I approve this PR\"",
|
|
48868
49146
|
"printf 'why is CI failing?\\nRelevant log: ...' | quire run --json",
|
|
48869
49147
|
"quire status run_9x4mdq7p2h8kc6nv4apz --json",
|
|
49148
|
+
"quire wait run_9x4mdq7p2h8kc6nv4apz --json",
|
|
48870
49149
|
"quire watch run_9x4mdq7p2h8kc6nv4apz"
|
|
48871
49150
|
]
|
|
48872
49151
|
};
|
|
@@ -48954,7 +49233,9 @@ async function runInvestigate(options) {
|
|
|
48954
49233
|
});
|
|
48955
49234
|
if (options.detach === true) log.message([
|
|
48956
49235
|
`${color.label("Run")}: ${color.path(handle.runPath)}`,
|
|
49236
|
+
...handle.webUrl === void 0 ? [] : [`${color.label("Case")}: ${handle.webUrl}`],
|
|
48957
49237
|
`${color.label("Status")}: ${color.command(handle.statusCommand)}`,
|
|
49238
|
+
`${color.label("Wait")}: ${color.command(handle.waitCommand)}`,
|
|
48958
49239
|
`${color.label("Watch")}: ${color.command(handle.watchCommand)}`,
|
|
48959
49240
|
`${color.label("Cancel")}: ${color.command(handle.cancelCommand)}`
|
|
48960
49241
|
], {
|
|
@@ -48965,15 +49246,16 @@ async function runInvestigate(options) {
|
|
|
48965
49246
|
else log.message([
|
|
48966
49247
|
`${color.label("Run")}: ${color.path(handle.runPath)}`,
|
|
48967
49248
|
`${color.label("Status")}: ${color.command(handle.statusCommand)}`,
|
|
49249
|
+
`${color.label("Wait")}: ${color.command(handle.waitCommand)}`,
|
|
48968
49250
|
`${color.label("Watch")}: ${color.command(handle.watchCommand)}`,
|
|
48969
|
-
`This run continues in the background. You can safely exit; use ${color.command(handle.statusCommand)} for the current state or ${color.command(handle.watchCommand)} to follow progress logs.`
|
|
49251
|
+
`This run continues in the background. You can safely exit; use ${color.command(handle.waitCommand)} for the terminal handoff, ${color.command(handle.statusCommand)} for the current state, or ${color.command(handle.watchCommand)} to follow progress logs.`
|
|
48970
49252
|
], {
|
|
48971
49253
|
output: stdout,
|
|
48972
49254
|
spacing: 0,
|
|
48973
49255
|
withGuide: true
|
|
48974
49256
|
});
|
|
48975
49257
|
}
|
|
48976
|
-
if (options.json !== true && options.detach !== true) await (options.watchRunner ?? watchRun)(handle.
|
|
49258
|
+
if (options.json !== true && options.detach !== true) await (options.watchRunner ?? watchRun)(handle.runPath, {
|
|
48977
49259
|
cwd: options.cwd,
|
|
48978
49260
|
runsRoot: options.runsRoot,
|
|
48979
49261
|
io: { stdout }
|
|
@@ -49114,11 +49396,13 @@ function pluralize(count, singular, plural) {
|
|
|
49114
49396
|
//#region src/doctor/checks.ts
|
|
49115
49397
|
const execFileAsync = promisify(execFile);
|
|
49116
49398
|
function createDoctorContext(overrides = {}) {
|
|
49399
|
+
const env = overrides.env ?? process.env;
|
|
49117
49400
|
return {
|
|
49118
49401
|
cwd: resolve(overrides.cwd ?? process.cwd()),
|
|
49119
|
-
runsRoot: resolve(overrides.runsRoot ?? defaultRunsRoot()),
|
|
49402
|
+
runsRoot: resolve(overrides.runsRoot ?? defaultRunsRoot(env)),
|
|
49403
|
+
runsRootExplicit: overrides.runsRootExplicit ?? (overrides.runsRoot !== void 0 || (env.QUIRE_RUNS_DIR?.trim().length ?? 0) > 0),
|
|
49120
49404
|
authStore: overrides.authStore ?? authStore,
|
|
49121
|
-
env
|
|
49405
|
+
env,
|
|
49122
49406
|
fetchFn: overrides.fetchFn ?? fetch,
|
|
49123
49407
|
execFn: overrides.execFn ?? defaultExecFn,
|
|
49124
49408
|
probeTimeoutMs: overrides.probeTimeoutMs ?? 2e3
|
|
@@ -49310,6 +49594,22 @@ async function checkRunsRoot(context) {
|
|
|
49310
49594
|
message: `Runs root writable (${context.runsRoot}).`
|
|
49311
49595
|
};
|
|
49312
49596
|
} catch (error) {
|
|
49597
|
+
if (!context.runsRootExplicit && isWriteAccessError(error)) {
|
|
49598
|
+
const fallbackRunsRoot = await mkdtemp(join(tmpdir(), "quire-runs-"));
|
|
49599
|
+
await mkdir(fallbackRunsRoot, {
|
|
49600
|
+
recursive: true,
|
|
49601
|
+
mode: 448
|
|
49602
|
+
});
|
|
49603
|
+
await access(fallbackRunsRoot, constants.W_OK);
|
|
49604
|
+
context.runsRoot = fallbackRunsRoot;
|
|
49605
|
+
return {
|
|
49606
|
+
id: "runs.rootWritable",
|
|
49607
|
+
section: "runs",
|
|
49608
|
+
severity: "warn",
|
|
49609
|
+
message: `Default runs root not writable (${toMessage(error)}); using temporary runs root ${fallbackRunsRoot}.`,
|
|
49610
|
+
fix: { command: `Set QUIRE_RUNS_DIR=${fallbackRunsRoot} for follow-up Quire commands in this shell.` }
|
|
49611
|
+
};
|
|
49612
|
+
}
|
|
49313
49613
|
return {
|
|
49314
49614
|
id: "runs.rootWritable",
|
|
49315
49615
|
section: "runs",
|
|
@@ -49354,6 +49654,9 @@ function checkStuckRuns(context) {
|
|
|
49354
49654
|
};
|
|
49355
49655
|
}
|
|
49356
49656
|
}
|
|
49657
|
+
function isWriteAccessError(error) {
|
|
49658
|
+
return error instanceof Error && "code" in error && (error.code === "EROFS" || error.code === "EACCES" || error.code === "EPERM");
|
|
49659
|
+
}
|
|
49357
49660
|
function formatUser$2(user) {
|
|
49358
49661
|
if (user.email !== void 0 && user.email !== null && user.email.length > 0) return user.email;
|
|
49359
49662
|
if (user.name !== void 0 && user.name !== null && user.name.length > 0) return user.name;
|
|
@@ -49664,6 +49967,7 @@ const logoutCommand = defineCommand({
|
|
|
49664
49967
|
async function runLogout(options) {
|
|
49665
49968
|
const store = options.store ?? authStore;
|
|
49666
49969
|
const stdout = options.io?.stdout ?? process.stdout;
|
|
49970
|
+
const env = options.env ?? process.env;
|
|
49667
49971
|
const existing = await store.get();
|
|
49668
49972
|
await store.clear();
|
|
49669
49973
|
if (existing === null) log.info("Already logged out.", {
|
|
@@ -49674,48 +49978,387 @@ async function runLogout(options) {
|
|
|
49674
49978
|
output: stdout,
|
|
49675
49979
|
spacing: 0
|
|
49676
49980
|
});
|
|
49981
|
+
if (env["QUIRE_API_TOKEN"]?.trim()) log.warn(`${QUIRE_API_TOKEN_ENV} is still set, so Quire commands may still authenticate.`, {
|
|
49982
|
+
output: stdout,
|
|
49983
|
+
spacing: 0
|
|
49984
|
+
});
|
|
49677
49985
|
}
|
|
49678
49986
|
//#endregion
|
|
49679
49987
|
//#region src/commands/setup.ts
|
|
49680
|
-
const
|
|
49681
|
-
const
|
|
49682
|
-
const
|
|
49988
|
+
const DEFAULT_PUBLIC_BASE_URL = "https://quire.sh";
|
|
49989
|
+
const SETUP_URL_PLACEHOLDER = "__QUIRE_SETUP_URL__";
|
|
49990
|
+
const VERIFICATION_SKILL_URL_PLACEHOLDER = "__QUIRE_VERIFICATION_SKILL_URL__";
|
|
49991
|
+
const AGENT_SETUP_PROMPT = `Help me connect Quire to this repository end-to-end.
|
|
49992
|
+
|
|
49993
|
+
Use or install the Quire CLI. If \`quire\` is unavailable, use \`npx @quireco/cli@latest\` for this setup or ask before installing it globally.
|
|
49994
|
+
|
|
49995
|
+
Load and follow the Quire setup skill from ${SETUP_URL_PLACEHOLDER}.
|
|
49996
|
+
|
|
49997
|
+
Check \`quire whoami --json\` and \`quire doctor --json\` using the same Quire command throughout setup. If account auth is missing, tell me exactly what I need to do, run \`quire login\` when browser auth is appropriate, and wait for me to finish. Do not ask me to paste secrets into chat.
|
|
49998
|
+
|
|
49999
|
+
After login, Quire Wallet is the default model source. If Wallet has balance, use it. If Wallet needs funds, show me the top-up action from \`quire whoami --json\` or open https://quire.sh/dashboard?settings=billing. If I prefer to use an existing subscription instead, help me connect ChatGPT/Codex with \`quire connect chatgpt\` or GitHub Copilot with \`quire connect copilot\`.
|
|
50000
|
+
|
|
50001
|
+
Inspect the project, ask focused questions for important missing runbook facts instead of writing placeholder unknowns, then create or update .quire/runbook.md with this repo's verification loop: target selection, local startup, browser/API/CLI/mobile checks, console/runtime signals, core flows, trusted commands, design/reference surfaces, auth/data setup, performance/accessibility expectations, and mutation-safety rules.
|
|
50002
|
+
|
|
50003
|
+
Update AGENTS.md with concise Quire notes. Rerun \`quire doctor\`, recover from sandbox write issues if needed, then start one small read-only smoke run with \`quire run --detach --json\`. Wait for a terminal result and report the synced Quire run link, local report/evidence path, and any blocker with the exact next action.`;
|
|
50004
|
+
const QUIRE_VERIFY_SKILL_MARKDOWN = `---
|
|
50005
|
+
name: verify
|
|
50006
|
+
description: Verify claims, code changes, bug fixes, workflows, and release readiness with independent evidence. Use when asked to verify, QA, smoke test, prove a fix, or check non-trivial work before completion.
|
|
50007
|
+
---
|
|
50008
|
+
|
|
50009
|
+
# Verify
|
|
50010
|
+
|
|
50011
|
+
Load and follow the current Quire verification skill from ${VERIFICATION_SKILL_URL_PLACEHOLDER}.
|
|
50012
|
+
|
|
50013
|
+
Use this skill to prove whether a claim, code change, bug fix, test result, workflow, or release/readiness assertion is actually correct. Quire is the independent read-only QA/verification agent for this workflow. Quire does not implement fixes. Quire reports pass, fail, blocked, or partial results honestly with evidence.
|
|
50014
|
+
|
|
50015
|
+
## When to use this skill
|
|
49683
50016
|
|
|
49684
|
-
|
|
50017
|
+
Use this skill when the user asks to verify, QA, smoke test, validate, prove a fix, check a workflow, confirm a release claim, ask Quire, run Quire, or get a Quire run id/verdict/handoff.
|
|
49685
50018
|
|
|
49686
|
-
|
|
49687
|
-
|
|
50019
|
+
Also use Quire before declaring non-trivial coding work complete when independent verification would materially increase confidence, especially for UI/workflow changes, bug fixes, failing or flaky tests, auth, security, permissions, billing, data loss, migrations, concurrency, integrations, or release-readiness work.
|
|
50020
|
+
|
|
50021
|
+
Do not use Quire for every tiny task. Usually skip independent verification for purely conversational answers, simple docs/formatting edits, trivial mechanical changes where local checks are enough, or repeated verification of the same unchanged diff. Exception: if the user explicitly asks for Quire or independent verification, use Quire anyway.
|
|
50022
|
+
|
|
50023
|
+
## Hard requirement
|
|
50024
|
+
|
|
50025
|
+
If the caller explicitly asks for Quire or independent verification, do not substitute your own direct checks, web fetches, curl commands, tests, browser checks, or manual reasoning and claim that is equivalent. You may do basic preparation, but Quire itself must be invoked.
|
|
50026
|
+
|
|
50027
|
+
Recommended path: if this harness exposes a native subagent named \`quire\`, delegate the verification brief to that subagent and wait for its result. Do not decide the task is simple enough to run \`quire\` directly from the parent session when a native \`quire\` subagent is available. Use CLI fallback only when no native \`quire\` subagent is available.
|
|
50028
|
+
|
|
50029
|
+
If the caller asks whether the Quire agent/subagent path was used, answer from your actual invocation path. If you used a native \`quire\` child task/subagent, report that the native Quire subagent path was used even when \`quire wait\` returns metadata for an older run whose internal harness was \`pi\`, Codex, or another agent. Treat old run metadata as separate historical context, not as evidence that the current adapter path was not used.
|
|
50030
|
+
|
|
50031
|
+
When communicating Quire results to the user, surface the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present. Include the report/handoff markdown summary and call out useful artifacts such as screenshots, recordings, traces, or other evidence when they help the user trust or review the result.
|
|
50032
|
+
|
|
50033
|
+
## How to invoke Quire
|
|
50034
|
+
|
|
50035
|
+
Preferred order:
|
|
50036
|
+
|
|
50037
|
+
1. If this harness exposes a native subagent named \`quire\`, delegate the verification brief to that subagent and wait for its result.
|
|
50038
|
+
2. If no native Quire subagent is available, run the CLI directly.
|
|
50039
|
+
|
|
50040
|
+
CLI fallback:
|
|
50041
|
+
|
|
50042
|
+
\`\`\`bash
|
|
50043
|
+
run_json=$(quire run --detach --json --url "<url>" "<verification request>")
|
|
50044
|
+
run_id=$(printf '%s' "$run_json" | jq -r '.runId')
|
|
50045
|
+
quire wait "$run_id" --json
|
|
50046
|
+
\`\`\`
|
|
50047
|
+
|
|
50048
|
+
If the caller provides an existing Quire run id, do not start a new run and do not use \`quire status\` as the final result. Wait for and return the handoff with:
|
|
50049
|
+
|
|
50050
|
+
\`\`\`bash
|
|
50051
|
+
quire wait "<run-id>" --json
|
|
50052
|
+
\`\`\`
|
|
50053
|
+
|
|
50054
|
+
The JSON output includes \`webUrl\` / \`caseUrl\` when the run has synced to Quire's web app. Use that link as the shareable evidence URL in user-facing summaries.
|
|
50055
|
+
|
|
50056
|
+
Omit \`--url\` when there is no URL and include the target context in the natural-language request. Use \`quire wait\` as the waiting primitive. Do not manually poll unless \`quire wait\` is unavailable.
|
|
50057
|
+
|
|
50058
|
+
If Quire reports \`EROFS\`, \`EACCES\`, or another write error under \`~/.local/share/quire/runs\`, retry with a temporary run store and reuse the same value for every lifecycle command:
|
|
50059
|
+
|
|
50060
|
+
\`\`\`bash
|
|
50061
|
+
quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
|
|
50062
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
|
|
50063
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
|
|
50064
|
+
\`\`\`
|
|
50065
|
+
|
|
50066
|
+
## Verification brief template
|
|
50067
|
+
|
|
50068
|
+
- Claim/change to verify:
|
|
50069
|
+
- Relevant files/paths:
|
|
50070
|
+
- Commands already run:
|
|
50071
|
+
- Expected behavior:
|
|
50072
|
+
- Known risks or edge cases:
|
|
50073
|
+
- Constraints: read-only verification; do not edit code.
|
|
50074
|
+
|
|
50075
|
+
Return Quire's terminal handoff with:
|
|
50076
|
+
|
|
50077
|
+
- Verdict: pass, fail, blocked, partial, or setup-blocked
|
|
50078
|
+
- Quire run id
|
|
50079
|
+
- Quire web URL when present
|
|
50080
|
+
- What Quire verified
|
|
50081
|
+
- Key evidence, artifacts, report, or handoff excerpt
|
|
50082
|
+
- Follow-up actions for the implementation agent
|
|
50083
|
+
`;
|
|
50084
|
+
const QUIRE_DEBUG_SKILL_MARKDOWN = `---
|
|
50085
|
+
name: debug
|
|
50086
|
+
description: Diagnose bugs, regressions, flaky behavior, and broken workflows with a reproduce-first loop. Use when asked to debug, diagnose, investigate a failure, reproduce a bug, or triage a regression.
|
|
50087
|
+
---
|
|
50088
|
+
|
|
50089
|
+
# Debug
|
|
50090
|
+
|
|
50091
|
+
Use this skill for disciplined diagnosis: reproduce the failure, minimize the loop, form hypotheses, instrument only what distinguishes them, fix, and regression-test. Quire is the independent evidence collector for this workflow when a fresh reproduction, browser/API/mobile check, or external verification handoff would materially improve confidence.
|
|
50092
|
+
|
|
50093
|
+
## When to use Quire during diagnosis
|
|
50094
|
+
|
|
50095
|
+
Use the native \`quire\` subagent when you need an independent read-only investigator to:
|
|
50096
|
+
|
|
50097
|
+
- reproduce a reported bug or flaky workflow;
|
|
50098
|
+
- verify a suspected fix against the real app, browser, API, CLI, or mobile surface;
|
|
50099
|
+
- gather evidence such as screenshots, recordings, console output, terminal logs, or a concise handoff;
|
|
50100
|
+
- check whether a failure is environment/setup-blocked versus a product bug;
|
|
50101
|
+
- produce a run id and terminal verdict that another agent can use in an implementation/verification loop.
|
|
50102
|
+
|
|
50103
|
+
Do not outsource implementation to Quire. Quire should not edit code. The parent/debugging agent owns hypotheses, code changes, and regression tests.
|
|
50104
|
+
|
|
50105
|
+
## Recommended invocation
|
|
50106
|
+
|
|
50107
|
+
1. Build or identify the best reproduction loop you can from the report.
|
|
50108
|
+
2. If this harness exposes a native subagent named \`quire\`, delegate a concise reproduction or verification brief to that subagent and wait for its handoff.
|
|
50109
|
+
3. If no native \`quire\` subagent is available, use the Quire CLI fallback from ${VERIFICATION_SKILL_URL_PLACEHOLDER}.
|
|
50110
|
+
4. Use Quire's result as evidence, not as a substitute for understanding the code path. When communicating the result, surface the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present, and mention useful report artifacts such as screenshots, recordings, traces, or the handoff markdown when they help the user review the diagnosis. If Quire reports blocked, preserve that honestly and fix the setup or ask for the missing environment.
|
|
50111
|
+
|
|
50112
|
+
## Quire brief template
|
|
50113
|
+
|
|
50114
|
+
- Failure or regression to reproduce:
|
|
50115
|
+
- User-visible symptom:
|
|
50116
|
+
- Target URL, command, app, or environment:
|
|
50117
|
+
- Steps already tried:
|
|
50118
|
+
- Expected behavior:
|
|
50119
|
+
- Actual behavior:
|
|
50120
|
+
- Evidence to capture:
|
|
50121
|
+
- Constraints: read-only investigation; do not edit code.
|
|
50122
|
+
|
|
50123
|
+
## CLI fallback
|
|
50124
|
+
|
|
50125
|
+
Use CLI fallback only if the native \`quire\` subagent is unavailable. Prefer detached JSON mode and wait for the terminal handoff:
|
|
50126
|
+
|
|
50127
|
+
\`\`\`bash
|
|
50128
|
+
run_json=$(quire run --detach --json --url "<url>" "<reproduction or verification request>")
|
|
50129
|
+
run_id=$(printf '%s' "$run_json" | jq -r '.runId')
|
|
50130
|
+
quire wait "$run_id" --json
|
|
50131
|
+
\`\`\`
|
|
50132
|
+
|
|
50133
|
+
If the caller provides an existing Quire run id, do not start a new run and do not use \`quire status\` as the final result:
|
|
50134
|
+
|
|
50135
|
+
\`\`\`bash
|
|
50136
|
+
quire wait "<run-id>" --json
|
|
50137
|
+
\`\`\`
|
|
50138
|
+
|
|
50139
|
+
For more CLI details, sandbox recovery, and handoff interpretation, follow ${VERIFICATION_SKILL_URL_PLACEHOLDER}.
|
|
50140
|
+
`;
|
|
50141
|
+
const QUIRE_AGENT_MARKDOWN = `---
|
|
49688
50142
|
name: quire
|
|
49689
|
-
description:
|
|
50143
|
+
description: "Run Quire, the independent read-only QA/verification/triage agent. Use when the user explicitly asks for Quire or when independent verification of a non-trivial change, bug fix, test failure, or release claim is warranted. Returns pass, fail, blocked, or partial with evidence; never edits code."
|
|
50144
|
+
color: green
|
|
50145
|
+
model: inherit
|
|
49690
50146
|
---
|
|
49691
50147
|
|
|
49692
|
-
|
|
50148
|
+
You are Quire's native harness adapter. Your job is to launch Quire through the Quire CLI, wait for the result, and return the evidence-backed result to the parent agent.
|
|
49693
50149
|
|
|
49694
|
-
|
|
50150
|
+
Do not implement code changes. Do not commit, stage, format, rewrite, patch, or edit files. Do not perform broad independent QA outside Quire unless Quire cannot run and the caller explicitly asks for fallback evidence. Do not read or print secrets, tokens, .env files, or credential vaults. Treat production targets as read-only unless the caller explicitly approves mutation.
|
|
49695
50151
|
|
|
49696
|
-
|
|
50152
|
+
When reporting whether the Quire agent/subagent path was used, answer only about this native adapter invocation. If you are running these instructions as the \`quire\` subagent, then the native Quire subagent path was used. Do not add a second "internal run harness" answer, do not discuss a previous run's \`harness: "pi"\` metadata, and do not answer "no" for the current adapter path.
|
|
50153
|
+
|
|
50154
|
+
## Workflow
|
|
50155
|
+
|
|
50156
|
+
1. Convert the parent request into a concise Quire verification brief.
|
|
50157
|
+
2. Prefer the local \`quire\` command. Use the exact command style available in the workspace if the caller provides one. Do not downgrade an explicit Quire request to direct curl/browser/test checks, and do not report \`N/A\` for the run id because the claim looked small.
|
|
50158
|
+
3. Include \`--url <url>\` when the caller provides a URL or the project runbook clearly identifies the local/preview target.
|
|
50159
|
+
4. Start one Quire run for the verification claim. Do not start duplicate runs for the same claim just because the first run is still active.
|
|
50160
|
+
|
|
50161
|
+
If the caller provides an existing Quire run id, do not start a new run. Use \`quire wait "<run-id>" --json\` and return its terminal handoff.
|
|
50162
|
+
|
|
50163
|
+
Prefer detached JSON mode for agent handoffs:
|
|
50164
|
+
|
|
50165
|
+
\`\`\`bash
|
|
50166
|
+
run_json=$(quire run --detach --json --url "<url>" "<verification request>")
|
|
50167
|
+
run_id=$(printf '%s' "$run_json" | jq -r '.runId')
|
|
50168
|
+
\`\`\`
|
|
50169
|
+
|
|
50170
|
+
If there is no URL, omit \`--url\` and put target context in the natural-language request.
|
|
50171
|
+
|
|
50172
|
+
5. Wait patiently until terminal status:
|
|
50173
|
+
|
|
50174
|
+
\`\`\`bash
|
|
50175
|
+
quire wait "$run_id" --json
|
|
50176
|
+
\`\`\`
|
|
50177
|
+
|
|
50178
|
+
If Quire reports \`EROFS\`, \`EACCES\`, or another write error under \`~/.local/share/quire/runs\`, retry with a temporary run store and reuse the same value for every lifecycle command:
|
|
50179
|
+
|
|
50180
|
+
\`\`\`bash
|
|
50181
|
+
quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
|
|
50182
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
|
|
50183
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
|
|
50184
|
+
\`\`\`
|
|
50185
|
+
|
|
50186
|
+
Terminal statuses are \`completed\`, \`failed\`, \`canceled\`, and \`stale\`. Quire can take several minutes while it inspects context, opens browsers, runs shell checks, captures evidence, and writes a handoff.
|
|
50187
|
+
|
|
50188
|
+
6. Return \`finalHandoff.handoffMarkdown\` from \`quire wait --json\` when present. Include the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present, and mention useful report artifacts such as screenshots, recordings, traces, or evidence references when they help the parent agent communicate the result.
|
|
50189
|
+
7. If Quire reports blocked, failed, stale, or untested checks, preserve that honestly and include the next action. Do not summarize blocked or untested assertions as passed.
|
|
50190
|
+
8. If Quire cannot start because auth, model provider, wallet, browser, mobile, or target setup is missing, report setup-blocked with the exact reason and the next command Quire suggested.
|
|
50191
|
+
|
|
50192
|
+
## Return format
|
|
50193
|
+
|
|
50194
|
+
- Native Quire adapter invocation: yes, if you are running as the \`quire\` subagent.
|
|
50195
|
+
- Verdict: pass, fail, blocked, partial, or setup-blocked
|
|
50196
|
+
- Quire run id
|
|
50197
|
+
- Quire web URL when present
|
|
50198
|
+
- What Quire verified
|
|
50199
|
+
- Key evidence, artifacts, report, or handoff excerpt
|
|
50200
|
+
- Follow-up actions for the implementation agent
|
|
49697
50201
|
`;
|
|
49698
|
-
const
|
|
49699
|
-
|
|
49700
|
-
|
|
49701
|
-
|
|
49702
|
-
|
|
49703
|
-
|
|
49704
|
-
|
|
49705
|
-
|
|
49706
|
-
|
|
49707
|
-
|
|
49708
|
-
|
|
49709
|
-
|
|
49710
|
-
|
|
49711
|
-
|
|
49712
|
-
|
|
49713
|
-
|
|
49714
|
-
|
|
49715
|
-
|
|
49716
|
-
|
|
49717
|
-
|
|
49718
|
-
|
|
50202
|
+
const QUIRE_CURSOR_MARKDOWN = QUIRE_AGENT_MARKDOWN.replace("model: inherit\n---", "model: inherit\nreadonly: true\n---");
|
|
50203
|
+
const QUIRE_CODEX_TOML = `name = "quire"
|
|
50204
|
+
description = "Run Quire, the independent read-only QA/verification/triage agent. Use when explicitly asked for Quire or when independent verification of a non-trivial change, bug fix, test failure, or release claim is warranted. Returns pass, fail, blocked, or partial with evidence; never edits code."
|
|
50205
|
+
|
|
50206
|
+
developer_instructions = """
|
|
50207
|
+
You are Quire's native harness adapter. Your job is to launch Quire through the Quire CLI, wait for the result, and return the evidence-backed result to the parent agent.
|
|
50208
|
+
|
|
50209
|
+
Do not implement code changes. Do not commit, stage, format, rewrite, patch, or edit files. Do not perform broad independent QA outside Quire unless Quire cannot run and the caller explicitly asks for fallback evidence. Do not read or print secrets, tokens, .env files, or credential vaults. Treat production targets as read-only unless the caller explicitly approves mutation.
|
|
50210
|
+
|
|
50211
|
+
When reporting whether the Quire agent/subagent path was used, answer only about this native adapter invocation. If you are running these instructions as the \`quire\` subagent, then the native Quire subagent path was used. Do not add a second "internal run harness" answer, do not discuss a previous run's \`harness: "pi"\` metadata, and do not answer "no" for the current adapter path.
|
|
50212
|
+
|
|
50213
|
+
Workflow:
|
|
50214
|
+
1. Convert the parent request into a concise Quire verification brief.
|
|
50215
|
+
2. Prefer the local \`quire\` command. Use the exact command style available in the workspace if the caller provides one. Do not downgrade an explicit Quire request to direct curl/browser/test checks, and do not report \`N/A\` for the run id because the claim looked small.
|
|
50216
|
+
3. Include \`--url <url>\` when the caller provides a URL or the project runbook clearly identifies the local/preview target.
|
|
50217
|
+
4. Start one Quire run for the verification claim. Do not start duplicate runs for the same claim just because the first run is still active.
|
|
50218
|
+
|
|
50219
|
+
If the caller provides an existing Quire run id, do not start a new run. Use \`quire wait "<run-id>" --json\` and return its terminal handoff.
|
|
50220
|
+
|
|
50221
|
+
run_json=$(quire run --detach --json --url "<url>" "<verification request>")
|
|
50222
|
+
run_id=$(printf '%s' "$run_json" | jq -r '.runId')
|
|
50223
|
+
quire wait "$run_id" --json
|
|
50224
|
+
|
|
50225
|
+
5. If Quire reports EROFS, EACCES, or another write error under ~/.local/share/quire/runs, retry with a temporary run store and reuse the same value for every lifecycle command:
|
|
50226
|
+
|
|
50227
|
+
quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
|
|
50228
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
|
|
50229
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
|
|
50230
|
+
|
|
50231
|
+
6. Return finalHandoff.handoffMarkdown from \`quire wait --json\` when present. Include the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present, and mention useful report artifacts such as screenshots, recordings, traces, or evidence references when they help the parent agent communicate the result.
|
|
50232
|
+
7. If Quire reports blocked, failed, stale, or untested checks, preserve that honestly and include the next action. Do not summarize blocked or untested assertions as passed.
|
|
50233
|
+
|
|
50234
|
+
Return:
|
|
50235
|
+
- Native Quire adapter invocation: yes, if you are running as the \`quire\` subagent.
|
|
50236
|
+
- Verdict: pass, fail, blocked, partial, or setup-blocked
|
|
50237
|
+
- Quire run id
|
|
50238
|
+
- Quire web URL when present
|
|
50239
|
+
- What Quire verified
|
|
50240
|
+
- Key evidence, artifacts, report, or handoff excerpt
|
|
50241
|
+
- Follow-up actions for the implementation agent
|
|
50242
|
+
"""
|
|
50243
|
+
`;
|
|
50244
|
+
const QUIRE_OPENCODE_MARKDOWN = `---
|
|
50245
|
+
description: Native @quire subagent. If the user mentions @quire, the parent must invoke the Task tool's quire subagent and must not run Bash/CLI directly in the parent session. For existing run IDs, this subagent uses quire wait <run-id> --json, not quire status. If asked whether the agent/subagent path was used, answer yes for this native child invocation; old run harness metadata is separate historical context.
|
|
50246
|
+
mode: subagent
|
|
50247
|
+
permission:
|
|
50248
|
+
edit: deny
|
|
50249
|
+
---
|
|
50250
|
+
|
|
50251
|
+
You are Quire's native harness adapter. Your job is to launch Quire through the Quire CLI, wait for the result, and return the evidence-backed result to the parent agent.
|
|
50252
|
+
|
|
50253
|
+
Do not implement code changes. Do not commit, stage, format, rewrite, patch, or edit files. Do not perform broad independent QA outside Quire unless Quire cannot run and the caller explicitly asks for fallback evidence. Do not read or print secrets, tokens, .env files, or credential vaults. Treat production targets as read-only unless the caller explicitly approves mutation.
|
|
50254
|
+
|
|
50255
|
+
When reporting whether the Quire agent/subagent path was used, answer only about this native adapter invocation. If you are running these instructions as the \`quire\` subagent, then the native Quire subagent path was used. Do not add a second "internal run harness" answer, do not discuss a previous run's \`harness: "pi"\` metadata, and do not answer "no" for the current adapter path.
|
|
50256
|
+
|
|
50257
|
+
## Workflow
|
|
50258
|
+
|
|
50259
|
+
1. Convert the parent request into a concise Quire verification brief.
|
|
50260
|
+
2. Prefer the local \`quire\` command. Use the exact command style available in the workspace if the caller provides one. Do not downgrade an explicit Quire request to direct curl/browser/test checks, and do not report \`N/A\` for the run id because the claim looked small.
|
|
50261
|
+
3. Include \`--url <url>\` when the caller provides a URL or the project runbook clearly identifies the local/preview target.
|
|
50262
|
+
4. Start one Quire run for the verification claim. Do not start duplicate runs for the same claim just because the first run is still active.
|
|
50263
|
+
|
|
50264
|
+
If the caller provides an existing Quire run id, do not start a new run. Use \`quire wait "<run-id>" --json\` and return its terminal handoff.
|
|
50265
|
+
|
|
50266
|
+
\`\`\`bash
|
|
50267
|
+
run_json=$(quire run --detach --json --url "<url>" "<verification request>")
|
|
50268
|
+
run_id=$(printf '%s' "$run_json" | jq -r '.runId')
|
|
50269
|
+
quire wait "$run_id" --json
|
|
50270
|
+
\`\`\`
|
|
50271
|
+
|
|
50272
|
+
5. If Quire reports \`EROFS\`, \`EACCES\`, or another write error under \`~/.local/share/quire/runs\`, retry with a temporary run store and reuse the same value for every lifecycle command:
|
|
50273
|
+
|
|
50274
|
+
\`\`\`bash
|
|
50275
|
+
quire_runs_dir=$(mktemp -d -t quire-runs-XXXXXX)
|
|
50276
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire run --detach --json --url "<url>" "<verification request>"
|
|
50277
|
+
QUIRE_RUNS_DIR=$quire_runs_dir quire wait "<run-id>" --json
|
|
50278
|
+
\`\`\`
|
|
50279
|
+
|
|
50280
|
+
6. Return \`finalHandoff.handoffMarkdown\` from \`quire wait --json\` when present. Include the synced Quire run link from \`webUrl\`, \`caseUrl\`, or \`sync.caseUrl\` when present, and mention useful report artifacts such as screenshots, recordings, traces, or evidence references when they help the parent agent communicate the result.
|
|
50281
|
+
7. If Quire reports blocked, failed, stale, or untested checks, preserve that honestly and include the next action. Do not summarize blocked or untested assertions as passed.
|
|
50282
|
+
|
|
50283
|
+
## Return format
|
|
50284
|
+
|
|
50285
|
+
- Native Quire adapter invocation: yes, if you are running as the \`quire\` subagent.
|
|
50286
|
+
- Verdict: pass, fail, blocked, partial, or setup-blocked
|
|
50287
|
+
- Quire run id
|
|
50288
|
+
- Quire web URL when present
|
|
50289
|
+
- What Quire verified
|
|
50290
|
+
- Key evidence, artifacts, report, or handoff excerpt
|
|
50291
|
+
- Follow-up actions for the implementation agent
|
|
50292
|
+
`;
|
|
50293
|
+
const SKILL_INSTALL_TARGETS = [
|
|
50294
|
+
{
|
|
50295
|
+
id: "agents",
|
|
50296
|
+
label: "Universal",
|
|
50297
|
+
skillPaths: [{
|
|
50298
|
+
path: ".agents/skills/verify/SKILL.md",
|
|
50299
|
+
contents: QUIRE_VERIFY_SKILL_MARKDOWN
|
|
50300
|
+
}, {
|
|
50301
|
+
path: ".agents/skills/debug/SKILL.md",
|
|
50302
|
+
contents: QUIRE_DEBUG_SKILL_MARKDOWN
|
|
50303
|
+
}],
|
|
50304
|
+
agentPath: ".agents/agents/quire.md",
|
|
50305
|
+
codexAgentPath: ".codex/agents/quire.toml",
|
|
50306
|
+
opencodeAgentPath: ".config/opencode/agents/quire.md",
|
|
50307
|
+
piAgentPath: ".pi/agent/agents/quire.md",
|
|
50308
|
+
hint: "~/.agents/skills/{verify,debug}/SKILL.md · plus Codex/OpenCode/Pi Quire subagents",
|
|
50309
|
+
aliases: [
|
|
50310
|
+
"agents",
|
|
50311
|
+
"agent-skills",
|
|
50312
|
+
"pi",
|
|
50313
|
+
"opencode",
|
|
50314
|
+
"amp",
|
|
50315
|
+
"codex",
|
|
50316
|
+
"other"
|
|
50317
|
+
]
|
|
50318
|
+
},
|
|
50319
|
+
{
|
|
50320
|
+
id: "claude",
|
|
50321
|
+
label: "Claude Code",
|
|
50322
|
+
skillPaths: [{
|
|
50323
|
+
path: ".claude/skills/verify/SKILL.md",
|
|
50324
|
+
contents: QUIRE_VERIFY_SKILL_MARKDOWN
|
|
50325
|
+
}, {
|
|
50326
|
+
path: ".claude/skills/debug/SKILL.md",
|
|
50327
|
+
contents: QUIRE_DEBUG_SKILL_MARKDOWN
|
|
50328
|
+
}],
|
|
50329
|
+
agentPath: ".claude/agents/quire.md",
|
|
50330
|
+
hint: "~/.claude/skills/{verify,debug}/SKILL.md · plus native Quire subagent",
|
|
50331
|
+
aliases: ["claude", "claude-code"]
|
|
50332
|
+
},
|
|
50333
|
+
{
|
|
50334
|
+
id: "cursor",
|
|
50335
|
+
label: "Cursor",
|
|
50336
|
+
skillPaths: [{
|
|
50337
|
+
path: ".cursor/skills/verify/SKILL.md",
|
|
50338
|
+
contents: QUIRE_VERIFY_SKILL_MARKDOWN
|
|
50339
|
+
}, {
|
|
50340
|
+
path: ".cursor/skills/debug/SKILL.md",
|
|
50341
|
+
contents: QUIRE_DEBUG_SKILL_MARKDOWN
|
|
50342
|
+
}],
|
|
50343
|
+
agentPath: ".cursor/agents/quire.md",
|
|
50344
|
+
agentContents: QUIRE_CURSOR_MARKDOWN,
|
|
50345
|
+
hint: "~/.cursor/skills/{verify,debug}/SKILL.md · plus native Quire subagent",
|
|
50346
|
+
aliases: ["cursor", "cursor-cli"]
|
|
50347
|
+
}
|
|
50348
|
+
];
|
|
50349
|
+
const LEGACY_QUIRE_VERIFIER_PATHS = [
|
|
50350
|
+
".agents/agents/quire-verifier.md",
|
|
50351
|
+
".agents/skills/quire-verifier/SKILL.md",
|
|
50352
|
+
".claude/agents/quire-verifier.md",
|
|
50353
|
+
".codex/agents/quire-verifier.toml",
|
|
50354
|
+
".cursor/agents/quire-verifier.md",
|
|
50355
|
+
".config/opencode/agents/quire-verifier.md"
|
|
50356
|
+
];
|
|
50357
|
+
const LEGACY_QUIRE_SKILL_PATHS = [
|
|
50358
|
+
".agents/skills/quire/SKILL.md",
|
|
50359
|
+
".claude/skills/quire/SKILL.md",
|
|
50360
|
+
".cursor/skills/quire/SKILL.md"
|
|
50361
|
+
];
|
|
49719
50362
|
const setupCommand = defineCommand({
|
|
49720
50363
|
meta: {
|
|
49721
50364
|
name: "setup",
|
|
@@ -49724,7 +50367,7 @@ const setupCommand = defineCommand({
|
|
|
49724
50367
|
args: {
|
|
49725
50368
|
skill: {
|
|
49726
50369
|
type: "string",
|
|
49727
|
-
description: "Comma-separated local agent skill targets to install: agents, claude, all, none.",
|
|
50370
|
+
description: "Comma-separated local agent skill targets to install: agents, claude, cursor, all, none.",
|
|
49728
50371
|
valueHint: "targets"
|
|
49729
50372
|
},
|
|
49730
50373
|
"no-skill": {
|
|
@@ -49745,29 +50388,66 @@ const setupCommand = defineCommand({
|
|
|
49745
50388
|
});
|
|
49746
50389
|
async function runSetup(options = {}) {
|
|
49747
50390
|
const stdout = options.io?.stdout ?? process.stdout;
|
|
50391
|
+
const env = options.env ?? process.env;
|
|
50392
|
+
const interactive = options.interactive ?? (options.io?.stdout === void 0 && process.stdout.isTTY === true);
|
|
50393
|
+
const authStore$1 = options.authStore ?? authStore;
|
|
50394
|
+
const urls = setupUrls(env);
|
|
49748
50395
|
const selectedTargets = await selectSkillInstallTargets({
|
|
49749
50396
|
skill: options.skill,
|
|
49750
|
-
interactive
|
|
50397
|
+
interactive
|
|
49751
50398
|
});
|
|
49752
|
-
const
|
|
50399
|
+
const installedAgentFiles = await installQuireAgentFiles(options.homeDir ?? homedir(), selectedTargets, urls);
|
|
49753
50400
|
log.step("Set up Quire for this workspace", {
|
|
49754
50401
|
output: stdout,
|
|
49755
50402
|
spacing: 0
|
|
49756
50403
|
});
|
|
49757
|
-
log.message([`Open ${color.info(
|
|
50404
|
+
log.message([`Open ${color.info(urls.setupUrl)} to load the Quire setup skill, or copy this prompt into your coding agent like Claude Code, Cursor, Codex, Pi, or Amp:`], {
|
|
49758
50405
|
output: stdout,
|
|
49759
50406
|
spacing: 0,
|
|
49760
50407
|
withGuide: true
|
|
49761
50408
|
});
|
|
49762
50409
|
writeLine(stdout);
|
|
49763
50410
|
writeLine(stdout, color.label("--- copy prompt ---"));
|
|
49764
|
-
writeLine(stdout, AGENT_SETUP_PROMPT);
|
|
50411
|
+
writeLine(stdout, renderSetupTemplate(AGENT_SETUP_PROMPT, urls));
|
|
49765
50412
|
writeLine(stdout, color.label("--- end prompt ---"));
|
|
49766
|
-
if (
|
|
50413
|
+
if (installedAgentFiles.length > 0) {
|
|
49767
50414
|
writeLine(stdout);
|
|
49768
|
-
writeLine(stdout, color.label("Installed local Quire
|
|
49769
|
-
for (const path of
|
|
50415
|
+
writeLine(stdout, color.label("Installed local Quire agent files:"));
|
|
50416
|
+
for (const path of installedAgentFiles) writeLine(stdout, ` ${color.success("✓")} ${path}`);
|
|
50417
|
+
}
|
|
50418
|
+
const authConfigured = await completeInteractiveLogin({
|
|
50419
|
+
interactive,
|
|
50420
|
+
env,
|
|
50421
|
+
store: authStore$1,
|
|
50422
|
+
login: options.login ?? runLogin,
|
|
50423
|
+
io: options.io
|
|
50424
|
+
});
|
|
50425
|
+
writeLine(stdout);
|
|
50426
|
+
writeLine(stdout, color.label("Next steps:"));
|
|
50427
|
+
writeLine(stdout, ` 1. Give the copy prompt above to your coding agent so it creates ${color.path(".quire/runbook.md")}.`);
|
|
50428
|
+
if (authConfigured) writeLine(stdout, ` 2. Run ${color.command("quire doctor")} to confirm this repo is ready.`);
|
|
50429
|
+
else writeLine(stdout, ` 2. Authenticate Quire with ${color.command("quire login")} or ${color.command("QUIRE_API_TOKEN")} if this machine is not already signed in.`);
|
|
50430
|
+
writeLine(stdout, ` 3. Ask your coding agent to run a small Quire smoke verification once ${color.path(".quire/runbook.md")} and auth are ready.`);
|
|
50431
|
+
}
|
|
50432
|
+
async function completeInteractiveLogin(options) {
|
|
50433
|
+
if (!options.interactive) return false;
|
|
50434
|
+
const stdout = options.io?.stdout ?? process.stdout;
|
|
50435
|
+
if (await resolveAuthCredentials({
|
|
50436
|
+
store: options.store,
|
|
50437
|
+
env: options.env
|
|
50438
|
+
}) !== null) {
|
|
50439
|
+
log.success("Quire authentication is already configured.", {
|
|
50440
|
+
output: stdout,
|
|
50441
|
+
spacing: 0
|
|
50442
|
+
});
|
|
50443
|
+
return true;
|
|
49770
50444
|
}
|
|
50445
|
+
await options.login({
|
|
50446
|
+
apiBaseUrl: readApiBaseUrl(options.env.QUIRE_API_URL),
|
|
50447
|
+
store: options.store,
|
|
50448
|
+
io: options.io
|
|
50449
|
+
});
|
|
50450
|
+
return true;
|
|
49771
50451
|
}
|
|
49772
50452
|
async function selectSkillInstallTargets(options) {
|
|
49773
50453
|
if (options.skill === false || options.skill === "none") return [];
|
|
@@ -49780,7 +50460,11 @@ async function selectSkillInstallTargets(options) {
|
|
|
49780
50460
|
value: target.id,
|
|
49781
50461
|
hint: target.hint
|
|
49782
50462
|
})),
|
|
49783
|
-
initialValues: [
|
|
50463
|
+
initialValues: [
|
|
50464
|
+
"agents",
|
|
50465
|
+
"claude",
|
|
50466
|
+
"cursor"
|
|
50467
|
+
],
|
|
49784
50468
|
required: false
|
|
49785
50469
|
});
|
|
49786
50470
|
if (isCancel(selected) || selected.length === 0) return [];
|
|
@@ -49796,19 +50480,121 @@ function resolveSkillTargets(value) {
|
|
|
49796
50480
|
}
|
|
49797
50481
|
return [...selected.values()];
|
|
49798
50482
|
}
|
|
49799
|
-
|
|
50483
|
+
function setupUrls(env) {
|
|
50484
|
+
const baseUrl = publicBaseUrl(env);
|
|
50485
|
+
return {
|
|
50486
|
+
setupUrl: new URL("/agent-setup", baseUrl).toString(),
|
|
50487
|
+
verificationSkillUrl: new URL("/agent-verification", baseUrl).toString()
|
|
50488
|
+
};
|
|
50489
|
+
}
|
|
50490
|
+
function publicBaseUrl(env) {
|
|
50491
|
+
const configured = env.QUIRE_SKILL_BASE_URL ?? env.QUIRE_PUBLIC_URL ?? env.QUIRE_API_URL;
|
|
50492
|
+
if (configured === void 0 || configured.trim().length === 0) return DEFAULT_PUBLIC_BASE_URL;
|
|
50493
|
+
return configured;
|
|
50494
|
+
}
|
|
50495
|
+
function renderSetupTemplate(contents, urls) {
|
|
50496
|
+
return contents.replaceAll(SETUP_URL_PLACEHOLDER, urls.setupUrl).replaceAll(VERIFICATION_SKILL_URL_PLACEHOLDER, urls.verificationSkillUrl);
|
|
50497
|
+
}
|
|
50498
|
+
async function installQuireAgentFiles(homeDir, targets, urls) {
|
|
49800
50499
|
const installed = [];
|
|
50500
|
+
if (targets.length === 0) return installed;
|
|
50501
|
+
await removeLegacyQuireVerifierFiles(homeDir);
|
|
50502
|
+
await removeLegacyQuireSkillFiles(homeDir);
|
|
49801
50503
|
await Promise.all(targets.map(async (target) => {
|
|
49802
|
-
const
|
|
49803
|
-
|
|
50504
|
+
for (const skill of target.skillPaths) {
|
|
50505
|
+
const skillPath = join(homeDir, skill.path);
|
|
50506
|
+
await mkdir(dirname(skillPath), {
|
|
50507
|
+
recursive: true,
|
|
50508
|
+
mode: 448
|
|
50509
|
+
});
|
|
50510
|
+
await writeFile(skillPath, renderSetupTemplate(skill.contents, urls), { mode: 384 });
|
|
50511
|
+
installed.push(skillPath);
|
|
50512
|
+
}
|
|
50513
|
+
const agentPath = join(homeDir, target.agentPath);
|
|
50514
|
+
await mkdir(dirname(agentPath), {
|
|
49804
50515
|
recursive: true,
|
|
49805
50516
|
mode: 448
|
|
49806
50517
|
});
|
|
49807
|
-
await writeFile(
|
|
49808
|
-
installed.push(
|
|
50518
|
+
await writeFile(agentPath, renderSetupTemplate(target.agentContents ?? QUIRE_AGENT_MARKDOWN, urls), { mode: 384 });
|
|
50519
|
+
installed.push(agentPath);
|
|
50520
|
+
if (target.codexAgentPath !== void 0) {
|
|
50521
|
+
const codexAgentPath = join(homeDir, target.codexAgentPath);
|
|
50522
|
+
await mkdir(dirname(codexAgentPath), {
|
|
50523
|
+
recursive: true,
|
|
50524
|
+
mode: 448
|
|
50525
|
+
});
|
|
50526
|
+
await writeFile(codexAgentPath, renderSetupTemplate(QUIRE_CODEX_TOML, urls), { mode: 384 });
|
|
50527
|
+
installed.push(codexAgentPath);
|
|
50528
|
+
}
|
|
50529
|
+
if (target.opencodeAgentPath !== void 0) {
|
|
50530
|
+
const opencodeAgentPath = join(homeDir, target.opencodeAgentPath);
|
|
50531
|
+
await mkdir(dirname(opencodeAgentPath), {
|
|
50532
|
+
recursive: true,
|
|
50533
|
+
mode: 448
|
|
50534
|
+
});
|
|
50535
|
+
await writeFile(opencodeAgentPath, renderSetupTemplate(QUIRE_OPENCODE_MARKDOWN, urls), { mode: 384 });
|
|
50536
|
+
installed.push(opencodeAgentPath);
|
|
50537
|
+
}
|
|
50538
|
+
if (target.piAgentPath !== void 0) {
|
|
50539
|
+
const piAgentPath = join(homeDir, target.piAgentPath);
|
|
50540
|
+
await mkdir(dirname(piAgentPath), {
|
|
50541
|
+
recursive: true,
|
|
50542
|
+
mode: 448
|
|
50543
|
+
});
|
|
50544
|
+
await writeFile(piAgentPath, renderSetupTemplate(QUIRE_AGENT_MARKDOWN, urls), { mode: 384 });
|
|
50545
|
+
installed.push(piAgentPath);
|
|
50546
|
+
}
|
|
49809
50547
|
}));
|
|
49810
50548
|
return installed.sort();
|
|
49811
50549
|
}
|
|
50550
|
+
async function removeLegacyQuireVerifierFiles(homeDir) {
|
|
50551
|
+
await Promise.all(LEGACY_QUIRE_VERIFIER_PATHS.map(async (legacyPath) => {
|
|
50552
|
+
const path = join(homeDir, legacyPath);
|
|
50553
|
+
try {
|
|
50554
|
+
if (isLegacyQuireVerifierFile(await readFile(path, "utf8"))) {
|
|
50555
|
+
await rm(path, { force: true });
|
|
50556
|
+
await removeEmptyDirectory(dirname(path));
|
|
50557
|
+
}
|
|
50558
|
+
} catch (error) {
|
|
50559
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
50560
|
+
await removeEmptyDirectory(dirname(path));
|
|
50561
|
+
return;
|
|
50562
|
+
}
|
|
50563
|
+
throw error;
|
|
50564
|
+
}
|
|
50565
|
+
}));
|
|
50566
|
+
}
|
|
50567
|
+
async function removeLegacyQuireSkillFiles(homeDir) {
|
|
50568
|
+
await Promise.all(LEGACY_QUIRE_SKILL_PATHS.map(async (legacyPath) => {
|
|
50569
|
+
const path = join(homeDir, legacyPath);
|
|
50570
|
+
try {
|
|
50571
|
+
if (isLegacyQuireSkillFile(await readFile(path, "utf8"))) {
|
|
50572
|
+
await rm(path, { force: true });
|
|
50573
|
+
await removeEmptyDirectory(dirname(path));
|
|
50574
|
+
}
|
|
50575
|
+
} catch (error) {
|
|
50576
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
50577
|
+
await removeEmptyDirectory(dirname(path));
|
|
50578
|
+
return;
|
|
50579
|
+
}
|
|
50580
|
+
throw error;
|
|
50581
|
+
}
|
|
50582
|
+
}));
|
|
50583
|
+
}
|
|
50584
|
+
async function removeEmptyDirectory(path) {
|
|
50585
|
+
try {
|
|
50586
|
+
await rmdir(path);
|
|
50587
|
+
} catch (error) {
|
|
50588
|
+
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTEMPTY" || error.code === "EEXIST" || error.code === "EISDIR")) return;
|
|
50589
|
+
throw error;
|
|
50590
|
+
}
|
|
50591
|
+
}
|
|
50592
|
+
function isLegacyQuireVerifierFile(contents) {
|
|
50593
|
+
return contents.includes("Quire") && contents.includes("quire wait") && (contents.includes("quire-verifier") || contents.includes("Quire verifier"));
|
|
50594
|
+
}
|
|
50595
|
+
function isLegacyQuireSkillFile(contents) {
|
|
50596
|
+
return contents.includes("name: quire") && contents.includes("/agent-verification") && contents.includes("native subagent named `quire`");
|
|
50597
|
+
}
|
|
49812
50598
|
//#endregion
|
|
49813
50599
|
//#region src/commands/whoami.ts
|
|
49814
50600
|
const whoamiCommand = defineCommand({
|
|
@@ -49959,7 +50745,7 @@ function formatWalletBalance(value) {
|
|
|
49959
50745
|
}
|
|
49960
50746
|
//#endregion
|
|
49961
50747
|
//#region package.json
|
|
49962
|
-
var version$1 = "0.0.
|
|
50748
|
+
var version$1 = "0.0.12";
|
|
49963
50749
|
//#endregion
|
|
49964
50750
|
//#region src/cli.ts
|
|
49965
50751
|
const subCommands = {
|
|
@@ -49990,6 +50776,7 @@ const subCommands = {
|
|
|
49990
50776
|
setup: setupCommand,
|
|
49991
50777
|
status: statusCommand,
|
|
49992
50778
|
sync: syncCommand,
|
|
50779
|
+
wait: waitCommand,
|
|
49993
50780
|
watch: watchCommand
|
|
49994
50781
|
};
|
|
49995
50782
|
const subCommandAliases = new Set(["me", "resume"]);
|