jules-orchestrator-kit 0.72.2 → 0.73.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/.agent/prompts/{Overseer.md → Auditor.md} +3 -3
- package/.agent/prompts/{Alchemist.md → Database.md} +1 -1
- package/.agent/prompts/Debugger.md +25 -0
- package/.agent/prompts/{Scribe.md → Docs.md} +8 -5
- package/.agent/prompts/{Spectator.md → E2E.md} +9 -6
- package/.agent/prompts/{Janitor.md → Hygiene.md} +2 -2
- package/.agent/prompts/{Bolt.md → Performance.md} +1 -1
- package/.agent/prompts/Resilience.md +20 -0
- package/.agent/prompts/Security.md +21 -0
- package/.agent/prompts/Testing.md +30 -0
- package/.agent/prompts/Types.md +19 -0
- package/.agent/rules/jules-protocol.md +4 -3
- package/AGENTS.md +78 -96
- package/CHANGELOG.md +193 -0
- package/JULES_RULES_TEMPLATE.md +83 -96
- package/LICENSE +1 -1
- package/README.md +77 -439
- package/ROADMAP_V1.md +22 -132
- package/bin/agentctl.mjs +443 -144
- package/bin/init.js +6 -3
- package/index.mjs +9 -6
- package/package.json +1 -1
- package/scripts/asset-integrity-check.mjs +1 -1
- package/scripts/doc-sync-check.mjs +47 -0
- package/scripts/generate-command-reference.mjs +39 -0
- package/scripts/jules-dispatch.mjs +12 -113
- package/scripts/jules-merge-swarm.mjs +8 -196
- package/scripts/jules-patch.mjs +7 -8
- package/scripts/jules-queue-runner.mjs +6 -8
- package/scripts/jules-scan-todos.mjs +10 -38
- package/scripts/jules-self-audit.mjs +8 -139
- package/scripts/jules-status.mjs +32 -38
- package/scripts/jules-webhook-receiver.mjs +1 -1
- package/src/assertions.mjs +5 -50
- package/src/bidi-guard.mjs +36 -0
- package/src/budget.mjs +3 -14
- package/src/config.mjs +2 -6
- package/src/dashboard.mjs +7 -9
- package/src/dispatch.mjs +212 -0
- package/src/engine.mjs +40 -16
- package/src/evidence.mjs +10 -41
- package/src/execution-envelope.mjs +13 -1
- package/src/flaky-ledger.mjs +1 -1
- package/src/fs-atomic.mjs +72 -0
- package/src/git.mjs +298 -27
- package/src/mcp.mjs +296 -7
- package/src/memory.mjs +0 -0
- package/src/merge-swarm.mjs +202 -0
- package/src/ops/cli-intent.mjs +1 -0
- package/src/ops/command-registry.mjs +796 -70
- package/src/ops/doctor-registry.mjs +134 -47
- package/src/ops/handover.mjs +3 -27
- package/src/ops/pr-harvest.mjs +1 -1
- package/src/prompt-guard.mjs +33 -3
- package/src/provider.mjs +51 -7
- package/src/remediation.mjs +2 -2
- package/src/role-resolver.mjs +113 -3
- package/src/router.mjs +19 -11
- package/src/runtime-env.mjs +67 -0
- package/src/scaffold.mjs +3 -1
- package/src/scope-guard.mjs +249 -0
- package/src/secret-scanner.mjs +530 -0
- package/src/security.mjs +81 -2972
- package/src/self-audit.mjs +140 -0
- package/src/session-ops.mjs +29 -0
- package/src/stability.mjs +8 -1
- package/src/stack-detector.mjs +5 -2
- package/src/state.mjs +45 -0
- package/src/swarm.mjs +76 -0
- package/src/task-optimizer.mjs +34 -7
- package/src/telemetry.mjs +23 -0
- package/src/test-tamper-guard.mjs +2173 -0
- package/src/todo-scanner.mjs +129 -0
- package/src/web-templates.mjs +3 -3
- package/src/webhook.mjs +10 -3
- package/src/wizard-init.mjs +12 -20
- package/src/wizard-oracle.mjs +4 -3
- package/src/wizard-task.mjs +37 -9
- package/.agent/prompts/Sentinel.md +0 -18
- package/scripts/utils.mjs +0 -241
package/src/engine.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import { recordVerifyRun, readVerifyRuns, flakyVerdict } from "./flaky-ledger.mj
|
|
|
13
13
|
import fs, { readdirSync, readFileSync, renameSync, existsSync } from "node:fs";
|
|
14
14
|
import { join, basename } from "node:path";
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
16
|
-
import {
|
|
16
|
+
import { appendTelemetryBestEffort as appendTelemetry } from "./telemetry.mjs";
|
|
17
17
|
|
|
18
18
|
import { spawn } from "node:child_process";
|
|
19
19
|
import { resolveAffectedTests, executeQueueDag } from "./dag-engine.mjs";
|
|
@@ -59,14 +59,6 @@ export {
|
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
function appendTelemetry(root, kind, fields = {}) {
|
|
63
|
-
try {
|
|
64
|
-
return appendTelemetryUnsafe(root, kind, fields);
|
|
65
|
-
} catch (_) {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
62
|
function isSafeQueueFileName(fileName) {
|
|
71
63
|
return typeof fileName === "string" && fileName.length > 0 && basename(fileName) === fileName && fileName !== "." && fileName !== "..";
|
|
72
64
|
}
|
|
@@ -1138,8 +1130,8 @@ function buildRepairPrompt(failure, attempt, _config, extraPromptDirective = nul
|
|
|
1138
1130
|
let escalationDirective = "1. Ground your fix strictly in the error log below. Do NOT guess file contents or function signatures.";
|
|
1139
1131
|
|
|
1140
1132
|
if (extraPromptDirective) {
|
|
1141
|
-
escalationStrategy = "
|
|
1142
|
-
escalationDirective = `1.
|
|
1133
|
+
escalationStrategy = "Test Oscillation Circuit Breaker Activated";
|
|
1134
|
+
escalationDirective = `1. Switching verification strategy: ${extraPromptDirective}`;
|
|
1143
1135
|
} else if (attempt === 2) {
|
|
1144
1136
|
escalationStrategy = "DIAGNOSTIC_ANALYSIS";
|
|
1145
1137
|
escalationDirective = "1. DIAGNOSE FIRST: Two attempts have failed. Analyze if the root cause is in a different file or component than where the previous patch was applied.";
|
|
@@ -1205,10 +1197,15 @@ export async function dispatch(task = {}, opts = {}) {
|
|
|
1205
1197
|
let cleanPrompt = redactSecrets(task.prompt || "");
|
|
1206
1198
|
|
|
1207
1199
|
// Specialist Role resolution (if role is set and not already present in prompt)
|
|
1208
|
-
if (task.role
|
|
1209
|
-
const roleObj = resolveRolePrompt(root, task.role);
|
|
1200
|
+
if (task.role) {
|
|
1201
|
+
const roleObj = resolveRolePrompt(root, task.role, { config });
|
|
1210
1202
|
if (roleObj) {
|
|
1211
|
-
|
|
1203
|
+
const alreadyHasRole = cleanPrompt.includes(roleObj.content.slice(0, 60)) ||
|
|
1204
|
+
cleanPrompt.includes(`# ${roleObj.role}`) ||
|
|
1205
|
+
cleanPrompt.includes("Protocol - ");
|
|
1206
|
+
if (!alreadyHasRole) {
|
|
1207
|
+
cleanPrompt = `${roleObj.content}\n\n${cleanPrompt}`.trim();
|
|
1208
|
+
}
|
|
1212
1209
|
} else {
|
|
1213
1210
|
// A role reaching here comes from a task envelope or an internal
|
|
1214
1211
|
// synthesis rather than a typed flag, so the dispatch still proceeds with
|
|
@@ -1227,7 +1224,13 @@ export async function dispatch(task = {}, opts = {}) {
|
|
|
1227
1224
|
}
|
|
1228
1225
|
|
|
1229
1226
|
// Wire Prompt Guard, Memory Hydration & Envelope to wrap raw task arguments
|
|
1230
|
-
|
|
1227
|
+
let taskInstructions = cleanPrompt;
|
|
1228
|
+
if (task.taskInstructions && task.taskInstructions !== cleanPrompt) {
|
|
1229
|
+
taskInstructions = cleanPrompt ? `${cleanPrompt}\n\n${task.taskInstructions}` : task.taskInstructions;
|
|
1230
|
+
}
|
|
1231
|
+
if (!taskInstructions) {
|
|
1232
|
+
taskInstructions = task.title || "Autonomous Task Execution";
|
|
1233
|
+
}
|
|
1231
1234
|
const untrustedData = Array.isArray(task.untrustedData) ? task.untrustedData : [];
|
|
1232
1235
|
const systemPolicy = task.systemPolicy || config.systemPolicy || "";
|
|
1233
1236
|
|
|
@@ -1236,6 +1239,11 @@ export async function dispatch(task = {}, opts = {}) {
|
|
|
1236
1239
|
|
|
1237
1240
|
const envelopedPrompt = buildAgentEnvelope(systemPolicy, taskInstructions, untrustedData, { learnedRemediations });
|
|
1238
1241
|
|
|
1242
|
+
// Enforce prompt size limit on the assembled final payload
|
|
1243
|
+
if (Buffer.byteLength(envelopedPrompt, "utf-8") > promptKb) {
|
|
1244
|
+
throw new Error(`Task prompt exceeds maximum payload limit of ${config.limits.promptKb} KB (${Math.ceil(Buffer.byteLength(envelopedPrompt, "utf-8") / 1024)} KB)`);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1239
1247
|
const cleanTask = { ...task, prompt: envelopedPrompt };
|
|
1240
1248
|
|
|
1241
1249
|
// Snapshot the tree before anything can change it.
|
|
@@ -1606,4 +1614,20 @@ export async function probeDevServer(serverConfig = {}, root = process.cwd()) {
|
|
|
1606
1614
|
}
|
|
1607
1615
|
}
|
|
1608
1616
|
|
|
1609
|
-
|
|
1617
|
+
/**
|
|
1618
|
+
* Classify a queue-run failure as "concurrency_limit" (retry later without
|
|
1619
|
+
* consuming a fresh dispatch) or "retriable".
|
|
1620
|
+
*
|
|
1621
|
+
* Moved out of scripts/jules-queue-runner.mjs, which keeps only the process
|
|
1622
|
+
* entry point; the queue loop itself already lives in this module.
|
|
1623
|
+
*
|
|
1624
|
+
* @param {Error|string} err
|
|
1625
|
+
* @returns {"concurrency_limit" | "retriable"}
|
|
1626
|
+
*/
|
|
1627
|
+
export function classifyQueueFailure(err) {
|
|
1628
|
+
const msg = String(err?.message || err || "");
|
|
1629
|
+
if (msg.includes("FAILED_PRECONDITION") || msg.includes("Active Session Limit") || msg.includes("concurrency")) {
|
|
1630
|
+
return "concurrency_limit";
|
|
1631
|
+
}
|
|
1632
|
+
return "retriable";
|
|
1633
|
+
}
|
package/src/evidence.mjs
CHANGED
|
@@ -4,26 +4,19 @@ import {
|
|
|
4
4
|
existsSync,
|
|
5
5
|
readdirSync,
|
|
6
6
|
statSync,
|
|
7
|
-
openSync,
|
|
8
|
-
writeSync,
|
|
9
|
-
fsyncSync,
|
|
10
|
-
closeSync,
|
|
11
|
-
renameSync,
|
|
12
7
|
unlinkSync,
|
|
13
8
|
} from "node:fs";
|
|
14
|
-
import { join, resolve, relative, isAbsolute,
|
|
9
|
+
import { join, resolve, relative, isAbsolute, extname } from "node:path";
|
|
15
10
|
import { createHash, randomUUID } from "node:crypto";
|
|
16
11
|
import { execSync } from "node:child_process";
|
|
17
12
|
import { isTestPath } from "./test-paths.mjs";
|
|
13
|
+
import { safeAtomicWrite } from "./security.mjs";
|
|
14
|
+
import { normalizePath } from "./config.mjs";
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*/
|
|
24
|
-
export function normalizePath(p = "") {
|
|
25
|
-
return p.split(sep).join("/").replace(/\\/g, "/");
|
|
26
|
-
}
|
|
16
|
+
// normalizePath is the single shared implementation in src/config.mjs (the
|
|
17
|
+
// copy that used to live here differed only by not guarding non-strings).
|
|
18
|
+
// Re-exported so `./evidence.mjs` callers keep the same entry point.
|
|
19
|
+
export { normalizePath };
|
|
27
20
|
|
|
28
21
|
/**
|
|
29
22
|
* Computes SHA-256 hex digest of a string or buffer.
|
|
@@ -232,30 +225,6 @@ export function computeEvidenceHash(manifest) {
|
|
|
232
225
|
return "sha256:" + sha256(canonicalPayload);
|
|
233
226
|
}
|
|
234
227
|
|
|
235
|
-
/**
|
|
236
|
-
* Safely writes a file atomically.
|
|
237
|
-
* @param {string} filePath
|
|
238
|
-
* @param {string} content
|
|
239
|
-
*/
|
|
240
|
-
function writeFileAtomically(filePath, content) {
|
|
241
|
-
const tmpPath = `${filePath}.${randomUUID()}.tmp`;
|
|
242
|
-
let fd;
|
|
243
|
-
try {
|
|
244
|
-
fd = openSync(tmpPath, "wx", 0o600);
|
|
245
|
-
writeSync(fd, content, "utf-8");
|
|
246
|
-
fsyncSync(fd);
|
|
247
|
-
closeSync(fd);
|
|
248
|
-
fd = undefined;
|
|
249
|
-
renameSync(tmpPath, filePath);
|
|
250
|
-
} catch (err) {
|
|
251
|
-
if (fd !== undefined) {
|
|
252
|
-
try { closeSync(fd); } catch (_) {}
|
|
253
|
-
}
|
|
254
|
-
try { unlinkSync(tmpPath); } catch (_) {}
|
|
255
|
-
throw err;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
228
|
/**
|
|
260
229
|
* Queries current git provenance data safely.
|
|
261
230
|
* @param {string} root
|
|
@@ -407,12 +376,12 @@ export function writeEvidenceManifest(root, manifest, customPath = null) {
|
|
|
407
376
|
? isAbsolute(customPath) ? customPath : join(root, customPath)
|
|
408
377
|
: join(evidenceDir, `${manifest.manifestId}.json`);
|
|
409
378
|
|
|
410
|
-
|
|
379
|
+
safeAtomicWrite(targetPath, JSON.stringify(manifest, null, 2), { mode: 0o600 });
|
|
411
380
|
|
|
412
381
|
// Also maintain symlink/latest pointer
|
|
413
382
|
const latestPath = join(evidenceDir, "manifest.v1.json");
|
|
414
383
|
try {
|
|
415
|
-
|
|
384
|
+
safeAtomicWrite(latestPath, JSON.stringify(manifest, null, 2), { mode: 0o600 });
|
|
416
385
|
} catch (_) {}
|
|
417
386
|
|
|
418
387
|
// Written at a custom path means the caller owns the location and its
|
|
@@ -612,7 +581,7 @@ export function exportJsonReport(manifest, customPath = null) {
|
|
|
612
581
|
};
|
|
613
582
|
|
|
614
583
|
if (customPath) {
|
|
615
|
-
|
|
584
|
+
safeAtomicWrite(customPath, JSON.stringify(report, null, 2), { mode: 0o600 });
|
|
616
585
|
}
|
|
617
586
|
|
|
618
587
|
return report;
|
|
@@ -33,6 +33,18 @@ export function hashExecutionEnvelope(envelope) {
|
|
|
33
33
|
return createHash("sha256").update(JSON.stringify(canonicalPayload)).digest("hex");
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Hashes the envelope's scope triple. The configSha contract: SHA-256 over
|
|
38
|
+
* exactly `{ denyPaths, allowPaths, protectPaths }`, defined once here so the
|
|
39
|
+
* implementation cannot drift from the specification.
|
|
40
|
+
*
|
|
41
|
+
* @param {{ denyPaths?: string[], allowPaths?: string[], protectPaths?: string[] }} scope
|
|
42
|
+
* @returns {string} Hex SHA-256 digest.
|
|
43
|
+
*/
|
|
44
|
+
export function hashConfigScope({ denyPaths = [], allowPaths = [], protectPaths = [] } = {}) {
|
|
45
|
+
return createHash("sha256").update(JSON.stringify({ denyPaths, allowPaths, protectPaths })).digest("hex");
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
/**
|
|
37
49
|
* Deep freezes an execution envelope to prevent modifications during OODA repair loops.
|
|
38
50
|
*/
|
|
@@ -93,7 +105,7 @@ export function createExecutionEnvelope(task = {}, opts = {}) {
|
|
|
93
105
|
taskId,
|
|
94
106
|
baseRef,
|
|
95
107
|
baseSha,
|
|
96
|
-
configSha:
|
|
108
|
+
configSha: hashConfigScope({ denyPaths, allowPaths, protectPaths }),
|
|
97
109
|
scope: {
|
|
98
110
|
deny: denyPaths,
|
|
99
111
|
allow: allowPaths,
|
package/src/flaky-ledger.mjs
CHANGED
|
@@ -250,7 +250,7 @@ export function synthesizeFlakyHealingTask(quarantinedItem, options = {}) {
|
|
|
250
250
|
// in a non-Node repo it produced a healing task that could not run.
|
|
251
251
|
const testCmd = item.testCmd || options.testCmd || options.config?.verify?.test || "";
|
|
252
252
|
const oscillationPct = Math.round((item.oscillation || 0.4) * 100);
|
|
253
|
-
const role = options.role || "
|
|
253
|
+
const role = options.role || "hygiene";
|
|
254
254
|
const slug = String(testCmd).replace(/[^a-zA-Z0-9]/g, "-").replace(/-+/g, "-").slice(0, 20).toLowerCase();
|
|
255
255
|
const taskId = options.taskId || `flaky-heal-${slug}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
256
256
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atomic filesystem writes for the kit's own state.
|
|
3
|
+
*
|
|
4
|
+
* Split out of src/security.mjs (P05): these are the write primitives every
|
|
5
|
+
* durable artefact in the kit rests on — a state file, an evidence bundle, a
|
|
6
|
+
* handover — and they have nothing to do with scanning a diff. They sit at the
|
|
7
|
+
* bottom of the dependency graph on purpose: nothing under src/ needs to import
|
|
8
|
+
* security scanning in order to write a file safely.
|
|
9
|
+
*
|
|
10
|
+
* Relocation only; the logic, the failure modes and the comment reasoning are
|
|
11
|
+
* the originals.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { openSync, writeSync, fsyncSync, closeSync, renameSync, realpathSync, existsSync, lstatSync, unlinkSync } from "node:fs";
|
|
15
|
+
import { dirname, join, basename } from "node:path";
|
|
16
|
+
import { randomBytes } from "node:crypto";
|
|
17
|
+
|
|
18
|
+
export function safeRenameSync(src, dst) {
|
|
19
|
+
try {
|
|
20
|
+
renameSync(src, dst);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
if (process.platform === "win32" && (err.code === "EEXIST" || err.code === "EPERM")) {
|
|
23
|
+
try { unlinkSync(dst); } catch (_) {}
|
|
24
|
+
renameSync(src, dst);
|
|
25
|
+
} else {
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* TOCTOU-safe atomic file write using O_CREAT|O_EXCL|O_WRONLY temp file in target dir.
|
|
33
|
+
*/
|
|
34
|
+
export function safeAtomicWrite(filePath, content, options = {}) {
|
|
35
|
+
const mode = options.mode || 0o644;
|
|
36
|
+
const encoding = options.encoding || "utf-8";
|
|
37
|
+
const rejectSymlinks = options.rejectSymlinks !== false;
|
|
38
|
+
|
|
39
|
+
if (existsSync(filePath)) {
|
|
40
|
+
const stat = lstatSync(filePath);
|
|
41
|
+
if (stat.isSymbolicLink()) {
|
|
42
|
+
const realPath = realpathSync(filePath);
|
|
43
|
+
if (rejectSymlinks) {
|
|
44
|
+
throw new Error(`TOCTOU Guard: Refusing to write to symlink ${filePath} -> ${realPath}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const dir = dirname(filePath);
|
|
50
|
+
const tempFile = join(dir, `.tmp-${basename(filePath)}-${randomBytes(6).toString("hex")}`);
|
|
51
|
+
|
|
52
|
+
let fd;
|
|
53
|
+
try {
|
|
54
|
+
fd = openSync(tempFile, "wx", mode);
|
|
55
|
+
writeSync(fd, content, null, encoding);
|
|
56
|
+
if (options.sync !== false) {
|
|
57
|
+
fsyncSync(fd);
|
|
58
|
+
}
|
|
59
|
+
closeSync(fd);
|
|
60
|
+
fd = undefined;
|
|
61
|
+
safeRenameSync(tempFile, filePath);
|
|
62
|
+
return true;
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (fd !== undefined) {
|
|
65
|
+
try { closeSync(fd); } catch (_) {}
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
if (existsSync(tempFile)) unlinkSync(tempFile);
|
|
69
|
+
} catch (_) {}
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|