jules-orchestrator-kit 0.72.2 → 0.73.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/.agent/prompts/{Overseer.md → Auditor.md} +3 -3
- package/.agent/prompts/{Alchemist.md → Database.md} +1 -1
- package/.agent/prompts/Debugger.md +25 -0
- package/.agent/prompts/{Scribe.md → Docs.md} +8 -5
- package/.agent/prompts/{Spectator.md → E2E.md} +9 -6
- package/.agent/prompts/{Janitor.md → Hygiene.md} +2 -2
- package/.agent/prompts/{Bolt.md → Performance.md} +1 -1
- package/.agent/prompts/Resilience.md +20 -0
- package/.agent/prompts/Security.md +21 -0
- package/.agent/prompts/Testing.md +30 -0
- package/.agent/prompts/Types.md +19 -0
- package/.agent/rules/jules-protocol.md +4 -3
- package/AGENTS.md +78 -96
- package/CHANGELOG.md +193 -0
- package/JULES_RULES_TEMPLATE.md +83 -96
- package/LICENSE +1 -1
- package/README.md +77 -439
- package/ROADMAP_V1.md +22 -132
- package/bin/agentctl.mjs +443 -144
- package/bin/init.js +6 -3
- package/index.mjs +9 -6
- package/package.json +1 -1
- package/scripts/asset-integrity-check.mjs +1 -1
- package/scripts/doc-sync-check.mjs +47 -0
- package/scripts/generate-command-reference.mjs +39 -0
- package/scripts/jules-dispatch.mjs +12 -113
- package/scripts/jules-merge-swarm.mjs +8 -196
- package/scripts/jules-patch.mjs +7 -8
- package/scripts/jules-queue-runner.mjs +6 -8
- package/scripts/jules-scan-todos.mjs +10 -38
- package/scripts/jules-self-audit.mjs +8 -139
- package/scripts/jules-status.mjs +32 -38
- package/scripts/jules-webhook-receiver.mjs +1 -1
- package/src/assertions.mjs +5 -50
- package/src/bidi-guard.mjs +36 -0
- package/src/budget.mjs +3 -14
- package/src/config.mjs +2 -6
- package/src/dashboard.mjs +7 -9
- package/src/dispatch.mjs +212 -0
- package/src/engine.mjs +40 -16
- package/src/evidence.mjs +10 -41
- package/src/execution-envelope.mjs +13 -1
- package/src/flaky-ledger.mjs +1 -1
- package/src/fs-atomic.mjs +72 -0
- package/src/git.mjs +298 -27
- package/src/mcp.mjs +296 -7
- package/src/memory.mjs +0 -0
- package/src/merge-swarm.mjs +202 -0
- package/src/ops/cli-intent.mjs +1 -0
- package/src/ops/command-registry.mjs +796 -70
- package/src/ops/doctor-registry.mjs +134 -47
- package/src/ops/handover.mjs +3 -27
- package/src/ops/pr-harvest.mjs +1 -1
- package/src/prompt-guard.mjs +33 -3
- package/src/provider.mjs +51 -7
- package/src/remediation.mjs +2 -2
- package/src/role-resolver.mjs +113 -3
- package/src/router.mjs +19 -11
- package/src/runtime-env.mjs +67 -0
- package/src/scaffold.mjs +3 -1
- package/src/scope-guard.mjs +249 -0
- package/src/secret-scanner.mjs +530 -0
- package/src/security.mjs +81 -2972
- package/src/self-audit.mjs +140 -0
- package/src/session-ops.mjs +29 -0
- package/src/stability.mjs +8 -1
- package/src/stack-detector.mjs +5 -2
- package/src/state.mjs +45 -0
- package/src/swarm.mjs +76 -0
- package/src/task-optimizer.mjs +34 -7
- package/src/telemetry.mjs +23 -0
- package/src/test-tamper-guard.mjs +2173 -0
- package/src/todo-scanner.mjs +129 -0
- package/src/web-templates.mjs +3 -3
- package/src/webhook.mjs +10 -3
- package/src/wizard-init.mjs +12 -20
- package/src/wizard-oracle.mjs +4 -3
- package/src/wizard-task.mjs +37 -9
- package/.agent/prompts/Sentinel.md +0 -18
- package/scripts/utils.mjs +0 -241
package/bin/agentctl.mjs
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
|
-
import { readFileSync, existsSync, readdirSync } from "node:fs";
|
|
4
|
+
import { readFileSync, existsSync, readdirSync, statSync, renameSync, mkdirSync } from "node:fs";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
6
|
import { applyEnvAliases } from "../src/env-aliases.mjs";
|
|
7
7
|
import { selectFailureOutput } from "../src/ops/verify-output.mjs";
|
|
8
8
|
import { loadConfig, resolveRoot, detectStack, bootstrapZeroTestRepo } from "../src/config.mjs";
|
|
9
9
|
import { gate, dispatch, run, isTaskFile } from "../src/engine.mjs";
|
|
10
10
|
import { acquireLock, releaseLock, lockStatus, getQueueDir } from "../src/state.mjs";
|
|
11
|
+
import { parseEnvelopeHeader } from "../src/envelope.mjs";
|
|
11
12
|
import { worktreePrune } from "../src/git.mjs";
|
|
12
13
|
import { reapOrphanedIntents, reapStaleMutexDirs } from "../src/journal.mjs";
|
|
13
14
|
import { KIT_VERSION } from "../src/version.mjs";
|
|
14
15
|
import { budgetStatus, listOpenReservations, releaseOpenReservations, resolveConcurrency } from "../src/budget.mjs";
|
|
16
|
+
import { COMMAND_REGISTRY } from "../src/ops/command-registry.mjs";
|
|
15
17
|
|
|
16
18
|
// Vendor-neutral env spellings are filled in before any module reads
|
|
17
19
|
// process.env, so `AGENT_API_KEY` works everywhere `JULES_API_KEY` does. A
|
|
@@ -42,78 +44,52 @@ export function formatBudgetLine(b) {
|
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
export function printHelp() {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
budget Show the 24h task budget, worker slots and their provenance (reset --yes)
|
|
92
|
-
scan Scan codebase for TODO/FIXME task candidates
|
|
93
|
-
hydrate [prompt] Prepend active system learnings and baton-pass state to a prompt
|
|
94
|
-
harvest Harvest failure traces and record/quarantine resolution rules
|
|
95
|
-
learning add Record a system learning rule into .agent/knowledge/
|
|
96
|
-
evidence <action> Manage cryptographic audit evidence (generate | verify | show)
|
|
97
|
-
assert Run declarative zero-dependency verification assertion primitives
|
|
98
|
-
version Output agentctl version
|
|
99
|
-
|
|
100
|
-
Options:
|
|
101
|
-
--prompt, -p Task prompt text — dispatch, task create and task optimize
|
|
102
|
-
also accept it as a positional argument
|
|
103
|
-
--prompt-file, -f Read the prompt from a file (-f is --fix on task optimize)
|
|
104
|
-
--role, -r Specify specialist agent role (overseer | bolt | sentinel | janitor)
|
|
105
|
-
--tier Force routing tier when router.enabled (fast | complex) — see .agent/config.yml router:
|
|
106
|
-
--check-premise Verify task goal/oracle passes locally before burning API budget
|
|
107
|
-
--dag Execute queue tasks via DAG dependency resolution
|
|
108
|
-
--dry-run, -d Simulate action without making API calls or modifying git
|
|
109
|
-
--mode, -m Gate evaluation mode (working-tree | committed | staged)
|
|
110
|
-
--repoless Dispatch task in repoless execution mode
|
|
111
|
-
--source, -s Specify Jules repository source name
|
|
112
|
-
--branch, -b Specify target starting branch
|
|
113
|
-
--json, -j Emit machine-readable JSON output
|
|
114
|
-
--json-report <path> Write structured machine-readable JSON diagnostics report
|
|
115
|
-
--help, -h Show command help
|
|
116
|
-
`);
|
|
47
|
+
// Single source of truth: the command list is generated from
|
|
48
|
+
// COMMAND_REGISTRY, so adding a descriptor automatically updates --help,
|
|
49
|
+
// `help <command>` (via formatCommandHelp) and docs/COMMAND_REFERENCE.md.
|
|
50
|
+
const CATEGORY_ORDER = ["Create", "Inspect", "Operate", "Repair", "Configure"];
|
|
51
|
+
const lines = [];
|
|
52
|
+
lines.push("");
|
|
53
|
+
lines.push(`agentctl v${VERSION} — Universal Agent Orchestrator & Safety Gatekeeper`);
|
|
54
|
+
lines.push("");
|
|
55
|
+
lines.push("Usage: agentctl <command> [options]");
|
|
56
|
+
lines.push("");
|
|
57
|
+
lines.push("Commands:");
|
|
58
|
+
for (const category of CATEGORY_ORDER) {
|
|
59
|
+
const cmds = COMMAND_REGISTRY.filter((c) => c.category === category);
|
|
60
|
+
if (cmds.length === 0) continue;
|
|
61
|
+
lines.push(` --- ${category} ---`);
|
|
62
|
+
for (const cmd of cmds) {
|
|
63
|
+
const name = cmd.path.join(" ");
|
|
64
|
+
const flagHint =
|
|
65
|
+
cmd.flags.length > 0
|
|
66
|
+
? ` (${cmd.flags.slice(0, 3).map((f) => `--${f.name}`).join(", ")}${cmd.flags.length > 3 ? ", ..." : ""})`
|
|
67
|
+
: "";
|
|
68
|
+
lines.push(` ${name.padEnd(20)} ${cmd.description}${flagHint}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
lines.push(" version Output agentctl version");
|
|
72
|
+
lines.push("");
|
|
73
|
+
lines.push("Run `agentctl help <command>` or `agentctl <command> --help` for full flag documentation.");
|
|
74
|
+
lines.push("");
|
|
75
|
+
lines.push("Options:");
|
|
76
|
+
lines.push(" --prompt, -p Task prompt text — dispatch, task create and task optimize");
|
|
77
|
+
lines.push(" also accept it as a positional argument");
|
|
78
|
+
lines.push(" --prompt-file, -f Read the prompt from a file (-f is --fix on task optimize)");
|
|
79
|
+
lines.push(" --role, -r Specify specialist agent role (auditor | performance | security | hygiene | resilience | types | debugger | testing | e2e | database | docs | a11y)");
|
|
80
|
+
lines.push(" --tier Force routing tier when router.enabled (fast | complex) — see .agent/config.yml router:");
|
|
81
|
+
lines.push(" --check-premise Verify task goal/oracle passes locally before burning API budget");
|
|
82
|
+
lines.push(" --dag Execute queue tasks via DAG dependency resolution");
|
|
83
|
+
lines.push(" --dry-run, -d Simulate action without making API calls or modifying git");
|
|
84
|
+
lines.push(" --mode, -m Gate evaluation mode (working-tree | committed | staged)");
|
|
85
|
+
lines.push(" --repoless Dispatch task in repoless execution mode");
|
|
86
|
+
lines.push(" --source, -s Specify Jules repository source name");
|
|
87
|
+
lines.push(" --branch, -b Specify target starting branch");
|
|
88
|
+
lines.push(" --json, -j Emit machine-readable JSON output");
|
|
89
|
+
lines.push(" --json-report <path> Write structured machine-readable JSON diagnostics report");
|
|
90
|
+
lines.push(" --help, -h Show command help");
|
|
91
|
+
lines.push("");
|
|
92
|
+
console.log(lines.join("\n"));
|
|
117
93
|
}
|
|
118
94
|
|
|
119
95
|
/**
|
|
@@ -130,17 +106,65 @@ Options:
|
|
|
130
106
|
* @param {string[]} [positionals] Remaining free arguments.
|
|
131
107
|
* @returns {string} The prompt, or "" when none was supplied.
|
|
132
108
|
*/
|
|
133
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Resolve the prompt text and source path a command was given.
|
|
111
|
+
*
|
|
112
|
+
* @param {Record<string, unknown>} values Parsed flags.
|
|
113
|
+
* @param {string[]} [positionals] Remaining free arguments.
|
|
114
|
+
* @param {string} [root] Repository root.
|
|
115
|
+
* @returns {{ content: string, sourceFile: string | null }}
|
|
116
|
+
*/
|
|
117
|
+
function resolvePromptDetails(values, positionals = [], root = process.cwd()) {
|
|
134
118
|
const file = values["prompt-file"] || values.file;
|
|
135
119
|
if (file) {
|
|
136
120
|
if (!existsSync(file)) {
|
|
137
121
|
console.error(`Error: prompt file not found: ${file}`);
|
|
138
122
|
process.exit(1);
|
|
139
123
|
}
|
|
140
|
-
return readFileSync(file, "utf-8");
|
|
124
|
+
return { content: readFileSync(file, "utf-8"), sourceFile: file };
|
|
141
125
|
}
|
|
142
|
-
if (values.prompt) return String(values.prompt);
|
|
143
|
-
|
|
126
|
+
if (values.prompt) return { content: String(values.prompt), sourceFile: null };
|
|
127
|
+
|
|
128
|
+
if (positionals.length === 1) {
|
|
129
|
+
const raw = String(positionals[0]).trim();
|
|
130
|
+
const candidateFiles = [
|
|
131
|
+
raw,
|
|
132
|
+
resolve(root, raw),
|
|
133
|
+
join(root, ".agent", "jules-queue", raw),
|
|
134
|
+
join(root, ".agent", "jules-queue", `${raw}.md`),
|
|
135
|
+
];
|
|
136
|
+
for (const cand of candidateFiles) {
|
|
137
|
+
if (cand && existsSync(cand)) {
|
|
138
|
+
try {
|
|
139
|
+
if (statSync(cand).isFile()) {
|
|
140
|
+
return { content: readFileSync(cand, "utf-8"), sourceFile: cand };
|
|
141
|
+
}
|
|
142
|
+
} catch (_) {}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (positionals.length === 0) {
|
|
148
|
+
const queueDir = getQueueDir(root);
|
|
149
|
+
if (existsSync(queueDir)) {
|
|
150
|
+
const candidates = readdirSync(queueDir)
|
|
151
|
+
.filter((f) => f.endsWith(".md") && !f.startsWith(".") && f !== "README.md" && isTaskFile(f, queueDir))
|
|
152
|
+
.sort();
|
|
153
|
+
if (candidates.length > 0) {
|
|
154
|
+
const selected = join(queueDir, candidates[0]);
|
|
155
|
+
if (!values?.json) {
|
|
156
|
+
console.log(`Auto-selected pending queue task: .agent/jules-queue/${candidates[0]}`);
|
|
157
|
+
}
|
|
158
|
+
return { content: readFileSync(selected, "utf-8"), sourceFile: selected };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return { content: positionals.join(" ").trim(), sourceFile: null };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function resolvePromptInput(values, positionals = [], root = process.cwd()) {
|
|
167
|
+
return resolvePromptDetails(values, positionals, root).content;
|
|
144
168
|
}
|
|
145
169
|
|
|
146
170
|
/**
|
|
@@ -275,8 +299,7 @@ async function main() {
|
|
|
275
299
|
const config = loadConfig(root);
|
|
276
300
|
|
|
277
301
|
switch (command) {
|
|
278
|
-
case "dispatch":
|
|
279
|
-
case "create": {
|
|
302
|
+
case "dispatch": {
|
|
280
303
|
const { values, positionals } = parseArgs({
|
|
281
304
|
args: args.slice(1),
|
|
282
305
|
options: {
|
|
@@ -290,21 +313,43 @@ async function main() {
|
|
|
290
313
|
repoless: { type: "boolean" },
|
|
291
314
|
"auto-pr": { type: "boolean" },
|
|
292
315
|
"require-plan-approval": { type: "boolean" },
|
|
316
|
+
"auto-approve-plans": { type: "boolean" },
|
|
317
|
+
"auto-approve": { type: "boolean" },
|
|
293
318
|
"check-premise": { type: "boolean" },
|
|
294
319
|
idempotent: { type: "boolean" },
|
|
295
320
|
author: { type: "string" },
|
|
321
|
+
"verify-cmd": { type: "string", short: "v" },
|
|
322
|
+
verify: { type: "string" },
|
|
296
323
|
"dry-run": { type: "boolean", short: "d" },
|
|
297
324
|
json: { type: "boolean", short: "j" },
|
|
298
325
|
},
|
|
299
326
|
allowPositionals: true,
|
|
300
327
|
});
|
|
301
328
|
|
|
302
|
-
const promptContent =
|
|
329
|
+
const { content: promptContent, sourceFile } = resolvePromptDetails(values, positionals, root);
|
|
303
330
|
if (!promptContent) {
|
|
304
|
-
console.error("Error: a prompt is required — pass it as --prompt, --prompt-file, or
|
|
331
|
+
console.error("Error: a prompt is required — pass it as --prompt, --prompt-file, a task file path, or create one in .agent/jules-queue/.");
|
|
305
332
|
process.exit(1);
|
|
306
333
|
}
|
|
307
334
|
|
|
335
|
+
// If promptContent has an envelope header, inherit defaults
|
|
336
|
+
const envelopeMeta = parseEnvelopeHeader(promptContent);
|
|
337
|
+
if (envelopeMeta) {
|
|
338
|
+
values.title = values.title || envelopeMeta.title;
|
|
339
|
+
values.role = values.role || envelopeMeta.role;
|
|
340
|
+
values.tier = values.tier || envelopeMeta.tier;
|
|
341
|
+
values["verify-cmd"] = values["verify-cmd"] || values.verify || envelopeMeta.verifyCmd;
|
|
342
|
+
if (values["auto-pr"] === undefined && envelopeMeta.flags?.autoPr !== undefined) {
|
|
343
|
+
values["auto-pr"] = envelopeMeta.flags.autoPr;
|
|
344
|
+
}
|
|
345
|
+
if (values["require-plan-approval"] === undefined && envelopeMeta.flags?.requirePlanApproval !== undefined) {
|
|
346
|
+
values["require-plan-approval"] = envelopeMeta.flags.requirePlanApproval;
|
|
347
|
+
}
|
|
348
|
+
if (values.repoless === undefined && envelopeMeta.flags?.repoless !== undefined) {
|
|
349
|
+
values.repoless = envelopeMeta.flags.repoless;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
308
353
|
// `task create --role` has always rejected a role it cannot resolve;
|
|
309
354
|
// `dispatch --role` used to drop it without a word and hand the work to a
|
|
310
355
|
// generic agent, so the two commands disagreed about what the same flag
|
|
@@ -314,13 +359,14 @@ async function main() {
|
|
|
314
359
|
const { resolveRolePrompt } = await import("../src/role-resolver.mjs");
|
|
315
360
|
if (!resolveRolePrompt(root, values.role, { config })) {
|
|
316
361
|
console.error(
|
|
317
|
-
`Error: Unknown agent role '${values.role}'. Expected matching prompt file in .agent/prompts/ (e.g.
|
|
362
|
+
`Error: Unknown agent role '${values.role}'. Expected matching prompt file in .agent/prompts/ (e.g. Auditor, Performance, Security, Hygiene, Testing).`
|
|
318
363
|
);
|
|
319
364
|
console.error(` Run 'agentctl init' to scaffold the shipped role prompts.`);
|
|
320
365
|
process.exit(1);
|
|
321
366
|
}
|
|
322
367
|
}
|
|
323
368
|
|
|
369
|
+
const autoApprove = Boolean(values["auto-approve-plans"] || values["auto-approve"]);
|
|
324
370
|
const task = {
|
|
325
371
|
title: values.title || "CLI Dispatch Task",
|
|
326
372
|
prompt: promptContent,
|
|
@@ -330,9 +376,10 @@ async function main() {
|
|
|
330
376
|
branch: values.branch,
|
|
331
377
|
repoless: values.repoless,
|
|
332
378
|
autoPr: values["auto-pr"],
|
|
333
|
-
requirePlanApproval: values["require-plan-approval"],
|
|
379
|
+
requirePlanApproval: autoApprove ? false : values["require-plan-approval"],
|
|
334
380
|
checkPremise: values["check-premise"] || values.idempotent,
|
|
335
381
|
author: values.author,
|
|
382
|
+
verifyCmd: values["verify-cmd"] || values.verify,
|
|
336
383
|
};
|
|
337
384
|
|
|
338
385
|
try {
|
|
@@ -374,6 +421,19 @@ async function main() {
|
|
|
374
421
|
}
|
|
375
422
|
}
|
|
376
423
|
}
|
|
424
|
+
if (session && session.ok !== false && !values["dry-run"] && sourceFile) {
|
|
425
|
+
const queueDir = getQueueDir(root);
|
|
426
|
+
const resolvedSrc = resolve(root, sourceFile);
|
|
427
|
+
if (resolvedSrc.startsWith(queueDir) && existsSync(resolvedSrc)) {
|
|
428
|
+
const completedDir = join(queueDir, "completed");
|
|
429
|
+
if (!existsSync(completedDir)) mkdirSync(completedDir, { recursive: true });
|
|
430
|
+
const baseName = resolvedSrc.slice(queueDir.length).replace(/^[/\\]+/, "");
|
|
431
|
+
const dstPath = join(completedDir, baseName);
|
|
432
|
+
if (!existsSync(dstPath)) {
|
|
433
|
+
renameSync(resolvedSrc, dstPath);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
377
437
|
process.exit(0);
|
|
378
438
|
} catch (err) {
|
|
379
439
|
const exitCode = typeof err.code === "number" ? err.code : 1;
|
|
@@ -818,6 +878,10 @@ async function main() {
|
|
|
818
878
|
min: { type: "string", short: "m" },
|
|
819
879
|
"min-pass-rate": { type: "string" },
|
|
820
880
|
cmd: { type: "string", short: "c" },
|
|
881
|
+
"test-cmd": { type: "string", short: "t" },
|
|
882
|
+
"verify-cmd": { type: "string" },
|
|
883
|
+
record: { type: "boolean" },
|
|
884
|
+
"no-record": { type: "boolean" },
|
|
821
885
|
json: { type: "boolean", short: "j" },
|
|
822
886
|
},
|
|
823
887
|
allowPositionals: true,
|
|
@@ -826,8 +890,10 @@ async function main() {
|
|
|
826
890
|
const root = resolveRoot();
|
|
827
891
|
const repeat = values.repeat ? parseInt(values.repeat, 10) : (values.iterations ? parseInt(values.iterations, 10) : 5);
|
|
828
892
|
const minPassRate = values.min ? parseFloat(values.min) : (values["min-pass-rate"] ? parseFloat(values["min-pass-rate"]) : 1.0);
|
|
893
|
+
const targetCmd = values["test-cmd"] || values["verify-cmd"] || values.cmd;
|
|
894
|
+
const record = values["no-record"] ? false : values.record !== false;
|
|
829
895
|
|
|
830
|
-
const report = runStabilityProbe(
|
|
896
|
+
const report = runStabilityProbe(targetCmd, { root, repeat, minPassRate, record });
|
|
831
897
|
|
|
832
898
|
if (values.json) {
|
|
833
899
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -846,6 +912,11 @@ async function main() {
|
|
|
846
912
|
}
|
|
847
913
|
}
|
|
848
914
|
|
|
915
|
+
if (record && !report.ok) {
|
|
916
|
+
console.log(`\n Recorded probe results to .agent/state/flaky.jsonl.`);
|
|
917
|
+
console.log(` Check status or trigger auto-healing: agentctl flaky status | agentctl flaky heal`);
|
|
918
|
+
}
|
|
919
|
+
|
|
849
920
|
console.log(`------------------------------------------------------------------`);
|
|
850
921
|
console.log(`Overall Result: ${report.ok ? "APPROVED (Deterministic 100% Pass — Exit 0)" : "REJECTED (Flaky / Intermittent Failures — Exit 1)"}\n`);
|
|
851
922
|
}
|
|
@@ -917,10 +988,11 @@ async function main() {
|
|
|
917
988
|
errorInput = readFileSync(values.file, "utf-8");
|
|
918
989
|
} else if (values.input) {
|
|
919
990
|
errorInput = values.input;
|
|
920
|
-
} else if (positionals.
|
|
921
|
-
errorInput = positionals.
|
|
991
|
+
} else if (positionals.length > 0) {
|
|
992
|
+
errorInput = positionals.join(" ");
|
|
922
993
|
} else if (!process.stdin.isTTY) {
|
|
923
|
-
|
|
994
|
+
const { text } = await import("node:stream/consumers");
|
|
995
|
+
errorInput = await text(process.stdin);
|
|
924
996
|
}
|
|
925
997
|
|
|
926
998
|
if (!errorInput.trim()) {
|
|
@@ -936,6 +1008,7 @@ async function main() {
|
|
|
936
1008
|
prompt: `Fix the following test/build failure:\n\n${cleanTrace.slice(0, 4000)}`,
|
|
937
1009
|
title: "Automated Failure Repair",
|
|
938
1010
|
verifyCmd: values.cmd || "npm test",
|
|
1011
|
+
allowHighEntropy: true,
|
|
939
1012
|
});
|
|
940
1013
|
|
|
941
1014
|
if (values.json) {
|
|
@@ -979,7 +1052,7 @@ async function main() {
|
|
|
979
1052
|
allowPositionals: true,
|
|
980
1053
|
});
|
|
981
1054
|
|
|
982
|
-
const sessionId = values.session || positionals[0]
|
|
1055
|
+
const sessionId = values.session || positionals[0];
|
|
983
1056
|
if (!sessionId) {
|
|
984
1057
|
console.error("❌ Error: Missing required session ID. Usage: agentctl patch <session_id> [--apply] [--save <path>]");
|
|
985
1058
|
process.exit(1);
|
|
@@ -1030,7 +1103,7 @@ async function main() {
|
|
|
1030
1103
|
allowPositionals: true,
|
|
1031
1104
|
});
|
|
1032
1105
|
|
|
1033
|
-
const sessionId = values.session || positionals[0]
|
|
1106
|
+
const sessionId = values.session || positionals[0];
|
|
1034
1107
|
if (!sessionId) {
|
|
1035
1108
|
console.error("❌ Error: Missing required session ID. Usage: agentctl retry <session_id> [--role <role>]");
|
|
1036
1109
|
process.exit(1);
|
|
@@ -1312,7 +1385,21 @@ async function main() {
|
|
|
1312
1385
|
}
|
|
1313
1386
|
|
|
1314
1387
|
case "budget": {
|
|
1315
|
-
const
|
|
1388
|
+
const { values: budgetValues, positionals: budgetPositionals } = parseArgs({
|
|
1389
|
+
args: args.slice(1),
|
|
1390
|
+
options: {
|
|
1391
|
+
"dry-run": { type: "boolean" },
|
|
1392
|
+
yes: { type: "boolean", short: "y" },
|
|
1393
|
+
all: { type: "boolean" },
|
|
1394
|
+
"by-user": { type: "boolean", short: "u" },
|
|
1395
|
+
json: { type: "boolean" },
|
|
1396
|
+
},
|
|
1397
|
+
allowPositionals: true,
|
|
1398
|
+
// strict:false so `reset` can refuse unknown flags with its own
|
|
1399
|
+
// ledger-aware message instead of a generic parseArgs throw.
|
|
1400
|
+
strict: false,
|
|
1401
|
+
});
|
|
1402
|
+
const action = budgetPositionals[0];
|
|
1316
1403
|
const b = budgetStatus(config, root);
|
|
1317
1404
|
|
|
1318
1405
|
if (action === "reset") {
|
|
@@ -1330,11 +1417,11 @@ async function main() {
|
|
|
1330
1417
|
console.error("Accepted: --dry-run, --yes/-y, --all. Nothing was released.");
|
|
1331
1418
|
process.exit(2);
|
|
1332
1419
|
}
|
|
1333
|
-
const dryRun =
|
|
1334
|
-
const confirmed =
|
|
1420
|
+
const dryRun = Boolean(budgetValues["dry-run"]);
|
|
1421
|
+
const confirmed = Boolean(budgetValues.yes);
|
|
1335
1422
|
// Committed reservations reached the provider, so releasing them makes
|
|
1336
1423
|
// the local count understate real usage. That has to be deliberate.
|
|
1337
|
-
const includeCommitted =
|
|
1424
|
+
const includeCommitted = Boolean(budgetValues.all);
|
|
1338
1425
|
if (!dryRun && !confirmed) {
|
|
1339
1426
|
const open = listOpenReservations(root);
|
|
1340
1427
|
const committed = open.filter((r) => r.committed).length;
|
|
@@ -1361,7 +1448,7 @@ async function main() {
|
|
|
1361
1448
|
process.exit(0);
|
|
1362
1449
|
}
|
|
1363
1450
|
|
|
1364
|
-
if (
|
|
1451
|
+
if (budgetValues["by-user"]) {
|
|
1365
1452
|
console.log("📊 Task Budget Attribution (Rolling 24h Window)");
|
|
1366
1453
|
console.log(`Daily Limit : ${b.limit} Tasks | Used: ${b.used} | Remaining: ${b.remaining}\n`);
|
|
1367
1454
|
const users = Object.entries(b.byUser || {});
|
|
@@ -1382,7 +1469,7 @@ async function main() {
|
|
|
1382
1469
|
process.exit(0);
|
|
1383
1470
|
}
|
|
1384
1471
|
|
|
1385
|
-
if (
|
|
1472
|
+
if (budgetValues.json) {
|
|
1386
1473
|
console.log(JSON.stringify({ ok: true, budget: { ...b, scope: "this-repository" } }, null, 2));
|
|
1387
1474
|
process.exit(0);
|
|
1388
1475
|
}
|
|
@@ -1408,7 +1495,16 @@ async function main() {
|
|
|
1408
1495
|
}
|
|
1409
1496
|
|
|
1410
1497
|
case "lock": {
|
|
1411
|
-
const
|
|
1498
|
+
const { values: lockValues, positionals: lockPositionals } = parseArgs({
|
|
1499
|
+
args: args.slice(1),
|
|
1500
|
+
options: {
|
|
1501
|
+
ttl: { type: "string" },
|
|
1502
|
+
pid: { type: "string" },
|
|
1503
|
+
json: { type: "boolean", short: "j" },
|
|
1504
|
+
},
|
|
1505
|
+
allowPositionals: true,
|
|
1506
|
+
});
|
|
1507
|
+
const action = lockPositionals[0];
|
|
1412
1508
|
if (action === "acquire") {
|
|
1413
1509
|
// A lock taken from the command line outlives the command. The process
|
|
1414
1510
|
// that runs `lock acquire` writes the record and exits; the agent it
|
|
@@ -1419,27 +1515,26 @@ async function main() {
|
|
|
1419
1515
|
// `--pid` is the escape hatch for a caller that *does* have a durable
|
|
1420
1516
|
// process to point at: the lock then releases itself when that process
|
|
1421
1517
|
// dies, exactly as an in-process acquire does.
|
|
1422
|
-
const
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
},
|
|
1430
|
-
allowPositionals: false,
|
|
1431
|
-
});
|
|
1432
|
-
const agent = positional[0] || "agent";
|
|
1433
|
-
const taskId = positional[1] || "task-1";
|
|
1518
|
+
const positional = lockPositionals.slice(1);
|
|
1519
|
+
if (positional.length < 3) {
|
|
1520
|
+
console.error("Usage: agentctl lock acquire <agent> <task_id> <file_path...> [--ttl <minutes>] [--pid <pid>]");
|
|
1521
|
+
process.exit(1);
|
|
1522
|
+
}
|
|
1523
|
+
const agent = positional[0];
|
|
1524
|
+
const taskId = positional[1];
|
|
1434
1525
|
const filePaths = positional.slice(2);
|
|
1435
|
-
const ttlMinutes = Number(
|
|
1436
|
-
const ownerPid = Number(
|
|
1526
|
+
const ttlMinutes = Number(lockValues.ttl);
|
|
1527
|
+
const ownerPid = Number(lockValues.pid);
|
|
1437
1528
|
const bindsToProcess = Number.isInteger(ownerPid) && ownerPid > 0;
|
|
1438
1529
|
const res = acquireLock(agent, taskId, filePaths, root, {
|
|
1439
1530
|
lease: !bindsToProcess,
|
|
1440
1531
|
ownerPid: bindsToProcess ? ownerPid : undefined,
|
|
1441
1532
|
ttlMs: Number.isFinite(ttlMinutes) && ttlMinutes > 0 ? ttlMinutes * 60_000 : undefined,
|
|
1442
1533
|
});
|
|
1534
|
+
if (lockValues.json) {
|
|
1535
|
+
console.log(JSON.stringify(res, null, 2));
|
|
1536
|
+
process.exit(res.ok ? 0 : 1);
|
|
1537
|
+
}
|
|
1443
1538
|
if (res.ok) {
|
|
1444
1539
|
const held = filePaths.length ? ` (${filePaths.length} path${filePaths.length === 1 ? "" : "s"})` : "";
|
|
1445
1540
|
const until = bindsToProcess ? `bound to pid ${ownerPid}` : `expires ${new Date(Date.now() + (Number.isFinite(ttlMinutes) && ttlMinutes > 0 ? ttlMinutes : 120) * 60_000).toISOString()}`;
|
|
@@ -1453,8 +1548,16 @@ async function main() {
|
|
|
1453
1548
|
process.exit(1);
|
|
1454
1549
|
}
|
|
1455
1550
|
} else if (action === "release") {
|
|
1456
|
-
const taskId =
|
|
1551
|
+
const taskId = lockPositionals[1];
|
|
1552
|
+
if (!taskId) {
|
|
1553
|
+
console.error("Usage: agentctl lock release <task_id>");
|
|
1554
|
+
process.exit(1);
|
|
1555
|
+
}
|
|
1457
1556
|
const ok = releaseLock(taskId, root);
|
|
1557
|
+
if (lockValues.json) {
|
|
1558
|
+
console.log(JSON.stringify({ ok, taskId }, null, 2));
|
|
1559
|
+
process.exit(ok ? 0 : 1);
|
|
1560
|
+
}
|
|
1458
1561
|
if (ok) {
|
|
1459
1562
|
console.log(`✅ Released lock for ${taskId}`);
|
|
1460
1563
|
} else {
|
|
@@ -1463,7 +1566,21 @@ async function main() {
|
|
|
1463
1566
|
}
|
|
1464
1567
|
} else {
|
|
1465
1568
|
const locks = lockStatus(root);
|
|
1466
|
-
|
|
1569
|
+
if (lockValues.json) {
|
|
1570
|
+
console.log(JSON.stringify({ ok: true, locks }, null, 2));
|
|
1571
|
+
process.exit(0);
|
|
1572
|
+
}
|
|
1573
|
+
if (locks.length === 0) {
|
|
1574
|
+
console.log("Active Locks: 0 (no held locks)");
|
|
1575
|
+
} else {
|
|
1576
|
+
console.log(`Active Locks (${locks.length}):`);
|
|
1577
|
+
for (const l of locks) {
|
|
1578
|
+
const files = Array.isArray(l.files) ? l.files.join(", ") : (l.files || "");
|
|
1579
|
+
const expires = l.expiresAt ? `expires ${l.expiresAt}` : (l.ownerPid ? `pid ${l.ownerPid}` : "active");
|
|
1580
|
+
console.log(` • taskId: ${l.taskId || l.id} [${l.agent || "unknown"}] (${expires})`);
|
|
1581
|
+
if (files) console.log(` Paths: ${files}`);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1467
1584
|
}
|
|
1468
1585
|
process.exit(0);
|
|
1469
1586
|
break;
|
|
@@ -1498,7 +1615,9 @@ async function main() {
|
|
|
1498
1615
|
console.log(`--------------------------------------------------`);
|
|
1499
1616
|
console.log(` Project Root : ${root}`);
|
|
1500
1617
|
console.log(` Config File : ${config._file || "None (Using defaults)"}`);
|
|
1501
|
-
|
|
1618
|
+
const stackInfo = detectStack(root);
|
|
1619
|
+
const edgeSuffix = stackInfo.isEdgeRuntime ? ` (Edge: ${stackInfo.edgePlatform || "generic"})` : "";
|
|
1620
|
+
console.log(` Detected Stack : ${stackInfo.stack}${edgeSuffix}`);
|
|
1502
1621
|
console.log(` Test Command : ${config.verify.test || "(None)"}`);
|
|
1503
1622
|
console.log(` Build Command : ${config.verify.build || "(None)"}`);
|
|
1504
1623
|
console.log(` Daily Budget : ${formatBudgetLine(budgetStatus(config, root))}`);
|
|
@@ -1526,11 +1645,16 @@ async function main() {
|
|
|
1526
1645
|
|
|
1527
1646
|
case "provider":
|
|
1528
1647
|
case "providers": {
|
|
1648
|
+
const { values, positionals } = parseArgs({
|
|
1649
|
+
args: args.slice(1),
|
|
1650
|
+
options: { json: { type: "boolean", short: "j" } },
|
|
1651
|
+
allowPositionals: true,
|
|
1652
|
+
});
|
|
1529
1653
|
// `agentctl providers` used to tell the operator to run
|
|
1530
1654
|
// `agentctl init --provider <name>` to switch — which restarts the whole
|
|
1531
1655
|
// onboarding wizard, plan question included, to change one line.
|
|
1532
|
-
if (
|
|
1533
|
-
const target =
|
|
1656
|
+
if (positionals[0] === "set") {
|
|
1657
|
+
const target = positionals[1];
|
|
1534
1658
|
if (!target) {
|
|
1535
1659
|
console.error(`❌ Usage: agentctl provider set <name> (see: agentctl providers)`);
|
|
1536
1660
|
process.exit(2);
|
|
@@ -1550,11 +1674,6 @@ async function main() {
|
|
|
1550
1674
|
process.exit(0);
|
|
1551
1675
|
}
|
|
1552
1676
|
|
|
1553
|
-
const { values } = parseArgs({
|
|
1554
|
-
args: args.slice(1),
|
|
1555
|
-
options: { json: { type: "boolean", short: "j" } },
|
|
1556
|
-
allowPositionals: true,
|
|
1557
|
-
});
|
|
1558
1677
|
const { detectAvailableProviders, probeProvider } = await import("../src/provider-readiness.mjs");
|
|
1559
1678
|
const probes = detectAvailableProviders();
|
|
1560
1679
|
const active = config.provider || "jules";
|
|
@@ -1702,11 +1821,16 @@ async function main() {
|
|
|
1702
1821
|
options: {
|
|
1703
1822
|
force: { type: "boolean", short: "f" },
|
|
1704
1823
|
"dry-run": { type: "boolean", short: "d" },
|
|
1824
|
+
json: { type: "boolean", short: "j" },
|
|
1705
1825
|
},
|
|
1706
1826
|
allowPositionals: true,
|
|
1707
1827
|
});
|
|
1708
1828
|
|
|
1709
1829
|
const res = bootstrapZeroTestRepo(root, { force: values.force });
|
|
1830
|
+
if (values.json) {
|
|
1831
|
+
console.log(JSON.stringify({ ok: res.bootstrapped, ...res }, null, 2));
|
|
1832
|
+
process.exit(res.bootstrapped ? 0 : 1);
|
|
1833
|
+
}
|
|
1710
1834
|
if (res.bootstrapped) {
|
|
1711
1835
|
console.log(`✅ Zero-test repo bootstrapped successfully!`);
|
|
1712
1836
|
console.log(` Stack detected : ${res.stack}`);
|
|
@@ -1727,7 +1851,12 @@ async function main() {
|
|
|
1727
1851
|
}
|
|
1728
1852
|
|
|
1729
1853
|
case "review-repair": {
|
|
1730
|
-
const
|
|
1854
|
+
const { positionals: reviewPositionals } = parseArgs({
|
|
1855
|
+
args: args.slice(1),
|
|
1856
|
+
options: {},
|
|
1857
|
+
allowPositionals: true,
|
|
1858
|
+
});
|
|
1859
|
+
const fileArg = reviewPositionals[0];
|
|
1731
1860
|
if (!fileArg || !existsSync(fileArg)) {
|
|
1732
1861
|
console.error("Error: Please provide a valid JSON file containing PR review comments.");
|
|
1733
1862
|
process.exit(1);
|
|
@@ -1745,9 +1874,33 @@ async function main() {
|
|
|
1745
1874
|
}
|
|
1746
1875
|
|
|
1747
1876
|
case "dashboard": {
|
|
1748
|
-
|
|
1877
|
+
// Accepts both `agentctl dashboard 3000` and `agentctl dashboard --port
|
|
1878
|
+
// 3000`. The old code read args[1] raw, so the documented flag form fed
|
|
1879
|
+
// the literal string "--port" into Number() and died with an uncaught
|
|
1880
|
+
// ERR_SOCKET_BAD_PORT instead of a usable error.
|
|
1881
|
+
let dashboardOpts;
|
|
1882
|
+
try {
|
|
1883
|
+
const parsed = parseArgs({
|
|
1884
|
+
args: args.slice(1),
|
|
1885
|
+
options: {
|
|
1886
|
+
port: { type: "string" },
|
|
1887
|
+
host: { type: "string" },
|
|
1888
|
+
},
|
|
1889
|
+
allowPositionals: true,
|
|
1890
|
+
});
|
|
1891
|
+
dashboardOpts = { port: parsed.values.port, host: parsed.values.host, positional: parsed.positionals[0] };
|
|
1892
|
+
} catch (err) {
|
|
1893
|
+
console.error(`Error: Invalid port argument: ${err.message}`);
|
|
1894
|
+
process.exit(1);
|
|
1895
|
+
}
|
|
1896
|
+
const rawPort = dashboardOpts.port ?? dashboardOpts.positional ?? "4100";
|
|
1897
|
+
const port = Number(rawPort);
|
|
1898
|
+
if (!Number.isInteger(port) || port < 1024 || port > 65535) {
|
|
1899
|
+
console.error(`Error: Invalid port "${rawPort}". Expected an integer between 1024 and 65535.`);
|
|
1900
|
+
process.exit(1);
|
|
1901
|
+
}
|
|
1749
1902
|
const { startDashboardServer } = await import("../src/dashboard.mjs");
|
|
1750
|
-
startDashboardServer(port, root);
|
|
1903
|
+
startDashboardServer(port, root, dashboardOpts.host || "127.0.0.1");
|
|
1751
1904
|
break;
|
|
1752
1905
|
}
|
|
1753
1906
|
|
|
@@ -1769,7 +1922,27 @@ async function main() {
|
|
|
1769
1922
|
allowPositionals: true,
|
|
1770
1923
|
});
|
|
1771
1924
|
|
|
1772
|
-
|
|
1925
|
+
if (values.tier) {
|
|
1926
|
+
const validTiers = ["free", "pro", "ultra", "enterprise"];
|
|
1927
|
+
const lowerTier = values.tier.toLowerCase();
|
|
1928
|
+
if (!validTiers.includes(lowerTier)) {
|
|
1929
|
+
if (["minimal", "standard", "max"].includes(lowerTier)) {
|
|
1930
|
+
console.error(`❌ Invalid tier "${values.tier}". Valid tiers: ${validTiers.join(", ")}. Did you mean '--profile ${values.tier}'?`);
|
|
1931
|
+
} else {
|
|
1932
|
+
console.error(`❌ Invalid tier "${values.tier}". Valid tiers: ${validTiers.join(", ")}.`);
|
|
1933
|
+
}
|
|
1934
|
+
process.exit(1);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
const isInteractive =
|
|
1939
|
+
values.interactive === true ||
|
|
1940
|
+
(values.interactive !== false &&
|
|
1941
|
+
Boolean(process.stdin.isTTY) &&
|
|
1942
|
+
!process.env.CI &&
|
|
1943
|
+
!values["non-interactive"] &&
|
|
1944
|
+
!values["no-interactive"] &&
|
|
1945
|
+
!values.yes);
|
|
1773
1946
|
|
|
1774
1947
|
const { runInitWizard } = await import("../src/wizard-init.mjs");
|
|
1775
1948
|
const res = await runInitWizard(root, {
|
|
@@ -1828,7 +2001,15 @@ async function main() {
|
|
|
1828
2001
|
// tree for files init had just written, which reads as the tool
|
|
1829
2002
|
// catching the user cheating on step three.
|
|
1830
2003
|
const rootContracts = ["SPEC.md", "CONSTRAINTS.md", "DESIGN.md"].filter((f) => existsSync(join(root, f)));
|
|
1831
|
-
|
|
2004
|
+
// A fresh `npm install`/`npm init -y` just created package manifests the
|
|
2005
|
+
// scope rules `protect`. Leaving them out of the hint meant the very
|
|
2006
|
+
// first `agentctl gate`/`agentctl task create` still rejected the tree
|
|
2007
|
+
// even after the user committed exactly what this message listed. Fold
|
|
2008
|
+
// the install-produced, currently-addable artifacts in so the suggested
|
|
2009
|
+
// commit actually leaves the tree clean enough for step three.
|
|
2010
|
+
const { addableOnboardingArtifacts } = await import("../src/git.mjs");
|
|
2011
|
+
const filesToAdd = [".agent", "AGENTS.md", ...rootContracts, ...addableOnboardingArtifacts(root), ".gitignore"]
|
|
2012
|
+
.filter((f) => existsSync(join(root, f)));
|
|
1832
2013
|
console.log(`\n Commit the manifest and contracts so the gate does not read them as agent edits:`);
|
|
1833
2014
|
console.log(` git add ${filesToAdd.join(" ")} && git commit -m "chore: add agent config"`);
|
|
1834
2015
|
|
|
@@ -1874,6 +2055,8 @@ async function main() {
|
|
|
1874
2055
|
interactive: values.interactive,
|
|
1875
2056
|
nonInteractive: values["non-interactive"] || values["no-interactive"],
|
|
1876
2057
|
yes: values.yes,
|
|
2058
|
+
ci: Boolean(process.env.CI),
|
|
2059
|
+
isTTY: Boolean(process.stdin.isTTY),
|
|
1877
2060
|
// A title and a prompt together state the whole task; there is
|
|
1878
2061
|
// nothing left for the wizard to ask.
|
|
1879
2062
|
fullySpecified: Boolean(values.title && resolvePromptInput(values, positionals)),
|
|
@@ -2027,14 +2210,32 @@ async function main() {
|
|
|
2027
2210
|
}
|
|
2028
2211
|
|
|
2029
2212
|
case "status": {
|
|
2213
|
+
const { values: statusValues } = parseArgs({
|
|
2214
|
+
args: args.slice(1),
|
|
2215
|
+
options: {
|
|
2216
|
+
json: { type: "boolean" },
|
|
2217
|
+
},
|
|
2218
|
+
allowPositionals: true,
|
|
2219
|
+
});
|
|
2030
2220
|
const queueDir = getQueueDir(root);
|
|
2031
2221
|
const files = existsSync(queueDir) ? readdirSync(queueDir).filter((f) => isTaskFile(f, queueDir)) : [];
|
|
2222
|
+
const budget = budgetStatus(config, root);
|
|
2223
|
+
if (statusValues.json) {
|
|
2224
|
+
console.log(JSON.stringify({
|
|
2225
|
+
ok: true,
|
|
2226
|
+
version: VERSION,
|
|
2227
|
+
root,
|
|
2228
|
+
pendingTasks: files.length,
|
|
2229
|
+
activeLocks: lockStatus(root).length,
|
|
2230
|
+
budget,
|
|
2231
|
+
}, null, 2));
|
|
2232
|
+
process.exit(0);
|
|
2233
|
+
}
|
|
2032
2234
|
console.log(`\n📊 agentctl Status Summary (v${VERSION})`);
|
|
2033
2235
|
console.log(`--------------------------------------------------`);
|
|
2034
2236
|
console.log(` Project Root : ${root}`);
|
|
2035
2237
|
console.log(` Pending Tasks : ${files.length}`);
|
|
2036
2238
|
console.log(` Active VFS Locks : ${lockStatus(root).length}`);
|
|
2037
|
-
const budget = budgetStatus(config, root);
|
|
2038
2239
|
console.log(` Daily Budget : ${formatBudgetLine(budget)}`);
|
|
2039
2240
|
console.log(`--------------------------------------------------\n`);
|
|
2040
2241
|
process.exit(0);
|
|
@@ -2042,7 +2243,7 @@ async function main() {
|
|
|
2042
2243
|
}
|
|
2043
2244
|
|
|
2044
2245
|
case "scan": {
|
|
2045
|
-
const { scanCodebaseForTodos } = await import("../
|
|
2246
|
+
const { scanCodebaseForTodos } = await import("../src/todo-scanner.mjs");
|
|
2046
2247
|
const todos = scanCodebaseForTodos(root);
|
|
2047
2248
|
console.log(`\n🔍 Scanned ${todos.length} TODO/FIXME annotation(s).`);
|
|
2048
2249
|
for (const t of todos.slice(0, 10)) {
|
|
@@ -2230,8 +2431,7 @@ async function main() {
|
|
|
2230
2431
|
|
|
2231
2432
|
case "resume": {
|
|
2232
2433
|
const { createProvider } = await import("../src/provider.mjs");
|
|
2233
|
-
const
|
|
2234
|
-
const { values } = parseArgs({
|
|
2434
|
+
const { values, positionals } = parseArgs({
|
|
2235
2435
|
args: args.slice(1),
|
|
2236
2436
|
options: {
|
|
2237
2437
|
response: { type: "string", short: "r" },
|
|
@@ -2240,13 +2440,14 @@ async function main() {
|
|
|
2240
2440
|
},
|
|
2241
2441
|
allowPositionals: true,
|
|
2242
2442
|
});
|
|
2443
|
+
const sessionId = positionals[0];
|
|
2243
2444
|
|
|
2244
2445
|
if (!sessionId || sessionId.startsWith("-")) {
|
|
2245
2446
|
console.error("Error: Session ID is required for agentctl resume <sessionId>.");
|
|
2246
2447
|
process.exit(1);
|
|
2247
2448
|
}
|
|
2248
2449
|
|
|
2249
|
-
const responseText = values.response ||
|
|
2450
|
+
const responseText = values.response || positionals.slice(1).join(" ");
|
|
2250
2451
|
if (!responseText) {
|
|
2251
2452
|
console.error("Error: --response text is required to resume warm session.");
|
|
2252
2453
|
process.exit(1);
|
|
@@ -2273,7 +2474,6 @@ async function main() {
|
|
|
2273
2474
|
case "plan": {
|
|
2274
2475
|
const subAction = args[1];
|
|
2275
2476
|
if (subAction === "approve") {
|
|
2276
|
-
const sessionId = args[2];
|
|
2277
2477
|
const { values, positionals } = parseArgs({
|
|
2278
2478
|
args: args.slice(2),
|
|
2279
2479
|
options: {
|
|
@@ -2282,7 +2482,7 @@ async function main() {
|
|
|
2282
2482
|
},
|
|
2283
2483
|
allowPositionals: true,
|
|
2284
2484
|
});
|
|
2285
|
-
const targetSessionId =
|
|
2485
|
+
const targetSessionId = positionals?.[0];
|
|
2286
2486
|
if (!targetSessionId) {
|
|
2287
2487
|
console.error("Error: Session ID is required for agentctl plan approve <sessionId>.");
|
|
2288
2488
|
process.exit(1);
|
|
@@ -2310,7 +2510,6 @@ async function main() {
|
|
|
2310
2510
|
}
|
|
2311
2511
|
|
|
2312
2512
|
case "approve": {
|
|
2313
|
-
const sessionId = args[1];
|
|
2314
2513
|
const { values, positionals } = parseArgs({
|
|
2315
2514
|
args: args.slice(1),
|
|
2316
2515
|
options: {
|
|
@@ -2319,7 +2518,7 @@ async function main() {
|
|
|
2319
2518
|
},
|
|
2320
2519
|
allowPositionals: true,
|
|
2321
2520
|
});
|
|
2322
|
-
const targetSessionId =
|
|
2521
|
+
const targetSessionId = positionals?.[0];
|
|
2323
2522
|
if (!targetSessionId) {
|
|
2324
2523
|
console.error("Error: Session ID is required for agentctl approve <sessionId>.");
|
|
2325
2524
|
process.exit(1);
|
|
@@ -2346,7 +2545,6 @@ async function main() {
|
|
|
2346
2545
|
case "session": {
|
|
2347
2546
|
const subAction = args[1];
|
|
2348
2547
|
if (subAction === "get" || subAction === "status") {
|
|
2349
|
-
const sessionId = args[2];
|
|
2350
2548
|
const { values, positionals } = parseArgs({
|
|
2351
2549
|
args: args.slice(2),
|
|
2352
2550
|
options: {
|
|
@@ -2355,7 +2553,7 @@ async function main() {
|
|
|
2355
2553
|
},
|
|
2356
2554
|
allowPositionals: true,
|
|
2357
2555
|
});
|
|
2358
|
-
const targetSessionId =
|
|
2556
|
+
const targetSessionId = positionals?.[0];
|
|
2359
2557
|
if (!targetSessionId) {
|
|
2360
2558
|
console.error("Error: Session ID is required for agentctl session get <sessionId>.");
|
|
2361
2559
|
process.exit(1);
|
|
@@ -2377,7 +2575,103 @@ async function main() {
|
|
|
2377
2575
|
process.exit(1);
|
|
2378
2576
|
}
|
|
2379
2577
|
}
|
|
2380
|
-
|
|
2578
|
+
|
|
2579
|
+
if (subAction === "list" || subAction === "ls") {
|
|
2580
|
+
const { values } = parseArgs({
|
|
2581
|
+
args: args.slice(2),
|
|
2582
|
+
options: {
|
|
2583
|
+
limit: { type: "string", short: "l" },
|
|
2584
|
+
"page-size": { type: "string" },
|
|
2585
|
+
remote: { type: "boolean" },
|
|
2586
|
+
"dry-run": { type: "boolean", short: "d" },
|
|
2587
|
+
json: { type: "boolean", short: "j" },
|
|
2588
|
+
},
|
|
2589
|
+
allowPositionals: true,
|
|
2590
|
+
});
|
|
2591
|
+
|
|
2592
|
+
const limit = Number(values.limit || values["page-size"]) || 20;
|
|
2593
|
+
const { createProvider } = await import("../src/provider.mjs");
|
|
2594
|
+
const provider = createProvider(config.provider || "jules", config);
|
|
2595
|
+
|
|
2596
|
+
try {
|
|
2597
|
+
const res = await provider.listSessions({
|
|
2598
|
+
root,
|
|
2599
|
+
pageSize: limit,
|
|
2600
|
+
dryRun: values["dry-run"],
|
|
2601
|
+
});
|
|
2602
|
+
const remoteSessions = Array.isArray(res?.sessions) ? res.sessions : [];
|
|
2603
|
+
|
|
2604
|
+
const localSessions = [];
|
|
2605
|
+
try {
|
|
2606
|
+
const { getStateDir } = await import("../src/state.mjs");
|
|
2607
|
+
const { readdirSync, readFileSync, existsSync } = await import("node:fs");
|
|
2608
|
+
const stateDir = getStateDir(root);
|
|
2609
|
+
if (existsSync(stateDir)) {
|
|
2610
|
+
const files = readdirSync(stateDir).filter((f) => f.startsWith("ledger-") && f.endsWith(".jsonl"));
|
|
2611
|
+
for (const file of files) {
|
|
2612
|
+
const lines = readFileSync(join(stateDir, file), "utf-8").split("\n").filter(Boolean);
|
|
2613
|
+
for (const line of lines) {
|
|
2614
|
+
try {
|
|
2615
|
+
const parsed = JSON.parse(line);
|
|
2616
|
+
if (parsed.sessionId) {
|
|
2617
|
+
localSessions.push({
|
|
2618
|
+
id: parsed.sessionId,
|
|
2619
|
+
status: parsed.status || "DISPATCHED",
|
|
2620
|
+
timestamp: parsed.timestamp,
|
|
2621
|
+
title: parsed.task || parsed.title || "",
|
|
2622
|
+
source: "local-ledger",
|
|
2623
|
+
});
|
|
2624
|
+
}
|
|
2625
|
+
} catch (_) {}
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
} catch (_) {}
|
|
2630
|
+
|
|
2631
|
+
const sessionMap = new Map();
|
|
2632
|
+
for (const s of localSessions) {
|
|
2633
|
+
sessionMap.set(s.id, s);
|
|
2634
|
+
}
|
|
2635
|
+
for (const s of remoteSessions) {
|
|
2636
|
+
const sid = s.id || s.name;
|
|
2637
|
+
if (sid) {
|
|
2638
|
+
sessionMap.set(sid, {
|
|
2639
|
+
id: sid,
|
|
2640
|
+
status: s.state || s.status || "UNKNOWN",
|
|
2641
|
+
createTime: s.createTime || s.created_at,
|
|
2642
|
+
title: s.title || s.prompt?.slice(0, 60) || "",
|
|
2643
|
+
source: "remote",
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
const combined = Array.from(sessionMap.values()).slice(0, limit);
|
|
2649
|
+
|
|
2650
|
+
if (values.json) {
|
|
2651
|
+
console.log(JSON.stringify({ ok: true, sessions: combined, count: combined.length, nextPageToken: res?.nextPageToken }, null, 2));
|
|
2652
|
+
} else {
|
|
2653
|
+
console.log(`\n📋 Jules Sessions List (${combined.length} found):`);
|
|
2654
|
+
console.log("--------------------------------------------------");
|
|
2655
|
+
if (combined.length === 0) {
|
|
2656
|
+
console.log(" No active or recent sessions found.");
|
|
2657
|
+
} else {
|
|
2658
|
+
for (const s of combined) {
|
|
2659
|
+
const sid = (s.id || "unknown").slice(0, 24).padEnd(26);
|
|
2660
|
+
const st = (s.status || "UNKNOWN").padEnd(14);
|
|
2661
|
+
const desc = s.title || s.source || "";
|
|
2662
|
+
console.log(` • ${sid} [${st}] ${desc}`);
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
console.log("--------------------------------------------------\n");
|
|
2666
|
+
}
|
|
2667
|
+
process.exit(0);
|
|
2668
|
+
} catch (err) {
|
|
2669
|
+
console.error(`❌ Session List Failed: ${err.message}`);
|
|
2670
|
+
process.exit(1);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
console.error(`Error: Unknown session subcommand '${subAction}'. Use get or list.`);
|
|
2381
2675
|
process.exit(1);
|
|
2382
2676
|
break;
|
|
2383
2677
|
}
|
|
@@ -2567,7 +2861,7 @@ async function main() {
|
|
|
2567
2861
|
args: args.slice(2),
|
|
2568
2862
|
options: {
|
|
2569
2863
|
dispatch: { type: "boolean" },
|
|
2570
|
-
role: { type: "string", short: "r", default: "
|
|
2864
|
+
role: { type: "string", short: "r", default: "hygiene" },
|
|
2571
2865
|
"test-cmd": { type: "string", short: "t" },
|
|
2572
2866
|
"dry-run": { type: "boolean", short: "d" },
|
|
2573
2867
|
json: { type: "boolean", short: "j" },
|
|
@@ -2672,7 +2966,7 @@ async function main() {
|
|
|
2672
2966
|
|
|
2673
2967
|
case "test-gen": {
|
|
2674
2968
|
const { scaffoldTddTest, runTddCycle } = await import("../src/ops/tdd-generator.mjs");
|
|
2675
|
-
const { values } = parseArgs({
|
|
2969
|
+
const { values, positionals } = parseArgs({
|
|
2676
2970
|
args: args.slice(1),
|
|
2677
2971
|
options: {
|
|
2678
2972
|
title: { type: "string", short: "t" },
|
|
@@ -2684,8 +2978,8 @@ async function main() {
|
|
|
2684
2978
|
allowPositionals: true,
|
|
2685
2979
|
});
|
|
2686
2980
|
|
|
2687
|
-
const title = values.title ||
|
|
2688
|
-
const specText = values.spec ||
|
|
2981
|
+
const title = values.title || positionals[0] || "feature-spec";
|
|
2982
|
+
const specText = values.spec || positionals.slice(1).join(" ") || "TDD requirement specification.";
|
|
2689
2983
|
|
|
2690
2984
|
try {
|
|
2691
2985
|
if (values.run) {
|
|
@@ -2814,11 +3108,16 @@ async function main() {
|
|
|
2814
3108
|
}
|
|
2815
3109
|
|
|
2816
3110
|
case "learning": {
|
|
2817
|
-
const
|
|
3111
|
+
const { positionals: learningPositionals } = parseArgs({
|
|
3112
|
+
args: args.slice(1),
|
|
3113
|
+
options: {},
|
|
3114
|
+
allowPositionals: true,
|
|
3115
|
+
});
|
|
3116
|
+
const subcmd = learningPositionals[0];
|
|
2818
3117
|
if (subcmd === "add") {
|
|
2819
3118
|
const { recordLearning } = await import("../src/memory.mjs");
|
|
2820
|
-
const trigger =
|
|
2821
|
-
const solution =
|
|
3119
|
+
const trigger = learningPositionals[1];
|
|
3120
|
+
const solution = learningPositionals[2];
|
|
2822
3121
|
if (!trigger || !solution) {
|
|
2823
3122
|
console.error('Usage: agentctl learning add "<trigger/symptom>" "<solution>"');
|
|
2824
3123
|
process.exit(1);
|