jules-orchestrator-kit 0.29.0 → 0.31.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/README.md +174 -89
- package/bin/agentctl.mjs +205 -6
- package/index.mjs +21 -1
- package/package.json +1 -1
- package/src/config.mjs +18 -2
- package/src/dag-engine.mjs +76 -0
- package/src/dashboard.mjs +2 -2
- package/src/engine.mjs +296 -37
- package/src/envelope.mjs +24 -2
- package/src/mcp.mjs +35 -1
- package/src/ops/checkpoint.mjs +167 -0
- package/src/ops/command-registry.mjs +274 -0
- package/src/ops/doctor-planner.mjs +214 -0
- package/src/ops/doctor-registry.mjs +442 -0
- package/src/ops/ide-scaffold.mjs +139 -0
- package/src/ops/receipts.mjs +171 -0
- package/src/ops/swarm-actions.mjs +156 -0
- package/src/ops/task-actions.mjs +214 -0
- package/src/ops/tdd-generator.mjs +115 -0
- package/src/ops/transaction.mjs +278 -0
- package/src/provider.mjs +164 -8
- package/src/security.mjs +15 -7
- package/src/stack-detector.mjs +15 -5
- package/src/state.mjs +44 -7
- package/src/task-optimizer.mjs +306 -0
- package/src/telemetry.mjs +35 -2
- package/src/tui.mjs +4 -1
- package/src/ux/capabilities.mjs +180 -0
- package/src/ux/diff-viewer.mjs +289 -0
- package/src/ux/key-decoder.mjs +281 -0
- package/src/ux/layout.mjs +195 -0
- package/src/ux/log-viewer.mjs +110 -0
- package/src/ux/palette.mjs +179 -0
- package/src/ux/queue-model.mjs +206 -0
- package/src/ux/renderer.mjs +188 -0
- package/src/ux/swarm-model.mjs +146 -0
- package/src/ux/terminal-session.mjs +215 -0
- package/src/ux/widgets.mjs +323 -0
- package/src/webhook.mjs +120 -0
- package/src/wizard-init.mjs +41 -11
- package/src/wizard-task.mjs +51 -15
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {"low" | "moderate" | "high" | "destructive"} RiskLevel
|
|
3
|
+
* @typedef {"Create" | "Inspect" | "Operate" | "Repair" | "Configure"} CommandCategory
|
|
4
|
+
*
|
|
5
|
+
* @typedef {Object} CommandFlag
|
|
6
|
+
* @property {string} name
|
|
7
|
+
* @property {"boolean" | "string"} type
|
|
8
|
+
* @property {string} description
|
|
9
|
+
* @property {any} [default]
|
|
10
|
+
*
|
|
11
|
+
* @typedef {Object} CommandDescriptor
|
|
12
|
+
* @property {string} id
|
|
13
|
+
* @property {string[]} path
|
|
14
|
+
* @property {string} title
|
|
15
|
+
* @property {string} description
|
|
16
|
+
* @property {CommandCategory} category
|
|
17
|
+
* @property {boolean} mutates
|
|
18
|
+
* @property {RiskLevel} risk
|
|
19
|
+
* @property {"never" | "optional" | "authoring-auto"} interactive
|
|
20
|
+
* @property {boolean} requiresRepository
|
|
21
|
+
* @property {string[]} shortcuts
|
|
22
|
+
* @property {string[]} examples
|
|
23
|
+
* @property {CommandFlag[]} flags
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** @type {CommandDescriptor[]} */
|
|
27
|
+
export const COMMAND_REGISTRY = [
|
|
28
|
+
{
|
|
29
|
+
id: "doctor",
|
|
30
|
+
path: ["doctor"],
|
|
31
|
+
title: "doctor",
|
|
32
|
+
description: "Run repository diagnostics and guided fixes",
|
|
33
|
+
category: "Repair",
|
|
34
|
+
mutates: false,
|
|
35
|
+
risk: "low",
|
|
36
|
+
interactive: "optional",
|
|
37
|
+
requiresRepository: true,
|
|
38
|
+
shortcuts: ["d", "doc"],
|
|
39
|
+
examples: [
|
|
40
|
+
"agentctl doctor",
|
|
41
|
+
"agentctl doctor --interactive",
|
|
42
|
+
"agentctl doctor --fix safe --yes",
|
|
43
|
+
"agentctl doctor --json",
|
|
44
|
+
],
|
|
45
|
+
flags: [
|
|
46
|
+
{ name: "interactive", type: "boolean", description: "Open full-screen diagnostic matrix" },
|
|
47
|
+
{ name: "fix", type: "string", description: "Apply fix classes (e.g. 'safe' or check IDs)" },
|
|
48
|
+
{ name: "probe", type: "boolean", description: "Enable active network/execution probes" },
|
|
49
|
+
{ name: "json", type: "boolean", description: "Output structured JSON doctor report" },
|
|
50
|
+
{ name: "yes", type: "boolean", description: "Bypass interactive confirmation for safe fixes" },
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "queue",
|
|
55
|
+
path: ["queue"],
|
|
56
|
+
title: "queue",
|
|
57
|
+
description: "Browse and manage canonical task queue",
|
|
58
|
+
category: "Operate",
|
|
59
|
+
mutates: false,
|
|
60
|
+
risk: "low",
|
|
61
|
+
interactive: "optional",
|
|
62
|
+
requiresRepository: true,
|
|
63
|
+
shortcuts: ["q"],
|
|
64
|
+
examples: [
|
|
65
|
+
"agentctl queue",
|
|
66
|
+
"agentctl queue --interactive",
|
|
67
|
+
"agentctl queue --json",
|
|
68
|
+
],
|
|
69
|
+
flags: [
|
|
70
|
+
{ name: "interactive", type: "boolean", description: "Open full-screen queue dashboard" },
|
|
71
|
+
{ name: "json", type: "boolean", description: "Output structured JSON queue snapshot" },
|
|
72
|
+
{ name: "limit", type: "string", description: "Maximum tasks to include in snapshot" },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "swarm",
|
|
77
|
+
path: ["swarm"],
|
|
78
|
+
title: "swarm",
|
|
79
|
+
description: "Inspect active worker slots and concurrency scheduler",
|
|
80
|
+
category: "Operate",
|
|
81
|
+
mutates: false,
|
|
82
|
+
risk: "low",
|
|
83
|
+
interactive: "optional",
|
|
84
|
+
requiresRepository: true,
|
|
85
|
+
shortcuts: ["s"],
|
|
86
|
+
examples: [
|
|
87
|
+
"agentctl swarm",
|
|
88
|
+
"agentctl swarm --interactive",
|
|
89
|
+
"agentctl swarm --json",
|
|
90
|
+
],
|
|
91
|
+
flags: [
|
|
92
|
+
{ name: "interactive", type: "boolean", description: "Open full-screen swarm dashboard" },
|
|
93
|
+
{ name: "json", type: "boolean", description: "Output structured JSON swarm snapshot" },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "task-create",
|
|
98
|
+
path: ["task", "create"],
|
|
99
|
+
title: "task create",
|
|
100
|
+
description: "Author a scoped, falsifiable task",
|
|
101
|
+
category: "Create",
|
|
102
|
+
mutates: true,
|
|
103
|
+
risk: "moderate",
|
|
104
|
+
interactive: "authoring-auto",
|
|
105
|
+
requiresRepository: true,
|
|
106
|
+
shortcuts: ["tc", "create"],
|
|
107
|
+
examples: [
|
|
108
|
+
'agentctl task create --title "Fix webhook" --prompt "Add retry handling" --verify-cmd "npm test"',
|
|
109
|
+
"agentctl task create --interactive",
|
|
110
|
+
],
|
|
111
|
+
flags: [
|
|
112
|
+
{ name: "title", type: "string", description: "Short title for task" },
|
|
113
|
+
{ name: "prompt", type: "string", description: "Detailed task instructions" },
|
|
114
|
+
{ name: "verify-cmd", type: "string", description: "Verification command" },
|
|
115
|
+
{ name: "interactive", type: "boolean", description: "Launch interactive task wizard" },
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "task-optimize",
|
|
120
|
+
path: ["task", "optimize"],
|
|
121
|
+
title: "task optimize",
|
|
122
|
+
description: "Score task prompt falsifiability and static path resolution",
|
|
123
|
+
category: "Inspect",
|
|
124
|
+
mutates: false,
|
|
125
|
+
risk: "low",
|
|
126
|
+
interactive: "never",
|
|
127
|
+
requiresRepository: true,
|
|
128
|
+
shortcuts: ["to", "optimize"],
|
|
129
|
+
examples: [
|
|
130
|
+
'agentctl task optimize "Fix JWT token expiry in src/auth.js"',
|
|
131
|
+
'agentctl task optimize "Refactor auth" --fix',
|
|
132
|
+
'agentctl task optimize --file prompt.txt --json',
|
|
133
|
+
],
|
|
134
|
+
flags: [
|
|
135
|
+
{ name: "fix", type: "boolean", description: "Synthesize optimized markdown task envelope" },
|
|
136
|
+
{ name: "json", type: "boolean", description: "Output structured JSON prompt evaluation" },
|
|
137
|
+
{ name: "file", type: "string", description: "Path to text file containing task prompt" },
|
|
138
|
+
{ name: "dir", type: "string", description: "Target repository directory root" },
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: "init",
|
|
143
|
+
path: ["init"],
|
|
144
|
+
title: "init",
|
|
145
|
+
description: "Configure Stack Oracle and Jules provider manifests",
|
|
146
|
+
category: "Configure",
|
|
147
|
+
mutates: true,
|
|
148
|
+
risk: "moderate",
|
|
149
|
+
interactive: "optional",
|
|
150
|
+
requiresRepository: true,
|
|
151
|
+
shortcuts: ["i"],
|
|
152
|
+
examples: [
|
|
153
|
+
"agentctl init",
|
|
154
|
+
"agentctl init --interactive",
|
|
155
|
+
"agentctl init --tier pro --yes",
|
|
156
|
+
],
|
|
157
|
+
flags: [
|
|
158
|
+
{ name: "tier", type: "string", description: "Target configuration tier (free, pro, enterprise)" },
|
|
159
|
+
{ name: "interactive", type: "boolean", description: "Launch interactive onboarding wizard" },
|
|
160
|
+
{ name: "yes", type: "boolean", description: "Accept auto-detected Stack Oracle defaults" },
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: "dashboard",
|
|
165
|
+
path: ["dashboard"],
|
|
166
|
+
title: "dashboard",
|
|
167
|
+
description: "Start local web dashboard server",
|
|
168
|
+
category: "Inspect",
|
|
169
|
+
mutates: false,
|
|
170
|
+
risk: "low",
|
|
171
|
+
interactive: "never",
|
|
172
|
+
requiresRepository: true,
|
|
173
|
+
shortcuts: ["dash"],
|
|
174
|
+
examples: [
|
|
175
|
+
"agentctl dashboard",
|
|
176
|
+
"agentctl dashboard --port 3000",
|
|
177
|
+
],
|
|
178
|
+
flags: [
|
|
179
|
+
{ name: "port", type: "string", description: "HTTP server port (default 3000)" },
|
|
180
|
+
{ name: "host", type: "string", description: "Bind host address (default 127.0.0.1)" },
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "status",
|
|
185
|
+
path: ["status"],
|
|
186
|
+
title: "status",
|
|
187
|
+
description: "Show operating status and health summary",
|
|
188
|
+
category: "Inspect",
|
|
189
|
+
mutates: false,
|
|
190
|
+
risk: "low",
|
|
191
|
+
interactive: "never",
|
|
192
|
+
requiresRepository: false,
|
|
193
|
+
shortcuts: ["st"],
|
|
194
|
+
examples: [
|
|
195
|
+
"agentctl status",
|
|
196
|
+
"agentctl status --json",
|
|
197
|
+
],
|
|
198
|
+
flags: [
|
|
199
|
+
{ name: "json", type: "boolean", description: "Output status in JSON format" },
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Lookup command descriptor by ID, command path, or shortcut alias.
|
|
206
|
+
* @param {string} idOrAlias
|
|
207
|
+
* @returns {CommandDescriptor | null}
|
|
208
|
+
*/
|
|
209
|
+
export function getCommandDescriptor(idOrAlias) {
|
|
210
|
+
if (!idOrAlias) return null;
|
|
211
|
+
const target = String(idOrAlias).trim().toLowerCase();
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
COMMAND_REGISTRY.find((cmd) => {
|
|
215
|
+
if (cmd.id.toLowerCase() === target) return true;
|
|
216
|
+
if (cmd.path.join(" ").toLowerCase() === target) return true;
|
|
217
|
+
if (cmd.shortcuts.some((s) => s.toLowerCase() === target)) return true;
|
|
218
|
+
return false;
|
|
219
|
+
}) || null
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* List commands filtered by category.
|
|
225
|
+
* @param {CommandCategory} [category]
|
|
226
|
+
* @returns {CommandDescriptor[]}
|
|
227
|
+
*/
|
|
228
|
+
export function listCommandsByCategory(category) {
|
|
229
|
+
if (!category) return COMMAND_REGISTRY;
|
|
230
|
+
return COMMAND_REGISTRY.filter((cmd) => cmd.category === category);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Format clean CLI --help text for a command descriptor.
|
|
235
|
+
* @param {CommandDescriptor} descriptor
|
|
236
|
+
* @returns {string}
|
|
237
|
+
*/
|
|
238
|
+
export function formatCommandHelp(descriptor) {
|
|
239
|
+
const lines = [];
|
|
240
|
+
lines.push(`Usage: agentctl ${descriptor.path.join(" ")} [flags]`);
|
|
241
|
+
lines.push("");
|
|
242
|
+
lines.push(`Description: ${descriptor.description}`);
|
|
243
|
+
lines.push(`Category: ${descriptor.category}`);
|
|
244
|
+
lines.push(`Risk Tier: ${descriptor.risk.toUpperCase()}`);
|
|
245
|
+
lines.push("");
|
|
246
|
+
|
|
247
|
+
if (descriptor.flags && descriptor.flags.length > 0) {
|
|
248
|
+
lines.push("Flags:");
|
|
249
|
+
for (const flag of descriptor.flags) {
|
|
250
|
+
lines.push(` --${flag.name.padEnd(16)} ${flag.description}`);
|
|
251
|
+
}
|
|
252
|
+
lines.push("");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (descriptor.examples && descriptor.examples.length > 0) {
|
|
256
|
+
lines.push("Examples:");
|
|
257
|
+
for (const ex of descriptor.examples) {
|
|
258
|
+
lines.push(` $ ${ex}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return lines.join("\n");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Format JSON representation of full command registry.
|
|
267
|
+
* @returns {{ ok: true, commands: CommandDescriptor[] }}
|
|
268
|
+
*/
|
|
269
|
+
export function formatRegistryJson() {
|
|
270
|
+
return {
|
|
271
|
+
ok: true,
|
|
272
|
+
commands: COMMAND_REGISTRY,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import("../ux/capabilities.mjs").ActionPlan} ActionPlan
|
|
7
|
+
* @typedef {import("./doctor-registry.mjs").DoctorReport} DoctorReport
|
|
8
|
+
*
|
|
9
|
+
* @typedef {Object} FixPlanningContext
|
|
10
|
+
* @property {string} root
|
|
11
|
+
* @property {DoctorReport} report
|
|
12
|
+
* @property {string[]} selectedFixIds
|
|
13
|
+
* @property {Record<string, unknown>} [answers]
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generate unified diff preview string for file creation/replacement.
|
|
18
|
+
* @param {string} path
|
|
19
|
+
* @param {string} oldContent
|
|
20
|
+
* @param {string} newContent
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
function createDiffPreview(path, oldContent, newContent) {
|
|
24
|
+
const oldLines = oldContent ? oldContent.split("\n") : [];
|
|
25
|
+
const newLines = newContent ? newContent.split("\n") : [];
|
|
26
|
+
const lines = [];
|
|
27
|
+
|
|
28
|
+
lines.push(`--- a/${path}`);
|
|
29
|
+
lines.push(`+++ b/${path}`);
|
|
30
|
+
lines.push(`@@ -1,${oldLines.length || 0} +1,${newLines.length || 0} @@`);
|
|
31
|
+
|
|
32
|
+
for (const line of oldLines) {
|
|
33
|
+
lines.push(`-${line}`);
|
|
34
|
+
}
|
|
35
|
+
for (const line of newLines) {
|
|
36
|
+
lines.push(`+${line}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return lines.join("\n");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function deepFreeze(value) {
|
|
43
|
+
if (!value || typeof value !== "object" || Object.isFrozen(value)) return value;
|
|
44
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
45
|
+
return Object.freeze(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function finalizePlan(plan) {
|
|
49
|
+
const { planHash: _oldHash, ...unsigned } = plan;
|
|
50
|
+
const planHash = "sha256:" + createHash("sha256").update(JSON.stringify(unsigned)).digest("hex");
|
|
51
|
+
return deepFreeze({ ...unsigned, planHash });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Plan diagnostic fixes into immutable ActionPlan objects.
|
|
56
|
+
* @param {FixPlanningContext} context
|
|
57
|
+
* @returns {Promise<ActionPlan[]>}
|
|
58
|
+
*/
|
|
59
|
+
export async function planDiagnosticFixes(context = {}) {
|
|
60
|
+
const root = resolve(context.root || process.cwd());
|
|
61
|
+
const report = context.report || { results: [] };
|
|
62
|
+
const selectedFixIds = Array.isArray(context.selectedFixIds) ? context.selectedFixIds : ["safe"];
|
|
63
|
+
|
|
64
|
+
const wantSafeOnly = selectedFixIds.includes("safe");
|
|
65
|
+
/** @type {ActionPlan[]} */
|
|
66
|
+
const plans = [];
|
|
67
|
+
|
|
68
|
+
for (const result of report.results || []) {
|
|
69
|
+
for (const fix of result.fixes || []) {
|
|
70
|
+
const isSelected = selectedFixIds.includes(fix.id) || (wantSafeOnly && fix.automatic);
|
|
71
|
+
if (!isSelected) continue;
|
|
72
|
+
|
|
73
|
+
if (fix.id === "config.create-default") {
|
|
74
|
+
const configPath = ".agent/config.yml";
|
|
75
|
+
const newContent = `# jules-orchestrator-kit configuration manifest
|
|
76
|
+
version: 1
|
|
77
|
+
tier: pro
|
|
78
|
+
verify:
|
|
79
|
+
test: "npm test"
|
|
80
|
+
build: "npm run build"
|
|
81
|
+
`;
|
|
82
|
+
const diff = createDiffPreview(configPath, "", newContent);
|
|
83
|
+
const planId = `PLAN-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
84
|
+
|
|
85
|
+
plans.push({
|
|
86
|
+
schema: "agentctl/action-plan-v1",
|
|
87
|
+
id: planId,
|
|
88
|
+
kind: "config.create-default",
|
|
89
|
+
title: "Create default .agent/config.yml",
|
|
90
|
+
summary: "Generate initial configuration manifest for jules-orchestrator-kit",
|
|
91
|
+
risk: "low",
|
|
92
|
+
repository: root,
|
|
93
|
+
createdAt: new Date().toISOString(),
|
|
94
|
+
preconditions: [],
|
|
95
|
+
fileMutations: [
|
|
96
|
+
{
|
|
97
|
+
operation: "create",
|
|
98
|
+
path: configPath,
|
|
99
|
+
newContent,
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
commandEffects: [],
|
|
103
|
+
stateTransitions: [],
|
|
104
|
+
preview: {
|
|
105
|
+
unifiedDiff: diff,
|
|
106
|
+
warnings: [],
|
|
107
|
+
estimatedImpact: ["Creates .agent/config.yml"],
|
|
108
|
+
},
|
|
109
|
+
confirmation: {
|
|
110
|
+
mode: "none",
|
|
111
|
+
prompt: "Apply default configuration?",
|
|
112
|
+
},
|
|
113
|
+
planHash: "sha256:" + createHash("sha256").update(planId).digest("hex"),
|
|
114
|
+
});
|
|
115
|
+
} else if (fix.id === "state.rebuild-head") {
|
|
116
|
+
const dateStr = new Date().toISOString().split("T")[0];
|
|
117
|
+
const stateDir = join(root, ".agent", "state");
|
|
118
|
+
const headPath = `.agent/state/telemetry-${dateStr}.head`;
|
|
119
|
+
let tailHash = "0".repeat(64);
|
|
120
|
+
let segment = 0;
|
|
121
|
+
try {
|
|
122
|
+
const telemetryFiles = readdirSync(stateDir)
|
|
123
|
+
.filter((name) => name.startsWith(`telemetry-${dateStr}`) && name.endsWith(".jsonl"))
|
|
124
|
+
.sort((a, b) => {
|
|
125
|
+
const index = (name) => Number(name.match(/-(\d+)\.jsonl$/)?.[1] || 0);
|
|
126
|
+
return index(a) - index(b);
|
|
127
|
+
});
|
|
128
|
+
const lastFile = telemetryFiles.at(-1);
|
|
129
|
+
if (lastFile) {
|
|
130
|
+
const lines = readFileSync(join(stateDir, lastFile), "utf-8").split("\n").filter(Boolean);
|
|
131
|
+
const lastEntry = JSON.parse(lines.at(-1));
|
|
132
|
+
if (typeof lastEntry?.hash === "string") tailHash = lastEntry.hash;
|
|
133
|
+
const segmentMatch = lastFile.match(/-(\d+)\.jsonl$/);
|
|
134
|
+
segment = segmentMatch ? Number(segmentMatch[1]) : 0;
|
|
135
|
+
}
|
|
136
|
+
} catch (_) {}
|
|
137
|
+
const newContent = JSON.stringify({ v: 1, hash: tailHash, segment, ts: new Date().toISOString() });
|
|
138
|
+
const planId = `PLAN-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
139
|
+
|
|
140
|
+
plans.push({
|
|
141
|
+
schema: "agentctl/action-plan-v1",
|
|
142
|
+
id: planId,
|
|
143
|
+
kind: "state.rebuild-head",
|
|
144
|
+
title: "Rebuild telemetry spine head pointer",
|
|
145
|
+
summary: "Re-initialize .head pointer for telemetry spine ledger",
|
|
146
|
+
risk: "low",
|
|
147
|
+
repository: root,
|
|
148
|
+
createdAt: new Date().toISOString(),
|
|
149
|
+
preconditions: [],
|
|
150
|
+
fileMutations: [
|
|
151
|
+
{
|
|
152
|
+
operation: "create",
|
|
153
|
+
path: headPath,
|
|
154
|
+
newContent,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
commandEffects: [],
|
|
158
|
+
stateTransitions: [],
|
|
159
|
+
preview: {
|
|
160
|
+
unifiedDiff: createDiffPreview(headPath, "", newContent),
|
|
161
|
+
warnings: [],
|
|
162
|
+
estimatedImpact: [`Creates ${headPath} pointer`],
|
|
163
|
+
},
|
|
164
|
+
confirmation: {
|
|
165
|
+
mode: "none",
|
|
166
|
+
prompt: "Rebuild telemetry head pointer?",
|
|
167
|
+
},
|
|
168
|
+
planHash: "sha256:" + createHash("sha256").update(planId).digest("hex"),
|
|
169
|
+
});
|
|
170
|
+
} else if (fix.id === "locks.prune-stale") {
|
|
171
|
+
const locksDir = join(root, ".agent", "state", "locks");
|
|
172
|
+
if (existsSync(locksDir)) {
|
|
173
|
+
const lockFiles = readdirSync(locksDir).filter((f) => f.endsWith(".json"));
|
|
174
|
+
for (const lockFile of lockFiles) {
|
|
175
|
+
const lockRelPath = `.agent/state/locks/${lockFile}`;
|
|
176
|
+
const planId = `PLAN-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
177
|
+
|
|
178
|
+
plans.push({
|
|
179
|
+
schema: "agentctl/action-plan-v1",
|
|
180
|
+
id: planId,
|
|
181
|
+
kind: "locks.prune-stale",
|
|
182
|
+
title: `Remove stale lock ${lockFile}`,
|
|
183
|
+
summary: `Prune inactive VFS lock file ${lockRelPath}`,
|
|
184
|
+
risk: "low",
|
|
185
|
+
repository: root,
|
|
186
|
+
createdAt: new Date().toISOString(),
|
|
187
|
+
preconditions: [],
|
|
188
|
+
fileMutations: [
|
|
189
|
+
{
|
|
190
|
+
operation: "delete",
|
|
191
|
+
path: lockRelPath,
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
commandEffects: [],
|
|
195
|
+
stateTransitions: [],
|
|
196
|
+
preview: {
|
|
197
|
+
unifiedDiff: `--- a/${lockRelPath}\n+++ /dev/null\n`,
|
|
198
|
+
warnings: [],
|
|
199
|
+
estimatedImpact: [`Removes ${lockRelPath}`],
|
|
200
|
+
},
|
|
201
|
+
confirmation: {
|
|
202
|
+
mode: "none",
|
|
203
|
+
prompt: `Remove lock ${lockFile}?`,
|
|
204
|
+
},
|
|
205
|
+
planHash: "sha256:" + createHash("sha256").update(planId).digest("hex"),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return plans.map(finalizePlan);
|
|
214
|
+
}
|