jules-orchestrator-kit 0.26.2 → 0.27.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/bin/agentctl.mjs CHANGED
@@ -14,7 +14,7 @@ const command = args[0];
14
14
 
15
15
  function printHelp() {
16
16
  console.log(`
17
- 🚀 agentctl v0.26.2 — Universal Agent Orchestrator & Safety Gatekeeper
17
+ 🚀 agentctl v0.27.0 — Universal Agent Orchestrator & Safety Gatekeeper
18
18
 
19
19
  Usage: agentctl <command> [options]
20
20
 
@@ -28,6 +28,8 @@ Commands:
28
28
  lock <action> Manage mutex locks (acquire | release | status | cleanup)
29
29
  doctor Run system diagnostics and stack resolution checks
30
30
  bootstrap Bootstrap zero-test repository with verification oracle
31
+ review-repair Parse PR review comments and synthesize OODA repair tasks
32
+ dashboard Start local HTTP telemetry and audit dashboard
31
33
  init Scaffold .agent/ directory and config.yml
32
34
  version Output agentctl version
33
35
 
@@ -45,7 +47,7 @@ async function main() {
45
47
  }
46
48
 
47
49
  if (command === "version" || command === "--version" || command === "-v") {
48
- console.log("agentctl v0.26.2");
50
+ console.log("agentctl v0.27.0");
49
51
  process.exit(0);
50
52
  }
51
53
 
@@ -263,6 +265,31 @@ async function main() {
263
265
  break;
264
266
  }
265
267
 
268
+ case "review-repair": {
269
+ const fileArg = args[1];
270
+ if (!fileArg || !existsSync(fileArg)) {
271
+ console.error("Error: Please provide a valid JSON file containing PR review comments.");
272
+ process.exit(1);
273
+ }
274
+ const { parseReviewComments, createReviewRepairTask } = await import("../src/review-repair.mjs");
275
+ const raw = readFileSync(fileArg, "utf-8");
276
+ const comments = parseReviewComments(raw);
277
+ console.log(`✅ Parsed ${comments.length} actionable review comment(s).`);
278
+ for (const c of comments) {
279
+ const task = createReviewRepairTask(c);
280
+ console.log(` Task: ${task.title} (Author: @${c.author}, Line: ${c.line || "N/A"})`);
281
+ }
282
+ process.exit(0);
283
+ break;
284
+ }
285
+
286
+ case "dashboard": {
287
+ const port = Number(args[1] || 4100);
288
+ const { startDashboardServer } = await import("../src/dashboard.mjs");
289
+ startDashboardServer(port, root);
290
+ break;
291
+ }
292
+
266
293
  case "init": {
267
294
  const agentDir = join(root, ".agent");
268
295
  ensureDir(agentDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jules-orchestrator-kit",
3
- "version": "0.26.2",
3
+ "version": "0.27.0",
4
4
  "description": "Orchestration kit for running Google Jules autonomous agents.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Zero-Dependency Local Telemetry & Audit HTTP Dashboard for jules-orchestrator-kit (v0.27.0).
3
+ * Uses node:http to serve real-time HTML/CSS dark mode visualizer and REST JSON endpoints.
4
+ */
5
+
6
+ import http from "node:http";
7
+ import { readTelemetry, verifyTelemetryIntegrity } from "./telemetry.mjs";
8
+ import { readVerifyRuns, flakyVerdict } from "./flaky-ledger.mjs";
9
+ import { lockStatus } from "./state.mjs";
10
+
11
+ export function getDashboardHtml(root = process.cwd()) {
12
+ return `<!DOCTYPE html>
13
+ <html lang="en">
14
+ <head>
15
+ <meta charset="UTF-8">
16
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
17
+ <title>jules-orchestrator-kit Dashboard</title>
18
+ <style>
19
+ body { background-color: #0d1117; color: #c9d1d9; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; margin: 0; padding: 24px; }
20
+ h1 { color: #58a6ff; font-size: 24px; margin-bottom: 8px; }
21
+ .subtitle { color: #8b949e; font-size: 14px; margin-bottom: 24px; }
22
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; margin-bottom: 24px; }
23
+ .card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
24
+ .card h2 { font-size: 16px; color: #f0f6fc; margin-top: 0; border-bottom: 1px solid #21262d; padding-bottom: 8px; }
25
+ table { width: 100%; border-collapse: collapse; font-size: 13px; }
26
+ th, td { text-align: left; padding: 8px; border-bottom: 1px solid #21262d; }
27
+ th { color: #8b949e; }
28
+ .badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 600; }
29
+ .badge-success { background: rgba(46, 160, 67, 0.15); color: #3fb950; border: 1px solid rgba(46, 160, 67, 0.4); }
30
+ .badge-warn { background: rgba(210, 153, 34, 0.15); color: #d29922; border: 1px solid rgba(210, 153, 34, 0.4); }
31
+ code { font-family: monospace; background: #21262d; padding: 2px 4px; border-radius: 4px; }
32
+ </style>
33
+ </head>
34
+ <body>
35
+ <h1>🚀 jules-orchestrator-kit Dashboard (v0.27.0)</h1>
36
+ <div class="subtitle">Repository: <code>${root}</code></div>
37
+
38
+ <div class="grid">
39
+ <div class="card">
40
+ <h2>🛡️ SHA-256 Telemetry Integrity</h2>
41
+ <div id="telemetry-status">Loading...</div>
42
+ </div>
43
+ <div class="card">
44
+ <h2>⚖️ Wilson-Score Flaky Ledger</h2>
45
+ <div id="flaky-status">Loading...</div>
46
+ </div>
47
+ <div class="card">
48
+ <h2>🔒 Active VFS Mutex Locks</h2>
49
+ <div id="locks-status">Loading...</div>
50
+ </div>
51
+ </div>
52
+
53
+ <script>
54
+ async function updateDashboard() {
55
+ try {
56
+ const [telRes, flakyRes, locksRes] = await Promise.all([
57
+ fetch('/api/telemetry?limit=10').then(r => r.json()),
58
+ fetch('/api/flaky').then(r => r.json()),
59
+ fetch('/api/locks').then(r => r.json())
60
+ ]);
61
+
62
+ document.getElementById('telemetry-status').innerHTML = telRes.integrity?.valid
63
+ ? '<span class="badge badge-success">✓ SHA-256 Hash Chain Valid</span> <p>Events logged: ' + telRes.events.length + '</p>'
64
+ : '<span class="badge badge-warn">⚠ Check Telemetry Logs</span>';
65
+
66
+ document.getElementById('flaky-status').innerHTML = '<p>Verdict: <code>' + (flakyRes.verdict?.action || 'CLEAN') + '</code></p><p>Tracked test runs: ' + flakyRes.count + '</p>';
67
+ document.getElementById('locks-status').innerHTML = '<p>Active VFS Locks: <code>' + locksRes.count + '</code></p>';
68
+ } catch (err) {
69
+ console.error("Dashboard update failed:", err);
70
+ }
71
+ }
72
+ updateDashboard();
73
+ setInterval(updateDashboard, 5000);
74
+ </script>
75
+ </body>
76
+ </html>`;
77
+ }
78
+
79
+ export function createDashboardServer({ root = process.cwd(), port: _port = 4100 } = {}) {
80
+ return http.createServer((req, res) => {
81
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
82
+ res.setHeader("Access-Control-Allow-Origin", "*");
83
+ res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
84
+
85
+ if (req.method === "OPTIONS") {
86
+ res.writeHead(204);
87
+ return res.end();
88
+ }
89
+
90
+ if (url.pathname === "/" || url.pathname === "/index.html") {
91
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
92
+ return res.end(getDashboardHtml(root));
93
+ }
94
+
95
+ if (url.pathname === "/api/status") {
96
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
97
+ return res.end(JSON.stringify({ ok: true, version: "0.27.0", root, ts: new Date().toISOString() }));
98
+ }
99
+
100
+ if (url.pathname === "/api/telemetry") {
101
+ const limit = Number(url.searchParams.get("limit") || "50");
102
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
103
+ return res.end(
104
+ JSON.stringify({
105
+ ok: true,
106
+ count: limit,
107
+ integrity: verifyTelemetryIntegrity(root),
108
+ events: readTelemetry(root, limit),
109
+ })
110
+ );
111
+ }
112
+
113
+ if (url.pathname === "/api/flaky") {
114
+ const runs = readVerifyRuns(root);
115
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
116
+ return res.end(JSON.stringify({ ok: true, count: runs.length, verdict: flakyVerdict(runs), runs }));
117
+ }
118
+
119
+ if (url.pathname === "/api/locks") {
120
+ const locks = lockStatus(root);
121
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
122
+ return res.end(JSON.stringify({ ok: true, count: locks.length, locks }));
123
+ }
124
+
125
+ res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
126
+ res.end(JSON.stringify({ ok: false, error: "Not Found", path: url.pathname }));
127
+ });
128
+ }
129
+
130
+ export function startDashboardServer(port = 4100, root = process.cwd()) {
131
+ const server = createDashboardServer({ root, port });
132
+ server.listen(port, "0.0.0.0", () => {
133
+ console.log(`🚀 jules-orchestrator-kit Dashboard running at http://localhost:${port}`);
134
+ });
135
+ return server;
136
+ }
package/src/provider.mjs CHANGED
@@ -218,7 +218,6 @@ export function createProvider(spec = "jules", config = {}) {
218
218
  const rawArgs = providerSpec.args || (providerSpec.command ? providerSpec.command.split(" ").slice(1) : []);
219
219
  const command = providerSpec.command ? providerSpec.command.split(" ")[0] : "claude";
220
220
 
221
- // CRITICAL H-a FIX: Filter out '{prompt}' argument if prompt is passed via stdin
222
221
  const filteredArgs = providerSpec.promptViaStdin
223
222
  ? rawArgs.filter((arg) => !arg.includes("{prompt}"))
224
223
  : rawArgs;
@@ -228,7 +227,7 @@ export function createProvider(spec = "jules", config = {}) {
228
227
  const res = spawnSync(command, processedArgs, {
229
228
  cwd: config._root || process.cwd(),
230
229
  encoding: "utf-8",
231
- shell: false, // CRITICAL: Ban shell execution to eliminate CWE-77
230
+ shell: false,
232
231
  input: providerSpec.promptViaStdin ? data.prompt : undefined,
233
232
  timeout: providerSpec.timeoutMs || 900000,
234
233
  maxBuffer: 32 * 1024 * 1024,
@@ -259,3 +258,44 @@ export function createProvider(spec = "jules", config = {}) {
259
258
  },
260
259
  };
261
260
  }
261
+
262
+ /**
263
+ * Creates a failover provider router that attempts dispatches sequentially across an ordered array of providers.
264
+ * Intercepts rate limits (429) and 5xx unavailability, logging telemetry events before falling over.
265
+ */
266
+ export function createFailoverProvider(providers = ["jules"], config = {}) {
267
+ const providerList = (Array.isArray(providers) && providers.length > 0 ? providers : ["jules"]).map((spec) =>
268
+ spec && typeof spec === "object" && typeof spec.dispatch === "function" ? spec : createProvider(spec, config)
269
+ );
270
+
271
+ return {
272
+ name: `failover:${providerList.map((p) => p.name).join("->")}`,
273
+
274
+ async dispatch(task, ctx = {}) {
275
+ const errors = [];
276
+ for (let i = 0; i < providerList.length; i++) {
277
+ const provider = providerList[i];
278
+ try {
279
+ const res = await provider.dispatch(task, ctx);
280
+ return { ...res, _routedProvider: provider.name, _failoverAttempts: i };
281
+ } catch (err) {
282
+ errors.push({ provider: provider.name, error: err });
283
+ const isRecoverable =
284
+ err instanceof ProviderRateLimitError ||
285
+ err instanceof ProviderUnavailableError ||
286
+ (err.status && err.status >= 500 && err.status < 600) ||
287
+ err.status === 429;
288
+
289
+ if (i === providerList.length - 1 || !isRecoverable) {
290
+ err._failoverErrors = errors;
291
+ throw err;
292
+ }
293
+ }
294
+ }
295
+ },
296
+
297
+ validate() {
298
+ return providerList.every((p) => p.validate());
299
+ },
300
+ };
301
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * PR Review Auto-Remediation Engine for jules-orchestrator-kit (v0.27.0).
3
+ * Parses GitHub PR review comments, filters conversational praise/noise,
4
+ * and synthesizes actionable OODA repair task envelopes.
5
+ */
6
+
7
+ export function parseReviewComments(input) {
8
+ let comments = typeof input === "string" ? (() => { try { return JSON.parse(input); } catch { return []; } })() : input;
9
+ comments = Array.isArray(comments) ? comments : comments?.comments || comments?.reviews || [comments];
10
+
11
+ const actionable = [];
12
+ let idx = 0;
13
+ for (const c of comments) {
14
+ if (!c || typeof c !== "object") continue;
15
+
16
+ const state = String(c.state || c.status || "").toUpperCase();
17
+ if (state === "APPROVED" || state === "DISMISSED" || c.isResolved === true || c.resolved === true) continue;
18
+
19
+ const body = String(c.body || c.comment || "").trim();
20
+ if (body.length < 5) continue;
21
+
22
+ const praiseRegex = /^(?:lgtm|looks good(?: to me)?|nice|awesome|thanks|thank you|great work|approved|ship it)[\s.!]*$/i;
23
+ if (praiseRegex.test(body)) continue;
24
+
25
+ idx++;
26
+ const path = c.path || c.file || null;
27
+ const line = c.line || c.original_line || null;
28
+ const author = c.user?.login || c.author || "reviewer";
29
+
30
+ actionable.push({
31
+ id: String(c.id || `review-${idx}`),
32
+ path,
33
+ line: line ? Number(line) : null,
34
+ author,
35
+ body,
36
+ actionable: true,
37
+ prompt: `Fix PR review comment by @${author} on ${path || "code"}${line ? ` line ${line}` : ""}:\n\n"${body}"\n\nEnsure all unit tests and safety gates pass cleanly after applying fix.`,
38
+ });
39
+ }
40
+
41
+ return actionable;
42
+ }
43
+
44
+ export function createReviewRepairTask(comment, baseBranch = "main") {
45
+ return {
46
+ id: `repair-${comment.id}`,
47
+ title: `PR Review Repair: ${comment.path || "code"} (${comment.id})`,
48
+ prompt: comment.prompt || `Fix review feedback: ${comment.body}`,
49
+ baseBranch,
50
+ targetFiles: comment.path ? [comment.path] : [],
51
+ metadata: { source: "pr-review", commentId: comment.id, author: comment.author, line: comment.line },
52
+ };
53
+ }