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/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.27.0 — Universal Agent Orchestrator & Safety Gatekeeper
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 Dispatch a single task to an AI agent
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.27.0");
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, { root, config, dryRun: values["dry-run"] });
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: err.code || 1 }, null, 2));
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(err.code || 1);
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(` Found ${files.length} queued task(s) in .agent/queue/`);
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.25.1)`);
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
- process.exit(err.code || 1);
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": "node scripts/jules-dispatch.mjs",
251
- "jules:queue": "node bin/agentctl.mjs queue",
252
- "jules:create": "node scripts/jules-create.mjs",
253
- "jules:status": "node scripts/jules-status.mjs",
254
- "jules:audit": "node scripts/jules-self-audit.mjs",
255
- "jules:scan": "node scripts/jules-scan-todos.mjs",
256
- "jules:swarm": "node bin/agentctl.mjs swarm",
257
- "jules:nightly": "node scripts/jules-nightly.mjs"
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.25.1)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jules-orchestrator-kit",
3
- "version": "0.27.0",
3
+ "version": "0.28.1",
4
4
  "description": "Orchestration kit for running Google Jules autonomous agents.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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.emitWarning(
14
- "scripts/jules-dispatch.mjs is deprecated and will be removed in v1.0.0. Use 'agentctl dispatch' or the programmatic SDK.",
15
- "DeprecationWarning"
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
- ...(parsed.limits || {}),
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.27.0)</h1>
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.27.0", root, ts: new Date().toISOString() }));
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, "0.0.0.0", () => {
133
- console.log(`🚀 jules-orchestrator-kit Dashboard running at http://localhost:${port}`);
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 untrustedData = task.untrustedData || [cleanPrompt];
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.map((t) => (typeof t === "string" ? t : t.id || t.file || t.title));
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: fileName, prompt };
569
+ const task = typeof fileOrItem === "object" ? { ...fileOrItem, title, prompt } : { title, prompt };
569
570
  const session = await dispatch(task, { root, config, dryRun: isDry });
570
- const dstPath = join(completedDir, fileName);
571
- if (existsSync(srcPath)) {
572
- renameSync(srcPath, dstPath);
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
- import { journalIntent, journalDone } from "./journal.mjs";
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 tokens = command.trim().split(/\s+/).filter(Boolean);
32
- binary = tokens[0] || "";
33
- args = tokens.slice(1);
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 stdout = execFileSync(binary, args, {
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
- const raw = git(["-c", "core.quotePath=false", "diff", "-z", "--name-only", `${resolvedRef}...HEAD`], {
144
- cwd: root,
145
- raw: true,
146
- });
147
- return raw
148
- .split("\0")
149
- .map(normalizePath)
150
- .filter(Boolean);
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 diffBytes(root = process.cwd(), base = "main") {
177
+ export function diffText(root = process.cwd(), base = "main", mode = "committed") {
154
178
  const resolvedRef = resolveBase(root, base);
155
- const raw = git(["diff", "--shortstat", `${resolvedRef}...HEAD`], { cwd: root });
156
- if (!raw) return 0;
157
- const diffStr = git(["diff", `${resolvedRef}...HEAD`], { cwd: root, raw: true });
158
- return Buffer.byteLength(diffStr, "utf-8");
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 diffText(root = process.cwd(), base = "main") {
162
- const resolvedRef = resolveBase(root, base);
163
- return git(["diff", `${resolvedRef}...HEAD`], { cwd: root, raw: true });
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
@@ -9,7 +9,7 @@ import { ProgressBus } from "./mcp-progress.mjs";
9
9
 
10
10
  export const MCP_SERVER_INFO = {
11
11
  name: "jules-orchestrator-kit",
12
- version: "0.25.1",
12
+ version: "0.28.1",
13
13
  };
14
14
 
15
15
  export const MAX_MCP_FRAME_SIZE = 4 * 1024 * 1024; // 4 MB memory safety ceiling
@@ -96,7 +96,10 @@ export function buildAgentEnvelope(systemPolicy = "", taskInstructions = "", unt
96
96
  }
97
97
 
98
98
  const sections = [];
99
- sections.push(`SYSTEM WARNING: ${warning}`);
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()}`);