@saccolabs/pi-claude-cli 0.4.2 → 0.4.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/README.md +32 -0
- package/index.ts +8 -0
- package/package.json +1 -1
- package/src/overflow.ts +63 -0
- package/src/process-manager.ts +16 -0
package/README.md
CHANGED
|
@@ -49,6 +49,38 @@ Requires the `claude` binary on your login-shell PATH (`npm install -g @anthropi
|
|
|
49
49
|
- Cross-platform subprocess management (Windows, macOS, Linux)
|
|
50
50
|
- Inactivity timeout and process registry for cleanup
|
|
51
51
|
|
|
52
|
+
## Architecture
|
|
53
|
+
|
|
54
|
+
`docs/ARCHITECTURE.md` covers the turn lifecycle, the three-way tool split,
|
|
55
|
+
the two-ledger session model, error recovery, and the CLI compatibility
|
|
56
|
+
notes (including the 2.x control-protocol shape).
|
|
57
|
+
|
|
58
|
+
## What your Claude environment contributes
|
|
59
|
+
|
|
60
|
+
Each turn runs a real `claude -p` subprocess in your workspace, so your
|
|
61
|
+
Claude Code environment participates through three doors:
|
|
62
|
+
|
|
63
|
+
1. **Bridged tools** — the six built-ins (Read/Write/Edit/Bash/Grep/Glob)
|
|
64
|
+
and pi custom tools become pi tool calls; pi executes them.
|
|
65
|
+
2. **CLI-side execution** — your personal/project MCP servers, WebSearch,
|
|
66
|
+
and sub-agents run _inside_ the CLI between cycles. They appear in the
|
|
67
|
+
transcript as one-line markers (`[Claude Code · WebSearch {…}]`) and
|
|
68
|
+
bill your plan.
|
|
69
|
+
3. **Prompt-level osmosis** — the CLI auto-loads project CLAUDE.md and
|
|
70
|
+
memory, your hooks fire, and skills can load twice (natively via
|
|
71
|
+
claude, and again via pi's own `~/.claude/skills` support).
|
|
72
|
+
|
|
73
|
+
### Hermetic mode
|
|
74
|
+
|
|
75
|
+
Set `PI_CLAUDE_CLI_HERMETIC=1` to keep that environment out of pi turns:
|
|
76
|
+
the subprocess runs with `--strict-mcp-config` (only this extension's
|
|
77
|
+
schema-only custom-tools server loads) and an empty `--setting-sources`
|
|
78
|
+
(no user/project/local settings — hooks, auto-memory, permission
|
|
79
|
+
allowlists). Model access and your subscription login are unaffected.
|
|
80
|
+
|
|
81
|
+
Related knobs: `PI_CLAUDE_CLI_TIMEOUT_MS` overrides the 300s inactivity
|
|
82
|
+
timeout (CLI-side tools can be silent on stdout for minutes).
|
|
83
|
+
|
|
52
84
|
## License
|
|
53
85
|
|
|
54
86
|
MIT
|
package/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
killAllProcesses,
|
|
16
16
|
} from "./src/process-manager.js";
|
|
17
17
|
import { getCustomToolDefs, writeMcpConfig } from "./src/mcp-config.js";
|
|
18
|
+
import { rewriteOverflowMessage } from "./src/overflow.js";
|
|
18
19
|
|
|
19
20
|
// Kill all active Claude subprocesses on process exit to prevent orphans
|
|
20
21
|
process.on("exit", killAllProcesses);
|
|
@@ -120,6 +121,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
120
121
|
PROVIDER_ID,
|
|
121
122
|
);
|
|
122
123
|
|
|
124
|
+
// Overflow recovery: rewrite provider-scoped context-limit errors to
|
|
125
|
+
// the prefix pi's auto-compaction recognizes (see src/overflow.ts).
|
|
126
|
+
|
|
127
|
+
(pi.on as any)("message_end", (event: any, ctx: any) => {
|
|
128
|
+
return rewriteOverflowMessage(event?.message ?? {}, ctx?.model?.provider);
|
|
129
|
+
});
|
|
130
|
+
|
|
123
131
|
pi.registerProvider(PROVIDER_ID, {
|
|
124
132
|
baseUrl: "pi-claude-cli",
|
|
125
133
|
apiKey: "unused",
|
package/package.json
CHANGED
package/src/overflow.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context-overflow error normalization (issue #4).
|
|
3
|
+
*
|
|
4
|
+
* When a request exceeds the model's context window, pi can recover by
|
|
5
|
+
* compacting the conversation and retrying — but only if it recognizes the
|
|
6
|
+
* failure. Detection runs on the finalized assistant message: pi checks
|
|
7
|
+
* `errorMessage` against its known overflow patterns, and the generic
|
|
8
|
+
* fallback it always recognizes is a `context_length_exceeded` prefix
|
|
9
|
+
* (see pi's custom-provider docs, "Context Overflow Errors").
|
|
10
|
+
*
|
|
11
|
+
* The Claude CLI surfaces the Anthropic API's error text verbatim, which pi
|
|
12
|
+
* does not recognize. This module rewrites ONLY provider-scoped, clearly
|
|
13
|
+
* overflow-shaped errors; rate limits and transient failures must never be
|
|
14
|
+
* rewritten (that would trigger compaction instead of pi's retry/backoff).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Anthropic API overflow phrasings, matched conservatively:
|
|
19
|
+
* - "prompt is too long: 214315 tokens > 200000 maximum"
|
|
20
|
+
* - "input length and `max_tokens` exceed context limit: ..."
|
|
21
|
+
*/
|
|
22
|
+
const OVERFLOW_PATTERNS: RegExp[] = [
|
|
23
|
+
/prompt is too long/i,
|
|
24
|
+
/input length and .?max_tokens.? exceed context limit/i,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export const OVERFLOW_PREFIX = "context_length_exceeded";
|
|
28
|
+
|
|
29
|
+
/** True when the error text is an overflow pi should recover from. */
|
|
30
|
+
export function isOverflowError(errorMessage: string): boolean {
|
|
31
|
+
if (errorMessage.includes(OVERFLOW_PREFIX)) return false; // already rewritten
|
|
32
|
+
return OVERFLOW_PATTERNS.some((pattern) => pattern.test(errorMessage));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* `message_end` handler body: returns the rewritten assistant message when
|
|
37
|
+
* this provider produced a recognizable overflow error, undefined otherwise
|
|
38
|
+
* (pi keeps the message unchanged).
|
|
39
|
+
*/
|
|
40
|
+
export function rewriteOverflowMessage(
|
|
41
|
+
message: {
|
|
42
|
+
role?: string;
|
|
43
|
+
provider?: string;
|
|
44
|
+
stopReason?: string;
|
|
45
|
+
errorMessage?: string;
|
|
46
|
+
},
|
|
47
|
+
ctxProvider?: string,
|
|
48
|
+
): { message: Record<string, unknown> } | undefined {
|
|
49
|
+
if (message.role !== "assistant") return undefined;
|
|
50
|
+
if (message.stopReason !== "error") return undefined;
|
|
51
|
+
if (message.provider !== "pi-claude-cli" && ctxProvider !== "pi-claude-cli")
|
|
52
|
+
return undefined;
|
|
53
|
+
|
|
54
|
+
const errorMessage = message.errorMessage ?? "";
|
|
55
|
+
if (!isOverflowError(errorMessage)) return undefined;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
message: {
|
|
59
|
+
...message,
|
|
60
|
+
errorMessage: `${OVERFLOW_PREFIX}: ${errorMessage}`,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
package/src/process-manager.ts
CHANGED
|
@@ -21,6 +21,12 @@ import type { ChildProcess } from "node:child_process";
|
|
|
21
21
|
* @param options - Optional cwd, AbortSignal, and effort level
|
|
22
22
|
* @returns The spawned ChildProcess with piped stdin/stdout/stderr
|
|
23
23
|
*/
|
|
24
|
+
/** Truthy PI_CLAUDE_CLI_HERMETIC opts in to hermetic mode (see README). */
|
|
25
|
+
function isHermetic(): boolean {
|
|
26
|
+
const value = (process.env.PI_CLAUDE_CLI_HERMETIC ?? "").toLowerCase();
|
|
27
|
+
return value === "1" || value === "true" || value === "yes";
|
|
28
|
+
}
|
|
29
|
+
|
|
24
30
|
export function spawnClaude(
|
|
25
31
|
modelId: string,
|
|
26
32
|
systemPrompt?: string,
|
|
@@ -47,6 +53,16 @@ export function spawnClaude(
|
|
|
47
53
|
"stdio",
|
|
48
54
|
];
|
|
49
55
|
|
|
56
|
+
// Hermetic mode: keep the user's Claude Code environment out of pi turns.
|
|
57
|
+
// --strict-mcp-config loads ONLY the servers from --mcp-config (our
|
|
58
|
+
// schema-only custom-tools server survives; personal/project MCP servers
|
|
59
|
+
// do not), and an empty --setting-sources skips user/project/local
|
|
60
|
+
// settings — hooks, CLAUDE.md auto-memory, permission allowlists.
|
|
61
|
+
// Both flags verified accepted on claude 2.1.237.
|
|
62
|
+
if (isHermetic()) {
|
|
63
|
+
args.push("--strict-mcp-config", "--setting-sources", "");
|
|
64
|
+
}
|
|
65
|
+
|
|
50
66
|
if (options?.resumeSessionId) {
|
|
51
67
|
// Resume an existing session — CLI loads prior conversation from disk
|
|
52
68
|
args.push("--resume", options.resumeSessionId);
|