jules-orchestrator-kit 0.9.4 → 0.20.0
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 +69 -274
- package/bin/agentctl.mjs +1 -1
- package/index.mjs +11 -2
- package/package.json +1 -1
- package/scripts/jules-merge-swarm.mjs +179 -11
- package/src/config.mjs +34 -0
- package/src/engine.mjs +119 -3
- package/src/execution_envelope.mjs +103 -0
- package/src/git.mjs +20 -2
- package/src/mcp.mjs +98 -12
- package/src/process-group.mjs +80 -0
- package/src/risk.mjs +3 -2
- package/src/rules_budget.mjs +102 -0
- package/src/security.mjs +45 -6
- package/src/state.mjs +160 -23
- package/src/webhook.mjs +17 -3
package/README.md
CHANGED
|
@@ -1,319 +1,114 @@
|
|
|
1
1
|
# jules-orchestrator-kit
|
|
2
2
|
|
|
3
|
-
*Disclaimer: This is an independent open-source orchestration tool and is not officially affiliated with or endorsed by Google.*
|
|
3
|
+
*Disclaimer: This is an independent open-source orchestration tool for Google Jules and is not officially affiliated with or endorsed by Google.*
|
|
4
4
|
|
|
5
5
|
[](https://github.com/FullThrottle83/jules-orchestrator-kit/actions/workflows/jules-audit.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/jules-orchestrator-kit)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
|
|
9
|
-
**
|
|
10
|
-
|
|
9
|
+
> **v0.20.0 Early Community Release Candidate**
|
|
10
|
+
> High-volume autonomous orchestration engine for Google Jules. Built specifically to handle 300+ daily sessions and parallel agent swarms with zero external runtime dependencies.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
> **Alpha Release:** This kit is in active development. Please exercise caution before integrating it into critical production pipelines.
|
|
12
|
+
---
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
> **Task Limit Warning:** Autonomous loops can quickly consume your daily API limits. We strongly recommend starting with `JULES_DRY_RUN=1` to understand the workflow before scaling up.
|
|
14
|
+
## ⚡ 2-Minute Quickstart
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
To use this kit, you will need:
|
|
20
|
-
- Node.js `18.0.0` or higher
|
|
21
|
-
- `git` installed and available in your PATH
|
|
22
|
-
- A Google Jules REST API key (set as `JULES_API_KEY`) **OR** the native `jules` binary in your PATH.
|
|
16
|
+
Get up and running in under 2 minutes:
|
|
23
17
|
|
|
24
|
-
## Quick Start
|
|
25
|
-
Initialize your repository:
|
|
26
18
|
```bash
|
|
19
|
+
# 1. Install or initialize in your target codebase
|
|
27
20
|
npx jules-orchestrator-kit init
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Dispatch a task or run the queue using `agentctl`:
|
|
31
|
-
```bash
|
|
32
|
-
agentctl dispatch --title "Refactor Auth" --prompt "Implement JWT verification in auth handler"
|
|
33
|
-
agentctl queue
|
|
34
|
-
agentctl gate
|
|
35
|
-
```
|
|
36
21
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
22
|
+
# 2. Dispatch a task (Dry-Run mode for testing)
|
|
23
|
+
JULES_DRY_RUN=1 npx agentctl dispatch \
|
|
24
|
+
--title "Add JWT Validator" \
|
|
25
|
+
--prompt "Implement JWT validation middleware with unit tests"
|
|
40
26
|
|
|
41
|
-
|
|
27
|
+
# 3. Run security & verification gatekeeper
|
|
28
|
+
npx agentctl gate
|
|
42
29
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
1. **You Assign Task:** Define what needs fixing or building (supports text and multimodal image mockups).
|
|
46
|
-
2. **Jules Writes Code:** Proposes changes in an isolated Git worktree sandbox.
|
|
47
|
-
3. **Run Tests & Linters:** The Gatekeeper runs your test suite, linters, and type checks.
|
|
48
|
-
4. **Self-Correction:** If anything fails, Jules automatically retries with fixes (OODA loop).
|
|
49
|
-
5. **Safe Delivery:** Once tests pass, the PR is verified and ready for review.
|
|
50
|
-
|
|
51
|
-
```mermaid
|
|
52
|
-
sequenceDiagram
|
|
53
|
-
autonumber
|
|
54
|
-
|
|
55
|
-
box "Client Edge" #F4F4F4
|
|
56
|
-
actor Trigger as Client (CLI / CI / SDK)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
box "Control Plane" #E8F4F8
|
|
60
|
-
participant Orc as Orchestrator Core
|
|
61
|
-
participant Envelope as Task Envelope Validator
|
|
62
|
-
participant Gate as Self-Audit Gatekeeper
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
box "Execution Sandbox" #F8E8E8
|
|
66
|
-
participant API as Google Jules API
|
|
67
|
-
participant Git as Git Worktree Sandbox
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
Trigger->>+Orc: Dispatch Task Payload / Envelope
|
|
71
|
-
|
|
72
|
-
note over Orc,Envelope: Phase 1: Pre-Flight Premise & Security Validation
|
|
73
|
-
Orc->>+Envelope: Validate Envelope (referenced_paths exist? base commit fresh?)
|
|
74
|
-
alt Premise Failure / Missing Referenced Paths / Stale Base
|
|
75
|
-
Envelope-->>Orc: Validation Failed (Premise Error)
|
|
76
|
-
Orc-->>Trigger: Abort Pre-Dispatch (Exit 1)
|
|
77
|
-
else Envelope Validated
|
|
78
|
-
Envelope-->>-Orc: Envelope Approved
|
|
79
|
-
Orc->>Orc: Redact Secrets (Entropy > 3.6) & Enforce Dynamic Guardrails
|
|
80
|
-
Orc->>+Git: Provision Isolation Sandbox (git worktree)
|
|
81
|
-
Git-->>-Orc: Sandbox Ready
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
loop OODA Repair Cycle (Max 3 Retries)
|
|
85
|
-
note over Orc,Git: Phase 2: Agent Execution & Dispatch
|
|
86
|
-
Orc->>+API: Dispatch Task + <MCP_DIRECTIVE> & Target Scope
|
|
87
|
-
API->>+Git: Apply Proposed Code Changes
|
|
88
|
-
Git-->>-API: Changes Written
|
|
89
|
-
API-->>-Orc: Execution Complete
|
|
90
|
-
|
|
91
|
-
note over Orc,Gate: Phase 3: Multi-Gate Verification & Risk Classification
|
|
92
|
-
Orc->>+Gate: Trigger Self-Audit (trusted origin/main rules)
|
|
93
|
-
|
|
94
|
-
Gate->>+Git: Run Scope, Payload & Stale-Base Audit (`git diff`)
|
|
95
|
-
Git-->>-Gate: Diff Stats & Base Commit Age
|
|
96
|
-
|
|
97
|
-
alt Scope Breach / Payload > 75 KB / Stale Base > 25 commits
|
|
98
|
-
Gate-->>Orc: Security / Scope / Stale-Base Violation
|
|
99
|
-
Orc->>Orc: Record Telemetry (metrics.jsonl)
|
|
100
|
-
Orc-->>Trigger: Abort Execution (Exit 3)
|
|
101
|
-
break Fatal Security or Base Error
|
|
102
|
-
Orc->>Git: Teardown Worktree Sandbox
|
|
103
|
-
end
|
|
104
|
-
else Static Scope & Base Passed
|
|
105
|
-
Gate->>Gate: Classify Risk Tier (R0 Cosmetic, R1 Routine, R2 Consequential, R3 Restricted)
|
|
106
|
-
Gate->>+Git: Run Integrity Checks (Asset Magic-Bytes, Rules Budget, testCmd & buildCmd)
|
|
107
|
-
Git-->>-Gate: Execution & Integrity Proof (stdout/stderr)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
alt 100% Verification Suite & Integrity Passed
|
|
111
|
-
Gate-->>-Orc: Verification Success + Risk Tier Metadata
|
|
112
|
-
Orc->>+Git: Commit & Push to Remote Branch / PR
|
|
113
|
-
Git-->>-Orc: PR Ready (Attached Execution Receipts)
|
|
114
|
-
Orc->>Orc: Record Telemetry (metrics.jsonl)
|
|
115
|
-
Orc-->>Trigger: Dispatch Succeeded (Exit 0)
|
|
116
|
-
break Task Completed
|
|
117
|
-
Orc->>Git: Teardown Worktree Sandbox
|
|
118
|
-
end
|
|
119
|
-
else Verification Failed
|
|
120
|
-
Gate-->>Orc: Verification Failed (Stderr Trace output)
|
|
121
|
-
Orc->>Orc: Record Failure Telemetry
|
|
122
|
-
|
|
123
|
-
alt Retries Remaining (< 3)
|
|
124
|
-
Orc->>Orc: Construct Repair Prompt with Stderr Trace
|
|
125
|
-
else Max Retries Exceeded (3/3)
|
|
126
|
-
Orc-->>-Trigger: Abort & Log Diagnostic Feedback (Exit 4)
|
|
127
|
-
Orc->>Git: Teardown Worktree Sandbox
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
end
|
|
30
|
+
# 4. Start stdio MCP Server (for Cursor, Claude Code, or AGY integration)
|
|
31
|
+
npx agentctl mcp
|
|
131
32
|
```
|
|
132
33
|
|
|
133
|
-
> 💡 **Core Architectural Invariants**:
|
|
134
|
-
> - **Task Envelope Premise Validation**: Pre-flight checks verify referenced paths and base freshness before dispatching tasks to prevent session burnout.
|
|
135
|
-
> - **Risk Tier Classification Engine (R0–R3)**: Classifies changes by blast radius to route auto-merge vs mandatory human review.
|
|
136
|
-
> - **Zero-Trust Base-Branch Security**: Security rules (`forbidden_paths`) are fetched exclusively from `origin/main` (never untrusted PR branches).
|
|
137
|
-
> - **Asset & Rules Integrity**: Guards binary assets against HTML corruption and enforces character/line budgets on rule files.
|
|
138
|
-
> - **Automatic PII & Secret Redaction**: Outbound task prompts are automatically sanitized to redact API secrets and mask sensitive PII (emails, IPs, phone numbers).
|
|
139
|
-
> - **Ledger Hash-Chain Integrity**: Hashing over JSONL event streams detects unauthorized log tampering or record deletions.
|
|
140
|
-
>
|
|
141
|
-
> 🔍 For a deep dive into the execution protocol, see the [Architecture & Pipeline Flow](docs/architecture.md).
|
|
142
|
-
|
|
143
34
|
---
|
|
144
35
|
|
|
145
|
-
##
|
|
146
|
-
|
|
147
|
-
The orchestrator automatically detects your tech stack, but you can edit `.agent/jules.yml` for fine-grained control:
|
|
148
|
-
|
|
149
|
-
```yaml
|
|
150
|
-
# Google Jules Repository Configuration (Version 2)
|
|
151
|
-
version: 2
|
|
152
|
-
test_cmd: "npm test"
|
|
153
|
-
build_cmd: "npm run build"
|
|
154
|
-
forbidden_paths:
|
|
155
|
-
- ".github/**"
|
|
156
|
-
- "**/secrets/**"
|
|
157
|
-
- "**/*.pem"
|
|
158
|
-
- "**/lock-manager/**"
|
|
159
|
-
- "scripts/jules-*"
|
|
160
|
-
- ".agent/jules.yml"
|
|
161
|
-
allow_paths: []
|
|
162
|
-
```
|
|
36
|
+
## 🚀 Built for High-Volume Jules Swarms
|
|
163
37
|
|
|
164
|
-
|
|
38
|
+
`jules-orchestrator-kit` provides the production-hardened control plane needed to execute parallel Google Jules agent swarms safely:
|
|
165
39
|
|
|
166
|
-
|
|
40
|
+
- **Parallel Agent Swarms**: Execute multi-agent task batches concurrently with deterministic lock management and automatic collision prevention.
|
|
41
|
+
- **Self-Healing OODA Loop**: Automatic test/build verification and repair loop with sliding-window thrash detection to halt non-convergent agent loops ($A \rightarrow B \rightarrow A \rightarrow B$) and preserve API token budgets.
|
|
42
|
+
- **4-Phase Security Gatekeeper**: Fails closed on untrusted PRs by verifying Scope (`forbidden_paths`), Diff Payload Size, Secret Entropy (> 3.6 bits), and Trusted Build/Test Execution.
|
|
167
43
|
|
|
168
|
-
|
|
44
|
+
---
|
|
169
45
|
|
|
170
|
-
|
|
171
|
-
* **SaaS APIs & Tooling:** Context 7, Linear, and v0.
|
|
172
|
-
* **Databases & Cloud:** Render, Neon, Supabase, Stitch.
|
|
173
|
-
* **Framework Documentation:** Astro Docs, Cloudflare Docs, Next.js Docs.
|
|
46
|
+
## ⚙️ Core Technical Architecture
|
|
174
47
|
|
|
175
|
-
|
|
48
|
+
Built strictly on native Node.js 18+ ESM with **Zero External Runtime Dependencies** (`"node": ">=18.0.0"`):
|
|
176
49
|
|
|
177
|
-
|
|
50
|
+
- **Linearizable VFS Mutex (`src/state.mjs`)**: Kernel-level directory mutex (`withVfsMutex`) guaranteeing serial linearizability for SHA-256 hash-chained session ledgers under high concurrency.
|
|
51
|
+
- **PID Recycling & Stale Lock Protection (`src/state.mjs`)**: Linux `/proc/<pid>/stat` launch-time validation prevents false-positive lock reaps from recycled OS process IDs.
|
|
52
|
+
- **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.
|
|
53
|
+
- **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.
|
|
54
|
+
- **TOCTOU & Symlink Defense (`src/security.mjs`)**: `safeAtomicWrite()` uses `O_CREAT | O_EXCL | O_WRONLY` temp files with `fsyncSync` + `renameSync` and `lstatSync`/`realpathSync` symlink checks.
|
|
55
|
+
- **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()`).
|
|
178
56
|
|
|
179
|
-
|
|
57
|
+
---
|
|
180
58
|
|
|
181
|
-
|
|
182
|
-
When `JULES_API_KEY` and `JULES_REPO` are present in your environment, payloads are dispatched directly to the official Google Jules REST API endpoint. Handles HTTP 429 rate limits gracefully.
|
|
59
|
+
## 🛠️ CLI Command Reference (`agentctl`)
|
|
183
60
|
|
|
184
|
-
|
|
185
|
-
|
|
61
|
+
| Command | Usage | Description |
|
|
62
|
+
| :--- | :--- | :--- |
|
|
63
|
+
| `agentctl dispatch` | `agentctl dispatch --title "..." --prompt "..."` | Dispatches an autonomous task to Jules |
|
|
64
|
+
| `agentctl gate` | `agentctl gate [--fix] [--base main]` | Runs 4-Phase Safety Gatekeeper against workspace |
|
|
65
|
+
| `agentctl queue` | `agentctl queue` | Processes pending task queue from `.agent/jules-queue/` |
|
|
66
|
+
| `agentctl swarm` | `agentctl swarm` | Executes parallel swarm task queue |
|
|
67
|
+
| `agentctl merge-swarm` | `agentctl merge-swarm` | Performs 3-way structural merge on completed swarm PRs |
|
|
68
|
+
| `agentctl mcp` | `agentctl mcp` | Starts stdio Model Context Protocol (MCP) server |
|
|
69
|
+
| `agentctl doctor` | `agentctl doctor` | Verifies stack configuration, environment, and budget |
|
|
186
70
|
|
|
187
|
-
|
|
188
|
-
Downstream Node.js tools, MCP servers, and LLM orchestrators can import kit functions directly:
|
|
189
|
-
```js
|
|
190
|
-
import { gate, dispatch, validateEnvelope, classifyRiskTier, checkAssetIntegrity, checkRulesBudget, redactSecrets } from "jules-orchestrator-kit";
|
|
71
|
+
---
|
|
191
72
|
|
|
192
|
-
|
|
193
|
-
const cleanPrompt = anonymizePii("Contact support at john@example.com");
|
|
73
|
+
## 📋 Exit Code Protocol
|
|
194
74
|
|
|
195
|
-
|
|
196
|
-
await dispatch({ title: "Refactor Auth", prompt: cleanPrompt });
|
|
75
|
+
Standardized exit codes enforced across all CLI utilities:
|
|
197
76
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
77
|
+
| Code | Status | Description |
|
|
78
|
+
| :---: | :--- | :--- |
|
|
79
|
+
| `0` | **Success** | Task completed cleanly; verification passed 100%. |
|
|
80
|
+
| `1` | **Arg / Pre-Dispatch Failure** | Invalid arguments, prompt > 50 KB, or pre-dispatch error. |
|
|
81
|
+
| `2` | **API / Network Failure** | Jules API rate-limit (429), `FAILED_PRECONDITION`, or timeout. |
|
|
82
|
+
| `3` | **Scope Violation** | Attempted modification of protected/forbidden files. |
|
|
83
|
+
| `4` | **OODA Exhausted / Regression** | Verification failed after max repair attempts or thrash loop. |
|
|
84
|
+
| `5` | **Diff Payload Exceeded** | Git diff exceeds payload budget (`limits.diffKb`, default 75 KB). |
|
|
85
|
+
| `6` | **Secret Detected** | High-confidence secret or token detected in patch diff. |
|
|
86
|
+
| `7` | **Budget Exhausted** | Daily task session quota limit reached (`limits.dailyTasks`, default 300). |
|
|
201
87
|
|
|
202
88
|
---
|
|
203
89
|
|
|
204
|
-
##
|
|
205
|
-
|
|
206
|
-
**Code Suggestions (Web UI Only)**
|
|
207
|
-
Currently, there is no way to automatically extract "Suggestions" (the inline code review comments Jules sometimes proposes instead of direct commits) via the CLI or API. Suggestions can only be read directly inside the **Jules Web UI**.
|
|
90
|
+
## 🤝 Join the Community & Field-Testing
|
|
208
91
|
|
|
209
|
-
|
|
92
|
+
We are actively field-testing `v0.20.0` across 300+ daily autonomous sessions and opening the kit to the Google Jules developer community for feedback and contributions!
|
|
210
93
|
|
|
211
|
-
|
|
94
|
+
- **Test & Benchmark**: Clone the repository, test your edge cases, and run parallel swarms against your codebases.
|
|
95
|
+
- **Report Issues**: Found a bug, state race condition, or edge-case failure? Open an issue on GitHub.
|
|
96
|
+
- **Submit PRs**: We welcome contributions! Ensure all additions preserve our Zero Runtime Dependency invariant and pass `npm test` & `npm run lint`.
|
|
212
97
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
| Stack / Ecosystem | Manifest / Workspace File | Test Command | Build Command |
|
|
216
|
-
| --------------------------- | ------------------------------------- | ---------------------------------------- | ---------------------------------------- |
|
|
217
|
-
| **Turborepo** | `turbo.json` | `npx turbo run test --filter=...` | `npx turbo run build --filter=...` |
|
|
218
|
-
| **pnpm Workspace** | `pnpm-workspace.yaml` | `pnpm --filter=... test` | `pnpm --filter=... build` |
|
|
219
|
-
| **Nx Workspace** | `nx.json` | `npx nx run-many -t test -p ...` | `npx nx run-many -t build -p ...` |
|
|
220
|
-
| **Bun** | `bunfig.toml` / `bun.lockb` | `bun test` | `bun run build` |
|
|
221
|
-
| **Deno** | `deno.json` / `deno.jsonc` | `deno test` | `deno task build` |
|
|
222
|
-
| **JavaScript / TypeScript** | `package.json` | `npm test` | `npm run build` |
|
|
223
|
-
| **Rust** | `Cargo.toml` | `cargo test --workspace` | `cargo build` |
|
|
224
|
-
| **Go** | `go.mod` | `go test ./...` | `go build ./...` |
|
|
225
|
-
| **Python** | `pyproject.toml` / `requirements.txt` | `pytest` | *(none)* |
|
|
226
|
-
| **Elixir** | `mix.exs` | `mix test` | `mix compile` |
|
|
227
|
-
| **Ruby** | `Gemfile` | `bundle exec rake test` | *(none)* |
|
|
228
|
-
| **Swift** | `Package.swift` | `swift test` | `swift build` |
|
|
229
|
-
| **Java (Maven/Gradle)** | `pom.xml` / `build.gradle` | `mvn test` / `./gradlew test` | `mvn compile` / `./gradlew assemble` |
|
|
230
|
-
| **C / C++** | `Makefile` | `make test` | `make build` |
|
|
98
|
+
### Running Tests Locally
|
|
231
99
|
|
|
232
|
-
|
|
100
|
+
```bash
|
|
101
|
+
# Clone the repository
|
|
102
|
+
git clone https://github.com/FullThrottle83/jules-orchestrator-kit.git
|
|
103
|
+
cd jules-orchestrator-kit
|
|
233
104
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
| Command | Description |
|
|
240
|
-
| ------- | ----------- |
|
|
241
|
-
| `npm run init` | Initializes the orchestrator and `.agent/` directory |
|
|
242
|
-
| `npm run test` | Runs the orchestrator kit's own unit tests |
|
|
243
|
-
| `npm run jules:dispatch` | Dispatches a single task directly to Jules |
|
|
244
|
-
| `npm run jules:queue` | Runs the local queue processor (picks up tasks from `.agent/jules-queue`) |
|
|
245
|
-
| `npm run jules:create` | Scaffolds a new boilerplate task markdown file |
|
|
246
|
-
| `npm run jules:status` | Shows real-time 3-bucket status (Action Required, In Progress, Completed) |
|
|
247
|
-
| `npm run jules:audit` | Runs the self-audit gatekeeper (verifies tests, forbidden paths, and scope) |
|
|
248
|
-
| `npm run jules:cleanup` | Audits and closes merged or stale REST sessions |
|
|
249
|
-
| `npm run jules:scan` | Scans the codebase for TODO/FIXME comments and generates a suggested tasks file |
|
|
250
|
-
| `npm run jules:swarm` | Launches a multi-agent swarm in parallel across isolated worktrees |
|
|
251
|
-
| `npm run jules:merge-swarm` | Autonomous PR merge engine with Safety Gate lock verification |
|
|
252
|
-
| `npm run jules:nightly` | Nightly maintenance job (usually triggered in CI) |
|
|
253
|
-
|
|
254
|
-
### Specialist Agent Prompts & Templates (`.agent/prompts/`)
|
|
255
|
-
The kit includes pre-configured single-responsibility prompt presets in `.agent/prompts/`:
|
|
256
|
-
- **`Overseer.md`**: Codebase architecture auditor & technical debt mapper.
|
|
257
|
-
- **`Bolt.md`**: Performance micro-optimizer and payload governor (enforces < 75 KB payload diff limits).
|
|
258
|
-
- **`Sentinel.md`**: Security audit specialist for input sanitization and secret scanning.
|
|
259
|
-
- **`Task_Template.md`**: Machine-readable master task prompt template schema.
|
|
260
|
-
|
|
261
|
-
### Environment Variables
|
|
262
|
-
|
|
263
|
-
| Variable | Description |
|
|
264
|
-
| -------- | ----------- |
|
|
265
|
-
| `JULES_API_KEY` | Your Google Jules REST API key (required for API mode) |
|
|
266
|
-
| `GEMINI_API_KEY` | Alias for `JULES_API_KEY` (fallback) |
|
|
267
|
-
| `JULES_API_URL` | Override the Jules REST API URL |
|
|
268
|
-
| `JULES_REPO` | Target GitHub Repository (Format: `owner/repo`) |
|
|
269
|
-
| `JULES_REPOLESS` | Set to `true` or `1` to run in repoless/serverless mode |
|
|
270
|
-
| `JULES_DRY_RUN` | Set to `true` or `1` to simulate dispatching without making API calls |
|
|
271
|
-
| `JULES_DAILY_BUDGET` | Daily max session budget for autonomous dispatches (Default: `300`) |
|
|
272
|
-
| `JULES_MAX_DIFF_KB` | Maximum git diff payload size in KB before aborting with Exit Code 5 (Default: `50`) |
|
|
273
|
-
| `JULES_ALLOW_COMMAND_FILE_CHANGES` | Set to `true` to allow PR changes to command/config files like `package.json`, `tsconfig.json`, `vite.config.ts` (Default: `false`) |
|
|
274
|
-
| `JULES_ALLOW_AGENT_RULE_CHANGES` | Set to `true` to allow PR changes to agent rule files like `AGENTS.md`, `JULES_RULES_TEMPLATE.md` (Default: `false`) |
|
|
275
|
-
| `BASE_BRANCH` | Base branch for PR Audits & Merge-Base calculations (Default: `main`) |
|
|
276
|
-
| `GITHUB_HEAD_REF` | PR Head Branch (Used dynamically by CI during OODA repair) |
|
|
277
|
-
| `JULES_PROJECT_ROOT` | Root directory of the project (Auto-assigned during swarm executions) |
|
|
278
|
-
| `JULES_SWARM_CONCURRENCY` | Maximum parallel dispatches for swarm runs (Default: `3`) |
|
|
279
|
-
| `JULES_SWARM_STAGGER_MS` | Dispatch stagger interval in milliseconds (Default: `1500`) |
|
|
280
|
-
| `JULES_PACE_MS` | Rate-limit for the `jules:queue` command (Default: `500`) |
|
|
281
|
-
| `JULES_USE_WORKTREES` | Use Git Worktrees instead of cloning for swarm isolation (Default: `false`) |
|
|
282
|
-
| `JULES_SLOT_INDEX` | Current slot index for partitioning tasks (Swarm mode) |
|
|
283
|
-
| `JULES_SLOT_TOTAL` | Total number of slots for partitioning tasks (Swarm mode) |
|
|
284
|
-
| `CI` | Set to `true` to change log output and fail-fast behaviors for CI environments |
|
|
285
|
-
| `ALLOW_AUTO_REPAIR` | Set to `true` to allow OODA Auto-Repair even when running in CI |
|
|
286
|
-
| `GITHUB_STEP_SUMMARY` | GitHub Actions Step Summary File Path |
|
|
287
|
-
| `NO_COLOR` | Set to `true` to disable ANSI color output |
|
|
288
|
-
|
|
289
|
-
> [!NOTE]
|
|
290
|
-
> **Scope Enforcement via `allow_paths`**
|
|
291
|
-
> In `.agent/jules.yml`, defining `allow_paths` acts as a strict allowlist (deny-by-default). When non-empty, any file modified outside `allow_paths` will trigger a security violation (Exit Code 3). `forbidden_paths` always take absolute precedence and cannot be overridden.
|
|
292
|
-
|
|
293
|
-
### Exit Codes
|
|
294
|
-
The Gatekeeper (`jules-self-audit.mjs` and related scripts) uses standard exit codes to signal status to CI systems.
|
|
295
|
-
|
|
296
|
-
| Code | Meaning | Action Taken |
|
|
297
|
-
| ---- | ------- | ------------ |
|
|
298
|
-
| `0` | **Success** | All tests and security checks passed. |
|
|
299
|
-
| `1` | **Pre-Dispatch / Arg Error** | Missing dependencies, prompt > 50 KB, syntax error, or pre-dispatch failure. |
|
|
300
|
-
| `2` | **API / Network / Quota Error** | REST API HTTP 429 rate limit, HTTP 400 `FAILED_PRECONDITION` quota (~30 active limit), or connection timeout. |
|
|
301
|
-
| `3` | **Security / Scope Violation** | Modified file breached `forbidden_paths` or changed command-defining files (`package.json`, `Cargo.toml`). Fails closed immediately. |
|
|
302
|
-
| `4` | **Verification Exhausted** | Tests failed and the OODA Auto-Repair loop either exhausted its max retries (3) or is disabled. |
|
|
303
|
-
| `5` | **Diff Payload Limit** | Diff payload size exceeded payload governor budget (`JULES_MAX_DIFF_KB`, default 50 KB). Split task. |
|
|
304
|
-
| `6` | **Secret Leak Prevented** | High-confidence secret or private key pattern detected in diff. Aborted immediately. |
|
|
305
|
-
| `7` | **Budget Exhausted** | Daily session budget limit reached or budget state locked. |
|
|
105
|
+
# Run ESLint & node unit test suite (100% zero external runtime deps)
|
|
106
|
+
npm run lint
|
|
107
|
+
npm test
|
|
108
|
+
```
|
|
306
109
|
|
|
307
110
|
---
|
|
308
111
|
|
|
309
|
-
##
|
|
310
|
-
We welcome contributions! Please follow these core principles:
|
|
311
|
-
1. **Zero Runtime Dependencies**: Use ONLY native Node.js built-in modules (`node:fs`, `node:path`, `node:child_process`, `node:crypto`, `node:util`).
|
|
312
|
-
2. **Verification Suite**: Ensure 100% of unit tests pass cleanly (`npm test`).
|
|
313
|
-
3. **Conventional Commits**: Use standardized prefixes (`feat:`, `fix:`, `docs:`, `test:`, `chore:`).
|
|
314
|
-
4. **Cross-Platform Compatibility**: Normalize Windows backslashes (`\`) to POSIX slashes (`/`) for glob patterns and paths.
|
|
315
|
-
|
|
316
|
-
Please read our [Code of Conduct](CODE_OF_CONDUCT.md) before participating.
|
|
112
|
+
## 📄 License
|
|
317
113
|
|
|
318
|
-
|
|
319
|
-
MIT License - feel free to use, modify, and share!
|
|
114
|
+
Distributed under the [MIT License](LICENSE).
|
package/bin/agentctl.mjs
CHANGED
|
@@ -13,7 +13,7 @@ const command = args[0];
|
|
|
13
13
|
|
|
14
14
|
function printHelp() {
|
|
15
15
|
console.log(`
|
|
16
|
-
🚀 agentctl v0.
|
|
16
|
+
🚀 agentctl v0.20.0 — Universal Agent Orchestrator & Safety Gatekeeper
|
|
17
17
|
|
|
18
18
|
Usage: agentctl <command> [options]
|
|
19
19
|
|
package/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* security auditing, repo gating, and state operations.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export { loadConfig, detectStack, resolveVerify, resolveRoot, normalizePath } from "./src/config.mjs";
|
|
8
|
+
export { loadConfig, detectStack, resolveVerify, resolveRoot, normalizePath, TIER_PRESETS } from "./src/config.mjs";
|
|
9
9
|
export {
|
|
10
10
|
shannonEntropy,
|
|
11
11
|
redactSecrets,
|
|
@@ -19,14 +19,23 @@ export { createProvider, JULES_PRESET, CLAUDE_PRESET, CODEX_PRESET } from "./src
|
|
|
19
19
|
export {
|
|
20
20
|
appendLedger,
|
|
21
21
|
readLedger,
|
|
22
|
+
verifyLedgerIntegrity,
|
|
23
|
+
reserveBudget,
|
|
24
|
+
commitBudgetReservation,
|
|
22
25
|
withBudget,
|
|
23
26
|
checkDailyBudget,
|
|
24
27
|
acquireLock,
|
|
25
28
|
releaseLock,
|
|
26
29
|
lockStatus,
|
|
27
30
|
} from "./src/state.mjs";
|
|
28
|
-
export { gate, dispatch, repair, run } from "./src/engine.mjs";
|
|
31
|
+
export { gate, dispatch, repair, run, fingerprintFailureState } from "./src/engine.mjs";
|
|
29
32
|
export { validateEnvelope } from "./src/envelope.mjs";
|
|
33
|
+
export {
|
|
34
|
+
createExecutionEnvelope,
|
|
35
|
+
verifyExecutionEnvelope,
|
|
36
|
+
freezeExecutionEnvelope,
|
|
37
|
+
hashExecutionEnvelope,
|
|
38
|
+
} from "./src/execution_envelope.mjs";
|
|
30
39
|
export { checkAssetIntegrity } from "./src/asset_integrity.mjs";
|
|
31
40
|
export { classifyRiskTier, RISK_TIERS } from "./src/risk.mjs";
|
|
32
41
|
export { checkRulesBudget } from "./src/rules_budget.mjs";
|
package/package.json
CHANGED
|
@@ -1,30 +1,198 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Swarm branch merger with 3-way structural JSON/object merge, git merge-file execution
|
|
5
|
+
* isolated in os.tmpdir(), and safety gate checking.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
import { existsSync, readdirSync,
|
|
8
|
+
import { writeFileSync, readFileSync, existsSync, readdirSync, rmSync, mkdtempSync } from "node:fs";
|
|
8
9
|
import { join } from "node:path";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
12
|
+
import { classifyRiskTier, RISK_TIERS } from "../src/risk.mjs";
|
|
13
|
+
import { changedFiles } from "../src/git.mjs";
|
|
9
14
|
|
|
15
|
+
export const EXIT = Object.freeze({
|
|
16
|
+
SUCCESS: 0,
|
|
17
|
+
R3_RESTRICTED: 3,
|
|
18
|
+
MERGE_CONFLICT: 4,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const BLOCKED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
22
|
+
|
|
23
|
+
function isPlainObject(v) {
|
|
24
|
+
return (
|
|
25
|
+
v !== null &&
|
|
26
|
+
typeof v === "object" &&
|
|
27
|
+
!Array.isArray(v) &&
|
|
28
|
+
(Object.getPrototypeOf(v) === null || Object.getPrototypeOf(v) === Object.prototype)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function deepClone(v) {
|
|
33
|
+
if (v === undefined) return undefined;
|
|
34
|
+
return JSON.parse(JSON.stringify(v));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function stableStringify(v) {
|
|
38
|
+
if (v === null || typeof v !== "object") return JSON.stringify(v);
|
|
39
|
+
if (Array.isArray(v)) return `[${v.map(stableStringify).join(",")}]`;
|
|
40
|
+
const keys = Object.keys(v).filter((k) => !BLOCKED_KEYS.has(k)).sort();
|
|
41
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(v[k])}`).join(",")}}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function deepEqual(a, b) {
|
|
45
|
+
return stableStringify(a) === stableStringify(b);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Recursive 3-way structural object & array merge.
|
|
50
|
+
*/
|
|
51
|
+
export function deepMerge3Way(base, ours, theirs, path = "$") {
|
|
52
|
+
const conflicts = [];
|
|
53
|
+
if (deepEqual(ours, theirs)) return { merged: deepClone(ours), conflicts };
|
|
54
|
+
if (deepEqual(ours, base)) return { merged: deepClone(theirs), conflicts };
|
|
55
|
+
if (deepEqual(theirs, base)) return { merged: deepClone(ours), conflicts };
|
|
56
|
+
|
|
57
|
+
if (isPlainObject(base) && isPlainObject(ours) && isPlainObject(theirs)) {
|
|
58
|
+
const merged = {};
|
|
59
|
+
const safeBase = base || {};
|
|
60
|
+
const safeOurs = ours || {};
|
|
61
|
+
const safeTheirs = theirs || {};
|
|
62
|
+
const keys = new Set([
|
|
63
|
+
...Object.keys(safeBase),
|
|
64
|
+
...Object.keys(safeOurs),
|
|
65
|
+
...Object.keys(safeTheirs),
|
|
66
|
+
]);
|
|
67
|
+
for (const k of keys) {
|
|
68
|
+
if (BLOCKED_KEYS.has(k)) continue;
|
|
69
|
+
const childPath = `${path}.${k}`;
|
|
70
|
+
const b = safeBase[k], o = safeOurs[k], t = safeTheirs[k];
|
|
71
|
+
if (o === undefined && t === undefined) continue;
|
|
72
|
+
if (o === undefined) { merged[k] = deepClone(t); continue; }
|
|
73
|
+
if (t === undefined) { merged[k] = deepClone(o); continue; }
|
|
74
|
+
const sub = deepMerge3Way(b, o, t, childPath);
|
|
75
|
+
merged[k] = sub.merged;
|
|
76
|
+
conflicts.push(...sub.conflicts);
|
|
77
|
+
}
|
|
78
|
+
return { merged, conflicts };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (Array.isArray(ours) && Array.isArray(theirs)) {
|
|
82
|
+
const baseArr = Array.isArray(base) ? base : [];
|
|
83
|
+
const minLen = Math.min(ours.length, theirs.length);
|
|
84
|
+
const merged = [];
|
|
85
|
+
for (let i = 0; i < minLen; i++) {
|
|
86
|
+
const sub = deepMerge3Way(baseArr[i], ours[i], theirs[i], `${path}[${i}]`);
|
|
87
|
+
merged.push(sub.merged);
|
|
88
|
+
conflicts.push(...sub.conflicts);
|
|
89
|
+
}
|
|
90
|
+
if (ours.length > minLen && theirs.length <= minLen) {
|
|
91
|
+
merged.push(...ours.slice(minLen).map(deepClone));
|
|
92
|
+
} else if (theirs.length > minLen && ours.length <= minLen) {
|
|
93
|
+
merged.push(...theirs.slice(minLen).map(deepClone));
|
|
94
|
+
} else if (ours.length > minLen && theirs.length > minLen) {
|
|
95
|
+
conflicts.push({
|
|
96
|
+
path: `${path}[tail]`,
|
|
97
|
+
base: baseArr.slice(minLen),
|
|
98
|
+
ours: ours.slice(minLen),
|
|
99
|
+
theirs: theirs.slice(minLen),
|
|
100
|
+
});
|
|
101
|
+
merged.push(...ours.slice(minLen).map(deepClone));
|
|
102
|
+
}
|
|
103
|
+
return { merged, conflicts };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
conflicts.push({ path, base: deepClone(base), ours: deepClone(ours), theirs: deepClone(theirs) });
|
|
107
|
+
return { merged: deepClone(ours), conflicts };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Executes a function inside a temporary directory in os.tmpdir() with guaranteed cleanup.
|
|
112
|
+
*/
|
|
113
|
+
export function withTempDir(prefix, fn) {
|
|
114
|
+
const dir = mkdtempSync(join(tmpdir(), prefix || "jules-merge-"));
|
|
115
|
+
try {
|
|
116
|
+
return fn(dir);
|
|
117
|
+
} finally {
|
|
118
|
+
try {
|
|
119
|
+
rmSync(dir, { recursive: true, force: true });
|
|
120
|
+
} catch (_) {}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Attempts a 3-way text file merge using git merge-file inside os.tmpdir().
|
|
126
|
+
*/
|
|
127
|
+
export function attemptCodeMergeFile(repoRoot, relPath, oursContent, baseContent, theirsContent) {
|
|
128
|
+
return withTempDir("jules-merge-", (tmp) => {
|
|
129
|
+
const baseFile = join(tmp, "base");
|
|
130
|
+
const oursFile = join(tmp, "ours");
|
|
131
|
+
const theirsFile = join(tmp, "theirs");
|
|
132
|
+
writeFileSync(baseFile, baseContent ?? "", "utf-8");
|
|
133
|
+
writeFileSync(oursFile, oursContent ?? "", "utf-8");
|
|
134
|
+
writeFileSync(theirsFile, theirsContent ?? "", "utf-8");
|
|
135
|
+
|
|
136
|
+
const res = spawnSync("git", ["merge-file", "-p", "--diff3", oursFile, baseFile, theirsFile], {
|
|
137
|
+
encoding: "utf-8",
|
|
138
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (res.error) {
|
|
142
|
+
return { ok: false, conflicts: 0, error: res.error.message };
|
|
143
|
+
}
|
|
144
|
+
const conflictCount = res.status && res.status > 0 ? res.status : 0;
|
|
145
|
+
const merged = res.stdout ?? "";
|
|
146
|
+
return {
|
|
147
|
+
ok: conflictCount === 0,
|
|
148
|
+
merged,
|
|
149
|
+
conflicts: conflictCount,
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Checks safety gate against active worker locks and risk tiers.
|
|
156
|
+
*/
|
|
10
157
|
export function checkSafetyGate(branchName = "", projectRoot = process.cwd()) {
|
|
11
158
|
const locksDir = join(projectRoot, ".agent/state/locks");
|
|
12
|
-
if (
|
|
159
|
+
if (existsSync(locksDir)) {
|
|
160
|
+
try {
|
|
161
|
+
const files = readdirSync(locksDir);
|
|
162
|
+
for (const f of files) {
|
|
163
|
+
if (!f.endsWith(".json")) continue;
|
|
164
|
+
const content = readFileSync(join(locksDir, f), "utf-8");
|
|
165
|
+
const parsed = JSON.parse(content);
|
|
166
|
+
if (parsed.branch === branchName) {
|
|
167
|
+
return { safe: false, reason: `Active lock held by worker ${parsed.agent || "unknown"}` };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch (_) {}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check R3 risk tier if files modified
|
|
13
174
|
try {
|
|
14
|
-
const files =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (parsed.branch === branchName) {
|
|
20
|
-
return { safe: false, reason: `Active lock held by worker ${parsed.agent || "unknown"}` };
|
|
175
|
+
const files = changedFiles(projectRoot, process.env.BASE_BRANCH || "main");
|
|
176
|
+
if (files.length > 0) {
|
|
177
|
+
const tier = classifyRiskTier(files);
|
|
178
|
+
if (tier.tier === RISK_TIERS.R3) {
|
|
179
|
+
return { safe: false, reason: `R3 Restricted Path violation: ${tier.reason}` };
|
|
21
180
|
}
|
|
22
181
|
}
|
|
23
182
|
} catch (_) {}
|
|
183
|
+
|
|
24
184
|
return { safe: true };
|
|
25
185
|
}
|
|
26
186
|
|
|
27
187
|
if (process.argv[1] && process.argv[1].endsWith("jules-merge-swarm.mjs")) {
|
|
188
|
+
const branchName = process.argv[2] || "";
|
|
189
|
+
const gateResult = checkSafetyGate(branchName);
|
|
190
|
+
if (!gateResult.safe) {
|
|
191
|
+
console.error(`[merge-swarm] Safety gate check failed: ${gateResult.reason}`);
|
|
192
|
+
process.exit(EXIT.R3_RESTRICTED);
|
|
193
|
+
}
|
|
194
|
+
|
|
28
195
|
console.log("No open Jules PRs found.");
|
|
29
|
-
process.exit(
|
|
196
|
+
process.exit(EXIT.SUCCESS);
|
|
30
197
|
}
|
|
198
|
+
|