jules-orchestrator-kit 0.22.5 ā 0.22.6
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 +5 -3
- package/index.mjs +1 -0
- package/package.json +1 -1
- package/src/git.mjs +22 -1
- package/src/journal.mjs +176 -0
- package/src/mcp.mjs +4 -1
package/bin/agentctl.mjs
CHANGED
|
@@ -7,13 +7,14 @@ import { loadConfig, resolveRoot, detectStack } from "../src/config.mjs";
|
|
|
7
7
|
import { gate, dispatch, run, isTaskFile } from "../src/engine.mjs";
|
|
8
8
|
import { acquireLock, releaseLock, lockStatus, checkDailyBudget, getQueueDir, ensureDir } from "../src/state.mjs";
|
|
9
9
|
import { worktreePrune } from "../src/git.mjs";
|
|
10
|
+
import { reapOrphanedIntents } from "../src/journal.mjs";
|
|
10
11
|
|
|
11
12
|
const args = process.argv.slice(2);
|
|
12
13
|
const command = args[0];
|
|
13
14
|
|
|
14
15
|
function printHelp() {
|
|
15
16
|
console.log(`
|
|
16
|
-
š agentctl v0.22.
|
|
17
|
+
š agentctl v0.22.6 ā Universal Agent Orchestrator & Safety Gatekeeper
|
|
17
18
|
|
|
18
19
|
Usage: agentctl <command> [options]
|
|
19
20
|
|
|
@@ -43,11 +44,12 @@ async function main() {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
if (command === "version" || command === "--version" || command === "-v") {
|
|
46
|
-
console.log("agentctl v0.22.
|
|
47
|
+
console.log("agentctl v0.22.6");
|
|
47
48
|
process.exit(0);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
const root = resolveRoot();
|
|
52
|
+
reapOrphanedIntents(root);
|
|
51
53
|
const config = loadConfig(root);
|
|
52
54
|
|
|
53
55
|
switch (command) {
|
|
@@ -223,7 +225,7 @@ async function main() {
|
|
|
223
225
|
}
|
|
224
226
|
|
|
225
227
|
case "doctor": {
|
|
226
|
-
console.log(`\nš agentctl System Diagnostics (v0.22.
|
|
228
|
+
console.log(`\nš agentctl System Diagnostics (v0.22.6)`);
|
|
227
229
|
console.log(`--------------------------------------------------`);
|
|
228
230
|
console.log(` Project Root : ${root}`);
|
|
229
231
|
console.log(` Config File : ${config._file || "None (Using defaults)"}`);
|
package/index.mjs
CHANGED
|
@@ -50,6 +50,7 @@ export { checkAssetIntegrity } from "./src/asset_integrity.mjs";
|
|
|
50
50
|
export { classifyRiskTier, RISK_TIERS } from "./src/risk.mjs";
|
|
51
51
|
export { checkRulesBudget } from "./src/rules_budget.mjs";
|
|
52
52
|
export { DagExecutor, DagCycleError } from "./src/dag-engine.mjs";
|
|
53
|
+
export { journalIntent, journalDone, reapOrphanedIntents } from "./src/journal.mjs";
|
|
53
54
|
|
|
54
55
|
// Legacy SDK shims for backward compatibility
|
|
55
56
|
export { resolveProjectCommands, resolveWorkspaceExecutionBoundary } from "./scripts/command-resolver.mjs";
|
package/package.json
CHANGED
package/src/git.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { sep } from "node:path";
|
|
3
|
+
import { journalIntent, journalDone } from "./journal.mjs";
|
|
3
4
|
|
|
4
5
|
export class GateError extends Error {
|
|
5
6
|
constructor(message, opts = {}) {
|
|
@@ -174,8 +175,28 @@ export function showFromOrigin(root = process.cwd(), base = "main", filePath = "
|
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
export function createBranch(root = process.cwd(), branch = "") {
|
|
179
|
+
const opId = journalIntent(root, { type: "create_branch", branch, targetPath: "" });
|
|
180
|
+
try {
|
|
181
|
+
const res = git(["branch", branch], { cwd: root });
|
|
182
|
+
journalDone(root, opId);
|
|
183
|
+
return res;
|
|
184
|
+
} catch (err) {
|
|
185
|
+
journalDone(root, opId);
|
|
186
|
+
throw err;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
177
190
|
export function worktreeAdd(root = process.cwd(), branch = "agent/task", targetDir = "") {
|
|
178
|
-
|
|
191
|
+
const opId = journalIntent(root, { type: "worktree_add", targetPath: targetDir, branch });
|
|
192
|
+
try {
|
|
193
|
+
const res = git(["worktree", "add", targetDir, "-b", branch], { cwd: root });
|
|
194
|
+
journalDone(root, opId);
|
|
195
|
+
return res;
|
|
196
|
+
} catch (err) {
|
|
197
|
+
journalDone(root, opId);
|
|
198
|
+
throw err;
|
|
199
|
+
}
|
|
179
200
|
}
|
|
180
201
|
|
|
181
202
|
export function worktreeRemove(root = process.cwd(), targetDir = "") {
|
package/src/journal.mjs
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { openSync, writeSync, fsyncSync, closeSync, existsSync, readFileSync, unlinkSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { getStateDir, getProcessStartTime, isPidAlive, getLockDir } from "./state.mjs";
|
|
5
|
+
import { worktreeRemove, worktreePrune } from "./git.mjs";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Appends an intent record to .agent/state/journal.jsonl before a mutating operation.
|
|
9
|
+
* @param {string} root - Project root directory
|
|
10
|
+
* @param {Object|string} op - Operation payload or name
|
|
11
|
+
* @returns {string} opId - Unique operation identifier
|
|
12
|
+
*/
|
|
13
|
+
export function journalIntent(root, op) {
|
|
14
|
+
const stateDir = getStateDir(root);
|
|
15
|
+
const journalPath = join(stateDir, "journal.jsonl");
|
|
16
|
+
|
|
17
|
+
const opObj = typeof op === "string" ? { type: op } : op || {};
|
|
18
|
+
const opId = opObj.opId || `op-${randomUUID()}`;
|
|
19
|
+
const pid = process.pid;
|
|
20
|
+
const processStartTime = getProcessStartTime(pid);
|
|
21
|
+
|
|
22
|
+
const record = {
|
|
23
|
+
opId,
|
|
24
|
+
event: "intent",
|
|
25
|
+
type: opObj.type || opObj.operation || "unknown",
|
|
26
|
+
targetPath: opObj.targetPath || opObj.targetDir || opObj.path || "",
|
|
27
|
+
pid,
|
|
28
|
+
processStartTime,
|
|
29
|
+
timestamp: new Date().toISOString(),
|
|
30
|
+
...opObj,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const fd = openSync(journalPath, "a");
|
|
34
|
+
try {
|
|
35
|
+
writeSync(fd, JSON.stringify(record) + "\n", "utf-8");
|
|
36
|
+
fsyncSync(fd);
|
|
37
|
+
} finally {
|
|
38
|
+
closeSync(fd);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return opId;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Appends a journal_done record to .agent/state/journal.jsonl upon operation completion.
|
|
46
|
+
* @param {string} root - Project root directory
|
|
47
|
+
* @param {string} opId - Operation identifier returned by journalIntent
|
|
48
|
+
* @returns {string|null} opId
|
|
49
|
+
*/
|
|
50
|
+
export function journalDone(root, opId) {
|
|
51
|
+
if (!opId) return null;
|
|
52
|
+
const stateDir = getStateDir(root);
|
|
53
|
+
const journalPath = join(stateDir, "journal.jsonl");
|
|
54
|
+
|
|
55
|
+
const record = {
|
|
56
|
+
opId,
|
|
57
|
+
event: "journal_done",
|
|
58
|
+
timestamp: new Date().toISOString(),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const fd = openSync(journalPath, "a");
|
|
62
|
+
try {
|
|
63
|
+
writeSync(fd, JSON.stringify(record) + "\n", "utf-8");
|
|
64
|
+
fsyncSync(fd);
|
|
65
|
+
} finally {
|
|
66
|
+
closeSync(fd);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return opId;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Scans .agent/state/journal.jsonl for uncompleted intents belonging to dead PIDs,
|
|
74
|
+
* prunes orphaned worktrees, removes stale lock files, and marks intents reaped.
|
|
75
|
+
* Idempotent: Subsequent calls result in 0 additional reaps.
|
|
76
|
+
* @param {string} root - Project root directory
|
|
77
|
+
* @returns {{ reapedCount: number, reaped: Array<Object> }}
|
|
78
|
+
*/
|
|
79
|
+
export function reapOrphanedIntents(root) {
|
|
80
|
+
const stateDir = getStateDir(root);
|
|
81
|
+
const journalPath = join(stateDir, "journal.jsonl");
|
|
82
|
+
|
|
83
|
+
if (!existsSync(journalPath)) {
|
|
84
|
+
return { reapedCount: 0, reaped: [] };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let lines = [];
|
|
88
|
+
try {
|
|
89
|
+
const raw = readFileSync(journalPath, "utf-8");
|
|
90
|
+
lines = raw.split("\n").filter(Boolean);
|
|
91
|
+
} catch (_) {
|
|
92
|
+
return { reapedCount: 0, reaped: [] };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const intents = new Map();
|
|
96
|
+
const finishedOpIds = new Set();
|
|
97
|
+
|
|
98
|
+
for (const line of lines) {
|
|
99
|
+
try {
|
|
100
|
+
const entry = JSON.parse(line);
|
|
101
|
+
if (!entry || !entry.opId) continue;
|
|
102
|
+
if (entry.event === "intent") {
|
|
103
|
+
intents.set(entry.opId, entry);
|
|
104
|
+
} else if (entry.event === "journal_done" || entry.event === "journal_reaped") {
|
|
105
|
+
finishedOpIds.add(entry.opId);
|
|
106
|
+
}
|
|
107
|
+
} catch (_) {
|
|
108
|
+
// Ignore corrupted or partial lines
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const reaped = [];
|
|
113
|
+
|
|
114
|
+
for (const [opId, intent] of intents.entries()) {
|
|
115
|
+
if (finishedOpIds.has(opId)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const alive = isPidAlive(intent.pid, intent.processStartTime);
|
|
120
|
+
if (!alive) {
|
|
121
|
+
// Owner PID is dead or recycled -> reap orphaned intent
|
|
122
|
+
if (intent.targetPath) {
|
|
123
|
+
try {
|
|
124
|
+
worktreeRemove(root, intent.targetPath);
|
|
125
|
+
} catch (_) {}
|
|
126
|
+
try {
|
|
127
|
+
worktreePrune(root);
|
|
128
|
+
} catch (_) {}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Clean up stale lock files associated with this PID or targetPath
|
|
132
|
+
try {
|
|
133
|
+
const lockDir = getLockDir(root);
|
|
134
|
+
if (existsSync(lockDir)) {
|
|
135
|
+
const lockFiles = readdirSync(lockDir);
|
|
136
|
+
for (const file of lockFiles) {
|
|
137
|
+
if (file.endsWith(".json")) {
|
|
138
|
+
const lockPath = join(lockDir, file);
|
|
139
|
+
try {
|
|
140
|
+
const lockContent = JSON.parse(readFileSync(lockPath, "utf-8"));
|
|
141
|
+
if (
|
|
142
|
+
lockContent.pid === intent.pid ||
|
|
143
|
+
(intent.targetPath && Array.isArray(lockContent.files) && lockContent.files.includes(intent.targetPath))
|
|
144
|
+
) {
|
|
145
|
+
unlinkSync(lockPath);
|
|
146
|
+
}
|
|
147
|
+
} catch (_) {}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} catch (_) {}
|
|
152
|
+
|
|
153
|
+
// Record journal_reaped to guarantee idempotency
|
|
154
|
+
const reapedRecord = {
|
|
155
|
+
opId,
|
|
156
|
+
event: "journal_reaped",
|
|
157
|
+
reapedAt: new Date().toISOString(),
|
|
158
|
+
pid: intent.pid,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const fd = openSync(journalPath, "a");
|
|
163
|
+
try {
|
|
164
|
+
writeSync(fd, JSON.stringify(reapedRecord) + "\n", "utf-8");
|
|
165
|
+
fsyncSync(fd);
|
|
166
|
+
} finally {
|
|
167
|
+
closeSync(fd);
|
|
168
|
+
}
|
|
169
|
+
} catch (_) {}
|
|
170
|
+
|
|
171
|
+
reaped.push(intent);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return { reapedCount: reaped.length, reaped };
|
|
176
|
+
}
|
package/src/mcp.mjs
CHANGED
|
@@ -3,10 +3,11 @@ import { loadConfig, resolveRoot, detectStack } from "./config.mjs";
|
|
|
3
3
|
import { gate, dispatch } from "./engine.mjs";
|
|
4
4
|
import { classifyRiskTier } from "./risk.mjs";
|
|
5
5
|
import { checkDailyBudget, lockStatus } from "./state.mjs";
|
|
6
|
+
import { reapOrphanedIntents } from "./journal.mjs";
|
|
6
7
|
|
|
7
8
|
export const MCP_SERVER_INFO = {
|
|
8
9
|
name: "jules-orchestrator-kit",
|
|
9
|
-
version: "0.
|
|
10
|
+
version: "0.22.6",
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
export const MAX_MCP_FRAME_SIZE = 4 * 1024 * 1024; // 4 MB memory safety ceiling
|
|
@@ -352,6 +353,8 @@ export function writeMcpFrame(targetOutput, data) {
|
|
|
352
353
|
}
|
|
353
354
|
|
|
354
355
|
export function startMcpServer(input = process.stdin, output = process.stdout, opts = {}) {
|
|
356
|
+
const root = opts.root || resolveRoot();
|
|
357
|
+
reapOrphanedIntents(root);
|
|
355
358
|
isolateMcpStdout(process.stdout, opts);
|
|
356
359
|
if (output && output !== process.stdout) {
|
|
357
360
|
isolateMcpStdout(output, opts);
|