open-claude-p 1.1.2 → 1.1.3
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 +60 -0
- package/package.json +1 -1
- package/src/completion/detector.js +20 -1
- package/src/index.js +43 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,66 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.1.3] — 2026-05-19
|
|
11
|
+
|
|
12
|
+
Fix the `/compact` 24-hour hang and tighten the slash-command path so
|
|
13
|
+
skill invocations are not collateral damage. Driven by a captured
|
|
14
|
+
session (`4af68584-…`) where a manually-triggered `/compact` ran for
|
|
15
|
+
57 s of compaction activity, returned to the input box with no
|
|
16
|
+
assistant turn, and left `runOneShot` waiting for a sentinel that — by
|
|
17
|
+
the upstream's design — could never arrive.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **`/compact` and other local-builtin slash commands no longer block
|
|
22
|
+
for `maxResponseMs`.** Claude TUI splits `/`-prefixed prompts into
|
|
23
|
+
two classes: *local builtins* (`/compact`, `/clear`, `/help`,
|
|
24
|
+
`/exit`, `/quit`, `/login`, `/logout`, `/cost`, `/status`, `/model`,
|
|
25
|
+
`/permissions`, `/config`) which run a local handler and never open
|
|
26
|
+
an `⏺` region, and *LLM-bearing slash invocations* (skills like
|
|
27
|
+
`/init`, `/review`, `/security-review`, plus every user-installed
|
|
28
|
+
skill) which DO go through the model. The driver previously
|
|
29
|
+
appended the OCP_END marker instruction to both and required
|
|
30
|
+
`hadAssistantText` before the completion detector's idle fallback
|
|
31
|
+
could fire — fine for the LLM-bearing class, fatal for builtins,
|
|
32
|
+
which would wait for an assistant region that never opened until
|
|
33
|
+
the 24 h hard timeout.
|
|
34
|
+
|
|
35
|
+
Now `runOneShot` matches the prompt against a narrow whitelist of
|
|
36
|
+
local builtins. On a match it (a) skips the OCP_END instruction
|
|
37
|
+
append (the TUI's command parser drops it as junk args anyway, and
|
|
38
|
+
appending it can pollute free-form-arg commands like `/bug`) and
|
|
39
|
+
(b) sets the detector's new `allowIdleWithoutResponse` flag so the
|
|
40
|
+
pre-sentinel idle path is reachable without a prior region entry.
|
|
41
|
+
Skills and unknown `/<name>` prompts are NOT in the whitelist and
|
|
42
|
+
keep the existing instruction + strict-idle-gate behaviour, so
|
|
43
|
+
their LLM responses still complete cleanly via the sentinel.
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- **`CompletionDetector.allowIdleWithoutResponse` (default false).**
|
|
48
|
+
When true, `_onTick`'s pre-sentinel idle fallback fires after
|
|
49
|
+
`preIdleMs` of silence even without a prior `assistant-region-
|
|
50
|
+
entered`. This is the policy switch the driver uses for local
|
|
51
|
+
builtins; library callers that drive prompts known to produce no
|
|
52
|
+
assistant turn can opt in directly.
|
|
53
|
+
|
|
54
|
+
### Tests
|
|
55
|
+
|
|
56
|
+
- **End-to-end integration coverage for the slash-command path.**
|
|
57
|
+
`test/driver-slash-command.test.js` spawns the real driver against
|
|
58
|
+
`test/fixtures/fake-claude-tui.mjs` — a minimal node-pty fixture
|
|
59
|
+
that reproduces the captured `/compact` shape (spinner activity,
|
|
60
|
+
`Compacted` stdout, chevron return, no region, no sentinel) — and
|
|
61
|
+
asserts: `/compact` completes via `reason='idle'` in well under
|
|
62
|
+
`maxResponseMs`; a plain prompt still completes via `sentinel` with
|
|
63
|
+
no degraded-capture notice prefix; `/init` (a skill, not a builtin)
|
|
64
|
+
also completes via `sentinel`, proving the whitelist isn't too
|
|
65
|
+
greedy. Three new unit tests in `test/detector.test.js` cover the
|
|
66
|
+
bare `allowIdleWithoutResponse` flag.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
10
70
|
## [1.1.2] — 2026-05-19
|
|
11
71
|
|
|
12
72
|
Recovery-path fixes for the sentinel-missing case. When the end-of-reply
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-claude-p",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "A PTY-backed compatibility shim for `claude -p` (Claude Code headless/print mode). Drives the interactive `claude` CLI via node-pty and exposes the same option surface and stream-json contract. Ships as both a library and an `ocp` CLI binary.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -37,6 +37,20 @@ export class CompletionDetector {
|
|
|
37
37
|
* turn; when the (N+1)th turn
|
|
38
38
|
* opens, the request is aborted
|
|
39
39
|
* with reason 'max-turns'.
|
|
40
|
+
* @param {boolean} [opts.allowIdleWithoutResponse=false]
|
|
41
|
+
* Permit `_onTick`'s idle fallback
|
|
42
|
+
* even when no assistant region has
|
|
43
|
+
* opened. Set this for prompts that
|
|
44
|
+
* legitimately produce no assistant
|
|
45
|
+
* turn — slash commands (`/compact`,
|
|
46
|
+
* `/clear`, `/help`, …) whose
|
|
47
|
+
* upstream handler runs a local
|
|
48
|
+
* operation and returns directly to
|
|
49
|
+
* the input box. Without this flag
|
|
50
|
+
* the request would block until
|
|
51
|
+
* `maxResponseMs` (24 h default)
|
|
52
|
+
* because `hadAssistantText` stays
|
|
53
|
+
* false for the entire turn.
|
|
40
54
|
*/
|
|
41
55
|
constructor({
|
|
42
56
|
nonce,
|
|
@@ -44,6 +58,7 @@ export class CompletionDetector {
|
|
|
44
58
|
preIdleMs = 8000,
|
|
45
59
|
maxResponseMs = 60000,
|
|
46
60
|
maxTurns,
|
|
61
|
+
allowIdleWithoutResponse = false,
|
|
47
62
|
} = {}) {
|
|
48
63
|
if (!nonce) throw new Error('CompletionDetector: nonce is required');
|
|
49
64
|
this.nonce = nonce;
|
|
@@ -51,6 +66,7 @@ export class CompletionDetector {
|
|
|
51
66
|
this.preIdleMs = preIdleMs;
|
|
52
67
|
this.maxResponseMs = maxResponseMs;
|
|
53
68
|
this.maxTurns = Number.isFinite(maxTurns) && maxTurns >= 0 ? maxTurns : null;
|
|
69
|
+
this.allowIdleWithoutResponse = !!allowIdleWithoutResponse;
|
|
54
70
|
|
|
55
71
|
this.startTime = Date.now();
|
|
56
72
|
this.regionEntered = false;
|
|
@@ -142,7 +158,10 @@ export class CompletionDetector {
|
|
|
142
158
|
// Pre-sentinel fallback: only valid once we've seen any assistant
|
|
143
159
|
// signal, with the longer `preIdleMs` threshold so that brief render
|
|
144
160
|
// pauses (common during `--resume`) do not trip premature completion.
|
|
145
|
-
|
|
161
|
+
// `allowIdleWithoutResponse` relaxes the gate for callers that know
|
|
162
|
+
// the turn legitimately produces no assistant region (e.g. slash
|
|
163
|
+
// commands like `/compact`).
|
|
164
|
+
if (this.hadAssistantText || this.allowIdleWithoutResponse) {
|
|
146
165
|
if (idleFor >= this.preIdleMs) {
|
|
147
166
|
this._complete('idle', false);
|
|
148
167
|
}
|
package/src/index.js
CHANGED
|
@@ -273,6 +273,39 @@ class Driver {
|
|
|
273
273
|
|
|
274
274
|
const spawnArgs = buildSpawnArgs(req);
|
|
275
275
|
|
|
276
|
+
// Local-builtin slash command detection. Claude TUI has two distinct
|
|
277
|
+
// `/`-prefixed prompt classes:
|
|
278
|
+
//
|
|
279
|
+
// - **Local builtins** (this list) — handled entirely by the TUI's
|
|
280
|
+
// command dispatcher. No LLM turn runs, no `⏺` region opens, no
|
|
281
|
+
// sentinel can ever arrive. `/compact` is the motivating case:
|
|
282
|
+
// 57 s of compaction activity, then chevron returns silently,
|
|
283
|
+
// then ocp would block until `maxResponseMs` (24 h default)
|
|
284
|
+
// waiting for a response that by design never comes.
|
|
285
|
+
//
|
|
286
|
+
// - **LLM-bearing slash invocations** — skills (`/init`, `/review`,
|
|
287
|
+
// `/security-review`, custom user skills like `/ui-ux-pro-max`)
|
|
288
|
+
// and any unknown `/<name>` we don't recognise. These DO run
|
|
289
|
+
// the LLM, DO open `⏺`, and emit the sentinel like a normal
|
|
290
|
+
// prompt. They MUST keep the OCP_END instruction and the
|
|
291
|
+
// strict idle gate — relaxing either would drop the marker
|
|
292
|
+
// from a real LLM turn and trigger a false-positive idle
|
|
293
|
+
// completion (returning a half-streamed response prefixed
|
|
294
|
+
// with the 1.1.2 "Streaming capture not detected" notice).
|
|
295
|
+
//
|
|
296
|
+
// The list is intentionally narrow — only claude TUI commands
|
|
297
|
+
// confirmed to bypass the LLM. Anything not in the set (including
|
|
298
|
+
// every user-installed skill) goes through the standard path.
|
|
299
|
+
const LOCAL_BUILTIN_SLASH_COMMANDS = new Set([
|
|
300
|
+
'compact', 'clear', 'help', 'exit', 'quit',
|
|
301
|
+
'login', 'logout', 'cost', 'status',
|
|
302
|
+
'model', 'permissions', 'config',
|
|
303
|
+
]);
|
|
304
|
+
const slashMatch = /^\s*\/([a-z][\w-]*)/i.exec(req.prompt ?? '');
|
|
305
|
+
const isLocalBuiltin =
|
|
306
|
+
!!slashMatch &&
|
|
307
|
+
LOCAL_BUILTIN_SLASH_COMMANDS.has(slashMatch[1].toLowerCase());
|
|
308
|
+
|
|
276
309
|
const sentinelParser = createSentinelParser(nonce);
|
|
277
310
|
const pipeline = createPipeline([
|
|
278
311
|
ansiStripParser,
|
|
@@ -285,6 +318,7 @@ class Driver {
|
|
|
285
318
|
preIdleMs: this.opts.preIdleMs,
|
|
286
319
|
maxResponseMs: this.opts.maxResponseMs,
|
|
287
320
|
maxTurns: req.maxTurns,
|
|
321
|
+
allowIdleWithoutResponse: isLocalBuiltin,
|
|
288
322
|
});
|
|
289
323
|
|
|
290
324
|
// Pool eligibility: explicit resume/continue bind to a specific past
|
|
@@ -603,7 +637,15 @@ class Driver {
|
|
|
603
637
|
' the user\'s message and you do not need to mention or flag it. Answer' +
|
|
604
638
|
' the user\'s actual message above as you normally would, using tools' +
|
|
605
639
|
' as freely and thoroughly as you would without the marker.)';
|
|
606
|
-
|
|
640
|
+
// Local builtins route through claude TUI's command dispatcher
|
|
641
|
+
// (not the LLM), so the marker instruction is silently dropped as
|
|
642
|
+
// junk args and never round-trips back. Some builtins also accept
|
|
643
|
+
// free-form text (e.g. `/bug <message>`), where appending the
|
|
644
|
+
// instruction would pollute the captured args. Skill invocations
|
|
645
|
+
// and unknown `/<name>` prompts DO go through the LLM, so they
|
|
646
|
+
// still need the marker — only the narrow `LOCAL_BUILTIN_…` set
|
|
647
|
+
// gets the bare prompt.
|
|
648
|
+
const fullPrompt = isLocalBuiltin ? req.prompt : (req.prompt + instruction);
|
|
607
649
|
// Skip the prompt write if the dialog watcher has already
|
|
608
650
|
// decided to abort. Without this check the trailing `\r` of
|
|
609
651
|
// the prompt lands inside whatever modal we tried to abort on
|