jules-orchestrator-kit 0.22.4 → 0.22.5

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
@@ -4,7 +4,7 @@ import { parseArgs } from "node:util";
4
4
  import { readFileSync, writeFileSync, existsSync, readdirSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  import { loadConfig, resolveRoot, detectStack } from "../src/config.mjs";
7
- import { gate, dispatch, run } from "../src/engine.mjs";
7
+ import { gate, dispatch, run, isTaskFile } from "../src/engine.mjs";
8
8
  import { acquireLock, releaseLock, lockStatus, checkDailyBudget, getQueueDir, ensureDir } from "../src/state.mjs";
9
9
  import { worktreePrune } from "../src/git.mjs";
10
10
 
@@ -13,7 +13,7 @@ const command = args[0];
13
13
 
14
14
  function printHelp() {
15
15
  console.log(`
16
- šŸš€ agentctl v0.22.4 — Universal Agent Orchestrator & Safety Gatekeeper
16
+ šŸš€ agentctl v0.22.5 — Universal Agent Orchestrator & Safety Gatekeeper
17
17
 
18
18
  Usage: agentctl <command> [options]
19
19
 
@@ -43,7 +43,7 @@ async function main() {
43
43
  }
44
44
 
45
45
  if (command === "version" || command === "--version" || command === "-v") {
46
- console.log("agentctl v0.22.4");
46
+ console.log("agentctl v0.22.5");
47
47
  process.exit(0);
48
48
  }
49
49
 
@@ -150,7 +150,7 @@ async function main() {
150
150
 
151
151
  case "queue": {
152
152
  const queueDir = getQueueDir(root);
153
- const files = readdirSync(queueDir).filter((f) => f.endsWith(".md"));
153
+ const files = readdirSync(queueDir).filter((f) => isTaskFile(f, queueDir));
154
154
  console.log(` Found ${files.length} queued task(s) in .agent/queue/`);
155
155
  if (files.length > 0) {
156
156
  const tasks = files.map((f) => ({
@@ -168,7 +168,7 @@ async function main() {
168
168
  case "swarm": {
169
169
  console.log("šŸš€ Running Swarm Orchestrator...");
170
170
  const queueDir = getQueueDir(root);
171
- const files = readdirSync(queueDir).filter((f) => f.endsWith(".md"));
171
+ const files = readdirSync(queueDir).filter((f) => isTaskFile(f, queueDir));
172
172
  if (files.length === 0) {
173
173
  console.log("No pending tasks found for swarm.");
174
174
  process.exit(0);
@@ -223,7 +223,7 @@ async function main() {
223
223
  }
224
224
 
225
225
  case "doctor": {
226
- console.log(`\nšŸ” agentctl System Diagnostics (v0.22.4)`);
226
+ console.log(`\nšŸ” agentctl System Diagnostics (v0.22.5)`);
227
227
  console.log(`--------------------------------------------------`);
228
228
  console.log(` Project Root : ${root}`);
229
229
  console.log(` Config File : ${config._file || "None (Using defaults)"}`);
package/index.mjs CHANGED
@@ -16,7 +16,7 @@ export {
16
16
  } from "./src/security.mjs";
17
17
  export { sanitizeUntrustedData, buildAgentEnvelope } from "./src/prompt-guard.mjs";
18
18
  export { isolateMcpStdout, writeMcpFrame } from "./src/mcp.mjs";
19
- export { git, runCmd, changedFiles, diffBytes, diffText } from "./src/git.mjs";
19
+ export { git, runCmd, resolveBase, changedFiles, diffBytes, diffText } from "./src/git.mjs";
20
20
  export { createProvider, JULES_PRESET, CLAUDE_PRESET, CODEX_PRESET } from "./src/provider.mjs";
21
21
  export {
22
22
  appendLedger,
@@ -36,8 +36,9 @@ export {
36
36
  BudgetError,
37
37
  isPidAlive,
38
38
  getProcessStartTime,
39
+ parseProcStat,
39
40
  } from "./src/state.mjs";
40
- export { gate, dispatch, repair, run, fingerprintFailureState } from "./src/engine.mjs";
41
+ export { gate, dispatch, repair, run, fingerprintFailureState, isTaskFile } from "./src/engine.mjs";
41
42
  export { validateEnvelope } from "./src/envelope.mjs";
42
43
  export {
43
44
  createExecutionEnvelope,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jules-orchestrator-kit",
3
- "version": "0.22.4",
3
+ "version": "0.22.5",
4
4
  "description": "Orchestration kit for running Google Jules autonomous agents.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { getQueueDir, ensureDir } from "../src/state.mjs";
8
+ import { isTaskFile } from "../src/engine.mjs";
8
9
  import { readdirSync, renameSync } from "node:fs";
9
10
  import { join } from "node:path";
10
11
 
@@ -19,7 +20,7 @@ export function classifyQueueFailure(err) {
19
20
  if (process.argv[1] && process.argv[1].endsWith("jules-queue-runner.mjs")) {
20
21
  const root = process.env.JULES_PROJECT_ROOT || process.cwd();
21
22
  const queueDir = getQueueDir(root);
22
- const files = readdirSync(queueDir).filter((f) => f.endsWith(".md"));
23
+ const files = readdirSync(queueDir).filter((f) => isTaskFile(f, queueDir));
23
24
 
24
25
  if (files.length === 0) {
25
26
  console.log("Queue processing complete!");
package/src/engine.mjs CHANGED
@@ -3,10 +3,60 @@ import { checkScope, scanDiff, redactSecrets } from "./security.mjs";
3
3
  import { changedFiles, diffBytes, diffText, showFromOrigin, runCmd } from "./git.mjs";
4
4
  import { createProvider } from "./provider.mjs";
5
5
  import { withBudget, appendLedger, getQueueDir, ensureDir } from "./state.mjs";
6
- import { readdirSync, readFileSync, renameSync } from "node:fs";
6
+ import { readdirSync, readFileSync, renameSync, existsSync } from "node:fs";
7
7
  import { join } from "node:path";
8
8
  import { createHash } from "node:crypto";
9
9
 
10
+ /**
11
+ * Validates whether a file in .agent/jules-queue/ is a task file.
12
+ * Filters out README.md and matches TASK-*.md or valid envelope front-matter.
13
+ */
14
+ export function isTaskFile(fileName, queueDirOrContent = null) {
15
+ if (!fileName || typeof fileName !== "string") return false;
16
+ const lower = fileName.toLowerCase();
17
+
18
+ // Explicitly filter out README.md
19
+ if (lower === "readme.md" || lower.endsWith("/readme.md") || lower.endsWith("\\readme.md")) {
20
+ return false;
21
+ }
22
+
23
+ if (!lower.endsWith(".md")) {
24
+ return false;
25
+ }
26
+
27
+ // Matching TASK-*.md or task-*.md
28
+ if (/^task-.*\.md$/i.test(fileName)) {
29
+ return true;
30
+ }
31
+
32
+ // Check valid envelope front-matter
33
+ if (queueDirOrContent) {
34
+ try {
35
+ let content = queueDirOrContent;
36
+ if (typeof queueDirOrContent === "string" && !queueDirOrContent.includes("\n") && existsSync(queueDirOrContent)) {
37
+ const fullPath = queueDirOrContent.endsWith(fileName) ? queueDirOrContent : join(queueDirOrContent, fileName);
38
+ if (existsSync(fullPath)) {
39
+ content = readFileSync(fullPath, "utf-8");
40
+ }
41
+ }
42
+ if (typeof content === "string") {
43
+ const trimmed = content.trimStart();
44
+ if (trimmed.startsWith("---")) {
45
+ const endFm = trimmed.indexOf("\n---", 3);
46
+ if (endFm !== -1) {
47
+ const frontMatter = trimmed.slice(3, endFm);
48
+ if (/(?:taskId|envelopeId|baseRef|baseSha|task|title|id|version|capabilities|scope|verify):/i.test(frontMatter)) {
49
+ return true;
50
+ }
51
+ }
52
+ }
53
+ }
54
+ } catch (_) {}
55
+ }
56
+
57
+ return false;
58
+ }
59
+
10
60
  /**
11
61
  * Computes a SHA-256 fingerprint for a failure state (normalized stderr + diff text).
12
62
  */
@@ -322,7 +372,7 @@ export async function run(opts = {}) {
322
372
  const completedDir = join(queueDir, "completed");
323
373
  ensureDir(completedDir);
324
374
 
325
- const files = readdirSync(queueDir).filter((f) => f.endsWith(".md"));
375
+ const files = readdirSync(queueDir).filter((f) => isTaskFile(f, queueDir));
326
376
  const results = [];
327
377
 
328
378
  for (let i = 0; i < files.length; i += concurrency) {
package/src/git.mjs CHANGED
@@ -14,8 +14,14 @@ function normalizePath(p) {
14
14
  return p.split(sep).join("/").replace(/\\/g, "/");
15
15
  }
16
16
 
17
+ const DEFAULT_TIMEOUT = 10 * 60 * 1000; // 10 minutes
18
+ const DEFAULT_MAX_BUFFER = 10 * 1024 * 1024; // 10 MB
19
+
17
20
  export function runCmd(command, opts = {}) {
18
21
  const cwd = opts.cwd || process.cwd();
22
+ const timeout = opts.timeout || DEFAULT_TIMEOUT;
23
+ const maxBuffer = opts.maxBuffer || DEFAULT_MAX_BUFFER;
24
+
19
25
  let binary = "";
20
26
  let args = [];
21
27
 
@@ -40,16 +46,36 @@ export function runCmd(command, opts = {}) {
40
46
  shell: false,
41
47
  stdio: ["ignore", "pipe", "pipe"],
42
48
  env: opts.env || process.env,
49
+ timeout,
50
+ maxBuffer,
43
51
  });
44
52
  return { status: 0, stdout: stdout.trim(), stderr: "" };
45
53
  } catch (err) {
54
+ const isTimeout = err.code === "ETIMEDOUT" || (err.signal === "SIGTERM" && err.killed);
55
+ const isNobufs = err.code === "ENOBUFS" || (err.message && err.message.includes("maxBuffer"));
56
+
57
+ const status = err.status || (isTimeout ? 124 : 1);
58
+ let stdout = (err.stdout || "").toString().trim();
59
+ let stderr = (err.stderr || err.message || "").toString().trim();
60
+
61
+ if (isTimeout && !stderr.includes("ETIMEDOUT")) {
62
+ stderr = `Command execution timed out after ${timeout}ms (ETIMEDOUT)${stderr ? "\n" + stderr : ""}`;
63
+ }
64
+ if (isNobufs && !stderr.includes("ENOBUFS")) {
65
+ stderr = `Command output buffer exceeded limit of ${maxBuffer} bytes (ENOBUFS)${stderr ? "\n" + stderr : ""}`;
66
+ }
67
+
46
68
  if (opts.ignoreError) {
47
- return {
48
- status: err.status || 1,
49
- stdout: (err.stdout || "").toString().trim(),
50
- stderr: (err.stderr || "").toString().trim(),
51
- };
69
+ return { status, stdout, stderr };
70
+ }
71
+
72
+ if (isTimeout) {
73
+ throw new GateError(`Command execution timed out (ETIMEDOUT): ${binary}`, { code: status });
74
+ }
75
+ if (isNobufs) {
76
+ throw new GateError(`Command output buffer exceeded limit (ENOBUFS): ${binary}`, { code: status });
52
77
  }
78
+
53
79
  throw err;
54
80
  }
55
81
  }
@@ -59,20 +85,35 @@ export function runCmd(command, opts = {}) {
59
85
  */
60
86
  export function git(args = [], opts = {}) {
61
87
  const cwd = opts.cwd || process.cwd();
88
+ const timeout = opts.timeout || DEFAULT_TIMEOUT;
89
+ const maxBuffer = opts.maxBuffer || DEFAULT_MAX_BUFFER;
62
90
  const argArray = Array.isArray(args) ? args : String(args).split(" ").filter(Boolean);
91
+
63
92
  try {
64
93
  const stdout = execFileSync("git", argArray, {
65
94
  cwd,
66
95
  encoding: "utf-8",
67
96
  shell: false,
68
97
  stdio: ["ignore", "pipe", "pipe"],
98
+ timeout,
99
+ maxBuffer,
69
100
  });
70
101
  return opts.raw ? stdout : stdout.trim();
71
102
  } catch (err) {
72
103
  if (opts.ignoreError) {
73
104
  return "";
74
105
  }
106
+ const isTimeout = err.code === "ETIMEDOUT" || (err.signal === "SIGTERM" && err.killed);
107
+ const isNobufs = err.code === "ENOBUFS" || (err.message && err.message.includes("maxBuffer"));
75
108
  const stderr = (err.stderr || err.message || "").toString().trim();
109
+
110
+ if (isTimeout) {
111
+ throw new GateError(`Git command timed out [git ${argArray.join(" ")}]: ETIMEDOUT`, { code: 124 });
112
+ }
113
+ if (isNobufs) {
114
+ throw new GateError(`Git command output buffer exceeded limit [git ${argArray.join(" ")}]: ENOBUFS`, { code: 1 });
115
+ }
116
+
76
117
  throw new GateError(`Git command failed [git ${argArray.join(" ")}]: ${stderr}`);
77
118
  }
78
119
  }
@@ -88,7 +129,7 @@ export function resolveBase(root = process.cwd(), baseRef = "main") {
88
129
  stdio: ["ignore", "pipe", "ignore"],
89
130
  });
90
131
  if (res && res.trim()) {
91
- return ref;
132
+ return res.trim();
92
133
  }
93
134
  } catch (_) {}
94
135
  }
package/src/state.mjs CHANGED
@@ -292,18 +292,24 @@ export function getLockDir(rootOrOpts = resolveRoot()) {
292
292
  return dir;
293
293
  }
294
294
 
295
+ export function parseProcStat(stat) {
296
+ if (typeof stat !== "string") return null;
297
+ const rpar = stat.lastIndexOf(")");
298
+ if (rpar === -1) return null;
299
+ const rest = stat.slice(rpar + 2).trim().split(/\s+/);
300
+ return rest[19] || null;
301
+ }
302
+
295
303
  export function getProcessStartTime(pid) {
296
- if (!pid || typeof pid !== "number") return null;
304
+ if (!pid) return null;
305
+ if (typeof pid === "string" && pid.includes(")")) {
306
+ return parseProcStat(pid);
307
+ }
308
+ if (typeof pid !== "number") return null;
297
309
  if (process.platform === "linux") {
298
310
  try {
299
311
  const stat = readFileSync(`/proc/${pid}/stat`, "utf-8");
300
- const rpar = stat.lastIndexOf(")");
301
- if (rpar !== -1) {
302
- const rest = stat.slice(rpar + 1).trim().split(/\s+/);
303
- return rest[19] || null;
304
- }
305
- const parts = stat.trim().split(/\s+/);
306
- return parts[21] || null;
312
+ return parseProcStat(stat);
307
313
  } catch (_) {}
308
314
  }
309
315
  return null;