jules-orchestrator-kit 0.51.0 → 0.52.1
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 +5 -2
- package/bin/agentctl.mjs +176 -6
- package/index.mjs +10 -0
- package/package.json +1 -1
- package/scripts/jules-scan-todos.mjs +2 -2
- package/src/mcp.mjs +277 -0
- package/src/ops/command-registry.mjs +204 -7
- package/src/ops/next-step.mjs +2 -1
- package/src/provider.mjs +134 -5
- package/src/session-ops.mjs +312 -0
- package/src/stack-detector.mjs +31 -9
- package/src/wizard-oracle.mjs +42 -8
- package/src/wizard-task.mjs +4 -3
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ npx jules-orchestrator-kit init
|
|
|
59
59
|
```bash
|
|
60
60
|
# 2. Commit what init wrote — .agent/config.yml is on the gate's deny list by
|
|
61
61
|
# design, so leaving it uncommitted makes the first gate reject your tree
|
|
62
|
-
git add .agent AGENTS.md .gitignore && git commit -m "chore: add agent config"
|
|
62
|
+
git add .agent AGENTS.md SPEC.md CONSTRAINTS.md .gitignore && git commit -m "chore: add agent config"
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
```bash
|
|
@@ -143,7 +143,7 @@ To maximize PR merge rates, dispatch tasks according to deterministic boundaries
|
|
|
143
143
|
* **Fail-Closed Security & Secret Redaction:** Evaluates explicit Deny rules before Allow rules against canonicalized, case-folded paths. Redacts high-entropy keys and base64-encoded credentials (such as Kubernetes `Secret` manifests).
|
|
144
144
|
* **Complexity & Cost Router:** Zero-dependency heuristic classifier (`src/router.mjs`) routing mechanical tasks to lightweight models while reserving primary models for complex refactors, with a `node --check` syntax-verification gate that transparently escalates a FAST-tier result to the primary provider if it left broken JS on disk.
|
|
145
145
|
* **Terminal UI & Diagnostic Matrix (`agentctl doctor`):** Interactive terminal dashboard, task sidecar manager, and automated transactional self-repair.
|
|
146
|
-
* **Verified Test Suite:** Tested with **
|
|
146
|
+
* **Verified Test Suite:** Tested with **802 unit tests across 91 suites passing in < 10.0s**.
|
|
147
147
|
|
|
148
148
|
<br/>
|
|
149
149
|
|
|
@@ -165,6 +165,9 @@ To maximize PR merge rates, dispatch tasks according to deterministic boundaries
|
|
|
165
165
|
| `dispatch` | `agentctl dispatch [<prompt>] [-p <prompt>] [-f <file>] [-r <role>] [-t <tier>] [--author <name>] [--check-premise] [--auto-pr] [--repoless] [--dry-run]` | Dispatches autonomous task to the active provider with pre-flight idempotency checks, payload limits, and role prompt resolution. `--dry-run` stops short of the provider call and reports itself as a rehearsal rather than a dispatch. | `0` (Dispatched), `1` (Error) |
|
|
166
166
|
| `plan approve` | `agentctl plan approve <sessionId> [--dry-run] [--json]` | Approves pending execution plan for an active Jules session (`:approvePlan`) with automatic 404/503 retry backoff. | `0` (Approved), `1` (Error) |
|
|
167
167
|
| `session get` | `agentctl session get <sessionId> [--dry-run] [--json]` | Retrieves live session lifecycle state from provider REST API with token rotation. | `0` (Fetched), `1` (Error) |
|
|
168
|
+
| `patch` | `agentctl patch <sessionId> [--apply] [--save <path>] [--json]` | Extracts raw git diff patch from a completed Jules session and tests or applies it locally with `git apply --check` safety. | `0` (Clean/Applied), `1` (Conflict/Error) |
|
|
169
|
+
| `retry` | `agentctl retry <sessionId> [--role <role>] [--with-failure] [--json]` | Fetches error traces and activity logs from a failed session and synthesizes a targeted OODA retry dispatch. | `0` (Dispatched), `1` (Error) |
|
|
170
|
+
| `prune` | `agentctl prune [--age 7d] [--state <state>] [--delete] [--yes] [--json]` | Queries and batch-archives or deletes stale/completed sessions via Jules v1alpha API to keep workspaces clean. | `0` (Cleaned) |
|
|
168
171
|
| `pr harvest` | `agentctl pr harvest [--tier r0,r1] [--limit <n>] [--auto] [--allow-no-checks] [--dry-run]` | Discovers open agent PRs, evaluates CI checks & risk tiers, and auto-squashes green low-risk changes autonomously. A PR reporting **no** CI checks is skipped unless `--allow-no-checks` is passed, and an unavailable changed-file list blocks rather than classifying as low risk. | `0` (Triaged/Merged), `1` (Error) |
|
|
169
172
|
| `doctor` | `agentctl doctor [--json]` | Diagnostic DAG check runner & automated transactional self-repair engine. | `0` (Healthy), `1` (Failures) |
|
|
170
173
|
| `queue` | `agentctl queue [--dag] [--concurrency <n>] [--dry-run] [--json]` | Consumes and executes task envelopes in `.agent/jules-queue/` with Kahn's DAG dependency resolution. Non-task files (manifests, `README.md`) are skipped, and `--dry-run` previews without moving anything. | `0` (Complete) |
|
package/bin/agentctl.mjs
CHANGED
|
@@ -69,6 +69,9 @@ Commands:
|
|
|
69
69
|
resume Resume warm session with human response (--response "<text>")
|
|
70
70
|
plan approve <id> Approve pending plan for a Jules session (:approvePlan)
|
|
71
71
|
session get <id> Retrieve remote session status from provider API
|
|
72
|
+
patch <id> Extract and test/apply git patch from a Jules session (--apply, --save)
|
|
73
|
+
retry <id> Retry failed session with automated failure-trace injection (--role)
|
|
74
|
+
prune Batch-archive or delete stale sessions via Jules API (--age 7d, --yes)
|
|
72
75
|
pr harvest Triage, verify CI, and auto-merge low-risk agent PRs (--auto, --tier r0,r1)
|
|
73
76
|
escalate Dispatch or manage webhook escalation incidents (--flush, --status, --clear)
|
|
74
77
|
flaky Manage Wilson-quarantined tests and dispatch healing swarm (status | heal | reset)
|
|
@@ -225,9 +228,16 @@ async function main() {
|
|
|
225
228
|
process.exit(0);
|
|
226
229
|
}
|
|
227
230
|
|
|
228
|
-
//
|
|
231
|
+
// Resolve --help / -h on subcommands (e.g. `agentctl init --help`, `agentctl mutate --help`)
|
|
229
232
|
const subArgs = args.slice(1);
|
|
230
233
|
if (subArgs.includes("--help") || subArgs.includes("-h")) {
|
|
234
|
+
const { getCommandDescriptor, formatCommandHelp } = await import("../src/ops/command-registry.mjs");
|
|
235
|
+
const subSub = subArgs[0] && !subArgs[0].startsWith("-") ? `${command} ${subArgs[0]}` : command;
|
|
236
|
+
const desc = getCommandDescriptor(subSub) || getCommandDescriptor(command);
|
|
237
|
+
if (desc) {
|
|
238
|
+
console.log(formatCommandHelp(desc));
|
|
239
|
+
process.exit(0);
|
|
240
|
+
}
|
|
231
241
|
printHelp();
|
|
232
242
|
process.exit(0);
|
|
233
243
|
}
|
|
@@ -720,6 +730,147 @@ async function main() {
|
|
|
720
730
|
break;
|
|
721
731
|
}
|
|
722
732
|
|
|
733
|
+
case "patch": {
|
|
734
|
+
const { applySessionPatch, resolveRoot } = await import("../index.mjs");
|
|
735
|
+
const { values, positionals } = parseArgs({
|
|
736
|
+
args: args.slice(1),
|
|
737
|
+
options: {
|
|
738
|
+
session: { type: "string", short: "s" },
|
|
739
|
+
apply: { type: "boolean", short: "a" },
|
|
740
|
+
save: { type: "string" },
|
|
741
|
+
json: { type: "boolean", short: "j" },
|
|
742
|
+
},
|
|
743
|
+
allowPositionals: true,
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
const sessionId = values.session || positionals[0] || positionals[1];
|
|
747
|
+
if (!sessionId) {
|
|
748
|
+
console.error("❌ Error: Missing required session ID. Usage: agentctl patch <session_id> [--apply] [--save <path>]");
|
|
749
|
+
process.exit(1);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const root = resolveRoot();
|
|
753
|
+
const res = await applySessionPatch(sessionId, {
|
|
754
|
+
root,
|
|
755
|
+
apply: Boolean(values.apply),
|
|
756
|
+
save: values.save,
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
if (values.json) {
|
|
760
|
+
console.log(JSON.stringify(res, null, 2));
|
|
761
|
+
} else if (!res.ok) {
|
|
762
|
+
console.error(`\n❌ Failed to extract or apply patch for session ${sessionId}:`);
|
|
763
|
+
console.error(` ${res.error || "Unknown error"}\n`);
|
|
764
|
+
} else {
|
|
765
|
+
console.log(`\n📦 Session Git Patch (${sessionId})`);
|
|
766
|
+
console.log(`------------------------------------------------------------------`);
|
|
767
|
+
console.log(` Modified Files : ${res.files && res.files.length > 0 ? res.files.join(", ") : "None"}`);
|
|
768
|
+
console.log(` git apply --check : ${res.checkPassed ? "✅ CLEAN PASS" : "❌ CONFLICT"}`);
|
|
769
|
+
console.log(` Applied to Disk : ${res.patchApplied ? "✅ APPLIED" : "DRY-RUN ONLY (--apply to write)"}`);
|
|
770
|
+
if (values.save) {
|
|
771
|
+
console.log(` Saved to File : ${values.save}`);
|
|
772
|
+
}
|
|
773
|
+
if (!values.apply && !values.save && res.patch) {
|
|
774
|
+
console.log(`\n--- Patch Content ---\n${res.patch}\n---------------------\n`);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
process.exit(res.ok ? 0 : 1);
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
case "retry": {
|
|
783
|
+
const { retrySession, resolveRoot } = await import("../index.mjs");
|
|
784
|
+
const { values, positionals } = parseArgs({
|
|
785
|
+
args: args.slice(1),
|
|
786
|
+
options: {
|
|
787
|
+
session: { type: "string", short: "s" },
|
|
788
|
+
role: { type: "string", short: "r" },
|
|
789
|
+
title: { type: "string", short: "t" },
|
|
790
|
+
"without-failure": { type: "boolean" },
|
|
791
|
+
"dry-run": { type: "boolean", short: "d" },
|
|
792
|
+
json: { type: "boolean", short: "j" },
|
|
793
|
+
},
|
|
794
|
+
allowPositionals: true,
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
const sessionId = values.session || positionals[0] || positionals[1];
|
|
798
|
+
if (!sessionId) {
|
|
799
|
+
console.error("❌ Error: Missing required session ID. Usage: agentctl retry <session_id> [--role <role>]");
|
|
800
|
+
process.exit(1);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const root = resolveRoot();
|
|
804
|
+
const res = await retrySession(sessionId, {
|
|
805
|
+
root,
|
|
806
|
+
role: values.role,
|
|
807
|
+
title: values.title,
|
|
808
|
+
withFailure: values["without-failure"] ? false : true,
|
|
809
|
+
dryRun: Boolean(values["dry-run"]),
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
if (values.json) {
|
|
813
|
+
console.log(JSON.stringify(res, null, 2));
|
|
814
|
+
} else {
|
|
815
|
+
console.log(`\n🔄 Session Retry Dispatched!`);
|
|
816
|
+
console.log(`------------------------------------------------------------------`);
|
|
817
|
+
console.log(` Original Session : ${res.originalSessionId}`);
|
|
818
|
+
console.log(` New Session ID : ${res.newSession?.id || "N/A"}`);
|
|
819
|
+
console.log(` Failure Trace Added : ${res.failureReason ? "YES" : "NO"}`);
|
|
820
|
+
console.log(`------------------------------------------------------------------\n`);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
process.exit(res.ok ? 0 : 1);
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
case "prune": {
|
|
828
|
+
const { pruneSessions, resolveRoot } = await import("../index.mjs");
|
|
829
|
+
const { values } = parseArgs({
|
|
830
|
+
args: args.slice(1),
|
|
831
|
+
options: {
|
|
832
|
+
age: { type: "string", short: "a" },
|
|
833
|
+
state: { type: "string", short: "s" },
|
|
834
|
+
delete: { type: "boolean" },
|
|
835
|
+
"dry-run": { type: "boolean", short: "d" },
|
|
836
|
+
yes: { type: "boolean", short: "y" },
|
|
837
|
+
json: { type: "boolean", short: "j" },
|
|
838
|
+
},
|
|
839
|
+
allowPositionals: true,
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
const root = resolveRoot();
|
|
843
|
+
const dryRun = values["dry-run"] || (!values.yes && !values.delete);
|
|
844
|
+
const res = await pruneSessions({
|
|
845
|
+
root,
|
|
846
|
+
age: values.age || "7d",
|
|
847
|
+
state: values.state,
|
|
848
|
+
delete: Boolean(values.delete),
|
|
849
|
+
dryRun,
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
if (values.json) {
|
|
853
|
+
console.log(JSON.stringify(res, null, 2));
|
|
854
|
+
} else {
|
|
855
|
+
console.log(`\n🧹 Session Pruning Report (${dryRun ? "DRY RUN" : "EXECUTED"})`);
|
|
856
|
+
console.log(`------------------------------------------------------------------`);
|
|
857
|
+
console.log(` Filter Age Cutoff : ${values.age || "7d"}`);
|
|
858
|
+
console.log(` Filter Target State : ${values.state || "ALL"}`);
|
|
859
|
+
console.log(` Matched Sessions : ${res.matchedCount}`);
|
|
860
|
+
console.log(` Archived / Deleted : ${res.archivedCount}`);
|
|
861
|
+
console.log(`------------------------------------------------------------------`);
|
|
862
|
+
for (const s of res.sessions) {
|
|
863
|
+
console.log(` - [${s.action}] ${s.id} (State: ${s.state || "UNKNOWN"})`);
|
|
864
|
+
}
|
|
865
|
+
if (dryRun && res.matchedCount > 0) {
|
|
866
|
+
console.log(`\n💡 Re-run with --yes to archive matched sessions.\n`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
process.exit(0);
|
|
871
|
+
break;
|
|
872
|
+
}
|
|
873
|
+
|
|
723
874
|
case "assert": {
|
|
724
875
|
const { runAssertion, parseYaml } = await import("../index.mjs");
|
|
725
876
|
const { values } = parseArgs({
|
|
@@ -1092,6 +1243,9 @@ async function main() {
|
|
|
1092
1243
|
args: args.slice(1),
|
|
1093
1244
|
options: {
|
|
1094
1245
|
interactive: { type: "boolean", short: "i" },
|
|
1246
|
+
"non-interactive": { type: "boolean" },
|
|
1247
|
+
"no-interactive": { type: "boolean" },
|
|
1248
|
+
yes: { type: "boolean", short: "y" },
|
|
1095
1249
|
tier: { type: "string", short: "t" },
|
|
1096
1250
|
json: { type: "boolean", short: "j" },
|
|
1097
1251
|
"dry-run": { type: "boolean", short: "d" },
|
|
@@ -1100,9 +1254,11 @@ async function main() {
|
|
|
1100
1254
|
allowPositionals: true,
|
|
1101
1255
|
});
|
|
1102
1256
|
|
|
1257
|
+
const isInteractive = values.interactive !== false && !values["non-interactive"] && !values["no-interactive"] && !values.yes;
|
|
1258
|
+
|
|
1103
1259
|
const { runInitWizard } = await import("../src/wizard-init.mjs");
|
|
1104
1260
|
const res = await runInitWizard(root, {
|
|
1105
|
-
interactive:
|
|
1261
|
+
interactive: isInteractive,
|
|
1106
1262
|
// No `|| "pro"`: a hardcoded default here overrode both the tier picked
|
|
1107
1263
|
// in the menu and the tier already recorded in .agent/config.yml on a
|
|
1108
1264
|
// re-run. Undefined lets the wizard seed the menu from the existing
|
|
@@ -1123,7 +1279,11 @@ async function main() {
|
|
|
1123
1279
|
} else {
|
|
1124
1280
|
console.log(`✅ Onboarding complete! Manifest generated at ${res.configPath}`);
|
|
1125
1281
|
console.log(` Tier: ${res.plan.tier.toUpperCase()} (${res.plan.limits.concurrency} worker(s), ${res.plan.limits.daily_tasks} daily tasks)`);
|
|
1126
|
-
|
|
1282
|
+
if (res.plan.verify.test) {
|
|
1283
|
+
console.log(` Verification Test Command : "${res.plan.verify.test}"`);
|
|
1284
|
+
} else {
|
|
1285
|
+
console.log(` Verification Test Command : None detected (run "agentctl bootstrap" to create a test oracle)`);
|
|
1286
|
+
}
|
|
1127
1287
|
console.log(` Active Presets : ${res.plan.presets.join(", ")}`);
|
|
1128
1288
|
for (const item of scaffold.created) {
|
|
1129
1289
|
console.log(` Scaffolded : ${item}`);
|
|
@@ -1137,8 +1297,10 @@ async function main() {
|
|
|
1137
1297
|
// uncommitted meant the very first `agentctl gate` rejected the working
|
|
1138
1298
|
// tree for files init had just written, which reads as the tool
|
|
1139
1299
|
// catching the user cheating on step three.
|
|
1140
|
-
|
|
1141
|
-
|
|
1300
|
+
const rootContracts = ["SPEC.md", "CONSTRAINTS.md", "DESIGN.md"].filter((f) => existsSync(join(root, f)));
|
|
1301
|
+
const filesToAdd = [".agent", "AGENTS.md", ...rootContracts, ".gitignore"].filter((f) => existsSync(join(root, f)));
|
|
1302
|
+
console.log(`\n Commit the manifest and contracts so the gate does not read them as agent edits:`);
|
|
1303
|
+
console.log(` git add ${filesToAdd.join(" ")} && git commit -m "chore: add agent config"`);
|
|
1142
1304
|
|
|
1143
1305
|
const { resolveNextStep, renderNextStep } = await import("../src/ops/next-step.mjs");
|
|
1144
1306
|
const next = resolveNextStep(root);
|
|
@@ -1166,12 +1328,19 @@ async function main() {
|
|
|
1166
1328
|
"auto-pr": { type: "boolean" },
|
|
1167
1329
|
"require-plan-approval": { type: "boolean" },
|
|
1168
1330
|
repoless: { type: "boolean" },
|
|
1331
|
+
interactive: { type: "boolean", short: "i" },
|
|
1332
|
+
"non-interactive": { type: "boolean" },
|
|
1333
|
+
"no-interactive": { type: "boolean" },
|
|
1334
|
+
yes: { type: "boolean", short: "y" },
|
|
1169
1335
|
json: { type: "boolean", short: "j" },
|
|
1170
1336
|
"dry-run": { type: "boolean", short: "d" },
|
|
1171
1337
|
},
|
|
1172
1338
|
allowPositionals: true,
|
|
1173
1339
|
});
|
|
1174
1340
|
|
|
1341
|
+
const isNonInteractive = Boolean(values["non-interactive"] || values["no-interactive"] || values.yes);
|
|
1342
|
+
const isInteractive = values.interactive === true ? true : (isNonInteractive ? false : undefined);
|
|
1343
|
+
|
|
1175
1344
|
const { runTaskCreateWizard } = await import("../src/wizard-task.mjs");
|
|
1176
1345
|
const res = await runTaskCreateWizard(root, {
|
|
1177
1346
|
title: values.title,
|
|
@@ -1184,6 +1353,7 @@ async function main() {
|
|
|
1184
1353
|
autoPr: values["auto-pr"],
|
|
1185
1354
|
requirePlanApproval: values["require-plan-approval"],
|
|
1186
1355
|
repoless: values.repoless,
|
|
1356
|
+
interactive: isInteractive,
|
|
1187
1357
|
});
|
|
1188
1358
|
|
|
1189
1359
|
if (values.json) {
|
|
@@ -1331,7 +1501,7 @@ async function main() {
|
|
|
1331
1501
|
const todos = scanCodebaseForTodos(root);
|
|
1332
1502
|
console.log(`\n🔍 Scanned ${todos.length} TODO/FIXME annotation(s).`);
|
|
1333
1503
|
for (const t of todos.slice(0, 10)) {
|
|
1334
|
-
console.log(` - ${t.file}:${t.line} [${t.type}] ${t.text}`);
|
|
1504
|
+
console.log(` - ${t.file}:${t.line} [${t.type || t.tag || "TODO"}] ${t.text}`);
|
|
1335
1505
|
}
|
|
1336
1506
|
if (todos.length > 10) {
|
|
1337
1507
|
console.log(` ... and ${todos.length - 10} more.`);
|
package/index.mjs
CHANGED
|
@@ -229,3 +229,13 @@ export {
|
|
|
229
229
|
getHandoverDir,
|
|
230
230
|
HandoverError,
|
|
231
231
|
} from "./src/ops/handover.mjs";
|
|
232
|
+
|
|
233
|
+
// Jules Power-User Session Operations & Lifecycle Engine
|
|
234
|
+
export {
|
|
235
|
+
extractSessionPatch,
|
|
236
|
+
applySessionPatch,
|
|
237
|
+
retrySession,
|
|
238
|
+
pruneSessions,
|
|
239
|
+
parseAgeDuration,
|
|
240
|
+
} from "./src/session-ops.mjs";
|
|
241
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jules-orchestrator-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.1",
|
|
4
4
|
"description": "Zero-dependency safety gatekeeper, test oracle generator, and multi-agent coordination protocol for Google Jules (jules) autonomous agents.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,9 +24,9 @@ export function scanCodebaseForTodos(dir = process.cwd()) {
|
|
|
24
24
|
const lines = content.split("\n");
|
|
25
25
|
lines.forEach((line, idx) => {
|
|
26
26
|
if (line.includes("TODO:")) {
|
|
27
|
-
todos.push({ file: full, line: idx + 1, text: line.trim(), tag: "TODO", priority: "MEDIUM" });
|
|
27
|
+
todos.push({ file: full, line: idx + 1, text: line.trim(), tag: "TODO", type: "TODO", priority: "MEDIUM" });
|
|
28
28
|
} else if (line.includes("FIXME:")) {
|
|
29
|
-
todos.push({ file: full, line: idx + 1, text: line.trim(), tag: "FIXME", priority: "HIGH" });
|
|
29
|
+
todos.push({ file: full, line: idx + 1, text: line.trim(), tag: "FIXME", type: "FIXME", priority: "HIGH" });
|
|
30
30
|
}
|
|
31
31
|
});
|
|
32
32
|
}
|
package/src/mcp.mjs
CHANGED
|
@@ -8,6 +8,13 @@ import { reapOrphanedIntents, reapStaleMutexDirs } from "./journal.mjs";
|
|
|
8
8
|
import { readTelemetry } from "./telemetry.mjs";
|
|
9
9
|
import { ProgressBus } from "./mcp-progress.mjs";
|
|
10
10
|
import { KIT_VERSION } from "./version.mjs";
|
|
11
|
+
import { createProvider } from "./provider.mjs";
|
|
12
|
+
import {
|
|
13
|
+
extractSessionPatch,
|
|
14
|
+
applySessionPatch,
|
|
15
|
+
retrySession,
|
|
16
|
+
pruneSessions,
|
|
17
|
+
} from "./session-ops.mjs";
|
|
11
18
|
|
|
12
19
|
export const MCP_SERVER_INFO = {
|
|
13
20
|
name: "jules-orchestrator-kit",
|
|
@@ -196,6 +203,113 @@ export const MCP_TOOLS = [
|
|
|
196
203
|
required: ["trigger", "solution"],
|
|
197
204
|
},
|
|
198
205
|
},
|
|
206
|
+
{
|
|
207
|
+
name: "jules_list_sessions",
|
|
208
|
+
description: "List remote Jules coding sessions with optional pagination and state filtering.",
|
|
209
|
+
inputSchema: {
|
|
210
|
+
type: "object",
|
|
211
|
+
properties: {
|
|
212
|
+
pageSize: { type: "number", description: "Maximum number of sessions to return (default 50)" },
|
|
213
|
+
pageToken: { type: "string", description: "Page token for cursor pagination" },
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: "jules_list_activities",
|
|
219
|
+
description: "List execution activities, logs, and error outputs for a specific Jules session.",
|
|
220
|
+
inputSchema: {
|
|
221
|
+
type: "object",
|
|
222
|
+
properties: {
|
|
223
|
+
sessionId: { type: "string", description: "Jules session ID" },
|
|
224
|
+
pageSize: { type: "number", description: "Maximum number of activities to return" },
|
|
225
|
+
pageToken: { type: "string", description: "Page token for pagination" },
|
|
226
|
+
},
|
|
227
|
+
required: ["sessionId"],
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "jules_get_session_output",
|
|
232
|
+
description: "Extract the git diff patch, pull request metadata, and changed files from a Jules session.",
|
|
233
|
+
inputSchema: {
|
|
234
|
+
type: "object",
|
|
235
|
+
properties: {
|
|
236
|
+
sessionId: { type: "string", description: "Jules session ID" },
|
|
237
|
+
},
|
|
238
|
+
required: ["sessionId"],
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: "jules_archive_session",
|
|
243
|
+
description: "Archive a Jules coding session to clean up the dashboard.",
|
|
244
|
+
inputSchema: {
|
|
245
|
+
type: "object",
|
|
246
|
+
properties: {
|
|
247
|
+
sessionId: { type: "string", description: "Jules session ID to archive" },
|
|
248
|
+
},
|
|
249
|
+
required: ["sessionId"],
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: "jules_delete_session",
|
|
254
|
+
description: "Permanently delete a Jules coding session.",
|
|
255
|
+
inputSchema: {
|
|
256
|
+
type: "object",
|
|
257
|
+
properties: {
|
|
258
|
+
sessionId: { type: "string", description: "Jules session ID to delete" },
|
|
259
|
+
},
|
|
260
|
+
required: ["sessionId"],
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "jules_retry_session",
|
|
265
|
+
description: "Retry a failed Jules session by extracting failure diagnostics and dispatching a new OODA repair session.",
|
|
266
|
+
inputSchema: {
|
|
267
|
+
type: "object",
|
|
268
|
+
properties: {
|
|
269
|
+
sessionId: { type: "string", description: "Original failed Jules session ID" },
|
|
270
|
+
role: { type: "string", description: "Optional specialist role override" },
|
|
271
|
+
withFailure: { type: "boolean", description: "Include previous failure diagnostic in retry prompt (default true)" },
|
|
272
|
+
},
|
|
273
|
+
required: ["sessionId"],
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: "jules_apply_patch",
|
|
278
|
+
description: "Extract and apply a Jules session's git patch to the local workspace with git apply safety verification.",
|
|
279
|
+
inputSchema: {
|
|
280
|
+
type: "object",
|
|
281
|
+
properties: {
|
|
282
|
+
sessionId: { type: "string", description: "Jules session ID to extract patch from" },
|
|
283
|
+
apply: { type: "boolean", description: "If true, applies the patch; if false, runs git apply --check dry-run" },
|
|
284
|
+
save: { type: "string", description: "Optional file path to save raw patch diff to" },
|
|
285
|
+
},
|
|
286
|
+
required: ["sessionId"],
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: "jules_list_sources",
|
|
291
|
+
description: "List connected GitHub repository sources available to the Jules account.",
|
|
292
|
+
inputSchema: {
|
|
293
|
+
type: "object",
|
|
294
|
+
properties: {
|
|
295
|
+
pageSize: { type: "number", description: "Maximum number of sources to return" },
|
|
296
|
+
pageToken: { type: "string", description: "Page token for pagination" },
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: "jules_prune_sessions",
|
|
302
|
+
description: "Batch-archive or prune old/completed Jules sessions matching age and state filters.",
|
|
303
|
+
inputSchema: {
|
|
304
|
+
type: "object",
|
|
305
|
+
properties: {
|
|
306
|
+
age: { type: "string", description: "Age cutoff string (e.g. '7d', '24h', '30m')" },
|
|
307
|
+
state: { type: "string", description: "Target session state to filter (e.g. 'FAILED', 'COMPLETED')" },
|
|
308
|
+
dryRun: { type: "boolean", description: "If true, previews sessions without modifying (default true)" },
|
|
309
|
+
delete: { type: "boolean", description: "If true, permanently deletes instead of archiving" },
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
},
|
|
199
313
|
];
|
|
200
314
|
|
|
201
315
|
export async function handleMcpRequest(request, opts = {}) {
|
|
@@ -431,6 +545,169 @@ export async function handleMcpRequest(request, opts = {}) {
|
|
|
431
545
|
};
|
|
432
546
|
}
|
|
433
547
|
|
|
548
|
+
if (toolName === "jules_list_sessions") {
|
|
549
|
+
const provider = createProvider(config.provider || "jules", config);
|
|
550
|
+
const res = await provider.listSessions({ root, pageSize: args.pageSize, pageToken: args.pageToken });
|
|
551
|
+
return {
|
|
552
|
+
jsonrpc: "2.0",
|
|
553
|
+
id,
|
|
554
|
+
result: {
|
|
555
|
+
content: [{ type: "text", text: JSON.stringify({ ok: true, ...res }, null, 2) }],
|
|
556
|
+
},
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (toolName === "jules_list_activities") {
|
|
561
|
+
if (!args.sessionId) {
|
|
562
|
+
return {
|
|
563
|
+
jsonrpc: "2.0",
|
|
564
|
+
id,
|
|
565
|
+
error: { code: -32602, message: "Missing required parameter 'sessionId'" },
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
const provider = createProvider(config.provider || "jules", config);
|
|
569
|
+
const res = await provider.listActivities(args.sessionId, { root, pageSize: args.pageSize, pageToken: args.pageToken });
|
|
570
|
+
return {
|
|
571
|
+
jsonrpc: "2.0",
|
|
572
|
+
id,
|
|
573
|
+
result: {
|
|
574
|
+
content: [{ type: "text", text: JSON.stringify({ ok: true, ...res }, null, 2) }],
|
|
575
|
+
},
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (toolName === "jules_get_session_output") {
|
|
580
|
+
if (!args.sessionId) {
|
|
581
|
+
return {
|
|
582
|
+
jsonrpc: "2.0",
|
|
583
|
+
id,
|
|
584
|
+
error: { code: -32602, message: "Missing required parameter 'sessionId'" },
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
const res = await extractSessionPatch(args.sessionId, { root, config });
|
|
588
|
+
return {
|
|
589
|
+
jsonrpc: "2.0",
|
|
590
|
+
id,
|
|
591
|
+
result: {
|
|
592
|
+
content: [{ type: "text", text: JSON.stringify(res, null, 2) }],
|
|
593
|
+
},
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (toolName === "jules_archive_session") {
|
|
598
|
+
if (!args.sessionId) {
|
|
599
|
+
return {
|
|
600
|
+
jsonrpc: "2.0",
|
|
601
|
+
id,
|
|
602
|
+
error: { code: -32602, message: "Missing required parameter 'sessionId'" },
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
const provider = createProvider(config.provider || "jules", config);
|
|
606
|
+
const res = await provider.archiveSession(args.sessionId, { root });
|
|
607
|
+
return {
|
|
608
|
+
jsonrpc: "2.0",
|
|
609
|
+
id,
|
|
610
|
+
result: {
|
|
611
|
+
content: [{ type: "text", text: JSON.stringify({ ok: true, ...res }, null, 2) }],
|
|
612
|
+
},
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (toolName === "jules_delete_session") {
|
|
617
|
+
if (!args.sessionId) {
|
|
618
|
+
return {
|
|
619
|
+
jsonrpc: "2.0",
|
|
620
|
+
id,
|
|
621
|
+
error: { code: -32602, message: "Missing required parameter 'sessionId'" },
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
const provider = createProvider(config.provider || "jules", config);
|
|
625
|
+
const res = await provider.deleteSession(args.sessionId, { root });
|
|
626
|
+
return {
|
|
627
|
+
jsonrpc: "2.0",
|
|
628
|
+
id,
|
|
629
|
+
result: {
|
|
630
|
+
content: [{ type: "text", text: JSON.stringify({ ok: true, ...res }, null, 2) }],
|
|
631
|
+
},
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (toolName === "jules_retry_session") {
|
|
636
|
+
if (!args.sessionId) {
|
|
637
|
+
return {
|
|
638
|
+
jsonrpc: "2.0",
|
|
639
|
+
id,
|
|
640
|
+
error: { code: -32602, message: "Missing required parameter 'sessionId'" },
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
const res = await retrySession(args.sessionId, {
|
|
644
|
+
root,
|
|
645
|
+
config,
|
|
646
|
+
role: args.role,
|
|
647
|
+
withFailure: args.withFailure !== false,
|
|
648
|
+
});
|
|
649
|
+
return {
|
|
650
|
+
jsonrpc: "2.0",
|
|
651
|
+
id,
|
|
652
|
+
result: {
|
|
653
|
+
content: [{ type: "text", text: JSON.stringify(res, null, 2) }],
|
|
654
|
+
},
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (toolName === "jules_apply_patch") {
|
|
659
|
+
if (!args.sessionId) {
|
|
660
|
+
return {
|
|
661
|
+
jsonrpc: "2.0",
|
|
662
|
+
id,
|
|
663
|
+
error: { code: -32602, message: "Missing required parameter 'sessionId'" },
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
const res = await applySessionPatch(args.sessionId, {
|
|
667
|
+
root,
|
|
668
|
+
config,
|
|
669
|
+
apply: Boolean(args.apply),
|
|
670
|
+
save: args.save,
|
|
671
|
+
});
|
|
672
|
+
return {
|
|
673
|
+
jsonrpc: "2.0",
|
|
674
|
+
id,
|
|
675
|
+
result: {
|
|
676
|
+
content: [{ type: "text", text: JSON.stringify(res, null, 2) }],
|
|
677
|
+
},
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (toolName === "jules_list_sources") {
|
|
682
|
+
const provider = createProvider(config.provider || "jules", config);
|
|
683
|
+
const res = await provider.listSources({ root, pageSize: args.pageSize, pageToken: args.pageToken });
|
|
684
|
+
return {
|
|
685
|
+
jsonrpc: "2.0",
|
|
686
|
+
id,
|
|
687
|
+
result: {
|
|
688
|
+
content: [{ type: "text", text: JSON.stringify({ ok: true, ...res }, null, 2) }],
|
|
689
|
+
},
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (toolName === "jules_prune_sessions") {
|
|
694
|
+
const res = await pruneSessions({
|
|
695
|
+
root,
|
|
696
|
+
config,
|
|
697
|
+
age: args.age || "7d",
|
|
698
|
+
state: args.state,
|
|
699
|
+
delete: Boolean(args.delete),
|
|
700
|
+
dryRun: args.dryRun !== false,
|
|
701
|
+
});
|
|
702
|
+
return {
|
|
703
|
+
jsonrpc: "2.0",
|
|
704
|
+
id,
|
|
705
|
+
result: {
|
|
706
|
+
content: [{ type: "text", text: JSON.stringify(res, null, 2) }],
|
|
707
|
+
},
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
434
711
|
return {
|
|
435
712
|
jsonrpc: "2.0",
|
|
436
713
|
id,
|