jules-orchestrator-kit 0.24.0 → 1.0.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 CHANGED
@@ -1,24 +1,33 @@
1
- # jules-orchestrator-kit
1
+ <div align="center">
2
2
 
3
- *Disclaimer: This is an independent open-source orchestration tool for Google Jules and is not officially affiliated with or endorsed by Google.*
3
+ # 🚀 jules-orchestrator-kit
4
+
5
+ ### High-Volume Autonomous AI Agent Orchestration Engine for Google Jules
4
6
 
5
7
  [![Jules PR Audit](https://github.com/FullThrottle83/jules-orchestrator-kit/actions/workflows/jules-audit.yml/badge.svg)](https://github.com/FullThrottle83/jules-orchestrator-kit/actions/workflows/jules-audit.yml)
6
8
  [![npm version](https://img.shields.io/npm/v/jules-orchestrator-kit.svg)](https://www.npmjs.com/package/jules-orchestrator-kit)
7
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
10
  [![Node.js Version](https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen.svg)](https://nodejs.org)
11
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-0%20native-blue.svg)](https://nodejs.org)
12
+
13
+ <p align="center">
14
+ <b>Zero-dependency safety kernel and high-throughput orchestration engine for autonomous AI agent swarms.</b><br/>
15
+ Built specifically to execute 300+ daily agent sessions and parallel worktree swarms safely on native Node.js 20+ ESM.
16
+ </p>
9
17
 
10
- > **High-Volume Autonomous Orchestration Engine for Google Jules**
11
- > Built specifically to execute 300+ daily agent sessions and parallel swarms safely. Zero external runtime dependencies. Built strictly on native Node.js 20+ ESM.
18
+ </div>
12
19
 
13
20
  ---
14
21
 
15
- ![Autonomous Orchestration Pipeline](docs/assets/hero-flow.svg?v=3)
22
+ <p align="center">
23
+ <img src="docs/assets/hero-flow.svg?v=3" alt="Autonomous Orchestration Pipeline" width="100%" />
24
+ </p>
16
25
 
17
26
  ---
18
27
 
19
28
  ## ⚡ 2-Minute Quickstart
20
29
 
21
- Get up and running in under 2 minutes with zero complex configuration:
30
+ > 💡 **TIP**: **New to Google Jules or agent automation?** You don't need any complex setup! `jules-orchestrator-kit` works out of the box with standard `npm test` and zero external dependencies.
22
31
 
23
32
  ```bash
24
33
  # 1. Initialize orchestrator structure in your target codebase
@@ -36,7 +45,7 @@ npx agentctl gate
36
45
  npx agentctl mcp
37
46
  ```
38
47
 
39
- > 📖 **Looking for production deployment patterns?**
48
+ > 📖 **NOTE**: **Looking for production deployment patterns?**
40
49
  > Check out [**EXAMPLES.md**](./EXAMPLES.md) for 6 real-world recipes (Nightly TODO Scanner, Composite CI Action, Multi-Worktree Swarms, OODA Auto-Fix, MCP IDE setup, and Specialist Rosters).
41
50
 
42
51
  ---
@@ -55,17 +64,19 @@ Whether you are dispatching your first automated coding task or managing high-th
55
64
 
56
65
  ---
57
66
 
67
+ ## 🏛️ System Architecture & Visual Diagrams
68
+
58
69
  <details open>
59
- <summary><b>⚙️ Control Plane Architecture & Deep Dive</b></summary>
70
+ <summary><b>📐 1. Control Plane Architecture Layers</b></summary>
60
71
 
61
72
  <br/>
62
73
 
63
- ![Control Plane Architecture Layers](docs/assets/architecture-layers.svg?v=3)
74
+ <p align="center">
75
+ <img src="docs/assets/architecture-layers.svg?v=3" alt="Control Plane Architecture Layers" width="100%" />
76
+ </p>
64
77
 
65
78
  <br/>
66
79
 
67
- ![Self-Healing OODA Loop](docs/assets/ooda-loop-cycle.svg?v=3)
68
-
69
80
  ### Engine System Highlights
70
81
 
71
82
  - **Native Task DAG Executor (`src/dag-engine.mjs`)**: Zero-dependency `DagExecutor` with Kahn's topological sort algorithm, SHA-256 interface fingerprinting post-task execution, and pre-execution cycle detection (`DagCycleError`).
@@ -80,12 +91,31 @@ Whether you are dispatching your first automated coding task or managing high-th
80
91
 
81
92
  </details>
82
93
 
83
- <details>
84
- <summary><b>🛡️ Zero-Trust Security Gatekeeper</b></summary>
94
+ <details open>
95
+ <summary><b>🔁 2. Self-Healing OODA Loop Cycle</b></summary>
85
96
 
86
97
  <br/>
87
98
 
88
- ![Zero-Trust Security Guarantees](docs/assets/security-shield.svg?v=3)
99
+ <p align="center">
100
+ <img src="docs/assets/ooda-loop-cycle.svg?v=3" alt="Self-Healing OODA Loop" width="100%" />
101
+ </p>
102
+
103
+ <br/>
104
+
105
+ > 🚨 **IMPORTANT**: The OODA (Observe-Orient-Decide-Act) loop executes up to 3 repair attempts when tests fail. If the failure output oscillates deterministically without progress, the OODA engine halts repair to save API tokens and returns Exit Code 4.
106
+
107
+ </details>
108
+
109
+ <details open>
110
+ <summary><b>🛡️ 3. Zero-Trust Security Shield & 4-Phase Gate</b></summary>
111
+
112
+ <br/>
113
+
114
+ <p align="center">
115
+ <img src="docs/assets/security-shield.svg?v=3" alt="Zero-Trust Security Guarantees" width="100%" />
116
+ </p>
117
+
118
+ <br/>
89
119
 
90
120
  ### The 4-Phase Safety Audit & Security Boundary (`agentctl gate`)
91
121
 
@@ -96,17 +126,35 @@ Whether you are dispatching your first automated coding task or managing high-th
96
126
  5. **Prompt Guard Boundary (`src/prompt-guard.mjs`)**: `sanitizeUntrustedData` strips bidi control characters, ANSI escape sequences, zero-width unicode, and neutralizes prompt injection tags (`<|im_start|>`, `[INST]`).
97
127
  6. **MCP Stream Isolation (`src/mcp.mjs`)**: Seals `process.stdout.write` framing stream to prevent log output from corrupting JSON-RPC stdio frames.
98
128
 
99
- > [!NOTE]
100
- > All security rules are fetched strictly from `origin/main` (never untrusted PR branches) to prevent prompt-injection attacks from altering security rules.
129
+ > ⚠️ **WARNING**: All security rules are fetched strictly from `origin/main` (never untrusted PR branches) to prevent prompt-injection attacks from altering security rules.
101
130
 
102
131
  </details>
103
132
 
104
- <details>
105
- <summary><b>🔌 Model Context Protocol (MCP) & IDE Integration</b></summary>
133
+ <details open>
134
+ <summary><b>🐝 4. Parallel Swarm Topology & Isolated Worktrees</b></summary>
135
+
136
+ <br/>
137
+
138
+ <p align="center">
139
+ <img src="docs/assets/swarm-topology.svg?v=3" alt="Multi-Agent Swarm Topology" width="100%" />
140
+ </p>
106
141
 
107
142
  <br/>
108
143
 
109
- ![Dual-Way MCP Integration](docs/assets/mcp-integration.svg?v=3)
144
+ > 📌 **NOTE**: Swarm execution spawns dedicated git worktrees for each task in parallel, isolated by VFS locks. Completed tasks are verified and merged back using 3-way AST/JSON structural merging (`agentctl swarm`).
145
+
146
+ </details>
147
+
148
+ <details open>
149
+ <summary><b>🔌 5. Model Context Protocol (MCP) & IDE Integration</b></summary>
150
+
151
+ <br/>
152
+
153
+ <p align="center">
154
+ <img src="docs/assets/mcp-integration.svg?v=3" alt="Dual-Way MCP Integration" width="100%" />
155
+ </p>
156
+
157
+ <br/>
110
158
 
111
159
  ### Connecting to Claude Desktop, Cursor, or Antigravity
112
160
 
@@ -121,15 +169,40 @@ npx agentctl mcp
121
169
  - `audit_jules_gate`: Execute the 4-phase safety gate against the workspace.
122
170
  - `check_risk_tier`: Classify workspace changes into Risk Tiers (R0 Cosmetic to R3 Restricted).
123
171
  - `get_jules_status`: Fetch real-time status of active, pending, and completed tasks.
172
+ - `telemetry_tail`: Query last N real-time telemetry events from the SHA-256 hash spine.
124
173
 
125
174
  </details>
126
175
 
127
- <details>
128
- <summary><b>🤖 Specialist Agent Prompt Presets (.agent/prompts/)</b></summary>
176
+ <details open>
177
+ <summary><b>💳 6. Subscription Tier Presets Matrix</b></summary>
178
+
179
+ <br/>
180
+
181
+ <p align="center">
182
+ <img src="docs/assets/tier-presets.svg?v=3" alt="Subscription Tier Presets Matrix" width="100%" />
183
+ </p>
129
184
 
130
185
  <br/>
131
186
 
132
- Specialized prompt presets enforcement payload limits (< 75 KB) and domain guardrails out of the box:
187
+ Tailor session limits and rate-limiting behavior to your Google Jules API subscription tier:
188
+
189
+ | Tier | `dailyTasks` | `repairAttempts` | `concurrency` | `staggerMs` | Target Usage |
190
+ | :--- | :---: | :---: | :---: | :---: | :--- |
191
+ | **`free`** | `15` | `1` | `1` | `3000 ms` | **Hobby / Free Tier:** Conserves quota, prevents HTTP 429 rate limits. |
192
+ | **`pro`** | `100` | `2` | `2` | `1500 ms` | **Developer Pro:** Balanced throughput for everyday work. |
193
+ | **`ultra`** *(default)* | `300` | `3` | `3` | `1000 ms` | **Swarm / Enterprise:** Maximum parallel throughput & CI/CD. |
194
+
195
+ **How to activate:**
196
+ - **Environment Variable:** `export JULES_TIER=free` (or set in `.env`)
197
+ - **Config File (`.agent/jules.yml`):** Set `tier: free`
198
+
199
+ </details>
200
+
201
+ ---
202
+
203
+ ## 🤖 Specialist Agent Prompt Presets (`.agent/prompts/`)
204
+
205
+ Specialized prompt presets enforce payload limits (< 75 KB) and domain guardrails out of the box:
133
206
 
134
207
  | Preset | Role & Domain | Primary Focus |
135
208
  | :--- | :--- | :--- |
@@ -144,12 +217,9 @@ npx agentctl dispatch \
144
217
  --prompt "$(cat .agent/prompts/Janitor.md) Prune unused helper methods in src/utils.mjs"
145
218
  ```
146
219
 
147
- </details>
148
-
149
- <details>
150
- <summary><b>⚡ GitHub Actions Composite Action (.github/actions/setup-jules)</b></summary>
220
+ ---
151
221
 
152
- <br/>
222
+ ## ⚡ GitHub Actions Composite Action (`.github/actions/setup-jules`)
153
223
 
154
224
  Integrate `jules-orchestrator-kit` into any GitHub Actions workflow with 3 lines of YAML:
155
225
 
@@ -165,12 +235,9 @@ steps:
165
235
  JULES_API_KEY: ${{ secrets.JULES_API_KEY }}
166
236
  ```
167
237
 
168
- </details>
169
-
170
- <details>
171
- <summary><b>🛠️ CLI Command Reference (`agentctl`)</b></summary>
238
+ ---
172
239
 
173
- <br/>
240
+ ## 🛠️ CLI Command Reference (`agentctl`)
174
241
 
175
242
  | Command | Usage Example | Description |
176
243
  | :--- | :--- | :--- |
@@ -185,35 +252,9 @@ steps:
185
252
  | **`scan`** | `agentctl scan` | Scans codebase for `TODO` and `FIXME` comments and generates task queue |
186
253
  | **`clean`** | `agentctl clean` | Audits and cleans up stale git worktrees, orphaned intents, locks, and temporary state files |
187
254
 
188
- </details>
189
-
190
- <details>
191
- <summary><b>💳 Subscription Tier Presets (Free / Pro / Ultra)</b></summary>
192
-
193
- <br/>
194
-
195
- ![Subscription Tier Presets Matrix](docs/assets/tier-presets.svg?v=3)
196
-
197
- <br/>
198
-
199
- Tailor session limits and rate-limiting behavior to your Google Jules API subscription tier:
200
-
201
- | Tier | `dailyTasks` | `repairAttempts` | `concurrency` | `staggerMs` | Target Usage |
202
- | :--- | :---: | :---: | :---: | :---: | :--- |
203
- | **`free`** | `15` | `1` | `1` | `3000 ms` | **Hobby / Free Tier:** Conserves quota, prevents HTTP 429 rate limits. |
204
- | **`pro`** | `100` | `2` | `2` | `1500 ms` | **Developer Pro:** Balanced throughput for everyday work. |
205
- | **`ultra`** *(default)* | `300` | `3` | `3` | `1000 ms` | **Swarm / Enterprise:** Maximum parallel throughput & CI/CD. |
206
-
207
- **How to activate:**
208
- - **Environment Variable:** `export JULES_TIER=free` (or set in `.env`)
209
- - **Config File (`.agent/jules.yml`):** Set `tier: free`
210
-
211
- </details>
212
-
213
- <details>
214
- <summary><b>📝 Configuration Reference (`.agent/jules.yml`)</b></summary>
255
+ ---
215
256
 
216
- <br/>
257
+ ## 📝 Configuration Reference (`.agent/jules.yml`)
217
258
 
218
259
  Auto-generated by `agentctl init` at the root of your project:
219
260
 
@@ -233,16 +274,13 @@ limits:
233
274
  diffKb: 75
234
275
  ```
235
276
 
236
- </details>
237
-
238
- <details>
239
- <summary><b>🚦 Exit Code Registry & Troubleshooting</b></summary>
277
+ ---
240
278
 
241
- <br/>
279
+ ## 🚦 Exit Code Registry & Troubleshooting
242
280
 
243
- Standardized exit codes enforced across all CLI utilities:
281
+ Standardized exit codes enforced across all CLI utilities and CI pipelines:
244
282
 
245
- | Exit Code | Status | Description & Immediate Action |
283
+ | Exit Code | Classification | Description & Immediate Remediation Action |
246
284
  | :---: | :--- | :--- |
247
285
  | `0` | **Success** | Task completed cleanly; PR opened or verification passed. |
248
286
  | `1` | **Pre-Dispatch / Arg Error** | Invalid arguments, prompt > 50 KB, or pre-dispatch validation error. |
@@ -250,15 +288,15 @@ Standardized exit codes enforced across all CLI utilities:
250
288
  | `3` | **Scope Violation** | Attempted modification of restricted files (`.github/`, command files, agent rules). |
251
289
  | `4` | **OODA Exhausted / Thrash** | Verification suite failed after 3 repair attempts or hit deterministic regression. |
252
290
  | `5` | **Diff Payload Limit** | Post-change git diff exceeds payload budget (`limits.diffKb`, default 75 KB). |
253
- | `6` | **Secret Detected** | High-confidence secret or private key detected in patch diff. |
291
+ | `6` | **Secret Detected** | High-confidence secret or private key detected in patch diff (Shannon entropy > 3.6 bits). |
254
292
  | `7` | **Budget Exhausted** | Daily task session quota limit reached (`limits.dailyTasks`, default 300). |
293
+ | `8` | **FLAKY_QUARANTINE** | Statistical test flakiness detected (oscillation >= 0.4, Wilson CI); OODA repair suppressed. |
294
+ | `124` | **Execution Timeout** | Subprocess execution exceeded hard timeout limit (default 10 minutes). |
295
+ | `188` | **ERR_UNMOCKED_NET** | Unmocked outbound HTTP/HTTPS egress intercepted by hermetic network guard. |
255
296
 
256
- </details>
257
-
258
- <details>
259
- <summary><b>🔐 Environment Variables Reference</b></summary>
297
+ ---
260
298
 
261
- <br/>
299
+ ## 🔐 Environment Variables Reference
262
300
 
263
301
  | Variable | Description | Default |
264
302
  | :--- | :--- | :--- |
@@ -273,12 +311,9 @@ Standardized exit codes enforced across all CLI utilities:
273
311
  | `BASE_BRANCH` | Base branch for PR Audits & Merge-Base checks | `main` |
274
312
  | `NO_COLOR` | Set to `true` to disable ANSI color output | `false` |
275
313
 
276
- </details>
277
-
278
- <details>
279
- <summary><b>🌐 Supported Tech Stacks & Auto-Detection</b></summary>
314
+ ---
280
315
 
281
- <br/>
316
+ ## 🌐 Supported Tech Stacks & Auto-Detection
282
317
 
283
318
  The orchestrator automatically infers verification and build commands across ecosystems:
284
319
 
@@ -295,8 +330,6 @@ The orchestrator automatically infers verification and build commands across eco
295
330
  | **Elixir / Ruby** | `mix.exs` / `Gemfile` | `mix test` / `rake test` | *(standard build)* |
296
331
  | **Java / C / C++** | `pom.xml` / `Makefile` | `mvn test` / `make test` | `mvn compile` / `make` |
297
332
 
298
- </details>
299
-
300
333
  ---
301
334
 
302
335
  ## 🤝 Contributing & Standards
package/bin/agentctl.mjs CHANGED
@@ -14,7 +14,7 @@ const command = args[0];
14
14
 
15
15
  function printHelp() {
16
16
  console.log(`
17
- 🚀 agentctl v0.24.0 — Universal Agent Orchestrator & Safety Gatekeeper
17
+ 🚀 agentctl v1.0.1 — Universal Agent Orchestrator & Safety Gatekeeper
18
18
 
19
19
  Usage: agentctl <command> [options]
20
20
 
@@ -44,7 +44,7 @@ async function main() {
44
44
  }
45
45
 
46
46
  if (command === "version" || command === "--version" || command === "-v") {
47
- console.log("agentctl v0.24.0");
47
+ console.log("agentctl v1.0.1");
48
48
  process.exit(0);
49
49
  }
50
50
 
@@ -226,7 +226,7 @@ async function main() {
226
226
  }
227
227
 
228
228
  case "doctor": {
229
- console.log(`\n🔍 agentctl System Diagnostics (v0.24.0)`);
229
+ console.log(`\n🔍 agentctl System Diagnostics (v1.0.1)`);
230
230
  console.log(`--------------------------------------------------`);
231
231
  console.log(` Project Root : ${root}`);
232
232
  console.log(` Config File : ${config._file || "None (Using defaults)"}`);
package/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Google Jules Orchestrator Kit - Node.js SDK (v0.24.0)
2
+ * Google Jules Orchestrator Kit - Node.js SDK (v1.0.1)
3
3
  *
4
4
  * Exposes core orchestrator functions for programmatically driving agent tasks,
5
5
  * security auditing, repo gating, and state operations.
@@ -17,7 +17,16 @@ export {
17
17
  export { sanitizeUntrustedData, buildAgentEnvelope } from "./src/prompt-guard.mjs";
18
18
  export { isolateMcpStdout, writeMcpFrame } from "./src/mcp.mjs";
19
19
  export { git, runCmd, resolveBase, changedFiles, diffBytes, diffText } from "./src/git.mjs";
20
- export { createProvider, JULES_PRESET, CLAUDE_PRESET, CODEX_PRESET } from "./src/provider.mjs";
20
+ export {
21
+ createProvider,
22
+ JULES_PRESET,
23
+ CLAUDE_PRESET,
24
+ CODEX_PRESET,
25
+ ProviderRateLimitError,
26
+ ProviderUnavailableError,
27
+ ProviderSchemaError,
28
+ parseRetryAfter,
29
+ } from "./src/provider.mjs";
21
30
  export {
22
31
  appendLedger,
23
32
  readLedger,
@@ -25,6 +34,7 @@ export {
25
34
  reserveBudget,
26
35
  reserveBudgetAtomic,
27
36
  commitBudgetReservation,
37
+ rollbackBudgetReservation,
28
38
  withBudget,
29
39
  checkDailyBudget,
30
40
  acquireLock,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jules-orchestrator-kit",
3
- "version": "0.24.0",
3
+ "version": "1.0.1",
4
4
  "description": "Orchestration kit for running Google Jules autonomous agents.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,20 +23,16 @@
23
23
  ".": "./index.mjs"
24
24
  },
25
25
  "files": [
26
- "index.mjs",
27
- "src/",
28
26
  "bin/",
27
+ "src/",
29
28
  "scripts/",
30
- ".agent/jules.yml",
29
+ "index.mjs",
30
+ "LICENSE",
31
+ "README.md",
32
+ "JULES_RULES_TEMPLATE.md",
31
33
  ".agent/rules/",
32
34
  ".agent/prompts/",
33
- ".agent/workflows/",
34
- ".agent/jules-queue/README.md",
35
- ".github/workflows/jules-audit.yml",
36
- ".env.example",
37
- "JULES_RULES_TEMPLATE.md",
38
- "README.md",
39
- "LICENSE"
35
+ ".agent/workflows/"
40
36
  ],
41
37
  "scripts": {
42
38
  "init": "node bin/init.js",
package/src/engine.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { loadConfig, parseYaml, normalizeScope } from "./config.mjs";
2
2
  import { checkScope, scanDiff, redactSecrets } from "./security.mjs";
3
3
  import { changedFiles, diffBytes, diffText, showFromOrigin, runCmd } from "./git.mjs";
4
- import { createProvider } from "./provider.mjs";
5
- import { withBudget, appendLedger, getQueueDir, ensureDir } from "./state.mjs";
4
+ import { createProvider, ProviderRateLimitError, ProviderUnavailableError } from "./provider.mjs";
5
+ import { withBudget, appendLedger, getQueueDir, ensureDir, rollbackBudgetReservation } from "./state.mjs";
6
6
  import { sanitizeUntrustedData, buildAgentEnvelope } from "./prompt-guard.mjs";
7
7
  import { recordVerifyRun, readVerifyRuns, flakyVerdict } from "./flaky-ledger.mjs";
8
8
  import { readdirSync, readFileSync, renameSync, existsSync } from "node:fs";
@@ -346,6 +346,24 @@ export async function repair(failure, opts = {}) {
346
346
  config.limits.dailyTasks
347
347
  );
348
348
  } catch (err) {
349
+ if (err instanceof ProviderRateLimitError || err instanceof ProviderUnavailableError) {
350
+ if (err.reservationId) {
351
+ rollbackBudgetReservation(root, err.reservationId);
352
+ }
353
+ const retryAfterMs = err.retryAfterMs || 60000;
354
+ const backoffSec = Math.ceil(retryAfterMs / 1000);
355
+ console.warn(`[PROVIDER_INFRASTRUCTURE_FAILURE] ${err.name}: ${err.message}. Recommended backoff: ${backoffSec}s.`);
356
+ attempts.push({ n, ok: false, error: err.message, providerError: true, retryAfterMs });
357
+ appendTelemetry(root, "ooda_repair_attempt", { attempt: n, ok: false, error: err.message, providerError: true });
358
+ return {
359
+ ok: false,
360
+ attempts,
361
+ finalStatus: "PROVIDER_INFRASTRUCTURE_FAILURE",
362
+ error: err.message,
363
+ retryAfterMs,
364
+ providerError: true,
365
+ };
366
+ }
349
367
  attempts.push({ n, ok: false, error: err.message });
350
368
  appendTelemetry(root, "ooda_repair_attempt", { attempt: n, ok: false, error: err.message });
351
369
  break;
@@ -482,11 +500,31 @@ export async function dispatch(task, opts = {}) {
482
500
 
483
501
  const cleanTask = { ...task, prompt: envelopedPrompt };
484
502
 
485
- return withBudget(
486
- () => provider.dispatch(cleanTask, { root, dryRun: opts.dryRun }),
487
- root,
488
- config.limits.dailyTasks
489
- );
503
+ try {
504
+ return await withBudget(
505
+ () => provider.dispatch(cleanTask, { root, dryRun: opts.dryRun }),
506
+ root,
507
+ config.limits.dailyTasks
508
+ );
509
+ } catch (err) {
510
+ if (err instanceof ProviderRateLimitError || err instanceof ProviderUnavailableError) {
511
+ if (err.reservationId) {
512
+ rollbackBudgetReservation(root, err.reservationId);
513
+ }
514
+ const retryAfterMs = err.retryAfterMs || 60000;
515
+ const status = err instanceof ProviderRateLimitError ? "RATE_LIMITED" : "PROVIDER_UNAVAILABLE";
516
+ const backoffSec = Math.ceil(retryAfterMs / 1000);
517
+ console.warn(`[PROVIDER_INFRASTRUCTURE_FAILURE] ${err.name}: ${err.message}. Recommended backoff: ${backoffSec}s.`);
518
+ return {
519
+ ok: false,
520
+ status,
521
+ error: err.message,
522
+ retryAfterMs,
523
+ providerError: true,
524
+ };
525
+ }
526
+ throw err;
527
+ }
490
528
  }
491
529
 
492
530
  export async function run(tasksOrOpts = {}, opts = {}) {
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.23.0",
12
+ version: "1.0.1",
13
13
  };
14
14
 
15
15
  export const MAX_MCP_FRAME_SIZE = 4 * 1024 * 1024; // 4 MB memory safety ceiling
package/src/provider.mjs CHANGED
@@ -31,6 +31,45 @@ export const CODEX_PRESET = {
31
31
  promptViaStdin: false,
32
32
  };
33
33
 
34
+ export class ProviderRateLimitError extends Error {
35
+ constructor(message, opts = {}) {
36
+ super(message);
37
+ this.name = "ProviderRateLimitError";
38
+ this.retryAfterMs = opts.retryAfterMs ?? 60000;
39
+ this.status = opts.status || 429;
40
+ }
41
+ }
42
+
43
+ export class ProviderUnavailableError extends Error {
44
+ constructor(message, opts = {}) {
45
+ super(message);
46
+ this.name = "ProviderUnavailableError";
47
+ this.status = opts.status || 503;
48
+ this.retryAfterMs = opts.retryAfterMs;
49
+ }
50
+ }
51
+
52
+ export class ProviderSchemaError extends Error {
53
+ constructor(message, opts = {}) {
54
+ super(message);
55
+ this.name = "ProviderSchemaError";
56
+ this.status = opts.status;
57
+ }
58
+ }
59
+
60
+ export function parseRetryAfter(header) {
61
+ if (!header) return null;
62
+ const seconds = Number(header);
63
+ if (!isNaN(seconds)) {
64
+ return Math.max(0, Math.round(seconds * 1000));
65
+ }
66
+ const dateMs = Date.parse(header);
67
+ if (!isNaN(dateMs)) {
68
+ return Math.max(0, dateMs - Date.now());
69
+ }
70
+ return null;
71
+ }
72
+
34
73
  function interpolateString(template, data) {
35
74
  if (typeof template !== "string") return template;
36
75
  return template.replace(/\{(\w+)\}/g, (_, key) => data[key] ?? "");
@@ -111,20 +150,63 @@ export function createProvider(spec = "jules", config = {}) {
111
150
  body = JSON.stringify(data);
112
151
  }
113
152
 
114
- const res = await fetch(url, {
115
- method: providerSpec.method || "POST",
116
- headers,
117
- body,
118
- });
153
+ const timeoutMs = ctx.timeoutMs || config.timeoutMs || providerSpec.timeoutMs || 120_000;
154
+ let res;
155
+ try {
156
+ res = await fetch(url, {
157
+ method: providerSpec.method || "POST",
158
+ headers,
159
+ body,
160
+ signal: AbortSignal.timeout(timeoutMs),
161
+ });
162
+ } catch (err) {
163
+ if (err.name === "TimeoutError" || err.name === "AbortError" || err.code === "ABORT_ERR") {
164
+ throw new ProviderUnavailableError(`Provider HTTP Timeout (${timeoutMs}ms): ${err.message}`, {
165
+ status: 504,
166
+ });
167
+ }
168
+ throw err;
169
+ }
119
170
 
120
171
  if (!res.ok) {
121
172
  const text = await res.text();
122
173
  const cleanText = text.slice(0, 500);
123
174
  const sanitizedText = rawToken ? cleanText.split(rawToken).join("[REDACTED]") : cleanText;
175
+ const retryAfterHeader = res.headers ? res.headers.get("retry-after") : null;
176
+ const retryAfterMs = parseRetryAfter(retryAfterHeader);
177
+
178
+ if (res.status === 429) {
179
+ throw new ProviderRateLimitError(`Provider HTTP Error (429): ${sanitizedText}`, {
180
+ retryAfterMs: retryAfterMs ?? 60000,
181
+ status: 429,
182
+ });
183
+ }
184
+
185
+ if (res.status >= 500 && res.status < 600) {
186
+ throw new ProviderUnavailableError(`Provider HTTP Error (${res.status}): ${sanitizedText}`, {
187
+ status: res.status,
188
+ retryAfterMs: retryAfterMs ?? undefined,
189
+ });
190
+ }
191
+
124
192
  throw new Error(`Provider HTTP Error (${res.status}): ${sanitizedText}`);
125
193
  }
126
194
 
127
- const json = await res.json();
195
+ let json;
196
+ try {
197
+ json = await res.json();
198
+ } catch (err) {
199
+ throw new ProviderSchemaError(`Provider Payload Error: Invalid JSON response: ${err.message}`, {
200
+ status: res.status,
201
+ });
202
+ }
203
+
204
+ if (!json || typeof json !== "object") {
205
+ throw new ProviderSchemaError("Provider Payload Error: Expected JSON object response", {
206
+ status: res.status,
207
+ });
208
+ }
209
+
128
210
  return {
129
211
  id: json.id || json.name || "http-session",
130
212
  status: json.state || "active",
package/src/state.mjs CHANGED
@@ -189,19 +189,31 @@ export function checkDailyBudget(arg1 = resolveRoot(), arg2 = 300) {
189
189
  const content = readFileSync(filePath, "utf-8");
190
190
  const lines = content.split("\n").filter(Boolean);
191
191
  let count = 0;
192
+ const activeIds = new Set();
192
193
  for (const line of lines) {
193
194
  try {
194
195
  const entry = JSON.parse(line);
195
196
  if (entry && entry.event === "budget_reserved") {
196
- count++;
197
+ if (entry.reservationId) {
198
+ activeIds.add(entry.reservationId);
199
+ } else {
200
+ count++;
201
+ }
202
+ } else if (entry && (entry.event === "budget_rolled_back" || entry.event === "budget_released")) {
203
+ if (entry.reservationId) {
204
+ activeIds.delete(entry.reservationId);
205
+ } else {
206
+ count = Math.max(0, count - 1);
207
+ }
197
208
  }
198
209
  } catch (_) {}
199
210
  }
211
+ const used = count + activeIds.size;
200
212
  return {
201
- ok: count < limit,
202
- used: count,
213
+ ok: used < limit,
214
+ used,
203
215
  budget: limit,
204
- remaining: Math.max(0, limit - count),
216
+ remaining: Math.max(0, limit - used),
205
217
  };
206
218
  } catch (_) {
207
219
  return { ok: true, used: 0, budget: limit, remaining: limit };
@@ -226,6 +238,7 @@ export function reserveBudgetAtomic(stateDirOrRoot = resolveRoot(), limit = 300,
226
238
  const filePath = join(stateDir, `ledger-${dateStr}.jsonl`);
227
239
 
228
240
  let count = 0;
241
+ const activeIds = new Set();
229
242
  let prevHash = "0".repeat(64);
230
243
 
231
244
  if (existsSync(filePath)) {
@@ -236,7 +249,17 @@ export function reserveBudgetAtomic(stateDirOrRoot = resolveRoot(), limit = 300,
236
249
  try {
237
250
  const entry = JSON.parse(line);
238
251
  if (entry && entry.event === "budget_reserved") {
239
- count++;
252
+ if (entry.reservationId) {
253
+ activeIds.add(entry.reservationId);
254
+ } else {
255
+ count++;
256
+ }
257
+ } else if (entry && (entry.event === "budget_rolled_back" || entry.event === "budget_released")) {
258
+ if (entry.reservationId) {
259
+ activeIds.delete(entry.reservationId);
260
+ } else {
261
+ count = Math.max(0, count - 1);
262
+ }
240
263
  }
241
264
  if (entry && entry.hash) {
242
265
  prevHash = entry.hash;
@@ -246,8 +269,9 @@ export function reserveBudgetAtomic(stateDirOrRoot = resolveRoot(), limit = 300,
246
269
  } catch (_) {}
247
270
  }
248
271
 
249
- if (count >= limit) {
250
- throw new BudgetError(`Daily budget exhausted (${count}/${limit} tasks executed)`);
272
+ const used = count + activeIds.size;
273
+ if (used >= limit) {
274
+ throw new BudgetError(`Daily budget exhausted (${used}/${limit} tasks executed)`);
251
275
  }
252
276
 
253
277
  const timestamp = new Date().toISOString();
@@ -267,8 +291,8 @@ export function reserveBudgetAtomic(stateDirOrRoot = resolveRoot(), limit = 300,
267
291
  return {
268
292
  ok: true,
269
293
  reservationId,
270
- remaining: Math.max(0, limit - (count + 1)),
271
- used: count + 1,
294
+ remaining: Math.max(0, limit - (used + 1)),
295
+ used: used + 1,
272
296
  };
273
297
  }, opts);
274
298
  }
@@ -282,6 +306,11 @@ export function commitBudgetReservation(rootOrOpts = resolveRoot(), reservationI
282
306
  return appendLedger({ event: "budget_committed", reservationId }, root);
283
307
  }
284
308
 
309
+ export function rollbackBudgetReservation(rootOrOpts = resolveRoot(), reservationId = "") {
310
+ const root = typeof rootOrOpts === "string" ? rootOrOpts : resolveRoot();
311
+ return appendLedger({ event: "budget_rolled_back", reservationId }, root);
312
+ }
313
+
285
314
  export async function withBudget(fn, root = resolveRoot(), limit = 300) {
286
315
  const reservation = reserveBudget(root, limit);
287
316
  try {
@@ -289,6 +318,9 @@ export async function withBudget(fn, root = resolveRoot(), limit = 300) {
289
318
  commitBudgetReservation(root, reservation.reservationId);
290
319
  return result;
291
320
  } catch (err) {
321
+ if (err && typeof err === "object") {
322
+ err.reservationId = reservation.reservationId;
323
+ }
292
324
  appendLedger({ event: "budget_reservation_failed", reservationId: reservation.reservationId, error: err.message }, root);
293
325
  throw err;
294
326
  }
@@ -1,30 +0,0 @@
1
- # Jules Task Queue Directory
2
-
3
- Drop markdown task specification files here (e.g. `TASK-001-feature-name.md`) to queue tasks for background execution by Google Jules.
4
-
5
- ### File Format Example
6
-
7
- ```markdown
8
- # TASK-001: Implement User Rate Limiting
9
-
10
- ## Objective
11
- Implement sliding window rate limiting for public API routes.
12
-
13
- ## Execution Rules
14
- - Use Redis / memory store for tracking hit counts.
15
- - Must return HTTP 429 Too Many Requests when limit is exceeded.
16
- - Must pass `npm test` before submitting PR.
17
- ```
18
-
19
- Process all queued tasks in batch:
20
-
21
- ```bash
22
- agentctl queue
23
- # or npm run jules:queue
24
- ```
25
-
26
- Or dispatch a single task using `agentctl dispatch`:
27
-
28
- ```bash
29
- agentctl dispatch --title "TASK-001 Rate Limiting" --prompt "$(cat .agent/jules-queue/TASK-001-rate-limiting.md)"
30
- ```
package/.agent/jules.yml DELETED
@@ -1,13 +0,0 @@
1
- # Google Jules Repository Configuration (Version 2)
2
- version: 2
3
- test_cmd: "npm test"
4
- build_cmd: ""
5
- forbidden_paths:
6
- - ".github/**"
7
- - "**/secrets/**"
8
- - "**/*.pem"
9
- - "**/lock-manager/**"
10
- - "scripts/jules-self-audit.mjs"
11
- - ".agent/jules.yml"
12
- allow_paths: []
13
-
package/.env.example DELETED
@@ -1,73 +0,0 @@
1
- # Google Jules Orchestration Kit - Environment Configuration Example
2
- # Copy this file to .env and populate with your credentials/settings.
3
-
4
- # --- Authentication & Core Settings ---
5
- # Google Jules API Key (Required for direct REST API dispatches)
6
- JULES_API_KEY=your_google_jules_api_key_here
7
-
8
- # Alias for API key (fallback if JULES_API_KEY is not set)
9
- # GEMINI_API_KEY=your_gemini_api_key_here
10
-
11
- # Override the Jules REST API URL
12
- # JULES_API_URL=https://jules.googleapis.com/v1alpha/sessions
13
-
14
- # Target GitHub Repository (Format: owner/repo)
15
- JULES_REPO=owner/repo
16
-
17
- # Set to "true" or "1" to run in repoless/serverless mode
18
- # JULES_REPOLESS=false
19
-
20
- # Enable Dry-Run mode to simulate payload dispatch without making API calls
21
- # JULES_DRY_RUN=false
22
-
23
- # --- Budget & Security Gatekeeper ---
24
- # Daily max session limit for autonomous dispatches (Default: 300)
25
- # JULES_DAILY_BUDGET=300
26
-
27
- # Allow modifications to command-defining files (package.json, Cargo.toml, etc.) in PRs (Default: false)
28
- # JULES_ALLOW_COMMAND_FILE_CHANGES=false
29
-
30
- # Allow modifications to agent rule files (AGENTS.md, JULES_RULES_TEMPLATE.md, .agent/rules/**) in PRs (Default: false)
31
- # JULES_ALLOW_AGENT_RULE_CHANGES=false
32
-
33
- # --- Execution Scope & Git ---
34
- # Base Branch for PR Audits & Merge-Base Calculations (Default: main)
35
- BASE_BRANCH=main
36
-
37
- # PR Head Branch (Used by CI to dynamically target branches during OODA repair)
38
- # GITHUB_HEAD_REF=my-feature-branch
39
-
40
- # Root directory of the project (Auto-assigned during swarm executions)
41
- # JULES_PROJECT_ROOT=/path/to/project
42
-
43
- # --- Swarm & Concurrency ---
44
- # Maximum parallel dispatches for swarm runs (Default: 3)
45
- JULES_SWARM_CONCURRENCY=3
46
-
47
- # Dispatch Stagger Interval in Milliseconds (Default: 1500)
48
- JULES_SWARM_STAGGER_MS=1500
49
-
50
- # Rate-limit for the jules:queue command (Default: 500)
51
- # JULES_PACE_MS=500
52
-
53
- # Use Git Worktrees instead of cloning for swarm isolation
54
- # JULES_USE_WORKTREES=false
55
-
56
- # Current slot index for partitioning tasks (Swarm mode)
57
- # JULES_SLOT_INDEX=1
58
-
59
- # Total number of slots for partitioning tasks (Swarm mode)
60
- # JULES_SLOT_TOTAL=3
61
-
62
- # --- CI & Output ---
63
- # Is this running in a CI environment? (Changes log output and fail-fast behaviors)
64
- # CI=true
65
-
66
- # Allow OODA Auto-Repair even in CI environments
67
- # ALLOW_AUTO_REPAIR=true
68
-
69
- # GitHub Actions Step Summary File Path
70
- # GITHUB_STEP_SUMMARY=/path/to/step_summary.md
71
-
72
- # Disable color in terminal output
73
- # NO_COLOR=true
@@ -1,49 +0,0 @@
1
- name: Jules PR Audit & Test Gatekeeper
2
-
3
- on:
4
- push:
5
- branches: [ main ]
6
- pull_request:
7
- branches: [ main ]
8
-
9
- jobs:
10
- audit-and-test:
11
- runs-on: ubuntu-latest
12
- strategy:
13
- matrix:
14
- node-version: ['20.x', '22.x', '24.x']
15
- steps:
16
- - name: Checkout repository
17
- uses: actions/checkout@v4
18
- with:
19
- fetch-depth: 0
20
-
21
- - name: Setup Node.js
22
- uses: actions/setup-node@v4
23
- with:
24
- node-version: ${{ matrix.node-version }}
25
-
26
- - name: Cache OODA state & ledgers
27
- uses: actions/cache@v4
28
- with:
29
- path: .agent/state/
30
- key: ooda-state-${{ runner.os }}-${{ github.run_id }}
31
- restore-keys: |
32
- ooda-state-${{ runner.os }}-
33
-
34
- - name: Install dependencies
35
- run: npm install
36
-
37
- - name: Run Linter
38
- run: npm run lint --if-present
39
-
40
- - name: Run Unit Tests
41
- run: npm test --if-present
42
-
43
- - name: Run Jules PR Self-Audit Gatekeeper
44
- if: matrix.node-version == '22.x' && github.event_name == 'pull_request'
45
- run: node scripts/jules-self-audit.mjs
46
- env:
47
- CI: "true"
48
- BASE_BRANCH: "main"
49
-