@ai-dev-methodologies/rlp-desk 0.11.1 → 0.12.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/docs/rlp-desk/artifact-schema.md +99 -0
- package/docs/rlp-desk/ci-setup.md +100 -0
- package/docs/rlp-desk/e2e-scenarios.md +102 -0
- package/docs/rlp-desk/plans/rlp-desk-tmux-flywheel-routing.md +730 -0
- package/install.sh +93 -20
- package/package.json +8 -2
- package/scripts/build-node-manifest.js +52 -0
- package/scripts/postinstall.js +162 -8
- package/src/commands/rlp-desk.md +48 -25
- package/src/governance.md +55 -6
- package/src/node/MANIFEST.txt +15 -0
- package/src/node/cli/command-builder.mjs +25 -5
- package/src/node/constants.mjs +19 -0
- package/src/node/polling/signal-poller.mjs +119 -3
- package/src/node/runner/campaign-main-loop.mjs +470 -41
- package/src/node/runner/leader-registry.mjs +100 -0
- package/src/node/runner/prompt-dismisser.mjs +200 -0
- package/src/node/shared/fs.mjs +38 -0
- package/src/node/util/debug-log.mjs +56 -0
- package/src/node/util/shell-quote.mjs +12 -0
- package/docs/superpowers/plans/2026-04-24-gpt-5-5-default.md +0 -517
- package/docs/superpowers/specs/2026-04-24-gpt-5-5-default.md +0 -107
- /package/docs/{TODO-verification-next.md → rlp-desk/TODO-verification-next.md} +0 -0
- /package/docs/{architecture.md → rlp-desk/architecture.md} +0 -0
- /package/docs/{blueprints → rlp-desk/blueprints}/blueprint-flywheel-enhancement.md +0 -0
- /package/docs/{blueprints → rlp-desk/blueprints}/blueprint-pivot-step.md +0 -0
- /package/docs/{blueprints → rlp-desk/blueprints}/plan-flywheel-enhancement.md +0 -0
- /package/docs/{blueprints → rlp-desk/blueprints}/sv-architecture-rethink.md +0 -0
- /package/docs/{getting-started.md → rlp-desk/getting-started.md} +0 -0
- /package/docs/{internal → rlp-desk/internal}/verification-policy-gap-analysis.md +0 -0
- /package/docs/{internal → rlp-desk/internal}/verification-strategy-research.md +0 -0
- /package/docs/{multi-mission-orchestration.md → rlp-desk/multi-mission-orchestration.md} +0 -0
- /package/docs/{plans → rlp-desk/plans}/cozy-gliding-trinket.md +0 -0
- /package/docs/{plans → rlp-desk/plans}/frolicking-churning-honey.md +0 -0
- /package/docs/{plans → rlp-desk/plans}/keen-sauteeing-snowflake.md +0 -0
- /package/docs/{plans → rlp-desk/plans}/mutable-booping-corbato.md +0 -0
- /package/docs/{plans → rlp-desk/plans}/rlp-desk-0.11-handoff-7fixes.md +0 -0
- /package/docs/{plans → rlp-desk/plans}/rlp-desk-0.11.1-tmux-pane-disappearance.md +0 -0
- /package/docs/{plans → rlp-desk/plans}/rlp-desk-elegant-papert-agent-a8cd695ffca2a3ad8.md +0 -0
- /package/docs/{plans → rlp-desk/plans}/rlp-desk-elegant-papert.md +0 -0
- /package/docs/{plans → rlp-desk/plans}/toasty-whistling-diffie-agent-a6814625642e956da.md +0 -0
- /package/docs/{plans → rlp-desk/plans}/toasty-whistling-diffie.md +0 -0
- /package/docs/{plans → rlp-desk/plans}/validated-snacking-crayon.md +0 -0
- /package/docs/{protocol-reference.md → rlp-desk/protocol-reference.md} +0 -0
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { shellQuote } from '../util/shell-quote.mjs';
|
|
2
|
+
import { OPUS_1M_BETA, isOpusModel } from '../constants.mjs';
|
|
3
|
+
|
|
1
4
|
const CLAUDE_BIN = 'claude';
|
|
2
5
|
const CODEX_BIN = 'codex';
|
|
3
6
|
const CLAUDE_MODELS = new Set(['haiku', 'sonnet', 'opus']);
|
|
@@ -11,19 +14,36 @@ function assertTuiMode(mode, builderName) {
|
|
|
11
14
|
export function buildClaudeCmd(mode, model, options = {}) {
|
|
12
15
|
assertTuiMode(mode, 'buildClaudeCmd');
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
// v5.7 §4.9: auto-enable 1M-token context for Opus models. Long campaigns
|
|
18
|
+
// no longer silently truncate at 200K. Header is benign for non-Opus calls
|
|
19
|
+
// but we omit it there to keep the cmdline tidy.
|
|
20
|
+
const parts = ['DISABLE_OMC=1'];
|
|
21
|
+
if (isOpusModel(model)) {
|
|
22
|
+
parts.push(`ANTHROPIC_BETA=${shellQuote(OPUS_1M_BETA)}`);
|
|
23
|
+
}
|
|
24
|
+
parts.push(
|
|
16
25
|
CLAUDE_BIN,
|
|
17
26
|
'--model',
|
|
18
|
-
model,
|
|
27
|
+
shellQuote(model),
|
|
19
28
|
'--mcp-config',
|
|
20
29
|
'\'{"mcpServers":{}}\'',
|
|
21
30
|
'--strict-mcp-config',
|
|
22
31
|
'--dangerously-skip-permissions',
|
|
23
|
-
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// v5.7 §4.11.a: explicit --add-dir whitelist. With --dangerously-skip-permissions
|
|
35
|
+
// alone, claude CLI still surfaces TUI prompts for cwd-adjacent paths in some
|
|
36
|
+
// versions. Add the home rlp-desk tree (where Leader writes registry.jsonl
|
|
37
|
+
// and reads governance docs) plus the campaign cwd, so Worker has full
|
|
38
|
+
// authorized access without prompts.
|
|
39
|
+
if (options.addDirs && Array.isArray(options.addDirs)) {
|
|
40
|
+
for (const dir of options.addDirs) {
|
|
41
|
+
if (dir) parts.push('--add-dir', shellQuote(dir));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
24
44
|
|
|
25
45
|
if (options.effort !== undefined && options.effort !== '') {
|
|
26
|
-
parts.push('--effort', options.effort);
|
|
46
|
+
parts.push('--effort', shellQuote(options.effort));
|
|
27
47
|
}
|
|
28
48
|
|
|
29
49
|
return parts.join(' ');
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Shared runtime constants. Single-source for cross-module values.
|
|
2
|
+
|
|
3
|
+
// Anthropic Claude API beta header that activates the 1M-token context window
|
|
4
|
+
// for Opus models. Auto-prepended to every claude CLI invocation that uses
|
|
5
|
+
// --model opus so long campaigns no longer silently truncate at 200K.
|
|
6
|
+
//
|
|
7
|
+
// Docs: https://docs.anthropic.com/en/docs/build-with-claude/context-windows
|
|
8
|
+
// (search "1M context") — header rotates with each beta phase.
|
|
9
|
+
export const OPUS_1M_BETA = 'context-1m-2025-08-07';
|
|
10
|
+
|
|
11
|
+
// Model id that triggers Opus 1M auto-enable. Plain string match against the
|
|
12
|
+
// --model value (post-shellQuote stripping). Bracketed form
|
|
13
|
+
// 'claude-opus-4-7[1m]' is also Opus and benefits from this; pattern match
|
|
14
|
+
// covers both.
|
|
15
|
+
export function isOpusModel(model) {
|
|
16
|
+
if (!model) return false;
|
|
17
|
+
const m = String(model).toLowerCase();
|
|
18
|
+
return m === 'opus' || m.startsWith('claude-opus-');
|
|
19
|
+
}
|
|
@@ -3,6 +3,8 @@ import { execFile } from 'node:child_process';
|
|
|
3
3
|
import { promisify } from 'node:util';
|
|
4
4
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
5
5
|
|
|
6
|
+
import { autoDismissPrompts } from '../runner/prompt-dismisser.mjs';
|
|
7
|
+
|
|
6
8
|
const execFileAsync = promisify(execFile);
|
|
7
9
|
const SHELL_COMMANDS = new Set(['', 'zsh', 'bash', 'sh']);
|
|
8
10
|
|
|
@@ -13,6 +15,35 @@ export class TimeoutError extends Error {
|
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
// v5.7 §4.17 (Node parity): default-No prompt detected while polling. Caller
|
|
19
|
+
// must write a BLOCKED `infra_failure` sentinel and abort — never auto-Enter,
|
|
20
|
+
// never wait silently for the human.
|
|
21
|
+
export class PromptBlockedError extends Error {
|
|
22
|
+
constructor(message, info = {}) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'PromptBlockedError';
|
|
25
|
+
this.paneId = info.paneId;
|
|
26
|
+
this.category = info.category ?? 'infra_failure';
|
|
27
|
+
this.reason = info.reason ?? message;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// v5.7 §4.22 (E2E real-claude-CLI finding): Worker process exited (back to
|
|
32
|
+
// shell prompt) but no signal/done-claim file was written. fresh-context +
|
|
33
|
+
// file-based architecture is broken — Leader has no way to know what Worker
|
|
34
|
+
// did. zsh runner has `handle_worker_exit_claude` for this; Node leader did
|
|
35
|
+
// not. Throw a specific error so the campaign loop can write BLOCKED with a
|
|
36
|
+
// descriptive reason instead of silent iter-timeout.
|
|
37
|
+
export class WorkerExitedError extends Error {
|
|
38
|
+
constructor(message, info = {}) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = 'WorkerExitedError';
|
|
41
|
+
this.paneId = info.paneId;
|
|
42
|
+
this.category = info.category ?? 'infra_failure';
|
|
43
|
+
this.reason = info.reason ?? message;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
16
47
|
async function defaultReadFile(filePath) {
|
|
17
48
|
return fs.readFile(filePath, 'utf8');
|
|
18
49
|
}
|
|
@@ -29,6 +60,28 @@ async function defaultGetPaneCommand(paneId) {
|
|
|
29
60
|
return stdout.trim();
|
|
30
61
|
}
|
|
31
62
|
|
|
63
|
+
async function defaultCapturePane(paneId) {
|
|
64
|
+
// v5.7 §4.21 (E2E real-claude-CLI finding): claude v2.x trust prompt is
|
|
65
|
+
// ~30+ lines tall when the pane wraps narrowly. -S -10 missed the question
|
|
66
|
+
// header ("Quick safety check / Is this a project you trust?") so PROMPT_RE
|
|
67
|
+
// never matched and the unknown-prompt fast-fail BLOCKed instead of
|
|
68
|
+
// auto-dismissing. -50 covers the full prompt with margin for typical
|
|
69
|
+
// pane heights.
|
|
70
|
+
const { stdout } = await execFileAsync('tmux', [
|
|
71
|
+
'capture-pane',
|
|
72
|
+
'-t',
|
|
73
|
+
paneId,
|
|
74
|
+
'-p',
|
|
75
|
+
'-S',
|
|
76
|
+
'-50',
|
|
77
|
+
]);
|
|
78
|
+
return stdout;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function defaultSendKeys(paneId, key) {
|
|
82
|
+
await execFileAsync('tmux', ['send-keys', '-t', paneId, key]);
|
|
83
|
+
}
|
|
84
|
+
|
|
32
85
|
function isMissingFileError(error) {
|
|
33
86
|
return error?.code === 'ENOENT';
|
|
34
87
|
}
|
|
@@ -71,11 +124,41 @@ export async function pollForSignal(
|
|
|
71
124
|
timeoutMs = 5000,
|
|
72
125
|
readFile = defaultReadFile,
|
|
73
126
|
getPaneCommand = defaultGetPaneCommand,
|
|
127
|
+
capturePane = defaultCapturePane,
|
|
128
|
+
sendKeys = defaultSendKeys,
|
|
129
|
+
log = () => {},
|
|
74
130
|
} = {},
|
|
75
131
|
) {
|
|
76
132
|
const deadline = Date.now() + timeoutMs;
|
|
133
|
+
let pendingBlock = null;
|
|
134
|
+
// v5.7 §4.22: track whether the worker process was ever observed running.
|
|
135
|
+
// Used to detect "worker started, did some work, then exited without
|
|
136
|
+
// writing signal/done-claim" — fresh-context architecture violation.
|
|
137
|
+
let seenWorkerRunning = false;
|
|
77
138
|
|
|
78
139
|
while (!deadlineExceeded(deadline)) {
|
|
140
|
+
// v5.7 §4.13.b: auto-dismiss mid-execution permission prompts before
|
|
141
|
+
// checking the signal file. Without this, Worker hangs on TUI prompts
|
|
142
|
+
// even with --dangerously-skip-permissions (Bug 4).
|
|
143
|
+
// v5.7 §4.17 (Node parity): default-No prompts must NOT be auto-Entered;
|
|
144
|
+
// they raise a PromptBlockedError so the caller writes BLOCKED and aborts.
|
|
145
|
+
if (paneId) {
|
|
146
|
+
await autoDismissPrompts(paneId, {
|
|
147
|
+
capturePane,
|
|
148
|
+
sendKeys,
|
|
149
|
+
log,
|
|
150
|
+
onDefaultNoBlock: (info) => {
|
|
151
|
+
pendingBlock = info;
|
|
152
|
+
},
|
|
153
|
+
}).catch(() => {});
|
|
154
|
+
if (pendingBlock) {
|
|
155
|
+
throw new PromptBlockedError(
|
|
156
|
+
`Default-No prompt on pane ${pendingBlock.paneId}: ${pendingBlock.reason}`,
|
|
157
|
+
pendingBlock,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
79
162
|
try {
|
|
80
163
|
const rawContent = await readFile(signalFile);
|
|
81
164
|
const parsed = JSON.parse(rawContent);
|
|
@@ -89,9 +172,42 @@ export async function pollForSignal(
|
|
|
89
172
|
}
|
|
90
173
|
|
|
91
174
|
return parsed;
|
|
92
|
-
} catch (
|
|
93
|
-
if (!isMissingFileError(
|
|
94
|
-
throw
|
|
175
|
+
} catch (signalError) {
|
|
176
|
+
if (!isMissingFileError(signalError) && !isJsonParseError(signalError)) {
|
|
177
|
+
throw signalError;
|
|
178
|
+
}
|
|
179
|
+
// Signal file missing OR partial JSON. v5.7 §4.22: parity with zsh
|
|
180
|
+
// `handle_worker_exit_claude` — if Worker pane process is back to
|
|
181
|
+
// shell, the worker exited without writing artifacts. Stop polling
|
|
182
|
+
// immediately and surface a WorkerExitedError so the campaign loop
|
|
183
|
+
// can write BLOCKED with reason `worker_exited_without_artifacts`.
|
|
184
|
+
//
|
|
185
|
+
// IMPORTANT: only run the pane-exit check on ENOENT (signal file
|
|
186
|
+
// entirely missing). A SyntaxError means the file EXISTS but the
|
|
187
|
+
// Worker is mid-write (atomic-rename race) — checking pane state
|
|
188
|
+
// here would race against the imminent successful read. Skip the
|
|
189
|
+
// check; next iteration's read will succeed.
|
|
190
|
+
if (paneId && isMissingFileError(signalError)) {
|
|
191
|
+
try {
|
|
192
|
+
const currentCommand = await getPaneCommand(paneId);
|
|
193
|
+
if (SHELL_COMMANDS.has(currentCommand)) {
|
|
194
|
+
if (seenWorkerRunning) {
|
|
195
|
+
throw new WorkerExitedError(
|
|
196
|
+
`Worker pane ${paneId} exited (now '${currentCommand || 'shell'}') without writing signal at ${signalFile} — fresh-context contract violated`,
|
|
197
|
+
{
|
|
198
|
+
paneId,
|
|
199
|
+
category: 'infra_failure',
|
|
200
|
+
reason: 'worker_exited_without_artifacts',
|
|
201
|
+
},
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
} else if (currentCommand) {
|
|
205
|
+
seenWorkerRunning = true;
|
|
206
|
+
}
|
|
207
|
+
} catch (commandError) {
|
|
208
|
+
if (commandError instanceof WorkerExitedError) throw commandError;
|
|
209
|
+
// Other tmux lookup errors: don't end the loop early.
|
|
210
|
+
}
|
|
95
211
|
}
|
|
96
212
|
}
|
|
97
213
|
|