@relay-baton/cli 1.0.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/LICENSE +21 -0
- package/dist/commands/agentFor.d.ts +8 -0
- package/dist/commands/agentFor.js +28 -0
- package/dist/commands/auditApiKeyEnv.d.ts +6 -0
- package/dist/commands/auditApiKeyEnv.js +27 -0
- package/dist/commands/budget.d.ts +5 -0
- package/dist/commands/budget.js +86 -0
- package/dist/commands/chat.d.ts +5 -0
- package/dist/commands/chat.js +218 -0
- package/dist/commands/checkpoint.d.ts +10 -0
- package/dist/commands/checkpoint.js +78 -0
- package/dist/commands/compact.d.ts +4 -0
- package/dist/commands/compact.js +22 -0
- package/dist/commands/compress.d.ts +4 -0
- package/dist/commands/compress.js +61 -0
- package/dist/commands/compressContext.d.ts +8 -0
- package/dist/commands/compressContext.js +51 -0
- package/dist/commands/conversation.d.ts +8 -0
- package/dist/commands/conversation.js +90 -0
- package/dist/commands/diagnostics.d.ts +23 -0
- package/dist/commands/diagnostics.js +254 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.js +104 -0
- package/dist/commands/execute.d.ts +9 -0
- package/dist/commands/execute.js +183 -0
- package/dist/commands/git.d.ts +6 -0
- package/dist/commands/git.js +82 -0
- package/dist/commands/guard.d.ts +7 -0
- package/dist/commands/guard.js +30 -0
- package/dist/commands/handoff.d.ts +10 -0
- package/dist/commands/handoff.js +133 -0
- package/dist/commands/handoffBundle.d.ts +12 -0
- package/dist/commands/handoffBundle.js +64 -0
- package/dist/commands/handoffHistory.d.ts +23 -0
- package/dist/commands/handoffHistory.js +129 -0
- package/dist/commands/handoffShow.d.ts +12 -0
- package/dist/commands/handoffShow.js +73 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +19 -0
- package/dist/commands/inventory.d.ts +5 -0
- package/dist/commands/inventory.js +23 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.js +80 -0
- package/dist/commands/migrate.d.ts +8 -0
- package/dist/commands/migrate.js +55 -0
- package/dist/commands/plan.d.ts +13 -0
- package/dist/commands/plan.js +159 -0
- package/dist/commands/profile.d.ts +5 -0
- package/dist/commands/profile.js +23 -0
- package/dist/commands/project.d.ts +18 -0
- package/dist/commands/project.js +173 -0
- package/dist/commands/projectOptions.d.ts +7 -0
- package/dist/commands/projectOptions.js +21 -0
- package/dist/commands/receipt.d.ts +8 -0
- package/dist/commands/receipt.js +48 -0
- package/dist/commands/replay.d.ts +8 -0
- package/dist/commands/replay.js +35 -0
- package/dist/commands/report.d.ts +6 -0
- package/dist/commands/report.js +54 -0
- package/dist/commands/review.d.ts +8 -0
- package/dist/commands/review.js +63 -0
- package/dist/commands/risk.d.ts +5 -0
- package/dist/commands/risk.js +25 -0
- package/dist/commands/run.d.ts +31 -0
- package/dist/commands/run.js +323 -0
- package/dist/commands/session.d.ts +40 -0
- package/dist/commands/session.js +158 -0
- package/dist/commands/sessionWorkspace.d.ts +25 -0
- package/dist/commands/sessionWorkspace.js +193 -0
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.js +116 -0
- package/dist/commands/tui.d.ts +4 -0
- package/dist/commands/tui.js +46 -0
- package/dist/commands/usage.d.ts +11 -0
- package/dist/commands/usage.js +40 -0
- package/dist/commands/verify.d.ts +15 -0
- package/dist/commands/verify.js +197 -0
- package/dist/commands/workspace.d.ts +5 -0
- package/dist/commands/workspace.js +27 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +394 -0
- package/package.json +57 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.resolveChain = resolveChain;
|
|
37
|
+
exports.runCommand = runCommand;
|
|
38
|
+
const core_1 = require("@relay-baton/core");
|
|
39
|
+
const readline = __importStar(require("readline"));
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const projectOptions_1 = require("./projectOptions");
|
|
42
|
+
const agentFor_1 = require("./agentFor");
|
|
43
|
+
const auditApiKeyEnv_1 = require("./auditApiKeyEnv");
|
|
44
|
+
/** Confirmation prompt for a bounded continue step. Resolves true on y/yes. */
|
|
45
|
+
function confirm(question) {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
48
|
+
rl.question(question, (answer) => {
|
|
49
|
+
rl.close();
|
|
50
|
+
resolve(/^\s*y(es)?\s*$/i.test(answer));
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Deterministic "who's next" policy (v2.3): the relay chain is an ordered list
|
|
56
|
+
* of agents. `--chain` wins; otherwise it is [primary, fallback] resolved from
|
|
57
|
+
* flags > project overrides > config. Supports reverse (claude->codex) and
|
|
58
|
+
* longer chains, not just codex->claude.
|
|
59
|
+
*/
|
|
60
|
+
function resolveChain(opts, project, config, assignedAgent) {
|
|
61
|
+
let ids;
|
|
62
|
+
if (opts.chain) {
|
|
63
|
+
ids = opts.chain.split(",").map(s => s.trim()).filter(Boolean);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// v2.6: a work item's assigned agent beats project/config defaults, but loses
|
|
67
|
+
// to explicit --primary/--chain flags.
|
|
68
|
+
const primary = opts.primary ?? assignedAgent ?? project?.primaryAgent ?? config.primaryAgent;
|
|
69
|
+
const fallback = opts.fallback ?? project?.fallbackAgent ?? config.fallbackAgent;
|
|
70
|
+
ids = fallback && fallback !== primary ? [primary, fallback] : [primary];
|
|
71
|
+
}
|
|
72
|
+
for (const id of ids) {
|
|
73
|
+
if (!(0, core_1.isAgentId)(id)) {
|
|
74
|
+
console.error(`[relay-baton] unknown agent in relay chain: ${id}`);
|
|
75
|
+
process.exit(2);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Collapse immediate duplicates (a,a -> a); keep distinct relay hops.
|
|
79
|
+
const chain = [];
|
|
80
|
+
for (const id of ids)
|
|
81
|
+
if (chain[chain.length - 1] !== id)
|
|
82
|
+
chain.push(id);
|
|
83
|
+
return chain;
|
|
84
|
+
}
|
|
85
|
+
async function runCommand(task, opts) {
|
|
86
|
+
const projectContext = (0, projectOptions_1.resolveProjectContext)(opts, true);
|
|
87
|
+
const mainRoot = projectContext.repoRoot;
|
|
88
|
+
// v2.6 item 3: if the active work item is backed by a git worktree, execute in
|
|
89
|
+
// that isolated checkout (own working tree + own .ai-session) so parallel work
|
|
90
|
+
// items never clobber each other's git state. The workspace registry stays in
|
|
91
|
+
// the main repo.
|
|
92
|
+
const activeItem = new core_1.WorkspaceManager(mainRoot).activeSession();
|
|
93
|
+
const repoRoot = activeItem?.worktree && fs.existsSync(activeItem.worktree) ? activeItem.worktree : mainRoot;
|
|
94
|
+
if (repoRoot !== mainRoot)
|
|
95
|
+
console.log(`[relay-baton] session "${activeItem.name}" → worktree ${repoRoot}`);
|
|
96
|
+
const { config } = core_1.ConfigLoader.load(repoRoot);
|
|
97
|
+
const sm = new core_1.SessionManager(repoRoot, config);
|
|
98
|
+
if (!sm.getMeta())
|
|
99
|
+
sm.init(task);
|
|
100
|
+
sm.writeTask(task);
|
|
101
|
+
const git = new core_1.GitService(repoRoot);
|
|
102
|
+
if (!git.isGitRepo()) {
|
|
103
|
+
console.error("[relay-baton] not a git repository. aborting.");
|
|
104
|
+
process.exit(2);
|
|
105
|
+
}
|
|
106
|
+
const profileName = (opts.diet ?? projectContext.project?.defaultDiet ?? config.tokenDiet.profile);
|
|
107
|
+
if (!config.tokenDiet.profiles[profileName]) {
|
|
108
|
+
console.error(`unknown diet profile: ${profileName}`);
|
|
109
|
+
process.exit(2);
|
|
110
|
+
}
|
|
111
|
+
// v2.6: honor the active work item's assigned agent as the default primary.
|
|
112
|
+
const assignedAgent = activeItem?.assignedAgent;
|
|
113
|
+
const chain = resolveChain(opts, projectContext.project, config, assignedAgent);
|
|
114
|
+
console.log(`[relay-baton] relay chain: ${chain.join(" → ")}${assignedAgent && !opts.primary && !opts.chain ? ` (session "${activeItem.name}" → ${assignedAgent})` : ""}`);
|
|
115
|
+
const startedAt = new Date().toISOString();
|
|
116
|
+
sm.updateMeta({
|
|
117
|
+
task,
|
|
118
|
+
status: "running",
|
|
119
|
+
primaryAgent: chain[0],
|
|
120
|
+
fallbackAgent: chain[1] ?? chain[0],
|
|
121
|
+
activeAgent: chain[0],
|
|
122
|
+
tokenDietProfile: profileName,
|
|
123
|
+
fallbackReason: null,
|
|
124
|
+
startedAt,
|
|
125
|
+
endedAt: undefined,
|
|
126
|
+
durationMs: undefined,
|
|
127
|
+
});
|
|
128
|
+
const finalize = (status, lastAgent, lastError) => {
|
|
129
|
+
const endedAt = new Date().toISOString();
|
|
130
|
+
sm.updateMeta({
|
|
131
|
+
status, lastAgent, activeAgent: "none", lastError,
|
|
132
|
+
endedAt, durationMs: Date.parse(endedAt) - Date.parse(startedAt),
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
// v2.5 project hooks (opt-in, local-only). No-op when unconfigured.
|
|
136
|
+
const hooks = new core_1.HookRunner(repoRoot, config);
|
|
137
|
+
const runHooks = (phase) => {
|
|
138
|
+
for (const r of hooks.run(phase, {
|
|
139
|
+
allowApiKeyEnv: opts.allowApiKeyEnv,
|
|
140
|
+
onStdout: l => process.stdout.write(l + "\n"),
|
|
141
|
+
onStderr: l => process.stderr.write(l + "\n"),
|
|
142
|
+
})) {
|
|
143
|
+
if (!r.ok)
|
|
144
|
+
console.error(`[relay-baton] hook failed (${phase}): ${r.command} → ${r.exitCode ?? r.error}`);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
for (let hop = 0; hop < chain.length; hop++) {
|
|
148
|
+
const agent = chain[hop];
|
|
149
|
+
const isFirst = hop === 0;
|
|
150
|
+
const nextAgent = hop + 1 < chain.length ? chain[hop + 1] : null;
|
|
151
|
+
const adapter = (0, agentFor_1.adapterFor)(agent, config);
|
|
152
|
+
// First hop runs the task fresh; later hops continue from the handoff. Pass
|
|
153
|
+
// the continuation in both task+prompt so task- and prompt-oriented adapters
|
|
154
|
+
// alike launch with the handoff framing.
|
|
155
|
+
const continuation = core_1.PromptBuilder.continuation();
|
|
156
|
+
const cmd = isFirst
|
|
157
|
+
? adapter.buildCommand({ task, repoRoot, sessionDir: sm.files.dir, dietProfile: profileName })
|
|
158
|
+
: adapter.buildCommand({ task: continuation, prompt: continuation, repoRoot, sessionDir: sm.files.dir, dietProfile: profileName });
|
|
159
|
+
// Only watch for fallback signals when there is a next agent to relay to.
|
|
160
|
+
const detector = nextAgent
|
|
161
|
+
? new core_1.FallbackDetector((0, core_1.resolveFallbackPatterns)([...config.fallbackPatterns, ...(0, core_1.agentFallbackPatterns)(agent)], projectContext.project?.fallbackPatterns))
|
|
162
|
+
: undefined;
|
|
163
|
+
sm.updateMeta({ status: isFirst ? "running" : "running_fallback", activeAgent: agent });
|
|
164
|
+
console.log(`[relay-baton] running ${cmd.command} ${cmd.args.join(" ")}`);
|
|
165
|
+
const r = await (0, core_1.runAgent)({
|
|
166
|
+
command: cmd,
|
|
167
|
+
logFile: sm.files.p("commandsLog"),
|
|
168
|
+
authPolicy: config.authPolicy,
|
|
169
|
+
allowApiKeyEnv: opts.allowApiKeyEnv,
|
|
170
|
+
fallbackDetector: detector,
|
|
171
|
+
onStdout: l => process.stdout.write(l + "\n"),
|
|
172
|
+
onStderr: l => process.stderr.write(l + "\n"),
|
|
173
|
+
onFallback: hit => console.error(`[relay-baton] fallback pattern detected: ${hit.pattern}`),
|
|
174
|
+
});
|
|
175
|
+
(0, auditApiKeyEnv_1.auditApiKeyEnv)(repoRoot, r.passedThroughEnvVars, sm.getMeta()?.id);
|
|
176
|
+
if (r.error) {
|
|
177
|
+
console.error(r.error);
|
|
178
|
+
finalize("failed", agent, r.error);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
const wantsFallback = r.fallbackReason !== null && nextAgent !== null;
|
|
182
|
+
if (!wantsFallback) {
|
|
183
|
+
// Terminal: either finished cleanly, or hit a limit with no one left to relay to.
|
|
184
|
+
if (r.fallbackReason !== null) {
|
|
185
|
+
sm.updateMeta({ fallbackReason: r.fallbackReason });
|
|
186
|
+
console.log(`[relay-baton] ${agent} hit a fallback signal but the relay chain is exhausted.`);
|
|
187
|
+
}
|
|
188
|
+
// v2.5: bounded auto-orchestration. Only when the agent finished cleanly
|
|
189
|
+
// and --until is set. Strictly capped, guardrail-gated, confirmation-first.
|
|
190
|
+
if (r.exitCode === 0 && r.fallbackReason === null && opts.until) {
|
|
191
|
+
await runUntilLoop(agent, { repoRoot, sm, config, profileName, opts, startedAt, finalize, runHooks });
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (r.exitCode === 0)
|
|
195
|
+
runHooks("postExecute");
|
|
196
|
+
finalize(r.exitCode === 0 ? "completed" : "failed", agent, r.exitCode === 0 ? null : `${agent} exited with ${r.exitCode}`);
|
|
197
|
+
console.log(`[relay-baton] ${agent} finished. exiting.`);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
// Relay to the next agent: record fallback, (optionally) compress, build+gate handoff.
|
|
201
|
+
sm.updateMeta({ lastAgent: agent, activeAgent: "none", fallbackReason: r.fallbackReason, status: "fallback_detected", lastError: null });
|
|
202
|
+
if (config.contextCompression?.enabled && config.contextCompression?.auto) {
|
|
203
|
+
const cc = new core_1.ContextCompressor(repoRoot, config);
|
|
204
|
+
const res = cc.compressIfNeeded(config.tokenDiet.profiles[profileName], {});
|
|
205
|
+
if (res.compressed) {
|
|
206
|
+
sm.updateMeta({ status: "compressing" });
|
|
207
|
+
console.log(`[relay-baton] context compressed: ${res.before.total} -> ${res.after?.total} chars`);
|
|
208
|
+
sm.updateMeta({ status: "fallback_detected" });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
runHooks("preHandoff");
|
|
212
|
+
console.log(`[relay-baton] building handoff for ${nextAgent}...`);
|
|
213
|
+
const wf = new core_1.BatonWorkflow(sm, config);
|
|
214
|
+
const h = wf.buildHandoff({ profileName, fallbackReason: r.fallbackReason, previousAgent: agent, nextAgent: nextAgent });
|
|
215
|
+
sm.updateMeta({ handoffCount: (sm.getMeta()?.handoffCount ?? 0) + 1 });
|
|
216
|
+
// v2.4 local usage insight (token proxy; never transmitted).
|
|
217
|
+
new core_1.UsageLedger(repoRoot).record("handoff", nextAgent, h.usedChars, `${agent}→${nextAgent}`);
|
|
218
|
+
const gate = new core_1.HandoffQualityGate(repoRoot).check();
|
|
219
|
+
const dietGate = new core_1.TokenDietQualityGate(repoRoot, profileName, config.tokenDiet.profiles[profileName]).check({ wasTruncated: h.truncated });
|
|
220
|
+
let blocked = false;
|
|
221
|
+
if (!gate.ok) {
|
|
222
|
+
console.error("Handoff Quality Gate failed:");
|
|
223
|
+
for (const f of gate.failures)
|
|
224
|
+
console.error(" - " + f);
|
|
225
|
+
blocked = true;
|
|
226
|
+
}
|
|
227
|
+
if (!dietGate.ok) {
|
|
228
|
+
console.error("Token Diet Quality Gate failed:");
|
|
229
|
+
for (const f of dietGate.failures)
|
|
230
|
+
console.error(" - " + f);
|
|
231
|
+
blocked = true;
|
|
232
|
+
}
|
|
233
|
+
for (const w of dietGate.warnings)
|
|
234
|
+
console.error("warn: " + w);
|
|
235
|
+
const highFindings = h.redaction.findings.filter(f => f.severity === "high");
|
|
236
|
+
if (highFindings.length > 0) {
|
|
237
|
+
console.error("Redaction Gate failed (handoff would leak secrets to the next agent):");
|
|
238
|
+
for (const f of highFindings)
|
|
239
|
+
console.error(` - ${f.category}: ${f.file}${f.line ? ":" + f.line : ""} (${f.hint})`);
|
|
240
|
+
blocked = true;
|
|
241
|
+
}
|
|
242
|
+
for (const f of h.redaction.findings.filter(f => f.severity !== "high")) {
|
|
243
|
+
console.error(`warn: redaction ${f.category} in ${f.file}${f.line ? ":" + f.line : ""} (${f.hint})`);
|
|
244
|
+
}
|
|
245
|
+
if (blocked && !opts.force) {
|
|
246
|
+
console.error("[relay-baton] aborting fallback launch. Use --force to override.");
|
|
247
|
+
process.exit(3);
|
|
248
|
+
}
|
|
249
|
+
// loop continues to nextAgent
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* v2.5 bounded auto-orchestration. STRICTLY bounded: capped by --until (and the
|
|
254
|
+
* guardrail step cap), gated by GuardrailPolicy each step, and confirmation-first
|
|
255
|
+
* unless --yes. Never a daemon, never unattended without explicit pre-approval.
|
|
256
|
+
*/
|
|
257
|
+
async function runUntilLoop(agent, ctx) {
|
|
258
|
+
const { repoRoot, sm, config, profileName, opts, startedAt, finalize, runHooks } = ctx;
|
|
259
|
+
const maxSteps = Math.max(1, Math.floor(Number(opts.until)) || 0);
|
|
260
|
+
if (maxSteps < 1) {
|
|
261
|
+
finalize("completed", agent, null);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const budgetCeiling = config.guardrails?.maxBudgetRatio ?? 0.9;
|
|
265
|
+
const orch = new core_1.BoundedOrchestrator({
|
|
266
|
+
maxSteps,
|
|
267
|
+
budgetCeiling,
|
|
268
|
+
evaluateGuardrail: () => new core_1.GuardrailPolicy(repoRoot, config).evaluate(),
|
|
269
|
+
});
|
|
270
|
+
const checkpoints = new core_1.ExecutionCheckpoints(repoRoot, config);
|
|
271
|
+
const ledger = new core_1.UsageLedger(repoRoot);
|
|
272
|
+
const adapter = (0, agentFor_1.adapterFor)(agent, config);
|
|
273
|
+
console.log(`[relay-baton] bounded auto-orchestration: up to ${maxSteps} extra step(s) on ${agent} (guardrail-gated${opts.yes ? "" : ", confirm each"})`);
|
|
274
|
+
let obs = {};
|
|
275
|
+
for (;;) {
|
|
276
|
+
const d = orch.next(obs);
|
|
277
|
+
if (!d.proceed) {
|
|
278
|
+
console.log(`[relay-baton] bounded loop stopped: ${d.reason ?? "done"}`);
|
|
279
|
+
if (d.guardrail?.blocked)
|
|
280
|
+
for (const v of d.guardrail.violations)
|
|
281
|
+
console.log(` - ${v.message}`);
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
if (d.requireConfirmation && !opts.yes) {
|
|
285
|
+
const ok = await confirm(`[relay-baton] continue step ${d.step}/${maxSteps} on ${agent}? [y/N] `);
|
|
286
|
+
if (!ok) {
|
|
287
|
+
console.log("[relay-baton] declined — stopping bounded loop.");
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
sm.updateMeta({ status: "running", activeAgent: agent });
|
|
292
|
+
const continuation = core_1.PromptBuilder.continuation();
|
|
293
|
+
const cmd = adapter.buildCommand({ task: continuation, prompt: continuation, repoRoot, sessionDir: sm.files.dir, dietProfile: profileName });
|
|
294
|
+
console.log(`[relay-baton] [until ${d.step}/${maxSteps}] running ${cmd.command} ${cmd.args.join(" ")}`);
|
|
295
|
+
const r = await (0, core_1.runAgent)({
|
|
296
|
+
command: cmd,
|
|
297
|
+
logFile: sm.files.p("commandsLog"),
|
|
298
|
+
authPolicy: config.authPolicy,
|
|
299
|
+
allowApiKeyEnv: opts.allowApiKeyEnv,
|
|
300
|
+
onStdout: (l) => process.stdout.write(l + "\n"),
|
|
301
|
+
onStderr: (l) => process.stderr.write(l + "\n"),
|
|
302
|
+
});
|
|
303
|
+
(0, auditApiKeyEnv_1.auditApiKeyEnv)(repoRoot, r.passedThroughEnvVars, sm.getMeta()?.id);
|
|
304
|
+
if (r.error || r.exitCode !== 0) {
|
|
305
|
+
finalize("failed", agent, r.error ?? `${agent} exited with ${r.exitCode}`);
|
|
306
|
+
console.error(`[relay-baton] bounded step failed; stopping.`);
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
checkpoints.append({ step: d.step, command: `run --until step ${d.step}`, result: "ok" });
|
|
310
|
+
const handoffChars = (() => { try {
|
|
311
|
+
return require("fs").statSync(sm.files.p("handoff")).size;
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return 0;
|
|
315
|
+
} })();
|
|
316
|
+
ledger.record("run", agent, handoffChars, `until ${d.step}/${maxSteps}`);
|
|
317
|
+
const summary = new core_1.GitService(repoRoot).summary(0);
|
|
318
|
+
obs = { budgetRatio: undefined, progressKey: `${summary.head}:${summary.changed}` };
|
|
319
|
+
}
|
|
320
|
+
runHooks("postExecute");
|
|
321
|
+
finalize("completed", agent, null);
|
|
322
|
+
console.log(`[relay-baton] ${agent} bounded loop finished. exiting.`);
|
|
323
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface SessionArchiveOpts {
|
|
2
|
+
project?: string;
|
|
3
|
+
path?: string;
|
|
4
|
+
json?: boolean;
|
|
5
|
+
dryRun?: boolean;
|
|
6
|
+
out?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function sessionArchiveCommand(opts?: SessionArchiveOpts): Promise<void>;
|
|
9
|
+
export interface SessionListOpts {
|
|
10
|
+
json?: boolean;
|
|
11
|
+
out?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function sessionListCommand(opts?: SessionListOpts): Promise<void>;
|
|
14
|
+
export interface SessionPruneOpts {
|
|
15
|
+
json?: boolean;
|
|
16
|
+
out?: string;
|
|
17
|
+
maxAgeDays?: string;
|
|
18
|
+
maxCount?: string;
|
|
19
|
+
apply?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function sessionPruneCommand(opts?: SessionPruneOpts): Promise<void>;
|
|
22
|
+
export interface SessionExportOpts {
|
|
23
|
+
json?: boolean;
|
|
24
|
+
out?: string;
|
|
25
|
+
to?: string;
|
|
26
|
+
overwrite?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare function sessionExportCommand(archive: string, opts?: SessionExportOpts): Promise<void>;
|
|
29
|
+
export interface SessionInspectOpts {
|
|
30
|
+
json?: boolean;
|
|
31
|
+
out?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function sessionInspectCommand(archive: string, opts?: SessionInspectOpts): Promise<void>;
|
|
34
|
+
export interface SessionResumeOpts {
|
|
35
|
+
project?: string;
|
|
36
|
+
path?: string;
|
|
37
|
+
json?: boolean;
|
|
38
|
+
staleHours?: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function sessionResumeCommand(opts?: SessionResumeOpts): Promise<void>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sessionArchiveCommand = sessionArchiveCommand;
|
|
4
|
+
exports.sessionListCommand = sessionListCommand;
|
|
5
|
+
exports.sessionPruneCommand = sessionPruneCommand;
|
|
6
|
+
exports.sessionExportCommand = sessionExportCommand;
|
|
7
|
+
exports.sessionInspectCommand = sessionInspectCommand;
|
|
8
|
+
exports.sessionResumeCommand = sessionResumeCommand;
|
|
9
|
+
const core_1 = require("@relay-baton/core");
|
|
10
|
+
const projectOptions_1 = require("./projectOptions");
|
|
11
|
+
async function sessionArchiveCommand(opts = {}) {
|
|
12
|
+
const repoRoot = (0, projectOptions_1.resolveRepoRoot)(opts);
|
|
13
|
+
core_1.ConfigLoader.load(repoRoot);
|
|
14
|
+
const result = new core_1.SessionArchiver(repoRoot).archive({
|
|
15
|
+
archiveRoot: opts.out,
|
|
16
|
+
dryRun: opts.dryRun,
|
|
17
|
+
});
|
|
18
|
+
if (opts.json) {
|
|
19
|
+
console.log(JSON.stringify(result, null, 2));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!result.available) {
|
|
23
|
+
console.log(`[relay-baton] session archive unavailable: ${result.reason}`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const count = result.manifest?.files.length ?? 0;
|
|
27
|
+
const prefix = result.dryRun ? "would archive" : "archived";
|
|
28
|
+
console.log(`[relay-baton] ${prefix} ${count} file(s)`);
|
|
29
|
+
console.log(`archive: ${result.archiveDir}`);
|
|
30
|
+
if (!result.dryRun)
|
|
31
|
+
console.log("manifest: manifest.json");
|
|
32
|
+
}
|
|
33
|
+
async function sessionListCommand(opts = {}) {
|
|
34
|
+
const result = new core_1.SessionArchiveStore({ archiveRoot: opts.out }).list();
|
|
35
|
+
if (opts.json) {
|
|
36
|
+
console.log(JSON.stringify(result, null, 2));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!result.available) {
|
|
40
|
+
console.log(`[relay-baton] no session archives: ${result.reason}`);
|
|
41
|
+
console.log(`archive root: ${result.archiveRoot}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (result.archives.length === 0) {
|
|
45
|
+
console.log("[relay-baton] no session archives found");
|
|
46
|
+
console.log(`archive root: ${result.archiveRoot}`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
console.log(`[relay-baton] ${result.archives.length} session archive(s) in ${result.archiveRoot}`);
|
|
50
|
+
for (const a of result.archives) {
|
|
51
|
+
const flag = a.valid ? "" : " (invalid)";
|
|
52
|
+
const when = a.createdAt ?? "unknown";
|
|
53
|
+
console.log(`- ${a.id}${flag} ${a.fileCount} file(s), ${a.totalBytes} bytes, ${when}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function sessionPruneCommand(opts = {}) {
|
|
57
|
+
const maxAgeDays = opts.maxAgeDays != null ? Number.parseFloat(opts.maxAgeDays) : undefined;
|
|
58
|
+
const maxCount = opts.maxCount != null ? Number.parseInt(opts.maxCount, 10) : undefined;
|
|
59
|
+
for (const [flag, v] of [["--max-age-days", maxAgeDays], ["--max-count", maxCount]]) {
|
|
60
|
+
if (v !== undefined && (!Number.isFinite(v) || v < 0)) {
|
|
61
|
+
console.error(`[relay-baton] ${flag} must be a non-negative number`);
|
|
62
|
+
process.exit(2);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const result = new core_1.SessionArchiveStore({ archiveRoot: opts.out }).prune({
|
|
66
|
+
maxAgeDays,
|
|
67
|
+
maxCount,
|
|
68
|
+
dryRun: !opts.apply,
|
|
69
|
+
});
|
|
70
|
+
if (opts.json) {
|
|
71
|
+
console.log(JSON.stringify(result, null, 2));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!result.available) {
|
|
75
|
+
console.log(`[relay-baton] cannot prune: ${result.reason}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (result.policy.maxAgeDays === null && result.policy.maxCount === null) {
|
|
79
|
+
console.log("[relay-baton] no retention policy — pass --max-age-days and/or --max-count. Nothing pruned.");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const verb = result.dryRun ? "would prune" : "pruned";
|
|
83
|
+
console.log(`[relay-baton] ${verb} ${result.pruned.length} archive(s), keeping ${result.kept.length}${result.dryRun ? " (dry-run)" : ""}`);
|
|
84
|
+
for (const p of result.pruned)
|
|
85
|
+
console.log(`- ${p.id} (${p.createdAt ?? "unknown"}) — ${p.reason}`);
|
|
86
|
+
if (result.dryRun && result.pruned.length > 0)
|
|
87
|
+
console.log("re-run with --apply to delete the above.");
|
|
88
|
+
}
|
|
89
|
+
async function sessionExportCommand(archive, opts = {}) {
|
|
90
|
+
if (!opts.to) {
|
|
91
|
+
console.error("[relay-baton] missing required option: --to <dir>");
|
|
92
|
+
process.exit(2);
|
|
93
|
+
}
|
|
94
|
+
const result = new core_1.SessionArchiveStore({ archiveRoot: opts.out }).exportArchive(archive, opts.to, { overwrite: opts.overwrite });
|
|
95
|
+
if (opts.json) {
|
|
96
|
+
console.log(JSON.stringify(result, null, 2));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (!result.available) {
|
|
100
|
+
console.log(`[relay-baton] cannot export: ${result.reason}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
console.log(`[relay-baton] exported ${result.id}`);
|
|
104
|
+
console.log(`to: ${result.dest}`);
|
|
105
|
+
}
|
|
106
|
+
async function sessionInspectCommand(archive, opts = {}) {
|
|
107
|
+
const result = new core_1.SessionArchiveStore({ archiveRoot: opts.out }).inspect(archive);
|
|
108
|
+
if (opts.json) {
|
|
109
|
+
console.log(JSON.stringify(result, null, 2));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (!result.available) {
|
|
113
|
+
console.log(`[relay-baton] cannot inspect archive: ${result.reason}`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
console.log(`[relay-baton] archive ${result.id}`);
|
|
117
|
+
console.log(`repoRoot: ${result.repoRoot ?? "unknown"}`);
|
|
118
|
+
console.log(`createdAt: ${result.createdAt ?? "unknown"}`);
|
|
119
|
+
console.log(`files: ${result.fileCount}, total ${result.totalBytes} bytes`);
|
|
120
|
+
console.log(`integrity: ${result.intact ? "intact" : "DAMAGED"}`);
|
|
121
|
+
console.log(`trust: ${result.safe ? "safe" : "UNSAFE — do not import"}`);
|
|
122
|
+
if (result.missing.length > 0)
|
|
123
|
+
console.log(`missing: ${result.missing.join(", ")}`);
|
|
124
|
+
if (result.corrupt.length > 0)
|
|
125
|
+
console.log(`corrupt: ${result.corrupt.join(", ")}`);
|
|
126
|
+
if (result.unsafe.length > 0) {
|
|
127
|
+
console.log(`⚠ unsafe targets (path traversal / oversized): ${result.unsafe.length}`);
|
|
128
|
+
for (const f of result.files.filter(x => !x.safe)) {
|
|
129
|
+
console.log(` - [${f.unsafeReason}] ${f.target}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async function sessionResumeCommand(opts = {}) {
|
|
134
|
+
const repoRoot = (0, projectOptions_1.resolveRepoRoot)(opts);
|
|
135
|
+
core_1.ConfigLoader.load(repoRoot);
|
|
136
|
+
const staleHours = opts.staleHours != null ? Number.parseFloat(opts.staleHours) : undefined;
|
|
137
|
+
const result = new core_1.ResumeDiagnostics(repoRoot).diagnose({
|
|
138
|
+
staleHours: Number.isFinite(staleHours) ? staleHours : undefined,
|
|
139
|
+
});
|
|
140
|
+
if (opts.json) {
|
|
141
|
+
console.log(JSON.stringify(result, null, 2));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
console.log(`[relay-baton] session resume diagnosis: ${result.status}`);
|
|
145
|
+
if (result.meta?.task)
|
|
146
|
+
console.log(`task: ${result.meta.task}`);
|
|
147
|
+
if (result.findings.length > 0) {
|
|
148
|
+
console.log("findings:");
|
|
149
|
+
for (const f of result.findings)
|
|
150
|
+
console.log(`- ${f}`);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.log("findings: none");
|
|
154
|
+
}
|
|
155
|
+
console.log("suggested next:");
|
|
156
|
+
for (const s of result.suggestions)
|
|
157
|
+
console.log(`- relay-baton ${s.command} (${s.reason})`);
|
|
158
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ProjectOpts } from "./projectOptions";
|
|
2
|
+
export interface SessionWorkOpts extends ProjectOpts {
|
|
3
|
+
json?: boolean;
|
|
4
|
+
switch?: boolean;
|
|
5
|
+
agent?: string;
|
|
6
|
+
init?: boolean;
|
|
7
|
+
deleteFiles?: boolean;
|
|
8
|
+
branch?: string;
|
|
9
|
+
worktreePath?: string;
|
|
10
|
+
force?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** `session new <name>` — create a named work item (optionally switch + init). */
|
|
13
|
+
export declare function sessionNewCommand(name: string, opts?: SessionWorkOpts): Promise<void>;
|
|
14
|
+
/** `session switch|use <name>` — set the active work item. */
|
|
15
|
+
export declare function sessionSwitchCommand(name: string, opts?: SessionWorkOpts): Promise<void>;
|
|
16
|
+
/** `session items` — list work items in this repo (read-only). */
|
|
17
|
+
export declare function sessionItemsCommand(opts?: SessionWorkOpts): Promise<void>;
|
|
18
|
+
/** `session assign <name> <agent>` — pin a work item to an agent (v2.6 item 2 groundwork). */
|
|
19
|
+
export declare function sessionAssignCommand(name: string, agent: string, opts?: SessionWorkOpts): Promise<void>;
|
|
20
|
+
/** `session worktree add <name>` — back a work item with an isolated git worktree. */
|
|
21
|
+
export declare function sessionWorktreeAddCommand(name: string, opts?: SessionWorkOpts): Promise<void>;
|
|
22
|
+
/** `session worktree remove <name>` — detach + remove the work item's worktree. */
|
|
23
|
+
export declare function sessionWorktreeRemoveCommand(name: string, opts?: SessionWorkOpts): Promise<void>;
|
|
24
|
+
/** `session remove <name>` — drop a named work item (never default). */
|
|
25
|
+
export declare function sessionRemoveCommand(name: string, opts?: SessionWorkOpts): Promise<void>;
|