@viraatdas/rudder 2.12.9 → 2.12.11
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 +103 -23
- package/dist/backends.js +56 -6
- package/dist/backends.js.map +1 -1
- package/dist/board/board.js +2 -2
- package/dist/board/daemon.d.ts +5 -1
- package/dist/board/daemon.js +26 -2
- package/dist/board/daemon.js.map +1 -1
- package/dist/brain.d.ts +1 -0
- package/dist/brain.js +49 -7
- package/dist/brain.js.map +1 -1
- package/dist/codex-binary.d.ts +4 -1
- package/dist/codex-binary.js +25 -6
- package/dist/codex-binary.js.map +1 -1
- package/dist/effort.js +5 -0
- package/dist/effort.js.map +1 -1
- package/dist/graph.js +6 -1
- package/dist/graph.js.map +1 -1
- package/dist/handoff.d.ts +83 -0
- package/dist/handoff.js +466 -0
- package/dist/handoff.js.map +1 -0
- package/dist/main.js +15 -2
- package/dist/main.js.map +1 -1
- package/dist/migration.js +1 -1
- package/dist/migration.js.map +1 -1
- package/dist/native/darwin-arm64/rudder-native +0 -0
- package/dist/native/darwin-x64/rudder-native +0 -0
- package/dist/native/linux-x64/rudder-native +0 -0
- package/dist/native-agents.js +21 -4
- package/dist/native-agents.js.map +1 -1
- package/dist/planner.js +9 -2
- package/dist/planner.js.map +1 -1
- package/dist/run-manager.js +13 -1
- package/dist/run-manager.js.map +1 -1
- package/dist/scheduler.js +10 -2
- package/dist/scheduler.js.map +1 -1
- package/dist/state.js +0 -1
- package/dist/state.js.map +1 -1
- package/dist/surfaces.d.ts +15 -0
- package/dist/surfaces.js +8 -0
- package/dist/surfaces.js.map +1 -1
- package/dist/types.d.ts +22 -4
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/handoff.js
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// CONVERSATION HANDOFF (push side).
|
|
3
|
+
//
|
|
4
|
+
// `rudder handoff "<next step>"` is meant to be run from INSIDE the chat you are
|
|
5
|
+
// already having — a plain `claude`/`codex` session in another terminal, or the
|
|
6
|
+
// agent in this one. It queues that conversation for the dashboard, which forks it
|
|
7
|
+
// into an agent pane holding the entire discussion: the files you looked at, the
|
|
8
|
+
// decisions you made, the plan you agreed on. Nothing is retyped.
|
|
9
|
+
//
|
|
10
|
+
// The queue is a directory of small JSON files under `.rudder/handoffs/`. The
|
|
11
|
+
// dashboard drains it on its poll (native/src/handoff.rs), so this command works
|
|
12
|
+
// whether Rudder is already open or is started afterwards.
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
import { execFileSync } from "node:child_process";
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import os from "node:os";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { randomUUID } from "node:crypto";
|
|
19
|
+
import { findRepoRoot } from "./git.js";
|
|
20
|
+
import { encodeClaudeProjectsCwd } from "./migration.js";
|
|
21
|
+
/** Transcripts grow to tens of megabytes; the opening prompt is at the top. */
|
|
22
|
+
const TITLE_SCAN_BYTES = 256 * 1024;
|
|
23
|
+
export function parseHandoffArgs(args) {
|
|
24
|
+
const words = [];
|
|
25
|
+
let sessionId;
|
|
26
|
+
let backend;
|
|
27
|
+
let here = false;
|
|
28
|
+
let list = false;
|
|
29
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
30
|
+
const arg = args[i] ?? "";
|
|
31
|
+
if (arg === "--here" || arg === "--main") {
|
|
32
|
+
here = true;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (arg === "--list" || arg === "--ls") {
|
|
36
|
+
list = true;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (arg === "--claude" || arg === "--codex" || arg === "--opencode") {
|
|
40
|
+
backend = arg.slice(2);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (arg === "--session" || arg === "-s") {
|
|
44
|
+
sessionId = args[i + 1];
|
|
45
|
+
i += 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (arg.startsWith("--session=")) {
|
|
49
|
+
sessionId = arg.slice("--session=".length);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
words.push(arg);
|
|
53
|
+
}
|
|
54
|
+
return { sessionId, backend, instruction: words.join(" ").trim(), here, list };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A session id is spliced onto a `claude`/`codex` command line by the dashboard.
|
|
58
|
+
* Accept only id-shaped strings — the same rule the Rust side enforces on read.
|
|
59
|
+
*/
|
|
60
|
+
export function isValidSessionId(value) {
|
|
61
|
+
const trimmed = value.trim();
|
|
62
|
+
return trimmed.length >= 8 && trimmed.length <= 128 && /^[A-Za-z0-9_-]+$/.test(trimmed);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Drop the wrapper blocks Claude Code injects around real user text
|
|
66
|
+
* (`<system-reminder>…`, slash-command echoes, the local-command caveat) and
|
|
67
|
+
* collapse what is left onto one line. Mirrors strip_wrapper_blocks in
|
|
68
|
+
* native/src/handoff.rs.
|
|
69
|
+
*/
|
|
70
|
+
export function stripWrapperBlocks(text) {
|
|
71
|
+
const kept = [];
|
|
72
|
+
let closing;
|
|
73
|
+
for (const raw of text.split("\n")) {
|
|
74
|
+
const line = raw.trim();
|
|
75
|
+
if (closing) {
|
|
76
|
+
if (line.endsWith(closing)) {
|
|
77
|
+
closing = undefined;
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (!line || line.startsWith("Caveat:")) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
// Wrapper tags may carry attributes: <teammate-message teammate_id="lead">.
|
|
85
|
+
const opening = /^<([A-Za-z0-9_-]+)(\s[^>]*)?>/.exec(line);
|
|
86
|
+
if (opening) {
|
|
87
|
+
const close = `</${opening[1]}>`;
|
|
88
|
+
if (!line.includes(close)) {
|
|
89
|
+
closing = close;
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
kept.push(line);
|
|
94
|
+
}
|
|
95
|
+
return kept.join(" ").split(/\s+/).filter(Boolean).join(" ");
|
|
96
|
+
}
|
|
97
|
+
/** The conversation's opening user prompt: what a human would call the chat. */
|
|
98
|
+
export function firstUserPrompt(raw) {
|
|
99
|
+
for (const line of raw.split("\n")) {
|
|
100
|
+
const prompt = userPromptFromLine(line);
|
|
101
|
+
if (prompt) {
|
|
102
|
+
return prompt;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* One transcript entry's user prose, or undefined when it is not a real user turn.
|
|
109
|
+
* Skips sidechain (subagent) turns, meta entries, and the wrapper blocks Claude
|
|
110
|
+
* Code injects around user text.
|
|
111
|
+
*/
|
|
112
|
+
function userPromptFromLine(line) {
|
|
113
|
+
let value;
|
|
114
|
+
try {
|
|
115
|
+
value = JSON.parse(line);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
if (value.type !== "user" || value.isSidechain === true || value.isMeta === true) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
const message = value.message;
|
|
124
|
+
const content = message?.content;
|
|
125
|
+
let text = "";
|
|
126
|
+
if (typeof content === "string") {
|
|
127
|
+
text = content;
|
|
128
|
+
}
|
|
129
|
+
else if (Array.isArray(content)) {
|
|
130
|
+
text = content
|
|
131
|
+
.filter((block) => Boolean(block) && block.type === "text")
|
|
132
|
+
.map((block) => block.text)
|
|
133
|
+
.join(" ");
|
|
134
|
+
}
|
|
135
|
+
return stripWrapperBlocks(text) || undefined;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* What the listing needs from a transcript, read from its head.
|
|
139
|
+
*
|
|
140
|
+
* `interactive` is whether a human talked to a REPL: Claude records its session
|
|
141
|
+
* mode at start, and the one-shot `claude -p` calls Rudder makes internally (task
|
|
142
|
+
* titles, completion notes) do not — which is how they stay out of the listing.
|
|
143
|
+
*/
|
|
144
|
+
export function scanTranscriptHead(raw) {
|
|
145
|
+
let interactive = false;
|
|
146
|
+
for (const line of raw.split("\n")) {
|
|
147
|
+
if (/"type"\s*:\s*"(mode|permission-mode)"/.test(line)) {
|
|
148
|
+
interactive = true;
|
|
149
|
+
}
|
|
150
|
+
const title = userPromptFromLine(line);
|
|
151
|
+
if (title) {
|
|
152
|
+
return { title, interactive };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
function readHead(file, maxBytes) {
|
|
158
|
+
let handle;
|
|
159
|
+
try {
|
|
160
|
+
handle = fs.openSync(file, "r");
|
|
161
|
+
const buffer = Buffer.alloc(maxBytes);
|
|
162
|
+
const read = fs.readSync(handle, buffer, 0, maxBytes, 0);
|
|
163
|
+
return buffer.subarray(0, read).toString("utf8");
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return "";
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
if (handle !== undefined) {
|
|
170
|
+
fs.closeSync(handle);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export function claudeProjectsDir() {
|
|
175
|
+
return path.join(os.homedir(), ".claude", "projects");
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Recent Claude conversations recorded for `cwd` and its subdirectories, newest
|
|
179
|
+
* first. Claude names each project folder after the directory the session ran in,
|
|
180
|
+
* so a chat started in `src/` lives in a sibling folder — still this repo's.
|
|
181
|
+
* Rudder's own agent sessions (under `.rudder-worktrees`) are not candidates.
|
|
182
|
+
*/
|
|
183
|
+
export function recentClaudeConversations(cwd, limit = 10, projects = claudeProjectsDir(), exclude = new Set()) {
|
|
184
|
+
const encoded = encodeClaudeProjectsCwd(path.resolve(cwd));
|
|
185
|
+
let dirs;
|
|
186
|
+
try {
|
|
187
|
+
dirs = fs.readdirSync(projects);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
const files = [];
|
|
193
|
+
for (const name of dirs) {
|
|
194
|
+
const inRepo = name === encoded || name.startsWith(`${encoded}-`);
|
|
195
|
+
if (!inRepo || name.includes("-rudder-worktrees-")) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
let sessions;
|
|
199
|
+
try {
|
|
200
|
+
sessions = fs.readdirSync(path.join(projects, name));
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
for (const session of sessions) {
|
|
206
|
+
if (!session.endsWith(".jsonl")) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const file = path.join(projects, name, session);
|
|
210
|
+
try {
|
|
211
|
+
files.push({ file, modifiedMs: fs.statSync(file).mtimeMs });
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// Raced with a delete; skip it.
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
files.sort((left, right) => right.modifiedMs - left.modifiedMs);
|
|
219
|
+
const interactive = [];
|
|
220
|
+
const all = [];
|
|
221
|
+
// Open a few times the requested count: the newest transcripts are often
|
|
222
|
+
// Rudder's own one-shot calls, and dropping them must not empty the list.
|
|
223
|
+
for (const { file, modifiedMs } of files.slice(0, limit * 4)) {
|
|
224
|
+
const sessionId = path.basename(file, ".jsonl");
|
|
225
|
+
if (!isValidSessionId(sessionId) || exclude.has(sessionId)) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const head = scanTranscriptHead(readHead(file, TITLE_SCAN_BYTES));
|
|
229
|
+
if (!head) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const candidate = { sessionId, title: head.title, modifiedMs, path: file };
|
|
233
|
+
if (head.interactive && interactive.length < limit) {
|
|
234
|
+
interactive.push(candidate);
|
|
235
|
+
}
|
|
236
|
+
if (all.length < limit) {
|
|
237
|
+
all.push(candidate);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// A noisy listing beats an empty one if the interactive marker ever disappears.
|
|
241
|
+
return interactive.length > 0 ? interactive : all;
|
|
242
|
+
}
|
|
243
|
+
export function claudeTranscriptPath(cwd, sessionId, projects = claudeProjectsDir()) {
|
|
244
|
+
const direct = path.join(projects, encodeClaudeProjectsCwd(path.resolve(cwd)), `${sessionId}.jsonl`);
|
|
245
|
+
if (fs.existsSync(direct)) {
|
|
246
|
+
return direct;
|
|
247
|
+
}
|
|
248
|
+
// A chat started in a subdirectory (or a differently-encoded path) still belongs
|
|
249
|
+
// to this machine; find it by id rather than refusing the handoff.
|
|
250
|
+
let dirs;
|
|
251
|
+
try {
|
|
252
|
+
dirs = fs.readdirSync(projects);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
for (const name of dirs) {
|
|
258
|
+
const candidate = path.join(projects, name, `${sessionId}.jsonl`);
|
|
259
|
+
if (fs.existsSync(candidate)) {
|
|
260
|
+
return candidate;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* The conversation this command is running inside. Claude Code exports its session
|
|
267
|
+
* id to every process it spawns, which is the whole trick: an agent asked to "hand
|
|
268
|
+
* this off to rudder" can run the command with no arguments and mean THIS chat.
|
|
269
|
+
* Falls back to the newest transcript recorded for the directory.
|
|
270
|
+
*/
|
|
271
|
+
export function resolveClaudeSessionId(cwd, env = process.env) {
|
|
272
|
+
const fromEnv = (env.CLAUDE_CODE_SESSION_ID ?? env.CLAUDE_SESSION_ID ?? "").trim();
|
|
273
|
+
if (isValidSessionId(fromEnv)) {
|
|
274
|
+
return fromEnv;
|
|
275
|
+
}
|
|
276
|
+
return recentClaudeConversations(cwd, 1)[0]?.sessionId;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Newest opencode session for `cwd`. opencode keeps sessions in a database and
|
|
280
|
+
* ships the query, so Rudder asks it rather than reading a schema it does not own.
|
|
281
|
+
* Running this from inside an opencode chat resolves to THAT chat: it is the one
|
|
282
|
+
* that was updated most recently.
|
|
283
|
+
*/
|
|
284
|
+
export function resolveOpencodeSessionId(cwd) {
|
|
285
|
+
return recentOpencodeConversations(cwd, 1)[0]?.sessionId;
|
|
286
|
+
}
|
|
287
|
+
export function recentOpencodeConversations(cwd, limit = 10) {
|
|
288
|
+
let raw;
|
|
289
|
+
try {
|
|
290
|
+
raw = execFileSync("opencode", ["session", "list", "--format", "json", "-n", String(limit * 4)], {
|
|
291
|
+
encoding: "utf8",
|
|
292
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
293
|
+
timeout: 10_000,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
return parseOpencodeSessions(raw, cwd, limit);
|
|
300
|
+
}
|
|
301
|
+
export function parseOpencodeSessions(raw, cwd, limit = 10) {
|
|
302
|
+
let sessions;
|
|
303
|
+
try {
|
|
304
|
+
sessions = JSON.parse(raw);
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
return [];
|
|
308
|
+
}
|
|
309
|
+
if (!Array.isArray(sessions)) {
|
|
310
|
+
return [];
|
|
311
|
+
}
|
|
312
|
+
const root = path.resolve(cwd);
|
|
313
|
+
return sessions
|
|
314
|
+
.filter((session) => Boolean(session) && typeof session === "object")
|
|
315
|
+
.filter((session) => {
|
|
316
|
+
const directory = session.directory;
|
|
317
|
+
return typeof directory === "string" && path.resolve(directory).startsWith(root);
|
|
318
|
+
})
|
|
319
|
+
.filter((session) => typeof session.id === "string" && isValidSessionId(session.id))
|
|
320
|
+
.map((session) => ({
|
|
321
|
+
sessionId: String(session.id),
|
|
322
|
+
title: typeof session.title === "string" ? session.title.trim() : "",
|
|
323
|
+
modifiedMs: typeof session.updated === "number" ? session.updated : 0,
|
|
324
|
+
path: typeof session.directory === "string" ? session.directory : cwd,
|
|
325
|
+
}))
|
|
326
|
+
.filter((candidate) => candidate.title.length > 0)
|
|
327
|
+
.sort((left, right) => right.modifiedMs - left.modifiedMs)
|
|
328
|
+
.slice(0, limit);
|
|
329
|
+
}
|
|
330
|
+
/** Newest Codex session recorded for `cwd`. Codex sessions live in one global tree. */
|
|
331
|
+
export function resolveCodexSessionId(cwd, root = path.join(os.homedir(), ".codex", "sessions")) {
|
|
332
|
+
const target = path.resolve(cwd);
|
|
333
|
+
let best;
|
|
334
|
+
const walk = (dir, depth) => {
|
|
335
|
+
if (depth > 8) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
let entries;
|
|
339
|
+
try {
|
|
340
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
for (const entry of entries) {
|
|
346
|
+
const full = path.join(dir, entry.name);
|
|
347
|
+
if (entry.isDirectory()) {
|
|
348
|
+
walk(full, depth + 1);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (!entry.name.endsWith(".jsonl")) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const head = readHead(full, 64 * 1024);
|
|
355
|
+
if (!head.includes(`"cwd":"${target}"`) && !head.includes(`"cwd": "${target}"`)) {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const id = codexSessionIdFrom(head) ?? path.basename(entry.name, ".jsonl");
|
|
359
|
+
if (!isValidSessionId(id)) {
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
let modifiedMs = 0;
|
|
363
|
+
try {
|
|
364
|
+
modifiedMs = fs.statSync(full).mtimeMs;
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (!best || modifiedMs > best.modifiedMs) {
|
|
370
|
+
best = { id, modifiedMs };
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
walk(root, 0);
|
|
375
|
+
return best?.id;
|
|
376
|
+
}
|
|
377
|
+
function codexSessionIdFrom(head) {
|
|
378
|
+
const match = /"id"\s*:\s*"([0-9a-fA-F-]{8,})"/.exec(head);
|
|
379
|
+
return match?.[1];
|
|
380
|
+
}
|
|
381
|
+
/** `.rudder/` lives at the dashboard root, never inside an agent's workspace. */
|
|
382
|
+
export function dashboardRoot(cwd = process.cwd()) {
|
|
383
|
+
const root = findRepoRoot(cwd);
|
|
384
|
+
const marker = `${path.sep}.rudder-worktrees${path.sep}`;
|
|
385
|
+
const index = root.indexOf(marker);
|
|
386
|
+
return index === -1 ? root : root.slice(0, index);
|
|
387
|
+
}
|
|
388
|
+
export function handoffQueueDir(repoRoot) {
|
|
389
|
+
return path.join(repoRoot, ".rudder", "handoffs");
|
|
390
|
+
}
|
|
391
|
+
export function writeHandoffRequest(repoRoot, request) {
|
|
392
|
+
const dir = handoffQueueDir(repoRoot);
|
|
393
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
394
|
+
// Filename order is queue order on the reading side, so lead with the stamp.
|
|
395
|
+
const file = path.join(dir, `${request.createdAt}-${request.requestId.slice(0, 8)}.json`);
|
|
396
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
397
|
+
fs.writeFileSync(temp, `${JSON.stringify(request, null, 2)}\n`);
|
|
398
|
+
fs.renameSync(temp, file);
|
|
399
|
+
return file;
|
|
400
|
+
}
|
|
401
|
+
export async function runHandoff(args, backendFlag) {
|
|
402
|
+
const parsed = parseHandoffArgs(args);
|
|
403
|
+
const cwd = process.cwd();
|
|
404
|
+
const repoRoot = dashboardRoot(cwd);
|
|
405
|
+
if (parsed.list) {
|
|
406
|
+
const candidates = [...recentClaudeConversations(repoRoot, 10), ...recentOpencodeConversations(repoRoot, 10)]
|
|
407
|
+
.sort((left, right) => right.modifiedMs - left.modifiedMs)
|
|
408
|
+
.slice(0, 10);
|
|
409
|
+
if (candidates.length === 0) {
|
|
410
|
+
console.log("No conversations recorded for this repository yet.");
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
console.log("Recent conversations in this repository:\n");
|
|
414
|
+
for (const candidate of candidates) {
|
|
415
|
+
const when = new Date(candidate.modifiedMs).toLocaleString();
|
|
416
|
+
console.log(` ${candidate.sessionId} ${when}`);
|
|
417
|
+
console.log(` ${candidate.title.slice(0, 100)}`);
|
|
418
|
+
}
|
|
419
|
+
console.log('\nHand one off with: rudder handoff --session <id> "<next step>"');
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const backend = parsed.backend ??
|
|
423
|
+
(backendFlag === "codex" || backendFlag === "opencode" ? backendFlag : undefined) ??
|
|
424
|
+
(process.env.CODEX_SESSION_ID || process.env.CODEX_HOME_SESSION ? "codex" : "claude");
|
|
425
|
+
let sessionId = parsed.sessionId?.trim();
|
|
426
|
+
if (!sessionId) {
|
|
427
|
+
// With no id, "the conversation this command is running inside" is the newest
|
|
428
|
+
// one this backend recorded for the directory.
|
|
429
|
+
sessionId =
|
|
430
|
+
backend === "codex"
|
|
431
|
+
? resolveCodexSessionId(cwd)
|
|
432
|
+
: backend === "opencode"
|
|
433
|
+
? resolveOpencodeSessionId(cwd)
|
|
434
|
+
: resolveClaudeSessionId(cwd);
|
|
435
|
+
}
|
|
436
|
+
if (!sessionId) {
|
|
437
|
+
throw new Error(`No ${backend} conversation found to hand off. Run this from inside the chat, or pass --session <id> (list them with \`rudder handoff --list\`).`);
|
|
438
|
+
}
|
|
439
|
+
if (!isValidSessionId(sessionId)) {
|
|
440
|
+
throw new Error(`Not a session id: ${sessionId}`);
|
|
441
|
+
}
|
|
442
|
+
let title;
|
|
443
|
+
if (backend === "claude") {
|
|
444
|
+
const transcript = claudeTranscriptPath(cwd, sessionId);
|
|
445
|
+
if (!transcript) {
|
|
446
|
+
throw new Error(`No Claude transcript found for session ${sessionId}. List the ones Rudder can see with \`rudder handoff --list\`.`);
|
|
447
|
+
}
|
|
448
|
+
title = firstUserPrompt(readHead(transcript, TITLE_SCAN_BYTES));
|
|
449
|
+
}
|
|
450
|
+
const request = {
|
|
451
|
+
requestId: randomUUID(),
|
|
452
|
+
sessionId,
|
|
453
|
+
backend,
|
|
454
|
+
target: parsed.here ? "here" : "worker",
|
|
455
|
+
instruction: parsed.instruction || undefined,
|
|
456
|
+
title,
|
|
457
|
+
sourceCwd: cwd,
|
|
458
|
+
createdAt: Date.now(),
|
|
459
|
+
};
|
|
460
|
+
writeHandoffRequest(repoRoot, request);
|
|
461
|
+
const where = request.target === "here" ? "the main checkout" : "an isolated worker";
|
|
462
|
+
const label = title ? `“${title.slice(0, 60)}”` : `session ${sessionId.slice(0, 8)}…`;
|
|
463
|
+
console.log(`Queued ${label} for handoff into ${where}.`);
|
|
464
|
+
console.log("The dashboard forks the conversation (the original chat is untouched) within a second; run `rudder` if it is not open.");
|
|
465
|
+
}
|
|
466
|
+
//# sourceMappingURL=handoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handoff.js","sourceRoot":"","sources":["../src/handoff.ts"],"names":[],"mappings":"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"}
|
package/dist/main.js
CHANGED
|
@@ -8,6 +8,7 @@ import { runCloudCommand } from "./cloud.js";
|
|
|
8
8
|
import { printContextAudit } from "./context-audit.js";
|
|
9
9
|
import { currentBranch, findRepoRoot } from "./git.js";
|
|
10
10
|
import { runGc } from "./gc.js";
|
|
11
|
+
import { runHandoff } from "./handoff.js";
|
|
11
12
|
import { ensureBoardRunning } from "./daemon.js";
|
|
12
13
|
import { createNodeWorkspace, currentJjChangeId, currentOpId, ensureColocated, ensureJj, undoLast, undoToOp, } from "./jj.js";
|
|
13
14
|
import { graphPath, mirrorPlanIntoGraph, updateGraph } from "./graph.js";
|
|
@@ -293,6 +294,12 @@ export async function main() {
|
|
|
293
294
|
await runContextCommand(parsed);
|
|
294
295
|
return;
|
|
295
296
|
}
|
|
297
|
+
case "handoff": {
|
|
298
|
+
// Run from inside a live claude/codex chat: queues THIS conversation for
|
|
299
|
+
// the dashboard, which forks it into an agent pane with all of its context.
|
|
300
|
+
await runHandoff(parsed.args, parsed.flags.backend);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
296
303
|
default: {
|
|
297
304
|
await maybeOnboard();
|
|
298
305
|
await startRun({
|
|
@@ -515,7 +522,7 @@ function readValue(argv, index, flag) {
|
|
|
515
522
|
return value;
|
|
516
523
|
}
|
|
517
524
|
function normalizeBackend(value) {
|
|
518
|
-
if (value === "claude" || value === "codex" || value === "acpx") {
|
|
525
|
+
if (value === "claude" || value === "codex" || value === "acpx" || value === "opencode") {
|
|
519
526
|
return value;
|
|
520
527
|
}
|
|
521
528
|
throw new Error(`Unknown backend: ${value}`);
|
|
@@ -938,6 +945,12 @@ Planner:
|
|
|
938
945
|
rudder fanout "task" [--n N] Fan out N variant agents on one task, then a judge picks/merges the best (default N=3)
|
|
939
946
|
rudder context audit Audit agent context files for size, duplication, secrets, and injection text
|
|
940
947
|
|
|
948
|
+
Conversation handoff:
|
|
949
|
+
rudder handoff "<next step>" Hand THIS chat to Rudder: it forks the conversation into a worker that already knows everything
|
|
950
|
+
rudder handoff --here "..." Continue it in the main checkout instead of an isolated workspace
|
|
951
|
+
rudder handoff --opencode "..." Hand off an opencode chat (default is the claude one; --codex for codex)
|
|
952
|
+
rudder handoff --list List recent conversations in this repo with their session ids
|
|
953
|
+
|
|
941
954
|
Memory:
|
|
942
955
|
rudder remember "<insight>" Append a durable cross-cutting decision to DECISIONS.md (shared, jj-tracked)
|
|
943
956
|
rudder share "<token/context>" Append gitignored shared context to RUDDER_SHARED.md for all agents
|
|
@@ -972,7 +985,7 @@ Options:
|
|
|
972
985
|
--worktree Always isolate in a worktree/workspace
|
|
973
986
|
--queue Queue mode (reserved)
|
|
974
987
|
-m, --model <model> Backend model
|
|
975
|
-
-b, --backend <backend> claude or
|
|
988
|
+
-b, --backend <backend> claude, codex, or opencode
|
|
976
989
|
-C, --cwd <dir> Run from another directory
|
|
977
990
|
-p, --port <port> Board server port (default 4774)
|
|
978
991
|
--n <count> Number of fan-out variants for rudder fanout (default 3)
|