@claudexor/cli 2.1.3 → 3.0.1
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/accounts-projection.d.ts +26 -0
- package/dist/accounts-projection.d.ts.map +1 -0
- package/dist/accounts-projection.js +90 -0
- package/dist/accounts-projection.js.map +1 -0
- package/dist/acp-surface-runner.d.ts.map +1 -1
- package/dist/acp-surface-runner.js +12 -16
- package/dist/acp-surface-runner.js.map +1 -1
- package/dist/bridge-serve.d.ts +9 -0
- package/dist/bridge-serve.d.ts.map +1 -1
- package/dist/bridge-serve.js +20 -1
- package/dist/bridge-serve.js.map +1 -1
- package/dist/claude-oauth-usage.d.ts +8 -2
- package/dist/claude-oauth-usage.d.ts.map +1 -1
- package/dist/claude-oauth-usage.js +27 -12
- package/dist/claude-oauth-usage.js.map +1 -1
- package/dist/claude-statusline.d.ts +5 -2
- package/dist/claude-statusline.d.ts.map +1 -1
- package/dist/claude-statusline.js +4 -2
- package/dist/claude-statusline.js.map +1 -1
- package/dist/claudexord.d.ts +7 -1
- package/dist/claudexord.d.ts.map +1 -1
- package/dist/claudexord.js +116 -19
- package/dist/claudexord.js.map +1 -1
- package/dist/cli.js +105 -276
- package/dist/cli.js.map +1 -1
- package/dist/codex-quota-source.d.ts +9 -3
- package/dist/codex-quota-source.d.ts.map +1 -1
- package/dist/codex-quota-source.js +82 -9
- package/dist/codex-quota-source.js.map +1 -1
- package/dist/command-flags.d.ts +1 -1
- package/dist/command-flags.d.ts.map +1 -1
- package/dist/command-flags.js +8 -9
- package/dist/command-flags.js.map +1 -1
- package/dist/command-registry.d.ts.map +1 -1
- package/dist/command-registry.js +19 -54
- package/dist/command-registry.js.map +1 -1
- package/dist/control-services.d.ts +163 -356
- package/dist/control-services.d.ts.map +1 -1
- package/dist/control-services.js +80 -126
- package/dist/control-services.js.map +1 -1
- package/dist/credential-commands.d.ts.map +1 -1
- package/dist/credential-commands.js +70 -8
- package/dist/credential-commands.js.map +1 -1
- package/dist/daemon-run.d.ts +49 -7
- package/dist/daemon-run.d.ts.map +1 -1
- package/dist/daemon-run.js +152 -30
- package/dist/daemon-run.js.map +1 -1
- package/dist/decision.js +2 -2
- package/dist/decision.js.map +1 -1
- package/dist/delegation-belt-descriptor.d.ts +43 -0
- package/dist/delegation-belt-descriptor.d.ts.map +1 -0
- package/dist/delegation-belt-descriptor.js +75 -0
- package/dist/delegation-belt-descriptor.js.map +1 -0
- package/dist/live.d.ts +5 -2
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +29 -12
- package/dist/live.js.map +1 -1
- package/dist/mcp-runner.d.ts.map +1 -1
- package/dist/mcp-runner.js +44 -20
- package/dist/mcp-runner.js.map +1 -1
- package/dist/plan-question-loop.d.ts +46 -0
- package/dist/plan-question-loop.d.ts.map +1 -0
- package/dist/plan-question-loop.js +149 -0
- package/dist/plan-question-loop.js.map +1 -0
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +1 -3
- package/dist/plugins.js.map +1 -1
- package/dist/primary-output.d.ts.map +1 -1
- package/dist/primary-output.js +9 -17
- package/dist/primary-output.js.map +1 -1
- package/dist/profile-registration.d.ts.map +1 -1
- package/dist/profile-registration.js +16 -0
- package/dist/profile-registration.js.map +1 -1
- package/dist/project-command.d.ts.map +1 -1
- package/dist/project-command.js +41 -2
- package/dist/project-command.js.map +1 -1
- package/dist/quota-command.js +8 -1
- package/dist/quota-command.js.map +1 -1
- package/dist/release-command.d.ts +12 -0
- package/dist/release-command.d.ts.map +1 -0
- package/dist/release-command.js +70 -0
- package/dist/release-command.js.map +1 -0
- package/dist/release.d.ts +59 -0
- package/dist/release.d.ts.map +1 -1
- package/dist/release.js +181 -0
- package/dist/release.js.map +1 -1
- package/dist/repl.d.ts +4 -3
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +27 -4
- package/dist/repl.js.map +1 -1
- package/dist/request-preflight.d.ts.map +1 -1
- package/dist/request-preflight.js +0 -2
- package/dist/request-preflight.js.map +1 -1
- package/dist/retention-service.d.ts.map +1 -1
- package/dist/retention-service.js +21 -0
- package/dist/retention-service.js.map +1 -1
- package/dist/settings-command.d.ts +38 -0
- package/dist/settings-command.d.ts.map +1 -1
- package/dist/settings-command.js +97 -15
- package/dist/settings-command.js.map +1 -1
- package/dist/settings-service.d.ts +5 -5
- package/dist/settings-service.d.ts.map +1 -1
- package/dist/settings-service.js +4 -1
- package/dist/settings-service.js.map +1 -1
- package/dist/setup-job-store.d.ts +9 -9
- package/dist/setup-job-store.d.ts.map +1 -1
- package/dist/setup-jobs.d.ts +13 -13
- package/dist/setup-jobs.d.ts.map +1 -1
- package/dist/thread-select.d.ts +6 -2
- package/dist/thread-select.d.ts.map +1 -1
- package/dist/thread-select.js +20 -3
- package/dist/thread-select.js.map +1 -1
- package/package.json +23 -24
- package/dist/orchestrate-options.d.ts +0 -9
- package/dist/orchestrate-options.d.ts.map +0 -1
- package/dist/orchestrate-options.js +0 -16
- package/dist/orchestrate-options.js.map +0 -1
- package/dist/spec-thread-scope.d.ts +0 -3
- package/dist/spec-thread-scope.d.ts.map +0 -1
- package/dist/spec-thread-scope.js +0 -9
- package/dist/spec-thread-scope.js.map +0 -1
- package/dist/spec.d.ts +0 -73
- package/dist/spec.d.ts.map +0 -1
- package/dist/spec.js +0 -466
- package/dist/spec.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -5,11 +5,11 @@ import { createHash } from "node:crypto";
|
|
|
5
5
|
import { Readable, Transform } from "node:stream";
|
|
6
6
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { ArtifactStore } from "@claudexor/artifact-store";
|
|
8
|
-
import { CLAUDEXOR_VERSION,
|
|
9
|
-
import {
|
|
10
|
-
import { serveAcpBridge, serveMcpBridge } from "./bridge-serve.js";
|
|
8
|
+
import { CLAUDEXOR_VERSION, noProjectRepoRoot, readTextSafe, userConfigDir } from "@claudexor/util";
|
|
9
|
+
import { releaseCommand } from "./release-command.js";
|
|
10
|
+
import { serveAcpBridge, serveBeltBridge, serveMcpBridge } from "./bridge-serve.js";
|
|
11
11
|
import { initProjectConfig } from "@claudexor/config";
|
|
12
|
-
import { DecisionRecord,
|
|
12
|
+
import { DecisionRecord, EffortHint, ExternalContextPolicy, ModeKind as ModeKindSchema, RoutingGoal, ControlThreadListResponse, RunTelemetry, StructuredOutputConformance, TaskContract, runOutcomeLabel, } from "@claudexor/schema";
|
|
13
13
|
import { flagBool, flagStr, flagStringList, flagValues, parseArgs, requiredStringFlagError, } from "./args.js";
|
|
14
14
|
import { print, printJson, printJsonLine, printUsageError, statusGlyph } from "./cli-io.js";
|
|
15
15
|
import { pickResumableThread } from "./thread-select.js";
|
|
@@ -21,7 +21,8 @@ import { controlApiFetch, followRun } from "./live.js";
|
|
|
21
21
|
import { retryCommand, runAgainCommand } from "./retry-command.js";
|
|
22
22
|
import { assertCliRunParamsHaveNoInlineSecrets } from "./run-secret-scan.js";
|
|
23
23
|
import { openLocalAttachment, resolveLocalAttachment, } from "./local-attachment.js";
|
|
24
|
-
import { connectDaemonIfRunning, daemonOutcomeSummary, ensureDaemon, enqueueAndAwait, exitCodeForState, fetchApplyEligibility,
|
|
24
|
+
import { connectDaemonIfRunning, daemonOutcomeSummary, ensureDaemon, enqueueAndAwait, exitCodeForState, fetchApplyEligibility, fetchCouncil, fetchOutcomeBanner, } from "./daemon-run.js";
|
|
25
|
+
import { runPlanQuestionLoop } from "./plan-question-loop.js";
|
|
25
26
|
import { resolveDecisionBody } from "./decision.js";
|
|
26
27
|
import { primaryOutputForCli } from "./primary-output.js";
|
|
27
28
|
import { PLUGIN_TARGETS, PLUGIN_VERBS, formatPluginResult, pluginCommandErrorResult, runPluginCommand, } from "./plugins.js";
|
|
@@ -30,13 +31,11 @@ import { settingsCommand } from "./settings-command.js";
|
|
|
30
31
|
import { quotaCommand } from "./quota-command.js";
|
|
31
32
|
import { trustCommand } from "./trust-command.js";
|
|
32
33
|
import { projectCommand } from "./project-command.js";
|
|
33
|
-
import { loadFrozenSpec, readAnswers, resolveRunTestCommands } from "./spec.js";
|
|
34
|
-
import { parseAutonomy } from "./orchestrate-options.js";
|
|
35
34
|
import { runRepl } from "./repl.js";
|
|
36
35
|
import { parseProtectedPathApprovalFlags, parseTestCommandFlags, parseReviewerEffortFlags, parseReviewerModelFlags, parseReviewerPanelFlags, } from "./run-options.js";
|
|
37
36
|
const CLI_VERSION = CLAUDEXOR_VERSION;
|
|
38
37
|
const HELP = renderHelp(CLI_VERSION);
|
|
39
|
-
const MODES = new Set(["ask", "plan", "
|
|
38
|
+
const MODES = new Set(["ask", "plan", "agent"]);
|
|
40
39
|
function normalizeMode(s) {
|
|
41
40
|
const trimmed = s.trim();
|
|
42
41
|
const parsed = ModeKindSchema.safeParse(trimmed);
|
|
@@ -240,44 +239,8 @@ async function orchestrate(args, mode, json, forced = {}) {
|
|
|
240
239
|
return printUsageError(json, "claudexor: stdin prompt (`-`) was empty");
|
|
241
240
|
}
|
|
242
241
|
}
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
try {
|
|
246
|
-
loadedSpec = specPath ? loadFrozenSpec(specPath) : null;
|
|
247
|
-
}
|
|
248
|
-
catch (err) {
|
|
249
|
-
return printUsageError(json, `claudexor: ${err instanceof Error ? err.message : String(err)}`);
|
|
250
|
-
}
|
|
251
|
-
const prompt = loadedSpec
|
|
252
|
-
? [
|
|
253
|
-
rawPrompt || loadedSpec.spec.intent.raw,
|
|
254
|
-
"",
|
|
255
|
-
"Use this frozen Claudexor SpecPack as the contract. Do not re-litigate settled choices; implement against the acceptance criteria and tests.",
|
|
256
|
-
"",
|
|
257
|
-
`Spec id: ${loadedSpec.spec.id} v${loadedSpec.spec.version}`,
|
|
258
|
-
`Spec hash: ${loadedSpec.specHash}`,
|
|
259
|
-
"",
|
|
260
|
-
"## Summary",
|
|
261
|
-
loadedSpec.spec.summary || "(none)",
|
|
262
|
-
"",
|
|
263
|
-
"## Acceptance Criteria",
|
|
264
|
-
...(loadedSpec.spec.success_criteria.length
|
|
265
|
-
? loadedSpec.spec.success_criteria.map((c) => `- [${c.id}] ${c.behavior}`)
|
|
266
|
-
: ["- (none)"]),
|
|
267
|
-
"",
|
|
268
|
-
"## Non-goals",
|
|
269
|
-
...(loadedSpec.spec.non_goals.length
|
|
270
|
-
? loadedSpec.spec.non_goals.map((x) => `- ${x}`)
|
|
271
|
-
: ["- (none)"]),
|
|
272
|
-
"",
|
|
273
|
-
"## Forbidden approaches",
|
|
274
|
-
...(loadedSpec.spec.forbidden_approaches.length
|
|
275
|
-
? loadedSpec.spec.forbidden_approaches.map((x) => `- ${x}`)
|
|
276
|
-
: ["- (none)"]),
|
|
277
|
-
].join("\n")
|
|
278
|
-
: rawPrompt;
|
|
279
|
-
const spec = loadedSpec?.spec ?? null;
|
|
280
|
-
if (!prompt && mode !== "audit") {
|
|
242
|
+
const prompt = rawPrompt;
|
|
243
|
+
if (!prompt) {
|
|
281
244
|
return printUsageError(json, "claudexor: missing prompt");
|
|
282
245
|
}
|
|
283
246
|
const portfolioRaw = flagStr(args, "portfolio");
|
|
@@ -297,10 +260,10 @@ async function orchestrate(args, mode, json, forced = {}) {
|
|
|
297
260
|
let resolvedAccess = undefined;
|
|
298
261
|
let resolvedEffort;
|
|
299
262
|
let paidBudget;
|
|
300
|
-
let maxToolCalls;
|
|
301
263
|
let nFlag;
|
|
302
264
|
let attemptsFlag;
|
|
303
|
-
let
|
|
265
|
+
let delegate;
|
|
266
|
+
let council;
|
|
304
267
|
let resolvedSynthesis = undefined;
|
|
305
268
|
let resolvedHarnesses;
|
|
306
269
|
let resolvedPrimaryHarness;
|
|
@@ -324,10 +287,10 @@ async function orchestrate(args, mode, json, forced = {}) {
|
|
|
324
287
|
resolvedModel = flagStr(args, "model");
|
|
325
288
|
const maxUsd = floatFlag(args, "max-usd");
|
|
326
289
|
paidBudget = maxUsd === undefined ? undefined : { kind: "finite", maxUsd };
|
|
327
|
-
maxToolCalls = intFlag(args, "max-tool-calls");
|
|
328
290
|
nFlag = intFlag(args, "n");
|
|
329
291
|
attemptsFlag = intFlag(args, "attempts");
|
|
330
|
-
|
|
292
|
+
delegate = flagBool(args, "delegate") ? true : undefined;
|
|
293
|
+
council = flagBool(args, "council") ? true : undefined;
|
|
331
294
|
resolvedSynthesis = synthesisMode(args);
|
|
332
295
|
attachmentRequest = attachmentInputs(args);
|
|
333
296
|
resolvedProtectedPathApprovals = protectedPathApprovals(args);
|
|
@@ -364,7 +327,7 @@ async function orchestrate(args, mode, json, forced = {}) {
|
|
|
364
327
|
let tests;
|
|
365
328
|
try {
|
|
366
329
|
const cliTests = testCommands(args) ?? [];
|
|
367
|
-
tests =
|
|
330
|
+
tests = cliTests.length > 0 ? cliTests : undefined;
|
|
368
331
|
assertCliRunParamsHaveNoInlineSecrets({
|
|
369
332
|
prompt,
|
|
370
333
|
instructions: resolvedInstructions,
|
|
@@ -384,24 +347,21 @@ async function orchestrate(args, mode, json, forced = {}) {
|
|
|
384
347
|
web: resolvedWebPolicy,
|
|
385
348
|
externalContextPolicy: resolvedWebPolicy,
|
|
386
349
|
synthesis: resolvedSynthesis,
|
|
387
|
-
autonomy,
|
|
388
|
-
specId: spec?.id,
|
|
389
|
-
specHash: loadedSpec?.specHash,
|
|
390
|
-
specPath: loadedSpec?.specPath,
|
|
391
350
|
});
|
|
392
351
|
}
|
|
393
352
|
catch (err) {
|
|
394
353
|
return printUsageError(json, `claudexor: ${err instanceof Error ? err.message : String(err)}`);
|
|
395
354
|
}
|
|
396
|
-
if (
|
|
397
|
-
return printUsageError(json, `claudexor: --
|
|
355
|
+
if (delegate && mode !== "agent") {
|
|
356
|
+
return printUsageError(json, `claudexor: --delegate is an agent strategy (got mode '${mode}')`);
|
|
398
357
|
}
|
|
399
|
-
if (
|
|
400
|
-
return printUsageError(json,
|
|
358
|
+
if (council && mode !== "plan") {
|
|
359
|
+
return printUsageError(json, `claudexor: --council is a plan strategy (got mode '${mode}')`);
|
|
401
360
|
}
|
|
402
361
|
return daemonRun(args, json, {
|
|
403
362
|
mode,
|
|
404
|
-
|
|
363
|
+
delegate,
|
|
364
|
+
council,
|
|
405
365
|
prompt: prompt || "audit this repository",
|
|
406
366
|
instructions: resolvedInstructions,
|
|
407
367
|
maxSeconds: resolvedMaxSeconds,
|
|
@@ -412,7 +372,6 @@ async function orchestrate(args, mode, json, forced = {}) {
|
|
|
412
372
|
paidBudget,
|
|
413
373
|
routingGoal: routingGoal?.success ? routingGoal.data : undefined,
|
|
414
374
|
credentialProfileId,
|
|
415
|
-
maxToolCalls,
|
|
416
375
|
reviewerPanel: resolvedReviewerPanel,
|
|
417
376
|
reviewerModels: resolvedReviewerModels,
|
|
418
377
|
reviewerEfforts: reviewerEffortOverrides,
|
|
@@ -426,9 +385,6 @@ async function orchestrate(args, mode, json, forced = {}) {
|
|
|
426
385
|
resolvedModel,
|
|
427
386
|
nFlag,
|
|
428
387
|
attemptsFlag,
|
|
429
|
-
specId: spec?.id,
|
|
430
|
-
specHash: loadedSpec?.specHash,
|
|
431
|
-
specPath: loadedSpec?.specPath,
|
|
432
388
|
attachmentRequest,
|
|
433
389
|
forced,
|
|
434
390
|
});
|
|
@@ -459,10 +415,11 @@ async function daemonRun(args, json, p) {
|
|
|
459
415
|
process.stderr.write(`claudexor: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
460
416
|
return 1;
|
|
461
417
|
}
|
|
462
|
-
// Thread continuation (W13): --thread <id> targets a thread explicitly;
|
|
463
|
-
// --resume picks the most recently updated one.
|
|
464
|
-
//
|
|
465
|
-
//
|
|
418
|
+
// Thread continuation (W13/D10): --thread <id> targets a thread explicitly;
|
|
419
|
+
// --resume picks the most recently updated one. When a threadId is present
|
|
420
|
+
// the run is enqueued through POST /threads/:id/turns (enqueueAndAwait routes
|
|
421
|
+
// it there) — the ONE turn-creation path that owns scope, lineage, and the
|
|
422
|
+
// continuation packet. POST /runs itself now refuses threadId.
|
|
466
423
|
let threadId = flagStr(args, "thread");
|
|
467
424
|
if (threadId === undefined && flagBool(args, "resume")) {
|
|
468
425
|
try {
|
|
@@ -474,11 +431,13 @@ async function daemonRun(args, json, p) {
|
|
|
474
431
|
// Parse through the typed DTO — the field is camelCase `updatedAt`, and a
|
|
475
432
|
// hand-rolled `updated_at` read silently sorts undefined and throws.
|
|
476
433
|
const list = ControlThreadListResponse.parse(await res.json());
|
|
477
|
-
|
|
434
|
+
// Scope to the current project (D28): --resume must never cross into
|
|
435
|
+
// another project's threads.
|
|
436
|
+
const newest = pickResumableThread(list.threads, process.cwd());
|
|
478
437
|
if (!newest) {
|
|
479
438
|
// Stay valid on the active output surface: NDJSON stream keeps compact
|
|
480
439
|
// lines; --json prints its one object; text goes to stderr.
|
|
481
|
-
const message = "claudexor: --resume found no threads to continue";
|
|
440
|
+
const message = "claudexor: --resume found no threads to continue in this project";
|
|
482
441
|
if (jsonStream)
|
|
483
442
|
printJsonLine({ ok: false, exitCode: 1, error: message });
|
|
484
443
|
else if (json)
|
|
@@ -527,7 +486,8 @@ async function daemonRun(args, json, p) {
|
|
|
527
486
|
...(attachmentRefs ? { attachments: attachmentRefs } : {}),
|
|
528
487
|
mode: p.mode,
|
|
529
488
|
...(threadId ? { threadId } : {}),
|
|
530
|
-
...(p.
|
|
489
|
+
...(p.delegate ? { delegate: true } : {}),
|
|
490
|
+
...(p.council ? { council: true } : {}),
|
|
531
491
|
scope: { kind: "project", root: process.cwd() },
|
|
532
492
|
execution: { isolation: inPlace ? "live" : "envelope" },
|
|
533
493
|
...(p.resolvedHarnesses ? { harnesses: p.resolvedHarnesses } : {}),
|
|
@@ -537,15 +497,12 @@ async function daemonRun(args, json, p) {
|
|
|
537
497
|
...(p.forced.race === true ? { n: p.nFlag ?? 2 } : p.nFlag !== undefined ? { n: p.nFlag } : {}),
|
|
538
498
|
...(p.attemptsFlag !== undefined ? { attempts: p.attemptsFlag } : {}),
|
|
539
499
|
...(flagBool(args, "until-clean") ? { untilClean: true } : {}),
|
|
540
|
-
...(p.forced.
|
|
500
|
+
...(p.forced.deepScan === true || flagBool(args, "deep-scan") ? { deepScan: true } : {}),
|
|
541
501
|
...(p.forced.create === true || flagBool(args, "create") ? { create: true } : {}),
|
|
542
502
|
...(p.resolvedSynthesis ? { synthesis: p.resolvedSynthesis } : {}),
|
|
543
503
|
...(p.tests ? { tests: p.tests } : {}),
|
|
544
504
|
...(p.protectedPathApprovals ? { protectedPathApprovals: p.protectedPathApprovals } : {}),
|
|
545
505
|
...(p.paidBudget !== undefined ? { paidBudget: p.paidBudget } : {}),
|
|
546
|
-
...(p.mode === "orchestrate" && p.maxToolCalls !== undefined
|
|
547
|
-
? { maxToolCalls: p.maxToolCalls }
|
|
548
|
-
: {}),
|
|
549
506
|
...(p.resolvedAccess ? { access: p.resolvedAccess } : {}),
|
|
550
507
|
...(p.resolvedWebPolicy ? { web: p.resolvedWebPolicy } : {}),
|
|
551
508
|
...(p.resolvedModel ? { model: p.resolvedModel } : {}),
|
|
@@ -553,9 +510,6 @@ async function daemonRun(args, json, p) {
|
|
|
553
510
|
...(p.reviewerPanel ? { reviewerPanel: p.reviewerPanel } : {}),
|
|
554
511
|
...(p.reviewerModels ? { reviewerModels: p.reviewerModels } : {}),
|
|
555
512
|
...(p.reviewerEfforts ? { reviewerEfforts: p.reviewerEfforts } : {}),
|
|
556
|
-
...(p.specId ? { specId: p.specId } : {}),
|
|
557
|
-
...(p.specHash ? { specHash: p.specHash } : {}),
|
|
558
|
-
...(p.specPath ? { specPath: p.specPath } : {}),
|
|
559
513
|
};
|
|
560
514
|
try {
|
|
561
515
|
if (jsonStream) {
|
|
@@ -570,7 +524,7 @@ async function daemonRun(args, json, p) {
|
|
|
570
524
|
frame: "run.terminal",
|
|
571
525
|
runId: "",
|
|
572
526
|
runDir: started.runDir,
|
|
573
|
-
status:
|
|
527
|
+
status: started.status,
|
|
574
528
|
jobId: started.jobId,
|
|
575
529
|
mode: p.mode,
|
|
576
530
|
...(started.error ? { error: started.error } : {}),
|
|
@@ -592,12 +546,13 @@ async function daemonRun(args, json, p) {
|
|
|
592
546
|
const out = {
|
|
593
547
|
runId: started.runId,
|
|
594
548
|
runDir: final?.runDir ?? started.runDir,
|
|
595
|
-
status:
|
|
549
|
+
status: status,
|
|
596
550
|
jobId: started.jobId,
|
|
597
551
|
error: final?.error ?? started.error,
|
|
598
552
|
};
|
|
599
553
|
const reason = daemonOutcomeSummary({ ...started, status, error: out.error });
|
|
600
554
|
const applyEligibility = await fetchApplyEligibility(addr, started.runId);
|
|
555
|
+
const outcomeBanner = await fetchOutcomeBanner(addr, started.runId);
|
|
601
556
|
printJsonLine({
|
|
602
557
|
frame: "run.terminal",
|
|
603
558
|
runId: out.runId,
|
|
@@ -607,6 +562,7 @@ async function daemonRun(args, json, p) {
|
|
|
607
562
|
mode: p.mode,
|
|
608
563
|
...(out.error ? { error: out.error } : {}),
|
|
609
564
|
...(reason ? { summary: reason } : {}),
|
|
565
|
+
...(outcomeBanner ? { outcomeBanner } : {}),
|
|
610
566
|
...(applyEligibility ? { applyEligibility } : {}),
|
|
611
567
|
});
|
|
612
568
|
return exitCodeForState(status);
|
|
@@ -620,14 +576,16 @@ async function daemonRun(args, json, p) {
|
|
|
620
576
|
// apply-gate verdict, so machine callers act on truth instead of
|
|
621
577
|
// re-implying eligibility from status.
|
|
622
578
|
const applyEligibility = await fetchApplyEligibility(addr, out.runId);
|
|
579
|
+
const outcomeBanner = await fetchOutcomeBanner(addr, out.runId);
|
|
623
580
|
printJson({
|
|
624
581
|
runId: out.runId,
|
|
625
582
|
runDir: out.runDir,
|
|
626
|
-
status:
|
|
583
|
+
status: out.status,
|
|
627
584
|
jobId: out.jobId,
|
|
628
585
|
mode: p.mode,
|
|
629
586
|
...(out.error ? { error: out.error } : {}),
|
|
630
587
|
...(reason ? { summary: reason } : {}),
|
|
588
|
+
...(outcomeBanner ? { outcomeBanner } : {}),
|
|
631
589
|
...(applyEligibility ? { applyEligibility } : {}),
|
|
632
590
|
});
|
|
633
591
|
return exitCodeForState(out.status);
|
|
@@ -643,24 +601,55 @@ async function daemonRun(args, json, p) {
|
|
|
643
601
|
await followRun(started.runId, false);
|
|
644
602
|
const final = started.jobId ? await client.status(started.jobId) : null;
|
|
645
603
|
const status = final?.state ?? started.status;
|
|
646
|
-
const publicStatus =
|
|
604
|
+
const publicStatus = status;
|
|
647
605
|
print("");
|
|
648
606
|
print(`run ${started.runId} [${publicStatus}]`);
|
|
607
|
+
// Server-owned outcome headline (D18), printed verbatim above any output.
|
|
608
|
+
const terminalBanner = await fetchOutcomeBanner(addr, started.runId);
|
|
609
|
+
if (terminalBanner)
|
|
610
|
+
print(` ${terminalBanner}`);
|
|
649
611
|
print(` artifacts: ${final?.runDir ?? started.runDir}`);
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
612
|
+
// A succeeded lifecycle exits 0 — INCLUDING a "Done · needs review" run
|
|
613
|
+
// (review blocked / checks failed). The apply-eligibility verdict (state
|
|
614
|
+
// needs_review) is the ONE source for the unblock guidance (D8).
|
|
615
|
+
// The plan question loop advances this past each answered follow-up turn.
|
|
616
|
+
let effectiveRunId = started.runId;
|
|
617
|
+
if (exitCodeForState(status) === 0) {
|
|
618
|
+
// Plan runs: server-derived readiness (D17) + interactive answer loop.
|
|
619
|
+
if (p.mode === "plan") {
|
|
620
|
+
// Council disclosure (INV-031): membership + merge, projected by the
|
|
621
|
+
// server (never a client re-derivation).
|
|
622
|
+
if (p.council) {
|
|
623
|
+
const council = await fetchCouncil(addr, started.runId);
|
|
624
|
+
if (council) {
|
|
625
|
+
print(` council: merged by ${council.mergedBy ?? "(none)"} from ${council.drafted} of ${council.requested} member(s)${council.degraded ? " (degraded)" : ""}`);
|
|
626
|
+
const failed = council.members.filter((m) => m.status === "failed");
|
|
627
|
+
if (failed.length > 0) {
|
|
628
|
+
print(` council failures: ${failed.map((m) => m.harnessId).join(", ")}`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
effectiveRunId = await runPlanQuestionLoop({
|
|
633
|
+
client,
|
|
634
|
+
addr,
|
|
635
|
+
threadId,
|
|
636
|
+
runId: started.runId,
|
|
637
|
+
interactive: !json && !jsonStream, // TTY plan turns answer inline
|
|
638
|
+
});
|
|
639
|
+
}
|
|
654
640
|
// Offer apply only after a positive gate; otherwise print inspect/unblock guidance.
|
|
655
|
-
const eligibility = await fetchApplyEligibility(addr,
|
|
641
|
+
const eligibility = await fetchApplyEligibility(addr, effectiveRunId);
|
|
656
642
|
if (eligibility?.eligible) {
|
|
657
|
-
print(` apply with: claudexor apply ${
|
|
643
|
+
print(` apply with: claudexor apply ${effectiveRunId}`);
|
|
644
|
+
}
|
|
645
|
+
else if (eligibility?.state === "needs_review") {
|
|
646
|
+
print(` needs review: unblock with \`claudexor decision ${effectiveRunId} --accept-risk\` or rerun with \`claudexor decision ${effectiveRunId} --rerun --feedback "..."\``);
|
|
658
647
|
}
|
|
659
648
|
else if (eligibility?.requiredAction) {
|
|
660
649
|
print(` not applyable yet: ${eligibility.requiredAction}`);
|
|
661
650
|
}
|
|
662
651
|
else {
|
|
663
|
-
print(` inspect with: claudexor inspect ${
|
|
652
|
+
print(` inspect with: claudexor inspect ${effectiveRunId}`);
|
|
664
653
|
}
|
|
665
654
|
}
|
|
666
655
|
return exitCodeForState(status);
|
|
@@ -739,7 +728,7 @@ async function decisionCommand(args, json) {
|
|
|
739
728
|
* Resolve the ArtifactStore that owns a given run, regardless of the cwd the
|
|
740
729
|
* CLI is invoked from. Order:
|
|
741
730
|
* 1. the project store rooted at the current cwd (the common case);
|
|
742
|
-
* 2. the
|
|
731
|
+
* 2. the user store (~/.claudexor/v3/runs) used by no-project Ask runs;
|
|
743
732
|
* 3. a daemon-tracked run that started in ANOTHER project — agent/race/create
|
|
744
733
|
* runs live under that project's external runtime namespace, so we ask
|
|
745
734
|
* the daemon for the run's absolute runDir (GET /runs/:id ->
|
|
@@ -793,131 +782,6 @@ async function resolveRunStore(runId) {
|
|
|
793
782
|
}
|
|
794
783
|
return null;
|
|
795
784
|
}
|
|
796
|
-
async function specCommand(args, json) {
|
|
797
|
-
const prompt = args._.slice(1).join(" ").trim();
|
|
798
|
-
if (!prompt) {
|
|
799
|
-
return printUsageError(json, "claudexor: missing spec prompt");
|
|
800
|
-
}
|
|
801
|
-
if (containsSecretLikeToken(prompt)) {
|
|
802
|
-
return printUsageError(json, "claudexor spec: prompt contains a secret-like token; specs are durable artifacts, so store secrets by ref and retry with a sanitized prompt");
|
|
803
|
-
}
|
|
804
|
-
const answersPath = flagStr(args, "answers");
|
|
805
|
-
// The grounding step is a real (multi-)harness plan run, so the same
|
|
806
|
-
// routing/cost/review controls as the plan verb apply — but ONLY that step
|
|
807
|
-
// spawns a run. Parse them up-front so malformed values fail loudly on
|
|
808
|
-
// every path, and refuse ALL grounding-only flags on the --answers path,
|
|
809
|
-
// where no grounding run exists for them to control.
|
|
810
|
-
let groundingEffort;
|
|
811
|
-
let groundingMaxUsd;
|
|
812
|
-
let groundingHarnesses;
|
|
813
|
-
let groundingN;
|
|
814
|
-
let groundingWeb;
|
|
815
|
-
let groundingReviewerPanel;
|
|
816
|
-
let groundingReviewerModels;
|
|
817
|
-
let groundingReviewerEfforts;
|
|
818
|
-
try {
|
|
819
|
-
groundingEffort = effortHint(args);
|
|
820
|
-
groundingMaxUsd = floatFlag(args, "max-usd");
|
|
821
|
-
groundingHarnesses = harnessList(args);
|
|
822
|
-
groundingN = intFlag(args, "n");
|
|
823
|
-
groundingWeb = webPolicy(args);
|
|
824
|
-
groundingReviewerPanel = reviewerPanel(args);
|
|
825
|
-
groundingReviewerModels = reviewerModels(args);
|
|
826
|
-
groundingReviewerEfforts = reviewerEfforts(args);
|
|
827
|
-
}
|
|
828
|
-
catch (err) {
|
|
829
|
-
return printUsageError(json, `claudexor: ${err instanceof Error ? err.message : String(err)}`);
|
|
830
|
-
}
|
|
831
|
-
if (answersPath &&
|
|
832
|
-
(groundingEffort !== undefined ||
|
|
833
|
-
groundingMaxUsd !== undefined ||
|
|
834
|
-
groundingHarnesses !== undefined ||
|
|
835
|
-
groundingN !== undefined ||
|
|
836
|
-
groundingWeb !== undefined ||
|
|
837
|
-
groundingReviewerPanel !== undefined ||
|
|
838
|
-
groundingReviewerModels !== undefined ||
|
|
839
|
-
groundingReviewerEfforts !== undefined)) {
|
|
840
|
-
return printUsageError(json, "claudexor spec: --harness/--n/--web/--effort/--max-usd/--reviewer-panel/--reviewer-model/--reviewer-effort control the grounding plan run and only apply when generating questions; drop them when re-running with --answers");
|
|
841
|
-
}
|
|
842
|
-
try {
|
|
843
|
-
const { addr } = await ensureDaemon();
|
|
844
|
-
await controlJson(addr, "/projects", {
|
|
845
|
-
method: "POST",
|
|
846
|
-
headers: { "content-type": "application/json" },
|
|
847
|
-
body: JSON.stringify({ root: process.cwd() }),
|
|
848
|
-
});
|
|
849
|
-
if (!answersPath) {
|
|
850
|
-
const session = ControlSpecSession.parse(await controlJson(addr, "/spec/sessions", {
|
|
851
|
-
method: "POST",
|
|
852
|
-
headers: { "content-type": "application/json" },
|
|
853
|
-
body: JSON.stringify({
|
|
854
|
-
prompt,
|
|
855
|
-
scope: { kind: "project", root: process.cwd(), context: "auto" },
|
|
856
|
-
harnesses: groundingHarnesses,
|
|
857
|
-
n: groundingN,
|
|
858
|
-
effort: groundingEffort,
|
|
859
|
-
paidBudget: groundingMaxUsd === undefined
|
|
860
|
-
? undefined
|
|
861
|
-
: { kind: "finite", maxUsd: groundingMaxUsd },
|
|
862
|
-
web: groundingWeb,
|
|
863
|
-
reviewerPanel: groundingReviewerPanel,
|
|
864
|
-
reviewerModels: groundingReviewerModels,
|
|
865
|
-
reviewerEfforts: groundingReviewerEfforts,
|
|
866
|
-
}),
|
|
867
|
-
}));
|
|
868
|
-
if (json)
|
|
869
|
-
printJson(session);
|
|
870
|
-
else {
|
|
871
|
-
print(`durable spec session: ${session.sessionId}`);
|
|
872
|
-
print(`grounding run: ${session.planRunId ?? "pending"}`);
|
|
873
|
-
for (const question of session.questions)
|
|
874
|
-
print(`- [${question.id}] ${question.prompt}`);
|
|
875
|
-
print("save `claudexor spec ... --json` output, fill its answers array, then pass it with --answers");
|
|
876
|
-
}
|
|
877
|
-
return session.state === "failed" || session.state === "interrupted_unknown" ? 1 : 0;
|
|
878
|
-
}
|
|
879
|
-
const answers = readAnswers(answersPath);
|
|
880
|
-
if (!answers.sessionId) {
|
|
881
|
-
throw new Error("answers file must contain sessionId from the durable spec session");
|
|
882
|
-
}
|
|
883
|
-
const encodedId = encodeURIComponent(answers.sessionId);
|
|
884
|
-
await controlJson(addr, `/spec/sessions/${encodedId}/answers`, {
|
|
885
|
-
method: "POST",
|
|
886
|
-
headers: { "content-type": "application/json" },
|
|
887
|
-
body: JSON.stringify({
|
|
888
|
-
answers: answers.answers,
|
|
889
|
-
priorDecisions: answers.priorDecisions,
|
|
890
|
-
}),
|
|
891
|
-
});
|
|
892
|
-
const session = ControlSpecSession.parse(await controlJson(addr, `/spec/sessions/${encodedId}/freeze`, { method: "POST" }));
|
|
893
|
-
if (session.state !== "frozen" || !session.specPath || !session.specId || !session.specHash) {
|
|
894
|
-
throw new Error(`spec session ended as ${session.state}`);
|
|
895
|
-
}
|
|
896
|
-
const runHint = `claudexor best-of --spec ${JSON.stringify(session.specPath)}`;
|
|
897
|
-
if (json)
|
|
898
|
-
printJson({ ...session, status: "frozen", runHint });
|
|
899
|
-
else {
|
|
900
|
-
print(`frozen SpecPack: ${session.specId}`);
|
|
901
|
-
print(` path: ${session.specPath}`);
|
|
902
|
-
print(` hash: ${session.specHash}`);
|
|
903
|
-
print(`run: ${runHint}`);
|
|
904
|
-
}
|
|
905
|
-
return 0;
|
|
906
|
-
}
|
|
907
|
-
catch (err) {
|
|
908
|
-
// Same runtime-error envelope contract as the run/race commands: --json
|
|
909
|
-
// callers get a machine-readable {ok:false} on stdout, never bare stderr.
|
|
910
|
-
if (json)
|
|
911
|
-
printJson({
|
|
912
|
-
ok: false,
|
|
913
|
-
exitCode: 1,
|
|
914
|
-
error: `claudexor spec: ${err instanceof Error ? err.message : String(err)}`,
|
|
915
|
-
});
|
|
916
|
-
else
|
|
917
|
-
process.stderr.write(`claudexor spec: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
918
|
-
return 1;
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
785
|
async function controlJson(addr, path, init = {}) {
|
|
922
786
|
const response = await controlApiFetch(addr, path, init);
|
|
923
787
|
const text = await response.text();
|
|
@@ -976,7 +840,7 @@ async function main() {
|
|
|
976
840
|
if (valueFlagError)
|
|
977
841
|
return printPreflightError(args, json, valueFlagError);
|
|
978
842
|
// Registry-enforced per-command flag scope: a KNOWN flag outside the
|
|
979
|
-
// command's declared set (e.g. `
|
|
843
|
+
// command's declared set (e.g. `plan --create`, `ask --force`) fails loudly
|
|
980
844
|
// instead of being silently ignored. Data-driven from CLI_COMMANDS for
|
|
981
845
|
// every verb (this replaced the old hand-listed plugin/--force special cases).
|
|
982
846
|
const scopeError = commandFlagScopeError(cmd, Object.keys(args.flags));
|
|
@@ -1003,65 +867,44 @@ async function main() {
|
|
|
1003
867
|
case "project":
|
|
1004
868
|
return projectCommand(args, json);
|
|
1005
869
|
case "agent": {
|
|
1006
|
-
const specStrategyError = "claudexor: --spec requires a gated strategy; use 'claudexor best-of --spec <file>' or 'claudexor agent --attempts N --spec <file>'";
|
|
1007
|
-
// ONE gate for both spellings: `agent --spec` and `agent --mode agent --spec`
|
|
1008
|
-
// must enforce the same gated-strategy requirement (a flag spelling must
|
|
1009
|
-
// never bypass a policy the bare verb enforces).
|
|
1010
|
-
const agentSpecGateError = () => {
|
|
1011
|
-
if (!flagStr(args, "spec") || flagBool(args, "until-clean"))
|
|
1012
|
-
return null;
|
|
1013
|
-
try {
|
|
1014
|
-
const hasGatedStrategy = intFlag(args, "attempts") !== undefined || intFlag(args, "n") !== undefined;
|
|
1015
|
-
return hasGatedStrategy ? null : specStrategyError;
|
|
1016
|
-
}
|
|
1017
|
-
catch (err) {
|
|
1018
|
-
return `claudexor: ${err instanceof Error ? err.message : String(err)}`;
|
|
1019
|
-
}
|
|
1020
|
-
};
|
|
1021
870
|
const modeStr = flagStr(args, "mode");
|
|
1022
871
|
if (modeStr !== undefined) {
|
|
1023
872
|
const mode = normalizeMode(modeStr);
|
|
1024
873
|
if (!MODES.has(mode)) {
|
|
1025
874
|
return printUsageError(json, `claudexor: unknown --mode '${modeStr}'. valid: ${[...MODES].join(", ")}`);
|
|
1026
875
|
}
|
|
1027
|
-
if ((mode === "ask" || mode === "audit") && flagStr(args, "spec")) {
|
|
1028
|
-
return printUsageError(json, specStrategyError);
|
|
1029
|
-
}
|
|
1030
|
-
if (mode === "agent") {
|
|
1031
|
-
const gateError = agentSpecGateError();
|
|
1032
|
-
if (gateError)
|
|
1033
|
-
return printUsageError(json, gateError);
|
|
1034
|
-
}
|
|
1035
876
|
return orchestrate(args, mode, json);
|
|
1036
877
|
}
|
|
1037
|
-
const gateError = agentSpecGateError();
|
|
1038
|
-
if (gateError)
|
|
1039
|
-
return printUsageError(json, gateError);
|
|
1040
878
|
return orchestrate(args, "agent", json);
|
|
1041
879
|
}
|
|
1042
880
|
case "ask":
|
|
1043
881
|
return orchestrate(args, "ask", json);
|
|
1044
|
-
case "explore":
|
|
1045
|
-
return orchestrate(args, "audit", json, { swarm: true });
|
|
1046
882
|
case "best-of":
|
|
1047
883
|
return orchestrate(args, "agent", json, { race: true });
|
|
1048
|
-
//
|
|
1049
|
-
// same doctrine as retired mode ids:
|
|
884
|
+
// RETIRED verb spellings hard-error with the new spelling — no silent
|
|
885
|
+
// aliases (same doctrine as retired mode ids: stale scripts fail loudly).
|
|
1050
886
|
case "run":
|
|
1051
|
-
return printPreflightError(args, json, "claudexor: the 'run' verb was renamed; use 'claudexor agent' (same flags)");
|
|
1052
887
|
case "race":
|
|
1053
|
-
|
|
888
|
+
case "audit":
|
|
889
|
+
case "map":
|
|
890
|
+
case "explore":
|
|
1054
891
|
case "orchestrate":
|
|
1055
|
-
|
|
892
|
+
case "spec": {
|
|
893
|
+
const replacement = cmd === "run"
|
|
894
|
+
? "claudexor agent (same flags)"
|
|
895
|
+
: cmd === "race"
|
|
896
|
+
? "claudexor best-of (same flags)"
|
|
897
|
+
: cmd === "orchestrate"
|
|
898
|
+
? "claudexor agent --delegate <prompt> (the harness spawns bounded isolated sub-runs; suggest-only planning is ordinary claudexor plan)"
|
|
899
|
+
: cmd === "spec"
|
|
900
|
+
? "claudexor plan <prompt> then Implement (the plan lifecycle surfaces open questions and freezes the plan on implement)"
|
|
901
|
+
: "claudexor ask --deep-scan <prompt>";
|
|
902
|
+
return printPreflightError(args, json, `claudexor: the '${cmd}' verb was retired; use ${replacement}`);
|
|
903
|
+
}
|
|
1056
904
|
case "plan":
|
|
1057
905
|
return orchestrate(args, "plan", json);
|
|
1058
|
-
case "spec":
|
|
1059
|
-
return specCommand(args, json);
|
|
1060
906
|
case "create":
|
|
1061
907
|
return orchestrate(args, "agent", json, { create: true });
|
|
1062
|
-
case "audit":
|
|
1063
|
-
case "map":
|
|
1064
|
-
return orchestrate(args, "audit", json);
|
|
1065
908
|
case "settings":
|
|
1066
909
|
return settingsCommand(args, json);
|
|
1067
910
|
case "quota":
|
|
@@ -1071,6 +914,10 @@ async function main() {
|
|
|
1071
914
|
case "mcp": {
|
|
1072
915
|
if (args._[1] === "serve")
|
|
1073
916
|
return serveMcpBridge();
|
|
917
|
+
// Internal: the scoped delegation belt injected into a delegate agent
|
|
918
|
+
// run's sandbox (D32). Not a user-facing verb.
|
|
919
|
+
if (args._[1] === "serve-belt")
|
|
920
|
+
return serveBeltBridge();
|
|
1074
921
|
return printUsageError(json, "usage: claudexor mcp serve");
|
|
1075
922
|
}
|
|
1076
923
|
case "acp": {
|
|
@@ -1189,7 +1036,8 @@ async function main() {
|
|
|
1189
1036
|
}
|
|
1190
1037
|
if (parsedDecision.success) {
|
|
1191
1038
|
const vb = parsedDecision.data.verification_basis;
|
|
1192
|
-
|
|
1039
|
+
const f = parsedDecision.data.facts;
|
|
1040
|
+
print(`decision: ${runOutcomeLabel(f)} (lifecycle=${f.lifecycle} checks=${f.checks} review=${f.review}${f.reason ? ` reason=${f.reason}` : ""}) apply=${parsedDecision.data.apply_recommendation}${vb !== "none" ? ` verified_by=${vb}` : ""}`);
|
|
1193
1041
|
const budget = parsedDecision.data.budget_summary;
|
|
1194
1042
|
print(`budget: spend=${budget.spend_usd ?? "unknown"}${budget.estimated ? " estimated" : ""}`);
|
|
1195
1043
|
}
|
|
@@ -1280,27 +1128,8 @@ async function main() {
|
|
|
1280
1128
|
}
|
|
1281
1129
|
case "decision":
|
|
1282
1130
|
return decisionCommand(args, json);
|
|
1283
|
-
case "release":
|
|
1284
|
-
|
|
1285
|
-
const name = args._[2] ?? "claudexor";
|
|
1286
|
-
const checks = await checkName(name);
|
|
1287
|
-
if (json)
|
|
1288
|
-
printJson({ name, checks });
|
|
1289
|
-
else {
|
|
1290
|
-
print(`naming gate for "${name}":`);
|
|
1291
|
-
for (const c of checks) {
|
|
1292
|
-
const tag = c.availability === "free"
|
|
1293
|
-
? "[free] "
|
|
1294
|
-
: c.availability === "taken"
|
|
1295
|
-
? "[taken] "
|
|
1296
|
-
: "[unknown]";
|
|
1297
|
-
print(` ${tag} ${c.registry}: ${c.detail}`);
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
return 0;
|
|
1301
|
-
}
|
|
1302
|
-
return printUsageError(json, "usage: claudexor release check-name <name>");
|
|
1303
|
-
}
|
|
1131
|
+
case "release":
|
|
1132
|
+
return releaseCommand(args, json);
|
|
1304
1133
|
case "plugin": {
|
|
1305
1134
|
const sub = args._[1];
|
|
1306
1135
|
const target = args._[2];
|