jules-orchestrator-kit 0.9.3 → 0.9.4
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 +29 -18
- package/bin/agentctl.mjs +9 -2
- package/bin/mcp-server.mjs +5 -0
- package/package.json +4 -2
- package/src/mcp.mjs +211 -0
- package/src/state.mjs +9 -1
package/README.md
CHANGED
|
@@ -58,6 +58,7 @@ sequenceDiagram
|
|
|
58
58
|
|
|
59
59
|
box "Control Plane" #E8F4F8
|
|
60
60
|
participant Orc as Orchestrator Core
|
|
61
|
+
participant Envelope as Task Envelope Validator
|
|
61
62
|
participant Gate as Self-Audit Gatekeeper
|
|
62
63
|
end
|
|
63
64
|
|
|
@@ -66,12 +67,19 @@ sequenceDiagram
|
|
|
66
67
|
participant Git as Git Worktree Sandbox
|
|
67
68
|
end
|
|
68
69
|
|
|
69
|
-
Trigger->>+Orc: Dispatch Task Payload
|
|
70
|
+
Trigger->>+Orc: Dispatch Task Payload / Envelope
|
|
70
71
|
|
|
71
|
-
note over Orc,
|
|
72
|
-
Orc
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
75
83
|
|
|
76
84
|
loop OODA Repair Cycle (Max 3 Retries)
|
|
77
85
|
note over Orc,Git: Phase 2: Agent Execution & Dispatch
|
|
@@ -80,28 +88,29 @@ sequenceDiagram
|
|
|
80
88
|
Git-->>-API: Changes Written
|
|
81
89
|
API-->>-Orc: Execution Complete
|
|
82
90
|
|
|
83
|
-
note over Orc,Gate: Phase 3:
|
|
91
|
+
note over Orc,Gate: Phase 3: Multi-Gate Verification & Risk Classification
|
|
84
92
|
Orc->>+Gate: Trigger Self-Audit (trusted origin/main rules)
|
|
85
93
|
|
|
86
|
-
Gate->>+Git: Scope Audit (`git diff
|
|
87
|
-
Git-->>-Gate: Diff Stats &
|
|
94
|
+
Gate->>+Git: Run Scope, Payload & Stale-Base Audit (`git diff`)
|
|
95
|
+
Git-->>-Gate: Diff Stats & Base Commit Age
|
|
88
96
|
|
|
89
|
-
alt Scope Breach
|
|
90
|
-
Gate-->>Orc: Security / Scope Violation
|
|
97
|
+
alt Scope Breach / Payload > 75 KB / Stale Base > 25 commits
|
|
98
|
+
Gate-->>Orc: Security / Scope / Stale-Base Violation
|
|
91
99
|
Orc->>Orc: Record Telemetry (metrics.jsonl)
|
|
92
100
|
Orc-->>Trigger: Abort Execution (Exit 3)
|
|
93
|
-
break Fatal Security Error
|
|
101
|
+
break Fatal Security or Base Error
|
|
94
102
|
Orc->>Git: Teardown Worktree Sandbox
|
|
95
103
|
end
|
|
96
|
-
else Scope
|
|
97
|
-
Gate
|
|
98
|
-
Git
|
|
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)
|
|
99
108
|
end
|
|
100
109
|
|
|
101
|
-
alt 100% Verification Suite Passed
|
|
102
|
-
Gate-->>-Orc: Verification Success
|
|
110
|
+
alt 100% Verification Suite & Integrity Passed
|
|
111
|
+
Gate-->>-Orc: Verification Success + Risk Tier Metadata
|
|
103
112
|
Orc->>+Git: Commit & Push to Remote Branch / PR
|
|
104
|
-
Git-->>-Orc: PR Ready
|
|
113
|
+
Git-->>-Orc: PR Ready (Attached Execution Receipts)
|
|
105
114
|
Orc->>Orc: Record Telemetry (metrics.jsonl)
|
|
106
115
|
Orc-->>Trigger: Dispatch Succeeded (Exit 0)
|
|
107
116
|
break Task Completed
|
|
@@ -122,10 +131,12 @@ sequenceDiagram
|
|
|
122
131
|
```
|
|
123
132
|
|
|
124
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.
|
|
125
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.
|
|
126
138
|
> - **Automatic PII & Secret Redaction**: Outbound task prompts are automatically sanitized to redact API secrets and mask sensitive PII (emails, IPs, phone numbers).
|
|
127
139
|
> - **Ledger Hash-Chain Integrity**: Hashing over JSONL event streams detects unauthorized log tampering or record deletions.
|
|
128
|
-
> - **Dynamic Command Resolution (`command-resolver.mjs`)**: Auto-detects workspace boundaries (Turborepo, pnpm, Nx, Cargo, pytest, npm).
|
|
129
140
|
>
|
|
130
141
|
> 🔍 For a deep dive into the execution protocol, see the [Architecture & Pipeline Flow](docs/architecture.md).
|
|
131
142
|
|
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.9.
|
|
16
|
+
🚀 agentctl v0.9.4 — Universal Agent Orchestrator & Safety Gatekeeper
|
|
17
17
|
|
|
18
18
|
Usage: agentctl <command> [options]
|
|
19
19
|
|
|
@@ -22,6 +22,7 @@ Commands:
|
|
|
22
22
|
gate | audit Run CI security and verification gate against current branch
|
|
23
23
|
queue Run pending task queue
|
|
24
24
|
swarm Run parallel task swarm
|
|
25
|
+
mcp Start stdio Model Context Protocol (MCP) server
|
|
25
26
|
clean Clean stale branches, worktrees, locks, and ledgers
|
|
26
27
|
lock <action> Manage mutex locks (acquire | release | status | cleanup)
|
|
27
28
|
doctor Run system diagnostics and stack resolution checks
|
|
@@ -42,7 +43,7 @@ async function main() {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
if (command === "version" || command === "--version" || command === "-v") {
|
|
45
|
-
console.log("agentctl v0.9.
|
|
46
|
+
console.log("agentctl v0.9.4");
|
|
46
47
|
process.exit(0);
|
|
47
48
|
}
|
|
48
49
|
|
|
@@ -261,6 +262,12 @@ base_branch: main
|
|
|
261
262
|
break;
|
|
262
263
|
}
|
|
263
264
|
|
|
265
|
+
case "mcp": {
|
|
266
|
+
const { startMcpServer } = await import("../src/mcp.mjs");
|
|
267
|
+
startMcpServer();
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
|
|
264
271
|
default:
|
|
265
272
|
console.error(`Unknown command: ${command}`);
|
|
266
273
|
printHelp();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jules-orchestrator-kit",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"description": "Orchestration kit for running Google Jules autonomous agents.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
"bin": {
|
|
15
15
|
"agentctl": "bin/agentctl.mjs",
|
|
16
16
|
"jules-init": "bin/init.js",
|
|
17
|
-
"jules-orchestrator-kit": "bin/agentctl.mjs"
|
|
17
|
+
"jules-orchestrator-kit": "bin/agentctl.mjs",
|
|
18
|
+
"jules-mcp": "bin/mcp-server.mjs",
|
|
19
|
+
"agentctl-mcp": "bin/mcp-server.mjs"
|
|
18
20
|
},
|
|
19
21
|
"main": "index.mjs",
|
|
20
22
|
"exports": {
|
package/src/mcp.mjs
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import { loadConfig, resolveRoot, detectStack } from "./config.mjs";
|
|
3
|
+
import { gate, dispatch } from "./engine.mjs";
|
|
4
|
+
import { classifyRiskTier } from "./risk.mjs";
|
|
5
|
+
import { checkDailyBudget, lockStatus } from "./state.mjs";
|
|
6
|
+
|
|
7
|
+
export const MCP_SERVER_INFO = {
|
|
8
|
+
name: "jules-orchestrator-kit",
|
|
9
|
+
version: "0.9.4",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const MCP_TOOLS = [
|
|
13
|
+
{
|
|
14
|
+
name: "dispatch_jules_task",
|
|
15
|
+
description: "Dispatch an autonomous task to the Jules AI agent provider with security bounds and budget check.",
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
title: { type: "string", description: "Short descriptive title for the task" },
|
|
20
|
+
prompt: { type: "string", description: "Detailed task instructions and prompt" },
|
|
21
|
+
},
|
|
22
|
+
required: ["prompt"],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "audit_jules_gate",
|
|
27
|
+
description: "Run the 4-phase safety gatekeeper (Scope, Payload, Secrets, Verify) against the current workspace branch.",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {
|
|
31
|
+
base: { type: "string", description: "Base branch to diff against (defaults to main)" },
|
|
32
|
+
fix: { type: "boolean", description: "Attempt OODA auto-repair loop on verification failure" },
|
|
33
|
+
allowProtected: { type: "boolean", description: "Allow edits to protected files" },
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "check_risk_tier",
|
|
39
|
+
description: "Classify changed files into Risk Tiers (R0 Cosmetic, R1 Routine, R2 Consequential, R3 Restricted).",
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: {
|
|
43
|
+
files: {
|
|
44
|
+
type: "array",
|
|
45
|
+
items: { type: "string" },
|
|
46
|
+
description: "List of file paths changed",
|
|
47
|
+
},
|
|
48
|
+
diffLines: { type: "number", description: "Total lines changed in diff" },
|
|
49
|
+
},
|
|
50
|
+
required: ["files"],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "get_jules_status",
|
|
55
|
+
description: "Retrieve orchestrator status including daily task budget, active locks, and stack diagnostics.",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
export async function handleMcpRequest(request, opts = {}) {
|
|
64
|
+
const root = opts.root || resolveRoot();
|
|
65
|
+
const config = opts.config || loadConfig(root);
|
|
66
|
+
const { id, method, params } = request;
|
|
67
|
+
|
|
68
|
+
if (method === "ping") {
|
|
69
|
+
return { jsonrpc: "2.0", id, result: {} };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (method === "initialize") {
|
|
73
|
+
return {
|
|
74
|
+
jsonrpc: "2.0",
|
|
75
|
+
id,
|
|
76
|
+
result: {
|
|
77
|
+
protocolVersion: "2024-11-05",
|
|
78
|
+
capabilities: { tools: {} },
|
|
79
|
+
serverInfo: MCP_SERVER_INFO,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (method === "notifications/initialized") {
|
|
85
|
+
return null; // Notifications produce no response
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (method === "tools/list") {
|
|
89
|
+
return {
|
|
90
|
+
jsonrpc: "2.0",
|
|
91
|
+
id,
|
|
92
|
+
result: { tools: MCP_TOOLS },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (method === "tools/call") {
|
|
97
|
+
const toolName = params?.name;
|
|
98
|
+
const args = params?.arguments || {};
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
if (toolName === "dispatch_jules_task") {
|
|
102
|
+
const session = await dispatch(
|
|
103
|
+
{ title: args.title || "MCP Task Dispatch", prompt: args.prompt },
|
|
104
|
+
{ root, config }
|
|
105
|
+
);
|
|
106
|
+
return {
|
|
107
|
+
jsonrpc: "2.0",
|
|
108
|
+
id,
|
|
109
|
+
result: {
|
|
110
|
+
content: [{ type: "text", text: JSON.stringify({ ok: true, session }, null, 2) }],
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (toolName === "audit_jules_gate") {
|
|
116
|
+
const res = await gate({
|
|
117
|
+
root,
|
|
118
|
+
config,
|
|
119
|
+
base: args.base || config.baseBranch || "main",
|
|
120
|
+
fix: args.fix || false,
|
|
121
|
+
allowProtected: args.allowProtected || false,
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
jsonrpc: "2.0",
|
|
125
|
+
id,
|
|
126
|
+
result: {
|
|
127
|
+
content: [{ type: "text", text: JSON.stringify(res, null, 2) }],
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (toolName === "check_risk_tier") {
|
|
133
|
+
const tierResult = classifyRiskTier(args.files || [], { diffLines: args.diffLines || 0 });
|
|
134
|
+
return {
|
|
135
|
+
jsonrpc: "2.0",
|
|
136
|
+
id,
|
|
137
|
+
result: {
|
|
138
|
+
content: [{ type: "text", text: JSON.stringify(tierResult, null, 2) }],
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (toolName === "get_jules_status") {
|
|
144
|
+
const stackInfo = detectStack(root);
|
|
145
|
+
const budget = checkDailyBudget(root, config.limits.dailyTasks);
|
|
146
|
+
const locks = lockStatus(root);
|
|
147
|
+
const status = {
|
|
148
|
+
version: MCP_SERVER_INFO.version,
|
|
149
|
+
root,
|
|
150
|
+
stack: stackInfo.stack,
|
|
151
|
+
budget: { used: budget.used, limit: budget.budget, remaining: budget.budget - budget.used },
|
|
152
|
+
activeLocksCount: locks.length,
|
|
153
|
+
locks,
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
jsonrpc: "2.0",
|
|
157
|
+
id,
|
|
158
|
+
result: {
|
|
159
|
+
content: [{ type: "text", text: JSON.stringify(status, null, 2) }],
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
jsonrpc: "2.0",
|
|
166
|
+
id,
|
|
167
|
+
error: { code: -32601, message: `Unknown tool: ${toolName}` },
|
|
168
|
+
};
|
|
169
|
+
} catch (err) {
|
|
170
|
+
return {
|
|
171
|
+
jsonrpc: "2.0",
|
|
172
|
+
id,
|
|
173
|
+
result: {
|
|
174
|
+
content: [{ type: "text", text: JSON.stringify({ ok: false, error: err.message }, null, 2) }],
|
|
175
|
+
isError: true,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
jsonrpc: "2.0",
|
|
183
|
+
id,
|
|
184
|
+
error: { code: -32601, message: `Method not found: ${method}` },
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function startMcpServer(input = process.stdin, output = process.stdout, opts = {}) {
|
|
189
|
+
const rl = createInterface({ input, output: null, terminal: false });
|
|
190
|
+
|
|
191
|
+
rl.on("line", async (line) => {
|
|
192
|
+
const trimmed = line.trim();
|
|
193
|
+
if (!trimmed) return;
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const request = JSON.parse(trimmed);
|
|
197
|
+
const response = await handleMcpRequest(request, opts);
|
|
198
|
+
if (response !== null) {
|
|
199
|
+
output.write(JSON.stringify(response) + "\n");
|
|
200
|
+
}
|
|
201
|
+
} catch (err) {
|
|
202
|
+
output.write(
|
|
203
|
+
JSON.stringify({
|
|
204
|
+
jsonrpc: "2.0",
|
|
205
|
+
id: null,
|
|
206
|
+
error: { code: -32700, message: `Parse error: ${err.message}` },
|
|
207
|
+
}) + "\n"
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
package/src/state.mjs
CHANGED
|
@@ -98,10 +98,18 @@ export function checkDailyBudget(arg1 = resolveRoot(), arg2 = 300) {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
export class BudgetError extends Error {
|
|
102
|
+
constructor(message) {
|
|
103
|
+
super(message);
|
|
104
|
+
this.name = "BudgetError";
|
|
105
|
+
this.code = 7;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
101
109
|
export async function withBudget(fn, root = resolveRoot(), limit = 300) {
|
|
102
110
|
const budget = checkDailyBudget(root, limit);
|
|
103
111
|
if (!budget.ok) {
|
|
104
|
-
throw new
|
|
112
|
+
throw new BudgetError(`Daily budget exhausted (${budget.used}/${budget.budget} tasks executed)`);
|
|
105
113
|
}
|
|
106
114
|
return fn();
|
|
107
115
|
}
|