@levistudio/redline 0.5.0 → 0.5.1
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/CHANGELOG.md +8 -1
- package/package.json +1 -1
- package/skills/redline-review/SKILL.md +5 -5
- package/src/authorHandoff.ts +25 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to Redline are documented here. The format follows [Keep a C
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.1] - 2026-05-15
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- `redline-review` now launches Redline with `nohup` so the local server survives short-lived agent shell calls instead of ending the browser session early.
|
|
11
|
+
- `redline author-wait` now returns `kind: "session-ended"` when the startup PID is gone and no result was written, so agents do not wait forever on a dead session.
|
|
12
|
+
|
|
7
13
|
## [0.5.0] - 2026-05-15
|
|
8
14
|
|
|
9
15
|
### Added
|
|
@@ -73,7 +79,8 @@ Initial public release on npm as `@levistudio/redline`.
|
|
|
73
79
|
- Auto-installs missing dependencies on first CLI run.
|
|
74
80
|
- Initial test suite: server, sidecar, parsing, model-picking, rendering, diff, SSE, integration, happy-dom client.
|
|
75
81
|
|
|
76
|
-
[Unreleased]: https://github.com/alevi/redline/compare/v0.5.
|
|
82
|
+
[Unreleased]: https://github.com/alevi/redline/compare/v0.5.1...HEAD
|
|
83
|
+
[0.5.1]: https://github.com/alevi/redline/compare/v0.5.0...v0.5.1
|
|
77
84
|
[0.5.0]: https://github.com/alevi/redline/compare/v0.4.1...v0.5.0
|
|
78
85
|
[0.4.1]: https://github.com/alevi/redline/compare/v0.4.0...v0.4.1
|
|
79
86
|
[0.4.0]: https://github.com/alevi/redline/compare/v0.3.0...v0.4.0
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ When you've produced a markdown document that the human needs to read, comment o
|
|
|
11
11
|
|
|
12
12
|
The redline launcher lives at `__REDLINE_BIN__` (substituted at install time — if you see the literal placeholder string, the skill was installed incorrectly; tell the human to re-run `redline install-skill`). Always invoke it by this absolute path. Do not call bare `redline` and do not try to "fix" PATH issues by running `bun link` or guessing where the repo lives.
|
|
13
13
|
|
|
14
|
-
**Always
|
|
14
|
+
**Always detach the launcher with `nohup` and poll.** Never run `__REDLINE_BIN__` as a foreground/blocking shell call: agent shell tools often buffer stdout until the process exits, so you would never see the URL the human needs to click and your "I'll wait while you review" message would be a lie. Also do not use a plain trailing `&` without `nohup`: in Codex-style short shell calls, the shell can exit and take the Redline server with it. Use this pattern:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
17
|
FILE=/abs/path/to/file.md
|
|
@@ -20,8 +20,8 @@ STARTUP="$DIR/.review/$BASE.startup.json"
|
|
|
20
20
|
RESULT="$DIR/.review/$BASE.result"
|
|
21
21
|
LOG=/tmp/redline-$BASE.log
|
|
22
22
|
|
|
23
|
-
# Kick off the review
|
|
24
|
-
__REDLINE_BIN__ "$FILE" --open > "$LOG" 2>&1 &
|
|
23
|
+
# Kick off the review detached from this short-lived shell and open it in the user's real browser.
|
|
24
|
+
nohup __REDLINE_BIN__ "$FILE" --open > "$LOG" 2>&1 < /dev/null &
|
|
25
25
|
|
|
26
26
|
# Step 1: wait for startup, read the URL.
|
|
27
27
|
for i in $(seq 1 60); do [ -f "$STARTUP" ] && break; sleep 0.5; done
|
|
@@ -46,7 +46,7 @@ The startup file at `.review/<basename>.startup.json` is written synchronously w
|
|
|
46
46
|
In practice, run the script above as **two separate shell calls** so you can tell the human the URL between steps:
|
|
47
47
|
1. First call: everything through `echo "REDLINE_PID: $PID"`. Returns in ~1s with the URL and PID on stdout.
|
|
48
48
|
2. Tell the human Redline opened in their browser, and include the URL only as a fallback (see "Surfacing the URL" below).
|
|
49
|
-
3. Second call: `__REDLINE_BIN__ author-wait "$FILE"`. It returns
|
|
49
|
+
3. Second call: `__REDLINE_BIN__ author-wait "$FILE"`. It returns `{ "kind": "author-needed", ... }`, `{ "kind": "result", ... }`, or `{ "kind": "session-ended", ... }`. Long timeout (`timeout: 1800000` = 30 min, or longer). If it returns author-needed JSON, answer with `author-reply`, then run `author-wait` again. If it returns session-ended, inspect `$LOG` and relaunch only after explaining the failed session to the human.
|
|
50
50
|
|
|
51
51
|
If invocation fails (binary missing, startup file never appears, etc.), surface the error verbatim and stop — do not try to recover. The human will re-run `redline install-skill`.
|
|
52
52
|
|
|
@@ -86,7 +86,7 @@ The full loop, when you are the outer agent producing the doc:
|
|
|
86
86
|
2. Tell the human in one sentence what's about to happen.
|
|
87
87
|
3. First shell call: launch `__REDLINE_BIN__ <abs-path> --context "<one-liner>" --open` in the background and poll for `.startup.json`. Returns in ~1s with the URL.
|
|
88
88
|
4. Tell the human Redline opened in their browser and include the URL only as a fallback.
|
|
89
|
-
5. Second shell call: run `__REDLINE_BIN__ author-wait "$FILE"`. If it returns `kind: "author-needed"`, answer with `__REDLINE_BIN__ author-reply "$FILE" <comment-id> --message "..."`, then run `author-wait` again. Do not start unrelated work while the session runs.
|
|
89
|
+
5. Second shell call: run `__REDLINE_BIN__ author-wait "$FILE"`. If it returns `kind: "author-needed"`, answer with `__REDLINE_BIN__ author-reply "$FILE" <comment-id> --message "..."`, then run `author-wait` again. If it returns `kind: "session-ended"`, inspect the log path from step 1 and tell the human the session died instead of silently relaunching. Do not start unrelated work while the session runs.
|
|
90
90
|
6. On `approved`: re-read the file from disk (it may have been revised) and continue with whatever required sign-off.
|
|
91
91
|
7. On `abandoned` or `error`: stop and ask the human how to proceed; do not retry automatically.
|
|
92
92
|
|
package/src/authorHandoff.ts
CHANGED
|
@@ -66,7 +66,8 @@ export interface AuthorReplyResult {
|
|
|
66
66
|
|
|
67
67
|
export type AuthorWaitResult =
|
|
68
68
|
| { kind: "author-needed"; file: string; author_needed: AuthorNeededItem[] }
|
|
69
|
-
| { kind: "result"; file: string; result: Record<string, unknown> }
|
|
69
|
+
| { kind: "result"; file: string; result: Record<string, unknown> }
|
|
70
|
+
| { kind: "session-ended"; file: string; pid: number; message: string };
|
|
70
71
|
|
|
71
72
|
function startupPath(filePath: string): string {
|
|
72
73
|
return path.join(path.dirname(filePath), ".review", path.basename(filePath) + ".startup.json");
|
|
@@ -76,16 +77,27 @@ function resultPath(filePath: string): string {
|
|
|
76
77
|
return path.join(path.dirname(filePath), ".review", path.basename(filePath) + ".result");
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
function readStartup(filePath: string): { url?: string; csrf_token?: string } | null {
|
|
80
|
+
function readStartup(filePath: string): { url?: string; csrf_token?: string; pid?: number } | null {
|
|
80
81
|
const sp = startupPath(filePath);
|
|
81
82
|
if (!existsSync(sp)) return null;
|
|
82
83
|
try {
|
|
83
|
-
return JSON.parse(readFileSync(sp, "utf-8")) as { url?: string; csrf_token?: string };
|
|
84
|
+
return JSON.parse(readFileSync(sp, "utf-8")) as { url?: string; csrf_token?: string; pid?: number };
|
|
84
85
|
} catch {
|
|
85
86
|
return null;
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
function processIsAlive(pid: number): boolean {
|
|
91
|
+
try {
|
|
92
|
+
process.kill(pid, 0);
|
|
93
|
+
return true;
|
|
94
|
+
} catch (e) {
|
|
95
|
+
const code = (e as { code?: string }).code;
|
|
96
|
+
// EPERM means the process exists but this user cannot signal it.
|
|
97
|
+
return code === "EPERM";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
89
101
|
async function postToLiveServer(
|
|
90
102
|
filePath: string,
|
|
91
103
|
commentId: string,
|
|
@@ -183,6 +195,16 @@ export async function waitForAuthorEvent(
|
|
|
183
195
|
return { kind: "result", file: filePath, result: JSON.parse(raw) as Record<string, unknown> };
|
|
184
196
|
}
|
|
185
197
|
|
|
198
|
+
const startup = readStartup(filePath);
|
|
199
|
+
if (typeof startup?.pid === "number" && !processIsAlive(startup.pid)) {
|
|
200
|
+
return {
|
|
201
|
+
kind: "session-ended",
|
|
202
|
+
file: filePath,
|
|
203
|
+
pid: startup.pid,
|
|
204
|
+
message: "Redline session process ended before writing a result.",
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
186
208
|
if (deadline != null && Date.now() >= deadline) {
|
|
187
209
|
throw new Error("Timed out waiting for author-needed comments or review result");
|
|
188
210
|
}
|