jules-orchestrator-kit 0.27.0 → 0.28.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/README.md +226 -108
- package/bin/agentctl.mjs +77 -12
- package/bin/init.js +10 -8
- package/index.mjs +5 -2
- package/package.json +1 -1
- package/scripts/jules-dispatch.mjs +6 -4
- package/scripts/jules-self-audit.mjs +14 -3
- package/src/config.mjs +14 -1
- package/src/dashboard.mjs +7 -14
- package/src/engine.mjs +16 -10
- package/src/git.mjs +65 -48
- package/src/mcp.mjs +1 -1
- package/src/prompt-guard.mjs +4 -1
- package/src/provider.mjs +42 -10
- package/src/process-group.mjs +0 -80
package/bin/agentctl.mjs
CHANGED
|
@@ -14,12 +14,12 @@ const command = args[0];
|
|
|
14
14
|
|
|
15
15
|
function printHelp() {
|
|
16
16
|
console.log(`
|
|
17
|
-
🚀 agentctl v0.
|
|
17
|
+
🚀 agentctl v0.28.1 — Universal Agent Orchestrator & Safety Gatekeeper
|
|
18
18
|
|
|
19
19
|
Usage: agentctl <command> [options]
|
|
20
20
|
|
|
21
21
|
Commands:
|
|
22
|
-
dispatch
|
|
22
|
+
dispatch | create Dispatch a single task to an AI agent
|
|
23
23
|
gate | audit Run CI security and verification gate against current branch
|
|
24
24
|
queue Run pending task queue
|
|
25
25
|
swarm Run parallel task swarm
|
|
@@ -31,10 +31,16 @@ Commands:
|
|
|
31
31
|
review-repair Parse PR review comments and synthesize OODA repair tasks
|
|
32
32
|
dashboard Start local HTTP telemetry and audit dashboard
|
|
33
33
|
init Scaffold .agent/ directory and config.yml
|
|
34
|
+
status Display queue and system status summary
|
|
35
|
+
scan Scan codebase for TODO/FIXME task candidates
|
|
34
36
|
version Output agentctl version
|
|
35
37
|
|
|
36
38
|
Options:
|
|
37
39
|
--dry-run, -d Simulate action without making API calls or modifying git
|
|
40
|
+
--mode, -m Gate evaluation mode (working-tree | committed | staged)
|
|
41
|
+
--repoless Dispatch task in repoless execution mode
|
|
42
|
+
--source, -s Specify Jules repository source name
|
|
43
|
+
--branch, -b Specify target starting branch
|
|
38
44
|
--json, -j Emit machine-readable JSON output
|
|
39
45
|
--help, -h Show command help
|
|
40
46
|
`);
|
|
@@ -47,7 +53,7 @@ async function main() {
|
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
if (command === "version" || command === "--version" || command === "-v") {
|
|
50
|
-
console.log("agentctl v0.
|
|
56
|
+
console.log("agentctl v0.28.1");
|
|
51
57
|
process.exit(0);
|
|
52
58
|
}
|
|
53
59
|
|
|
@@ -57,13 +63,19 @@ async function main() {
|
|
|
57
63
|
const config = loadConfig(root);
|
|
58
64
|
|
|
59
65
|
switch (command) {
|
|
60
|
-
case "dispatch":
|
|
66
|
+
case "dispatch":
|
|
67
|
+
case "create": {
|
|
61
68
|
const { values } = parseArgs({
|
|
62
69
|
args: args.slice(1),
|
|
63
70
|
options: {
|
|
64
71
|
title: { type: "string", short: "t" },
|
|
65
72
|
prompt: { type: "string", short: "p" },
|
|
66
73
|
"prompt-file": { type: "string", short: "f" },
|
|
74
|
+
source: { type: "string", short: "s" },
|
|
75
|
+
branch: { type: "string", short: "b" },
|
|
76
|
+
repoless: { type: "boolean" },
|
|
77
|
+
"auto-pr": { type: "boolean" },
|
|
78
|
+
"require-plan-approval": { type: "boolean" },
|
|
67
79
|
"dry-run": { type: "boolean", short: "d" },
|
|
68
80
|
json: { type: "boolean", short: "j" },
|
|
69
81
|
},
|
|
@@ -87,10 +99,22 @@ async function main() {
|
|
|
87
99
|
const task = {
|
|
88
100
|
title: values.title || "CLI Dispatch Task",
|
|
89
101
|
prompt: promptContent,
|
|
102
|
+
source: values.source,
|
|
103
|
+
branch: values.branch,
|
|
104
|
+
repoless: values.repoless,
|
|
105
|
+
autoPr: values["auto-pr"],
|
|
106
|
+
requirePlanApproval: values["require-plan-approval"],
|
|
90
107
|
};
|
|
91
108
|
|
|
92
109
|
try {
|
|
93
|
-
const session = await dispatch(task, {
|
|
110
|
+
const session = await dispatch(task, {
|
|
111
|
+
root,
|
|
112
|
+
config,
|
|
113
|
+
dryRun: values["dry-run"],
|
|
114
|
+
repoless: values.repoless,
|
|
115
|
+
source: values.source,
|
|
116
|
+
branch: values.branch,
|
|
117
|
+
});
|
|
94
118
|
if (values.json) {
|
|
95
119
|
console.log(JSON.stringify({ ok: true, session }, null, 2));
|
|
96
120
|
} else {
|
|
@@ -100,12 +124,13 @@ async function main() {
|
|
|
100
124
|
}
|
|
101
125
|
process.exit(0);
|
|
102
126
|
} catch (err) {
|
|
127
|
+
const exitCode = typeof err.code === "number" ? err.code : 1;
|
|
103
128
|
if (values.json) {
|
|
104
|
-
console.log(JSON.stringify({ ok: false, error: err.message, code:
|
|
129
|
+
console.log(JSON.stringify({ ok: false, error: err.message, code: exitCode }, null, 2));
|
|
105
130
|
} else {
|
|
106
131
|
console.error(`❌ Dispatch Failed: ${err.message}`);
|
|
107
132
|
}
|
|
108
|
-
process.exit(
|
|
133
|
+
process.exit(exitCode);
|
|
109
134
|
}
|
|
110
135
|
break;
|
|
111
136
|
}
|
|
@@ -116,6 +141,10 @@ async function main() {
|
|
|
116
141
|
args: args.slice(1),
|
|
117
142
|
options: {
|
|
118
143
|
base: { type: "string", short: "b", default: config.baseBranch || "main" },
|
|
144
|
+
mode: { type: "string", short: "m", default: "working-tree" },
|
|
145
|
+
"working-tree": { type: "boolean" },
|
|
146
|
+
staged: { type: "boolean" },
|
|
147
|
+
committed: { type: "boolean" },
|
|
119
148
|
fix: { type: "boolean" },
|
|
120
149
|
"allow-protected": { type: "boolean" },
|
|
121
150
|
json: { type: "boolean", short: "j" },
|
|
@@ -123,10 +152,16 @@ async function main() {
|
|
|
123
152
|
allowPositionals: true,
|
|
124
153
|
});
|
|
125
154
|
|
|
155
|
+
let selectedMode = values.mode || "working-tree";
|
|
156
|
+
if (values.staged) selectedMode = "staged";
|
|
157
|
+
if (values.committed) selectedMode = "committed";
|
|
158
|
+
if (values["working-tree"]) selectedMode = "working-tree";
|
|
159
|
+
|
|
126
160
|
const res = await gate({
|
|
127
161
|
root,
|
|
128
162
|
config,
|
|
129
163
|
base: values.base,
|
|
164
|
+
mode: selectedMode,
|
|
130
165
|
fix: values.fix,
|
|
131
166
|
allowProtected: values["allow-protected"],
|
|
132
167
|
});
|
|
@@ -134,7 +169,7 @@ async function main() {
|
|
|
134
169
|
if (values.json) {
|
|
135
170
|
console.log(JSON.stringify(res, null, 2));
|
|
136
171
|
} else {
|
|
137
|
-
console.log(`\n🛡️ agentctl Safety Gate Audit Results (Base: ${values.base})`);
|
|
172
|
+
console.log(`\n🛡️ agentctl Safety Gate Audit Results (Base: ${values.base}, Mode: ${selectedMode})`);
|
|
138
173
|
console.log(`-----------------------------------------------------`);
|
|
139
174
|
for (const p of res.phases) {
|
|
140
175
|
const status = p.ok ? "✅ PASS" : "❌ FAIL";
|
|
@@ -150,14 +185,14 @@ async function main() {
|
|
|
150
185
|
console.log(`Overall Result: ${res.ok ? "APPROVED (Exit 0)" : `REJECTED (Exit ${res.code})`}\n`);
|
|
151
186
|
}
|
|
152
187
|
|
|
153
|
-
process.exit(res.code);
|
|
188
|
+
process.exit(typeof res.code === "number" ? res.code : 0);
|
|
154
189
|
break;
|
|
155
190
|
}
|
|
156
191
|
|
|
157
192
|
case "queue": {
|
|
158
193
|
const queueDir = getQueueDir(root);
|
|
159
194
|
const files = readdirSync(queueDir).filter((f) => isTaskFile(f, queueDir));
|
|
160
|
-
console.log(`
|
|
195
|
+
console.log(`Found ${files.length} queued task(s) in .agent/queue/`);
|
|
161
196
|
if (files.length > 0) {
|
|
162
197
|
const tasks = files.map((f) => ({
|
|
163
198
|
id: f,
|
|
@@ -229,7 +264,7 @@ async function main() {
|
|
|
229
264
|
}
|
|
230
265
|
|
|
231
266
|
case "doctor": {
|
|
232
|
-
console.log(`\n🔍 agentctl System Diagnostics (v0.
|
|
267
|
+
console.log(`\n🔍 agentctl System Diagnostics (v0.28.1)`);
|
|
233
268
|
console.log(`--------------------------------------------------`);
|
|
234
269
|
console.log(` Project Root : ${root}`);
|
|
235
270
|
console.log(` Config File : ${config._file || "None (Using defaults)"}`);
|
|
@@ -315,6 +350,35 @@ base_branch: main
|
|
|
315
350
|
break;
|
|
316
351
|
}
|
|
317
352
|
|
|
353
|
+
case "status": {
|
|
354
|
+
const queueDir = getQueueDir(root);
|
|
355
|
+
const files = existsSync(queueDir) ? readdirSync(queueDir).filter((f) => isTaskFile(f, queueDir)) : [];
|
|
356
|
+
console.log(`\n📊 agentctl Status Summary (v0.28.1)`);
|
|
357
|
+
console.log(`--------------------------------------------------`);
|
|
358
|
+
console.log(` Project Root : ${root}`);
|
|
359
|
+
console.log(` Pending Tasks : ${files.length}`);
|
|
360
|
+
console.log(` Active VFS Locks : ${lockStatus(root).length}`);
|
|
361
|
+
const budget = checkDailyBudget(root, config.limits.dailyTasks);
|
|
362
|
+
console.log(` Daily Budget : ${budget.used} / ${budget.budget} used`);
|
|
363
|
+
console.log(`--------------------------------------------------\n`);
|
|
364
|
+
process.exit(0);
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
case "scan": {
|
|
369
|
+
const { scanCodebaseForTodos } = await import("../scripts/jules-scan-todos.mjs");
|
|
370
|
+
const todos = scanCodebaseForTodos(root);
|
|
371
|
+
console.log(`\n🔍 Scanned ${todos.length} TODO/FIXME annotation(s).`);
|
|
372
|
+
for (const t of todos.slice(0, 10)) {
|
|
373
|
+
console.log(` - ${t.file}:${t.line} [${t.type}] ${t.text}`);
|
|
374
|
+
}
|
|
375
|
+
if (todos.length > 10) {
|
|
376
|
+
console.log(` ... and ${todos.length - 10} more.`);
|
|
377
|
+
}
|
|
378
|
+
process.exit(0);
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
|
|
318
382
|
case "mcp": {
|
|
319
383
|
const { startMcpServer } = await import("../src/mcp.mjs");
|
|
320
384
|
startMcpServer();
|
|
@@ -330,5 +394,6 @@ base_branch: main
|
|
|
330
394
|
|
|
331
395
|
main().catch((err) => {
|
|
332
396
|
console.error(`[FATAL ERROR] ${err.message}`);
|
|
333
|
-
|
|
397
|
+
const exitCode = typeof err.code === "number" ? err.code : 1;
|
|
398
|
+
process.exit(exitCode);
|
|
334
399
|
});
|
package/bin/init.js
CHANGED
|
@@ -221,7 +221,9 @@ let skippedCount = 0;
|
|
|
221
221
|
|
|
222
222
|
if (fs.existsSync(sourceScriptsDir)) {
|
|
223
223
|
const scriptFiles = fs.readdirSync(sourceScriptsDir);
|
|
224
|
+
const excludeFiles = new Set(["release.mjs"]);
|
|
224
225
|
scriptFiles.forEach((file) => {
|
|
226
|
+
if (excludeFiles.has(file)) return;
|
|
225
227
|
const srcFile = path.join(sourceScriptsDir, file);
|
|
226
228
|
const destFile = path.join(targetScriptsDir, file);
|
|
227
229
|
if (!fs.existsSync(destFile) || isForce) {
|
|
@@ -247,14 +249,14 @@ if (fs.existsSync(targetPkgPath) && targetDir !== kitRoot) {
|
|
|
247
249
|
pkg.scripts = pkg.scripts || {};
|
|
248
250
|
let updated = false;
|
|
249
251
|
const julesScripts = {
|
|
250
|
-
"jules:dispatch": "
|
|
251
|
-
"jules:queue": "
|
|
252
|
-
"jules:create": "
|
|
253
|
-
"jules:status": "
|
|
254
|
-
"jules:audit": "
|
|
255
|
-
"jules:scan": "
|
|
256
|
-
"jules:swarm": "
|
|
257
|
-
"jules:nightly": "
|
|
252
|
+
"jules:dispatch": "npx agentctl dispatch",
|
|
253
|
+
"jules:queue": "npx agentctl queue",
|
|
254
|
+
"jules:create": "npx agentctl create",
|
|
255
|
+
"jules:status": "npx agentctl status",
|
|
256
|
+
"jules:audit": "npx agentctl gate",
|
|
257
|
+
"jules:scan": "npx agentctl scan",
|
|
258
|
+
"jules:swarm": "npx agentctl swarm",
|
|
259
|
+
"jules:nightly": "npx agentctl clean"
|
|
258
260
|
};
|
|
259
261
|
|
|
260
262
|
for (const [key, val] of Object.entries(julesScripts)) {
|
package/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Google Jules Orchestrator Kit - Node.js SDK (v0.
|
|
2
|
+
* Google Jules Orchestrator Kit - Node.js SDK (v0.28.1)
|
|
3
3
|
*
|
|
4
4
|
* Exposes core orchestrator functions for programmatically driving agent tasks,
|
|
5
5
|
* security auditing, repo gating, and state operations.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export { loadConfig, detectStack, resolveVerify, resolveRoot, normalizePath, TIER_PRESETS } from "./src/config.mjs";
|
|
8
|
+
export { loadConfig, parseYaml, detectStack, resolveVerify, resolveRoot, normalizePath, TIER_PRESETS } from "./src/config.mjs";
|
|
9
9
|
export {
|
|
10
10
|
shannonEntropy,
|
|
11
11
|
redactSecrets,
|
|
@@ -19,14 +19,17 @@ export { isolateMcpStdout, writeMcpFrame } from "./src/mcp.mjs";
|
|
|
19
19
|
export { git, runCmd, resolveBase, changedFiles, diffBytes, diffText } from "./src/git.mjs";
|
|
20
20
|
export {
|
|
21
21
|
createProvider,
|
|
22
|
+
createFailoverProvider,
|
|
22
23
|
JULES_PRESET,
|
|
23
24
|
CLAUDE_PRESET,
|
|
24
25
|
CODEX_PRESET,
|
|
26
|
+
MissingApiKeyError,
|
|
25
27
|
ProviderRateLimitError,
|
|
26
28
|
ProviderUnavailableError,
|
|
27
29
|
ProviderSchemaError,
|
|
28
30
|
parseRetryAfter,
|
|
29
31
|
} from "./src/provider.mjs";
|
|
32
|
+
export { detectPolyglotStack, resolveWorkspaceBoundary, bootstrapZeroTestRepo } from "./src/stack-detector.mjs";
|
|
30
33
|
export {
|
|
31
34
|
appendLedger,
|
|
32
35
|
readLedger,
|
package/package.json
CHANGED
|
@@ -10,10 +10,12 @@ import { loadConfig } from "../src/config.mjs";
|
|
|
10
10
|
import { existsSync, statSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
|
|
13
|
-
process.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
if (process.argv[1] && (process.argv[1].endsWith("jules-dispatch.mjs") || process.argv[1].endsWith("jules-dispatch"))) {
|
|
14
|
+
process.emitWarning(
|
|
15
|
+
"scripts/jules-dispatch.mjs is deprecated and will be removed in v1.0.0. Use 'agentctl dispatch' or the programmatic SDK.",
|
|
16
|
+
"DeprecationWarning"
|
|
17
|
+
);
|
|
18
|
+
}
|
|
17
19
|
|
|
18
20
|
const args = process.argv.slice(2);
|
|
19
21
|
let title = "Task Dispatch";
|
|
@@ -5,12 +5,23 @@
|
|
|
5
5
|
* Delegates execution to src/engine.mjs gate().
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { gate } from "../src/engine.mjs";
|
|
9
|
-
import { loadConfig, parseYaml } from "../src/config.mjs";
|
|
10
|
-
import { matchesGlob } from "../src/security.mjs";
|
|
11
8
|
import { existsSync } from "node:fs";
|
|
12
9
|
import { join } from "node:path";
|
|
13
10
|
|
|
11
|
+
let gate, loadConfig, parseYaml, matchesGlob;
|
|
12
|
+
try {
|
|
13
|
+
({ gate } = await import("../src/engine.mjs"));
|
|
14
|
+
({ loadConfig, parseYaml } = await import("../src/config.mjs"));
|
|
15
|
+
({ matchesGlob } = await import("../src/security.mjs"));
|
|
16
|
+
} catch (_) {
|
|
17
|
+
try {
|
|
18
|
+
({ gate, loadConfig, parseYaml, matchesGlob } = await import("jules-orchestrator-kit"));
|
|
19
|
+
} catch (err) {
|
|
20
|
+
console.error("Error: Could not resolve jules-orchestrator-kit modules.", err.message);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
14
25
|
export function matchGlob(filepath, globPattern) {
|
|
15
26
|
return matchesGlob(filepath, globPattern);
|
|
16
27
|
}
|
package/src/config.mjs
CHANGED
|
@@ -257,6 +257,19 @@ export function loadConfig(root = resolveRoot(), explicitPath = null) {
|
|
|
257
257
|
const envDailyTasks = process.env.JULES_DAILY_BUDGET !== undefined ? Number(process.env.JULES_DAILY_BUDGET) : null;
|
|
258
258
|
const envDiffKb = process.env.JULES_MAX_DIFF_KB !== undefined ? Number(process.env.JULES_MAX_DIFF_KB) : null;
|
|
259
259
|
|
|
260
|
+
const parsedLimits = parsed.limits || {};
|
|
261
|
+
const normalizedLimits = {
|
|
262
|
+
diffKb: parsedLimits.diff_kb ?? parsedLimits.diffKb,
|
|
263
|
+
promptKb: parsedLimits.prompt_kb ?? parsedLimits.promptKb,
|
|
264
|
+
dailyTasks: parsedLimits.daily_tasks ?? parsedLimits.dailyTasks,
|
|
265
|
+
repairAttempts: parsedLimits.repair_attempts ?? parsedLimits.repairAttempts,
|
|
266
|
+
concurrency: parsedLimits.concurrency,
|
|
267
|
+
staggerMs: parsedLimits.stagger_ms ?? parsedLimits.staggerMs,
|
|
268
|
+
};
|
|
269
|
+
for (const k of Object.keys(normalizedLimits)) {
|
|
270
|
+
if (normalizedLimits[k] === undefined) delete normalizedLimits[k];
|
|
271
|
+
}
|
|
272
|
+
|
|
260
273
|
const config = {
|
|
261
274
|
version: parsed.version || DEFAULTS.version,
|
|
262
275
|
provider: parsed.provider || DEFAULTS.provider,
|
|
@@ -269,7 +282,7 @@ export function loadConfig(root = resolveRoot(), explicitPath = null) {
|
|
|
269
282
|
limits: {
|
|
270
283
|
...DEFAULTS.limits,
|
|
271
284
|
...tierLimits,
|
|
272
|
-
...
|
|
285
|
+
...normalizedLimits,
|
|
273
286
|
...(envDailyTasks !== null && !isNaN(envDailyTasks) ? { dailyTasks: envDailyTasks } : {}),
|
|
274
287
|
...(envDiffKb !== null && !isNaN(envDiffKb) ? { diffKb: envDiffKb } : {}),
|
|
275
288
|
},
|
package/src/dashboard.mjs
CHANGED
|
@@ -32,7 +32,7 @@ export function getDashboardHtml(root = process.cwd()) {
|
|
|
32
32
|
</style>
|
|
33
33
|
</head>
|
|
34
34
|
<body>
|
|
35
|
-
<h1>🚀 jules-orchestrator-kit Dashboard (v0.
|
|
35
|
+
<h1>🚀 jules-orchestrator-kit Dashboard (v0.28.1)</h1>
|
|
36
36
|
<div class="subtitle">Repository: <code>${root}</code></div>
|
|
37
37
|
|
|
38
38
|
<div class="grid">
|
|
@@ -59,11 +59,11 @@ export function getDashboardHtml(root = process.cwd()) {
|
|
|
59
59
|
fetch('/api/locks').then(r => r.json())
|
|
60
60
|
]);
|
|
61
61
|
|
|
62
|
-
document.getElementById('telemetry-status').innerHTML = telRes.integrity?.valid
|
|
62
|
+
document.getElementById('telemetry-status').innerHTML = (telRes.integrity?.ok || telRes.integrity?.valid)
|
|
63
63
|
? '<span class="badge badge-success">✓ SHA-256 Hash Chain Valid</span> <p>Events logged: ' + telRes.events.length + '</p>'
|
|
64
64
|
: '<span class="badge badge-warn">⚠ Check Telemetry Logs</span>';
|
|
65
65
|
|
|
66
|
-
document.getElementById('flaky-status').innerHTML = '<p>Verdict: <code>' + (flakyRes.verdict?.action || 'CLEAN') + '</code></p><p>Tracked test runs: ' + flakyRes.count + '</p>';
|
|
66
|
+
document.getElementById('flaky-status').innerHTML = '<p>Verdict: <code>' + (flakyRes.verdict?.verdict || flakyRes.verdict?.action || 'CLEAN') + '</code></p><p>Tracked test runs: ' + flakyRes.count + '</p>';
|
|
67
67
|
document.getElementById('locks-status').innerHTML = '<p>Active VFS Locks: <code>' + locksRes.count + '</code></p>';
|
|
68
68
|
} catch (err) {
|
|
69
69
|
console.error("Dashboard update failed:", err);
|
|
@@ -79,13 +79,6 @@ export function getDashboardHtml(root = process.cwd()) {
|
|
|
79
79
|
export function createDashboardServer({ root = process.cwd(), port: _port = 4100 } = {}) {
|
|
80
80
|
return http.createServer((req, res) => {
|
|
81
81
|
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
82
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
83
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
84
|
-
|
|
85
|
-
if (req.method === "OPTIONS") {
|
|
86
|
-
res.writeHead(204);
|
|
87
|
-
return res.end();
|
|
88
|
-
}
|
|
89
82
|
|
|
90
83
|
if (url.pathname === "/" || url.pathname === "/index.html") {
|
|
91
84
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
@@ -94,7 +87,7 @@ export function createDashboardServer({ root = process.cwd(), port: _port = 4100
|
|
|
94
87
|
|
|
95
88
|
if (url.pathname === "/api/status") {
|
|
96
89
|
res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
|
|
97
|
-
return res.end(JSON.stringify({ ok: true, version: "0.
|
|
90
|
+
return res.end(JSON.stringify({ ok: true, version: "0.28.1", root, ts: new Date().toISOString() }));
|
|
98
91
|
}
|
|
99
92
|
|
|
100
93
|
if (url.pathname === "/api/telemetry") {
|
|
@@ -127,10 +120,10 @@ export function createDashboardServer({ root = process.cwd(), port: _port = 4100
|
|
|
127
120
|
});
|
|
128
121
|
}
|
|
129
122
|
|
|
130
|
-
export function startDashboardServer(port = 4100, root = process.cwd()) {
|
|
123
|
+
export function startDashboardServer(port = 4100, root = process.cwd(), host = "127.0.0.1") {
|
|
131
124
|
const server = createDashboardServer({ root, port });
|
|
132
|
-
server.listen(port,
|
|
133
|
-
console.log(`🚀 jules-orchestrator-kit Dashboard running at http
|
|
125
|
+
server.listen(port, host, () => {
|
|
126
|
+
console.log(`🚀 jules-orchestrator-kit Dashboard running at http://${host}:${port}`);
|
|
134
127
|
});
|
|
135
128
|
return server;
|
|
136
129
|
}
|
package/src/engine.mjs
CHANGED
|
@@ -493,9 +493,9 @@ export async function dispatch(task, opts = {}) {
|
|
|
493
493
|
const cleanPrompt = redactSecrets(task.prompt || "");
|
|
494
494
|
|
|
495
495
|
// Wire Prompt Guard & Envelope to wrap raw task arguments
|
|
496
|
-
const
|
|
496
|
+
const taskInstructions = task.taskInstructions || cleanPrompt || task.title || "Autonomous Task Execution";
|
|
497
|
+
const untrustedData = Array.isArray(task.untrustedData) ? task.untrustedData : [];
|
|
497
498
|
const systemPolicy = task.systemPolicy || config.systemPolicy || "";
|
|
498
|
-
const taskInstructions = task.taskInstructions || task.title || "Autonomous Task Execution";
|
|
499
499
|
const envelopedPrompt = buildAgentEnvelope(systemPolicy, taskInstructions, untrustedData);
|
|
500
500
|
|
|
501
501
|
const cleanTask = { ...task, prompt: envelopedPrompt };
|
|
@@ -547,7 +547,7 @@ export async function run(tasksOrOpts = {}, opts = {}) {
|
|
|
547
547
|
|
|
548
548
|
let filesToProcess = [];
|
|
549
549
|
if (Array.isArray(tasks)) {
|
|
550
|
-
filesToProcess = tasks
|
|
550
|
+
filesToProcess = tasks;
|
|
551
551
|
} else {
|
|
552
552
|
filesToProcess = readdirSync(queueDir).filter((f) => isTaskFile(f, queueDir));
|
|
553
553
|
}
|
|
@@ -558,21 +558,27 @@ export async function run(tasksOrOpts = {}, opts = {}) {
|
|
|
558
558
|
const batch = filesToProcess.slice(i, i + concurrency);
|
|
559
559
|
await Promise.all(
|
|
560
560
|
batch.map(async (fileOrItem) => {
|
|
561
|
-
const fileName = typeof fileOrItem === "string" ? fileOrItem : fileOrItem.id || fileOrItem.file;
|
|
561
|
+
const fileName = typeof fileOrItem === "string" ? fileOrItem : fileOrItem.id || fileOrItem.file || fileOrItem.title || "task";
|
|
562
562
|
const srcPath = join(queueDir, fileName);
|
|
563
563
|
try {
|
|
564
564
|
let prompt = typeof fileOrItem === "object" && fileOrItem.prompt ? fileOrItem.prompt : "";
|
|
565
|
+
let title = typeof fileOrItem === "object" && fileOrItem.title ? fileOrItem.title : fileName;
|
|
565
566
|
if (!prompt && existsSync(srcPath)) {
|
|
566
567
|
prompt = await fs.promises.readFile(srcPath, "utf-8");
|
|
567
568
|
}
|
|
568
|
-
const task = { title:
|
|
569
|
+
const task = typeof fileOrItem === "object" ? { ...fileOrItem, title, prompt } : { title, prompt };
|
|
569
570
|
const session = await dispatch(task, { root, config, dryRun: isDry });
|
|
570
|
-
|
|
571
|
-
if (
|
|
572
|
-
|
|
571
|
+
|
|
572
|
+
if (session && session.ok === false) {
|
|
573
|
+
results.push({ file: fileName, ok: false, status: session.status, error: session.error, session });
|
|
574
|
+
} else {
|
|
575
|
+
const dstPath = join(completedDir, fileName);
|
|
576
|
+
if (existsSync(srcPath)) {
|
|
577
|
+
renameSync(srcPath, dstPath);
|
|
578
|
+
}
|
|
579
|
+
appendLedger({ event: "task_completed", file: fileName, session }, root);
|
|
580
|
+
results.push({ file: fileName, ok: true, session });
|
|
573
581
|
}
|
|
574
|
-
appendLedger({ event: "task_completed", file: fileName, session }, root);
|
|
575
|
-
results.push({ file: fileName, ok: true, session });
|
|
576
582
|
} catch (err) {
|
|
577
583
|
results.push({ file: fileName, ok: false, error: err.message });
|
|
578
584
|
}
|
package/src/git.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
2
4
|
import { normalizePath } from "./config.mjs";
|
|
3
|
-
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
export const NET_GUARD_PRELOAD_URL = new URL("./preload-net-guard.mjs", import.meta.url).href;
|
|
6
8
|
export const NET_GUARD_FLAG = `--import ${NET_GUARD_PRELOAD_URL}`;
|
|
@@ -23,23 +25,35 @@ export function runCmd(command, opts = {}) {
|
|
|
23
25
|
|
|
24
26
|
let binary = "";
|
|
25
27
|
let args = [];
|
|
28
|
+
let useShell = false;
|
|
29
|
+
let shellCmd = "";
|
|
26
30
|
|
|
27
31
|
if (Array.isArray(command)) {
|
|
28
32
|
binary = command[0];
|
|
29
33
|
args = command.slice(1);
|
|
30
34
|
} else if (typeof command === "string") {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
const trimmed = command.trim();
|
|
36
|
+
if (/[\&\|\>\<\$\"\'\n\;]/.test(trimmed)) {
|
|
37
|
+
useShell = true;
|
|
38
|
+
shellCmd = trimmed;
|
|
39
|
+
} else {
|
|
40
|
+
const tokens = trimmed.split(/\s+/).filter(Boolean);
|
|
41
|
+
binary = tokens[0] || "";
|
|
42
|
+
args = tokens.slice(1);
|
|
43
|
+
}
|
|
34
44
|
}
|
|
35
45
|
|
|
36
|
-
if (!binary) {
|
|
46
|
+
if (!binary && !useShell) {
|
|
37
47
|
if (opts.ignoreError) return { status: 0, stdout: "", stderr: "" };
|
|
38
48
|
throw new GateError("Empty command provided");
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
try {
|
|
42
|
-
const
|
|
52
|
+
const isWin = process.platform === "win32";
|
|
53
|
+
const execBin = useShell ? (isWin ? (process.env.ComSpec || "cmd.exe") : "/bin/sh") : binary;
|
|
54
|
+
const execArgs = useShell ? (isWin ? ["/d", "/s", "/c", shellCmd] : ["-c", shellCmd]) : args;
|
|
55
|
+
|
|
56
|
+
const stdout = execFileSync(execBin, execArgs, {
|
|
43
57
|
cwd,
|
|
44
58
|
encoding: "utf-8",
|
|
45
59
|
shell: false,
|
|
@@ -69,10 +83,10 @@ export function runCmd(command, opts = {}) {
|
|
|
69
83
|
}
|
|
70
84
|
|
|
71
85
|
if (isTimeout) {
|
|
72
|
-
throw new GateError(`Command execution timed out (ETIMEDOUT): ${binary}`, { code: status });
|
|
86
|
+
throw new GateError(`Command execution timed out (ETIMEDOUT): ${useShell ? shellCmd : binary}`, { code: status });
|
|
73
87
|
}
|
|
74
88
|
if (isNobufs) {
|
|
75
|
-
throw new GateError(`Command output buffer exceeded limit (ENOBUFS): ${binary}`, { code: status });
|
|
89
|
+
throw new GateError(`Command output buffer exceeded limit (ENOBUFS): ${useShell ? shellCmd : binary}`, { code: status });
|
|
76
90
|
}
|
|
77
91
|
|
|
78
92
|
throw err;
|
|
@@ -138,29 +152,55 @@ export function resolveBase(root = process.cwd(), baseRef = "main") {
|
|
|
138
152
|
);
|
|
139
153
|
}
|
|
140
154
|
|
|
141
|
-
export function changedFiles(root = process.cwd(), base = "main") {
|
|
155
|
+
export function changedFiles(root = process.cwd(), base = "main", mode = "committed") {
|
|
142
156
|
const resolvedRef = resolveBase(root, base);
|
|
143
|
-
|
|
144
|
-
cwd: root,
|
|
145
|
-
raw: true,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
157
|
+
if (mode === "working-tree" || mode === "working") {
|
|
158
|
+
const committedRaw = git(["-c", "core.quotePath=false", "diff", "-z", "--name-only", `${resolvedRef}...HEAD`], { cwd: root, raw: true, ignoreError: true }) || "";
|
|
159
|
+
const uncommittedRaw = git(["-c", "core.quotePath=false", "diff", "-z", "--name-only", "HEAD"], { cwd: root, raw: true, ignoreError: true }) || "";
|
|
160
|
+
const untrackedRaw = git(["-c", "core.quotePath=false", "ls-files", "-z", "--others", "--exclude-standard"], { cwd: root, raw: true, ignoreError: true }) || "";
|
|
161
|
+
|
|
162
|
+
const set = new Set([
|
|
163
|
+
...committedRaw.split("\0").map(normalizePath).filter(Boolean),
|
|
164
|
+
...uncommittedRaw.split("\0").map(normalizePath).filter(Boolean),
|
|
165
|
+
...untrackedRaw.split("\0").map(normalizePath).filter(Boolean),
|
|
166
|
+
]);
|
|
167
|
+
return Array.from(set);
|
|
168
|
+
} else if (mode === "staged" || mode === "index") {
|
|
169
|
+
const raw = git(["-c", "core.quotePath=false", "diff", "-z", "--name-only", "--cached", resolvedRef], { cwd: root, raw: true });
|
|
170
|
+
return raw.split("\0").map(normalizePath).filter(Boolean);
|
|
171
|
+
} else {
|
|
172
|
+
const raw = git(["-c", "core.quotePath=false", "diff", "-z", "--name-only", `${resolvedRef}...HEAD`], { cwd: root, raw: true });
|
|
173
|
+
return raw.split("\0").map(normalizePath).filter(Boolean);
|
|
174
|
+
}
|
|
151
175
|
}
|
|
152
176
|
|
|
153
|
-
export function
|
|
177
|
+
export function diffText(root = process.cwd(), base = "main", mode = "committed") {
|
|
154
178
|
const resolvedRef = resolveBase(root, base);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
179
|
+
if (mode === "working-tree" || mode === "working") {
|
|
180
|
+
const committed = git(["diff", `${resolvedRef}...HEAD`], { cwd: root, raw: true, ignoreError: true }) || "";
|
|
181
|
+
const uncommitted = git(["diff", "HEAD"], { cwd: root, raw: true, ignoreError: true }) || "";
|
|
182
|
+
|
|
183
|
+
const untrackedRaw = git(["-c", "core.quotePath=false", "ls-files", "-z", "--others", "--exclude-standard"], { cwd: root, raw: true, ignoreError: true }) || "";
|
|
184
|
+
const untrackedFiles = untrackedRaw.split("\0").map(normalizePath).filter(Boolean);
|
|
185
|
+
let untrackedDiff = "";
|
|
186
|
+
for (const file of untrackedFiles) {
|
|
187
|
+
try {
|
|
188
|
+
const fullPath = join(root, file);
|
|
189
|
+
if (existsSync(fullPath)) {
|
|
190
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
191
|
+
untrackedDiff += `\ndiff --git a/${file} b/${file}\nnew file mode 100644\n--- /dev/null\n+++ b/${file}\n`;
|
|
192
|
+
untrackedDiff += content.split("\n").map((line) => `+${line}`).join("\n") + "\n";
|
|
193
|
+
}
|
|
194
|
+
} catch (_) {}
|
|
195
|
+
}
|
|
196
|
+
return [committed, uncommitted, untrackedDiff].filter(Boolean).join("\n");
|
|
197
|
+
}
|
|
198
|
+
return git(["diff", `${resolvedRef}...HEAD`], { cwd: root, raw: true });
|
|
159
199
|
}
|
|
160
200
|
|
|
161
|
-
export function
|
|
162
|
-
const
|
|
163
|
-
return
|
|
201
|
+
export function diffBytes(root = process.cwd(), base = "main", mode = "committed") {
|
|
202
|
+
const text = diffText(root, base, mode);
|
|
203
|
+
return Buffer.byteLength(text, "utf-8");
|
|
164
204
|
}
|
|
165
205
|
|
|
166
206
|
export function showFromOrigin(root = process.cwd(), base = "main", filePath = "") {
|
|
@@ -173,29 +213,6 @@ export function showFromOrigin(root = process.cwd(), base = "main", filePath = "
|
|
|
173
213
|
}
|
|
174
214
|
}
|
|
175
215
|
|
|
176
|
-
export function createBranch(root = process.cwd(), branch = "") {
|
|
177
|
-
const opId = journalIntent(root, { type: "create_branch", branch, targetPath: "" });
|
|
178
|
-
try {
|
|
179
|
-
const res = git(["branch", branch], { cwd: root });
|
|
180
|
-
journalDone(root, opId);
|
|
181
|
-
return res;
|
|
182
|
-
} catch (err) {
|
|
183
|
-
journalDone(root, opId);
|
|
184
|
-
throw err;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export function worktreeAdd(root = process.cwd(), branch = "agent/task", targetDir = "") {
|
|
189
|
-
const opId = journalIntent(root, { type: "worktree_add", targetPath: targetDir, branch });
|
|
190
|
-
try {
|
|
191
|
-
const res = git(["worktree", "add", targetDir, "-b", branch], { cwd: root });
|
|
192
|
-
journalDone(root, opId);
|
|
193
|
-
return res;
|
|
194
|
-
} catch (err) {
|
|
195
|
-
journalDone(root, opId);
|
|
196
|
-
throw err;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
216
|
|
|
200
217
|
export function worktreeRemove(root = process.cwd(), targetDir = "") {
|
|
201
218
|
return git(["worktree", "remove", targetDir, "--force"], { cwd: root });
|
package/src/mcp.mjs
CHANGED
package/src/prompt-guard.mjs
CHANGED
|
@@ -96,7 +96,10 @@ export function buildAgentEnvelope(systemPolicy = "", taskInstructions = "", unt
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
const sections = [];
|
|
99
|
-
|
|
99
|
+
|
|
100
|
+
if (sanitizedBlocks.length > 0) {
|
|
101
|
+
sections.push(`SYSTEM WARNING: ${warning}`);
|
|
102
|
+
}
|
|
100
103
|
|
|
101
104
|
if (systemPolicy && typeof systemPolicy === "string" && systemPolicy.trim()) {
|
|
102
105
|
sections.push(`[SYSTEM POLICY]\n${systemPolicy.trim()}`);
|