@quireco/cli 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -13
- package/dist/{cli-C2HyQykL.mjs → cli-CqxFgCaa.mjs} +713 -114
- package/dist/index.mjs +1 -1
- package/dist/quire.mjs +1 -1
- package/package.json +2 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as __require, r as __toESM, t as __commonJSMin } from "./chunk-e9Ob2GDo.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { createMain, defineCommand, parseArgs, renderUsage, runCommand, showUsage } from "citty";
|
|
4
|
-
import { isCancel, log, note, spinner, text } from "@clack/prompts";
|
|
4
|
+
import { isCancel, log, multiselect, note, spinner, text } from "@clack/prompts";
|
|
5
5
|
import { loginGitHubCopilot, loginOpenAICodex } from "@earendil-works/pi-ai/oauth";
|
|
6
6
|
import { AuthStorage, DefaultResourceLoader, ModelRegistry, SessionManager, SettingsManager, createAgentSession, defineTool, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import { exec, execFile, execFileSync, spawn } from "node:child_process";
|
|
@@ -722,7 +722,7 @@ function createQuireCreditsStream(options) {
|
|
|
722
722
|
const output = createEmptyAssistantMessage(model);
|
|
723
723
|
try {
|
|
724
724
|
const accessToken = streamOptions?.apiKey;
|
|
725
|
-
if (!accessToken) throw new CliError$1("Not logged in. Run `quire login` before `quire
|
|
725
|
+
if (!accessToken) throw new CliError$1("Not logged in. Run `quire login` before `quire run` so Quire Credits can be checked.", ExitCode.AuthFailure);
|
|
726
726
|
stream.push({
|
|
727
727
|
type: "start",
|
|
728
728
|
partial: output
|
|
@@ -753,7 +753,7 @@ function createQuireCreditsStream(options) {
|
|
|
753
753
|
}
|
|
754
754
|
async function readValidatedQuireCredentials(options) {
|
|
755
755
|
const credentials = await resolveAuthCredentials({ store: options.store });
|
|
756
|
-
if (!credentials) throw new CliError$1("Not logged in. Run `quire login` or set QUIRE_API_TOKEN before `quire
|
|
756
|
+
if (!credentials) throw new CliError$1("Not logged in. Run `quire login` or set QUIRE_API_TOKEN before `quire run` so Quire Credits can be checked.", ExitCode.AuthFailure);
|
|
757
757
|
if (credentials.tokenType === "QuireApiKey") return credentials;
|
|
758
758
|
const sessionLookup = await fetchCurrentSession({
|
|
759
759
|
apiBaseUrl: credentials.apiBaseUrl,
|
|
@@ -1410,6 +1410,9 @@ function runStatusPath(runPath) {
|
|
|
1410
1410
|
function runProgressPath(runPath) {
|
|
1411
1411
|
return join(resolve(runPath), "progress.jsonl");
|
|
1412
1412
|
}
|
|
1413
|
+
function runPlanPath(runPath) {
|
|
1414
|
+
return join(resolve(runPath), "plan.md");
|
|
1415
|
+
}
|
|
1413
1416
|
function runHandoffPath(runPath) {
|
|
1414
1417
|
return join(resolve(runPath), "handoff.md");
|
|
1415
1418
|
}
|
|
@@ -1418,6 +1421,7 @@ function runDirPaths(root) {
|
|
|
1418
1421
|
return {
|
|
1419
1422
|
status: runStatusPath(root),
|
|
1420
1423
|
progress: runProgressPath(root),
|
|
1424
|
+
plan: runPlanPath(root),
|
|
1421
1425
|
handoff: runHandoffPath(root),
|
|
1422
1426
|
evidence,
|
|
1423
1427
|
screenshots: join(evidence, "screenshots"),
|
|
@@ -1466,6 +1470,7 @@ function appendRunProgressEvent(runDir, event) {
|
|
|
1466
1470
|
seq
|
|
1467
1471
|
};
|
|
1468
1472
|
appendLineAtomically(runDir.paths.progress, `${stableJson(progressEvent)}\n`);
|
|
1473
|
+
if (progressEvent.kind === "plan" && progressEvent.plan !== void 0) writeRunPlan(runDir, progressEvent);
|
|
1469
1474
|
return progressEvent;
|
|
1470
1475
|
});
|
|
1471
1476
|
}
|
|
@@ -1499,6 +1504,45 @@ function patchRunStatusJson(runDir, patch) {
|
|
|
1499
1504
|
function runProgressEventId(seq) {
|
|
1500
1505
|
return `progress-${seq.toString().padStart(6, "0")}`;
|
|
1501
1506
|
}
|
|
1507
|
+
function writeRunPlan(runDir, event) {
|
|
1508
|
+
const plan = event.plan;
|
|
1509
|
+
if (plan === void 0) return;
|
|
1510
|
+
const lines = [
|
|
1511
|
+
"# Evidence plan",
|
|
1512
|
+
"",
|
|
1513
|
+
`Run: ${runDir.runId}`,
|
|
1514
|
+
`Plan: ${plan.id}`,
|
|
1515
|
+
`Recorded: ${event.ts}`,
|
|
1516
|
+
...plan.supersedes === void 0 ? [] : [`Supersedes: ${plan.supersedes}`],
|
|
1517
|
+
...plan.reason === void 0 ? [] : [`Reason: ${plan.reason}`],
|
|
1518
|
+
"",
|
|
1519
|
+
event.summary === void 0 ? void 0 : `## Summary\n\n${event.summary}\n`,
|
|
1520
|
+
"## Steps",
|
|
1521
|
+
"",
|
|
1522
|
+
...plan.steps.flatMap((step, index) => [
|
|
1523
|
+
`${index + 1}. **${step.title}** (${step.purpose})`,
|
|
1524
|
+
...step.expectedOutcome === void 0 ? [] : [` - Expected: ${step.expectedOutcome}`],
|
|
1525
|
+
...step.assertionIds === void 0 || step.assertionIds.length === 0 ? [] : [` - Assertions: ${step.assertionIds.join(", ")}`]
|
|
1526
|
+
]),
|
|
1527
|
+
""
|
|
1528
|
+
].filter((line) => line !== void 0);
|
|
1529
|
+
writeFileAtomically(runDir.paths.plan, `${lines.join("\n").trimEnd()}\n`, 384);
|
|
1530
|
+
patchRunStatusJson(runDir, {
|
|
1531
|
+
files: {
|
|
1532
|
+
status: "status.json",
|
|
1533
|
+
progress: "progress.jsonl",
|
|
1534
|
+
plan: "plan.md",
|
|
1535
|
+
handoff: "handoff.md",
|
|
1536
|
+
evidence: "evidence"
|
|
1537
|
+
},
|
|
1538
|
+
plan: {
|
|
1539
|
+
path: "plan.md",
|
|
1540
|
+
progressEventId: event.id,
|
|
1541
|
+
planId: plan.id,
|
|
1542
|
+
updatedAt: event.ts
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1502
1546
|
function readRepoSha(repoPath) {
|
|
1503
1547
|
try {
|
|
1504
1548
|
return execFileSync("git", [
|
|
@@ -1555,6 +1599,21 @@ function appendLineAtomically(filePath, line) {
|
|
|
1555
1599
|
closeSync(file);
|
|
1556
1600
|
}
|
|
1557
1601
|
}
|
|
1602
|
+
function writeFileAtomically(filePath, contents, mode) {
|
|
1603
|
+
mkdirSync(dirname(filePath), {
|
|
1604
|
+
recursive: true,
|
|
1605
|
+
mode: 448
|
|
1606
|
+
});
|
|
1607
|
+
const tempPath = join(dirname(filePath), `.${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.${basename(filePath)}.tmp`);
|
|
1608
|
+
const file = openSync(tempPath, constants$1.O_CREAT | constants$1.O_EXCL | constants$1.O_WRONLY, mode);
|
|
1609
|
+
try {
|
|
1610
|
+
writeFileSync(file, contents, "utf8");
|
|
1611
|
+
fsyncSync(file);
|
|
1612
|
+
} finally {
|
|
1613
|
+
closeSync(file);
|
|
1614
|
+
}
|
|
1615
|
+
renameSync(tempPath, filePath);
|
|
1616
|
+
}
|
|
1558
1617
|
function nextJsonlSequence(filePath) {
|
|
1559
1618
|
try {
|
|
1560
1619
|
const file = openSync(filePath, constants$1.O_RDONLY);
|
|
@@ -1618,6 +1677,7 @@ function createRunStatus(runDir, input) {
|
|
|
1618
1677
|
files: {
|
|
1619
1678
|
status: "status.json",
|
|
1620
1679
|
progress: "progress.jsonl",
|
|
1680
|
+
plan: "plan.md",
|
|
1621
1681
|
handoff: "handoff.md",
|
|
1622
1682
|
evidence: "evidence"
|
|
1623
1683
|
},
|
|
@@ -1625,6 +1685,7 @@ function createRunStatus(runDir, input) {
|
|
|
1625
1685
|
status: input.status,
|
|
1626
1686
|
createdAt: timestamp,
|
|
1627
1687
|
updatedAt: timestamp,
|
|
1688
|
+
lastActivityAt: timestamp,
|
|
1628
1689
|
...input.status === "running" ? { startedAt: timestamp } : {},
|
|
1629
1690
|
...input.pid === void 0 ? {} : { pid: input.pid },
|
|
1630
1691
|
...input.error === void 0 ? {} : { error: input.error }
|
|
@@ -1648,6 +1709,22 @@ function updateRunStatus(runPath, patch) {
|
|
|
1648
1709
|
writeStatus(runStatusPath(runPath), status);
|
|
1649
1710
|
return status;
|
|
1650
1711
|
}
|
|
1712
|
+
function touchRunActivity(runPath, options = {}) {
|
|
1713
|
+
const existing = readRunStatus(runPath);
|
|
1714
|
+
if (existing === void 0) throw new CliError$1(`Run status not found: ${runPath}`, ExitCode.InvalidInput);
|
|
1715
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
1716
|
+
const minIntervalMs = options.minIntervalMs ?? 5e3;
|
|
1717
|
+
const previous = existing.lastActivityAt ?? existing.updatedAt;
|
|
1718
|
+
const previousTime = Date.parse(previous);
|
|
1719
|
+
if (Number.isFinite(previousTime) && now.getTime() - previousTime < minIntervalMs) return existing;
|
|
1720
|
+
const timestamp = now.toISOString();
|
|
1721
|
+
const status = {
|
|
1722
|
+
...existing,
|
|
1723
|
+
lastActivityAt: timestamp
|
|
1724
|
+
};
|
|
1725
|
+
writeStatus(runStatusPath(runPath), status);
|
|
1726
|
+
return status;
|
|
1727
|
+
}
|
|
1651
1728
|
function readRunStatus(runPath) {
|
|
1652
1729
|
const filePath = runStatusPath(runPath);
|
|
1653
1730
|
if (!existsSync(filePath)) return;
|
|
@@ -1765,6 +1842,7 @@ function parseRunStatus(value) {
|
|
|
1765
1842
|
status: value.status,
|
|
1766
1843
|
createdAt: value.createdAt,
|
|
1767
1844
|
updatedAt: value.updatedAt,
|
|
1845
|
+
...typeof value.lastActivityAt === "string" ? { lastActivityAt: value.lastActivityAt } : {},
|
|
1768
1846
|
...typeof value.pid === "number" ? { pid: value.pid } : {},
|
|
1769
1847
|
...typeof value.startedAt === "string" ? { startedAt: value.startedAt } : {},
|
|
1770
1848
|
...typeof value.completedAt === "string" ? { completedAt: value.completedAt } : {},
|
|
@@ -1772,6 +1850,7 @@ function parseRunStatus(value) {
|
|
|
1772
1850
|
...value.agent === void 0 ? {} : { agent: value.agent },
|
|
1773
1851
|
...value.request === void 0 ? {} : { request: value.request },
|
|
1774
1852
|
...value.metadata === void 0 ? {} : { metadata: value.metadata },
|
|
1853
|
+
...value.plan === void 0 ? {} : { plan: value.plan },
|
|
1775
1854
|
...value.harnessSession === void 0 ? {} : { harnessSession: value.harnessSession },
|
|
1776
1855
|
...value.finalHandoff === void 0 ? {} : { finalHandoff: value.finalHandoff },
|
|
1777
1856
|
...value.finalResult === void 0 ? {} : { finalResult: value.finalResult },
|
|
@@ -1783,12 +1862,14 @@ function parseRunStatusFiles(value) {
|
|
|
1783
1862
|
if (isRecord$14(value) && typeof value.status === "string" && typeof value.progress === "string" && typeof value.handoff === "string" && typeof value.evidence === "string") return {
|
|
1784
1863
|
status: value.status,
|
|
1785
1864
|
progress: value.progress,
|
|
1865
|
+
...typeof value.plan === "string" ? { plan: value.plan } : {},
|
|
1786
1866
|
handoff: value.handoff,
|
|
1787
1867
|
evidence: value.evidence
|
|
1788
1868
|
};
|
|
1789
1869
|
return {
|
|
1790
1870
|
status: "status.json",
|
|
1791
1871
|
progress: "progress.jsonl",
|
|
1872
|
+
plan: "plan.md",
|
|
1792
1873
|
handoff: "handoff.md",
|
|
1793
1874
|
evidence: "evidence"
|
|
1794
1875
|
};
|
|
@@ -2288,7 +2369,7 @@ const BrowserToolInputSchema = Type.Object({
|
|
|
2288
2369
|
argv: Type.Array(Type.String(), { minItems: 1 }),
|
|
2289
2370
|
cwd: Type.Optional(Type.String({ minLength: 1 }))
|
|
2290
2371
|
}, { additionalProperties: false });
|
|
2291
|
-
function makeBrowserTool({ runDir, sessionId, cwd: defaultCwd, headed = false, agentBrowserExecutableResolver = resolveAgentBrowserExecutable }) {
|
|
2372
|
+
function makeBrowserTool({ runDir, sessionId, cwd: defaultCwd, headed = false, onProgressEvent, agentBrowserExecutableResolver = resolveAgentBrowserExecutable }) {
|
|
2292
2373
|
const captureState = { videoStarted: false };
|
|
2293
2374
|
return createAgentCliTool({
|
|
2294
2375
|
name: "agent-browser",
|
|
@@ -2302,6 +2383,7 @@ function makeBrowserTool({ runDir, sessionId, cwd: defaultCwd, headed = false, a
|
|
|
2302
2383
|
defaultCwd,
|
|
2303
2384
|
headed,
|
|
2304
2385
|
captureState,
|
|
2386
|
+
onProgressEvent,
|
|
2305
2387
|
agentBrowserExecutableResolver,
|
|
2306
2388
|
input,
|
|
2307
2389
|
signal
|
|
@@ -2376,7 +2458,7 @@ async function runBestEffortBrowserCommand({ executable, args, cwd, timeoutMs })
|
|
|
2376
2458
|
function parseBrowserToolInput(input) {
|
|
2377
2459
|
return parseAgentCliToolInput(BrowserToolInputSchema, input, "agent-browser");
|
|
2378
2460
|
}
|
|
2379
|
-
async function runBrowserCommand({ runDir, sessionId, defaultCwd, headed, captureState, agentBrowserExecutableResolver, input, signal }) {
|
|
2461
|
+
async function runBrowserCommand({ runDir, sessionId, defaultCwd, headed, captureState, onProgressEvent, agentBrowserExecutableResolver, input, signal }) {
|
|
2380
2462
|
const [executable, ...args] = input.argv;
|
|
2381
2463
|
if (executable !== "agent-browser") throw new Error(`Refusing to spawn non-agent-browser executable: ${executable}`);
|
|
2382
2464
|
const cwd = input.cwd ?? defaultCwd ?? process.cwd();
|
|
@@ -2394,6 +2476,7 @@ async function runBrowserCommand({ runDir, sessionId, defaultCwd, headed, captur
|
|
|
2394
2476
|
cwd,
|
|
2395
2477
|
headed,
|
|
2396
2478
|
captureState,
|
|
2479
|
+
onProgressEvent,
|
|
2397
2480
|
agentBrowserExecutableResolver,
|
|
2398
2481
|
signal
|
|
2399
2482
|
});
|
|
@@ -2463,11 +2546,20 @@ function spawnAgentBrowser({ args, sessionId, cwd, agentBrowserExecutableResolve
|
|
|
2463
2546
|
});
|
|
2464
2547
|
});
|
|
2465
2548
|
}
|
|
2466
|
-
async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, headed, captureState, agentBrowserExecutableResolver, signal }) {
|
|
2549
|
+
async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, headed, captureState, onProgressEvent, agentBrowserExecutableResolver, signal }) {
|
|
2467
2550
|
const stdout = [];
|
|
2468
2551
|
const stderr = [];
|
|
2469
2552
|
for (const command of commands) {
|
|
2470
2553
|
const args = applyBrowserLaunchArgs(applyRunDefaults(applyHeadedMode(command.args, headed), runDir));
|
|
2554
|
+
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
|
+
});
|
|
2471
2563
|
const result = await spawnAgentBrowser({
|
|
2472
2564
|
args,
|
|
2473
2565
|
sessionId,
|
|
@@ -2496,12 +2588,30 @@ async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, hea
|
|
|
2496
2588
|
signal
|
|
2497
2589
|
}));
|
|
2498
2590
|
stderr.push(result.stderr);
|
|
2499
|
-
if (!result.ok)
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2591
|
+
if (!result.ok) {
|
|
2592
|
+
emitBrowserProgress(runDir, onProgressEvent, {
|
|
2593
|
+
kind: "action",
|
|
2594
|
+
status: "failed",
|
|
2595
|
+
title: `Browser: ${commandLabel}`,
|
|
2596
|
+
summary: oneLine(result.stderr || result.stdout || "agent-browser command failed"),
|
|
2597
|
+
importance: "normal",
|
|
2598
|
+
source: "runtime"
|
|
2599
|
+
});
|
|
2600
|
+
return {
|
|
2601
|
+
ok: false,
|
|
2602
|
+
exitCode: result.exitCode,
|
|
2603
|
+
stdout: stdout.join(""),
|
|
2604
|
+
stderr: stderr.join("")
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
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
|
+
});
|
|
2505
2615
|
}
|
|
2506
2616
|
return {
|
|
2507
2617
|
ok: true,
|
|
@@ -2510,6 +2620,24 @@ async function runPlannedBrowserCommands({ commands, runDir, sessionId, cwd, hea
|
|
|
2510
2620
|
stderr: stderr.join("")
|
|
2511
2621
|
};
|
|
2512
2622
|
}
|
|
2623
|
+
function emitBrowserProgress(runDir, onProgressEvent, event) {
|
|
2624
|
+
const logged = appendRunProgressEvent(runDir, event);
|
|
2625
|
+
onProgressEvent?.(logged);
|
|
2626
|
+
}
|
|
2627
|
+
function browserCommandLabel(args) {
|
|
2628
|
+
const command = args[0] ?? "command";
|
|
2629
|
+
if (command === "open") return `open ${redactBrowserArg(args[1] ?? "URL")}`;
|
|
2630
|
+
if (command === "batch") return "batch";
|
|
2631
|
+
return command;
|
|
2632
|
+
}
|
|
2633
|
+
function redactBrowserArg(value) {
|
|
2634
|
+
try {
|
|
2635
|
+
const url = new URL(value);
|
|
2636
|
+
return `${url.origin}${url.pathname}`;
|
|
2637
|
+
} catch {
|
|
2638
|
+
return value.length > 80 ? `${value.slice(0, 77)}…` : value;
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2513
2641
|
function augmentBrowserArtifactOutput({ runDir, sessionId, cwd, args, result, agentBrowserExecutableResolver, signal }) {
|
|
2514
2642
|
if (!result.ok) return Promise.resolve(result.stdout);
|
|
2515
2643
|
const output = [result.stdout];
|
|
@@ -2605,27 +2733,35 @@ function hasOption$1(args, option) {
|
|
|
2605
2733
|
return args.some((arg) => arg === option || arg.startsWith(`${option}=`));
|
|
2606
2734
|
}
|
|
2607
2735
|
function shouldStartRecordingForCommand(args) {
|
|
2608
|
-
|
|
2736
|
+
const command = args[0];
|
|
2737
|
+
return command !== void 0 && RECORDING_START_COMMANDS.has(command);
|
|
2609
2738
|
}
|
|
2610
2739
|
function shouldCaptureScreenshotAfterCommand$1(args) {
|
|
2611
2740
|
const command = args[0];
|
|
2612
|
-
return command !== void 0 &&
|
|
2613
|
-
"close",
|
|
2614
|
-
"console",
|
|
2615
|
-
"cookies",
|
|
2616
|
-
"diff",
|
|
2617
|
-
"errors",
|
|
2618
|
-
"network",
|
|
2619
|
-
"pdf",
|
|
2620
|
-
"profiler",
|
|
2621
|
-
"record",
|
|
2622
|
-
"screenshot",
|
|
2623
|
-
"set",
|
|
2624
|
-
"skills",
|
|
2625
|
-
"storage",
|
|
2626
|
-
"trace"
|
|
2627
|
-
].includes(command);
|
|
2741
|
+
return command !== void 0 && AUTO_SCREENSHOT_COMMANDS.has(command);
|
|
2628
2742
|
}
|
|
2743
|
+
const RECORDING_START_COMMANDS = new Set([
|
|
2744
|
+
"back",
|
|
2745
|
+
"check",
|
|
2746
|
+
"click",
|
|
2747
|
+
"dblclick",
|
|
2748
|
+
"drag",
|
|
2749
|
+
"fill",
|
|
2750
|
+
"forward",
|
|
2751
|
+
"open",
|
|
2752
|
+
"press",
|
|
2753
|
+
"reload",
|
|
2754
|
+
"select",
|
|
2755
|
+
"type",
|
|
2756
|
+
"uncheck",
|
|
2757
|
+
"upload"
|
|
2758
|
+
]);
|
|
2759
|
+
const AUTO_SCREENSHOT_COMMANDS = new Set([
|
|
2760
|
+
"back",
|
|
2761
|
+
"forward",
|
|
2762
|
+
"open",
|
|
2763
|
+
"reload"
|
|
2764
|
+
]);
|
|
2629
2765
|
//#endregion
|
|
2630
2766
|
//#region src/run-artifacts.ts
|
|
2631
2767
|
function relativeRunPath(runDir, path) {
|
|
@@ -2670,14 +2806,19 @@ function publicRunProgressEvent(runDir, event) {
|
|
|
2670
2806
|
//#region src/pipeline/handoff-materializer.ts
|
|
2671
2807
|
function materializeHandoff({ handoff, runDir, continuation, modelUsage, metadata, harness }) {
|
|
2672
2808
|
const result = toInvestigationResult(handoff, runDir, continuation, modelUsage);
|
|
2673
|
-
const
|
|
2809
|
+
const materializedHandoff = materializeHandoffMarkdown(handoff, result.evidence);
|
|
2810
|
+
const materializedResult = {
|
|
2811
|
+
...result,
|
|
2812
|
+
handoffMarkdown: materializedHandoff.handoffMarkdown
|
|
2813
|
+
};
|
|
2814
|
+
const nextMetadata = updateMetadataWithHandoff(runDir, metadata, materializedHandoff, materializedResult, harness);
|
|
2674
2815
|
writeRunFinalHandoff(runDir, {
|
|
2675
|
-
handoff,
|
|
2676
|
-
result
|
|
2816
|
+
handoff: materializedHandoff,
|
|
2817
|
+
result: materializedResult
|
|
2677
2818
|
});
|
|
2678
|
-
writeHandoffArtifact(runDir,
|
|
2819
|
+
writeHandoffArtifact(runDir, materializedHandoff);
|
|
2679
2820
|
return {
|
|
2680
|
-
result,
|
|
2821
|
+
result: materializedResult,
|
|
2681
2822
|
metadata: nextMetadata
|
|
2682
2823
|
};
|
|
2683
2824
|
}
|
|
@@ -2714,13 +2855,20 @@ function toInvestigationResult(handoff, runDir, continuation, modelUsage) {
|
|
|
2714
2855
|
}
|
|
2715
2856
|
function appendAutoCapturedEvidence(evidence, runDir) {
|
|
2716
2857
|
const seenPaths = new Set(evidence.flatMap((item) => item.path === void 0 ? [] : [item.path]));
|
|
2717
|
-
const autoEvidence = [...readAutoCapturedFiles(runDir.paths.screenshots, runDir, "screenshot"), ...readAutoCapturedFiles(runDir.paths.video, runDir, "recording")].filter((item) => {
|
|
2858
|
+
const autoEvidence = [...selectAutoScreenshotEvidence(evidence, readAutoCapturedFiles(runDir.paths.screenshots, runDir, "screenshot")), ...readAutoCapturedFiles(runDir.paths.video, runDir, "recording")].filter((item) => {
|
|
2718
2859
|
if (item.path === void 0 || seenPaths.has(item.path)) return false;
|
|
2719
2860
|
seenPaths.add(item.path);
|
|
2720
2861
|
return true;
|
|
2721
2862
|
});
|
|
2722
2863
|
return [...evidence, ...autoEvidence];
|
|
2723
2864
|
}
|
|
2865
|
+
function selectAutoScreenshotEvidence(handoffEvidence, autoScreenshots) {
|
|
2866
|
+
if (handoffEvidence.some((item) => item.kind === "screenshot")) return [];
|
|
2867
|
+
if (autoScreenshots.length <= 2) return autoScreenshots;
|
|
2868
|
+
const first = autoScreenshots[0];
|
|
2869
|
+
const last = autoScreenshots[autoScreenshots.length - 1];
|
|
2870
|
+
return first === void 0 || last === void 0 || first.path === last.path ? autoScreenshots.slice(0, 1) : [first, last];
|
|
2871
|
+
}
|
|
2724
2872
|
function readAutoCapturedFiles(directory, runDir, kind) {
|
|
2725
2873
|
if (!existsSync(directory)) return [];
|
|
2726
2874
|
try {
|
|
@@ -2806,6 +2954,86 @@ function confidenceFor(handoff, evidence) {
|
|
|
2806
2954
|
if (handoff.status !== "reproduced" || evidence.length === 0) return "low";
|
|
2807
2955
|
return evidence.length >= 2 ? "high" : "medium";
|
|
2808
2956
|
}
|
|
2957
|
+
const EVIDENCE_SECTION_START = "<!-- quire:evidence-start -->";
|
|
2958
|
+
const EVIDENCE_SECTION_END = "<!-- quire:evidence-end -->";
|
|
2959
|
+
function materializeHandoffMarkdown(handoff, evidence) {
|
|
2960
|
+
const evidenceSection = renderEvidenceSection(evidence);
|
|
2961
|
+
const handoffMarkdown = withGeneratedEvidenceSection(handoff.handoffMarkdown, evidenceSection);
|
|
2962
|
+
return {
|
|
2963
|
+
...handoff,
|
|
2964
|
+
handoffMarkdown,
|
|
2965
|
+
evidenceRefs: evidence.map((item) => ({
|
|
2966
|
+
kind: item.kind,
|
|
2967
|
+
note: item.note,
|
|
2968
|
+
...item.path === void 0 ? {} : { path: item.path },
|
|
2969
|
+
...item.url === void 0 ? {} : { url: item.url }
|
|
2970
|
+
}))
|
|
2971
|
+
};
|
|
2972
|
+
}
|
|
2973
|
+
function withGeneratedEvidenceSection(markdown, evidenceSection) {
|
|
2974
|
+
const withoutExisting = markdown.trimEnd().replace(new RegExp(`\\n*${escapeRegExp$1(EVIDENCE_SECTION_START)}[\\s\\S]*?${escapeRegExp$1(EVIDENCE_SECTION_END)}\\s*$`), "");
|
|
2975
|
+
return evidenceSection.length === 0 ? withoutExisting : `${withoutExisting}\n\n${evidenceSection}`;
|
|
2976
|
+
}
|
|
2977
|
+
function renderEvidenceSection(evidence) {
|
|
2978
|
+
const publicEvidence = evidence.filter((item) => item.path !== void 0 || item.url !== void 0);
|
|
2979
|
+
if (publicEvidence.length === 0) return "";
|
|
2980
|
+
return [
|
|
2981
|
+
EVIDENCE_SECTION_START,
|
|
2982
|
+
"## Evidence artifacts",
|
|
2983
|
+
"",
|
|
2984
|
+
...publicEvidence.flatMap(renderEvidenceItem),
|
|
2985
|
+
EVIDENCE_SECTION_END
|
|
2986
|
+
].join("\n");
|
|
2987
|
+
}
|
|
2988
|
+
function renderEvidenceItem(item) {
|
|
2989
|
+
const label = `${evidenceDisplayKind(item)} — ${item.note}`;
|
|
2990
|
+
const target = evidenceTarget(item);
|
|
2991
|
+
if (target === void 0) return [];
|
|
2992
|
+
if (isImageEvidence(item)) return [
|
|
2993
|
+
`- ${label}`,
|
|
2994
|
+
` `,
|
|
2995
|
+
""
|
|
2996
|
+
];
|
|
2997
|
+
if (isVideoEvidence(item)) return [
|
|
2998
|
+
`- ${label}`,
|
|
2999
|
+
` <video controls src="${escapeHtmlAttribute(target)}"></video>`,
|
|
3000
|
+
` [Open recording](${target})`,
|
|
3001
|
+
""
|
|
3002
|
+
];
|
|
3003
|
+
return [
|
|
3004
|
+
`- ${label}`,
|
|
3005
|
+
` [Open artifact](${target})`,
|
|
3006
|
+
""
|
|
3007
|
+
];
|
|
3008
|
+
}
|
|
3009
|
+
function evidenceDisplayKind(item) {
|
|
3010
|
+
const kind = item.kind.replaceAll("_", " ").trim();
|
|
3011
|
+
return kind.length === 0 ? "Evidence" : `${kind[0]?.toUpperCase() ?? "E"}${kind.slice(1)}`;
|
|
3012
|
+
}
|
|
3013
|
+
function evidenceTarget(item) {
|
|
3014
|
+
if (item.url !== void 0) return item.url;
|
|
3015
|
+
if (item.path === void 0) return;
|
|
3016
|
+
return `./${item.path.replace(/^\.\//, "")}`;
|
|
3017
|
+
}
|
|
3018
|
+
function isImageEvidence(item) {
|
|
3019
|
+
const path = item.path?.toLowerCase() ?? "";
|
|
3020
|
+
const kind = item.kind.toLowerCase();
|
|
3021
|
+
return kind.includes("screenshot") || kind.includes("image") || path.endsWith(".png") || path.endsWith(".jpg") || path.endsWith(".jpeg") || path.endsWith(".webp") || path.endsWith(".gif");
|
|
3022
|
+
}
|
|
3023
|
+
function isVideoEvidence(item) {
|
|
3024
|
+
const path = item.path?.toLowerCase() ?? "";
|
|
3025
|
+
const kind = item.kind.toLowerCase();
|
|
3026
|
+
return kind.includes("recording") || kind.includes("video") || path.endsWith(".webm") || path.endsWith(".mp4");
|
|
3027
|
+
}
|
|
3028
|
+
function escapeMarkdownAlt(value) {
|
|
3029
|
+
return value.replace(/[[\]\\]/g, " ").replace(/\s+/g, " ").trim();
|
|
3030
|
+
}
|
|
3031
|
+
function escapeHtmlAttribute(value) {
|
|
3032
|
+
return value.replaceAll("&", "&").replaceAll("\"", """).replaceAll("<", "<").replaceAll(">", ">");
|
|
3033
|
+
}
|
|
3034
|
+
function escapeRegExp$1(value) {
|
|
3035
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3036
|
+
}
|
|
2809
3037
|
function updateMetadataWithHandoff(runDir, metadata, handoff, result, harness) {
|
|
2810
3038
|
const sessionState = readRunSessionState(runDir);
|
|
2811
3039
|
return writeRunMetadata(runDir, {
|
|
@@ -41110,11 +41338,11 @@ function errorMessage$2(error) {
|
|
|
41110
41338
|
}
|
|
41111
41339
|
//#endregion
|
|
41112
41340
|
//#region ../../packages/agent-mobile/src/wait/android.ts
|
|
41113
|
-
const DEFAULT_TIMEOUT_MS$
|
|
41341
|
+
const DEFAULT_TIMEOUT_MS$2 = 1e4;
|
|
41114
41342
|
const POLL_INTERVAL_MS$1 = 400;
|
|
41115
41343
|
async function waitAndroid(request) {
|
|
41116
41344
|
const waitFor = request.waitFor ?? { kind: "settle" };
|
|
41117
|
-
const timeoutMs = waitFor.timeoutMs ?? DEFAULT_TIMEOUT_MS$
|
|
41345
|
+
const timeoutMs = waitFor.timeoutMs ?? DEFAULT_TIMEOUT_MS$2;
|
|
41118
41346
|
if (waitFor.kind === "settle") {
|
|
41119
41347
|
await waitForSettle$1(request, waitFor, timeoutMs);
|
|
41120
41348
|
return;
|
|
@@ -41192,11 +41420,11 @@ function sleep$1(ms) {
|
|
|
41192
41420
|
}
|
|
41193
41421
|
//#endregion
|
|
41194
41422
|
//#region ../../packages/agent-mobile/src/wait/ios.ts
|
|
41195
|
-
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
41423
|
+
const DEFAULT_TIMEOUT_MS$1 = 1e4;
|
|
41196
41424
|
const POLL_INTERVAL_MS = 400;
|
|
41197
41425
|
async function waitIos(request) {
|
|
41198
41426
|
const waitFor = request.waitFor ?? { kind: "settle" };
|
|
41199
|
-
const timeoutMs = waitFor.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
41427
|
+
const timeoutMs = waitFor.timeoutMs ?? DEFAULT_TIMEOUT_MS$1;
|
|
41200
41428
|
if (waitFor.kind === "settle") {
|
|
41201
41429
|
await waitForSettle(request, waitFor, timeoutMs);
|
|
41202
41430
|
return;
|
|
@@ -44646,48 +44874,293 @@ function shouldCaptureScreenshotAfterCommand(command) {
|
|
|
44646
44874
|
return command !== void 0 && !["install", "screenshot"].includes(command);
|
|
44647
44875
|
}
|
|
44648
44876
|
//#endregion
|
|
44877
|
+
//#region src/tools/shell-check.ts
|
|
44878
|
+
const DEFAULT_TIMEOUT_MS = 12e4;
|
|
44879
|
+
const MAX_OUTPUT_CHARS = 6e4;
|
|
44880
|
+
const ShellCheckToolInputSchema = Type.Object({
|
|
44881
|
+
command: Type.String({ minLength: 1 }),
|
|
44882
|
+
cwd: Type.Optional(Type.String({ minLength: 1 })),
|
|
44883
|
+
timeoutMs: Type.Optional(Type.Number({
|
|
44884
|
+
minimum: 1e3,
|
|
44885
|
+
maximum: 6e5
|
|
44886
|
+
})),
|
|
44887
|
+
reason: Type.String({ minLength: 1 })
|
|
44888
|
+
}, { additionalProperties: false });
|
|
44889
|
+
function makeShellCheckTool({ runDir, cwd: defaultCwd, env = process.env }) {
|
|
44890
|
+
return createAgentCliTool({
|
|
44891
|
+
name: "run_shell_check",
|
|
44892
|
+
description: "Run a project shell command for evidence collection, local app startup checks, tests, typechecks, builds, lint/check commands, or source inspection. Clearly dangerous, destructive, deploy, install, database mutation, and external write commands are blocked.",
|
|
44893
|
+
parameters: ShellCheckToolInputSchema,
|
|
44894
|
+
parseInput: parseShellCheckToolInput,
|
|
44895
|
+
runCommand(input, signal) {
|
|
44896
|
+
return runShellCheckCommand({
|
|
44897
|
+
runDir,
|
|
44898
|
+
defaultCwd,
|
|
44899
|
+
env,
|
|
44900
|
+
input,
|
|
44901
|
+
signal
|
|
44902
|
+
});
|
|
44903
|
+
}
|
|
44904
|
+
});
|
|
44905
|
+
}
|
|
44906
|
+
function parseShellCheckToolInput(input) {
|
|
44907
|
+
return parseAgentCliToolInput(ShellCheckToolInputSchema, input, "run_shell_check");
|
|
44908
|
+
}
|
|
44909
|
+
async function runShellCheckCommand({ runDir, defaultCwd, env, input, signal }) {
|
|
44910
|
+
const command = input.command.trim();
|
|
44911
|
+
const policy = classifyShellCheckCommand(command);
|
|
44912
|
+
if (!policy.allowed) return {
|
|
44913
|
+
ok: false,
|
|
44914
|
+
exitCode: null,
|
|
44915
|
+
stdout: "",
|
|
44916
|
+
stderr: `Blocked by Quire shell safety policy: ${policy.reason}`,
|
|
44917
|
+
blocked: true,
|
|
44918
|
+
reason: policy.reason
|
|
44919
|
+
};
|
|
44920
|
+
const cwd = input.cwd ?? defaultCwd ?? process.cwd();
|
|
44921
|
+
const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
44922
|
+
const result = await spawnShell(command, {
|
|
44923
|
+
cwd,
|
|
44924
|
+
env,
|
|
44925
|
+
timeoutMs,
|
|
44926
|
+
signal
|
|
44927
|
+
});
|
|
44928
|
+
const artifactPath = writeShellArtifact(runDir, {
|
|
44929
|
+
command,
|
|
44930
|
+
cwd,
|
|
44931
|
+
reason: input.reason,
|
|
44932
|
+
timeoutMs,
|
|
44933
|
+
...result
|
|
44934
|
+
});
|
|
44935
|
+
const output = {
|
|
44936
|
+
exitCode: result.exitCode,
|
|
44937
|
+
stdout: truncateOutput(redactSecrets(result.stdout)),
|
|
44938
|
+
stderr: `${truncateOutput(redactSecrets(result.stderr))}\n[quire:artifact] shell_check saved to ${artifactPath}\n`.trimStart(),
|
|
44939
|
+
artifactPath
|
|
44940
|
+
};
|
|
44941
|
+
return result.exitCode === 0 ? {
|
|
44942
|
+
ok: true,
|
|
44943
|
+
...output,
|
|
44944
|
+
exitCode: 0
|
|
44945
|
+
} : {
|
|
44946
|
+
ok: false,
|
|
44947
|
+
...output
|
|
44948
|
+
};
|
|
44949
|
+
}
|
|
44950
|
+
function classifyShellCheckCommand(command) {
|
|
44951
|
+
const trimmed = command.trim();
|
|
44952
|
+
if (trimmed.length === 0) return {
|
|
44953
|
+
allowed: false,
|
|
44954
|
+
reason: "command is empty"
|
|
44955
|
+
};
|
|
44956
|
+
if (/[;&|`$<>]/.test(trimmed)) return {
|
|
44957
|
+
allowed: false,
|
|
44958
|
+
reason: "shell operators, pipes, redirects, and command substitution are not allowed"
|
|
44959
|
+
};
|
|
44960
|
+
const words = splitSimpleCommand(trimmed);
|
|
44961
|
+
if (words === void 0 || words.length === 0) return {
|
|
44962
|
+
allowed: false,
|
|
44963
|
+
reason: "command could not be parsed as a simple command"
|
|
44964
|
+
};
|
|
44965
|
+
const dangerous = firstDangerousToken(words.map((word) => word.toLowerCase()));
|
|
44966
|
+
if (dangerous !== void 0) return {
|
|
44967
|
+
allowed: false,
|
|
44968
|
+
reason: `contains unsafe token ${JSON.stringify(dangerous)}`
|
|
44969
|
+
};
|
|
44970
|
+
return { allowed: true };
|
|
44971
|
+
}
|
|
44972
|
+
function splitSimpleCommand(command) {
|
|
44973
|
+
const words = [];
|
|
44974
|
+
const pattern = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^']*)'|(\S+)/g;
|
|
44975
|
+
let match;
|
|
44976
|
+
let consumed = "";
|
|
44977
|
+
while ((match = pattern.exec(command)) !== null) {
|
|
44978
|
+
consumed += match[0];
|
|
44979
|
+
words.push(match[1] ?? match[2] ?? match[3] ?? "");
|
|
44980
|
+
}
|
|
44981
|
+
if (words.length === 0 || consumed.replace(/\s+/g, "") !== command.replace(/\s+/g, "")) return;
|
|
44982
|
+
return words;
|
|
44983
|
+
}
|
|
44984
|
+
const DANGEROUS_TOKENS = new Set([
|
|
44985
|
+
"rm",
|
|
44986
|
+
"rmdir",
|
|
44987
|
+
"mv",
|
|
44988
|
+
"cp",
|
|
44989
|
+
"sudo",
|
|
44990
|
+
"chmod",
|
|
44991
|
+
"chown",
|
|
44992
|
+
"kill",
|
|
44993
|
+
"pkill",
|
|
44994
|
+
"reset",
|
|
44995
|
+
"restore",
|
|
44996
|
+
"clean",
|
|
44997
|
+
"checkout",
|
|
44998
|
+
"commit",
|
|
44999
|
+
"push",
|
|
45000
|
+
"publish",
|
|
45001
|
+
"deploy",
|
|
45002
|
+
"release",
|
|
45003
|
+
"install",
|
|
45004
|
+
"add",
|
|
45005
|
+
"remove",
|
|
45006
|
+
"unlink",
|
|
45007
|
+
"migrate",
|
|
45008
|
+
"seed",
|
|
45009
|
+
"drop",
|
|
45010
|
+
"truncate",
|
|
45011
|
+
"delete",
|
|
45012
|
+
"insert",
|
|
45013
|
+
"update",
|
|
45014
|
+
"create",
|
|
45015
|
+
"curl",
|
|
45016
|
+
"wget",
|
|
45017
|
+
"ssh",
|
|
45018
|
+
"scp",
|
|
45019
|
+
"rsync"
|
|
45020
|
+
]);
|
|
45021
|
+
function firstDangerousToken(words) {
|
|
45022
|
+
return words.find((word) => DANGEROUS_TOKENS.has(word));
|
|
45023
|
+
}
|
|
45024
|
+
async function spawnShell(command, { cwd, env, timeoutMs, signal }) {
|
|
45025
|
+
return await new Promise((resolve) => {
|
|
45026
|
+
let stdout = "";
|
|
45027
|
+
let stderr = "";
|
|
45028
|
+
let settled = false;
|
|
45029
|
+
const child = spawn("/bin/sh", ["-lc", command], {
|
|
45030
|
+
cwd,
|
|
45031
|
+
env,
|
|
45032
|
+
stdio: [
|
|
45033
|
+
"ignore",
|
|
45034
|
+
"pipe",
|
|
45035
|
+
"pipe"
|
|
45036
|
+
]
|
|
45037
|
+
});
|
|
45038
|
+
const settle = (exitCode, extraStderr = "") => {
|
|
45039
|
+
if (settled) return;
|
|
45040
|
+
settled = true;
|
|
45041
|
+
clearTimeout(timer);
|
|
45042
|
+
signal?.removeEventListener("abort", abort);
|
|
45043
|
+
resolve({
|
|
45044
|
+
exitCode,
|
|
45045
|
+
stdout,
|
|
45046
|
+
stderr: `${stderr}${extraStderr}`
|
|
45047
|
+
});
|
|
45048
|
+
};
|
|
45049
|
+
const abort = () => {
|
|
45050
|
+
child.kill("SIGTERM");
|
|
45051
|
+
settle(null, "\nCommand aborted.\n");
|
|
45052
|
+
};
|
|
45053
|
+
const timer = setTimeout(() => {
|
|
45054
|
+
child.kill("SIGTERM");
|
|
45055
|
+
settle(null, `\nCommand timed out after ${timeoutMs}ms.\n`);
|
|
45056
|
+
}, timeoutMs);
|
|
45057
|
+
signal?.addEventListener("abort", abort);
|
|
45058
|
+
child.stdout.on("data", (chunk) => {
|
|
45059
|
+
stdout += chunk.toString("utf8");
|
|
45060
|
+
});
|
|
45061
|
+
child.stderr.on("data", (chunk) => {
|
|
45062
|
+
stderr += chunk.toString("utf8");
|
|
45063
|
+
});
|
|
45064
|
+
child.on("error", (error) => settle(null, `\n${errorMessage$5(error)}\n`));
|
|
45065
|
+
child.on("close", (code) => settle(code));
|
|
45066
|
+
});
|
|
45067
|
+
}
|
|
45068
|
+
function writeShellArtifact(runDir, result) {
|
|
45069
|
+
mkdirSync(runDir.paths.trace, {
|
|
45070
|
+
recursive: true,
|
|
45071
|
+
mode: 448
|
|
45072
|
+
});
|
|
45073
|
+
const artifactPath = join(runDir.paths.trace, `shell-check-${Date.now().toString(36)}-${safeArtifactName(result.command.split(/\s+/)[0] ?? "command")}.txt`);
|
|
45074
|
+
writeFileSync(artifactPath, [
|
|
45075
|
+
`$ ${result.command}`,
|
|
45076
|
+
`cwd: ${result.cwd}`,
|
|
45077
|
+
`reason: ${result.reason}`,
|
|
45078
|
+
`timeoutMs: ${result.timeoutMs}`,
|
|
45079
|
+
`exitCode: ${result.exitCode ?? "null"}`,
|
|
45080
|
+
"",
|
|
45081
|
+
"--- stdout ---",
|
|
45082
|
+
redactSecrets(result.stdout),
|
|
45083
|
+
"",
|
|
45084
|
+
"--- stderr ---",
|
|
45085
|
+
redactSecrets(result.stderr)
|
|
45086
|
+
].join("\n"), { mode: 384 });
|
|
45087
|
+
return artifactPath;
|
|
45088
|
+
}
|
|
45089
|
+
function truncateOutput(value) {
|
|
45090
|
+
if (value.length <= MAX_OUTPUT_CHARS) return value;
|
|
45091
|
+
return `${value.slice(0, MAX_OUTPUT_CHARS)}\n[quire:truncated] output truncated to ${MAX_OUTPUT_CHARS} characters\n`;
|
|
45092
|
+
}
|
|
45093
|
+
function redactSecrets(value) {
|
|
45094
|
+
return value.replace(/([A-Za-z_][A-Za-z0-9_]*(?:TOKEN|SECRET|PASSWORD|KEY)[A-Za-z0-9_]*=)([^\s]+)/gi, "$1[REDACTED]").replace(/(Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, "$1[REDACTED]");
|
|
45095
|
+
}
|
|
45096
|
+
//#endregion
|
|
45097
|
+
//#region src/pipeline/prompts/local-run-safety-contract.ts
|
|
45098
|
+
const localRunSafetyContractPrompt = `Local Quire runtime contract:
|
|
45099
|
+
|
|
45100
|
+
- Obey the enabled tool list exactly; do not invent unavailable tools or capabilities.
|
|
45101
|
+
- Code inspection tools are read-only. Do not edit files, install dependencies, start app servers, reset repositories, or mutate user data.
|
|
45102
|
+
- Do not expose hidden reasoning, raw prompts, raw completions, provider credentials, API tokens, or secrets.
|
|
45103
|
+
- record_progress writes the user-visible progress.jsonl timeline. write_handoff is the required final action. The durable public product artifacts are progress.jsonl, handoff.md, and referenced evidence. Private harness sessions under .harness-sessions are diagnostic implementation state.`;
|
|
45104
|
+
//#endregion
|
|
45105
|
+
//#region src/pipeline/prompts/system-instructions.ts
|
|
45106
|
+
const systemInstructionsPrompt = `You are Quire, a read-only software run agent for investigation, verification, triage, explanation, and project-specific procedures.
|
|
45107
|
+
|
|
45108
|
+
Interpret the run brief first. Infer the user's intent naturally; do not require the user to choose a mode.
|
|
45109
|
+
|
|
45110
|
+
Resolve the target, procedure, and blockers from the user request, caller context, project runbook, available tools, and loaded skills.
|
|
45111
|
+
|
|
45112
|
+
Do not assume the run is web, mobile, or code-only before reading the brief. Use browser or mobile tools only when they are available and relevant.
|
|
45113
|
+
|
|
45114
|
+
When a tool provides its own skills, workflows, or usage guides, load the relevant guidance before using that tool for substantive work. For example, load the relevant \`agent-browser skills get ...\` content before browser-backed QA, exploration, or interaction.
|
|
45115
|
+
|
|
45116
|
+
Use read, grep, find, and ls for read-only repository inspection when code can clarify routes, screens, feature names, state requirements, verification commands, or likely causes.
|
|
45117
|
+
|
|
45118
|
+
Code inspection can guide exploration, but product behavior claims need direct evidence from the requested surface when the run requires browser or mobile observation.
|
|
45119
|
+
|
|
45120
|
+
Before broad exploration or target interaction, call record_progress with kind plan and a concise grounded plan.
|
|
45121
|
+
|
|
45122
|
+
Call write_handoff exactly once when the run is complete or blocked.`;
|
|
45123
|
+
//#endregion
|
|
44649
45124
|
//#region src/pipeline/run-instructions.ts
|
|
44650
|
-
const DEFAULT_RUN_INSTRUCTIONS_ID = "local-run-2026-06-
|
|
45125
|
+
const DEFAULT_RUN_INSTRUCTIONS_ID = "local-run-2026-06-06";
|
|
44651
45126
|
const DEFAULT_REMOTE_RUN_ADDONS_ID = "local-none";
|
|
44652
|
-
const LOCAL_RUN_SAFETY_CONTRACT =
|
|
44653
|
-
|
|
44654
|
-
"- Obey the enabled tool list exactly; do not invent unavailable tools or capabilities.",
|
|
44655
|
-
"- Code inspection tools are read-only. Do not edit files, install dependencies, start app servers, reset repositories, or mutate user data.",
|
|
44656
|
-
"- Do not expose hidden reasoning, raw prompts, raw completions, provider credentials, API tokens, or secrets.",
|
|
44657
|
-
"- record_progress writes the user-visible progress.jsonl timeline. write_handoff is the required final action. The durable public product artifacts are progress.jsonl, handoff.md, and referenced evidence. Private harness sessions under .harness-sessions are diagnostic implementation state."
|
|
44658
|
-
].join("\n");
|
|
44659
|
-
const SYSTEM_INSTRUCTIONS = [
|
|
44660
|
-
"You are Quire, a read-only software run agent for investigation, verification, triage, explanation, and project-specific procedures.",
|
|
44661
|
-
"Interpret the run brief first. Infer the user's intent naturally; do not require the user to choose a mode.",
|
|
44662
|
-
"Resolve the target, procedure, and blockers from the user request, caller context, project runbook, available tools, and loaded skills.",
|
|
44663
|
-
"Do not assume the run is web, mobile, or code-only before reading the brief. Use browser or mobile tools only when they are available and relevant.",
|
|
44664
|
-
"Use read, grep, find, and ls for read-only repository inspection when code can clarify routes, screens, feature names, state requirements, verification commands, or likely causes.",
|
|
44665
|
-
"Code inspection can guide exploration, but product behavior claims need direct evidence from the requested surface when the run requires browser or mobile observation.",
|
|
44666
|
-
"Before broad exploration or target interaction, call record_progress with kind plan and a concise grounded plan.",
|
|
44667
|
-
"Call write_handoff exactly once when the run is complete or blocked."
|
|
44668
|
-
].join("\n");
|
|
45127
|
+
const LOCAL_RUN_SAFETY_CONTRACT = localRunSafetyContractPrompt.trim();
|
|
45128
|
+
const SYSTEM_INSTRUCTIONS = systemInstructionsPrompt.trim();
|
|
44669
45129
|
const COMMON_GUIDANCE = [
|
|
44670
45130
|
"Start by understanding the user request, inferred intent, caller context, runtime capabilities, and project runbook status.",
|
|
44671
45131
|
"Read .quire/runbook.md when target context, verification procedures, deployments, local URLs, devices, accounts, or safety conventions are needed.",
|
|
44672
45132
|
"If the runbook is missing or insufficient, report the smallest missing context instead of guessing ports, app IDs, accounts, or deployment names.",
|
|
44673
45133
|
"Prefer deterministic setup scripts or skills from the runbook over manually repeating login, seed-data, runner, or browser-state setup.",
|
|
45134
|
+
"Use loaded skills as optional QA lenses, not mandatory modes. Select the lenses relevant to the request, target, and available evidence.",
|
|
45135
|
+
"Verification can target browser apps, CLI tools, APIs, mobile apps, docs, static sites, or code paths. Choose the relevant lenses and tools for the target rather than forcing every run through browser UI checks.",
|
|
45136
|
+
"When claiming accessibility, design, responsive, link/form, workflow, CLI, API, mobile, command, or smoke verification, state the evidence boundary. If the boundary was not exercised, mark the check untested or blocked instead of passing it.",
|
|
44674
45137
|
"For direct product tasks, perform the requested workflow and report what happened rather than reframing it as a bug.",
|
|
44675
45138
|
"For bug reports, reproduce or rule out the reported behavior honestly with concrete evidence.",
|
|
45139
|
+
"When the target is a localhost or dev-server URL, classify framework toolbars, debug panels, dev-only overlays, hot-reload artifacts, and transient local dependency noise as local-dev caveats unless they block the requested check or clearly affect the built/preview/production surface.",
|
|
45140
|
+
"Separate product findings from environment caveats: a broken CTA, inaccessible interaction, or weak visual hierarchy is a product finding; an Astro/Vite/dev toolbar covering a screenshot, a DEV-gated tuning panel, or a local-only dependency fetch warning is an environment caveat unless it reproduces outside dev.",
|
|
45141
|
+
"If local dev caveats obstruct visual evidence or runtime confidence and the request needs production-like assurance, recommend or perform a preview/production-build check when safe and available. Otherwise report the caveat without treating it as a user-facing failure.",
|
|
44676
45142
|
"Stay within the target product, URLs, devices, commands, and systems named by the request, caller context, or runbook."
|
|
44677
45143
|
];
|
|
44678
45144
|
const PLANNING_GUIDANCE = [
|
|
44679
45145
|
"For any non-trivial run, record a plan before taking broad exploratory actions or interacting with browser/mobile targets.",
|
|
45146
|
+
"The latest structured plan is materialized to plan.md in the run directory for humans and caller agents. Make it useful as an evidence plan: name the claims, boundaries, and evidence sources you expect to gather.",
|
|
44680
45147
|
"Ground the plan in the user request, caller context, project runbook, and source/config inspection. Do not invent app paths, ports, setup state, accounts, or device IDs.",
|
|
44681
45148
|
"Match each planned assertion to the scope it claims. A file-level or code-only assertion can be supported by file, command, or test evidence; an end-to-end, integration, sync, API, persistence, auth, billing, storage, deployment, browser, or mobile assertion needs evidence from the relevant boundary that makes the claim true.",
|
|
44682
45149
|
"If a required boundary cannot be inspected or exercised with available tools, narrow the assertion to what was actually verified or mark the broader assertion as untested or blocked.",
|
|
44683
45150
|
"For verification-like requests, name the assertions you will check and later record each assertion as passed, failed, untested, or blocked.",
|
|
45151
|
+
"For verification requests, plan the relevant evidence paths early across available tools: shell checks for builds/tests/typechecks/CLI behavior, browser evidence for web product behavior, mobile evidence for app/device behavior, source/config inspection for implementation boundaries, and MCP/API evidence when configured.",
|
|
45152
|
+
"For post-fix verification, include relevant deterministic project checks from .quire/runbook.md, package scripts, or explicit caller instructions when they are safe and applicable. Use run_shell_check so the command evidence lands in Quire artifacts instead of leaving caller agents to duplicate those checks.",
|
|
45153
|
+
"For web or mobile product verification, capture representative viewport/device evidence and exercise the important interactions, safe links/forms/CTAs, and accessibility basics needed to support the requested claims. Do not stop early while named high-value assertions remain feasible.",
|
|
44684
45154
|
"For triage-like requests, name the severity, impact, ownership, reproducibility, and next-action signals you will inspect.",
|
|
44685
45155
|
"For investigation-like requests, name the hypotheses, surfaces, code paths, logs, or product states you will examine.",
|
|
44686
45156
|
"Record expected behavior before executing the action that tests it; this makes unexpected observations harder to rationalize as success."
|
|
44687
45157
|
];
|
|
44688
45158
|
const TOOL_GUIDANCE = [
|
|
45159
|
+
"Use run_shell_check for non-dangerous project commands that produce useful evidence: local app startup checks, typechecks, builds, tests, lint/check commands, CLI smoke checks, custom project scripts, and read-only git/source inspection. Include the command artifact path in evidenceRefs when it supports an assertion.",
|
|
45160
|
+
"Do not use run_shell_check for installs, deploys, publishing, git mutation, destructive filesystem operations, database writes, seed/migrate commands, or external write requests. If such a command is required, mark it blocked or ask the caller to approve/add a safe runbook procedure.",
|
|
44689
45161
|
"Use agent-browser only when the runtime capabilities say browser automation is available.",
|
|
44690
45162
|
"Before browser exploration, run agent-browser skills get core once when that skill is available so browser guidance matches the installed CLI.",
|
|
45163
|
+
"For verification-like browser runs, especially QA, dogfooding, design review, accessibility, responsive, link, interaction, or form checks, run agent-browser skills get dogfood once before substantive browser actions when that skill is available. Use core plus dogfood; core alone is insufficient for product QA unless the run is only a tiny page-load smoke test.",
|
|
44691
45164
|
"Every agent-browser invocation is recorded as evidence. Do not call non-investigation browser commands such as install, upgrade, doctor, profile, or dashboard during exploration.",
|
|
44692
45165
|
"Use agent-mobile only when runtime capabilities say the mobile tool is registered. Treat device, simulator, app, and provider readiness as unknown until the tool or runbook proves them.",
|
|
44693
45166
|
"Every agent-mobile invocation is recorded as evidence. Do not call non-replayable commands such as help, doctor, serve, or close during exploration.",
|
|
@@ -44722,8 +45195,13 @@ const DEFAULT_HANDOFF_GUIDANCE = [
|
|
|
44722
45195
|
"Use needs_info when the report lacks necessary route, account, data, state, device, runner, or expected-behavior details after reasonable discovery.",
|
|
44723
45196
|
"Use environment_blocked when the endpoint, app install/launch, auth, data, network, runner, or browser/mobile tooling prevents reaching the needed state.",
|
|
44724
45197
|
"Write handoffMarkdown so a human can understand the verdict without reading raw logs and a coding agent can continue from handoff.md without rerunning the whole exploration.",
|
|
45198
|
+
"Include every artifact that supports the final verdict in write_handoff.evidenceRefs. Use artifactPath for [quire:artifact] paths returned by browser, mobile, or run_shell_check tools.",
|
|
45199
|
+
"Do not rely on prose-only evidence mentions. Quire will append a renderable Evidence artifacts section to handoff.md from public evidenceRefs and auto-captured artifacts.",
|
|
45200
|
+
"Do not include .harness-sessions paths, raw model transcripts, secrets, or private diagnostic state in evidenceRefs or handoffMarkdown.",
|
|
45201
|
+
"For markdown evidence links you write manually, prefer run-relative ./evidence/... paths over absolute local paths.",
|
|
44725
45202
|
"Treat progress.jsonl as the canonical structured ledger. handoff.md is the human-readable report view, not the machine-readable source of truth.",
|
|
44726
45203
|
"For verification-like requests or runs with structured assertions, use a Verification Report shape with Target, Request, Run, Summary, Results, Pre-existing Issues / Non-regressions when relevant, and Evidence sections.",
|
|
45204
|
+
"For post-fix or regression verification, make the requested claim verdict easy to find, then summarize product evidence, deterministic command evidence, blockers/untested checks, and unrelated or follow-up findings. Use natural prose and concise tables where helpful; do not force boilerplate if a simpler report is clearer.",
|
|
44727
45205
|
"For verification-like requests, include a decisive first paragraph with pass, fail, untested, blocked, and unresolved counts plus the strongest supported conclusion.",
|
|
44728
45206
|
"For verification-like requests, include a results table with assertion id, check title, verdict, expected, and actual. Include unresolved planned assertions explicitly; unresolved means no assertion record exists, not that the check passed or was untested.",
|
|
44729
45207
|
"Keep pre-existing, not-regression, and out-of-scope findings separate from the requested verification verdict.",
|
|
@@ -45507,6 +45985,11 @@ function resolveRuntimeCapabilities(input) {
|
|
|
45507
45985
|
available: true,
|
|
45508
45986
|
names: [...READ_ONLY_RUN_CODE_TOOL_NAMES]
|
|
45509
45987
|
},
|
|
45988
|
+
shellCheck: {
|
|
45989
|
+
available: true,
|
|
45990
|
+
toolName: "run_shell_check",
|
|
45991
|
+
safety: "dangerous_commands_blocked"
|
|
45992
|
+
},
|
|
45510
45993
|
browser: browserCapability(input.headed),
|
|
45511
45994
|
mobile: mobileCapability(),
|
|
45512
45995
|
mcp: {
|
|
@@ -45545,6 +46028,7 @@ function composeRunBrief(input) {
|
|
|
45545
46028
|
"## Run Artifacts",
|
|
45546
46029
|
`Run directory: ${toJsonPath(input.runDir.root)}`,
|
|
45547
46030
|
`Narrated progress file: ${toJsonPath(input.runDir.paths.progress)}`,
|
|
46031
|
+
`Evidence plan file: ${toJsonPath(input.runDir.paths.plan)}`,
|
|
45548
46032
|
`Final handoff path: ${toJsonPath(input.runDir.paths.handoff)}`,
|
|
45549
46033
|
`Evidence directory: ${toJsonPath(input.runDir.paths.evidence)}`,
|
|
45550
46034
|
`Private diagnostic harness session directory: ${toJsonPath(input.runDir.paths.harnessSessions)}`,
|
|
@@ -45558,6 +46042,7 @@ function composeRunBrief(input) {
|
|
|
45558
46042
|
"## Planning Requirement",
|
|
45559
46043
|
[
|
|
45560
46044
|
"Before broad exploration or browser/mobile interaction, record a concise plan with record_progress kind plan.",
|
|
46045
|
+
"The latest structured plan is materialized to plan.md in the run directory so caller agents can see what evidence Quire intends to gather while the run is active.",
|
|
45561
46046
|
"For verification-like work, state assertions before checking them and later record each as passed, failed, untested, or blocked.",
|
|
45562
46047
|
"For triage-like work, state the severity, impact, ownership, and next-action signals to inspect.",
|
|
45563
46048
|
"For investigation-like work, state the hypotheses, surfaces, or evidence paths to examine."
|
|
@@ -45643,6 +46128,7 @@ function capabilityLines(capabilities) {
|
|
|
45643
46128
|
`Runner: ${capabilities.runnerKind} on ${capabilities.platform}/${capabilities.arch}`,
|
|
45644
46129
|
`Repository cwd: ${toJsonPath(capabilities.cwd)}`,
|
|
45645
46130
|
`Read-only code tools: ${capabilities.tools.readOnlyCode.names.join(", ")}`,
|
|
46131
|
+
`Shell checks: ${capabilities.tools.shellCheck.toolName} available for non-dangerous project commands, local app startup checks, and verification evidence`,
|
|
45646
46132
|
`Browser automation: ${browser.available ? `${browser.toolName}${browser.headed === true ? " (headed)" : ""}` : `unavailable (${browser.unavailableReason ?? "unknown"})`}`,
|
|
45647
46133
|
`Mobile automation: ${mobile.available ? `${mobile.toolName} tool registered; device/app readiness unknown until checked` : `unavailable (${mobile.unavailableReason ?? "unknown"})`}`,
|
|
45648
46134
|
`MCP gateway: ${mcp.available ? `available (${mcp.toolNames.length === 0 ? "no tools listed" : mcp.toolNames.join(", ")})` : "unavailable"}`
|
|
@@ -45660,15 +46146,16 @@ function followupLines(context) {
|
|
|
45660
46146
|
function orderedListOrNone(values) {
|
|
45661
46147
|
return values.length === 0 ? "(none provided)" : values.map((value, index) => `${index + 1}. ${value}`).join("\n");
|
|
45662
46148
|
}
|
|
46149
|
+
//#endregion
|
|
46150
|
+
//#region src/pipeline/quire-agent.ts
|
|
46151
|
+
const RUN_ACTIVITY_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
45663
46152
|
const QUIRE_AGENT_THINKING_LEVEL = INVESTIGATION_THINKING_LEVEL;
|
|
45664
46153
|
async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons, runtimeCapabilities, runDir, sessionId, headed = false, progress, onProgressEvent, followup, skillPack, mcpGateway, onResolvedModel }) {
|
|
45665
46154
|
const codebasePath = readRunMetadata(runDir)?.repoPath ?? runDir.root;
|
|
45666
46155
|
let emittedHandoff;
|
|
45667
46156
|
let observedToolAttempts = 0;
|
|
45668
|
-
let diagnosisSteered = false;
|
|
45669
46157
|
const observedTargetToolCallIds = /* @__PURE__ */ new Set();
|
|
45670
46158
|
const targetToolCallArgs = /* @__PURE__ */ new Map();
|
|
45671
|
-
const pendingSessionActions = [];
|
|
45672
46159
|
const runtime = await createQuireAgentRuntime({
|
|
45673
46160
|
runDir,
|
|
45674
46161
|
codebasePath,
|
|
@@ -45691,23 +46178,16 @@ async function runQuireAgent({ intent, runBrief, runInstructions, remoteAddons,
|
|
|
45691
46178
|
modelId: runtime.model.id
|
|
45692
46179
|
});
|
|
45693
46180
|
const unsubscribe = session.subscribe((event) => {
|
|
46181
|
+
touchRunActivity(runDir.root, { minIntervalMs: RUN_ACTIVITY_HEARTBEAT_INTERVAL_MS });
|
|
45694
46182
|
logAgentProgress(event);
|
|
45695
46183
|
emittedHandoff ??= extractWriteHandoff(event);
|
|
45696
46184
|
rememberTargetToolCallArgs(event, registeredTargetTools, targetToolCallArgs);
|
|
45697
46185
|
const attemptsObserved = countNewTargetAttempts(event, registeredTargetTools, observedTargetToolCallIds, targetToolCallArgs);
|
|
45698
|
-
if (attemptsObserved > 0)
|
|
45699
|
-
observedToolAttempts += attemptsObserved;
|
|
45700
|
-
if (observedToolAttempts >= 6 && !diagnosisSteered && emittedHandoff === void 0) {
|
|
45701
|
-
diagnosisSteered = true;
|
|
45702
|
-
pendingSessionActions.push(session.steer(buildRunSteeringPrompt(observedToolAttempts, registeredTargetTools)));
|
|
45703
|
-
}
|
|
45704
|
-
}
|
|
46186
|
+
if (attemptsObserved > 0) observedToolAttempts += attemptsObserved;
|
|
45705
46187
|
});
|
|
45706
46188
|
try {
|
|
45707
46189
|
await session.prompt(runBrief, { expandPromptTemplates: false });
|
|
45708
|
-
await Promise.all(pendingSessionActions);
|
|
45709
46190
|
if (emittedHandoff === void 0) await session.prompt(buildMissingRunHandoffPrompt(observedToolAttempts), { expandPromptTemplates: false });
|
|
45710
|
-
await Promise.all(pendingSessionActions);
|
|
45711
46191
|
} finally {
|
|
45712
46192
|
runtime.writeSessionState();
|
|
45713
46193
|
unsubscribe();
|
|
@@ -45733,12 +46213,17 @@ async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed
|
|
|
45733
46213
|
runDir,
|
|
45734
46214
|
sessionId,
|
|
45735
46215
|
cwd: codebasePath,
|
|
45736
|
-
headed
|
|
46216
|
+
headed,
|
|
46217
|
+
onProgressEvent
|
|
45737
46218
|
}) : void 0;
|
|
45738
46219
|
const mobileTool = runtimeCapabilities.tools.mobile.available === true ? makeMobileTool({
|
|
45739
46220
|
runDir,
|
|
45740
46221
|
cwd: codebasePath
|
|
45741
46222
|
}) : void 0;
|
|
46223
|
+
const shellCheckTool = makeShellCheckTool({
|
|
46224
|
+
runDir,
|
|
46225
|
+
cwd: codebasePath
|
|
46226
|
+
});
|
|
45742
46227
|
const mcpGatewayTool = mcpGateway === void 0 ? void 0 : makeQuireMcpGatewayTool({
|
|
45743
46228
|
gateway: mcpGateway,
|
|
45744
46229
|
runId: runDir.runId
|
|
@@ -45781,6 +46266,7 @@ async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed
|
|
|
45781
46266
|
customTools: [
|
|
45782
46267
|
...browserTool === void 0 ? [] : [browserTool],
|
|
45783
46268
|
...mobileTool === void 0 ? [] : [mobileTool],
|
|
46269
|
+
shellCheckTool,
|
|
45784
46270
|
withPromptGuidelines(createRecordProgressTool({
|
|
45785
46271
|
runDir,
|
|
45786
46272
|
onProgressEvent
|
|
@@ -45792,6 +46278,7 @@ async function createQuireAgentRuntime({ runDir, codebasePath, sessionId, headed
|
|
|
45792
46278
|
...READ_ONLY_RUN_CODE_TOOL_NAMES,
|
|
45793
46279
|
...browserTool === void 0 ? [] : [browserTool.name],
|
|
45794
46280
|
...mobileTool === void 0 ? [] : [mobileTool.name],
|
|
46281
|
+
shellCheckTool.name,
|
|
45795
46282
|
"record_progress",
|
|
45796
46283
|
writeHandoffTool.name,
|
|
45797
46284
|
...mcpGatewayTool === void 0 ? [] : [mcpGatewayTool.name]
|
|
@@ -45834,13 +46321,6 @@ function modelSourceProgressMessage(source, model) {
|
|
|
45834
46321
|
case "quire_credits": return `Using Quire Credits via brokered model source (${model.provider}/${model.id}).`;
|
|
45835
46322
|
}
|
|
45836
46323
|
}
|
|
45837
|
-
function buildRunSteeringPrompt(attempts, targetTools) {
|
|
45838
|
-
return [
|
|
45839
|
-
`${attempts} browser/mobile-backed attempts have been observed through ${targetTools.map((tool) => tool.toolName).join(", ") || "target tools"}.`,
|
|
45840
|
-
"Stop blind retries. Decide whether the run has enough evidence, needs narrower target context, is blocked by runtime/tooling/account/data, or requires one different action.",
|
|
45841
|
-
"If the run is complete or blocked, call write_handoff now. If more evidence is needed, take the next action that directly tests the diagnosis."
|
|
45842
|
-
].join("\n");
|
|
45843
|
-
}
|
|
45844
46324
|
function buildMissingRunHandoffPrompt(targetActions) {
|
|
45845
46325
|
return [
|
|
45846
46326
|
"The previous run ended without calling write_handoff.",
|
|
@@ -45883,10 +46363,13 @@ function isTargetReproductionAttempt(args, target) {
|
|
|
45883
46363
|
if (args === void 0) return true;
|
|
45884
46364
|
if (target === "mobile") {
|
|
45885
46365
|
const command = findMobileCommand(args);
|
|
45886
|
-
return command !== "open" && command !== "install";
|
|
46366
|
+
return command !== "open" && command !== "install" && command !== "screenshot";
|
|
45887
46367
|
}
|
|
45888
|
-
|
|
45889
|
-
if (
|
|
46368
|
+
const command = args[0];
|
|
46369
|
+
if (command === "open") return false;
|
|
46370
|
+
if (command === "set" && args[1] === "viewport") return false;
|
|
46371
|
+
if (command === "skills") return false;
|
|
46372
|
+
if (command === "snapshot" || command === "screenshot" || command === "get" || command === "eval") return false;
|
|
45890
46373
|
return true;
|
|
45891
46374
|
}
|
|
45892
46375
|
function rememberToolCallId(toolCallId, observedToolCallIds) {
|
|
@@ -48279,8 +48762,8 @@ function defaultSleep$1(ms) {
|
|
|
48279
48762
|
const ARTIFACT_MODE = "evidence";
|
|
48280
48763
|
const investigationCommandSchema = {
|
|
48281
48764
|
version: 1,
|
|
48282
|
-
command: "quire
|
|
48283
|
-
description: "Starts a durable Quire
|
|
48765
|
+
command: "quire run",
|
|
48766
|
+
description: "Starts a durable Quire run for a natural-language request or piped brief. Bare `quire \"<prompt>\"` is equivalent to `quire run \"<prompt>\"`.",
|
|
48284
48767
|
defaultMode: "attached",
|
|
48285
48768
|
input: {
|
|
48286
48769
|
pipedStdin: true,
|
|
@@ -48290,7 +48773,7 @@ const investigationCommandSchema = {
|
|
|
48290
48773
|
stdout: {
|
|
48291
48774
|
human: "Without --json, stdout shows a short start summary and attaches to the live run log unless --detach is passed.",
|
|
48292
48775
|
json: "With --json, stdout contains exactly one newline-terminated JSON start handle and does not attach to the live log.",
|
|
48293
|
-
schema: "With --schema, stdout contains this newline-terminated JSON schema and no
|
|
48776
|
+
schema: "With --schema, stdout contains this newline-terminated JSON schema and no run starts."
|
|
48294
48777
|
},
|
|
48295
48778
|
stderr: { progress: "Machine-readable errors are written to stderr. Human attached runs stream live run logs after the start summary." },
|
|
48296
48779
|
arguments: [
|
|
@@ -48299,7 +48782,7 @@ const investigationCommandSchema = {
|
|
|
48299
48782
|
type: "string",
|
|
48300
48783
|
positional: true,
|
|
48301
48784
|
requiredUnless: "piped stdin",
|
|
48302
|
-
description: "Natural-language
|
|
48785
|
+
description: "Natural-language run request or instruction."
|
|
48303
48786
|
},
|
|
48304
48787
|
{
|
|
48305
48788
|
name: "--json",
|
|
@@ -48382,21 +48865,20 @@ const investigationCommandSchema = {
|
|
|
48382
48865
|
safety: ["Raw model thinking, prompts, completions, provider credentials, API tokens, and local absolute repo paths are not synced to the web case.", "Sync failures are non-fatal for local investigations; local artifacts and retry state remain in the run directory."],
|
|
48383
48866
|
examples: [
|
|
48384
48867
|
"quire \"verify checkout before I approve this PR\"",
|
|
48385
|
-
"printf 'why is CI failing?\\nRelevant log: ...' | quire
|
|
48868
|
+
"printf 'why is CI failing?\\nRelevant log: ...' | quire run --json",
|
|
48386
48869
|
"quire status run_9x4mdq7p2h8kc6nv4apz --json",
|
|
48387
48870
|
"quire watch run_9x4mdq7p2h8kc6nv4apz"
|
|
48388
48871
|
]
|
|
48389
48872
|
};
|
|
48390
|
-
const
|
|
48873
|
+
const quireRunCommand = defineCommand({
|
|
48391
48874
|
meta: {
|
|
48392
|
-
name: "
|
|
48393
|
-
|
|
48394
|
-
description: "Investigate a question against the current workspace."
|
|
48875
|
+
name: "run",
|
|
48876
|
+
description: "Run evidence-backed QA, verification, triage, or investigation work."
|
|
48395
48877
|
},
|
|
48396
48878
|
args: {
|
|
48397
48879
|
prompt: {
|
|
48398
48880
|
type: "positional",
|
|
48399
|
-
description: "Natural-language
|
|
48881
|
+
description: "Natural-language run request.",
|
|
48400
48882
|
required: false
|
|
48401
48883
|
},
|
|
48402
48884
|
url: {
|
|
@@ -48419,11 +48901,11 @@ const investigateCommand = defineCommand({
|
|
|
48419
48901
|
},
|
|
48420
48902
|
headed: {
|
|
48421
48903
|
type: "boolean",
|
|
48422
|
-
description: "Show the browser window while the
|
|
48904
|
+
description: "Show the browser window while the Quire agent runs."
|
|
48423
48905
|
},
|
|
48424
48906
|
schema: {
|
|
48425
48907
|
type: "boolean",
|
|
48426
|
-
description: "Emit the machine-readable
|
|
48908
|
+
description: "Emit the machine-readable run invocation schema and exit."
|
|
48427
48909
|
}
|
|
48428
48910
|
},
|
|
48429
48911
|
async run({ args }) {
|
|
@@ -48466,7 +48948,7 @@ async function runInvestigate(options) {
|
|
|
48466
48948
|
});
|
|
48467
48949
|
if (options.json === true) writeJson(stdout, handle);
|
|
48468
48950
|
else {
|
|
48469
|
-
log.success(`Started
|
|
48951
|
+
log.success(`Started run ${color.value(handle.runId)}.`, {
|
|
48470
48952
|
output: stdout,
|
|
48471
48953
|
spacing: 0
|
|
48472
48954
|
});
|
|
@@ -48480,7 +48962,12 @@ async function runInvestigate(options) {
|
|
|
48480
48962
|
spacing: 0,
|
|
48481
48963
|
withGuide: true
|
|
48482
48964
|
});
|
|
48483
|
-
else log.message(
|
|
48965
|
+
else log.message([
|
|
48966
|
+
`${color.label("Run")}: ${color.path(handle.runPath)}`,
|
|
48967
|
+
`${color.label("Status")}: ${color.command(handle.statusCommand)}`,
|
|
48968
|
+
`${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.`
|
|
48970
|
+
], {
|
|
48484
48971
|
output: stdout,
|
|
48485
48972
|
spacing: 0,
|
|
48486
48973
|
withGuide: true
|
|
@@ -48631,6 +49118,7 @@ function createDoctorContext(overrides = {}) {
|
|
|
48631
49118
|
cwd: resolve(overrides.cwd ?? process.cwd()),
|
|
48632
49119
|
runsRoot: resolve(overrides.runsRoot ?? defaultRunsRoot()),
|
|
48633
49120
|
authStore: overrides.authStore ?? authStore,
|
|
49121
|
+
env: overrides.env ?? process.env,
|
|
48634
49122
|
fetchFn: overrides.fetchFn ?? fetch,
|
|
48635
49123
|
execFn: overrides.execFn ?? defaultExecFn,
|
|
48636
49124
|
probeTimeoutMs: overrides.probeTimeoutMs ?? 2e3
|
|
@@ -48647,25 +49135,30 @@ async function defaultExecFn(file, args, options) {
|
|
|
48647
49135
|
};
|
|
48648
49136
|
}
|
|
48649
49137
|
async function checkAuthPresent(context) {
|
|
48650
|
-
const
|
|
49138
|
+
const source = context.env["QUIRE_API_TOKEN"]?.trim() ? "env_api_token" : "stored_login";
|
|
49139
|
+
const credentials = await resolveAuthCredentials({
|
|
49140
|
+
store: context.authStore,
|
|
49141
|
+
env: context.env
|
|
49142
|
+
});
|
|
48651
49143
|
if (credentials === null) return { result: {
|
|
48652
49144
|
id: "auth.present",
|
|
48653
49145
|
section: "identity",
|
|
48654
49146
|
severity: "fail",
|
|
48655
|
-
message: "No Quire credentials
|
|
48656
|
-
fix: { command: "quire login" }
|
|
49147
|
+
message: "No Quire credentials found. Authenticate with an API token or interactive login.",
|
|
49148
|
+
fix: { command: "Set QUIRE_API_TOKEN or run `quire login`" }
|
|
48657
49149
|
} };
|
|
48658
49150
|
return {
|
|
48659
49151
|
result: {
|
|
48660
49152
|
id: "auth.present",
|
|
48661
49153
|
section: "identity",
|
|
48662
49154
|
severity: "pass",
|
|
48663
|
-
message:
|
|
49155
|
+
message: `Credentials present (${authSourceLabel(source)}; API ${credentials.apiBaseUrl}).`
|
|
48664
49156
|
},
|
|
48665
49157
|
auth: {
|
|
48666
49158
|
apiBaseUrl: credentials.apiBaseUrl,
|
|
48667
49159
|
accessToken: credentials.accessToken,
|
|
48668
49160
|
tokenType: credentials.tokenType,
|
|
49161
|
+
source,
|
|
48669
49162
|
...credentials.user === void 0 ? {} : { user: credentials.user }
|
|
48670
49163
|
}
|
|
48671
49164
|
};
|
|
@@ -48688,8 +49181,8 @@ async function checkAuthValid(context, auth) {
|
|
|
48688
49181
|
id: "auth.valid",
|
|
48689
49182
|
section: "identity",
|
|
48690
49183
|
severity: "fail",
|
|
48691
|
-
message: `Could not validate
|
|
48692
|
-
fix: { command:
|
|
49184
|
+
message: `Could not validate API token against ${auth.apiBaseUrl}: ${toMessage(error)}`,
|
|
49185
|
+
fix: { command: apiTokenFixCommand(auth.apiBaseUrl) }
|
|
48693
49186
|
} };
|
|
48694
49187
|
}
|
|
48695
49188
|
try {
|
|
@@ -48703,8 +49196,8 @@ async function checkAuthValid(context, auth) {
|
|
|
48703
49196
|
id: "auth.valid",
|
|
48704
49197
|
section: "identity",
|
|
48705
49198
|
severity: "fail",
|
|
48706
|
-
message:
|
|
48707
|
-
fix: { command:
|
|
49199
|
+
message: `Stored login was rejected by ${auth.apiBaseUrl}.`,
|
|
49200
|
+
fix: { command: loginFixCommand(auth.apiBaseUrl) }
|
|
48708
49201
|
} };
|
|
48709
49202
|
const user = lookup.data.user;
|
|
48710
49203
|
return {
|
|
@@ -48721,8 +49214,8 @@ async function checkAuthValid(context, auth) {
|
|
|
48721
49214
|
id: "auth.valid",
|
|
48722
49215
|
section: "identity",
|
|
48723
49216
|
severity: "fail",
|
|
48724
|
-
message: `Could not validate
|
|
48725
|
-
fix: { command:
|
|
49217
|
+
message: `Could not validate stored login against ${auth.apiBaseUrl}: ${toMessage(error)}`,
|
|
49218
|
+
fix: { command: loginFixCommand(auth.apiBaseUrl) }
|
|
48726
49219
|
} };
|
|
48727
49220
|
}
|
|
48728
49221
|
}
|
|
@@ -48764,6 +49257,15 @@ function brokerToCheckResult(status) {
|
|
|
48764
49257
|
message: `Model broker available (${status.selectedProviderMode ?? "default"}).`
|
|
48765
49258
|
};
|
|
48766
49259
|
}
|
|
49260
|
+
function authSourceLabel(source) {
|
|
49261
|
+
return source === "env_api_token" ? `${QUIRE_API_TOKEN_ENV} API token` : "stored login";
|
|
49262
|
+
}
|
|
49263
|
+
function apiTokenFixCommand(apiBaseUrl) {
|
|
49264
|
+
return `Set QUIRE_API_TOKEN to an active token for ${apiBaseUrl}; set QUIRE_API_URL if that API URL is wrong; or run \`quire login --api-url ${apiBaseUrl}\``;
|
|
49265
|
+
}
|
|
49266
|
+
function loginFixCommand(apiBaseUrl) {
|
|
49267
|
+
return `Run \`quire login --api-url ${apiBaseUrl}\`, or set QUIRE_API_TOKEN and QUIRE_API_URL for API-token auth`;
|
|
49268
|
+
}
|
|
48767
49269
|
async function checkRunbook(context) {
|
|
48768
49270
|
const runbook = await inspectProjectRunbookStatus(context.cwd);
|
|
48769
49271
|
if (!runbook.exists) return {
|
|
@@ -49176,22 +49678,78 @@ async function runLogout(options) {
|
|
|
49176
49678
|
//#endregion
|
|
49177
49679
|
//#region src/commands/setup.ts
|
|
49178
49680
|
const SETUP_URL = "https://quire.sh/agent-setup";
|
|
49681
|
+
const VERIFICATION_SKILL_URL = "https://quire.sh/agent-verification";
|
|
49179
49682
|
const AGENT_SETUP_PROMPT = `Set up this repository for Quire investigations.
|
|
49180
49683
|
|
|
49181
49684
|
Please load and follow the Quire agent setup instructions from ${SETUP_URL}.
|
|
49182
49685
|
|
|
49183
|
-
Inspect the project,
|
|
49686
|
+
Inspect the project, ask focused questions for important missing runbook facts instead of writing placeholder unknowns, then create a .quire/runbook.md that encodes the best-practices verification loop for this specific project. Capture how Quire should self-verify work in this domain: target/environment choice, local startup, browser/API/CLI/mobile checks, console/runtime signals, core flows, verification commands, design/reference surfaces, auth/data setup, performance/accessibility expectations, and mutation-safety rules. Update AGENTS.md with concise Quire notes, then run Quire doctor using the available Quire command, e.g. \`quire doctor\` or \`npx @quireco/cli doctor\`, and report anything still blocked.`;
|
|
49687
|
+
const QUIRE_SKILL_MARKDOWN = `---
|
|
49688
|
+
name: quire
|
|
49689
|
+
description: Runs Quire CLI evidence-backed QA, verification, triage, and investigation work. Use after implementing UI or workflow changes, when testing local/preview/prod URLs, or when claims need screenshots, command output, observations, and a trustworthy handoff.
|
|
49690
|
+
---
|
|
49691
|
+
|
|
49692
|
+
# Quire verification
|
|
49693
|
+
|
|
49694
|
+
Load and follow the current Quire verification skill from ${VERIFICATION_SKILL_URL}.
|
|
49695
|
+
|
|
49696
|
+
Use Quire when a task needs evidence-backed verification, QA, product dogfooding, bug reproduction, or triage. Prefer \`quire run\` for new work.
|
|
49697
|
+
`;
|
|
49698
|
+
const SKILL_INSTALL_TARGETS = [{
|
|
49699
|
+
id: "agents",
|
|
49700
|
+
label: "Universal",
|
|
49701
|
+
targetPath: ".agents/skills/quire/SKILL.md",
|
|
49702
|
+
hint: "~/.agents/skills/quire/SKILL.md · Pi, OpenCode, Amp, Codex-style agents",
|
|
49703
|
+
aliases: [
|
|
49704
|
+
"agents",
|
|
49705
|
+
"agent-skills",
|
|
49706
|
+
"pi",
|
|
49707
|
+
"opencode",
|
|
49708
|
+
"amp",
|
|
49709
|
+
"codex",
|
|
49710
|
+
"other"
|
|
49711
|
+
]
|
|
49712
|
+
}, {
|
|
49713
|
+
id: "claude",
|
|
49714
|
+
label: "Claude Code",
|
|
49715
|
+
targetPath: ".claude/skills/quire/SKILL.md",
|
|
49716
|
+
hint: "~/.claude/skills/quire/SKILL.md",
|
|
49717
|
+
aliases: ["claude", "claude-code"]
|
|
49718
|
+
}];
|
|
49184
49719
|
const setupCommand = defineCommand({
|
|
49185
49720
|
meta: {
|
|
49186
49721
|
name: "setup",
|
|
49187
49722
|
description: "Prepare this workspace for Quire investigations."
|
|
49188
49723
|
},
|
|
49189
|
-
|
|
49190
|
-
|
|
49724
|
+
args: {
|
|
49725
|
+
skill: {
|
|
49726
|
+
type: "string",
|
|
49727
|
+
description: "Comma-separated local agent skill targets to install: agents, claude, all, none.",
|
|
49728
|
+
valueHint: "targets"
|
|
49729
|
+
},
|
|
49730
|
+
"no-skill": {
|
|
49731
|
+
type: "boolean",
|
|
49732
|
+
description: "Skip installing local Quire agent skills."
|
|
49733
|
+
},
|
|
49734
|
+
"no-interactive": {
|
|
49735
|
+
type: "boolean",
|
|
49736
|
+
description: "Do not prompt; use default setup choices."
|
|
49737
|
+
}
|
|
49738
|
+
},
|
|
49739
|
+
async run({ args }) {
|
|
49740
|
+
await runSetup({
|
|
49741
|
+
skill: args["no-skill"] === true ? false : args.skill,
|
|
49742
|
+
interactive: args["no-interactive"] !== true
|
|
49743
|
+
});
|
|
49191
49744
|
}
|
|
49192
49745
|
});
|
|
49193
49746
|
async function runSetup(options = {}) {
|
|
49194
49747
|
const stdout = options.io?.stdout ?? process.stdout;
|
|
49748
|
+
const selectedTargets = await selectSkillInstallTargets({
|
|
49749
|
+
skill: options.skill,
|
|
49750
|
+
interactive: options.interactive ?? (options.io?.stdout === void 0 && process.stdout.isTTY === true)
|
|
49751
|
+
});
|
|
49752
|
+
const installedSkills = await installQuireSkill(options.homeDir ?? homedir(), selectedTargets);
|
|
49195
49753
|
log.step("Set up Quire for this workspace", {
|
|
49196
49754
|
output: stdout,
|
|
49197
49755
|
spacing: 0
|
|
@@ -49205,6 +49763,51 @@ async function runSetup(options = {}) {
|
|
|
49205
49763
|
writeLine(stdout, color.label("--- copy prompt ---"));
|
|
49206
49764
|
writeLine(stdout, AGENT_SETUP_PROMPT);
|
|
49207
49765
|
writeLine(stdout, color.label("--- end prompt ---"));
|
|
49766
|
+
if (installedSkills.length > 0) {
|
|
49767
|
+
writeLine(stdout);
|
|
49768
|
+
writeLine(stdout, color.label("Installed local Quire skill:"));
|
|
49769
|
+
for (const path of installedSkills) writeLine(stdout, ` ${color.success("✓")} ${path}`);
|
|
49770
|
+
}
|
|
49771
|
+
}
|
|
49772
|
+
async function selectSkillInstallTargets(options) {
|
|
49773
|
+
if (options.skill === false || options.skill === "none") return [];
|
|
49774
|
+
if (options.skill !== void 0) return resolveSkillTargets(options.skill);
|
|
49775
|
+
if (!options.interactive) return [...SKILL_INSTALL_TARGETS];
|
|
49776
|
+
const selected = await multiselect({
|
|
49777
|
+
message: "Which local agent skill directories should Quire install into?",
|
|
49778
|
+
options: SKILL_INSTALL_TARGETS.map((target) => ({
|
|
49779
|
+
label: target.label,
|
|
49780
|
+
value: target.id,
|
|
49781
|
+
hint: target.hint
|
|
49782
|
+
})),
|
|
49783
|
+
initialValues: ["agents", "claude"],
|
|
49784
|
+
required: false
|
|
49785
|
+
});
|
|
49786
|
+
if (isCancel(selected) || selected.length === 0) return [];
|
|
49787
|
+
return resolveSkillTargets(selected.join(","));
|
|
49788
|
+
}
|
|
49789
|
+
function resolveSkillTargets(value) {
|
|
49790
|
+
const parts = value.split(",").map((part) => part.trim().toLowerCase()).filter((part) => part.length > 0);
|
|
49791
|
+
if (parts.includes("all")) return [...SKILL_INSTALL_TARGETS];
|
|
49792
|
+
const selected = /* @__PURE__ */ new Map();
|
|
49793
|
+
for (const part of parts) {
|
|
49794
|
+
const target = SKILL_INSTALL_TARGETS.find((candidate) => candidate.id === part || candidate.aliases.includes(part));
|
|
49795
|
+
if (target !== void 0) selected.set(target.id, target);
|
|
49796
|
+
}
|
|
49797
|
+
return [...selected.values()];
|
|
49798
|
+
}
|
|
49799
|
+
async function installQuireSkill(homeDir, targets) {
|
|
49800
|
+
const installed = [];
|
|
49801
|
+
await Promise.all(targets.map(async (target) => {
|
|
49802
|
+
const path = join(homeDir, target.targetPath);
|
|
49803
|
+
await mkdir(dirname(path), {
|
|
49804
|
+
recursive: true,
|
|
49805
|
+
mode: 448
|
|
49806
|
+
});
|
|
49807
|
+
await writeFile(path, QUIRE_SKILL_MARKDOWN, { mode: 384 });
|
|
49808
|
+
installed.push(path);
|
|
49809
|
+
}));
|
|
49810
|
+
return installed.sort();
|
|
49208
49811
|
}
|
|
49209
49812
|
//#endregion
|
|
49210
49813
|
//#region src/commands/whoami.ts
|
|
@@ -49356,7 +49959,7 @@ function formatWalletBalance(value) {
|
|
|
49356
49959
|
}
|
|
49357
49960
|
//#endregion
|
|
49358
49961
|
//#region package.json
|
|
49359
|
-
var version$1 = "0.0.
|
|
49962
|
+
var version$1 = "0.0.10";
|
|
49360
49963
|
//#endregion
|
|
49361
49964
|
//#region src/cli.ts
|
|
49362
49965
|
const subCommands = {
|
|
@@ -49382,18 +49985,14 @@ const subCommands = {
|
|
|
49382
49985
|
continue: continueCommand,
|
|
49383
49986
|
doctor: doctorCommand,
|
|
49384
49987
|
env: envCommand,
|
|
49385
|
-
|
|
49988
|
+
run: quireRunCommand,
|
|
49386
49989
|
runs: runsCommand,
|
|
49387
49990
|
setup: setupCommand,
|
|
49388
49991
|
status: statusCommand,
|
|
49389
49992
|
sync: syncCommand,
|
|
49390
49993
|
watch: watchCommand
|
|
49391
49994
|
};
|
|
49392
|
-
const subCommandAliases = new Set([
|
|
49393
|
-
"ask",
|
|
49394
|
-
"me",
|
|
49395
|
-
"resume"
|
|
49396
|
-
]);
|
|
49995
|
+
const subCommandAliases = new Set(["me", "resume"]);
|
|
49397
49996
|
const cli = defineCommand({
|
|
49398
49997
|
meta: {
|
|
49399
49998
|
name: "quire",
|
|
@@ -49427,7 +50026,7 @@ function isVersionRequest(rawArgs) {
|
|
|
49427
50026
|
function normalizeRawArgs(rawArgs) {
|
|
49428
50027
|
const first = rawArgs[0];
|
|
49429
50028
|
if (first === void 0 || isTopLevelFlag(first) || isSubCommandName(first)) return rawArgs;
|
|
49430
|
-
return ["
|
|
50029
|
+
return ["run", ...rawArgs];
|
|
49431
50030
|
}
|
|
49432
50031
|
function isTopLevelFlag(arg) {
|
|
49433
50032
|
return arg === "--help" || arg === "-h" || arg === "--version" || arg === "-v";
|