jules-orchestrator-kit 0.27.1 → 0.28.2
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 +54 -0
- 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 +21 -14
- package/src/git.mjs +64 -24
- package/src/mcp.mjs +1 -1
- package/src/prompt-guard.mjs +4 -1
- package/src/provider.mjs +56 -11
package/README.md
CHANGED
|
@@ -36,6 +36,12 @@
|
|
|
36
36
|
|
|
37
37
|
<br/>
|
|
38
38
|
|
|
39
|
+
<p align="center">
|
|
40
|
+
<img src="docs/assets/hero-flow.svg" alt="Autonomous Orchestration Pipeline" width="100%" />
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<br/>
|
|
44
|
+
|
|
39
45
|
---
|
|
40
46
|
|
|
41
47
|
<br/>
|
|
@@ -75,6 +81,12 @@ Autonomous coding agents can write software at 100× human speed—but unconstra
|
|
|
75
81
|
|
|
76
82
|
<br/>
|
|
77
83
|
|
|
84
|
+
<p align="center">
|
|
85
|
+
<img src="docs/assets/security-shield.svg" alt="Zero-Trust Security & Reliability Guarantees" width="100%" />
|
|
86
|
+
</p>
|
|
87
|
+
|
|
88
|
+
<br/>
|
|
89
|
+
|
|
78
90
|
---
|
|
79
91
|
|
|
80
92
|
<br/>
|
|
@@ -125,6 +137,12 @@ To ensure maximum merge success, dispatch tasks according to our deterministic t
|
|
|
125
137
|
|
|
126
138
|
<br/>
|
|
127
139
|
|
|
140
|
+
<p align="center">
|
|
141
|
+
<img src="docs/assets/tier-presets.svg" alt="Subscription Tier Allocation Matrix" width="100%" />
|
|
142
|
+
</p>
|
|
143
|
+
|
|
144
|
+
<br/>
|
|
145
|
+
|
|
128
146
|
---
|
|
129
147
|
|
|
130
148
|
<br/>
|
|
@@ -229,9 +247,23 @@ Ecosystems Natively Supported by src/stack-detector.mjs:
|
|
|
229
247
|
|
|
230
248
|
<br/>
|
|
231
249
|
|
|
250
|
+
<p align="center">
|
|
251
|
+
<img src="docs/assets/architecture-layers.svg" alt="Control Plane Architecture Layers" width="100%" />
|
|
252
|
+
</p>
|
|
253
|
+
|
|
254
|
+
<br/>
|
|
255
|
+
|
|
232
256
|
### 1. The Autonomous OODA Verification Loop
|
|
233
257
|
Every task dispatched to `jules-orchestrator-kit` executes within an immutable, fail-closed verification loop:
|
|
234
258
|
|
|
259
|
+
<br/>
|
|
260
|
+
|
|
261
|
+
<p align="center">
|
|
262
|
+
<img src="docs/assets/ooda-loop-cycle.svg" alt="Self-Healing OODA Repair Loop & Thrash Breaker" width="100%" />
|
|
263
|
+
</p>
|
|
264
|
+
|
|
265
|
+
<br/>
|
|
266
|
+
|
|
235
267
|
```mermaid
|
|
236
268
|
flowchart TD
|
|
237
269
|
A["📩 Task Envelope"] --> B["1. Validate Scope & Base Freshness"]
|
|
@@ -265,6 +297,28 @@ flowchart TD
|
|
|
265
297
|
|
|
266
298
|
<br/>
|
|
267
299
|
|
|
300
|
+
### 3. Multi-Agent Parallel Swarm Topology
|
|
301
|
+
Run concurrent agents across parallel worktree slots with AST/JSON 3-way merging:
|
|
302
|
+
|
|
303
|
+
<br/>
|
|
304
|
+
|
|
305
|
+
<p align="center">
|
|
306
|
+
<img src="docs/assets/swarm-topology.svg" alt="Multi-Agent Parallel Swarm Topology" width="100%" />
|
|
307
|
+
</p>
|
|
308
|
+
|
|
309
|
+
<br/>
|
|
310
|
+
|
|
311
|
+
### 4. Model Context Protocol (MCP) Integration
|
|
312
|
+
Native stdio server exposing task dispatch, gate verification, and risk auditing to client tools (Antigravity, Claude, Cursor):
|
|
313
|
+
|
|
314
|
+
<br/>
|
|
315
|
+
|
|
316
|
+
<p align="center">
|
|
317
|
+
<img src="docs/assets/mcp-integration.svg" alt="Model Context Protocol (MCP) Integration" width="100%" />
|
|
318
|
+
</p>
|
|
319
|
+
|
|
320
|
+
<br/>
|
|
321
|
+
|
|
268
322
|
---
|
|
269
323
|
|
|
270
324
|
<br/>
|
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.2 — 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.2");
|
|
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.2)`);
|
|
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.2)`);
|
|
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.2)
|
|
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.2)</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.2", 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
|
@@ -101,20 +101,21 @@ export async function gate(opts = {}) {
|
|
|
101
101
|
const root = opts.root || process.cwd();
|
|
102
102
|
const config = opts.config || loadConfig(root);
|
|
103
103
|
const base = opts.base || config.baseBranch || "main";
|
|
104
|
+
const mode = opts.mode || (opts.workingTree ? "working-tree" : (process.env.JULES_GATE_MODE || "working-tree"));
|
|
104
105
|
const progressBus = opts.progressBus;
|
|
105
106
|
const progressToken = opts.progressToken;
|
|
106
107
|
const phases = [];
|
|
107
108
|
|
|
108
|
-
appendTelemetry(root, "gate_started", { base });
|
|
109
|
+
appendTelemetry(root, "gate_started", { base, mode });
|
|
109
110
|
|
|
110
111
|
let files = [];
|
|
111
112
|
let bytes = 0;
|
|
112
113
|
let diffStr = "";
|
|
113
114
|
|
|
114
115
|
try {
|
|
115
|
-
files = changedFiles(root, base);
|
|
116
|
-
bytes = diffBytes(root, base);
|
|
117
|
-
diffStr = diffText(root, base);
|
|
116
|
+
files = changedFiles(root, base, mode);
|
|
117
|
+
bytes = diffBytes(root, base, mode);
|
|
118
|
+
diffStr = diffText(root, base, mode);
|
|
118
119
|
} catch (err) {
|
|
119
120
|
phases.push({ phase: "git_resolution", ok: false, error: err.message });
|
|
120
121
|
appendTelemetry(root, "gate_finished", { ok: false, code: 1, error: err.message });
|
|
@@ -493,9 +494,9 @@ export async function dispatch(task, opts = {}) {
|
|
|
493
494
|
const cleanPrompt = redactSecrets(task.prompt || "");
|
|
494
495
|
|
|
495
496
|
// Wire Prompt Guard & Envelope to wrap raw task arguments
|
|
496
|
-
const
|
|
497
|
+
const taskInstructions = task.taskInstructions || cleanPrompt || task.title || "Autonomous Task Execution";
|
|
498
|
+
const untrustedData = Array.isArray(task.untrustedData) ? task.untrustedData : [];
|
|
497
499
|
const systemPolicy = task.systemPolicy || config.systemPolicy || "";
|
|
498
|
-
const taskInstructions = task.taskInstructions || task.title || "Autonomous Task Execution";
|
|
499
500
|
const envelopedPrompt = buildAgentEnvelope(systemPolicy, taskInstructions, untrustedData);
|
|
500
501
|
|
|
501
502
|
const cleanTask = { ...task, prompt: envelopedPrompt };
|
|
@@ -547,7 +548,7 @@ export async function run(tasksOrOpts = {}, opts = {}) {
|
|
|
547
548
|
|
|
548
549
|
let filesToProcess = [];
|
|
549
550
|
if (Array.isArray(tasks)) {
|
|
550
|
-
filesToProcess = tasks
|
|
551
|
+
filesToProcess = tasks;
|
|
551
552
|
} else {
|
|
552
553
|
filesToProcess = readdirSync(queueDir).filter((f) => isTaskFile(f, queueDir));
|
|
553
554
|
}
|
|
@@ -558,21 +559,27 @@ export async function run(tasksOrOpts = {}, opts = {}) {
|
|
|
558
559
|
const batch = filesToProcess.slice(i, i + concurrency);
|
|
559
560
|
await Promise.all(
|
|
560
561
|
batch.map(async (fileOrItem) => {
|
|
561
|
-
const fileName = typeof fileOrItem === "string" ? fileOrItem : fileOrItem.id || fileOrItem.file;
|
|
562
|
+
const fileName = typeof fileOrItem === "string" ? fileOrItem : fileOrItem.id || fileOrItem.file || fileOrItem.title || "task";
|
|
562
563
|
const srcPath = join(queueDir, fileName);
|
|
563
564
|
try {
|
|
564
565
|
let prompt = typeof fileOrItem === "object" && fileOrItem.prompt ? fileOrItem.prompt : "";
|
|
566
|
+
let title = typeof fileOrItem === "object" && fileOrItem.title ? fileOrItem.title : fileName;
|
|
565
567
|
if (!prompt && existsSync(srcPath)) {
|
|
566
568
|
prompt = await fs.promises.readFile(srcPath, "utf-8");
|
|
567
569
|
}
|
|
568
|
-
const task = { title:
|
|
570
|
+
const task = typeof fileOrItem === "object" ? { ...fileOrItem, title, prompt } : { title, prompt };
|
|
569
571
|
const session = await dispatch(task, { root, config, dryRun: isDry });
|
|
570
|
-
|
|
571
|
-
if (
|
|
572
|
-
|
|
572
|
+
|
|
573
|
+
if (session && session.ok === false) {
|
|
574
|
+
results.push({ file: fileName, ok: false, status: session.status, error: session.error, session });
|
|
575
|
+
} else {
|
|
576
|
+
const dstPath = join(completedDir, fileName);
|
|
577
|
+
if (existsSync(srcPath)) {
|
|
578
|
+
renameSync(srcPath, dstPath);
|
|
579
|
+
}
|
|
580
|
+
appendLedger({ event: "task_completed", file: fileName, session }, root);
|
|
581
|
+
results.push({ file: fileName, ok: true, session });
|
|
573
582
|
}
|
|
574
|
-
appendLedger({ event: "task_completed", file: fileName, session }, root);
|
|
575
|
-
results.push({ file: fileName, ok: true, session });
|
|
576
583
|
} catch (err) {
|
|
577
584
|
results.push({ file: fileName, ok: false, error: err.message });
|
|
578
585
|
}
|
package/src/git.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
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
|
|
|
@@ -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 = "") {
|
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()}`);
|
package/src/provider.mjs
CHANGED
|
@@ -3,15 +3,20 @@ import { spawnSync } from "node:child_process";
|
|
|
3
3
|
export const JULES_PRESET = {
|
|
4
4
|
name: "jules",
|
|
5
5
|
type: "http",
|
|
6
|
-
url: "https://jules.googleapis.com/
|
|
6
|
+
url: "https://jules.googleapis.com/v1alpha/sessions",
|
|
7
7
|
headers: {
|
|
8
|
-
"
|
|
8
|
+
"X-Goog-Api-Key": "{token}",
|
|
9
9
|
"Content-Type": "application/json",
|
|
10
10
|
},
|
|
11
11
|
bodyTemplate: {
|
|
12
|
-
prompt: "{prompt}",
|
|
13
12
|
title: "{title}",
|
|
14
|
-
|
|
13
|
+
prompt: "{prompt}",
|
|
14
|
+
sourceContext: {
|
|
15
|
+
source: "{source}",
|
|
16
|
+
githubRepoContext: {
|
|
17
|
+
startingBranch: "{branch}",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
15
20
|
},
|
|
16
21
|
};
|
|
17
22
|
|
|
@@ -31,6 +36,14 @@ export const CODEX_PRESET = {
|
|
|
31
36
|
promptViaStdin: false,
|
|
32
37
|
};
|
|
33
38
|
|
|
39
|
+
export class MissingApiKeyError extends Error {
|
|
40
|
+
constructor(message = "JULES_API_KEY environment variable is required for Google Jules API dispatch.") {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = "MissingApiKeyError";
|
|
43
|
+
this.status = 401;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
34
47
|
export class ProviderRateLimitError extends Error {
|
|
35
48
|
constructor(message, opts = {}) {
|
|
36
49
|
super(message);
|
|
@@ -92,6 +105,9 @@ function interpolateDeep(node, data) {
|
|
|
92
105
|
* Creates an HTTP or Exec provider adapter based on spec/config.
|
|
93
106
|
*/
|
|
94
107
|
export function createProvider(spec = "jules", config = {}) {
|
|
108
|
+
if (spec && typeof spec === "object" && typeof spec.dispatch === "function") {
|
|
109
|
+
return spec;
|
|
110
|
+
}
|
|
95
111
|
const providerSpec = typeof spec === "string"
|
|
96
112
|
? (spec === "claude-code" ? CLAUDE_PRESET : spec === "codex" ? CODEX_PRESET : JULES_PRESET)
|
|
97
113
|
: spec;
|
|
@@ -100,11 +116,28 @@ export function createProvider(spec = "jules", config = {}) {
|
|
|
100
116
|
name: providerSpec.name || "custom-provider",
|
|
101
117
|
|
|
102
118
|
async dispatch(task, ctx = {}) {
|
|
103
|
-
const rawToken = process.env.JULES_API_KEY || process.env.GEMINI_API_KEY || "";
|
|
119
|
+
const rawToken = process.env.JULES_API_KEY || (ctx.allowLegacyKey ? process.env.GEMINI_API_KEY : "") || "";
|
|
120
|
+
if (!rawToken && !ctx.dryRun && providerSpec.name === "jules") {
|
|
121
|
+
throw new MissingApiKeyError();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const isRepoless = Boolean(task.repoless || ctx.repoless);
|
|
125
|
+
const rawRepo = task.source || ctx.source || process.env.JULES_REPO || "";
|
|
126
|
+
if (!rawRepo && !ctx.dryRun && providerSpec.name === "jules" && !isRepoless) {
|
|
127
|
+
throw new Error("Missing connected Jules repository source. Set JULES_REPO or pass source/repoless option.");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const sourceName = rawRepo
|
|
131
|
+
? (rawRepo.startsWith("sources/") ? rawRepo : `sources/github/${rawRepo}`)
|
|
132
|
+
: undefined;
|
|
133
|
+
|
|
134
|
+
const startingBranch = task.branch || ctx.branch || config.baseBranch || process.env.BASE_BRANCH || "main";
|
|
135
|
+
|
|
104
136
|
const data = {
|
|
105
137
|
prompt: task.prompt || "",
|
|
106
138
|
title: task.title || "Agent Task",
|
|
107
|
-
branch:
|
|
139
|
+
branch: startingBranch,
|
|
140
|
+
source: sourceName || "",
|
|
108
141
|
project: process.env.JULES_PROJECT_ID || "default",
|
|
109
142
|
...ctx,
|
|
110
143
|
};
|
|
@@ -135,19 +168,31 @@ export function createProvider(spec = "jules", config = {}) {
|
|
|
135
168
|
}
|
|
136
169
|
|
|
137
170
|
let body;
|
|
171
|
+
let bodyObj;
|
|
138
172
|
if (typeof providerSpec.bodyTemplate === "string") {
|
|
139
173
|
try {
|
|
140
174
|
const parsedObj = JSON.parse(providerSpec.bodyTemplate);
|
|
141
|
-
|
|
142
|
-
body = JSON.stringify(interpolatedObj);
|
|
175
|
+
bodyObj = interpolateDeep(parsedObj, data);
|
|
143
176
|
} catch (_) {
|
|
144
177
|
body = interpolateString(providerSpec.bodyTemplate, data);
|
|
145
178
|
}
|
|
146
179
|
} else if (providerSpec.bodyTemplate) {
|
|
147
|
-
|
|
148
|
-
body = JSON.stringify(interpolatedObj);
|
|
180
|
+
bodyObj = interpolateDeep(providerSpec.bodyTemplate, data);
|
|
149
181
|
} else {
|
|
150
|
-
|
|
182
|
+
bodyObj = { ...data };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (bodyObj && typeof bodyObj === "object") {
|
|
186
|
+
if (isRepoless || !sourceName) {
|
|
187
|
+
delete bodyObj.sourceContext;
|
|
188
|
+
}
|
|
189
|
+
if (task.autoPr || ctx.autoPr) {
|
|
190
|
+
bodyObj.automationMode = "AUTO_CREATE_PR";
|
|
191
|
+
}
|
|
192
|
+
if (task.requirePlanApproval || ctx.requirePlanApproval) {
|
|
193
|
+
bodyObj.requirePlanApproval = true;
|
|
194
|
+
}
|
|
195
|
+
body = JSON.stringify(bodyObj);
|
|
151
196
|
}
|
|
152
197
|
|
|
153
198
|
const timeoutMs = ctx.timeoutMs || config.timeoutMs || providerSpec.timeoutMs || 120_000;
|