@trycadence/cli 0.1.8 → 0.1.10-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cadence +1261 -247
- package/package.json +1 -1
package/dist/cadence
CHANGED
|
@@ -1511,7 +1511,7 @@ function createCadenceClient(options = {}) {
|
|
|
1511
1511
|
}
|
|
1512
1512
|
|
|
1513
1513
|
// src/index.ts
|
|
1514
|
-
import { spawnSync } from "child_process";
|
|
1514
|
+
import { spawn, spawnSync } from "child_process";
|
|
1515
1515
|
import { createHash, randomUUID } from "crypto";
|
|
1516
1516
|
import { mkdir, readFile, rm, stat, writeFile } from "fs/promises";
|
|
1517
1517
|
import { basename, dirname, isAbsolute, join, parse } from "path";
|
|
@@ -1522,9 +1522,17 @@ var sessionFileChangeKinds = ["added", "modified", "deleted", "renamed", "unknow
|
|
|
1522
1522
|
var workLogEntryKinds = ["intent", "decision", "rationale", "action", "verification", "blocker", "correction", "note"];
|
|
1523
1523
|
var workLogParentSelectors = ["last", "ticket-last", "session-last", "last-decision", "last-correction", "last-action"];
|
|
1524
1524
|
var changesetPrNoteSources = ["agent", "human", "system"];
|
|
1525
|
+
var hookScopes = ["repo", "global", "both"];
|
|
1526
|
+
var agentEventSources = ["codex", "claude-code", "opencode", "openrouter", "unknown"];
|
|
1525
1527
|
var defaultLeaseTtlSeconds = 5 * 60 * 60;
|
|
1526
1528
|
var defaultCliApiBaseUrl = "https://cadenceapi.deploy.lvl8studios.com";
|
|
1527
1529
|
var defaultCliWebBaseUrl = "https://cadence.deploy.lvl8studios.com";
|
|
1530
|
+
var defaultCheckpointThresholdMin = 3;
|
|
1531
|
+
var defaultCheckpointThresholdMax = 5;
|
|
1532
|
+
var defaultCheckpointCooldownSeconds = 10 * 60;
|
|
1533
|
+
var defaultCheckpointWorkerTimeoutMs = 10 * 60 * 1000;
|
|
1534
|
+
var defaultHookCommand = "cadence agent-run ingest-stop --source codex --event stop";
|
|
1535
|
+
var agentLoopSuppressEnv = "CADENCE_AGENT_EVENT_SUPPRESS";
|
|
1528
1536
|
var credentialRefreshSkewMs = 60 * 1000;
|
|
1529
1537
|
var credentialRefreshLockTimeoutMs = 10 * 1000;
|
|
1530
1538
|
var credentialRefreshLockStaleMs = 60 * 1000;
|
|
@@ -1560,6 +1568,12 @@ var knownCommandPaths = [
|
|
|
1560
1568
|
["changesets", "notes", "get"],
|
|
1561
1569
|
["changesets", "notes", "put"],
|
|
1562
1570
|
["changesets", "notes", "apply"],
|
|
1571
|
+
["agent-run", "ingest-stop"],
|
|
1572
|
+
["agent-run", "closeout"],
|
|
1573
|
+
["agent-run", "sweep"],
|
|
1574
|
+
["agent-run", "doctor"],
|
|
1575
|
+
["hooks", "doctor"],
|
|
1576
|
+
["hooks", "install"],
|
|
1563
1577
|
["sessions", "start"],
|
|
1564
1578
|
["sessions", "end"],
|
|
1565
1579
|
["sessions", "files"],
|
|
@@ -1578,7 +1592,6 @@ var knownCommandPaths = [
|
|
|
1578
1592
|
["events", "list"],
|
|
1579
1593
|
["work", "overview"],
|
|
1580
1594
|
["projects", "list"],
|
|
1581
|
-
["projects", "use"],
|
|
1582
1595
|
["init"],
|
|
1583
1596
|
["status"],
|
|
1584
1597
|
["help"]
|
|
@@ -1827,39 +1840,6 @@ async function findRepoCadenceDirectory(cwd) {
|
|
|
1827
1840
|
current = parent;
|
|
1828
1841
|
}
|
|
1829
1842
|
}
|
|
1830
|
-
async function findRepoConfigDirectory(cwd) {
|
|
1831
|
-
let current = cwd;
|
|
1832
|
-
while (true) {
|
|
1833
|
-
const repoConfig = join(current, ".cadence", "config.json");
|
|
1834
|
-
if (await Bun.file(repoConfig).exists()) {
|
|
1835
|
-
return join(current, ".cadence");
|
|
1836
|
-
}
|
|
1837
|
-
const parent = dirname(current);
|
|
1838
|
-
if (parent === current) {
|
|
1839
|
-
return null;
|
|
1840
|
-
}
|
|
1841
|
-
current = parent;
|
|
1842
|
-
}
|
|
1843
|
-
}
|
|
1844
|
-
function resolveGitRootFromCommand(cwd) {
|
|
1845
|
-
const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
|
|
1846
|
-
cwd,
|
|
1847
|
-
encoding: "utf8"
|
|
1848
|
-
});
|
|
1849
|
-
if (result.status !== 0) {
|
|
1850
|
-
return null;
|
|
1851
|
-
}
|
|
1852
|
-
return result.stdout.trim() || null;
|
|
1853
|
-
}
|
|
1854
|
-
async function repoConfigPathForWrite(options) {
|
|
1855
|
-
const cwd = options.cwd ?? process.cwd();
|
|
1856
|
-
const existingConfigDirectory = await findRepoConfigDirectory(cwd);
|
|
1857
|
-
if (existingConfigDirectory) {
|
|
1858
|
-
return join(existingConfigDirectory, "config.json");
|
|
1859
|
-
}
|
|
1860
|
-
const gitRoot = options.resolveGitRoot ? await options.resolveGitRoot() : resolveGitRootFromCommand(cwd);
|
|
1861
|
-
return join(gitRoot ?? cwd, ".cadence", "config.json");
|
|
1862
|
-
}
|
|
1863
1843
|
function getConfigHome(env) {
|
|
1864
1844
|
return env.CADENCE_CONFIG_HOME ?? (env.HOME ? join(env.HOME, ".config", "cadence") : join(parse(process.cwd()).root, ".config", "cadence"));
|
|
1865
1845
|
}
|
|
@@ -1944,6 +1924,77 @@ function isInteractive(options) {
|
|
|
1944
1924
|
function getCliWebBaseUrl(config, parsed, options) {
|
|
1945
1925
|
return parsed.options["web-base-url"] ?? options.env?.CADENCE_WEB_BASE_URL ?? process.env.CADENCE_WEB_BASE_URL ?? config.webBaseUrl;
|
|
1946
1926
|
}
|
|
1927
|
+
function normalizeBaseUrl(value, label) {
|
|
1928
|
+
try {
|
|
1929
|
+
return new URL(value).toString().replace(/\/$/, "");
|
|
1930
|
+
} catch {
|
|
1931
|
+
throw new CliError("CLI_USAGE", `${label} must be a valid URL.`);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
function cliConfigDiscoveryUrl(webBaseUrl) {
|
|
1935
|
+
return new URL("/cli/config", webBaseUrl).toString();
|
|
1936
|
+
}
|
|
1937
|
+
function hasExplicitWebBaseUrl(parsed, options) {
|
|
1938
|
+
return Boolean(parsed.options["web-base-url"] ?? options.env?.CADENCE_WEB_BASE_URL ?? process.env.CADENCE_WEB_BASE_URL);
|
|
1939
|
+
}
|
|
1940
|
+
async function discoverApiBaseUrlFromWeb(webBaseUrl, options) {
|
|
1941
|
+
const configUrl = cliConfigDiscoveryUrl(webBaseUrl);
|
|
1942
|
+
const requestFetch = options.fetch ?? fetch;
|
|
1943
|
+
let response;
|
|
1944
|
+
try {
|
|
1945
|
+
response = await requestFetch(configUrl, {
|
|
1946
|
+
headers: {
|
|
1947
|
+
accept: "application/json"
|
|
1948
|
+
}
|
|
1949
|
+
});
|
|
1950
|
+
} catch (error) {
|
|
1951
|
+
throw new CliError("WEB_CONFIG_DISCOVERY_FAILED", "Could not discover Cadence API URL from the web app.", {
|
|
1952
|
+
webBaseUrl,
|
|
1953
|
+
configUrl,
|
|
1954
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
if (!response.ok) {
|
|
1958
|
+
throw new CliError("WEB_CONFIG_DISCOVERY_FAILED", "Could not discover Cadence API URL from the web app.", {
|
|
1959
|
+
webBaseUrl,
|
|
1960
|
+
configUrl,
|
|
1961
|
+
status: response.status
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
const parsed = await response.json();
|
|
1965
|
+
const apiBaseUrl = parsed && typeof parsed === "object" ? parsed.apiBaseUrl : undefined;
|
|
1966
|
+
if (typeof apiBaseUrl !== "string") {
|
|
1967
|
+
throw new CliError("WEB_CONFIG_DISCOVERY_FAILED", "Cadence web app returned invalid CLI configuration.", {
|
|
1968
|
+
webBaseUrl,
|
|
1969
|
+
configUrl
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
return normalizeBaseUrl(apiBaseUrl, "Discovered API base URL");
|
|
1973
|
+
}
|
|
1974
|
+
async function resolveAuthLoginConfig(config, parsed, options) {
|
|
1975
|
+
const webBaseUrl = normalizeBaseUrl(getCliWebBaseUrl(config, parsed, options), "--web-base-url");
|
|
1976
|
+
if (parsed.flags.server && !hasExplicitWebBaseUrl(parsed, options)) {
|
|
1977
|
+
return {
|
|
1978
|
+
...config,
|
|
1979
|
+
webBaseUrl
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
try {
|
|
1983
|
+
return {
|
|
1984
|
+
...config,
|
|
1985
|
+
server: await discoverApiBaseUrlFromWeb(webBaseUrl, options),
|
|
1986
|
+
webBaseUrl
|
|
1987
|
+
};
|
|
1988
|
+
} catch (error) {
|
|
1989
|
+
if (hasExplicitWebBaseUrl(parsed, options)) {
|
|
1990
|
+
throw error;
|
|
1991
|
+
}
|
|
1992
|
+
return {
|
|
1993
|
+
...config,
|
|
1994
|
+
webBaseUrl
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1947
1998
|
function deriveWebBaseUrl(server) {
|
|
1948
1999
|
try {
|
|
1949
2000
|
const url = new URL(server);
|
|
@@ -2151,118 +2202,6 @@ function formatJson(envelope) {
|
|
|
2151
2202
|
return `${JSON.stringify(envelope, null, 2)}
|
|
2152
2203
|
`;
|
|
2153
2204
|
}
|
|
2154
|
-
function isRecord(value) {
|
|
2155
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2156
|
-
}
|
|
2157
|
-
function stringField(value, field) {
|
|
2158
|
-
if (!isRecord(value)) {
|
|
2159
|
-
return null;
|
|
2160
|
-
}
|
|
2161
|
-
const fieldValue = value[field];
|
|
2162
|
-
return typeof fieldValue === "string" ? fieldValue : null;
|
|
2163
|
-
}
|
|
2164
|
-
function countLabel(value, singular, plural = `${singular}s`) {
|
|
2165
|
-
const count = Array.isArray(value) ? value.length : 0;
|
|
2166
|
-
return `${count} ${count === 1 ? singular : plural}`;
|
|
2167
|
-
}
|
|
2168
|
-
function humanCommandOutput(commandName, data) {
|
|
2169
|
-
switch (commandName) {
|
|
2170
|
-
case "auth.status":
|
|
2171
|
-
return [
|
|
2172
|
-
`Server: ${stringField(data, "server") ?? "unknown"}`,
|
|
2173
|
-
`Project: ${stringField(data, "projectId") ?? "not configured"}`,
|
|
2174
|
-
`Credential: ${isRecord(data) && data.credentialConfigured ? "configured" : "not configured"}`
|
|
2175
|
-
].join(`
|
|
2176
|
-
`) + `
|
|
2177
|
-
`;
|
|
2178
|
-
case "auth.logout":
|
|
2179
|
-
return `Cadence CLI credential removed.
|
|
2180
|
-
`;
|
|
2181
|
-
case "events.list":
|
|
2182
|
-
return `Found ${countLabel(data, "event")}.
|
|
2183
|
-
`;
|
|
2184
|
-
case "work.overview":
|
|
2185
|
-
return `Loaded work overview.
|
|
2186
|
-
`;
|
|
2187
|
-
case "tickets.get":
|
|
2188
|
-
return `Loaded ticket ${stringField(data, "id") ?? ""}`.trimEnd() + `.
|
|
2189
|
-
`;
|
|
2190
|
-
case "tickets.list":
|
|
2191
|
-
return `Found ${countLabel(data, "ticket")}.
|
|
2192
|
-
`;
|
|
2193
|
-
case "sessions.current":
|
|
2194
|
-
return `Loaded current session context.
|
|
2195
|
-
`;
|
|
2196
|
-
case "changesets.get":
|
|
2197
|
-
case "changesets.context":
|
|
2198
|
-
case "changesets.current":
|
|
2199
|
-
return `Loaded ChangeSet context.
|
|
2200
|
-
`;
|
|
2201
|
-
case "changesets.list":
|
|
2202
|
-
return `Found ${countLabel(data, "ChangeSet")}.
|
|
2203
|
-
`;
|
|
2204
|
-
case "changesets.notes.get":
|
|
2205
|
-
return `Loaded ChangeSet PR notes.
|
|
2206
|
-
`;
|
|
2207
|
-
case "projects.list":
|
|
2208
|
-
return `Found ${countLabel(data, "project")}.
|
|
2209
|
-
`;
|
|
2210
|
-
case "actors.ensure-workspace-agent": {
|
|
2211
|
-
const displayName = stringField(data, "displayName");
|
|
2212
|
-
const actorId = stringField(data, "actorId");
|
|
2213
|
-
return `Workspace agent ready${displayName ? `: ${displayName}` : ""}${actorId ? ` (${actorId})` : ""}.
|
|
2214
|
-
`;
|
|
2215
|
-
}
|
|
2216
|
-
case "intake":
|
|
2217
|
-
return `Created intake ${stringField(data, "id") ?? ""}`.trimEnd() + `.
|
|
2218
|
-
`;
|
|
2219
|
-
case "intake.dismiss":
|
|
2220
|
-
return `Dismissed intake.
|
|
2221
|
-
`;
|
|
2222
|
-
case "tickets.attach":
|
|
2223
|
-
return `Attached intake to ticket.
|
|
2224
|
-
`;
|
|
2225
|
-
case "tickets.create":
|
|
2226
|
-
return `Created ticket ${stringField(data, "id") ?? ""}`.trimEnd() + `.
|
|
2227
|
-
`;
|
|
2228
|
-
case "tickets.update":
|
|
2229
|
-
return `Updated ticket ${stringField(data, "id") ?? ""}`.trimEnd() + `.
|
|
2230
|
-
`;
|
|
2231
|
-
case "tickets.claim":
|
|
2232
|
-
return `Claimed ticket lease ${stringField(data, "id") ?? ""}`.trimEnd() + `.
|
|
2233
|
-
`;
|
|
2234
|
-
case "tickets.release":
|
|
2235
|
-
return `Released ticket lease ${stringField(data, "id") ?? ""}`.trimEnd() + `.
|
|
2236
|
-
`;
|
|
2237
|
-
case "tickets.log":
|
|
2238
|
-
return `Added ticket work-log entry.
|
|
2239
|
-
`;
|
|
2240
|
-
case "tickets.complete":
|
|
2241
|
-
return `Completed ticket ${stringField(data, "id") ?? ""}`.trimEnd() + `.
|
|
2242
|
-
`;
|
|
2243
|
-
case "sessions.start":
|
|
2244
|
-
return `Started session ${stringField(data, "id") ?? ""}`.trimEnd() + `.
|
|
2245
|
-
`;
|
|
2246
|
-
case "sessions.end":
|
|
2247
|
-
return `Ended session ${stringField(data, "id") ?? ""}`.trimEnd() + `.
|
|
2248
|
-
`;
|
|
2249
|
-
case "sessions.files":
|
|
2250
|
-
return `Recorded session files.
|
|
2251
|
-
`;
|
|
2252
|
-
case "changesets.create":
|
|
2253
|
-
return `Created ChangeSet ${stringField(data, "id") ?? ""}`.trimEnd() + `.
|
|
2254
|
-
`;
|
|
2255
|
-
case "changesets.notes.put":
|
|
2256
|
-
return `Saved ChangeSet PR notes.
|
|
2257
|
-
`;
|
|
2258
|
-
case "changesets.notes.apply":
|
|
2259
|
-
return `Marked ChangeSet PR notes applied.
|
|
2260
|
-
`;
|
|
2261
|
-
default:
|
|
2262
|
-
return `Done.
|
|
2263
|
-
`;
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
2205
|
function helpText() {
|
|
2267
2206
|
return [
|
|
2268
2207
|
"Cadence CLI",
|
|
@@ -2275,7 +2214,6 @@ function helpText() {
|
|
|
2275
2214
|
" cadence actors ensure-workspace-agent --agent-kind <kind> [--workspace-name <name>] [--workspace-ref <ref>] [--display-name <name>] [--project <project-id>] [--json]",
|
|
2276
2215
|
" cadence status [--project <project-id>] [--json]",
|
|
2277
2216
|
" cadence projects list [--json]",
|
|
2278
|
-
" cadence projects use <project-id|org/project> [--server <url>] [--json]",
|
|
2279
2217
|
" cadence work overview [--project <project-id>] [--json]",
|
|
2280
2218
|
" cadence tickets get <ticket-id> [--project <project-id>] [--json]",
|
|
2281
2219
|
" cadence tickets list [--project <project-id>] [--status <status>] [--json]",
|
|
@@ -2300,10 +2238,16 @@ function helpText() {
|
|
|
2300
2238
|
" cadence changesets notes get [--changeset <id>|--branch current|<branch>] [--project <project-id>] [--json]",
|
|
2301
2239
|
" cadence changesets notes put [--changeset <id>|--branch current|<branch>] --title <text> --body-file <path> [--head-sha <sha>] [--base-sha <sha>] [--pr-url <url>] [--pr-number <n>] [--project <project-id>] [--json]",
|
|
2302
2240
|
" cadence changesets notes apply [--changeset <id>|--branch current|<branch>] --provider github --pr-number <n> --pr-url <url> [--project <project-id>] [--json]",
|
|
2241
|
+
" cadence agent-run ingest-stop --source <codex|claude-code|opencode|openrouter> [--event <event>] [--threshold <n>] [--dry-run true|false] [--project <project-id>] [--json]",
|
|
2242
|
+
" cadence agent-run closeout --agent-session-key <key> [--reason <threshold|idle|manual>] [--event-file <path>] [--log-kind <kind>] [--update-summary true|false] [--json]",
|
|
2243
|
+
" cadence agent-run sweep [--idle-after-seconds <n>] [--dry-run true|false] [--json]",
|
|
2244
|
+
" cadence agent-run doctor [--json]",
|
|
2245
|
+
" cadence hooks install --provider codex --scope <repo|global|both> [--command <command>] [--json]",
|
|
2246
|
+
" cadence hooks doctor --provider codex --scope <repo|global|both> [--json]",
|
|
2303
2247
|
" cadence events list [--project <project-id>] [--ticket <ticket-id>] [--changeset <changeset-id>] [--session <session-id>] [--json]",
|
|
2304
2248
|
"",
|
|
2305
2249
|
"Global flags:",
|
|
2306
|
-
" --project <id
|
|
2250
|
+
" --project <id> Cadence project ID or org/project slug",
|
|
2307
2251
|
" --server <url> Cadence API server override",
|
|
2308
2252
|
" --json Print stable JSON envelope",
|
|
2309
2253
|
"",
|
|
@@ -2553,12 +2497,578 @@ async function changesetLookupFromOptions(parsed, options) {
|
|
|
2553
2497
|
const branchOption = parsed.options.branch ?? "current";
|
|
2554
2498
|
const branchName = branchOption === "current" ? await resolveCurrentBranch(options) : branchOption;
|
|
2555
2499
|
return {
|
|
2556
|
-
branchName
|
|
2500
|
+
branchName
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
async function readBodyFile(path, options) {
|
|
2504
|
+
const resolvedPath = isAbsolute(path) ? path : join(options.cwd ?? process.cwd(), path);
|
|
2505
|
+
return readFile(resolvedPath, "utf8");
|
|
2506
|
+
}
|
|
2507
|
+
function parseHookScope(value) {
|
|
2508
|
+
if (!value) {
|
|
2509
|
+
return "repo";
|
|
2510
|
+
}
|
|
2511
|
+
if (hookScopes.includes(value)) {
|
|
2512
|
+
return value;
|
|
2513
|
+
}
|
|
2514
|
+
throw new CliError("CLI_USAGE", "--scope must be one of repo, global, or both.");
|
|
2515
|
+
}
|
|
2516
|
+
function parseAgentEventSource(value) {
|
|
2517
|
+
if (!value) {
|
|
2518
|
+
return "unknown";
|
|
2519
|
+
}
|
|
2520
|
+
if (agentEventSources.includes(value)) {
|
|
2521
|
+
return value;
|
|
2522
|
+
}
|
|
2523
|
+
throw new CliError("CLI_USAGE", "--source must be one of codex, claude-code, opencode, openrouter, or unknown.");
|
|
2524
|
+
}
|
|
2525
|
+
function parseBooleanOption(value, defaultValue) {
|
|
2526
|
+
if (!value) {
|
|
2527
|
+
return defaultValue;
|
|
2528
|
+
}
|
|
2529
|
+
if (value === "true") {
|
|
2530
|
+
return true;
|
|
2531
|
+
}
|
|
2532
|
+
if (value === "false") {
|
|
2533
|
+
return false;
|
|
2534
|
+
}
|
|
2535
|
+
throw new CliError("CLI_USAGE", "Boolean options must be true or false.");
|
|
2536
|
+
}
|
|
2537
|
+
function truncateText(value, maxLength) {
|
|
2538
|
+
if (value.length <= maxLength) {
|
|
2539
|
+
return value;
|
|
2540
|
+
}
|
|
2541
|
+
return `${value.slice(0, maxLength - 15)}
|
|
2542
|
+
[truncated]`;
|
|
2543
|
+
}
|
|
2544
|
+
function stableHash(value) {
|
|
2545
|
+
return createHash("sha256").update(value).digest("hex");
|
|
2546
|
+
}
|
|
2547
|
+
async function readStdin(options) {
|
|
2548
|
+
if (options.readStdin) {
|
|
2549
|
+
return await options.readStdin();
|
|
2550
|
+
}
|
|
2551
|
+
return await Bun.stdin.text();
|
|
2552
|
+
}
|
|
2553
|
+
function tryParseJsonObject(source, label) {
|
|
2554
|
+
if (!source.trim()) {
|
|
2555
|
+
return {};
|
|
2556
|
+
}
|
|
2557
|
+
const parsed = JSON.parse(source);
|
|
2558
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2559
|
+
throw new CliError("CLI_USAGE", `${label} must be a JSON object.`);
|
|
2560
|
+
}
|
|
2561
|
+
return parsed;
|
|
2562
|
+
}
|
|
2563
|
+
function readNestedString(record, path) {
|
|
2564
|
+
let value = record;
|
|
2565
|
+
for (const key of path) {
|
|
2566
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
value = value[key];
|
|
2570
|
+
}
|
|
2571
|
+
return typeof value === "string" ? value : undefined;
|
|
2572
|
+
}
|
|
2573
|
+
function firstString(record, paths) {
|
|
2574
|
+
for (const path of paths) {
|
|
2575
|
+
const value = readNestedString(record, path);
|
|
2576
|
+
if (value?.trim()) {
|
|
2577
|
+
return value.trim();
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2582
|
+
function normalizeAgentEvent(input, parsed, options) {
|
|
2583
|
+
const source = parseAgentEventSource(parsed.options.source);
|
|
2584
|
+
const event = parsed.options.event ?? "stop";
|
|
2585
|
+
const base = normalizeAgentEventBase(input, source, event, options);
|
|
2586
|
+
switch (source) {
|
|
2587
|
+
case "codex":
|
|
2588
|
+
return normalizeCodexAgentEvent(input, base);
|
|
2589
|
+
case "claude-code":
|
|
2590
|
+
case "opencode":
|
|
2591
|
+
case "openrouter":
|
|
2592
|
+
case "unknown":
|
|
2593
|
+
return normalizeGenericAgentEvent(input, base);
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
function normalizeAgentEventBase(input, source, event, options) {
|
|
2597
|
+
const threadId = firstString(input, [["thread_id"], ["threadId"], ["conversation_id"], ["conversationId"]]);
|
|
2598
|
+
const turnId = firstString(input, [["turn_id"], ["turnId"], ["id"]]);
|
|
2599
|
+
const lastAssistantMessage = firstString(input, [
|
|
2600
|
+
["last_assistant_message"],
|
|
2601
|
+
["lastAssistantMessage"],
|
|
2602
|
+
["assistant_response"],
|
|
2603
|
+
["assistantResponse"],
|
|
2604
|
+
["message", "content"],
|
|
2605
|
+
["lastMessage", "content"]
|
|
2606
|
+
]);
|
|
2607
|
+
return {
|
|
2608
|
+
source,
|
|
2609
|
+
event,
|
|
2610
|
+
workspacePath: options.cwd ?? process.cwd(),
|
|
2611
|
+
occurredAt: new Date().toISOString(),
|
|
2612
|
+
...threadId ? { threadId } : {},
|
|
2613
|
+
...turnId ? { turnId } : {},
|
|
2614
|
+
...lastAssistantMessage ? { lastAssistantMessage: truncateText(lastAssistantMessage, 6000) } : {},
|
|
2615
|
+
payloadKeys: Object.keys(input).sort()
|
|
2616
|
+
};
|
|
2617
|
+
}
|
|
2618
|
+
function normalizeCodexAgentEvent(input, base) {
|
|
2619
|
+
const agentSessionId = firstString(input, [["session_id"], ["sessionId"], ["session", "id"]]);
|
|
2620
|
+
if (!agentSessionId) {
|
|
2621
|
+
return {
|
|
2622
|
+
...base,
|
|
2623
|
+
diagnosticReason: "missing_agent_session_id"
|
|
2624
|
+
};
|
|
2625
|
+
}
|
|
2626
|
+
return {
|
|
2627
|
+
...base,
|
|
2628
|
+
agentSessionId,
|
|
2629
|
+
agentSessionKey: agentSessionKey(base.source, agentSessionId)
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2632
|
+
function normalizeGenericAgentEvent(input, base) {
|
|
2633
|
+
const agentSessionId = firstString(input, [
|
|
2634
|
+
["agent_session_id"],
|
|
2635
|
+
["agentSessionId"],
|
|
2636
|
+
["session_id"],
|
|
2637
|
+
["sessionId"],
|
|
2638
|
+
["session", "id"]
|
|
2639
|
+
]);
|
|
2640
|
+
if (!agentSessionId) {
|
|
2641
|
+
return {
|
|
2642
|
+
...base,
|
|
2643
|
+
diagnosticReason: "missing_agent_session_id"
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
return {
|
|
2647
|
+
...base,
|
|
2648
|
+
agentSessionId,
|
|
2649
|
+
agentSessionKey: agentSessionKey(base.source, agentSessionId)
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
function agentSessionKey(source, agentSessionId) {
|
|
2653
|
+
return `${source}:${stableHash(agentSessionId)}`;
|
|
2654
|
+
}
|
|
2655
|
+
function defaultAgentLoopState() {
|
|
2656
|
+
return {
|
|
2657
|
+
version: 2,
|
|
2658
|
+
sessions: {}
|
|
2659
|
+
};
|
|
2660
|
+
}
|
|
2661
|
+
function randomCheckpointThreshold() {
|
|
2662
|
+
return defaultCheckpointThresholdMin + Math.floor(Math.random() * (defaultCheckpointThresholdMax - defaultCheckpointThresholdMin + 1));
|
|
2663
|
+
}
|
|
2664
|
+
function agentLoopDirectory(parsed, options) {
|
|
2665
|
+
return parsed.options["state-dir"] ? isAbsolute(parsed.options["state-dir"]) ? parsed.options["state-dir"] : join(options.cwd ?? process.cwd(), parsed.options["state-dir"]) : join(options.cwd ?? process.cwd(), ".context", "cadence-agent-loop");
|
|
2666
|
+
}
|
|
2667
|
+
function agentLoopStatePath(parsed, options) {
|
|
2668
|
+
return join(agentLoopDirectory(parsed, options), "state.json");
|
|
2669
|
+
}
|
|
2670
|
+
function agentLoopLockPath(parsed, options, agentSessionKeyValue) {
|
|
2671
|
+
return join(agentLoopDirectory(parsed, options), agentSessionKeyValue ? `closeout-${stableHash(agentSessionKeyValue)}.lock` : "closeout.lock");
|
|
2672
|
+
}
|
|
2673
|
+
async function readAgentLoopState(parsed, options) {
|
|
2674
|
+
const filePath = agentLoopStatePath(parsed, options);
|
|
2675
|
+
const file = Bun.file(filePath);
|
|
2676
|
+
if (!await file.exists()) {
|
|
2677
|
+
return defaultAgentLoopState();
|
|
2678
|
+
}
|
|
2679
|
+
const parsedState = JSON.parse(await file.text());
|
|
2680
|
+
if (!parsedState || typeof parsedState !== "object" || Array.isArray(parsedState)) {
|
|
2681
|
+
return defaultAgentLoopState();
|
|
2682
|
+
}
|
|
2683
|
+
const record = parsedState;
|
|
2684
|
+
if (record.version === 2) {
|
|
2685
|
+
return {
|
|
2686
|
+
version: 2,
|
|
2687
|
+
sessions: readAgentLoopSessions(record.sessions),
|
|
2688
|
+
...record.diagnostics && typeof record.diagnostics === "object" && !Array.isArray(record.diagnostics) ? { diagnostics: readAgentLoopDiagnostics(record.diagnostics) } : {}
|
|
2689
|
+
};
|
|
2690
|
+
}
|
|
2691
|
+
return {
|
|
2692
|
+
version: 2,
|
|
2693
|
+
sessions: {},
|
|
2694
|
+
diagnostics: {
|
|
2695
|
+
migratedLegacyRepoCounter: "stopCount" in record
|
|
2696
|
+
}
|
|
2697
|
+
};
|
|
2698
|
+
}
|
|
2699
|
+
async function writeAgentLoopState(parsed, options, state) {
|
|
2700
|
+
const filePath = agentLoopStatePath(parsed, options);
|
|
2701
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
2702
|
+
await writeFile(filePath, `${JSON.stringify(state, null, 2)}
|
|
2703
|
+
`);
|
|
2704
|
+
}
|
|
2705
|
+
function readAgentLoopSessions(rawSessions) {
|
|
2706
|
+
const sessions = {};
|
|
2707
|
+
if (!rawSessions || typeof rawSessions !== "object" || Array.isArray(rawSessions)) {
|
|
2708
|
+
return sessions;
|
|
2709
|
+
}
|
|
2710
|
+
for (const [key, rawSession] of Object.entries(rawSessions)) {
|
|
2711
|
+
if (!rawSession || typeof rawSession !== "object" || Array.isArray(rawSession)) {
|
|
2712
|
+
continue;
|
|
2713
|
+
}
|
|
2714
|
+
const record = rawSession;
|
|
2715
|
+
const source = parseAgentEventSource(typeof record.source === "string" ? record.source : undefined);
|
|
2716
|
+
const stopCount = typeof record.stopCount === "number" && Number.isInteger(record.stopCount) && record.stopCount >= 0 ? record.stopCount : 0;
|
|
2717
|
+
const threshold = typeof record.threshold === "number" && Number.isInteger(record.threshold) && record.threshold > 0 ? record.threshold : randomCheckpointThreshold();
|
|
2718
|
+
sessions[key] = {
|
|
2719
|
+
source,
|
|
2720
|
+
stopCount,
|
|
2721
|
+
threshold,
|
|
2722
|
+
...typeof record.firstObservedAt === "string" ? { firstObservedAt: record.firstObservedAt } : {},
|
|
2723
|
+
...typeof record.lastObservedAt === "string" ? { lastObservedAt: record.lastObservedAt } : {},
|
|
2724
|
+
...typeof record.lastAction === "string" ? { lastAction: record.lastAction } : {},
|
|
2725
|
+
...typeof record.lastReason === "string" ? { lastReason: record.lastReason } : {},
|
|
2726
|
+
...typeof record.lastEventFile === "string" ? { lastEventFile: record.lastEventFile } : {},
|
|
2727
|
+
...typeof record.lastAssistantMessage === "string" ? { lastAssistantMessage: record.lastAssistantMessage } : {},
|
|
2728
|
+
...typeof record.lastCheckpointAt === "string" ? { lastCheckpointAt: record.lastCheckpointAt } : {},
|
|
2729
|
+
...typeof record.lastCheckpointFingerprint === "string" ? { lastCheckpointFingerprint: record.lastCheckpointFingerprint } : {},
|
|
2730
|
+
...typeof record.previousCheckpointSummary === "string" ? { previousCheckpointSummary: record.previousCheckpointSummary } : {}
|
|
2731
|
+
};
|
|
2732
|
+
}
|
|
2733
|
+
return sessions;
|
|
2734
|
+
}
|
|
2735
|
+
function readAgentLoopDiagnostics(record) {
|
|
2736
|
+
return {
|
|
2737
|
+
...typeof record.missingSessionIdCount === "number" && Number.isInteger(record.missingSessionIdCount) && record.missingSessionIdCount > 0 ? { missingSessionIdCount: record.missingSessionIdCount } : {},
|
|
2738
|
+
...typeof record.lastMissingSessionIdAt === "string" ? { lastMissingSessionIdAt: record.lastMissingSessionIdAt } : {},
|
|
2739
|
+
...typeof record.lastMissingSessionIdSource === "string" ? { lastMissingSessionIdSource: parseAgentEventSource(record.lastMissingSessionIdSource) } : {},
|
|
2740
|
+
...Array.isArray(record.lastMissingSessionIdPayloadKeys) ? { lastMissingSessionIdPayloadKeys: record.lastMissingSessionIdPayloadKeys.filter((key) => typeof key === "string") } : {},
|
|
2741
|
+
...typeof record.migratedLegacyRepoCounter === "boolean" ? { migratedLegacyRepoCounter: record.migratedLegacyRepoCounter } : {}
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
function recordMissingAgentSessionId(state, event) {
|
|
2745
|
+
return {
|
|
2746
|
+
...state,
|
|
2747
|
+
diagnostics: {
|
|
2748
|
+
...state.diagnostics ?? {},
|
|
2749
|
+
missingSessionIdCount: (state.diagnostics?.missingSessionIdCount ?? 0) + 1,
|
|
2750
|
+
lastMissingSessionIdAt: new Date().toISOString(),
|
|
2751
|
+
lastMissingSessionIdSource: event.source,
|
|
2752
|
+
lastMissingSessionIdPayloadKeys: event.payloadKeys
|
|
2753
|
+
}
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
function clearAgentSessionReason(state) {
|
|
2757
|
+
const { lastReason: _lastReason, ...stateWithoutReason } = state;
|
|
2758
|
+
return stateWithoutReason;
|
|
2759
|
+
}
|
|
2760
|
+
async function writeAgentEventFile(parsed, options, event) {
|
|
2761
|
+
const eventsDirectory = join(agentLoopDirectory(parsed, options), "events");
|
|
2762
|
+
const fileName = `${new Date().toISOString().replace(/[:.]/g, "-")}-${randomUUID()}.json`;
|
|
2763
|
+
const filePath = join(eventsDirectory, fileName);
|
|
2764
|
+
await mkdir(eventsDirectory, { recursive: true });
|
|
2765
|
+
await writeFile(filePath, `${JSON.stringify(event, null, 2)}
|
|
2766
|
+
`);
|
|
2767
|
+
return filePath;
|
|
2768
|
+
}
|
|
2769
|
+
async function removeStaleAgentLoopLock(lockPath) {
|
|
2770
|
+
try {
|
|
2771
|
+
const lockStats = await stat(lockPath);
|
|
2772
|
+
if (Date.now() - lockStats.mtimeMs < defaultCheckpointWorkerTimeoutMs) {
|
|
2773
|
+
return false;
|
|
2774
|
+
}
|
|
2775
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
2776
|
+
return true;
|
|
2777
|
+
} catch (error) {
|
|
2778
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
2779
|
+
return false;
|
|
2780
|
+
}
|
|
2781
|
+
throw error;
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
async function acquireAgentLoopLock(parsed, options, agentSessionKeyValue) {
|
|
2785
|
+
const lockPath = agentLoopLockPath(parsed, options, agentSessionKeyValue);
|
|
2786
|
+
await mkdir(dirname(lockPath), { recursive: true });
|
|
2787
|
+
try {
|
|
2788
|
+
await mkdir(lockPath);
|
|
2789
|
+
await writeFile(join(lockPath, "holder.json"), `${JSON.stringify({ pid: process.pid, acquiredAt: new Date().toISOString() }, null, 2)}
|
|
2790
|
+
`);
|
|
2791
|
+
return { acquired: true, lockPath };
|
|
2792
|
+
} catch (error) {
|
|
2793
|
+
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
|
|
2794
|
+
throw error;
|
|
2795
|
+
}
|
|
2796
|
+
if (await removeStaleAgentLoopLock(lockPath)) {
|
|
2797
|
+
await mkdir(lockPath);
|
|
2798
|
+
await writeFile(join(lockPath, "holder.json"), `${JSON.stringify({ pid: process.pid, acquiredAt: new Date().toISOString(), recoveredStale: true }, null, 2)}
|
|
2799
|
+
`);
|
|
2800
|
+
return { acquired: true, lockPath };
|
|
2801
|
+
}
|
|
2802
|
+
return { acquired: false, lockPath };
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
async function releaseAgentLoopLock(lockPath) {
|
|
2806
|
+
if (lockPath) {
|
|
2807
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
function runLocalCommand(command, args, options, commandOptions = {}) {
|
|
2811
|
+
if (options.runCommand) {
|
|
2812
|
+
return options.runCommand(command, args, commandOptions);
|
|
2813
|
+
}
|
|
2814
|
+
const result = spawnSync(command, [...args], {
|
|
2815
|
+
cwd: commandOptions.cwd,
|
|
2816
|
+
env: {
|
|
2817
|
+
...process.env,
|
|
2818
|
+
...commandOptions.env ?? {}
|
|
2819
|
+
},
|
|
2820
|
+
encoding: "utf8",
|
|
2821
|
+
timeout: commandOptions.timeoutMs,
|
|
2822
|
+
maxBuffer: 1024 * 1024
|
|
2823
|
+
});
|
|
2824
|
+
return {
|
|
2825
|
+
status: result.status,
|
|
2826
|
+
stdout: result.stdout ?? "",
|
|
2827
|
+
stderr: result.stderr ?? "",
|
|
2828
|
+
...result.error ? { error: result.error } : {}
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2831
|
+
function gitOutput(args, options) {
|
|
2832
|
+
const result = runLocalCommand("git", args, options, {
|
|
2833
|
+
cwd: options.cwd ?? process.cwd(),
|
|
2834
|
+
timeoutMs: 1e4
|
|
2835
|
+
});
|
|
2836
|
+
if (result.status !== 0) {
|
|
2837
|
+
return "";
|
|
2838
|
+
}
|
|
2839
|
+
return result.stdout.trim();
|
|
2840
|
+
}
|
|
2841
|
+
function currentCliWorkerInvocation() {
|
|
2842
|
+
const scriptPath = Bun.argv[1];
|
|
2843
|
+
if (scriptPath) {
|
|
2844
|
+
return {
|
|
2845
|
+
command: process.execPath,
|
|
2846
|
+
argsPrefix: [scriptPath]
|
|
2847
|
+
};
|
|
2848
|
+
}
|
|
2849
|
+
return {
|
|
2850
|
+
command: "cadence",
|
|
2851
|
+
argsPrefix: []
|
|
2852
|
+
};
|
|
2853
|
+
}
|
|
2854
|
+
async function spawnAgentRunCloseout(args, options) {
|
|
2855
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2856
|
+
const invocation = currentCliWorkerInvocation();
|
|
2857
|
+
const env = {
|
|
2858
|
+
...process.env,
|
|
2859
|
+
[agentLoopSuppressEnv]: "1"
|
|
2860
|
+
};
|
|
2861
|
+
if (options.spawnDetached) {
|
|
2862
|
+
await options.spawnDetached(invocation.command, [...invocation.argsPrefix, ...args], { cwd, env });
|
|
2863
|
+
return;
|
|
2864
|
+
}
|
|
2865
|
+
const child = spawn(invocation.command, [...invocation.argsPrefix, ...args], {
|
|
2866
|
+
cwd,
|
|
2867
|
+
env,
|
|
2868
|
+
detached: true,
|
|
2869
|
+
stdio: "ignore"
|
|
2870
|
+
});
|
|
2871
|
+
child.unref();
|
|
2872
|
+
}
|
|
2873
|
+
function activeSessionFromCurrent(current) {
|
|
2874
|
+
const record = current && typeof current === "object" ? current : null;
|
|
2875
|
+
const sessions = Array.isArray(record?.sessions) ? record.sessions : [];
|
|
2876
|
+
const activeSession = sessions.find((session) => {
|
|
2877
|
+
if (!session || typeof session !== "object") {
|
|
2878
|
+
return false;
|
|
2879
|
+
}
|
|
2880
|
+
const sessionRecord2 = session;
|
|
2881
|
+
return sessionRecord2.status === "active" && typeof sessionRecord2.ticketId === "string";
|
|
2882
|
+
});
|
|
2883
|
+
if (activeSession && typeof activeSession === "object") {
|
|
2884
|
+
return activeSession;
|
|
2885
|
+
}
|
|
2886
|
+
const activeLeases = Array.isArray(record?.activeLeases) ? record.activeLeases : Array.isArray(record?.leases) ? record.leases.filter((lease) => lease && typeof lease === "object" && lease.status === "active") : [];
|
|
2887
|
+
const activeLease = activeLeases.find((lease) => {
|
|
2888
|
+
if (!lease || typeof lease !== "object") {
|
|
2889
|
+
return false;
|
|
2890
|
+
}
|
|
2891
|
+
const leaseRecord2 = lease;
|
|
2892
|
+
return typeof leaseRecord2.ticketId === "string";
|
|
2893
|
+
});
|
|
2894
|
+
if (!activeLease || typeof activeLease !== "object") {
|
|
2895
|
+
return null;
|
|
2896
|
+
}
|
|
2897
|
+
const leaseRecord = activeLease;
|
|
2898
|
+
const sessionId = typeof leaseRecord.sessionId === "string" ? leaseRecord.sessionId : undefined;
|
|
2899
|
+
const matchingSession = sessions.find((session) => {
|
|
2900
|
+
if (!session || typeof session !== "object") {
|
|
2901
|
+
return false;
|
|
2902
|
+
}
|
|
2903
|
+
return sessionId && session.id === sessionId;
|
|
2904
|
+
});
|
|
2905
|
+
const sessionRecord = matchingSession && typeof matchingSession === "object" ? matchingSession : {};
|
|
2906
|
+
return {
|
|
2907
|
+
...sessionRecord,
|
|
2908
|
+
...sessionId ? { id: sessionId } : {},
|
|
2909
|
+
ticketId: leaseRecord.ticketId,
|
|
2910
|
+
...typeof leaseRecord.changesetId === "string" ? { changesetId: leaseRecord.changesetId } : typeof sessionRecord.changesetId === "string" ? { changesetId: sessionRecord.changesetId } : {},
|
|
2911
|
+
status: "active"
|
|
2912
|
+
};
|
|
2913
|
+
}
|
|
2914
|
+
function fingerprintForCheckpoint(event, options) {
|
|
2915
|
+
const status = gitOutput(["status", "--short"], options);
|
|
2916
|
+
const changedFiles = gitOutput(["diff", "--name-only", "origin/dev..."], options);
|
|
2917
|
+
return stableHash(JSON.stringify({
|
|
2918
|
+
source: event.source,
|
|
2919
|
+
event: event.event,
|
|
2920
|
+
lastAssistantMessage: event.lastAssistantMessage ?? "",
|
|
2921
|
+
status,
|
|
2922
|
+
changedFiles
|
|
2923
|
+
}));
|
|
2924
|
+
}
|
|
2925
|
+
function shouldSkipForCooldown(state, cooldownSeconds) {
|
|
2926
|
+
if (!state.lastCheckpointAt) {
|
|
2927
|
+
return false;
|
|
2928
|
+
}
|
|
2929
|
+
const lastCheckpointMs = new Date(state.lastCheckpointAt).getTime();
|
|
2930
|
+
return !Number.isNaN(lastCheckpointMs) && Date.now() - lastCheckpointMs < cooldownSeconds * 1000;
|
|
2931
|
+
}
|
|
2932
|
+
function synthesizeAgentEventFromSession(agentSessionKeyValue, session, options) {
|
|
2933
|
+
return {
|
|
2934
|
+
source: session.source,
|
|
2935
|
+
event: "closeout",
|
|
2936
|
+
workspacePath: options.cwd ?? process.cwd(),
|
|
2937
|
+
occurredAt: new Date().toISOString(),
|
|
2938
|
+
agentSessionKey: agentSessionKeyValue,
|
|
2939
|
+
...session.lastAssistantMessage ? { lastAssistantMessage: session.lastAssistantMessage } : {},
|
|
2940
|
+
payloadKeys: []
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
function parseCheckpointJson(raw) {
|
|
2944
|
+
const trimmed = raw.trim();
|
|
2945
|
+
try {
|
|
2946
|
+
const parsed = JSON.parse(trimmed);
|
|
2947
|
+
if (parsed && typeof parsed === "object") {
|
|
2948
|
+
const record = parsed;
|
|
2949
|
+
return {
|
|
2950
|
+
body: typeof record.body === "string" && record.body.trim() ? record.body.trim() : trimmed,
|
|
2951
|
+
summary: typeof record.summary === "string" && record.summary.trim() ? record.summary.trim() : undefined
|
|
2952
|
+
};
|
|
2953
|
+
}
|
|
2954
|
+
} catch {
|
|
2955
|
+
const start = trimmed.indexOf("{");
|
|
2956
|
+
const end = trimmed.lastIndexOf("}");
|
|
2957
|
+
if (start >= 0 && end > start) {
|
|
2958
|
+
try {
|
|
2959
|
+
const parsed = JSON.parse(trimmed.slice(start, end + 1));
|
|
2960
|
+
if (parsed && typeof parsed === "object") {
|
|
2961
|
+
const record = parsed;
|
|
2962
|
+
return {
|
|
2963
|
+
body: typeof record.body === "string" && record.body.trim() ? record.body.trim() : trimmed,
|
|
2964
|
+
summary: typeof record.summary === "string" && record.summary.trim() ? record.summary.trim() : undefined
|
|
2965
|
+
};
|
|
2966
|
+
}
|
|
2967
|
+
} catch {}
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
return {
|
|
2971
|
+
body: trimmed,
|
|
2972
|
+
summary: undefined
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
function buildCheckpointPrompt(input) {
|
|
2976
|
+
return [
|
|
2977
|
+
"You are generating a concise Cadence checkpoint for an active coding session.",
|
|
2978
|
+
"Do not include raw diffs, raw transcripts, terminal logs, tool streams, secrets, or model reasoning.",
|
|
2979
|
+
'Return JSON only with this shape: {"summary":"one short current work summary","body":"checkpoint body suitable for a Cadence ticket work log"}.',
|
|
2980
|
+
"The body should mention what changed, decisions made, verification if known, and the next risk or next step. Keep it under 1200 characters.",
|
|
2981
|
+
"",
|
|
2982
|
+
`Ticket: ${input.ticketId}`,
|
|
2983
|
+
input.sessionId ? `Session: ${input.sessionId}` : "",
|
|
2984
|
+
input.changesetId ? `ChangeSet: ${input.changesetId}` : "",
|
|
2985
|
+
`Agent event: ${input.event.source}/${input.event.event}`,
|
|
2986
|
+
input.previousCheckpointSummary ? `Previous checkpoint summary: ${input.previousCheckpointSummary}` : "",
|
|
2987
|
+
input.event.lastAssistantMessage ? `Last assistant message:
|
|
2988
|
+
${truncateText(input.event.lastAssistantMessage, 3000)}` : "Last assistant message: unavailable",
|
|
2989
|
+
input.gitStatus ? `Git status --short:
|
|
2990
|
+
${truncateText(input.gitStatus, 2000)}` : "Git status --short: clean or unavailable",
|
|
2991
|
+
input.gitDiffStat ? `Git diff --stat origin/dev...:
|
|
2992
|
+
${truncateText(input.gitDiffStat, 2000)}` : "Git diff stat: unavailable",
|
|
2993
|
+
input.changedFiles ? `Changed files:
|
|
2994
|
+
${truncateText(input.changedFiles, 2000)}` : "Changed files: unavailable"
|
|
2995
|
+
].filter(Boolean).join(`
|
|
2996
|
+
`);
|
|
2997
|
+
}
|
|
2998
|
+
async function findRepoRoot(cwd) {
|
|
2999
|
+
const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
|
|
3000
|
+
cwd,
|
|
3001
|
+
encoding: "utf8"
|
|
3002
|
+
});
|
|
3003
|
+
if (result.status === 0 && result.stdout.trim()) {
|
|
3004
|
+
return result.stdout.trim();
|
|
3005
|
+
}
|
|
3006
|
+
const cadenceDirectory = await findRepoCadenceDirectory(cwd);
|
|
3007
|
+
return cadenceDirectory ? dirname(cadenceDirectory) : cwd;
|
|
3008
|
+
}
|
|
3009
|
+
function codexHookEntry(command) {
|
|
3010
|
+
return {
|
|
3011
|
+
hooks: [
|
|
3012
|
+
{
|
|
3013
|
+
type: "command",
|
|
3014
|
+
command,
|
|
3015
|
+
timeout: 5,
|
|
3016
|
+
statusMessage: "Checking Cadence checkpoint"
|
|
3017
|
+
}
|
|
3018
|
+
]
|
|
3019
|
+
};
|
|
3020
|
+
}
|
|
3021
|
+
async function readJsonObjectFile(filePath) {
|
|
3022
|
+
const file = Bun.file(filePath);
|
|
3023
|
+
if (!await file.exists()) {
|
|
3024
|
+
return {};
|
|
3025
|
+
}
|
|
3026
|
+
return tryParseJsonObject(await file.text(), filePath);
|
|
3027
|
+
}
|
|
3028
|
+
async function installCodexHookFile(filePath, command) {
|
|
3029
|
+
const existing = await readJsonObjectFile(filePath);
|
|
3030
|
+
const hooksValue = existing.hooks;
|
|
3031
|
+
const hooks = hooksValue && typeof hooksValue === "object" && !Array.isArray(hooksValue) ? hooksValue : {};
|
|
3032
|
+
const stopValue = hooks.Stop;
|
|
3033
|
+
const stopHooks = Array.isArray(stopValue) ? stopValue : [];
|
|
3034
|
+
const alreadyInstalled = stopHooks.some((entry) => JSON.stringify(entry).includes(command));
|
|
3035
|
+
const nextStopHooks = alreadyInstalled ? stopHooks : [...stopHooks, codexHookEntry(command)];
|
|
3036
|
+
const nextConfig = {
|
|
3037
|
+
...existing,
|
|
3038
|
+
hooks: {
|
|
3039
|
+
...hooks,
|
|
3040
|
+
Stop: nextStopHooks
|
|
3041
|
+
}
|
|
3042
|
+
};
|
|
3043
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
3044
|
+
await writeFile(filePath, `${JSON.stringify(nextConfig, null, 2)}
|
|
3045
|
+
`);
|
|
3046
|
+
return {
|
|
3047
|
+
path: filePath,
|
|
3048
|
+
installed: !alreadyInstalled,
|
|
3049
|
+
alreadyInstalled
|
|
2557
3050
|
};
|
|
2558
3051
|
}
|
|
2559
|
-
async function
|
|
2560
|
-
const
|
|
2561
|
-
|
|
3052
|
+
async function codexHookPaths(scope, options) {
|
|
3053
|
+
const cwd = options.cwd ?? process.cwd();
|
|
3054
|
+
const paths = [];
|
|
3055
|
+
if (scope === "repo" || scope === "both") {
|
|
3056
|
+
paths.push(join(await findRepoRoot(cwd), ".codex", "hooks.json"));
|
|
3057
|
+
}
|
|
3058
|
+
if (scope === "global" || scope === "both") {
|
|
3059
|
+
const home = options.env?.HOME ?? process.env.HOME;
|
|
3060
|
+
if (!home) {
|
|
3061
|
+
throw new CliError("CONFIG_ERROR", "HOME is required for global hook installation.");
|
|
3062
|
+
}
|
|
3063
|
+
paths.push(join(home, ".codex", "hooks.json"));
|
|
3064
|
+
}
|
|
3065
|
+
return paths;
|
|
3066
|
+
}
|
|
3067
|
+
async function codexHookInstalled(filePath, command) {
|
|
3068
|
+
const existing = await readJsonObjectFile(filePath);
|
|
3069
|
+
const hooks = existing.hooks;
|
|
3070
|
+
const stopHooks = hooks && typeof hooks === "object" && !Array.isArray(hooks) && Array.isArray(hooks.Stop) ? hooks.Stop : [];
|
|
3071
|
+
return stopHooks.some((entry) => JSON.stringify(entry).includes(command));
|
|
2562
3072
|
}
|
|
2563
3073
|
async function runStatus(parsed, options) {
|
|
2564
3074
|
const config = await resolveCliConfig(parsed.flags, options);
|
|
@@ -2589,21 +3099,8 @@ async function runStatus(parsed, options) {
|
|
|
2589
3099
|
exitCode: 0
|
|
2590
3100
|
};
|
|
2591
3101
|
}
|
|
2592
|
-
const projectLabel = config.projectId ?? "not configured";
|
|
2593
|
-
const profileLabel = config.profile ?? "default";
|
|
2594
|
-
const credentialLabel = credential ? "configured" : "not configured";
|
|
2595
3102
|
return {
|
|
2596
|
-
stdout:
|
|
2597
|
-
`Cadence API: ${health.ok ? "ok" : "unavailable"} at ${config.server}`,
|
|
2598
|
-
`Cadence web: ${webBaseUrl}`,
|
|
2599
|
-
`Profile: ${profileLabel}`,
|
|
2600
|
-
`Project: ${projectLabel}`,
|
|
2601
|
-
`Credential: ${credentialLabel}`,
|
|
2602
|
-
`Repo config: ${config.repoConfigPath ?? "not found"}`,
|
|
2603
|
-
`Local config: ${config.localConfigPath ?? "not found"}`,
|
|
2604
|
-
`Global config: ${config.globalConfigPath}`
|
|
2605
|
-
].join(`
|
|
2606
|
-
`) + `
|
|
3103
|
+
stdout: `Cadence API ${health.ok ? "ok" : "unavailable"} at ${config.server}
|
|
2607
3104
|
`,
|
|
2608
3105
|
stderr: "",
|
|
2609
3106
|
exitCode: 0
|
|
@@ -2611,7 +3108,7 @@ async function runStatus(parsed, options) {
|
|
|
2611
3108
|
}
|
|
2612
3109
|
async function runInit(parsed, options) {
|
|
2613
3110
|
const cwd = options.cwd ?? process.cwd();
|
|
2614
|
-
const repoConfigPath =
|
|
3111
|
+
const repoConfigPath = join(cwd, ".cadence", "config.json");
|
|
2615
3112
|
const config = await resolveCliConfig(parsed.flags, {
|
|
2616
3113
|
...options,
|
|
2617
3114
|
cwd
|
|
@@ -2669,15 +3166,6 @@ async function resolveProjectReference(projectReference, client) {
|
|
|
2669
3166
|
projectSlug
|
|
2670
3167
|
});
|
|
2671
3168
|
}
|
|
2672
|
-
async function resolveRequiredProject(config, client) {
|
|
2673
|
-
return await resolveProjectReference(requireProjectId(config), client);
|
|
2674
|
-
}
|
|
2675
|
-
function configWithProject(config, project) {
|
|
2676
|
-
return {
|
|
2677
|
-
...config,
|
|
2678
|
-
projectId: project.id
|
|
2679
|
-
};
|
|
2680
|
-
}
|
|
2681
3169
|
function formatProjectChoice(project, index) {
|
|
2682
3170
|
const slug = project.orgSlug && project.projectSlug ? `${project.orgSlug}/${project.projectSlug}` : project.id;
|
|
2683
3171
|
const name = project.name ? ` ${project.name}` : "";
|
|
@@ -2720,17 +3208,579 @@ async function selectProject(parsed, client, options) {
|
|
|
2720
3208
|
}
|
|
2721
3209
|
return matching;
|
|
2722
3210
|
}
|
|
3211
|
+
async function runAgentRunCommand(parsed, options) {
|
|
3212
|
+
const config = await resolveCliConfig(parsed.flags, options);
|
|
3213
|
+
const projectId = config.projectId;
|
|
3214
|
+
const meta = commandMeta(parsed, config);
|
|
3215
|
+
switch (parsed.command.name) {
|
|
3216
|
+
case "agent-run.ingest-stop":
|
|
3217
|
+
return await runAgentRunIngestStop(parsed, options, config, meta);
|
|
3218
|
+
case "agent-run.closeout":
|
|
3219
|
+
return await runAgentRunCloseout(parsed, options, config, meta);
|
|
3220
|
+
case "agent-run.sweep":
|
|
3221
|
+
return await runAgentRunSweep(parsed, options, meta);
|
|
3222
|
+
case "agent-run.doctor": {
|
|
3223
|
+
const state = await readAgentLoopState(parsed, options);
|
|
3224
|
+
const data = {
|
|
3225
|
+
action: "doctor",
|
|
3226
|
+
state,
|
|
3227
|
+
sessionCount: Object.keys(state.sessions).length,
|
|
3228
|
+
pendingSessions: Object.entries(state.sessions).filter(([, session]) => session.stopCount > 0).map(([agentSessionKeyValue, session]) => ({
|
|
3229
|
+
agentSessionKey: agentSessionKeyValue,
|
|
3230
|
+
source: session.source,
|
|
3231
|
+
stopCount: session.stopCount,
|
|
3232
|
+
threshold: session.threshold,
|
|
3233
|
+
lastObservedAt: session.lastObservedAt,
|
|
3234
|
+
lastAction: session.lastAction
|
|
3235
|
+
}))
|
|
3236
|
+
};
|
|
3237
|
+
return {
|
|
3238
|
+
stdout: parsed.flags.json ? formatJson(successEnvelope(data, meta)) : `${JSON.stringify(data, null, 2)}
|
|
3239
|
+
`,
|
|
3240
|
+
stderr: "",
|
|
3241
|
+
exitCode: 0
|
|
3242
|
+
};
|
|
3243
|
+
}
|
|
3244
|
+
default:
|
|
3245
|
+
throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
async function runAgentRunIngestStop(parsed, options, config, meta) {
|
|
3249
|
+
const suppressed = options.env?.[agentLoopSuppressEnv] === "1" || options.env?.CADENCE_HOOK_SUPPRESS === "1" || options.env?.CADENCE_STOP_HOOK_SUPPRESS === "1" || process.env[agentLoopSuppressEnv] === "1" || process.env.CADENCE_HOOK_SUPPRESS === "1" || process.env.CADENCE_STOP_HOOK_SUPPRESS === "1";
|
|
3250
|
+
if (suppressed) {
|
|
3251
|
+
const data2 = {
|
|
3252
|
+
action: "skipped",
|
|
3253
|
+
reason: "suppressed"
|
|
3254
|
+
};
|
|
3255
|
+
return {
|
|
3256
|
+
stdout: parsed.flags.json ? formatJson(successEnvelope(data2, meta)) : `${JSON.stringify(data2, null, 2)}
|
|
3257
|
+
`,
|
|
3258
|
+
stderr: "",
|
|
3259
|
+
exitCode: 0
|
|
3260
|
+
};
|
|
3261
|
+
}
|
|
3262
|
+
const rawInput = await readStdin(options);
|
|
3263
|
+
const normalized = normalizeAgentEvent(tryParseJsonObject(rawInput, "agent event input"), parsed, options);
|
|
3264
|
+
const projectId = config.projectId;
|
|
3265
|
+
if (!projectId) {
|
|
3266
|
+
const data2 = {
|
|
3267
|
+
action: "skipped",
|
|
3268
|
+
reason: "no_project",
|
|
3269
|
+
event: normalized
|
|
3270
|
+
};
|
|
3271
|
+
return {
|
|
3272
|
+
stdout: parsed.flags.json ? formatJson(successEnvelope(data2, meta)) : `${JSON.stringify(data2, null, 2)}
|
|
3273
|
+
`,
|
|
3274
|
+
stderr: "",
|
|
3275
|
+
exitCode: 0
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3278
|
+
const state = await readAgentLoopState(parsed, options);
|
|
3279
|
+
if (!normalized.agentSessionKey) {
|
|
3280
|
+
const nextState = recordMissingAgentSessionId(state, normalized);
|
|
3281
|
+
await writeAgentLoopState(parsed, options, nextState);
|
|
3282
|
+
const data2 = {
|
|
3283
|
+
action: "skipped",
|
|
3284
|
+
reason: normalized.diagnosticReason ?? "missing_agent_session_id",
|
|
3285
|
+
event: normalized,
|
|
3286
|
+
diagnostics: nextState.diagnostics
|
|
3287
|
+
};
|
|
3288
|
+
return {
|
|
3289
|
+
stdout: parsed.flags.json ? formatJson(successEnvelope(data2, meta)) : `${JSON.stringify(data2, null, 2)}
|
|
3290
|
+
`,
|
|
3291
|
+
stderr: "",
|
|
3292
|
+
exitCode: 0
|
|
3293
|
+
};
|
|
3294
|
+
}
|
|
3295
|
+
const forcedThreshold = parsePositiveInteger(parsed.options.threshold, "--threshold");
|
|
3296
|
+
const cooldownSeconds = parsePositiveInteger(parsed.options["cooldown-seconds"], "--cooldown-seconds") ?? defaultCheckpointCooldownSeconds;
|
|
3297
|
+
const dryRun = parseBooleanOption(parsed.options["dry-run"], false);
|
|
3298
|
+
const existingSession = state.sessions[normalized.agentSessionKey];
|
|
3299
|
+
const now = new Date().toISOString();
|
|
3300
|
+
const threshold = forcedThreshold ?? existingSession?.threshold ?? randomCheckpointThreshold();
|
|
3301
|
+
const nextCount = (existingSession?.stopCount ?? 0) + 1;
|
|
3302
|
+
const observedSession = {
|
|
3303
|
+
...clearAgentSessionReason(existingSession ?? {
|
|
3304
|
+
source: normalized.source,
|
|
3305
|
+
stopCount: 0,
|
|
3306
|
+
threshold
|
|
3307
|
+
}),
|
|
3308
|
+
source: normalized.source,
|
|
3309
|
+
stopCount: nextCount,
|
|
3310
|
+
threshold,
|
|
3311
|
+
firstObservedAt: existingSession?.firstObservedAt ?? now,
|
|
3312
|
+
lastObservedAt: now,
|
|
3313
|
+
lastAction: "counted",
|
|
3314
|
+
...normalized.lastAssistantMessage ? { lastAssistantMessage: normalized.lastAssistantMessage } : {}
|
|
3315
|
+
};
|
|
3316
|
+
const countedState = {
|
|
3317
|
+
...state,
|
|
3318
|
+
sessions: {
|
|
3319
|
+
...state.sessions,
|
|
3320
|
+
[normalized.agentSessionKey]: observedSession
|
|
3321
|
+
}
|
|
3322
|
+
};
|
|
3323
|
+
if (nextCount < threshold) {
|
|
3324
|
+
await writeAgentLoopState(parsed, options, countedState);
|
|
3325
|
+
const data2 = {
|
|
3326
|
+
action: "counted",
|
|
3327
|
+
agentSessionKey: normalized.agentSessionKey,
|
|
3328
|
+
stopCount: nextCount,
|
|
3329
|
+
threshold
|
|
3330
|
+
};
|
|
3331
|
+
return {
|
|
3332
|
+
stdout: parsed.flags.json ? formatJson(successEnvelope(data2, meta)) : `${JSON.stringify(data2, null, 2)}
|
|
3333
|
+
`,
|
|
3334
|
+
stderr: "",
|
|
3335
|
+
exitCode: 0
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
if (shouldSkipForCooldown(observedSession, cooldownSeconds)) {
|
|
3339
|
+
await writeAgentLoopState(parsed, options, {
|
|
3340
|
+
...state,
|
|
3341
|
+
sessions: {
|
|
3342
|
+
...state.sessions,
|
|
3343
|
+
[normalized.agentSessionKey]: {
|
|
3344
|
+
...observedSession,
|
|
3345
|
+
lastAction: "skipped",
|
|
3346
|
+
lastReason: "cooldown"
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
});
|
|
3350
|
+
const data2 = {
|
|
3351
|
+
action: "skipped",
|
|
3352
|
+
reason: "cooldown",
|
|
3353
|
+
agentSessionKey: normalized.agentSessionKey,
|
|
3354
|
+
stopCount: nextCount,
|
|
3355
|
+
threshold
|
|
3356
|
+
};
|
|
3357
|
+
return {
|
|
3358
|
+
stdout: parsed.flags.json ? formatJson(successEnvelope(data2, meta)) : `${JSON.stringify(data2, null, 2)}
|
|
3359
|
+
`,
|
|
3360
|
+
stderr: "",
|
|
3361
|
+
exitCode: 0
|
|
3362
|
+
};
|
|
3363
|
+
}
|
|
3364
|
+
const fingerprint = fingerprintForCheckpoint(normalized, options);
|
|
3365
|
+
if (observedSession.lastCheckpointFingerprint && observedSession.lastCheckpointFingerprint === fingerprint) {
|
|
3366
|
+
await writeAgentLoopState(parsed, options, {
|
|
3367
|
+
...state,
|
|
3368
|
+
sessions: {
|
|
3369
|
+
...state.sessions,
|
|
3370
|
+
[normalized.agentSessionKey]: {
|
|
3371
|
+
...observedSession,
|
|
3372
|
+
stopCount: 0,
|
|
3373
|
+
threshold: randomCheckpointThreshold(),
|
|
3374
|
+
lastAction: "skipped",
|
|
3375
|
+
lastReason: "unchanged"
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
});
|
|
3379
|
+
const data2 = {
|
|
3380
|
+
action: "skipped",
|
|
3381
|
+
reason: "unchanged",
|
|
3382
|
+
agentSessionKey: normalized.agentSessionKey
|
|
3383
|
+
};
|
|
3384
|
+
return {
|
|
3385
|
+
stdout: parsed.flags.json ? formatJson(successEnvelope(data2, meta)) : `${JSON.stringify(data2, null, 2)}
|
|
3386
|
+
`,
|
|
3387
|
+
stderr: "",
|
|
3388
|
+
exitCode: 0
|
|
3389
|
+
};
|
|
3390
|
+
}
|
|
3391
|
+
const eventFile = await writeAgentEventFile(parsed, options, normalized);
|
|
3392
|
+
const lockPath = agentLoopLockPath(parsed, options, normalized.agentSessionKey);
|
|
3393
|
+
const workerArgs = [
|
|
3394
|
+
"agent-run",
|
|
3395
|
+
"closeout",
|
|
3396
|
+
"--agent-session-key",
|
|
3397
|
+
normalized.agentSessionKey,
|
|
3398
|
+
"--reason",
|
|
3399
|
+
"threshold",
|
|
3400
|
+
"--event-file",
|
|
3401
|
+
eventFile,
|
|
3402
|
+
"--lock",
|
|
3403
|
+
lockPath,
|
|
3404
|
+
...parsed.options["log-kind"] ? ["--log-kind", parsed.options["log-kind"]] : [],
|
|
3405
|
+
...parsed.options["update-summary"] ? ["--update-summary", parsed.options["update-summary"]] : [],
|
|
3406
|
+
...parsed.flags.project ? ["--project", parsed.flags.project] : [],
|
|
3407
|
+
...parsed.flags.server ? ["--server", parsed.flags.server] : []
|
|
3408
|
+
];
|
|
3409
|
+
if (dryRun) {
|
|
3410
|
+
await writeAgentLoopState(parsed, options, {
|
|
3411
|
+
...state,
|
|
3412
|
+
sessions: {
|
|
3413
|
+
...state.sessions,
|
|
3414
|
+
[normalized.agentSessionKey]: {
|
|
3415
|
+
...observedSession,
|
|
3416
|
+
lastAction: "would_spawn",
|
|
3417
|
+
lastEventFile: eventFile
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
});
|
|
3421
|
+
const data2 = {
|
|
3422
|
+
action: "would_spawn",
|
|
3423
|
+
agentSessionKey: normalized.agentSessionKey,
|
|
3424
|
+
eventFile,
|
|
3425
|
+
workerArgs
|
|
3426
|
+
};
|
|
3427
|
+
return {
|
|
3428
|
+
stdout: parsed.flags.json ? formatJson(successEnvelope(data2, meta)) : `${JSON.stringify(data2, null, 2)}
|
|
3429
|
+
`,
|
|
3430
|
+
stderr: "",
|
|
3431
|
+
exitCode: 0
|
|
3432
|
+
};
|
|
3433
|
+
}
|
|
3434
|
+
const lock = await acquireAgentLoopLock(parsed, options, normalized.agentSessionKey);
|
|
3435
|
+
if (!lock.acquired) {
|
|
3436
|
+
await writeAgentLoopState(parsed, options, {
|
|
3437
|
+
...state,
|
|
3438
|
+
sessions: {
|
|
3439
|
+
...state.sessions,
|
|
3440
|
+
[normalized.agentSessionKey]: {
|
|
3441
|
+
...observedSession,
|
|
3442
|
+
lastAction: "skipped",
|
|
3443
|
+
lastReason: "lock_held"
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
});
|
|
3447
|
+
const data2 = {
|
|
3448
|
+
action: "skipped",
|
|
3449
|
+
reason: "lock_held",
|
|
3450
|
+
lockPath: lock.lockPath,
|
|
3451
|
+
agentSessionKey: normalized.agentSessionKey
|
|
3452
|
+
};
|
|
3453
|
+
return {
|
|
3454
|
+
stdout: parsed.flags.json ? formatJson(successEnvelope(data2, meta)) : `${JSON.stringify(data2, null, 2)}
|
|
3455
|
+
`,
|
|
3456
|
+
stderr: "",
|
|
3457
|
+
exitCode: 0
|
|
3458
|
+
};
|
|
3459
|
+
}
|
|
3460
|
+
try {
|
|
3461
|
+
await spawnAgentRunCloseout(workerArgs, options);
|
|
3462
|
+
} catch (error) {
|
|
3463
|
+
await releaseAgentLoopLock(lock.lockPath);
|
|
3464
|
+
throw error;
|
|
3465
|
+
}
|
|
3466
|
+
await writeAgentLoopState(parsed, options, {
|
|
3467
|
+
...state,
|
|
3468
|
+
sessions: {
|
|
3469
|
+
...state.sessions,
|
|
3470
|
+
[normalized.agentSessionKey]: {
|
|
3471
|
+
...observedSession,
|
|
3472
|
+
stopCount: 0,
|
|
3473
|
+
threshold: randomCheckpointThreshold(),
|
|
3474
|
+
lastAction: "spawned",
|
|
3475
|
+
lastEventFile: eventFile,
|
|
3476
|
+
lastCheckpointAt: new Date().toISOString(),
|
|
3477
|
+
lastCheckpointFingerprint: fingerprint
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
});
|
|
3481
|
+
const data = {
|
|
3482
|
+
action: "spawned",
|
|
3483
|
+
agentSessionKey: normalized.agentSessionKey,
|
|
3484
|
+
eventFile,
|
|
3485
|
+
lockPath: lock.lockPath
|
|
3486
|
+
};
|
|
3487
|
+
return {
|
|
3488
|
+
stdout: parsed.flags.json ? formatJson(successEnvelope(data, meta)) : `${JSON.stringify(data, null, 2)}
|
|
3489
|
+
`,
|
|
3490
|
+
stderr: "",
|
|
3491
|
+
exitCode: 0
|
|
3492
|
+
};
|
|
3493
|
+
}
|
|
3494
|
+
async function runAgentRunCloseout(parsed, options, config, meta) {
|
|
3495
|
+
const projectId = requireProjectId(config);
|
|
3496
|
+
const client = await createClient(config, options);
|
|
3497
|
+
const agentSessionKeyValue = requireOption(parsed, "agent-session-key");
|
|
3498
|
+
const state = await readAgentLoopState(parsed, options);
|
|
3499
|
+
const sessionState = state.sessions[agentSessionKeyValue];
|
|
3500
|
+
if (!sessionState) {
|
|
3501
|
+
throw new CliError("AGENT_RUN_SESSION_NOT_FOUND", "No local agent-run session state exists for --agent-session-key.", {
|
|
3502
|
+
agentSessionKey: agentSessionKeyValue
|
|
3503
|
+
});
|
|
3504
|
+
}
|
|
3505
|
+
const current = await client.sessions.current({
|
|
3506
|
+
projectId,
|
|
3507
|
+
filters: {
|
|
3508
|
+
limit: 100
|
|
3509
|
+
}
|
|
3510
|
+
});
|
|
3511
|
+
const currentSession = activeSessionFromCurrent(current);
|
|
3512
|
+
const ticketId = parsed.options.ticket ?? (currentSession && typeof currentSession.ticketId === "string" ? currentSession.ticketId : undefined);
|
|
3513
|
+
const sessionId = parsed.options.session ?? (currentSession && typeof currentSession.id === "string" ? currentSession.id : undefined);
|
|
3514
|
+
const changesetId = parsed.options.changeset ?? (currentSession && typeof currentSession.changesetId === "string" ? currentSession.changesetId : undefined);
|
|
3515
|
+
if (!ticketId) {
|
|
3516
|
+
await writeAgentLoopState(parsed, options, {
|
|
3517
|
+
...state,
|
|
3518
|
+
sessions: {
|
|
3519
|
+
...state.sessions,
|
|
3520
|
+
[agentSessionKeyValue]: {
|
|
3521
|
+
...sessionState,
|
|
3522
|
+
lastAction: "skipped",
|
|
3523
|
+
lastReason: "no_active_ticket"
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
});
|
|
3527
|
+
const data = {
|
|
3528
|
+
action: "skipped",
|
|
3529
|
+
reason: "no_active_ticket",
|
|
3530
|
+
agentSessionKey: agentSessionKeyValue
|
|
3531
|
+
};
|
|
3532
|
+
return {
|
|
3533
|
+
stdout: parsed.flags.json ? formatJson(successEnvelope(data, meta)) : `${JSON.stringify(data, null, 2)}
|
|
3534
|
+
`,
|
|
3535
|
+
stderr: "",
|
|
3536
|
+
exitCode: 0
|
|
3537
|
+
};
|
|
3538
|
+
}
|
|
3539
|
+
const eventFile = parsed.options["event-file"] ?? sessionState.lastEventFile;
|
|
3540
|
+
const event = eventFile ? tryParseJsonObject(await readFile(eventFile, "utf8"), eventFile) : synthesizeAgentEventFromSession(agentSessionKeyValue, sessionState, options);
|
|
3541
|
+
const logKind = parseWorkLogEntryKind(parsed.options["log-kind"] ?? "note");
|
|
3542
|
+
const updateSummary = parseBooleanOption(parsed.options["update-summary"], false);
|
|
3543
|
+
const dryRun = parseBooleanOption(parsed.options["dry-run"], false);
|
|
3544
|
+
const lockPath = parsed.options.lock;
|
|
3545
|
+
const gitStatus = gitOutput(["status", "--short"], options);
|
|
3546
|
+
const gitDiffStat = gitOutput(["diff", "--stat", "origin/dev..."], options);
|
|
3547
|
+
const changedFiles = gitOutput(["diff", "--name-only", "origin/dev..."], options);
|
|
3548
|
+
const prompt = buildCheckpointPrompt({
|
|
3549
|
+
event,
|
|
3550
|
+
ticketId,
|
|
3551
|
+
...sessionId ? { sessionId } : {},
|
|
3552
|
+
...changesetId ? { changesetId } : {},
|
|
3553
|
+
gitStatus,
|
|
3554
|
+
gitDiffStat,
|
|
3555
|
+
changedFiles,
|
|
3556
|
+
...sessionState.previousCheckpointSummary ? { previousCheckpointSummary: sessionState.previousCheckpointSummary } : {}
|
|
3557
|
+
});
|
|
3558
|
+
if (dryRun) {
|
|
3559
|
+
const data = {
|
|
3560
|
+
action: "would_closeout",
|
|
3561
|
+
prompt,
|
|
3562
|
+
ticketId,
|
|
3563
|
+
agentSessionKey: agentSessionKeyValue,
|
|
3564
|
+
...sessionId ? { sessionId } : {},
|
|
3565
|
+
...changesetId ? { changesetId } : {}
|
|
3566
|
+
};
|
|
3567
|
+
return {
|
|
3568
|
+
stdout: parsed.flags.json ? formatJson(successEnvelope(data, meta)) : `${JSON.stringify(data, null, 2)}
|
|
3569
|
+
`,
|
|
3570
|
+
stderr: "",
|
|
3571
|
+
exitCode: 0
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
try {
|
|
3575
|
+
const codexCommand = parsed.options["codex-command"] ?? "codex";
|
|
3576
|
+
const codex = runLocalCommand(codexCommand, ["exec", "--disable", "hooks", "--sandbox", "read-only", "-C", options.cwd ?? process.cwd(), prompt], options, {
|
|
3577
|
+
cwd: options.cwd ?? process.cwd(),
|
|
3578
|
+
env: {
|
|
3579
|
+
[agentLoopSuppressEnv]: "1",
|
|
3580
|
+
CADENCE_HOOK_SUPPRESS: "1"
|
|
3581
|
+
},
|
|
3582
|
+
timeoutMs: defaultCheckpointWorkerTimeoutMs
|
|
3583
|
+
});
|
|
3584
|
+
if (codex.status !== 0 || codex.error) {
|
|
3585
|
+
throw new CliError("AGENT_RUN_CLOSEOUT_FAILED", "Codex closeout generation failed.", {
|
|
3586
|
+
status: codex.status,
|
|
3587
|
+
stderr: truncateText(codex.stderr, 2000),
|
|
3588
|
+
error: codex.error?.message
|
|
3589
|
+
});
|
|
3590
|
+
}
|
|
3591
|
+
const checkpoint = parseCheckpointJson(codex.stdout);
|
|
3592
|
+
const body = checkpoint.body.trim();
|
|
3593
|
+
const summary = checkpoint.summary ?? truncateText(body.replace(/\s+/g, " "), 500);
|
|
3594
|
+
if (!body) {
|
|
3595
|
+
throw new CliError("AGENT_RUN_CLOSEOUT_FAILED", "Codex closeout generation returned an empty checkpoint.");
|
|
3596
|
+
}
|
|
3597
|
+
await client.tickets.log({
|
|
3598
|
+
projectId,
|
|
3599
|
+
ticketId,
|
|
3600
|
+
entry: {
|
|
3601
|
+
entryKind: logKind,
|
|
3602
|
+
body,
|
|
3603
|
+
summary,
|
|
3604
|
+
...sessionId ? { sessionId } : {},
|
|
3605
|
+
...changesetId ? { changesetId } : {},
|
|
3606
|
+
...commandMetadata()
|
|
3607
|
+
}
|
|
3608
|
+
});
|
|
3609
|
+
if (updateSummary) {
|
|
3610
|
+
const ticket = await client.tickets.get({ projectId, ticketId });
|
|
3611
|
+
if (typeof ticket.projectionVersion === "number") {
|
|
3612
|
+
await client.tickets.update({
|
|
3613
|
+
projectId,
|
|
3614
|
+
ticketId,
|
|
3615
|
+
ifVersion: ticket.projectionVersion,
|
|
3616
|
+
ticket: {
|
|
3617
|
+
currentSummary: summary,
|
|
3618
|
+
...commandMetadata()
|
|
3619
|
+
}
|
|
3620
|
+
});
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
await writeAgentLoopState(parsed, options, {
|
|
3624
|
+
...state,
|
|
3625
|
+
sessions: {
|
|
3626
|
+
...state.sessions,
|
|
3627
|
+
[agentSessionKeyValue]: {
|
|
3628
|
+
...sessionState,
|
|
3629
|
+
previousCheckpointSummary: summary,
|
|
3630
|
+
lastAction: "closed_out",
|
|
3631
|
+
...eventFile ? { lastEventFile: eventFile } : {}
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
});
|
|
3635
|
+
const data = {
|
|
3636
|
+
action: "closed_out",
|
|
3637
|
+
ticketId,
|
|
3638
|
+
summary,
|
|
3639
|
+
agentSessionKey: agentSessionKeyValue,
|
|
3640
|
+
...sessionId ? { sessionId } : {},
|
|
3641
|
+
...changesetId ? { changesetId } : {}
|
|
3642
|
+
};
|
|
3643
|
+
return {
|
|
3644
|
+
stdout: parsed.flags.json ? formatJson(successEnvelope(data, meta)) : `${JSON.stringify(data, null, 2)}
|
|
3645
|
+
`,
|
|
3646
|
+
stderr: "",
|
|
3647
|
+
exitCode: 0
|
|
3648
|
+
};
|
|
3649
|
+
} finally {
|
|
3650
|
+
await releaseAgentLoopLock(lockPath);
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
async function runAgentRunSweep(parsed, options, meta) {
|
|
3654
|
+
const state = await readAgentLoopState(parsed, options);
|
|
3655
|
+
const idleAfterSeconds = parsePositiveInteger(parsed.options["idle-after-seconds"], "--idle-after-seconds") ?? 5 * 60;
|
|
3656
|
+
const dryRun = parseBooleanOption(parsed.options["dry-run"], false);
|
|
3657
|
+
const nowMs = Date.now();
|
|
3658
|
+
const staleSessions = Object.entries(state.sessions).filter(([, session]) => {
|
|
3659
|
+
if (session.stopCount <= 0 || !session.lastObservedAt) {
|
|
3660
|
+
return false;
|
|
3661
|
+
}
|
|
3662
|
+
const lastObservedMs = new Date(session.lastObservedAt).getTime();
|
|
3663
|
+
return !Number.isNaN(lastObservedMs) && nowMs - lastObservedMs >= idleAfterSeconds * 1000;
|
|
3664
|
+
}).map(([agentSessionKeyValue, session]) => ({
|
|
3665
|
+
agentSessionKey: agentSessionKeyValue,
|
|
3666
|
+
source: session.source,
|
|
3667
|
+
stopCount: session.stopCount,
|
|
3668
|
+
threshold: session.threshold,
|
|
3669
|
+
lastObservedAt: session.lastObservedAt
|
|
3670
|
+
}));
|
|
3671
|
+
if (!dryRun) {
|
|
3672
|
+
for (const staleSession of staleSessions) {
|
|
3673
|
+
await spawnAgentRunCloseout([
|
|
3674
|
+
"agent-run",
|
|
3675
|
+
"closeout",
|
|
3676
|
+
"--agent-session-key",
|
|
3677
|
+
staleSession.agentSessionKey,
|
|
3678
|
+
"--reason",
|
|
3679
|
+
"idle",
|
|
3680
|
+
"--lock",
|
|
3681
|
+
agentLoopLockPath(parsed, options, staleSession.agentSessionKey),
|
|
3682
|
+
...parsed.flags.project ? ["--project", parsed.flags.project] : [],
|
|
3683
|
+
...parsed.flags.server ? ["--server", parsed.flags.server] : []
|
|
3684
|
+
], options);
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
const data = {
|
|
3688
|
+
action: dryRun ? "would_sweep" : "swept",
|
|
3689
|
+
idleAfterSeconds,
|
|
3690
|
+
staleSessions
|
|
3691
|
+
};
|
|
3692
|
+
return {
|
|
3693
|
+
stdout: parsed.flags.json ? formatJson(successEnvelope(data, meta)) : `${JSON.stringify(data, null, 2)}
|
|
3694
|
+
`,
|
|
3695
|
+
stderr: "",
|
|
3696
|
+
exitCode: 0
|
|
3697
|
+
};
|
|
3698
|
+
}
|
|
3699
|
+
async function runHooksCommand(parsed, options) {
|
|
3700
|
+
const config = await resolveCliConfig(parsed.flags, options);
|
|
3701
|
+
const meta = commandMeta(parsed, config);
|
|
3702
|
+
let data;
|
|
3703
|
+
switch (parsed.command.name) {
|
|
3704
|
+
case "hooks.install":
|
|
3705
|
+
{
|
|
3706
|
+
const provider = parsed.options.provider ?? "codex";
|
|
3707
|
+
if (provider !== "codex") {
|
|
3708
|
+
throw new CliError("CLI_USAGE", "Only --provider codex is supported for hook installation in this version.");
|
|
3709
|
+
}
|
|
3710
|
+
const scope = parseHookScope(parsed.options.scope);
|
|
3711
|
+
const command = parsed.options.command ?? defaultHookCommand;
|
|
3712
|
+
const paths = await codexHookPaths(scope, options);
|
|
3713
|
+
const results = [];
|
|
3714
|
+
for (const filePath of paths) {
|
|
3715
|
+
results.push(await installCodexHookFile(filePath, command));
|
|
3716
|
+
}
|
|
3717
|
+
data = {
|
|
3718
|
+
provider,
|
|
3719
|
+
scope,
|
|
3720
|
+
command,
|
|
3721
|
+
results,
|
|
3722
|
+
trustRequired: "Open Codex /hooks and trust the Cadence hook before it can run."
|
|
3723
|
+
};
|
|
3724
|
+
}
|
|
3725
|
+
break;
|
|
3726
|
+
case "hooks.doctor":
|
|
3727
|
+
{
|
|
3728
|
+
const provider = parsed.options.provider ?? "codex";
|
|
3729
|
+
if (provider !== "codex") {
|
|
3730
|
+
throw new CliError("CLI_USAGE", "Only --provider codex is supported for hook doctor in this version.");
|
|
3731
|
+
}
|
|
3732
|
+
const scope = parseHookScope(parsed.options.scope);
|
|
3733
|
+
const command = parsed.options.command ?? defaultHookCommand;
|
|
3734
|
+
const paths = await codexHookPaths(scope, options);
|
|
3735
|
+
const codexVersion = runLocalCommand("codex", ["--version"], options, {
|
|
3736
|
+
cwd: options.cwd ?? process.cwd(),
|
|
3737
|
+
timeoutMs: 1e4
|
|
3738
|
+
});
|
|
3739
|
+
data = {
|
|
3740
|
+
provider,
|
|
3741
|
+
scope,
|
|
3742
|
+
command,
|
|
3743
|
+
codexAvailable: codexVersion.status === 0,
|
|
3744
|
+
codexVersion: codexVersion.status === 0 ? codexVersion.stdout.trim() : null,
|
|
3745
|
+
hooks: await Promise.all(paths.map(async (filePath) => ({
|
|
3746
|
+
path: filePath,
|
|
3747
|
+
exists: await Bun.file(filePath).exists(),
|
|
3748
|
+
installed: await codexHookInstalled(filePath, command)
|
|
3749
|
+
})))
|
|
3750
|
+
};
|
|
3751
|
+
}
|
|
3752
|
+
break;
|
|
3753
|
+
default:
|
|
3754
|
+
throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
|
|
3755
|
+
}
|
|
3756
|
+
if (parsed.flags.json) {
|
|
3757
|
+
return {
|
|
3758
|
+
stdout: formatJson(successEnvelope(data, meta)),
|
|
3759
|
+
stderr: "",
|
|
3760
|
+
exitCode: 0
|
|
3761
|
+
};
|
|
3762
|
+
}
|
|
3763
|
+
return {
|
|
3764
|
+
stdout: `${JSON.stringify(data, null, 2)}
|
|
3765
|
+
`,
|
|
3766
|
+
stderr: "",
|
|
3767
|
+
exitCode: 0
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
2723
3770
|
async function runAuthCommand(parsed, options) {
|
|
2724
3771
|
const config = await resolveCliConfig(parsed.flags, options);
|
|
2725
3772
|
const store = getCredentialStore(options);
|
|
2726
|
-
|
|
3773
|
+
let meta = commandMeta(parsed, config);
|
|
2727
3774
|
let data;
|
|
2728
3775
|
switch (parsed.command.name) {
|
|
2729
3776
|
case "auth.login":
|
|
2730
3777
|
{
|
|
2731
|
-
const
|
|
3778
|
+
const loginConfig = await resolveAuthLoginConfig(config, parsed, options);
|
|
3779
|
+
meta = commandMeta(parsed, loginConfig);
|
|
3780
|
+
const client = await createClient(loginConfig, options, false);
|
|
3781
|
+
const loginBaseUrl = loginConfig.webBaseUrl;
|
|
2732
3782
|
const challenge = await client.auth.cli.start({
|
|
2733
|
-
loginBaseUrl
|
|
3783
|
+
loginBaseUrl
|
|
2734
3784
|
});
|
|
2735
3785
|
if (parsed.flags.json || !isInteractive(options)) {
|
|
2736
3786
|
throw new CliError("HUMAN_AUTH_REQUIRED", "Cadence login must be completed by a human in the browser.", {
|
|
@@ -2762,20 +3812,15 @@ async function runAuthCommand(parsed, options) {
|
|
|
2762
3812
|
if (poll.status === "denied") {
|
|
2763
3813
|
throw new CliError("AUTH_LOGIN_DENIED", "Cadence browser login was denied.");
|
|
2764
3814
|
}
|
|
2765
|
-
await writeStoredCredential(store,
|
|
2766
|
-
await mergeConfigFile(
|
|
3815
|
+
await writeStoredCredential(store, loginConfig.server, poll.credential);
|
|
3816
|
+
await mergeConfigFile(loginConfig.globalConfigPath, { server: loginConfig.server, webBaseUrl: loginBaseUrl });
|
|
2767
3817
|
await writeInteractiveStatus("Cadence CLI login approved. Credential stored.", options);
|
|
2768
|
-
const projectConfigured = Boolean(config.projectId);
|
|
2769
|
-
if (!projectConfigured) {
|
|
2770
|
-
await writeInteractiveStatus("No Cadence project is configured for this repo. Run cadence init to choose one.", options);
|
|
2771
|
-
}
|
|
2772
3818
|
data = {
|
|
2773
|
-
server:
|
|
3819
|
+
server: loginConfig.server,
|
|
3820
|
+
webBaseUrl: loginBaseUrl,
|
|
2774
3821
|
credentialStored: true,
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
loginUrl: challenge.loginUrl,
|
|
2778
|
-
...!projectConfigured ? { nextCommand: "cadence init" } : {}
|
|
3822
|
+
globalConfigPath: loginConfig.globalConfigPath,
|
|
3823
|
+
loginUrl: challenge.loginUrl
|
|
2779
3824
|
};
|
|
2780
3825
|
}
|
|
2781
3826
|
break;
|
|
@@ -2813,26 +3858,18 @@ async function runAuthCommand(parsed, options) {
|
|
|
2813
3858
|
exitCode: 0
|
|
2814
3859
|
};
|
|
2815
3860
|
}
|
|
2816
|
-
if (parsed.command.name === "auth.login") {
|
|
2817
|
-
return {
|
|
2818
|
-
stdout: "",
|
|
2819
|
-
stderr: "",
|
|
2820
|
-
exitCode: 0
|
|
2821
|
-
};
|
|
2822
|
-
}
|
|
2823
3861
|
return {
|
|
2824
|
-
stdout:
|
|
3862
|
+
stdout: `${JSON.stringify(data, null, 2)}
|
|
3863
|
+
`,
|
|
2825
3864
|
stderr: "",
|
|
2826
3865
|
exitCode: 0
|
|
2827
3866
|
};
|
|
2828
3867
|
}
|
|
2829
3868
|
async function runReadCommand(parsed, options) {
|
|
2830
3869
|
const config = await resolveCliConfig(parsed.flags, options);
|
|
3870
|
+
const projectId = requireProjectId(config);
|
|
2831
3871
|
const client = await createClient(config, options);
|
|
2832
|
-
const
|
|
2833
|
-
const resolvedConfig = configWithProject(config, project);
|
|
2834
|
-
const projectId = project.id;
|
|
2835
|
-
const meta = commandMeta(parsed, resolvedConfig);
|
|
3872
|
+
const meta = commandMeta(parsed, config);
|
|
2836
3873
|
let data;
|
|
2837
3874
|
switch (parsed.command.name) {
|
|
2838
3875
|
case "events.list":
|
|
@@ -2925,7 +3962,8 @@ async function runReadCommand(parsed, options) {
|
|
|
2925
3962
|
};
|
|
2926
3963
|
}
|
|
2927
3964
|
return {
|
|
2928
|
-
stdout:
|
|
3965
|
+
stdout: `${JSON.stringify(data, null, 2)}
|
|
3966
|
+
`,
|
|
2929
3967
|
stderr: "",
|
|
2930
3968
|
exitCode: 0
|
|
2931
3969
|
};
|
|
@@ -2933,53 +3971,25 @@ async function runReadCommand(parsed, options) {
|
|
|
2933
3971
|
async function runProjectCommand(parsed, options) {
|
|
2934
3972
|
const config = await resolveCliConfig(parsed.flags, options);
|
|
2935
3973
|
const client = await createClient(config, options);
|
|
3974
|
+
const meta = commandMeta(parsed, config);
|
|
2936
3975
|
let data;
|
|
2937
|
-
let outputConfig = config;
|
|
2938
3976
|
switch (parsed.command.name) {
|
|
2939
3977
|
case "projects.list":
|
|
2940
3978
|
data = await client.projects.list();
|
|
2941
3979
|
break;
|
|
2942
|
-
case "projects.use":
|
|
2943
|
-
{
|
|
2944
|
-
const project = await resolveProjectReference(requireArg(parsed, 0, "<project-id|org/project>"), client);
|
|
2945
|
-
const repoConfigPath = await repoConfigPathForWrite(options);
|
|
2946
|
-
const updates = {
|
|
2947
|
-
projectId: project.id,
|
|
2948
|
-
...parsed.flags.server ? { server: parsed.flags.server } : {}
|
|
2949
|
-
};
|
|
2950
|
-
await mergeConfigFile(repoConfigPath, updates);
|
|
2951
|
-
outputConfig = configWithProject(config, project);
|
|
2952
|
-
data = {
|
|
2953
|
-
repoConfigPath,
|
|
2954
|
-
projectId: project.id,
|
|
2955
|
-
project,
|
|
2956
|
-
...parsed.flags.server ? { server: parsed.flags.server } : {}
|
|
2957
|
-
};
|
|
2958
|
-
}
|
|
2959
|
-
break;
|
|
2960
3980
|
default:
|
|
2961
3981
|
throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
|
|
2962
3982
|
}
|
|
2963
3983
|
if (parsed.flags.json) {
|
|
2964
3984
|
return {
|
|
2965
|
-
stdout: formatJson(successEnvelope(data,
|
|
2966
|
-
stderr: "",
|
|
2967
|
-
exitCode: 0
|
|
2968
|
-
};
|
|
2969
|
-
}
|
|
2970
|
-
if (parsed.command.name === "projects.use") {
|
|
2971
|
-
const project = data;
|
|
2972
|
-
const slug = project.project?.orgSlug && project.project.projectSlug ? `${project.project.orgSlug}/${project.project.projectSlug}` : project.projectId;
|
|
2973
|
-
const name = project.project?.name ? ` (${project.project.name})` : "";
|
|
2974
|
-
return {
|
|
2975
|
-
stdout: `Using Cadence project ${slug}${name}.
|
|
2976
|
-
`,
|
|
3985
|
+
stdout: formatJson(successEnvelope(data, meta)),
|
|
2977
3986
|
stderr: "",
|
|
2978
3987
|
exitCode: 0
|
|
2979
3988
|
};
|
|
2980
3989
|
}
|
|
2981
3990
|
return {
|
|
2982
|
-
stdout:
|
|
3991
|
+
stdout: `${JSON.stringify(data, null, 2)}
|
|
3992
|
+
`,
|
|
2983
3993
|
stderr: "",
|
|
2984
3994
|
exitCode: 0
|
|
2985
3995
|
};
|
|
@@ -2987,13 +3997,11 @@ async function runProjectCommand(parsed, options) {
|
|
|
2987
3997
|
async function runActorCommand(parsed, options) {
|
|
2988
3998
|
const config = await resolveCliConfig(parsed.flags, options);
|
|
2989
3999
|
const client = await createClient(config, options);
|
|
2990
|
-
const
|
|
2991
|
-
const resolvedConfig = configWithProject(config, project);
|
|
2992
|
-
const meta = commandMeta(parsed, resolvedConfig);
|
|
4000
|
+
const meta = commandMeta(parsed, config);
|
|
2993
4001
|
let data;
|
|
2994
4002
|
switch (parsed.command.name) {
|
|
2995
4003
|
case "actors.ensure-workspace-agent":
|
|
2996
|
-
data = await ensureWorkspaceAgentIdentity(parsed,
|
|
4004
|
+
data = await ensureWorkspaceAgentIdentity(parsed, config, client, options);
|
|
2997
4005
|
break;
|
|
2998
4006
|
default:
|
|
2999
4007
|
throw new CliError("CLI_USAGE", `Unknown command: ${parsed.command.path.join(" ")}`);
|
|
@@ -3006,18 +4014,17 @@ async function runActorCommand(parsed, options) {
|
|
|
3006
4014
|
};
|
|
3007
4015
|
}
|
|
3008
4016
|
return {
|
|
3009
|
-
stdout:
|
|
4017
|
+
stdout: `${JSON.stringify(data, null, 2)}
|
|
4018
|
+
`,
|
|
3010
4019
|
stderr: "",
|
|
3011
4020
|
exitCode: 0
|
|
3012
4021
|
};
|
|
3013
4022
|
}
|
|
3014
4023
|
async function runIntakeCommand(parsed, options) {
|
|
3015
4024
|
const config = await resolveCliConfig(parsed.flags, options);
|
|
4025
|
+
const projectId = requireProjectId(config);
|
|
3016
4026
|
const client = await createClient(config, options);
|
|
3017
|
-
const
|
|
3018
|
-
const resolvedConfig = configWithProject(config, project);
|
|
3019
|
-
const projectId = project.id;
|
|
3020
|
-
const meta = commandMeta(parsed, resolvedConfig);
|
|
4027
|
+
const meta = commandMeta(parsed, config);
|
|
3021
4028
|
let data;
|
|
3022
4029
|
const ifVersion = () => parseRequiredPositiveInteger(requireOption(parsed, "if-version"), "--if-version");
|
|
3023
4030
|
switch (parsed.command.name) {
|
|
@@ -3249,7 +4256,8 @@ async function runIntakeCommand(parsed, options) {
|
|
|
3249
4256
|
};
|
|
3250
4257
|
}
|
|
3251
4258
|
return {
|
|
3252
|
-
stdout:
|
|
4259
|
+
stdout: `${JSON.stringify(data, null, 2)}
|
|
4260
|
+
`,
|
|
3253
4261
|
stderr: "",
|
|
3254
4262
|
exitCode: 0
|
|
3255
4263
|
};
|
|
@@ -3313,9 +4321,15 @@ async function runCli(argv, options = {}) {
|
|
|
3313
4321
|
if (parsed.command.name === "events.list" || parsed.command.name === "work.overview" || parsed.command.name === "tickets.get" || parsed.command.name === "tickets.list" || parsed.command.name === "sessions.current" || parsed.command.name === "changesets.get" || parsed.command.name === "changesets.context" || parsed.command.name === "changesets.current" || parsed.command.name === "changesets.list" || parsed.command.name === "changesets.notes.get") {
|
|
3314
4322
|
return await runReadCommand(parsed, options);
|
|
3315
4323
|
}
|
|
3316
|
-
if (parsed.command.name === "projects.list"
|
|
4324
|
+
if (parsed.command.name === "projects.list") {
|
|
3317
4325
|
return await runProjectCommand(parsed, options);
|
|
3318
4326
|
}
|
|
4327
|
+
if (parsed.command.name === "agent-run.ingest-stop" || parsed.command.name === "agent-run.closeout" || parsed.command.name === "agent-run.sweep" || parsed.command.name === "agent-run.doctor") {
|
|
4328
|
+
return await runAgentRunCommand(parsed, options);
|
|
4329
|
+
}
|
|
4330
|
+
if (parsed.command.name === "hooks.install" || parsed.command.name === "hooks.doctor") {
|
|
4331
|
+
return await runHooksCommand(parsed, options);
|
|
4332
|
+
}
|
|
3319
4333
|
if (parsed.command.name === "intake" || parsed.command.name === "intake.dismiss" || parsed.command.name === "tickets.attach" || parsed.command.name === "tickets.create" || parsed.command.name === "tickets.update" || parsed.command.name === "tickets.claim" || parsed.command.name === "tickets.release" || parsed.command.name === "tickets.log" || parsed.command.name === "tickets.complete" || parsed.command.name === "sessions.start" || parsed.command.name === "sessions.end" || parsed.command.name === "sessions.files" || parsed.command.name === "changesets.create" || parsed.command.name === "changesets.notes.put" || parsed.command.name === "changesets.notes.apply") {
|
|
3320
4334
|
return await runIntakeCommand(parsed, options);
|
|
3321
4335
|
}
|