@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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/dist/commands/agentFor.d.ts +8 -0
  3. package/dist/commands/agentFor.js +28 -0
  4. package/dist/commands/auditApiKeyEnv.d.ts +6 -0
  5. package/dist/commands/auditApiKeyEnv.js +27 -0
  6. package/dist/commands/budget.d.ts +5 -0
  7. package/dist/commands/budget.js +86 -0
  8. package/dist/commands/chat.d.ts +5 -0
  9. package/dist/commands/chat.js +218 -0
  10. package/dist/commands/checkpoint.d.ts +10 -0
  11. package/dist/commands/checkpoint.js +78 -0
  12. package/dist/commands/compact.d.ts +4 -0
  13. package/dist/commands/compact.js +22 -0
  14. package/dist/commands/compress.d.ts +4 -0
  15. package/dist/commands/compress.js +61 -0
  16. package/dist/commands/compressContext.d.ts +8 -0
  17. package/dist/commands/compressContext.js +51 -0
  18. package/dist/commands/conversation.d.ts +8 -0
  19. package/dist/commands/conversation.js +90 -0
  20. package/dist/commands/diagnostics.d.ts +23 -0
  21. package/dist/commands/diagnostics.js +254 -0
  22. package/dist/commands/doctor.d.ts +5 -0
  23. package/dist/commands/doctor.js +104 -0
  24. package/dist/commands/execute.d.ts +9 -0
  25. package/dist/commands/execute.js +183 -0
  26. package/dist/commands/git.d.ts +6 -0
  27. package/dist/commands/git.js +82 -0
  28. package/dist/commands/guard.d.ts +7 -0
  29. package/dist/commands/guard.js +30 -0
  30. package/dist/commands/handoff.d.ts +10 -0
  31. package/dist/commands/handoff.js +133 -0
  32. package/dist/commands/handoffBundle.d.ts +12 -0
  33. package/dist/commands/handoffBundle.js +64 -0
  34. package/dist/commands/handoffHistory.d.ts +23 -0
  35. package/dist/commands/handoffHistory.js +129 -0
  36. package/dist/commands/handoffShow.d.ts +12 -0
  37. package/dist/commands/handoffShow.js +73 -0
  38. package/dist/commands/init.d.ts +2 -0
  39. package/dist/commands/init.js +19 -0
  40. package/dist/commands/inventory.d.ts +5 -0
  41. package/dist/commands/inventory.js +23 -0
  42. package/dist/commands/login.d.ts +3 -0
  43. package/dist/commands/login.js +80 -0
  44. package/dist/commands/migrate.d.ts +8 -0
  45. package/dist/commands/migrate.js +55 -0
  46. package/dist/commands/plan.d.ts +13 -0
  47. package/dist/commands/plan.js +159 -0
  48. package/dist/commands/profile.d.ts +5 -0
  49. package/dist/commands/profile.js +23 -0
  50. package/dist/commands/project.d.ts +18 -0
  51. package/dist/commands/project.js +173 -0
  52. package/dist/commands/projectOptions.d.ts +7 -0
  53. package/dist/commands/projectOptions.js +21 -0
  54. package/dist/commands/receipt.d.ts +8 -0
  55. package/dist/commands/receipt.js +48 -0
  56. package/dist/commands/replay.d.ts +8 -0
  57. package/dist/commands/replay.js +35 -0
  58. package/dist/commands/report.d.ts +6 -0
  59. package/dist/commands/report.js +54 -0
  60. package/dist/commands/review.d.ts +8 -0
  61. package/dist/commands/review.js +63 -0
  62. package/dist/commands/risk.d.ts +5 -0
  63. package/dist/commands/risk.js +25 -0
  64. package/dist/commands/run.d.ts +31 -0
  65. package/dist/commands/run.js +323 -0
  66. package/dist/commands/session.d.ts +40 -0
  67. package/dist/commands/session.js +158 -0
  68. package/dist/commands/sessionWorkspace.d.ts +25 -0
  69. package/dist/commands/sessionWorkspace.js +193 -0
  70. package/dist/commands/status.d.ts +5 -0
  71. package/dist/commands/status.js +116 -0
  72. package/dist/commands/tui.d.ts +4 -0
  73. package/dist/commands/tui.js +46 -0
  74. package/dist/commands/usage.d.ts +11 -0
  75. package/dist/commands/usage.js +40 -0
  76. package/dist/commands/verify.d.ts +15 -0
  77. package/dist/commands/verify.js +197 -0
  78. package/dist/commands/workspace.d.ts +5 -0
  79. package/dist/commands/workspace.js +27 -0
  80. package/dist/index.d.ts +2 -0
  81. package/dist/index.js +394 -0
  82. package/package.json +57 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DongGeon Lee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,8 @@
