rl-workspaces 0.0.0-stage → 0.6.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/README.md +735 -2
- package/bin/rl +54 -0
- package/config.example.zsh +9 -0
- package/dist/access.js +40 -0
- package/dist/activity.js +90 -0
- package/dist/agent-claude.js +16 -0
- package/dist/agent-codex.js +22 -0
- package/dist/agent-launch.js +74 -0
- package/dist/agent-presets.js +70 -0
- package/dist/agent-registry.js +8 -0
- package/dist/agent-resume.js +96 -0
- package/dist/agent-runner.js +386 -0
- package/dist/agent-workflows.js +275 -0
- package/dist/agents.js +73 -0
- package/dist/automation-config.js +381 -0
- package/dist/base.js +66 -0
- package/dist/cleanup-path.js +33 -0
- package/dist/cli.js +57 -0
- package/dist/codex-sessions.js +188 -0
- package/dist/command-context.js +67 -0
- package/dist/common.js +262 -0
- package/dist/delete.js +113 -0
- package/dist/display.js +134 -0
- package/dist/doctor.js +145 -0
- package/dist/git-safety.js +24 -0
- package/dist/git-workflow.js +89 -0
- package/dist/help.js +726 -0
- package/dist/hooks.js +159 -0
- package/dist/json.js +34 -0
- package/dist/mcp.js +336 -0
- package/dist/picker.js +182 -0
- package/dist/pr-discovery.js +223 -0
- package/dist/pr-update.js +419 -0
- package/dist/pr-workflow.js +161 -0
- package/dist/repos.js +537 -0
- package/dist/review-policy.js +150 -0
- package/dist/session-workflows.js +650 -0
- package/dist/session.js +12 -0
- package/dist/state.js +250 -0
- package/dist/storage.js +366 -0
- package/dist/store.js +379 -0
- package/dist/summary.js +164 -0
- package/dist/task-state.js +65 -0
- package/dist/task.js +192 -0
- package/dist/templates.js +4 -0
- package/dist/workflow-collaboration.js +531 -0
- package/dist/workflow-config.js +177 -0
- package/dist/workflow.js +211 -0
- package/lib/rl.zsh +329 -0
- package/node_modules/graceful-fs/LICENSE +15 -0
- package/node_modules/graceful-fs/README.md +143 -0
- package/node_modules/graceful-fs/clone.js +23 -0
- package/node_modules/graceful-fs/graceful-fs.js +448 -0
- package/node_modules/graceful-fs/legacy-streams.js +118 -0
- package/node_modules/graceful-fs/package.json +53 -0
- package/node_modules/graceful-fs/polyfills.js +355 -0
- package/node_modules/proper-lockfile/LICENSE +21 -0
- package/node_modules/proper-lockfile/README.md +183 -0
- package/node_modules/proper-lockfile/index.js +40 -0
- package/node_modules/proper-lockfile/lib/adapter.js +85 -0
- package/node_modules/proper-lockfile/lib/lockfile.js +342 -0
- package/node_modules/proper-lockfile/lib/mtime-precision.js +55 -0
- package/node_modules/proper-lockfile/package.json +71 -0
- package/node_modules/retry/.npmignore +3 -0
- package/node_modules/retry/.travis.yml +15 -0
- package/node_modules/retry/License +21 -0
- package/node_modules/retry/Makefile +18 -0
- package/node_modules/retry/README.md +227 -0
- package/node_modules/retry/equation.gif +0 -0
- package/node_modules/retry/example/dns.js +31 -0
- package/node_modules/retry/example/stop.js +40 -0
- package/node_modules/retry/index.js +1 -0
- package/node_modules/retry/lib/retry.js +100 -0
- package/node_modules/retry/lib/retry_operation.js +158 -0
- package/node_modules/retry/package.json +32 -0
- package/node_modules/retry/test/common.js +10 -0
- package/node_modules/retry/test/integration/test-forever.js +24 -0
- package/node_modules/retry/test/integration/test-retry-operation.js +258 -0
- package/node_modules/retry/test/integration/test-retry-wrap.js +101 -0
- package/node_modules/retry/test/integration/test-timeouts.js +69 -0
- package/node_modules/signal-exit/LICENSE.txt +16 -0
- package/node_modules/signal-exit/README.md +39 -0
- package/node_modules/signal-exit/index.js +202 -0
- package/node_modules/signal-exit/package.json +38 -0
- package/node_modules/signal-exit/signals.js +53 -0
- package/package.json +57 -4
- package/shell/rl.zsh +11 -0
package/dist/task.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { args, arity, canonical, json, now, object, timestamp, } from "./common.js";
|
|
5
|
+
import { agentAdapter } from "./agent-registry.js";
|
|
6
|
+
import { resumeAgent } from "./agent-resume.js";
|
|
7
|
+
import { currentSessionId } from "./session.js";
|
|
8
|
+
import { commandContext, contextOptions, resolveWorkspace, writeWorkspaceOutput, } from "./command-context.js";
|
|
9
|
+
import { HANDOFF_FIELDS, handoffNotes, handoffText, } from "./task-state.js";
|
|
10
|
+
export const PAUSE_FIELDS = [...HANDOFF_FIELDS, "agent", "sessionId"];
|
|
11
|
+
export async function pauseTask(store, input, cwd = process.cwd()) {
|
|
12
|
+
if (!object(input) ||
|
|
13
|
+
Object.keys(input).some((key) => !PAUSE_FIELDS.includes(key)))
|
|
14
|
+
throw new Error("Unexpected task handoff argument");
|
|
15
|
+
const notes = handoffNotes(input);
|
|
16
|
+
if (input.agent !== undefined && typeof input.agent !== "string")
|
|
17
|
+
throw new Error("agent must be codex or claude");
|
|
18
|
+
const agent = agentAdapter(input.agent ?? "codex").name;
|
|
19
|
+
const sid = currentSessionId(agent, input.sessionId);
|
|
20
|
+
return store.locked(() => {
|
|
21
|
+
const [file, data] = store.current(cwd);
|
|
22
|
+
const stamp = now();
|
|
23
|
+
const handoff = {
|
|
24
|
+
...notes,
|
|
25
|
+
id: randomUUID(),
|
|
26
|
+
agent,
|
|
27
|
+
sessionId: sid,
|
|
28
|
+
savedAt: stamp,
|
|
29
|
+
};
|
|
30
|
+
store.associateSession(file, data, agent, sid, stamp);
|
|
31
|
+
const task = data.task ?? { state: "paused", handoffs: [] };
|
|
32
|
+
task.handoffs.push(handoff);
|
|
33
|
+
task.state = "paused";
|
|
34
|
+
delete task.continuedAt;
|
|
35
|
+
data.task = task;
|
|
36
|
+
store.touch(file, data, stamp);
|
|
37
|
+
return handoff;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function selectTask(context, selector) {
|
|
41
|
+
let records = context.store
|
|
42
|
+
.records()
|
|
43
|
+
.filter(([, data]) => data.status === "active" && data.task);
|
|
44
|
+
if (selector !== undefined) {
|
|
45
|
+
records = records.filter(([, data]) => data.id === selector ||
|
|
46
|
+
data.branch === selector ||
|
|
47
|
+
path.relative(context.managedDir, data.worktree) === selector ||
|
|
48
|
+
(path.isAbsolute(selector) && canonical(selector) === data.worktree));
|
|
49
|
+
if (records.length !== 1)
|
|
50
|
+
throw new Error(records.length
|
|
51
|
+
? "Ambiguous task; use the full branch or workspace path"
|
|
52
|
+
: `No saved handoff for workspace ${selector}`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
records.sort(([, a], [, b]) => {
|
|
56
|
+
const paused = Number(b.task.state === "paused") - Number(a.task.state === "paused");
|
|
57
|
+
if (paused)
|
|
58
|
+
return paused;
|
|
59
|
+
const at = timestamp(a.task.continuedAt ?? a.task.handoffs.at(-1).savedAt);
|
|
60
|
+
const bt = timestamp(b.task.continuedAt ?? b.task.handoffs.at(-1).savedAt);
|
|
61
|
+
return at > bt ? -1 : at < bt ? 1 : a.key.localeCompare(b.key);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const selected = records[0];
|
|
65
|
+
if (!selected)
|
|
66
|
+
throw new Error("No saved task handoffs in this repository. Ask your agent to pause the task first");
|
|
67
|
+
// Fail visibly on a missing or switched workspace instead of silently choosing another task.
|
|
68
|
+
const current = context.store.current(selected[1].worktree);
|
|
69
|
+
if (current[0] !== selected[0])
|
|
70
|
+
throw new Error("Task workspace instance changed");
|
|
71
|
+
return current;
|
|
72
|
+
}
|
|
73
|
+
export async function continueTask(context, options = {}) {
|
|
74
|
+
const [file, data, handoff, previousState, previousTime, stamp] = await context.store.locked(() => {
|
|
75
|
+
const [file, data] = selectTask(context, options.workspace);
|
|
76
|
+
const handoff = data.task.handoffs.at(-1);
|
|
77
|
+
if (!data.sessions[handoff.agent].some((item) => item.id === handoff.sessionId))
|
|
78
|
+
throw new Error("Handoff session is missing from workspace history");
|
|
79
|
+
const stamp = now();
|
|
80
|
+
const previousState = data.task.state;
|
|
81
|
+
const previousTime = data.task.continuedAt;
|
|
82
|
+
// Validate shell output before changing task state.
|
|
83
|
+
writeWorkspaceOutput(data.worktree, options.output);
|
|
84
|
+
data.task.state = "active";
|
|
85
|
+
data.task.continuedAt = stamp;
|
|
86
|
+
context.store.touch(file, data, stamp);
|
|
87
|
+
return [file, data, handoff, previousState, previousTime, stamp];
|
|
88
|
+
});
|
|
89
|
+
const prompt = [
|
|
90
|
+
`Continue RL task ${data.id} on branch ${data.branch}.`,
|
|
91
|
+
handoffText(handoff),
|
|
92
|
+
`Read the full shared task documents before continuing:\n${path.join(path.dirname(file), "context.md")}\n${path.join(path.dirname(file), "progress.md")}`,
|
|
93
|
+
"Recheck the current code and Git state. The handoff records what the previous agent reported; it does not establish that checks still pass. Follow the next steps and record new milestones. When asked to pause again, save a new handoff with rl_task_pause or rl pause.",
|
|
94
|
+
].join("\n\n");
|
|
95
|
+
console.error(handoffText(handoff));
|
|
96
|
+
if (options.noAgent)
|
|
97
|
+
return 0;
|
|
98
|
+
const restore = () => context.store.locked(() => {
|
|
99
|
+
const [currentFile, current] = context.store.current(data.worktree);
|
|
100
|
+
const task = current.task;
|
|
101
|
+
if (currentFile !== file ||
|
|
102
|
+
task?.handoffs.at(-1)?.id !== handoff.id ||
|
|
103
|
+
task.state !== "active" ||
|
|
104
|
+
task.continuedAt !== stamp)
|
|
105
|
+
return;
|
|
106
|
+
task.state = previousState;
|
|
107
|
+
if (previousTime === undefined)
|
|
108
|
+
delete task.continuedAt;
|
|
109
|
+
else
|
|
110
|
+
task.continuedAt = previousTime;
|
|
111
|
+
context.store.save(file, current);
|
|
112
|
+
});
|
|
113
|
+
try {
|
|
114
|
+
const code = await resumeAgent(context.store, handoff.agent, {
|
|
115
|
+
cwd: data.worktree,
|
|
116
|
+
sessionId: handoff.sessionId,
|
|
117
|
+
prompt,
|
|
118
|
+
expectedKey: data.key,
|
|
119
|
+
});
|
|
120
|
+
if (code !== 0)
|
|
121
|
+
await restore();
|
|
122
|
+
return code;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
// Retain a newer pause written by the running agent or another process.
|
|
126
|
+
try {
|
|
127
|
+
await restore();
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
/* Preserve the original launch error. */
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export async function main(argv) {
|
|
136
|
+
const { values, positionals } = args(argv, [
|
|
137
|
+
...contextOptions,
|
|
138
|
+
...HANDOFF_FIELDS,
|
|
139
|
+
"agent",
|
|
140
|
+
"session",
|
|
141
|
+
"workspace",
|
|
142
|
+
"file",
|
|
143
|
+
"output-path",
|
|
144
|
+
], ["json", "no-agent"]);
|
|
145
|
+
const context = commandContext(values);
|
|
146
|
+
const [command, ...rest] = positionals;
|
|
147
|
+
const supplied = Object.keys(values).filter((key) => !contextOptions.includes(key));
|
|
148
|
+
if (command === "pause") {
|
|
149
|
+
arity(rest, 0);
|
|
150
|
+
if (supplied.some((key) => ![
|
|
151
|
+
...HANDOFF_FIELDS,
|
|
152
|
+
"agent",
|
|
153
|
+
"session",
|
|
154
|
+
"workspace",
|
|
155
|
+
"file",
|
|
156
|
+
"json",
|
|
157
|
+
].includes(key)))
|
|
158
|
+
throw new Error("Unsupported pause option");
|
|
159
|
+
if (values.file !== undefined &&
|
|
160
|
+
supplied.some((key) => [...HANDOFF_FIELDS, "agent", "session"].includes(key)))
|
|
161
|
+
throw new Error("Use --file or handoff fields, not both");
|
|
162
|
+
const target = resolveWorkspace(context, typeof values.workspace === "string" ? values.workspace : undefined);
|
|
163
|
+
const input = typeof values.file === "string"
|
|
164
|
+
? JSON.parse(fs.readFileSync(values.file === "-" ? 0 : values.file, "utf8"))
|
|
165
|
+
: Object.fromEntries([...HANDOFF_FIELDS, "agent", "session"]
|
|
166
|
+
.filter((key) => values[key] !== undefined)
|
|
167
|
+
.map((key) => [
|
|
168
|
+
key === "session" ? "sessionId" : key,
|
|
169
|
+
values[key],
|
|
170
|
+
]));
|
|
171
|
+
const handoff = await pauseTask(context.store, input, target.path);
|
|
172
|
+
if (values.json)
|
|
173
|
+
json(handoff);
|
|
174
|
+
else
|
|
175
|
+
console.log(`Paused ${target.branch}. Handoff saved; return with rl continue.`);
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
if (command !== "continue")
|
|
179
|
+
throw new Error("Usage: rl pause | rl continue [workspace] [--no-agent]");
|
|
180
|
+
arity(rest, 0, 1);
|
|
181
|
+
if (supplied.some((key) => !["workspace", "no-agent", "output-path"].includes(key)) ||
|
|
182
|
+
(rest.length && values.workspace !== undefined))
|
|
183
|
+
throw new Error("Usage: rl continue [workspace] [--no-agent]");
|
|
184
|
+
return continueTask(context, {
|
|
185
|
+
workspace: rest[0] ??
|
|
186
|
+
(typeof values.workspace === "string" ? values.workspace : undefined),
|
|
187
|
+
noAgent: !!values["no-agent"],
|
|
188
|
+
output: typeof values["output-path"] === "string"
|
|
189
|
+
? values["output-path"]
|
|
190
|
+
: undefined,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const NOTE_TEMPLATES = {
|
|
2
|
+
"context.md": "# Feature and domain documentation\n\n## Purpose and scope\nDescribe the user problem, intended behavior, and what is outside this feature.\n\n## Domain rules and acceptance criteria\nRecord terminology, constraints, examples, and observable completion criteria.\n\n## Design decisions\nRecord decisions and their rationale, including alternatives when relevant.\n\n## References\nLink specifications, issues, relevant code, and related documentation.\n",
|
|
3
|
+
"progress.md": "# Implementation documentation\n\n## Completed\nRecord implemented behavior with concrete file or symbol references.\n\n## Remaining work\nKeep an actionable checklist; distinguish planned work from completed work.\n\n## Implementation details\nExplain the flow, important modules, APIs/data structures, and integration points.\n\n## Validation\nRecord checks actually run, their results, and gaps that remain unverified.\n\n## Blockers and open questions\nRecord unresolved decisions, known issues, and dependencies.\n\n## Next steps\nDescribe where the next agent or developer should continue.\n\n## Progress log\nAppend dated milestone updates here when several agents are working concurrently.\n",
|
|
4
|
+
};
|