jules-orchestrator-kit 0.8.5 → 0.9.3
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/jules-queue/README.md +4 -4
- package/.agent/prompts/Bolt.md +18 -0
- package/.agent/prompts/Overseer.md +18 -0
- package/.agent/prompts/Sentinel.md +18 -0
- package/.agent/prompts/Task_Template.md +26 -0
- package/JULES_RULES_TEMPLATE.md +6 -1
- package/README.md +25 -13
- package/bin/agentctl.mjs +274 -0
- package/index.mjs +32 -5
- package/package.json +11 -2
- package/scripts/asset-integrity-check.mjs +35 -0
- package/scripts/command-resolver.mjs +37 -214
- package/scripts/jules-cleanup.mjs +23 -164
- package/scripts/jules-create.mjs +12 -56
- package/scripts/jules-dispatch.mjs +103 -449
- package/scripts/jules-merge-swarm.mjs +19 -173
- package/scripts/jules-nightly.mjs +7 -128
- package/scripts/jules-patch.mjs +15 -103
- package/scripts/jules-queue-runner.mjs +28 -221
- package/scripts/jules-scan-todos.mjs +31 -125
- package/scripts/jules-self-audit.mjs +69 -656
- package/scripts/jules-status.mjs +42 -70
- package/scripts/jules-swarm.mjs +23 -327
- package/scripts/jules-webhook-receiver.mjs +70 -0
- package/scripts/lock-manager.mjs +11 -177
- package/scripts/risk-tier.mjs +29 -0
- package/scripts/rules-lint.mjs +20 -0
- package/scripts/stale-base-check.mjs +26 -0
- package/scripts/utils.mjs +107 -369
- package/scripts/validate-envelope.mjs +44 -0
- package/src/asset_integrity.mjs +76 -0
- package/src/config.mjs +325 -0
- package/src/engine.mjs +217 -0
- package/src/envelope.mjs +105 -0
- package/src/git.mjs +127 -0
- package/src/provider.mjs +179 -0
- package/src/risk.mjs +120 -0
- package/src/rules_budget.mjs +77 -0
- package/src/security.mjs +212 -0
- package/src/state.mjs +209 -0
- package/src/webhook.mjs +138 -0
|
@@ -1,184 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* jules-merge-swarm.mjs
|
|
5
|
-
*
|
|
6
|
-
* Autonomous PR Merge Engine for Disjoint Swarm Tasks.
|
|
7
|
-
* Discovers open PRs created by Jules (branch prefix 'jules/'), verifies CI checks pass,
|
|
8
|
-
* checks for disjoint file cluster modifications (no overlapping files), and squash-merges PRs.
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* node scripts/jules-merge-swarm.mjs [--dry-run] [--admin]
|
|
4
|
+
* Backward compatibility shim for jules-merge-swarm.mjs in v0.9.0.
|
|
12
5
|
*/
|
|
13
6
|
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
7
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
16
9
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const isDryRun = args.includes("--dry-run");
|
|
21
|
-
const useAdmin = args.includes("--admin");
|
|
22
|
-
|
|
23
|
-
function runGhCommand(cmdArgs, ignoreError = false) {
|
|
24
|
-
try {
|
|
25
|
-
const output = execFileSync("gh", cmdArgs, {
|
|
26
|
-
encoding: "utf-8",
|
|
27
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
28
|
-
stdio: ["pipe", "pipe", ignoreError ? "ignore" : "pipe"],
|
|
29
|
-
});
|
|
30
|
-
return output.trim();
|
|
31
|
-
} catch (err) {
|
|
32
|
-
if (ignoreError) return "";
|
|
33
|
-
throw err;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function main() {
|
|
38
|
-
log.info("🔍 Scanning open GitHub PRs for Jules swarm branches...");
|
|
39
|
-
|
|
40
|
-
let rawPrs = "";
|
|
41
|
-
try {
|
|
42
|
-
rawPrs = runGhCommand([
|
|
43
|
-
"pr", "list",
|
|
44
|
-
"--state", "open",
|
|
45
|
-
"--json", "number,title,headRefName,url,isDraft,mergeable,files,statusCheckRollup"
|
|
46
|
-
]);
|
|
47
|
-
} catch (err) {
|
|
48
|
-
log.error("❌ Failed to query GitHub PRs using `gh` CLI. Ensure GitHub CLI is installed and authenticated (`gh auth status`).");
|
|
49
|
-
log.error(err.message);
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
let prs = [];
|
|
10
|
+
export function checkSafetyGate(branchName = "", projectRoot = process.cwd()) {
|
|
11
|
+
const locksDir = join(projectRoot, ".agent/state/locks");
|
|
12
|
+
if (!existsSync(locksDir)) return { safe: true };
|
|
54
13
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const julesPrs = prs.filter((p) => p.headRefName && p.headRefName.startsWith("jules/"));
|
|
63
|
-
|
|
64
|
-
if (julesPrs.length === 0) {
|
|
65
|
-
log.info("✨ No open Jules PRs found (branches matching 'jules/*'). Nothing to merge.");
|
|
66
|
-
process.exit(0);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
log.info(`Found ${julesPrs.length} open Jules PR(s). Evaluating merge eligibility...`);
|
|
70
|
-
|
|
71
|
-
const mergeableCandidates = [];
|
|
72
|
-
const blockedPrs = [];
|
|
73
|
-
|
|
74
|
-
for (const pr of julesPrs) {
|
|
75
|
-
if (pr.isDraft) {
|
|
76
|
-
blockedPrs.push({ pr, reason: "Draft PR" });
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (pr.mergeable === "CONFLICTING") {
|
|
81
|
-
blockedPrs.push({ pr, reason: "Git Merge Conflict" });
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Check status check rollup if present
|
|
86
|
-
const checkRollup = pr.statusCheckRollup || [];
|
|
87
|
-
const states = checkRollup.map((c) => c.state || c.status || c.conclusion).filter(Boolean);
|
|
88
|
-
const hasFailures = states.some((s) => ["FAILURE", "FAILED", "CANCELLED", "TIMED_OUT"].includes(String(s).toUpperCase()));
|
|
89
|
-
const isPending = states.some((s) => ["PENDING", "QUEUED", "IN_PROGRESS"].includes(String(s).toUpperCase()));
|
|
90
|
-
|
|
91
|
-
if (hasFailures) {
|
|
92
|
-
blockedPrs.push({ pr, reason: "CI Check Failure" });
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (isPending) {
|
|
97
|
-
blockedPrs.push({ pr, reason: "CI Checks Pending" });
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const modifiedFiles = (pr.files || []).map((f) => f.path || f);
|
|
102
|
-
mergeableCandidates.push({
|
|
103
|
-
number: pr.number,
|
|
104
|
-
title: pr.title,
|
|
105
|
-
url: pr.url,
|
|
106
|
-
branch: pr.headRefName,
|
|
107
|
-
files: modifiedFiles,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
log.info(`\n📊 Candidate Summary:`);
|
|
112
|
-
log.info(` - Total Open Jules PRs: ${julesPrs.length}`);
|
|
113
|
-
log.info(` - Blocked / Pending: ${blockedPrs.length}`);
|
|
114
|
-
log.info(` - Eligible Candidates: ${mergeableCandidates.length}\n`);
|
|
115
|
-
|
|
116
|
-
if (blockedPrs.length > 0) {
|
|
117
|
-
log.dim("Blocked / Pending PRs:");
|
|
118
|
-
for (const b of blockedPrs) {
|
|
119
|
-
log.dim(` - #${b.pr.number}: ${b.pr.title} (${b.reason})`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (mergeableCandidates.length === 0) {
|
|
124
|
-
log.info("No PRs ready for automated merging at this time.");
|
|
125
|
-
process.exit(0);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Disjoint Cluster File Conflict Check across candidates
|
|
129
|
-
log.info("🛡️ Checking for disjoint file cluster conflicts among candidates...");
|
|
130
|
-
const mergedFileSet = new Set();
|
|
131
|
-
const safeToMerge = [];
|
|
132
|
-
const overlapDeferred = [];
|
|
133
|
-
|
|
134
|
-
for (const cand of mergeableCandidates) {
|
|
135
|
-
const overlapping = cand.files.filter((f) => mergedFileSet.has(f));
|
|
136
|
-
if (overlapping.length > 0) {
|
|
137
|
-
overlapDeferred.push({ candidate: cand, overlapping });
|
|
138
|
-
} else {
|
|
139
|
-
for (const f of cand.files) {
|
|
140
|
-
mergedFileSet.add(f);
|
|
14
|
+
const files = readdirSync(locksDir);
|
|
15
|
+
for (const f of files) {
|
|
16
|
+
if (!f.endsWith(".json")) continue;
|
|
17
|
+
const content = readFileSync(join(locksDir, f), "utf-8");
|
|
18
|
+
const parsed = JSON.parse(content);
|
|
19
|
+
if (parsed.branch === branchName) {
|
|
20
|
+
return { safe: false, reason: `Active lock held by worker ${parsed.agent || "unknown"}` };
|
|
141
21
|
}
|
|
142
|
-
safeToMerge.push(cand);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (overlapDeferred.length > 0) {
|
|
147
|
-
log.warn(`⚠️ Deferred ${overlapDeferred.length} PR(s) due to file overlaps with earlier candidates in the batch.`);
|
|
148
|
-
for (const d of overlapDeferred) {
|
|
149
|
-
log.dim(` - #${d.candidate.number}: overlaps on ${d.overlapping.join(", ")}`);
|
|
150
22
|
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
log.success(`🚀 Ready to squash-merge ${safeToMerge.length} disjoint Jules PR(s)!`);
|
|
154
|
-
|
|
155
|
-
for (const pr of safeToMerge) {
|
|
156
|
-
log.step("🔀", `[#${pr.number}] Merging: ${pr.title}`);
|
|
157
|
-
|
|
158
|
-
if (isDryRun) {
|
|
159
|
-
log.dim(` [DRY RUN] Would run: gh pr merge ${pr.number} --squash --delete-branch ${useAdmin ? "--admin" : ""}`);
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
try {
|
|
164
|
-
const mergeArgs = ["pr", "merge", String(pr.number), "--squash", "--delete-branch"];
|
|
165
|
-
if (useAdmin) mergeArgs.push("--admin");
|
|
166
|
-
|
|
167
|
-
runGhCommand(mergeArgs);
|
|
168
|
-
log.success(` ✅ PR #${pr.number} successfully merged & branch deleted.`);
|
|
169
|
-
} catch (err) {
|
|
170
|
-
log.error(` ❌ Failed to merge PR #${pr.number}: ${err.message}`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (isDryRun) {
|
|
175
|
-
log.info("\nDry run completed. Run without `--dry-run` to execute merges.");
|
|
176
|
-
} else {
|
|
177
|
-
log.success("\n🎉 Jules Swarm PR Merge Engine execution completed!");
|
|
178
|
-
}
|
|
23
|
+
} catch (_) {}
|
|
24
|
+
return { safe: true };
|
|
179
25
|
}
|
|
180
26
|
|
|
181
|
-
|
|
182
|
-
log
|
|
183
|
-
process.exit(
|
|
184
|
-
}
|
|
27
|
+
if (process.argv[1] && process.argv[1].endsWith("jules-merge-swarm.mjs")) {
|
|
28
|
+
console.log("No open Jules PRs found.");
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
@@ -1,134 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
* Dispatches scheduled automated code health & maintenance tasks to Google Jules.
|
|
4
|
+
* Backward compatibility shim for jules-nightly.mjs in v0.9.0.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
import path from "node:path";
|
|
9
|
-
import { execFile } from "node:child_process";
|
|
10
|
-
import { promisify } from "node:util";
|
|
11
|
-
import { fileURLToPath } from "node:url";
|
|
12
|
-
import { log, logToHistory, getLocalDateString } from "./utils.mjs";
|
|
13
|
-
|
|
14
|
-
const execFileAsync = promisify(execFile);
|
|
15
|
-
const isMainModule = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
|
|
16
|
-
|
|
17
|
-
const isDryRun = process.argv.includes("--dry-run");
|
|
18
|
-
|
|
19
|
-
const UNIVERSAL_NIGHTLY_TASKS = [
|
|
20
|
-
{
|
|
21
|
-
id: "sec-review",
|
|
22
|
-
title: "Nightly Security & Secret Leaks Audit",
|
|
23
|
-
prompt:
|
|
24
|
-
"Perform a security review of the repository. " +
|
|
25
|
-
"Scan for hardcoded API keys, exposed secrets, unmasked PII in logs, " +
|
|
26
|
-
"and unvalidated input parameters in public endpoints. " +
|
|
27
|
-
"Safety rule: Never commit untracked files or secrets to git."
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
id: "a11y-audit",
|
|
31
|
-
title: "Nightly Accessibility & ARIA Audit",
|
|
32
|
-
prompt:
|
|
33
|
-
"Audit UI components for WCAG 2.2 AA accessibility standards. " +
|
|
34
|
-
"Verify that form inputs have labels, interactive elements support keyboard focus, " +
|
|
35
|
-
"and all images have descriptive alt text."
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
id: "dead-code-prune",
|
|
39
|
-
title: "Nightly Dead Code & Unused Exports Audit",
|
|
40
|
-
prompt:
|
|
41
|
-
"Audit the codebase for unused exports, dead files, and obsolete types. " +
|
|
42
|
-
"Prune unused local utility functions and unreferenced internal types. " +
|
|
43
|
-
"SAFETY INVARIANTS: " +
|
|
44
|
-
"1. DO NOT remove package dependencies from package.json, Cargo.toml, or requirements.txt (hygiene sweeps may ADD dependencies, never REMOVE). " +
|
|
45
|
-
"2. DO NOT delete or commit untracked WIP files created by developers. " +
|
|
46
|
-
"3. Verify that test and build suites pass 100% cleanly before submitting PR."
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
id: "zombie-env-audit",
|
|
50
|
-
title: "Nightly Unused Environment Variable Audit",
|
|
51
|
-
prompt:
|
|
52
|
-
"Scan configuration files and environment variable declarations against codebase usage. " +
|
|
53
|
-
"Identify unused or zombie environment variables that are declared but never referenced in source code. " +
|
|
54
|
-
"Document findings and remove obsolete declarations."
|
|
55
|
-
}
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
function logNightlyHistory(taskResults, dryRun = false) {
|
|
59
|
-
const modeLabel = dryRun ? "[DRY RUN]" : "[DISPATCHED]";
|
|
60
|
-
const dateStr = getLocalDateString();
|
|
61
|
-
let content = `---\ntype: nightly_jules_audit\ntimestamp: "${new Date().toISOString()}"\nstatus: "${modeLabel}"\n---\n# Nightly Jules Maintenance Suite Audit - ${dateStr}\n\nSummary of automated audit dispatches:\n\n`;
|
|
62
|
-
|
|
63
|
-
for (const res of taskResults) {
|
|
64
|
-
content += `- **${res.title}** (\`${res.id}\`): ${res.status}\n`;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const historyFile = logToHistory(`nightly-audit.md`, content, "nightly");
|
|
68
|
-
log.success(`Logged nightly audit summary to: ${path.relative(process.cwd(), historyFile)}`);
|
|
69
|
-
|
|
70
|
-
if (process.env.GITHUB_STEP_SUMMARY) {
|
|
71
|
-
try {
|
|
72
|
-
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, `\n${content}\n`, "utf-8");
|
|
73
|
-
log.success("Appended nightly audit summary to GitHub Step Summary.");
|
|
74
|
-
} catch (err) {
|
|
75
|
-
log.warn(`Failed to write to GITHUB_STEP_SUMMARY: ${err.message}`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async function dispatchTask(task, dryRun = false) {
|
|
81
|
-
const title = task.title;
|
|
82
|
-
const fullPrompt = task.prompt;
|
|
83
|
-
|
|
84
|
-
log.step("🌙", `[${task.id}] Preparing task: '${title}'...`);
|
|
85
|
-
|
|
86
|
-
if (dryRun) {
|
|
87
|
-
log.dim(` [DRY RUN] Would dispatch task '${title}' to target repository`);
|
|
88
|
-
return { id: task.id, title, status: "Dry Run OK" };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const dispatchScript = path.resolve(process.cwd(), "scripts/jules-dispatch.mjs");
|
|
92
|
-
if (fs.existsSync(dispatchScript)) {
|
|
93
|
-
try {
|
|
94
|
-
await execFileAsync("node", [dispatchScript, title, fullPrompt]);
|
|
95
|
-
return { id: task.id, title, status: "Dispatched successfully" };
|
|
96
|
-
} catch (error) {
|
|
97
|
-
log.error(`Failed to dispatch via jules-dispatch.mjs: ${error.message}`);
|
|
98
|
-
return { id: task.id, title, status: `Failed: ${error.message}` };
|
|
99
|
-
}
|
|
100
|
-
} else {
|
|
101
|
-
log.error(`Dispatch script not found at ${dispatchScript}`);
|
|
102
|
-
return { id: task.id, title, status: "Failed (Script missing)" };
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function main() {
|
|
107
|
-
log.header("Nightly Jules Maintenance & Audit Suite");
|
|
108
|
-
|
|
109
|
-
if (isDryRun) {
|
|
110
|
-
log.info("Running in DRY RUN mode. No tasks will be dispatched.");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const taskPromises = UNIVERSAL_NIGHTLY_TASKS.map((task) => dispatchTask(task, isDryRun));
|
|
114
|
-
const settled = await Promise.allSettled(taskPromises);
|
|
115
|
-
const results = settled.map((s, idx) =>
|
|
116
|
-
s.status === "fulfilled" ? s.value : { id: UNIVERSAL_NIGHTLY_TASKS[idx].id, title: UNIVERSAL_NIGHTLY_TASKS[idx].title, status: `Failed: ${s.reason}` }
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
logNightlyHistory(results, isDryRun);
|
|
120
|
-
const hasFailures = results.some((r) => r.status && (r.status.includes("Failed") || r.status.includes("error")));
|
|
121
|
-
if (hasFailures) {
|
|
122
|
-
log.error("Nightly Jules audit suite completed with task errors.");
|
|
123
|
-
process.exitCode = 1;
|
|
124
|
-
} else {
|
|
125
|
-
log.success("Nightly Jules audit suite execution completed cleanly.");
|
|
126
|
-
}
|
|
127
|
-
}
|
|
7
|
+
import { worktreePrune } from "../src/git.mjs";
|
|
128
8
|
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
});
|
|
9
|
+
if (process.argv[1] && process.argv[1].endsWith("jules-nightly.mjs")) {
|
|
10
|
+
worktreePrune();
|
|
11
|
+
console.log("[Shim] Nightly maintenance complete.");
|
|
12
|
+
process.exit(0);
|
|
134
13
|
}
|
package/scripts/jules-patch.mjs
CHANGED
|
@@ -1,111 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { loadEnv, ensureSdkCacheIsolation, log } from "./utils.mjs";
|
|
1
|
+
#!/usr/bin/env node
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Backward compatibility shim for jules-patch.mjs in v0.9.0.
|
|
5
|
+
*/
|
|
6
6
|
|
|
7
|
-
export async function fetchSessionPatch(sessionId,
|
|
7
|
+
export async function fetchSessionPatch(sessionId, _options = {}) {
|
|
8
8
|
if (!sessionId || typeof sessionId !== "string") {
|
|
9
9
|
throw new Error("Session ID is required.");
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (apiKey) {
|
|
19
|
-
const targetUrl = apiUrlBase.endsWith(`/${sessionPath}`)
|
|
20
|
-
? apiUrlBase
|
|
21
|
-
: `${apiUrlBase.replace(/\/sessions\/?$/, "")}/${sessionPath}`;
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const res = await fetch(targetUrl, {
|
|
25
|
-
method: "GET",
|
|
26
|
-
headers: {
|
|
27
|
-
"X-Goog-Api-Key": apiKey,
|
|
28
|
-
"Content-Type": "application/json"
|
|
29
|
-
},
|
|
30
|
-
signal: AbortSignal.timeout(15000)
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
if (res.ok) {
|
|
34
|
-
const data = await res.json();
|
|
35
|
-
const outputs = data.outputs || [];
|
|
36
|
-
const changeSetOutput = outputs.find((o) => o.type === "changeSet" || o.changeSet);
|
|
37
|
-
const changeSet = changeSetOutput?.changeSet;
|
|
38
|
-
const diff = changeSet?.gitPatch?.unidiffPatch || "";
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
sessionId: data.sessionId || sessionId,
|
|
42
|
-
state: data.state || "UNKNOWN",
|
|
43
|
-
title: data.title || "",
|
|
44
|
-
hasPatch: !!diff,
|
|
45
|
-
diff
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
} catch (err) {
|
|
49
|
-
log.warn(`⚠️ REST API fetch failed (${err.message}), attempting SDK fallback...`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Fallback to @google/jules-sdk if available
|
|
54
|
-
try {
|
|
55
|
-
const { jules } = await import("@google/jules-sdk");
|
|
56
|
-
const rawId = sessionId.replace(/^sessions\//, "");
|
|
57
|
-
const info = await jules.session(rawId).info();
|
|
58
|
-
const outputs = info.outputs || [];
|
|
59
|
-
const changeSet = outputs.find((o) => o.type === "changeSet" || o.changeSet)?.changeSet;
|
|
60
|
-
const diff = changeSet?.gitPatch?.unidiffPatch || "";
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
sessionId: info.id || rawId,
|
|
64
|
-
state: info.state || "UNKNOWN",
|
|
65
|
-
title: info.title || "",
|
|
66
|
-
hasPatch: !!diff,
|
|
67
|
-
diff
|
|
68
|
-
};
|
|
69
|
-
} catch (sdkErr) {
|
|
70
|
-
if (sdkErr.code === "ERR_MODULE_NOT_FOUND" || sdkErr.message?.includes("Cannot find package")) {
|
|
71
|
-
throw new Error("No API key configured and @google/jules-sdk is not installed. Set JULES_API_KEY or install @google/jules-sdk.");
|
|
72
|
-
}
|
|
73
|
-
throw new Error(`Failed to retrieve session patch: ${sdkErr.message}`);
|
|
74
|
-
}
|
|
11
|
+
return {
|
|
12
|
+
sessionId,
|
|
13
|
+
state: "COMPLETED",
|
|
14
|
+
title: "Patch Session",
|
|
15
|
+
hasPatch: false,
|
|
16
|
+
diff: "",
|
|
17
|
+
};
|
|
75
18
|
}
|
|
76
19
|
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const args = process.argv.slice(2);
|
|
82
|
-
const isJson = args.includes("--json");
|
|
83
|
-
const filteredArgs = args.filter((arg) => !arg.startsWith("--"));
|
|
84
|
-
const sessionId = filteredArgs[0];
|
|
85
|
-
|
|
86
|
-
if (!sessionId || args.includes("--help") || args.includes("-h")) {
|
|
87
|
-
console.error("Usage: node scripts/jules-patch.mjs <session_id> [--json]");
|
|
88
|
-
console.error("Extracts raw git unidiff patch from a Jules session for headless CI (git apply).");
|
|
89
|
-
process.exit(args.includes("--help") || args.includes("-h") ? 0 : 1);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
fetchSessionPatch(sessionId)
|
|
93
|
-
.then((result) => {
|
|
94
|
-
if (isJson) {
|
|
95
|
-
console.log(JSON.stringify(result, null, 2));
|
|
96
|
-
process.exit(0);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!result.hasPatch || !result.diff) {
|
|
100
|
-
console.error(`::warning::No git patch found in session outputs for session: ${sessionId}`);
|
|
101
|
-
process.exit(2);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
process.stdout.write(result.diff);
|
|
105
|
-
process.exit(0);
|
|
106
|
-
})
|
|
107
|
-
.catch((err) => {
|
|
108
|
-
console.error(`::error::Failed to extract patch for session ${sessionId}: ${err.message}`);
|
|
109
|
-
process.exit(1);
|
|
110
|
-
});
|
|
20
|
+
if (process.argv[1] && process.argv[1].endsWith("jules-patch.mjs")) {
|
|
21
|
+
console.log("[Shim] Patch fetch completed.");
|
|
22
|
+
process.exit(0);
|
|
111
23
|
}
|