1
+ import type { AgentAdapter } from "@relay-baton/core";
2
+ import type { AgentId, RelayBatonConfig } from "@relay-baton/shared";
3
+ /**
4
+ * Resolve an AgentAdapter for a given agent id, honoring config.agents overrides.
5
+ * codex / claude are first-class; opencode / gemini / aider / cursor are v2.3
6
+ * supported adapters (see AgentRegistry for tiers).
7
+ */
8
+ export declare function adapterFor(id: AgentId, config: RelayBatonConfig): AgentAdapter;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.adapterFor = adapterFor;
4
+ const core_1 = require("@relay-baton/core");
5
+ /**
6
+ * Resolve an AgentAdapter for a given agent id, honoring config.agents overrides.
7
+ * codex / claude are first-class; opencode / gemini / aider / cursor are v2.3
8
+ * supported adapters (see AgentRegistry for tiers).
9
+ */
10
+ function adapterFor(id, config) {
11
+ switch (id) {
12
+ case "codex":
13
+ return new core_1.CodexAdapter(config.agents.codex);
14
+ case "claude":
15
+ return new core_1.ClaudeCodeAdapter(config.agents.claude);
16
+ case "opencode":
17
+ return new core_1.OpenCodeAdapter(config.agents.opencode);
18
+ case "gemini":
19
+ return new core_1.GeminiAdapter(config.agents.gemini);
20
+ case "aider":
21
+ return new core_1.AiderAdapter(config.agents.aider);
22
+ case "cursor":
23
+ return new core_1.CursorAdapter(config.agents.cursor);
24
+ default:
25
+ console.error(`[relay-baton] unsupported agent: ${id} (supported: ${core_1.ALL_AGENT_IDS.join(", ")})`);
26
+ process.exit(2);
27
+ }
28
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Record an audit trail when blocked provider key env vars were intentionally
3
+ * passed through to a child agent (via `--allow-api-key-env`). Names only — the
4
+ * values are never read, logged, or stored.
5
+ */
6
+ export declare function auditApiKeyEnv(repoRoot: string, passedThrough: string[] | undefined, sessionId?: string): void;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.auditApiKeyEnv = auditApiKeyEnv;
4
+ const core_1 = require("@relay-baton/core");
5
+ /**
6
+ * Record an audit trail when blocked provider key env vars were intentionally
7
+ * passed through to a child agent (via `--allow-api-key-env`). Names only — the
8
+ * values are never read, logged, or stored.
9
+ */
10
+ function auditApiKeyEnv(repoRoot, passedThrough, sessionId) {
11
+ if (!passedThrough || passedThrough.length === 0)
12
+ return;
13
+ const names = passedThrough.join(", ");
14
+ console.error(`[relay-baton] audit: passed API key env to child (names only): ${names}`);
15
+ try {
16
+ new core_1.ConversationLog(repoRoot).append({
17
+ role: "relay-baton",
18
+ kind: "status",
19
+ text: `audit: API key env passed to child via --allow-api-key-env: ${names} (names only; values never logged)`,
20
+ sessionId,
21
+ meta: { confirmed: true },
22
+ });
23
+ }
24
+ catch {
25
+ /* audit logging is best-effort */
26
+ }
27
+ }
@@ -0,0 +1,5 @@
1
+ import { ProjectOpts } from "./projectOptions";
2
+ export interface BudgetOpts extends ProjectOpts {
3
+ json?: boolean;
4
+ }
5
+ export declare function budgetCommand(opts?: BudgetOpts): Promise<void>;
@@ -0,0 +1,86 @@
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.budgetCommand = budgetCommand;
37
+ const fs = __importStar(require("fs"));
38
+ const core_1 = require("@relay-baton/core");
39
+ const projectOptions_1 = require("./projectOptions");
40
+ async function budgetCommand(opts = {}) {
41
+ const repoRoot = (0, projectOptions_1.resolveRepoRoot)(opts);
42
+ const { config } = core_1.ConfigLoader.load(repoRoot);
43
+ const sm = new core_1.SessionManager(repoRoot, config);
44
+ const meta = sm.getMeta();
45
+ const activeProfile = meta?.tokenDietProfile ?? config.tokenDiet.profile;
46
+ const profile = config.tokenDiet.profiles[activeProfile];
47
+ const read = (p) => { try {
48
+ return fs.readFileSync(p, "utf8");
49
+ }
50
+ catch {
51
+ return "";
52
+ } };
53
+ const handoff = read(sm.files.p("handoff"));
54
+ const repoMap = read(sm.files.p("repoMap"));
55
+ const fullDiff = read(sm.files.p("fullDiff"));
56
+ const log = read(sm.files.p("commandsLog"));
57
+ const compactState = read(sm.files.p("compactState"));
58
+ let budgetSnap = null;
59
+ try {
60
+ budgetSnap = JSON.parse(read(sm.files.p("contextBudget")));
61
+ }
62
+ catch { /**/ }
63
+ if (opts.json) {
64
+ console.log(JSON.stringify({
65
+ activeProfile,
66
+ maxHandoffChars: profile.maxHandoffChars,
67
+ used: {
68
+ handoff: handoff.length,
69
+ repoMap: repoMap.length,
70
+ fullDiff: fullDiff.length,
71
+ commandsLog: log.length,
72
+ compactState: compactState.length,
73
+ },
74
+ truncated: budgetSnap?.truncated ?? false,
75
+ }, null, 2));
76
+ return;
77
+ }
78
+ console.log("active profile:", activeProfile);
79
+ console.log("maxHandoffChars:", profile.maxHandoffChars);
80
+ console.log("handoff.md chars:", handoff.length);
81
+ console.log("repo-map.md chars:", repoMap.length);
82
+ console.log("full-diff.patch chars:", fullDiff.length);
83
+ console.log("commands.log chars:", log.length);
84
+ console.log("compact-state.md chars:", compactState.length);
85
+ console.log("truncated:", budgetSnap?.truncated ?? false);
86
+ }
@@ -0,0 +1,5 @@
1
+ import { ProjectOpts } from "./projectOptions";
2
+ export interface ChatOpts extends ProjectOpts {
3
+ allowApiKeyEnv?: boolean;
4
+ }
5
+ export declare function chatCommand(opts?: ChatOpts): Promise<void>;
@@ -0,0 +1,218 @@
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.chatCommand = chatCommand;
37
+ const readline = __importStar(require("readline"));
38
+ const core_1 = require("@relay-baton/core");
39
+ const projectOptions_1 = require("./projectOptions");
40
+ const agentFor_1 = require("./agentFor");
41
+ const review_1 = require("./review");
42
+ const doctor_1 = require("./doctor");
43
+ const budget_1 = require("./budget");
44
+ const status_1 = require("./status");
45
+ const plan_1 = require("./plan");
46
+ const execute_1 = require("./execute");
47
+ const handoff_1 = require("./handoff");
48
+ const LABELS = {
49
+ user: "you",
50
+ claude: "claude",
51
+ codex: "codex",
52
+ "relay-baton": "relay-baton",
53
+ };
54
+ function say(role, text) {
55
+ console.log(`[${LABELS[role] ?? role}] ${text}`);
56
+ }
57
+ /**
58
+ * Build a model-free prompt PREVIEW for a run-type command. This shows exactly
59
+ * what would be spawned; it never executes. Confirmation-first by design.
60
+ */
61
+ function buildPreview(command, args, agent, repoRoot, sessionDir, config, maxSteps = 3) {
62
+ if (command === "plan" || command === "replan") {
63
+ const prompt = core_1.PromptBuilder.planner(args);
64
+ const c = (0, agentFor_1.adapterFor)(agent, config).buildCommand({ task: prompt, prompt, repoRoot, sessionDir });
65
+ const note = command === "replan" ? " (current plan.md is backed up first)" : "";
66
+ return `${c.command} ${c.args.slice(0, -1).join(" ")} "<planner prompt, ${prompt.length} chars>"${note}`;
67
+ }
68
+ if (command === "continue") {
69
+ const prompt = core_1.PromptBuilder.executor();
70
+ const c = (0, agentFor_1.adapterFor)(agent, config).buildCommand({ task: prompt, prompt, repoRoot, sessionDir });
71
+ return `bounded loop: up to ${maxSteps} execute step(s), each: ${c.command} ${c.args.slice(0, -1).join(" ")} "<executor prompt>" — stops on no-progress or budget`;
72
+ }
73
+ if (command === "execute") {
74
+ const prompt = core_1.PromptBuilder.executor();
75
+ const c = (0, agentFor_1.adapterFor)(agent, config).buildCommand({ task: prompt, prompt, repoRoot, sessionDir });
76
+ return `${c.command} ${c.args.slice(0, -1).join(" ")} "<executor prompt, ${prompt.length} chars>"`;
77
+ }
78
+ if (command === "handoff") {
79
+ return `relay-baton handoff --to ${agent} (builds handoff.md, then launches ${agent})`;
80
+ }
81
+ return null;
82
+ }
83
+ function ask(rl, q) {
84
+ return new Promise(resolve => rl.question(q, a => resolve(a)));
85
+ }
86
+ async function chatCommand(opts = {}) {
87
+ const projectContext = (0, projectOptions_1.resolveProjectContext)(opts, true);
88
+ const repoRoot = projectContext.repoRoot;
89
+ const { config } = core_1.ConfigLoader.load(repoRoot);
90
+ const sm = new core_1.SessionManager(repoRoot, config);
91
+ if (!sm.getMeta())
92
+ sm.init("");
93
+ const sessionId = sm.getMeta()?.id;
94
+ const sessionDir = sm.files.dir;
95
+ const git = new core_1.GitService(repoRoot);
96
+ if (!git.isGitRepo()) {
97
+ console.error("[relay-baton] not a git repository. aborting.");
98
+ process.exit(2);
99
+ }
100
+ const engine = new core_1.RoomEngine(repoRoot, sessionId, "claude");
101
+ console.log("relay-baton room (v0.8 first cut). Turn-based, confirmation-first.");
102
+ console.log("This is NOT a realtime autopilot — every real agent run asks first.");
103
+ console.log("Type /help for commands, /exit to leave.\n");
104
+ say("relay-baton", `addressing ${engine.currentAgent} (planner/reviewer = claude, executor = codex).`);
105
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
106
+ const passthrough = {
107
+ project: opts.project,
108
+ path: opts.path,
109
+ allowApiKeyEnv: opts.allowApiKeyEnv,
110
+ };
111
+ let running = true;
112
+ while (running) {
113
+ const line = await ask(rl, `\n${engine.currentAgent}› `);
114
+ const action = engine.handle(line);
115
+ switch (action.kind) {
116
+ case "noop":
117
+ break;
118
+ case "exit":
119
+ running = false;
120
+ break;
121
+ case "help":
122
+ console.log("commands:\n" + action.text);
123
+ break;
124
+ case "error":
125
+ say("relay-baton", action.message);
126
+ break;
127
+ case "switch-agent":
128
+ say("relay-baton", `now addressing ${action.agent}.`);
129
+ break;
130
+ case "chat":
131
+ // Confirmation-first: a plain message is recorded but never auto-runs an
132
+ // agent. The user drives real work with explicit slash commands.
133
+ say("relay-baton", `noted (logged to conversation.jsonl). Use /plan, /execute, /review, or /handoff to act with ${action.agent}.`);
134
+ break;
135
+ case "run": {
136
+ await runAction(action, rl, { repoRoot, sessionDir, config, passthrough });
137
+ break;
138
+ }
139
+ }
140
+ }
141
+ rl.close();
142
+ console.log("\n[relay-baton] left the room.");
143
+ }
144
+ async function runAction(action, rl, ctx) {
145
+ const { command, args, agent } = action;
146
+ const maxSteps = action.maxSteps ?? 3;
147
+ // Read-only / no-model commands run immediately.
148
+ if (command === "review")
149
+ return void (await (0, review_1.reviewCommand)({ ...ctx.passthrough }));
150
+ if (command === "diagnose")
151
+ return void (await (0, doctor_1.doctorCommand)({ ...ctx.passthrough, deep: true }));
152
+ if (command === "budget")
153
+ return void (await (0, budget_1.budgetCommand)({ ...ctx.passthrough }));
154
+ if (command === "status")
155
+ return void (await (0, status_1.statusCommand)({ ...ctx.passthrough }));
156
+ if (command === "replay") {
157
+ for (const l of new core_1.ConversationReplay(ctx.repoRoot).renderLines())
158
+ console.log(l);
159
+ return;
160
+ }
161
+ // Real agent runs: show a prompt preview, then require explicit confirmation.
162
+ const preview = buildPreview(command, args, agent, ctx.repoRoot, ctx.sessionDir, ctx.config, maxSteps);
163
+ say("relay-baton", `prompt preview for /${command} (${agent}):`);
164
+ console.log(" " + (preview ?? "(no preview available)"));
165
+ const answer = (await ask(rl, " run this? [y/N] ")).trim().toLowerCase();
166
+ if (answer !== "y" && answer !== "yes") {
167
+ say("relay-baton", "cancelled. nothing ran.");
168
+ return;
169
+ }
170
+ if (command === "plan") {
171
+ await (0, plan_1.planCommand)(args, { ...ctx.passthrough, with: agent });
172
+ }
173
+ else if (command === "replan") {
174
+ try {
175
+ (0, core_1.backupPlan)(ctx.repoRoot, new Date());
176
+ }
177
+ catch { /* best-effort */ }
178
+ await (0, plan_1.planCommand)(args, { ...ctx.passthrough, with: agent });
179
+ }
180
+ else if (command === "execute") {
181
+ await (0, execute_1.executeCommand)({ ...ctx.passthrough, with: agent });
182
+ }
183
+ else if (command === "handoff") {
184
+ await (0, handoff_1.handoffCommand)({ ...ctx.passthrough, to: agent });
185
+ }
186
+ else if (command === "continue") {
187
+ await runBoundedContinue(maxSteps, agent, ctx);
188
+ }
189
+ }
190
+ /**
191
+ * Bounded plan↔execute continuation. Runs at most `maxSteps` execute passes,
192
+ * each gated by LoopController. Stops early on no-progress (divergence) using a
193
+ * git working-tree signature as the progress key. Never unbounded; never
194
+ * auto-commits. One confirmation already happened upstream.
195
+ */
196
+ async function runBoundedContinue(maxSteps, agent, ctx) {
197
+ const loop = new core_1.LoopController({ maxSteps });
198
+ const git = new core_1.GitService(ctx.repoRoot);
199
+ const progressKey = () => {
200
+ try {
201
+ return git.diff().length + ":" + git.changedFiles().length;
202
+ }
203
+ catch {
204
+ return undefined;
205
+ }
206
+ };
207
+ let obs = {};
208
+ for (;;) {
209
+ const d = loop.next(obs);
210
+ if (!d.proceed) {
211
+ say("relay-baton", `continue stopped after step ${d.step} (${d.reason ?? "done"}).`);
212
+ break;
213
+ }
214
+ say("relay-baton", `continue step ${d.step}/${maxSteps} (execute via ${agent})…`);
215
+ await (0, execute_1.executeCommand)({ ...ctx.passthrough, with: agent });
216
+ obs = { progressKey: progressKey() };
217
+ }
218
+ }
@@ -0,0 +1,10 @@
1
+ import { ProjectOpts } from "./projectOptions";
2
+ export interface CheckpointOpts extends ProjectOpts {
3
+ command?: string;
4
+ result?: string;
5
+ note?: string;
6
+ json?: boolean;
7
+ }
8
+ export declare function checkpointCommand(step: string, opts?: CheckpointOpts): Promise<void>;
9
+ export declare function checkpointSummaryCommand(opts?: CheckpointOpts): Promise<void>;
10
+ export declare function checkpointListCommand(opts?: CheckpointOpts): Promise<void>;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkpointCommand = checkpointCommand;
4
+ exports.checkpointSummaryCommand = checkpointSummaryCommand;
5
+ exports.checkpointListCommand = checkpointListCommand;
6
+ const core_1 = require("@relay-baton/core");
7
+ const projectOptions_1 = require("./projectOptions");
8
+ const RESULTS = ["ok", "fail", "pending"];
9
+ async function checkpointCommand(step, opts = {}) {
10
+ const n = Number(step);
11
+ if (!Number.isInteger(n) || n < 1) {
12
+ console.error(`[relay-baton] step must be a positive integer, got: ${step}`);
13
+ process.exit(2);
14
+ }
15
+ const result = (opts.result ?? "pending");
16
+ if (!RESULTS.includes(result)) {
17
+ console.error(`[relay-baton] result must be one of ${RESULTS.join("|")}, got: ${opts.result}`);
18
+ process.exit(2);
19
+ }
20
+ const repoRoot = (0, projectOptions_1.resolveRepoRoot)(opts);
21
+ const { config } = core_1.ConfigLoader.load(repoRoot);
22
+ const meta = new core_1.SessionManager(repoRoot, config).getMeta();
23
+ const checkpoint = new core_1.ExecutionCheckpoints(repoRoot, config).append({
24
+ step: n,
25
+ command: opts.command,
26
+ result,
27
+ note: opts.note,
28
+ });
29
+ new core_1.ConversationLog(repoRoot).append({
30
+ role: "relay-baton",
31
+ kind: "execute",
32
+ text: `checkpoint: step ${n} [${result}]${checkpoint.note ? " — " + checkpoint.note : ""}`,
33
+ sessionId: meta?.id,
34
+ meta: { stepRefs: [`step-${n}`], confirmed: true },
35
+ });
36
+ if (opts.json) {
37
+ console.log(JSON.stringify(checkpoint, null, 2));
38
+ return;
39
+ }
40
+ console.log(`[relay-baton] checkpoint recorded [${result}] for step ${n}.`);
41
+ console.log(`git: ${checkpoint.git.branch ?? "(no git)"} · ${checkpoint.git.changed} changed file(s)`);
42
+ }
43
+ async function checkpointSummaryCommand(opts = {}) {
44
+ const repoRoot = (0, projectOptions_1.resolveRepoRoot)(opts);
45
+ const summary = new core_1.ExecutionCheckpoints(repoRoot).summarize();
46
+ if (opts.json) {
47
+ console.log(JSON.stringify(summary, null, 2));
48
+ return;
49
+ }
50
+ if (summary.total === 0) {
51
+ console.log("[relay-baton] no execution checkpoints recorded.");
52
+ return;
53
+ }
54
+ const { results } = summary;
55
+ console.log(`[relay-baton] execution receipt: ${summary.total} checkpoint(s)`);
56
+ console.log(`results: ${results.ok} ok · ${results.fail} fail · ${results.pending} pending`);
57
+ console.log(`last: step ${summary.lastStep} [${summary.lastResult}]${summary.lastCommand ? " — " + summary.lastCommand : ""}`);
58
+ console.log(`max changed files in a step: ${summary.maxChangedFiles}`);
59
+ if (summary.latestBudget) {
60
+ console.log(`latest budget: ${summary.latestBudget.handoffChars}/${summary.latestBudget.maxHandoffChars} chars (${summary.latestBudget.activeProfile})`);
61
+ }
62
+ }
63
+ async function checkpointListCommand(opts = {}) {
64
+ const repoRoot = (0, projectOptions_1.resolveRepoRoot)(opts);
65
+ const checkpoints = new core_1.ExecutionCheckpoints(repoRoot).list();
66
+ if (opts.json) {
67
+ console.log(JSON.stringify(checkpoints, null, 2));
68
+ return;
69
+ }
70
+ if (checkpoints.length === 0) {
71
+ console.log("[relay-baton] no execution checkpoints recorded.");
72
+ return;
73
+ }
74
+ for (const c of checkpoints) {
75
+ const cmd = c.command ? ` — ${c.command}` : "";
76
+ console.log(`[${c.result}] step ${c.step}${cmd} (${c.changedFiles.length} files, ${c.ts})`);
77
+ }
78
+ }
@@ -0,0 +1,4 @@
1
+ import { ProjectOpts } from "./projectOptions";
2
+ export declare function compactCommand(opts: {
3
+ diet?: string;
4
+ } & ProjectOpts): Promise<void>;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compactCommand = compactCommand;
4
+ const core_1 = require("@relay-baton/core");
5
+ const projectOptions_1 = require("./projectOptions");
6
+ async function compactCommand(opts) {
7
+ const projectContext = (0, projectOptions_1.resolveProjectContext)(opts);
8
+ const repoRoot = projectContext.repoRoot;
9
+ const { config } = core_1.ConfigLoader.load(repoRoot);
10
+ const sm = new core_1.SessionManager(repoRoot, config);
11
+ if (!sm.getMeta())
12
+ sm.init("");
13
+ const profileName = (opts.diet ?? projectContext.project?.defaultDiet ?? config.tokenDiet.profile);
14
+ if (!config.tokenDiet.profiles[profileName]) {
15
+ console.error(`unknown diet profile: ${profileName}`);
16
+ process.exit(2);
17
+ }
18
+ const wf = new core_1.BatonWorkflow(sm, config);
19
+ wf.refreshArtifacts(profileName);
20
+ sm.updateMeta({ tokenDietProfile: profileName });
21
+ console.log(`[relay-baton] compacted session with profile=${profileName}`);
22
+ }
@@ -0,0 +1,4 @@
1
+ export declare function compressCommand(file: string, opts: {
2
+ write?: boolean;
3
+ out?: string;
4
+ }): Promise<void>;
@@ -0,0 +1,61 @@
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.compressCommand = compressCommand;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const core_1 = require("@relay-baton/core");
40
+ async function compressCommand(file, opts) {
41
+ const repoRoot = process.cwd();
42
+ const target = path.isAbsolute(file) ? file : path.join(repoRoot, file);
43
+ if (!fs.existsSync(target)) {
44
+ console.error(`file not found: ${target}`);
45
+ process.exit(2);
46
+ }
47
+ const src = fs.readFileSync(target, "utf8");
48
+ const out = (0, core_1.deterministicCompress)(src);
49
+ if (opts.out) {
50
+ fs.writeFileSync(opts.out, out, "utf8");
51
+ console.log(`[relay-baton] wrote ${opts.out} (${src.length} -> ${out.length} chars)`);
52
+ }
53
+ else if (opts.write) {
54
+ fs.writeFileSync(target, out, "utf8");
55
+ console.log(`[relay-baton] rewrote ${target} (${src.length} -> ${out.length} chars)`);
56
+ }
57
+ else {
58
+ process.stdout.write(out);
59
+ process.stderr.write(`\n[relay-baton] preview only (${src.length} -> ${out.length} chars). Use --write or --out.\n`);
60
+ }
61
+ }
@@ -0,0 +1,8 @@
1
+ import { ProjectOpts } from "./projectOptions";
2
+ export interface CompressContextOpts extends ProjectOpts {
3
+ diet?: string;
4
+ threshold?: string;
5
+ dryRun?: boolean;
6
+ force?: boolean;
7
+ }
8
+ export declare function compressContextCommand(opts: CompressContextOpts): Promise<void>;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compressContextCommand = compressContextCommand;
4
+ const core_1 = require("@relay-baton/core");
5
+ const projectOptions_1 = require("./projectOptions");
6
+ async function compressContextCommand(opts) {
7
+ const projectContext = (0, projectOptions_1.resolveProjectContext)(opts);
8
+ const repoRoot = projectContext.repoRoot;
9
+ const { config } = core_1.ConfigLoader.load(repoRoot);
10
+ const sm = new core_1.SessionManager(repoRoot, config);
11
+ if (!sm.getMeta()) {
12
+ console.log("[relay-baton] no session. Run `relay-baton init`.");
13
+ return;
14
+ }
15
+ const meta = sm.getMeta();
16
+ const profileName = (opts.diet ?? meta.tokenDietProfile ?? projectContext.project?.defaultDiet ?? config.tokenDiet.profile);
17
+ const profile = config.tokenDiet.profiles[profileName];
18
+ if (!profile) {
19
+ console.error(`unknown diet profile: ${profileName}`);
20
+ process.exit(2);
21
+ }
22
+ const threshold = opts.threshold != null ? Number(opts.threshold) : undefined;
23
+ if (threshold != null && (Number.isNaN(threshold) || threshold < 0 || threshold > 1)) {
24
+ console.error(`--threshold must be between 0 and 1 (got ${opts.threshold})`);
25
+ process.exit(2);
26
+ }
27
+ const compressor = new core_1.ContextCompressor(repoRoot, config);
28
+ const before = compressor.weigh(profile);
29
+ console.log(`[relay-baton] context weight: ${before.total}/${before.budget} chars (ratio ${before.ratio.toFixed(2)})`);
30
+ const result = compressor.compressIfNeeded(profile, { force: opts.force, threshold, dryRun: opts.dryRun });
31
+ if (opts.dryRun) {
32
+ console.log(`[relay-baton] dry-run: ${result.reason}`);
33
+ return;
34
+ }
35
+ if (!result.compressed) {
36
+ if (result.rolledBack) {
37
+ console.error("[relay-baton] compression rolled back (gate failed):");
38
+ for (const f of result.gateFailures ?? [])
39
+ console.error(" - " + f);
40
+ process.exit(3);
41
+ }
42
+ console.log(`[relay-baton] skipped: ${result.reason}`);
43
+ return;
44
+ }
45
+ sm.updateMeta({ status: "compressing" });
46
+ console.log(`[relay-baton] compressed: ${result.before.total} -> ${result.after?.total} chars`);
47
+ if (result.rotatedLog)
48
+ console.log(`[relay-baton] raw log rotated to ${result.rotatedLog}`);
49
+ // Compression is a transient phase; leave the prior status semantics intact.
50
+ sm.updateMeta({ status: meta.status });
51
+ }