jules-orchestrator-kit 1.0.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 +282 -137
- package/bin/agentctl.mjs +3 -3
- package/index.mjs +12 -2
- package/package.json +1 -1
- package/src/engine.mjs +45 -7
- package/src/mcp.mjs +1 -1
- package/src/provider.mjs +88 -6
- package/src/state.mjs +41 -9
package/README.md
CHANGED
|
@@ -1,208 +1,353 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# 🚀 jules-orchestrator-kit
|
|
4
|
+
|
|
5
|
+
### High-Volume Autonomous AI Agent Orchestration Engine for Google Jules
|
|
4
6
|
|
|
5
7
|
[](https://github.com/FullThrottle83/jules-orchestrator-kit/actions/workflows/jules-audit.yml)
|
|
6
8
|
[](https://www.npmjs.com/package/jules-orchestrator-kit)
|
|
7
9
|
[](https://opensource.org/licenses/MIT)
|
|
8
10
|
[](https://nodejs.org)
|
|
11
|
+
[](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
|
-
>
|
|
11
|
-
> Built specifically to execute 300+ daily agent sessions and parallel swarms safely with zero external runtime dependencies. Built strictly on native Node.js 20+ ESM standard library.
|
|
18
|
+
</div>
|
|
12
19
|
|
|
13
20
|
---
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
===================================================================================
|
|
19
|
-
JULES ORCHESTRATOR KIT — ARCHITECTURE LAYERS
|
|
20
|
-
===================================================================================
|
|
21
|
-
|
|
22
|
-
+-------------------------------------------------------------------------------+
|
|
23
|
-
| GOVERNANCE PLANE |
|
|
24
|
-
| +--------------------+ +--------------------+ +-------------------------+ |
|
|
25
|
-
| | Scope Guard | | Secret Scanner | | Risk Classifier | |
|
|
26
|
-
| | (builtin & custom) | | (entropy > 3.6b) | | (R0 Cosmetic - R3 Restr)| |
|
|
27
|
-
| +--------------------+ +--------------------+ +-------------------------+ |
|
|
28
|
-
| +--------------------+ +--------------------+ +-------------------------+ |
|
|
29
|
-
| | Prompt Firewall | | Dynamic Guardrails | | Flaky Test Quarantine | |
|
|
30
|
-
| | (injection strip) | | (rule budget sync) | | (Wilson CI / Exit 8) | |
|
|
31
|
-
| +--------------------+ +--------------------+ +-------------------------+ |
|
|
32
|
-
+----------------------------------------+--------------------------------------+
|
|
33
|
-
|
|
|
34
|
-
v
|
|
35
|
-
+-------------------------------------------------------------------------------+
|
|
36
|
-
| EXECUTION PLANE |
|
|
37
|
-
| +--------------------+ +--------------------+ +-------------------------+ |
|
|
38
|
-
| | Agent Envelope | | Task DAG Engine | | OODA Auto-Repair | |
|
|
39
|
-
| | (sanitized payload)| | (Kahn's / Fingerpr)| | (3-strike thrash guard) | |
|
|
40
|
-
| +--------------------+ +--------------------+ +-------------------------+ |
|
|
41
|
-
| +--------------------+ +--------------------+ +-------------------------+ |
|
|
42
|
-
| | Process Group Mgr | | Hermetic Net Guard | | 3-Way Structural Merger | |
|
|
43
|
-
| | (detached / SIGKILL| | (ERR_UNMOCKED_NET) | | (AST/JSON & block diff) | |
|
|
44
|
-
| +--------------------+ +--------------------+ +-------------------------+ |
|
|
45
|
-
+----------------------------------------+--------------------------------------+
|
|
46
|
-
|
|
|
47
|
-
v
|
|
48
|
-
+-------------------------------------------------------------------------------+
|
|
49
|
-
| KERNEL PLANE |
|
|
50
|
-
| +--------------------+ +--------------------+ +-------------------------+ |
|
|
51
|
-
| | VFS Mutex | | Telemetry Spine | | Stdio MCP Stream | |
|
|
52
|
-
| | (linearizable CAS) | | (O(1) SHA-256 chain)| | (Content-Length 4MB) | |
|
|
53
|
-
| +--------------------+ +--------------------+ +-------------------------+ |
|
|
54
|
-
| +--------------------+ +--------------------+ +-------------------------+ |
|
|
55
|
-
| | Intent Journal | | Worktree Reaper | | Atomic Budget Ledger | |
|
|
56
|
-
| | (append-only sync) | | (zombie PID prune) | | (sliding window / proc) | |
|
|
57
|
-
| +--------------------+ +--------------------+ +-------------------------+ |
|
|
58
|
-
+-------------------------------------------------------------------------------+
|
|
59
|
-
===================================================================================
|
|
60
|
-
```
|
|
22
|
+
<p align="center">
|
|
23
|
+
<img src="docs/assets/hero-flow.svg?v=3" alt="Autonomous Orchestration Pipeline" width="100%" />
|
|
24
|
+
</p>
|
|
61
25
|
|
|
62
26
|
---
|
|
63
27
|
|
|
64
|
-
## ⚡
|
|
28
|
+
## ⚡ 2-Minute Quickstart
|
|
65
29
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
Install globally or run via `npx`:
|
|
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.
|
|
69
31
|
|
|
70
32
|
```bash
|
|
71
|
-
# Initialize orchestrator structure
|
|
72
|
-
npx
|
|
33
|
+
# 1. Initialize orchestrator structure in your target codebase
|
|
34
|
+
npx jules-orchestrator-kit init
|
|
73
35
|
|
|
74
|
-
# Dispatch an autonomous
|
|
36
|
+
# 2. Dispatch an autonomous task (Dry-Run mode for local simulation)
|
|
75
37
|
JULES_DRY_RUN=1 npx agentctl dispatch \
|
|
76
|
-
--title "
|
|
38
|
+
--title "Add JWT Validator" \
|
|
77
39
|
--prompt "Implement JWT validation middleware with unit tests"
|
|
78
40
|
|
|
79
|
-
# Run the 4-phase security
|
|
41
|
+
# 3. Run the 4-phase security & verification gatekeeper
|
|
80
42
|
npx agentctl gate
|
|
81
43
|
|
|
82
|
-
#
|
|
83
|
-
npx agentctl
|
|
44
|
+
# 4. Connect as a native stdio MCP server (for Claude Code, Cursor, or Antigravity)
|
|
45
|
+
npx agentctl mcp
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
> 📖 **NOTE**: **Looking for production deployment patterns?**
|
|
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).
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 🎯 Why jules-orchestrator-kit?
|
|
54
|
+
|
|
55
|
+
Whether you are dispatching your first automated coding task or managing high-throughput CI/CD swarms across large engineering teams, `jules-orchestrator-kit` provides total operational safety:
|
|
56
|
+
|
|
57
|
+
| Feature | 🐣 For Rookies & Beginners | 🛠️ For Senior Developers & Infrastructure Engineers |
|
|
58
|
+
| :--- | :--- | :--- |
|
|
59
|
+
| **Safety First** | Never breaks `main` branch or pushes failing code. | 4-Phase Safety Gatekeeper fails closed on scope drift, high entropy secrets, or test regressions. |
|
|
60
|
+
| **Token Budget Protection** | Prevents runaway loops from burning API quotas. | Sliding-window OODA thrash detector ($A \rightarrow B \rightarrow A \rightarrow B$) halts non-convergent repair cycles automatically. |
|
|
61
|
+
| **Zero Setup Hassle** | Works out of the box with standard `npm test`. | **Zero External Runtime Dependencies** (`node:fs`, `node:path`, `node:crypto`, `node:child_process`). |
|
|
62
|
+
| **Multi-Agent Swarms** | Run multiple tasks simultaneously without conflict. | Deterministic VFS mutex and 3-way structural merge engine resolve parallel worktree changes cleanly. |
|
|
63
|
+
| **IDE & Tooling** | Seamlessly connects to your favorite editor. | Native Model Context Protocol (MCP) server over memory-bounded stdio streams. |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 🏛️ System Architecture & Visual Diagrams
|
|
68
|
+
|
|
69
|
+
<details open>
|
|
70
|
+
<summary><b>📐 1. Control Plane Architecture Layers</b></summary>
|
|
71
|
+
|
|
72
|
+
<br/>
|
|
73
|
+
|
|
74
|
+
<p align="center">
|
|
75
|
+
<img src="docs/assets/architecture-layers.svg?v=3" alt="Control Plane Architecture Layers" width="100%" />
|
|
76
|
+
</p>
|
|
77
|
+
|
|
78
|
+
<br/>
|
|
79
|
+
|
|
80
|
+
### Engine System Highlights
|
|
81
|
+
|
|
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`).
|
|
83
|
+
- **Intent Journaling & Zombie Worktree Reaper (`src/journal.mjs`)**: Automatic boot-time scan (`reapOrphanedIntents`) in `agentctl` and MCP server that tracks git operations in `.agent/state/journal.jsonl` and prunes orphaned worktrees left by crashed/recycled processes.
|
|
84
|
+
- **Hermetic Network Egress Guard (`src/preload-net-guard.mjs`)**: Intercepts and blocks unmocked outbound HTTP/HTTPS egress during test execution (`NODE_OPTIONS="--import ./src/preload-net-guard.mjs"`), enforcing hermetic testing while allowing local loopback (`localhost`, `127.0.0.1`).
|
|
85
|
+
- **Linearizable VFS Mutex (`src/state.mjs`)**: Kernel-level directory mutex (`withVfsMutex`) guaranteeing serial linearizability for SHA-256 hash-chained session ledgers with atomic budget reservation (`reserveBudgetAtomic`).
|
|
86
|
+
- **PID Recycling & Stale Lock Protection (`src/state.mjs`)**: Linux `/proc/<pid>/stat` launch-time validation and random UUID nonces prevent false-positive lock reaps from recycled OS process IDs.
|
|
87
|
+
- **Memory-Bounded Content-Length MCP Streaming (`src/mcp.mjs`)**: Native MCP server over stdio streams using `McpFrameDecoder` with a 4 MB memory safety ceiling and panic boundaries to prevent stdout stack trace leaks.
|
|
88
|
+
- **Process Group Isolation (`src/process-group.mjs`)**: `ProcessGroupManager` creates isolated process groups (`detached: true`) and catches `SIGINT`/`SIGTERM`/`exit` signals to execute `process.kill(-pgid)`, guaranteeing zero zombie processes.
|
|
89
|
+
- **TOCTOU & Symlink Defense (`src/security.mjs`)**: `safeAtomicWrite()` uses `O_CREAT | O_EXCL | O_WRONLY` temp files with `fsyncSync` + `renameSync` and `lstatSync`/`realpathSync` symlink checks.
|
|
90
|
+
- **3-Way Structural AST/JSON Merge (`scripts/jules-merge-swarm.mjs`)**: Pure Node `deepMerge3Way()` algorithm for recursive object and array merges executed in isolated temporary directories (`os.tmpdir()`).
|
|
91
|
+
|
|
92
|
+
</details>
|
|
93
|
+
|
|
94
|
+
<details open>
|
|
95
|
+
<summary><b>🔁 2. Self-Healing OODA Loop Cycle</b></summary>
|
|
96
|
+
|
|
97
|
+
<br/>
|
|
98
|
+
|
|
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/>
|
|
119
|
+
|
|
120
|
+
### The 4-Phase Safety Audit & Security Boundary (`agentctl gate`)
|
|
121
|
+
|
|
122
|
+
1. **Scope Fencing (`forbidden_paths`)**: Ensures agents cannot modify protected files (`package.json`, `.github/`, deployment keys) without explicit overrides.
|
|
123
|
+
2. **Diff Payload Governor**: Rejects oversized diffs (> 75 KB) to prevent truncation and hidden payload injections.
|
|
124
|
+
3. **Secret Entropy Scanner**: Scans diffs for high-confidence secrets (AWS keys, Stripe keys, GitHub tokens, SSH private keys) using Shannon Entropy analysis (> 3.6 bits).
|
|
125
|
+
4. **Trusted Verification Suite**: Executes auto-detected unit tests and linters (`npm test`) inside a hermetic network sandbox to guarantee zero regressions before merging.
|
|
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]`).
|
|
127
|
+
6. **MCP Stream Isolation (`src/mcp.mjs`)**: Seals `process.stdout.write` framing stream to prevent log output from corrupting JSON-RPC stdio frames.
|
|
128
|
+
|
|
129
|
+
> ⚠️ **WARNING**: All security rules are fetched strictly from `origin/main` (never untrusted PR branches) to prevent prompt-injection attacks from altering security rules.
|
|
130
|
+
|
|
131
|
+
</details>
|
|
132
|
+
|
|
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>
|
|
141
|
+
|
|
142
|
+
<br/>
|
|
143
|
+
|
|
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/>
|
|
158
|
+
|
|
159
|
+
### Connecting to Claude Desktop, Cursor, or Antigravity
|
|
160
|
+
|
|
161
|
+
Start the native stdio MCP server:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npx agentctl mcp
|
|
84
165
|
```
|
|
85
166
|
|
|
86
|
-
|
|
167
|
+
#### MCP Tool Registry Exposed:
|
|
168
|
+
- `dispatch_jules_task`: Dispatch autonomous coding tasks directly from your LLM prompt.
|
|
169
|
+
- `audit_jules_gate`: Execute the 4-phase safety gate against the workspace.
|
|
170
|
+
- `check_risk_tier`: Classify workspace changes into Risk Tiers (R0 Cosmetic to R3 Restricted).
|
|
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.
|
|
87
173
|
|
|
88
|
-
|
|
174
|
+
</details>
|
|
175
|
+
|
|
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>
|
|
184
|
+
|
|
185
|
+
<br/>
|
|
186
|
+
|
|
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
|
+
---
|
|
89
202
|
|
|
90
|
-
|
|
203
|
+
## 🤖 Specialist Agent Prompt Presets (`.agent/prompts/`)
|
|
91
204
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
205
|
+
Specialized prompt presets enforce payload limits (< 75 KB) and domain guardrails out of the box:
|
|
206
|
+
|
|
207
|
+
| Preset | Role & Domain | Primary Focus |
|
|
208
|
+
| :--- | :--- | :--- |
|
|
209
|
+
| **`Overseer.md`** | **Architect & Supervisor** | System-wide refactoring, linearizable state, and structural integrity. |
|
|
210
|
+
| **`Bolt.md`** | **Performance Engineer** | Bottleneck elimination, streaming optimization, and low-latency execution. |
|
|
211
|
+
| **`Sentinel.md`** | **Security Auditor** | Vulnerability patching, secret sanitization, and TOCTOU defense. |
|
|
212
|
+
| **`Janitor.md`** | **Technical Debt & Cleanup** | Dead code elimination, unused import pruning, and zero-dependency compliance. |
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# Example: Dispatch a cleanup task using the Janitor preset
|
|
216
|
+
npx agentctl dispatch \
|
|
217
|
+
--prompt "$(cat .agent/prompts/Janitor.md) Prune unused helper methods in src/utils.mjs"
|
|
104
218
|
```
|
|
105
219
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## ⚡ GitHub Actions Composite Action (`.github/actions/setup-jules`)
|
|
223
|
+
|
|
224
|
+
Integrate `jules-orchestrator-kit` into any GitHub Actions workflow with 3 lines of YAML:
|
|
225
|
+
|
|
226
|
+
```yaml
|
|
227
|
+
steps:
|
|
228
|
+
- uses: actions/checkout@v4
|
|
229
|
+
- uses: FullThrottle83/jules-orchestrator-kit/.github/actions/setup-jules@main
|
|
230
|
+
with:
|
|
231
|
+
action: 'gate'
|
|
232
|
+
base_branch: 'main'
|
|
233
|
+
tier: 'ultra'
|
|
234
|
+
env:
|
|
235
|
+
JULES_API_KEY: ${{ secrets.JULES_API_KEY }}
|
|
120
236
|
```
|
|
121
237
|
|
|
122
238
|
---
|
|
123
239
|
|
|
124
|
-
##
|
|
240
|
+
## 🛠️ CLI Command Reference (`agentctl`)
|
|
241
|
+
|
|
242
|
+
| Command | Usage Example | Description |
|
|
243
|
+
| :--- | :--- | :--- |
|
|
244
|
+
| **`init`** | `npx agentctl init` | Initializes `.agent/` configuration, workflows, and task queue directory |
|
|
245
|
+
| **`dispatch`** | `agentctl dispatch --title "Fix Bug" --prompt "..."` | Dispatches an autonomous task to Google Jules |
|
|
246
|
+
| **`gate`** | `agentctl gate [--fix] [--base main]` | Runs 4-phase safety gatekeeper audit against workspace |
|
|
247
|
+
| **`queue`** | `agentctl queue` | Processes pending task queue sequentially from `.agent/jules-queue/` |
|
|
248
|
+
| **`swarm`** | `agentctl swarm` | Launches parallel multi-agent swarm in isolated git worktrees |
|
|
249
|
+
| **`merge-swarm`** | `agentctl merge-swarm` | Performs 3-way structural merge on completed swarm PRs |
|
|
250
|
+
| **`mcp`** | `agentctl mcp` | Starts stdio Model Context Protocol (MCP) JSON-RPC 2.0 server |
|
|
251
|
+
| **`doctor`** | `agentctl doctor` | Verifies stack configuration, environment keys, and daily token budget |
|
|
252
|
+
| **`scan`** | `agentctl scan` | Scans codebase for `TODO` and `FIXME` comments and generates task queue |
|
|
253
|
+
| **`clean`** | `agentctl clean` | Audits and cleans up stale git worktrees, orphaned intents, locks, and temporary state files |
|
|
254
|
+
|
|
255
|
+
---
|
|
125
256
|
|
|
126
|
-
|
|
257
|
+
## 📝 Configuration Reference (`.agent/jules.yml`)
|
|
258
|
+
|
|
259
|
+
Auto-generated by `agentctl init` at the root of your project:
|
|
127
260
|
|
|
128
261
|
```yaml
|
|
129
|
-
# Google Jules Repository Configuration
|
|
130
262
|
version: 2
|
|
131
|
-
tier: "
|
|
132
|
-
provider: "jules"
|
|
133
|
-
|
|
134
|
-
# Automated verification suite commands (auto-detected if omitted)
|
|
263
|
+
tier: "pro" # Options: free, pro, ultra (default: ultra)
|
|
135
264
|
test_cmd: "npm test"
|
|
136
265
|
build_cmd: "npm run build"
|
|
137
|
-
|
|
138
|
-
# Scope protection boundaries (builtin protections are automatically merged)
|
|
139
266
|
forbidden_paths:
|
|
140
|
-
- ".github
|
|
267
|
+
- ".github/"
|
|
141
268
|
- "package.json"
|
|
142
|
-
- ".agent/config.yml"
|
|
143
269
|
- ".agent/jules.yml"
|
|
144
|
-
|
|
145
270
|
allow_paths: []
|
|
146
|
-
|
|
147
|
-
# Operational limits & budgets
|
|
148
271
|
limits:
|
|
149
|
-
dailyTasks: 300
|
|
150
|
-
repairAttempts: 3
|
|
151
|
-
diffKb: 75
|
|
152
|
-
concurrency: 3 # Maximum parallel worktree swarm workers
|
|
153
|
-
staggerMs: 1000 # Dispatch stagger delay in milliseconds
|
|
272
|
+
dailyTasks: 300
|
|
273
|
+
repairAttempts: 3
|
|
274
|
+
diffKb: 75
|
|
154
275
|
```
|
|
155
276
|
|
|
156
277
|
---
|
|
157
278
|
|
|
158
|
-
## 🚦 Exit Code
|
|
279
|
+
## 🚦 Exit Code Registry & Troubleshooting
|
|
159
280
|
|
|
160
|
-
Standardized exit codes enforced across all CLI
|
|
281
|
+
Standardized exit codes enforced across all CLI utilities and CI pipelines:
|
|
161
282
|
|
|
162
283
|
| Exit Code | Classification | Description & Immediate Remediation Action |
|
|
163
284
|
| :---: | :--- | :--- |
|
|
164
|
-
| `0` | **
|
|
165
|
-
| `1` | **
|
|
166
|
-
| `2` | **API / Network Failure** | Jules
|
|
167
|
-
| `3` | **Scope
|
|
168
|
-
| `4` | **
|
|
169
|
-
| `5` | **Diff Payload
|
|
170
|
-
| `6` | **Secret
|
|
171
|
-
| `7` | **Budget Exhausted** | Daily task session quota limit reached (`limits.dailyTasks`, default 300
|
|
285
|
+
| `0` | **Success** | Task completed cleanly; PR opened or verification passed. |
|
|
286
|
+
| `1` | **Pre-Dispatch / Arg Error** | Invalid arguments, prompt > 50 KB, or pre-dispatch validation error. |
|
|
287
|
+
| `2` | **API / Network Failure** | Jules API rate-limit (HTTP 429), `FAILED_PRECONDITION` quota, or timeout. |
|
|
288
|
+
| `3` | **Scope Violation** | Attempted modification of restricted files (`.github/`, command files, agent rules). |
|
|
289
|
+
| `4` | **OODA Exhausted / Thrash** | Verification suite failed after 3 repair attempts or hit deterministic regression. |
|
|
290
|
+
| `5` | **Diff Payload Limit** | Post-change git diff exceeds payload budget (`limits.diffKb`, default 75 KB). |
|
|
291
|
+
| `6` | **Secret Detected** | High-confidence secret or private key detected in patch diff (Shannon entropy > 3.6 bits). |
|
|
292
|
+
| `7` | **Budget Exhausted** | Daily task session quota limit reached (`limits.dailyTasks`, default 300). |
|
|
172
293
|
| `8` | **FLAKY_QUARANTINE** | Statistical test flakiness detected (oscillation >= 0.4, Wilson CI); OODA repair suppressed. |
|
|
173
|
-
| `124` | **Execution Timeout** | Subprocess execution exceeded hard
|
|
174
|
-
| `188` | **ERR_UNMOCKED_NET** | Unmocked outbound HTTP/HTTPS egress intercepted
|
|
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. |
|
|
175
296
|
|
|
176
297
|
---
|
|
177
298
|
|
|
178
|
-
##
|
|
299
|
+
## 🔐 Environment Variables Reference
|
|
300
|
+
|
|
301
|
+
| Variable | Description | Default |
|
|
302
|
+
| :--- | :--- | :--- |
|
|
303
|
+
| `JULES_API_KEY` | Google Jules REST API key | *(none)* |
|
|
304
|
+
| `JULES_REPO` | Target GitHub Repository (`owner/repo`) | Auto-detected from `git remote` |
|
|
305
|
+
| `JULES_TIER` | Subscription tier preset (`free`, `pro`, `ultra`) | `ultra` |
|
|
306
|
+
| `JULES_DRY_RUN` | Set to `1` or `true` for dry-run simulation mode | `false` |
|
|
307
|
+
| `JULES_DAILY_BUDGET` | Custom daily session budget limit | `300` |
|
|
308
|
+
| `JULES_MAX_DIFF_KB` | Custom git diff payload limit in KB | `75` |
|
|
309
|
+
| `JULES_ALLOW_COMMAND_FILE_CHANGES` | Allow PR changes to command files (`package.json`, etc.) | `false` |
|
|
310
|
+
| `JULES_ALLOW_AGENT_RULE_CHANGES` | Allow PR changes to agent rule files (`AGENTS.md`, etc.) | `false` |
|
|
311
|
+
| `BASE_BRANCH` | Base branch for PR Audits & Merge-Base checks | `main` |
|
|
312
|
+
| `NO_COLOR` | Set to `true` to disable ANSI color output | `false` |
|
|
179
313
|
|
|
180
|
-
|
|
314
|
+
---
|
|
181
315
|
|
|
182
|
-
|
|
183
|
-
2. **Shannon Entropy Secret Scanner**: Scans diffs for high-confidence secrets (AWS, Stripe, GitHub tokens, RSA private keys) using Shannon Entropy analysis (> 3.6 bits).
|
|
184
|
-
3. **Hermetic Network Guard (`ERR_UNMOCKED_NET`)**: Intercepts unmocked outbound HTTP/HTTPS egress during verification runs (`node:http`, `node:https`, `fetch`), enforcing offline-first hermetic execution while permitting local loopback.
|
|
185
|
-
4. **Prompt Firewall (`src/prompt-guard.mjs`)**: Neutralizes prompt injection patterns, strips zero-width unicode, bidi control characters, and ANSI escape codes before payloads reach LLM context boundaries.
|
|
186
|
-
5. **Linearizable VFS Directory Mutex (`src/state.mjs`)**: CAS directory mutex guarantees serial linearizability across multi-process swarms. Process start time validation against `/proc/<pid>/stat` prevents stale lock corruption from recycled OS process IDs.
|
|
187
|
-
6. **O(1) SHA-256 Telemetry Hash-Spine (`src/telemetry.mjs`)**: Cryptographically links all session events in an append-only ledger with automatic head-desync self-healing.
|
|
316
|
+
## 🌐 Supported Tech Stacks & Auto-Detection
|
|
188
317
|
|
|
189
|
-
|
|
318
|
+
The orchestrator automatically infers verification and build commands across ecosystems:
|
|
190
319
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
320
|
+
| Stack / Ecosystem | Manifest File | Inferred Test Command | Inferred Build Command |
|
|
321
|
+
| :--- | :--- | :--- | :--- |
|
|
322
|
+
| **Turborepo** | `turbo.json` | `npx turbo run test` | `npx turbo run build` |
|
|
323
|
+
| **pnpm Workspace** | `pnpm-workspace.yaml` | `pnpm test` | `pnpm build` |
|
|
324
|
+
| **Nx Workspace** | `nx.json` | `npx nx run-many -t test` | `npx nx run-many -t build` |
|
|
325
|
+
| **JavaScript / TypeScript** | `package.json` | `npm test` | `npm run build` |
|
|
326
|
+
| **Rust** | `Cargo.toml` | `cargo test --workspace` | `cargo build` |
|
|
327
|
+
| **Go** | `go.mod` | `go test ./...` | `go build ./...` |
|
|
328
|
+
| **Python** | `pyproject.toml` | `pytest` | *(none)* |
|
|
329
|
+
| **Bun / Deno** | `bunfig.toml` / `deno.json` | `bun test` / `deno test` | `bun run build` |
|
|
330
|
+
| **Elixir / Ruby** | `mix.exs` / `Gemfile` | `mix test` / `rake test` | *(standard build)* |
|
|
331
|
+
| **Java / C / C++** | `pom.xml` / `Makefile` | `mvn test` / `make test` | `mvn compile` / `make` |
|
|
195
332
|
|
|
196
333
|
---
|
|
197
334
|
|
|
198
|
-
## 🤝 Contributing &
|
|
335
|
+
## 🤝 Contributing & Standards
|
|
336
|
+
|
|
337
|
+
We welcome community contributions! Please adhere to our core engineering invariants:
|
|
338
|
+
|
|
339
|
+
1. **Zero External Runtime Dependencies**: Use ONLY native Node.js ESM built-in modules (`node:fs`, `node:path`, `node:crypto`, `node:child_process`, `node:os`).
|
|
340
|
+
2. **100% Verification Suite**: All test suites must pass cleanly with 0 errors.
|
|
341
|
+
3. **Cross-Platform Compatibility**: Always normalize Windows backslashes (`\`) to POSIX slashes (`/`).
|
|
342
|
+
|
|
343
|
+
### Running Tests Locally
|
|
199
344
|
|
|
200
345
|
```bash
|
|
201
|
-
# Clone repository
|
|
346
|
+
# Clone the repository
|
|
202
347
|
git clone https://github.com/FullThrottle83/jules-orchestrator-kit.git
|
|
203
348
|
cd jules-orchestrator-kit
|
|
204
349
|
|
|
205
|
-
# Run ESLint
|
|
350
|
+
# Run ESLint & node unit test suite (100% zero external runtime deps)
|
|
206
351
|
npm run lint
|
|
207
352
|
npm test
|
|
208
353
|
```
|
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 v1.0.
|
|
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 v1.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 (v1.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 (v1.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 {
|
|
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
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
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
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
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
202
|
-
used
|
|
213
|
+
ok: used < limit,
|
|
214
|
+
used,
|
|
203
215
|
budget: limit,
|
|
204
|
-
remaining: Math.max(0, limit -
|
|
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
|
-
|
|
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
|
-
|
|
250
|
-
|
|
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 - (
|
|
271
|
-
used:
|
|
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
|
}
|