jules-orchestrator-kit 0.22.7 → 0.22.8

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.22.7 — Universal Agent Orchestrator & Safety Gatekeeper
17
+ šŸš€ agentctl v0.22.8 — Universal Agent Orchestrator & Safety Gatekeeper
18
18
 
19
19
  Usage: agentctl <command> [options]
20
20
 
@@ -44,7 +44,7 @@ async function main() {
44
44
  }
45
45
 
46
46
  if (command === "version" || command === "--version" || command === "-v") {
47
- console.log("agentctl v0.22.7");
47
+ console.log("agentctl v0.22.8");
48
48
  process.exit(0);
49
49
  }
50
50
 
@@ -226,7 +226,7 @@ async function main() {
226
226
  }
227
227
 
228
228
  case "doctor": {
229
- console.log(`\nšŸ” agentctl System Diagnostics (v0.22.7)`);
229
+ console.log(`\nšŸ” agentctl System Diagnostics (v0.22.8)`);
230
230
  console.log(`--------------------------------------------------`);
231
231
  console.log(` Project Root : ${root}`);
232
232
  console.log(` Config File : ${config._file || "None (Using defaults)"}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jules-orchestrator-kit",
3
- "version": "0.22.7",
3
+ "version": "0.22.8",
4
4
  "description": "Orchestration kit for running Google Jules autonomous agents.",
5
5
  "repository": {
6
6
  "type": "git",
package/src/engine.mjs CHANGED
@@ -4,10 +4,13 @@ import { changedFiles, diffBytes, diffText, showFromOrigin, runCmd } from "./git
4
4
  import { createProvider } from "./provider.mjs";
5
5
  import { withBudget, appendLedger, getQueueDir, ensureDir } from "./state.mjs";
6
6
  import { sanitizeUntrustedData, buildAgentEnvelope } from "./prompt-guard.mjs";
7
+ import { recordVerifyRun, readVerifyRuns, flakyVerdict } from "./flaky-ledger.mjs";
7
8
  import { readdirSync, readFileSync, renameSync, existsSync } from "node:fs";
8
9
  import { join } from "node:path";
9
10
  import { createHash } from "node:crypto";
10
11
 
12
+ export { recordVerifyRun, readVerifyRuns, flakyVerdict };
13
+
11
14
  /**
12
15
  * Validates whether a file in .agent/jules-queue/ is a task file.
13
16
  * Filters out README.md and matches TASK-*.md or valid envelope front-matter.
@@ -176,9 +179,25 @@ export async function gate(opts = {}) {
176
179
  NODE_OPTIONS: guardNodeOptions,
177
180
  };
178
181
 
182
+ let flakyVerdictResult = null;
183
+
179
184
  if (testCmd) {
185
+ const startTime = Date.now();
180
186
  const res = runCmd(testCmd, { cwd: root, ignoreError: true, env: testEnv });
187
+ const durationMs = Date.now() - startTime;
181
188
  testResult = { ok: res.status === 0, status: res.status, stdout: res.stdout, stderr: res.stderr, command: testCmd };
189
+
190
+ const fingerprint = !testResult.ok ? fingerprintFailureState(testResult, root) : null;
191
+ recordVerifyRun(root, testCmd, testResult.ok, fingerprint, durationMs);
192
+
193
+ if (!testResult.ok) {
194
+ const runs = readVerifyRuns(root, testCmd);
195
+ flakyVerdictResult = flakyVerdict(runs);
196
+ if (flakyVerdictResult.verdict === "QUARANTINED") {
197
+ phases.push({ phase: "verify", ok: false, testResult, buildResult, flakyVerdict: flakyVerdictResult });
198
+ return { ok: false, code: 8, phases, flakyVerdict: flakyVerdictResult };
199
+ }
200
+ }
182
201
  }
183
202
 
184
203
  if (testResult.ok && buildCmd) {
@@ -0,0 +1,173 @@
1
+ import { readFileSync, existsSync, appendFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getStateDir, ensureDir } from "./state.mjs";
4
+
5
+ /**
6
+ * Calculates Wilson score interval for binomial proportion.
7
+ */
8
+ export function wilsonScoreInterval(k, n, z = 1.96) {
9
+ if (n === 0) return { lower: 0, upper: 0 };
10
+ const p = k / n;
11
+ const z2 = z * z;
12
+ const denominator = 1 + z2 / n;
13
+ const center = (p + z2 / (2 * n)) / denominator;
14
+ const stdErr = Math.sqrt((p * (1 - p) + z2 / (4 * n)) / n);
15
+ const margin = (z * stdErr) / denominator;
16
+ const lower = Math.max(0, center - margin);
17
+ const upper = Math.min(1, center + margin);
18
+ return { lower, upper };
19
+ }
20
+
21
+ /**
22
+ * Computes oscillation rate (fraction of state transitions between consecutive runs).
23
+ */
24
+ export function computeOscillation(runs) {
25
+ if (!runs || runs.length <= 1) return 0;
26
+ let transitions = 0;
27
+ for (let i = 0; i < runs.length - 1; i++) {
28
+ const current = typeof runs[i] === "boolean" ? runs[i] : Boolean(runs[i] && runs[i].pass);
29
+ const next = typeof runs[i + 1] === "boolean" ? runs[i + 1] : Boolean(runs[i + 1] && runs[i + 1].pass);
30
+ if (current !== next) {
31
+ transitions++;
32
+ }
33
+ }
34
+ return transitions / (runs.length - 1);
35
+ }
36
+
37
+ /**
38
+ * Appends a test outcome record to .agent/state/flaky.jsonl.
39
+ */
40
+ export function recordVerifyRun(root, testCmd, pass, fingerprint = null, durationMs = 0) {
41
+ const stateDir = getStateDir(root);
42
+ ensureDir(stateDir);
43
+ const filePath = join(stateDir, "flaky.jsonl");
44
+
45
+ const entry = {
46
+ timestamp: new Date().toISOString(),
47
+ testCmd: testCmd || "",
48
+ pass: Boolean(pass),
49
+ fingerprint: fingerprint || null,
50
+ durationMs: Number(durationMs) || 0,
51
+ };
52
+
53
+ appendFileSync(filePath, JSON.stringify(entry) + "\n", "utf-8");
54
+ return entry;
55
+ }
56
+
57
+ /**
58
+ * Reads test run records from .agent/state/flaky.jsonl.
59
+ */
60
+ export function readVerifyRuns(root, testCmd = null) {
61
+ const stateDir = getStateDir(root);
62
+ const filePath = join(stateDir, "flaky.jsonl");
63
+
64
+ if (!existsSync(filePath)) {
65
+ return [];
66
+ }
67
+
68
+ try {
69
+ const raw = readFileSync(filePath, "utf-8");
70
+ const lines = raw.split("\n").filter((l) => l.trim().length > 0);
71
+ const runs = [];
72
+ for (const line of lines) {
73
+ try {
74
+ const parsed = JSON.parse(line);
75
+ if (!testCmd || parsed.testCmd === testCmd) {
76
+ runs.push(parsed);
77
+ }
78
+ } catch (_) {}
79
+ }
80
+ return runs;
81
+ } catch (_) {
82
+ return [];
83
+ }
84
+ }
85
+
86
+ export const getVerifyRuns = readVerifyRuns;
87
+
88
+ /**
89
+ * Evaluates flaky test verdict using a sliding window of the last n <= 10 runs.
90
+ */
91
+ export function flakyVerdict(runs = []) {
92
+ const normalized = (runs || []).map((r) => {
93
+ if (typeof r === "boolean") return { pass: r };
94
+ if (typeof r === "number") return { pass: r === 1 };
95
+ return { pass: Boolean(r && r.pass) };
96
+ });
97
+
98
+ const window = normalized.slice(-10);
99
+ const n = window.length;
100
+ const fails = window.filter((r) => !r.pass).length;
101
+ const passes = n - fails;
102
+
103
+ if (n === 0) {
104
+ return {
105
+ verdict: "INSUFFICIENT_DATA",
106
+ allowRepair: true,
107
+ n: 0,
108
+ fails: 0,
109
+ passes: 0,
110
+ oscillation: 0,
111
+ };
112
+ }
113
+
114
+ if (fails === 0) {
115
+ return {
116
+ verdict: "HEALTHY",
117
+ allowRepair: true,
118
+ n,
119
+ fails,
120
+ passes,
121
+ oscillation: computeOscillation(window),
122
+ };
123
+ }
124
+
125
+ const trailing3Fail = n >= 3 && window.slice(-3).every((r) => !r.pass);
126
+ if (fails === n || trailing3Fail) {
127
+ return {
128
+ verdict: "REPAIRABLE_REGRESSION",
129
+ allowRepair: true,
130
+ n,
131
+ fails,
132
+ passes,
133
+ oscillation: computeOscillation(window),
134
+ };
135
+ }
136
+
137
+ if (n < 6) {
138
+ return {
139
+ verdict: "INSUFFICIENT_DATA",
140
+ allowRepair: true,
141
+ n,
142
+ fails,
143
+ passes,
144
+ oscillation: computeOscillation(window),
145
+ };
146
+ }
147
+
148
+ const oscillation = computeOscillation(window);
149
+ const wilson = wilsonScoreInterval(fails, n, 1.96);
150
+ const interiorWilson = wilson.lower > 0 && wilson.upper < 1;
151
+
152
+ if (oscillation >= 0.4 && interiorWilson) {
153
+ return {
154
+ verdict: "QUARANTINED",
155
+ allowRepair: false,
156
+ n,
157
+ fails,
158
+ passes,
159
+ oscillation,
160
+ wilson,
161
+ };
162
+ }
163
+
164
+ return {
165
+ verdict: "REPAIRABLE_REGRESSION",
166
+ allowRepair: true,
167
+ n,
168
+ fails,
169
+ passes,
170
+ oscillation,
171
+ wilson,
172
+ };
173
+ }
package/src/mcp.mjs CHANGED
@@ -7,7 +7,7 @@ import { reapOrphanedIntents, reapStaleMutexDirs } from "./journal.mjs";
7
7
 
8
8
  export const MCP_SERVER_INFO = {
9
9
  name: "jules-orchestrator-kit",
10
- version: "0.22.7",
10
+ version: "0.22.8",
11
11
  };
12
12
 
13
13
  export const MAX_MCP_FRAME_SIZE = 4 * 1024 * 1024; // 4 MB memory safety ceiling