planpong 0.3.0 → 0.5.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/dist/src/config/defaults.js +1 -0
- package/dist/src/config/defaults.js.map +1 -1
- package/dist/src/config/loader.d.ts +1 -0
- package/dist/src/config/loader.js +3 -0
- package/dist/src/config/loader.js.map +1 -1
- package/dist/src/core/apply-edits.d.ts +40 -0
- package/dist/src/core/apply-edits.js +220 -0
- package/dist/src/core/apply-edits.js.map +1 -0
- package/dist/src/core/convergence.d.ts +57 -4
- package/dist/src/core/convergence.js +134 -6
- package/dist/src/core/convergence.js.map +1 -1
- package/dist/src/core/loop.js +3 -3
- package/dist/src/core/loop.js.map +1 -1
- package/dist/src/core/operations.d.ts +14 -1
- package/dist/src/core/operations.js +592 -56
- package/dist/src/core/operations.js.map +1 -1
- package/dist/src/core/plan-diff.d.ts +23 -0
- package/dist/src/core/plan-diff.js +135 -0
- package/dist/src/core/plan-diff.js.map +1 -0
- package/dist/src/core/session.d.ts +11 -0
- package/dist/src/core/session.js +51 -1
- package/dist/src/core/session.js.map +1 -1
- package/dist/src/mcp/tools/get-feedback.d.ts +16 -0
- package/dist/src/mcp/tools/get-feedback.js +118 -114
- package/dist/src/mcp/tools/get-feedback.js.map +1 -1
- package/dist/src/mcp/tools/revise.d.ts +16 -0
- package/dist/src/mcp/tools/revise.js +76 -61
- package/dist/src/mcp/tools/revise.js.map +1 -1
- package/dist/src/mcp/tools/status.js +15 -1
- package/dist/src/mcp/tools/status.js.map +1 -1
- package/dist/src/prompts/planner.d.ts +34 -1
- package/dist/src/prompts/planner.js +272 -17
- package/dist/src/prompts/planner.js.map +1 -1
- package/dist/src/prompts/reviewer.d.ts +14 -1
- package/dist/src/prompts/reviewer.js +84 -1
- package/dist/src/prompts/reviewer.js.map +1 -1
- package/dist/src/providers/claude.d.ts +3 -0
- package/dist/src/providers/claude.js +151 -13
- package/dist/src/providers/claude.js.map +1 -1
- package/dist/src/providers/codex.d.ts +3 -0
- package/dist/src/providers/codex.js +150 -14
- package/dist/src/providers/codex.js.map +1 -1
- package/dist/src/providers/types.d.ts +69 -3
- package/dist/src/schemas/config.d.ts +3 -0
- package/dist/src/schemas/config.js +6 -0
- package/dist/src/schemas/config.js.map +1 -1
- package/dist/src/schemas/json-schema.d.ts +21 -0
- package/dist/src/schemas/json-schema.js +172 -0
- package/dist/src/schemas/json-schema.js.map +1 -0
- package/dist/src/schemas/metrics.d.ts +171 -0
- package/dist/src/schemas/metrics.js +49 -0
- package/dist/src/schemas/metrics.js.map +1 -0
- package/dist/src/schemas/revision.d.ts +166 -2
- package/dist/src/schemas/revision.js +35 -2
- package/dist/src/schemas/revision.js.map +1 -1
- package/dist/src/schemas/session.d.ts +6 -0
- package/dist/src/schemas/session.js +10 -0
- package/dist/src/schemas/session.js.map +1 -1
- package/package.json +4 -2
|
@@ -13,41 +13,151 @@ function cleanEnv() {
|
|
|
13
13
|
}
|
|
14
14
|
return env;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Parse claude's `--output-format json` envelope and extract the
|
|
18
|
+
* `structured_output` field as a JSON string ready for downstream parsing.
|
|
19
|
+
* Returns null if the envelope is malformed or the field is missing.
|
|
20
|
+
*
|
|
21
|
+
* Envelope shape (subset):
|
|
22
|
+
* {
|
|
23
|
+
* "type": "result",
|
|
24
|
+
* "is_error": false,
|
|
25
|
+
* "result": "",
|
|
26
|
+
* "structured_output": { ...model's constrained JSON... },
|
|
27
|
+
* ...
|
|
28
|
+
* }
|
|
29
|
+
*/
|
|
30
|
+
function extractStructuredOutput(stdout) {
|
|
31
|
+
try {
|
|
32
|
+
const envelope = JSON.parse(stdout);
|
|
33
|
+
if (envelope &&
|
|
34
|
+
typeof envelope === "object" &&
|
|
35
|
+
"structured_output" in envelope &&
|
|
36
|
+
envelope.structured_output !== null &&
|
|
37
|
+
typeof envelope.structured_output === "object") {
|
|
38
|
+
return JSON.stringify(envelope.structured_output);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Not JSON — may indicate a pre-envelope error or auth failure
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Classify a CLI invocation failure as `capability` (downgrade-eligible) or
|
|
48
|
+
* `fatal` (terminal). Capability errors indicate the CLI doesn't support the
|
|
49
|
+
* requested structured output flag; fatal errors are everything else.
|
|
50
|
+
*/
|
|
51
|
+
function classifyError(stderr, exitCode) {
|
|
52
|
+
const lower = stderr.toLowerCase();
|
|
53
|
+
const capabilityIndicators = [
|
|
54
|
+
"unknown flag",
|
|
55
|
+
"unknown option",
|
|
56
|
+
"unrecognized",
|
|
57
|
+
"invalid schema",
|
|
58
|
+
"invalid json schema",
|
|
59
|
+
"json-schema",
|
|
60
|
+
"unsupported",
|
|
61
|
+
];
|
|
62
|
+
const isCapability = capabilityIndicators.some((indicator) => lower.includes(indicator));
|
|
63
|
+
return {
|
|
64
|
+
kind: isCapability ? "capability" : "fatal",
|
|
65
|
+
message: stderr.slice(0, 500) || `claude exited with code ${exitCode}`,
|
|
66
|
+
exitCode,
|
|
67
|
+
stderr,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
16
70
|
export class ClaudeProvider {
|
|
17
71
|
name = "claude";
|
|
72
|
+
capabilityCache = null;
|
|
18
73
|
async invoke(prompt, options) {
|
|
19
|
-
// claude -p reads prompt from stdin when no positional arg is given
|
|
20
|
-
|
|
74
|
+
// claude -p reads prompt from stdin when no positional arg is given.
|
|
75
|
+
// --bare skips hooks/MCP/auto-memory/CLAUDE.md/plugin-sync for faster
|
|
76
|
+
// subprocess startup, but it bypasses OAuth/keychain — only safe to use
|
|
77
|
+
// when ANTHROPIC_API_KEY is set.
|
|
78
|
+
const args = ["-p"];
|
|
79
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
80
|
+
args.push("--bare");
|
|
81
|
+
}
|
|
82
|
+
if (options.jsonSchema) {
|
|
83
|
+
// With a schema, use --output-format json so the response envelope
|
|
84
|
+
// includes a `structured_output` field containing the model's
|
|
85
|
+
// constrained JSON as a native object. --output-format text drops
|
|
86
|
+
// the structured_output field entirely.
|
|
87
|
+
args.push("--output-format", "json", "--json-schema", JSON.stringify(options.jsonSchema));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
args.push("--output-format", "text");
|
|
91
|
+
}
|
|
21
92
|
if (options.model) {
|
|
22
93
|
args.push("--model", options.model);
|
|
23
94
|
}
|
|
95
|
+
// Persistent conversation. `--session-id` creates a new session with the
|
|
96
|
+
// given UUID; `--resume` continues an existing one. Caller must use one
|
|
97
|
+
// or the other, never both. Lets us drop heavy "current plan + prior
|
|
98
|
+
// decisions" stuffing on round 2+ since the model retains context.
|
|
99
|
+
if (options.newSessionId && options.resumeSessionId) {
|
|
100
|
+
throw new Error("claude provider: newSessionId and resumeSessionId are mutually exclusive");
|
|
101
|
+
}
|
|
102
|
+
if (options.newSessionId) {
|
|
103
|
+
args.push("--session-id", options.newSessionId);
|
|
104
|
+
}
|
|
105
|
+
else if (options.resumeSessionId) {
|
|
106
|
+
args.push("--resume", options.resumeSessionId);
|
|
107
|
+
}
|
|
24
108
|
const start = Date.now();
|
|
25
109
|
try {
|
|
26
110
|
const result = await execa("claude", args, {
|
|
27
111
|
cwd: options.cwd,
|
|
28
112
|
preferLocal: true,
|
|
29
|
-
timeout: options.timeout ??
|
|
113
|
+
timeout: options.timeout ?? 600_000,
|
|
30
114
|
reject: false,
|
|
31
115
|
env: cleanEnv(),
|
|
32
116
|
extendEnv: false,
|
|
33
117
|
input: prompt,
|
|
34
118
|
});
|
|
35
|
-
|
|
36
|
-
|
|
119
|
+
const duration = Date.now() - start;
|
|
120
|
+
const exitCode = result.exitCode ?? 1;
|
|
121
|
+
// claude -p can exit non-zero with valid stdout. Treat presence of
|
|
122
|
+
// stdout as success even on non-zero exit.
|
|
123
|
+
// Canonical session ID for the response: echoes the input UUID
|
|
124
|
+
// (claude accepts external UUIDs as session IDs, so input == output).
|
|
125
|
+
const sessionId = options.newSessionId ?? options.resumeSessionId;
|
|
126
|
+
if (result.stdout && result.stdout.trim().length > 0) {
|
|
127
|
+
if (options.jsonSchema) {
|
|
128
|
+
// Parse claude's envelope and extract structured_output.
|
|
129
|
+
const extracted = extractStructuredOutput(result.stdout);
|
|
130
|
+
if (extracted === null) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
error: {
|
|
134
|
+
kind: "capability",
|
|
135
|
+
message: `claude returned envelope without structured_output field: ${result.stdout.slice(0, 300)}`,
|
|
136
|
+
exitCode,
|
|
137
|
+
stderr: result.stderr,
|
|
138
|
+
},
|
|
139
|
+
duration,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return { ok: true, output: extracted, duration, sessionId };
|
|
143
|
+
}
|
|
144
|
+
return { ok: true, output: result.stdout, duration, sessionId };
|
|
37
145
|
}
|
|
146
|
+
// No usable output — classify the failure
|
|
147
|
+
process.stderr.write(`[claude-provider] exit=${exitCode} stderr=${result.stderr?.slice(0, 500)}\n`);
|
|
38
148
|
return {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
duration
|
|
149
|
+
ok: false,
|
|
150
|
+
error: classifyError(result.stderr ?? "", exitCode),
|
|
151
|
+
duration,
|
|
42
152
|
};
|
|
43
153
|
}
|
|
44
154
|
catch (error) {
|
|
155
|
+
const duration = Date.now() - start;
|
|
156
|
+
const message = error instanceof Error ? error.message : "Unknown error invoking claude";
|
|
45
157
|
return {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
exitCode: 1,
|
|
50
|
-
duration: Date.now() - start,
|
|
158
|
+
ok: false,
|
|
159
|
+
error: { kind: "fatal", message, exitCode: 1 },
|
|
160
|
+
duration,
|
|
51
161
|
};
|
|
52
162
|
}
|
|
53
163
|
}
|
|
@@ -66,6 +176,34 @@ export class ClaudeProvider {
|
|
|
66
176
|
return false;
|
|
67
177
|
}
|
|
68
178
|
}
|
|
179
|
+
async checkStructuredOutputSupport() {
|
|
180
|
+
if (this.capabilityCache !== null) {
|
|
181
|
+
return this.capabilityCache;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const result = await execa("claude", ["--help"], {
|
|
185
|
+
preferLocal: true,
|
|
186
|
+
timeout: 5_000,
|
|
187
|
+
reject: false,
|
|
188
|
+
env: cleanEnv(),
|
|
189
|
+
extendEnv: false,
|
|
190
|
+
});
|
|
191
|
+
const helpText = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
|
|
192
|
+
const supported = helpText.includes("--json-schema");
|
|
193
|
+
this.capabilityCache = supported;
|
|
194
|
+
if (!supported) {
|
|
195
|
+
process.stderr.write(`[planpong] Structured output not supported by claude — using legacy parsing\n`);
|
|
196
|
+
}
|
|
197
|
+
return supported;
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
this.capabilityCache = false;
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
markNonCapable() {
|
|
205
|
+
this.capabilityCache = false;
|
|
206
|
+
}
|
|
69
207
|
getModels() {
|
|
70
208
|
return MODELS;
|
|
71
209
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../../src/providers/claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../../src/providers/claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { Provider, InvokeOptions, ProviderResponse } from "./types.js";
|
|
2
2
|
export declare class CodexProvider implements Provider {
|
|
3
3
|
name: string;
|
|
4
|
+
private capabilityCache;
|
|
4
5
|
invoke(prompt: string, options: InvokeOptions): Promise<ProviderResponse>;
|
|
5
6
|
isAvailable(): Promise<boolean>;
|
|
7
|
+
checkStructuredOutputSupport(): Promise<boolean>;
|
|
8
|
+
markNonCapable(): void;
|
|
6
9
|
getModels(): string[];
|
|
7
10
|
getEffortLevels(): string[];
|
|
8
11
|
}
|
|
@@ -1,14 +1,79 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { readFileSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { execa } from "execa";
|
|
6
6
|
const MODELS = ["gpt-5.3-codex", "o3-pro", "o3", "o4-mini"];
|
|
7
7
|
const EFFORT_LEVELS = ["low", "medium", "high", "xhigh"];
|
|
8
|
+
/**
|
|
9
|
+
* Classify a CLI invocation failure as `capability` (downgrade-eligible) or
|
|
10
|
+
* `fatal` (terminal). Capability errors indicate the CLI doesn't support the
|
|
11
|
+
* requested structured output flag; fatal errors are everything else.
|
|
12
|
+
*
|
|
13
|
+
* Patterns must be narrow — codex's normal session header includes flag
|
|
14
|
+
* names like "output-schema:" in its info output, so substring matches on
|
|
15
|
+
* the flag name alone produce false positives.
|
|
16
|
+
*/
|
|
17
|
+
function extractCodexThreadId(stdout) {
|
|
18
|
+
if (!stdout)
|
|
19
|
+
return undefined;
|
|
20
|
+
// The first non-empty line of `codex exec --json` stdout is a
|
|
21
|
+
// `thread.started` or `thread.resumed` event with `thread_id`. Scan
|
|
22
|
+
// the first ~10 lines defensively in case the model emits anything
|
|
23
|
+
// else first.
|
|
24
|
+
const lines = stdout.split("\n").slice(0, 10);
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
const trimmed = line.trim();
|
|
27
|
+
if (!trimmed.startsWith("{"))
|
|
28
|
+
continue;
|
|
29
|
+
try {
|
|
30
|
+
const evt = JSON.parse(trimmed);
|
|
31
|
+
if ((evt.type === "thread.started" || evt.type === "thread.resumed") &&
|
|
32
|
+
typeof evt.thread_id === "string") {
|
|
33
|
+
return evt.thread_id;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
function classifyError(stderr, exitCode) {
|
|
43
|
+
const lower = stderr.toLowerCase();
|
|
44
|
+
const capabilityPatterns = [
|
|
45
|
+
/\bunknown (?:flag|option|argument)\b/,
|
|
46
|
+
/\bunrecognized (?:flag|option|argument)\b/,
|
|
47
|
+
/\bunexpected (?:flag|option|argument)\b/,
|
|
48
|
+
/\binvalid_json_schema\b/,
|
|
49
|
+
/\binvalid schema\b/,
|
|
50
|
+
/\bschema is not supported\b/,
|
|
51
|
+
/\bstructured output (?:not|isn't) supported\b/,
|
|
52
|
+
];
|
|
53
|
+
const isCapability = capabilityPatterns.some((pattern) => pattern.test(lower));
|
|
54
|
+
return {
|
|
55
|
+
kind: isCapability ? "capability" : "fatal",
|
|
56
|
+
message: stderr.slice(0, 500) || `codex exited with code ${exitCode}`,
|
|
57
|
+
exitCode,
|
|
58
|
+
stderr,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
8
61
|
export class CodexProvider {
|
|
9
62
|
name = "codex";
|
|
63
|
+
capabilityCache = null;
|
|
10
64
|
async invoke(prompt, options) {
|
|
11
|
-
|
|
65
|
+
// codex doesn't accept an externally-generated session UUID. The first
|
|
66
|
+
// call always creates a fresh thread; we capture `thread_id` from the
|
|
67
|
+
// `--json` event stream on stdout and the caller persists it. Resume
|
|
68
|
+
// subsequent calls via `codex exec resume <id>` (subcommand form).
|
|
69
|
+
if (options.newSessionId) {
|
|
70
|
+
// Silent ignore — codex generates its own ID. The caller will get
|
|
71
|
+
// the actual ID back via ProviderResponse.sessionId.
|
|
72
|
+
}
|
|
73
|
+
const isResume = options.resumeSessionId != null && options.resumeSessionId.length > 0;
|
|
74
|
+
const args = isResume
|
|
75
|
+
? ["exec", "resume", options.resumeSessionId]
|
|
76
|
+
: ["exec"];
|
|
12
77
|
if (options.model) {
|
|
13
78
|
args.push("-m", options.model);
|
|
14
79
|
}
|
|
@@ -18,6 +83,23 @@ export class CodexProvider {
|
|
|
18
83
|
// Write clean output to a temp file to avoid parsing header/footer
|
|
19
84
|
const outFile = join(tmpdir(), `planpong-codex-${randomBytes(6).toString("hex")}.txt`);
|
|
20
85
|
args.push("-o", outFile);
|
|
86
|
+
// Always enable --json so we can capture the thread_id event from
|
|
87
|
+
// stdout. The `-o` file still receives the agent's clean text output;
|
|
88
|
+
// `--json` only changes stdout/stderr streaming.
|
|
89
|
+
args.push("--json");
|
|
90
|
+
// Optional structured output schema
|
|
91
|
+
let schemaFile = null;
|
|
92
|
+
if (options.jsonSchema) {
|
|
93
|
+
schemaFile = join(tmpdir(), `planpong-codex-schema-${randomBytes(6).toString("hex")}.json`);
|
|
94
|
+
try {
|
|
95
|
+
writeFileSync(schemaFile, JSON.stringify(options.jsonSchema));
|
|
96
|
+
args.push("--output-schema", schemaFile);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
// If we can't write the schema file, fall through without structured output
|
|
100
|
+
schemaFile = null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
21
103
|
// Use stdin for prompt (CLI arg has length limits)
|
|
22
104
|
args.push("-");
|
|
23
105
|
const start = Date.now();
|
|
@@ -25,38 +107,66 @@ export class CodexProvider {
|
|
|
25
107
|
const result = await execa("codex", args, {
|
|
26
108
|
cwd: options.cwd,
|
|
27
109
|
preferLocal: true,
|
|
28
|
-
timeout: options.timeout ??
|
|
110
|
+
timeout: options.timeout ?? 600_000,
|
|
29
111
|
reject: false,
|
|
30
112
|
input: prompt,
|
|
31
113
|
});
|
|
32
|
-
|
|
114
|
+
const duration = Date.now() - start;
|
|
115
|
+
const exitCode = result.exitCode ?? 1;
|
|
116
|
+
let content = "";
|
|
33
117
|
try {
|
|
34
118
|
content = readFileSync(outFile, "utf-8");
|
|
35
119
|
}
|
|
36
120
|
catch {
|
|
37
121
|
// Fall back to stdout if output file wasn't created
|
|
38
|
-
content = result.stdout;
|
|
122
|
+
content = result.stdout ?? "";
|
|
39
123
|
}
|
|
40
|
-
// Clean up temp
|
|
124
|
+
// Clean up temp files
|
|
41
125
|
try {
|
|
42
126
|
unlinkSync(outFile);
|
|
43
127
|
}
|
|
44
128
|
catch {
|
|
45
129
|
// ignore
|
|
46
130
|
}
|
|
131
|
+
if (schemaFile) {
|
|
132
|
+
try {
|
|
133
|
+
unlinkSync(schemaFile);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// ignore
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (content && content.trim().length > 0) {
|
|
140
|
+
// Capture thread_id from --json stdout. The first event is
|
|
141
|
+
// `thread.started` (fresh) or `thread.resumed` (resume). Both
|
|
142
|
+
// carry `thread_id`. We treat parse failures as "no session
|
|
143
|
+
// tracking" rather than as errors — sessions are an
|
|
144
|
+
// optimization, not a correctness requirement.
|
|
145
|
+
const sessionId = extractCodexThreadId(result.stdout);
|
|
146
|
+
return { ok: true, output: content, duration, sessionId };
|
|
147
|
+
}
|
|
47
148
|
return {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
duration
|
|
149
|
+
ok: false,
|
|
150
|
+
error: classifyError(result.stderr ?? "", exitCode),
|
|
151
|
+
duration,
|
|
51
152
|
};
|
|
52
153
|
}
|
|
53
154
|
catch (error) {
|
|
155
|
+
const duration = Date.now() - start;
|
|
156
|
+
// Cleanup on error path
|
|
157
|
+
if (schemaFile) {
|
|
158
|
+
try {
|
|
159
|
+
unlinkSync(schemaFile);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// ignore
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const message = error instanceof Error ? error.message : "Unknown error invoking codex";
|
|
54
166
|
return {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
exitCode: 1,
|
|
59
|
-
duration: Date.now() - start,
|
|
167
|
+
ok: false,
|
|
168
|
+
error: { kind: "fatal", message, exitCode: 1 },
|
|
169
|
+
duration,
|
|
60
170
|
};
|
|
61
171
|
}
|
|
62
172
|
}
|
|
@@ -73,6 +183,32 @@ export class CodexProvider {
|
|
|
73
183
|
return false;
|
|
74
184
|
}
|
|
75
185
|
}
|
|
186
|
+
async checkStructuredOutputSupport() {
|
|
187
|
+
if (this.capabilityCache !== null) {
|
|
188
|
+
return this.capabilityCache;
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
const result = await execa("codex", ["exec", "--help"], {
|
|
192
|
+
preferLocal: true,
|
|
193
|
+
timeout: 5_000,
|
|
194
|
+
reject: false,
|
|
195
|
+
});
|
|
196
|
+
const helpText = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
|
|
197
|
+
const supported = helpText.includes("--output-schema");
|
|
198
|
+
this.capabilityCache = supported;
|
|
199
|
+
if (!supported) {
|
|
200
|
+
process.stderr.write(`[planpong] Structured output not supported by codex — using legacy parsing\n`);
|
|
201
|
+
}
|
|
202
|
+
return supported;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
this.capabilityCache = false;
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
markNonCapable() {
|
|
210
|
+
this.capabilityCache = false;
|
|
211
|
+
}
|
|
76
212
|
getModels() {
|
|
77
213
|
return MODELS;
|
|
78
214
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex.js","sourceRoot":"","sources":["../../../src/providers/codex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"codex.js","sourceRoot":"","sources":["../../../src/providers/codex.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,16 +3,82 @@ export interface InvokeOptions {
|
|
|
3
3
|
model?: string;
|
|
4
4
|
effort?: string;
|
|
5
5
|
timeout?: number;
|
|
6
|
+
/**
|
|
7
|
+
* JSON Schema to constrain model output. When set, providers pass this to
|
|
8
|
+
* their respective structured-output flags (`--json-schema` for claude,
|
|
9
|
+
* `--output-schema` for codex).
|
|
10
|
+
*/
|
|
11
|
+
jsonSchema?: Record<string, unknown>;
|
|
12
|
+
/**
|
|
13
|
+
* Initialize a NEW persistent conversation with this UUID. Mutually
|
|
14
|
+
* exclusive with `resumeSessionId`. After the first call succeeds, the
|
|
15
|
+
* caller must use `resumeSessionId` on subsequent calls.
|
|
16
|
+
*/
|
|
17
|
+
newSessionId?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Resume an EXISTING persistent conversation. The model retains context
|
|
20
|
+
* from prior turns. Only valid for sessions previously created with
|
|
21
|
+
* `newSessionId`.
|
|
22
|
+
*/
|
|
23
|
+
resumeSessionId?: string;
|
|
6
24
|
}
|
|
7
|
-
|
|
8
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Provider invocation error categories. Used by the operations-layer state
|
|
27
|
+
* machine to decide whether to downgrade or fail terminally.
|
|
28
|
+
*
|
|
29
|
+
* - `capability`: schema rejected, flag unrecognized at runtime, structured
|
|
30
|
+
* output format error. Indicates the CLI doesn't support the requested
|
|
31
|
+
* structured output mode. Downgrade-eligible.
|
|
32
|
+
* - `fatal`: auth failure, timeout, network/transport error, non-zero exit
|
|
33
|
+
* with no output. Unrelated to structured output capability. Terminal.
|
|
34
|
+
*/
|
|
35
|
+
export type ProviderErrorKind = "capability" | "fatal";
|
|
36
|
+
export interface ProviderError {
|
|
37
|
+
kind: ProviderErrorKind;
|
|
38
|
+
message: string;
|
|
9
39
|
exitCode: number;
|
|
10
|
-
|
|
40
|
+
stderr?: string;
|
|
11
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Discriminated result of a single provider invocation. Providers are
|
|
44
|
+
* single-shot — they perform one invocation and return either the output
|
|
45
|
+
* or a typed error. They do NOT retry or downgrade internally; that is
|
|
46
|
+
* the operations-layer state machine's job.
|
|
47
|
+
*
|
|
48
|
+
* `sessionId` (success only): the canonical conversation ID the provider
|
|
49
|
+
* used. For providers that accept an externally-generated UUID (claude),
|
|
50
|
+
* this echoes the input; for providers that generate their own IDs
|
|
51
|
+
* (codex), this is parsed from the provider's output. Caller persists
|
|
52
|
+
* this and passes it as `resumeSessionId` on subsequent calls to keep
|
|
53
|
+
* the same conversation thread.
|
|
54
|
+
*/
|
|
55
|
+
export type ProviderResponse = {
|
|
56
|
+
ok: true;
|
|
57
|
+
output: string;
|
|
58
|
+
duration: number;
|
|
59
|
+
sessionId?: string;
|
|
60
|
+
} | {
|
|
61
|
+
ok: false;
|
|
62
|
+
error: ProviderError;
|
|
63
|
+
duration: number;
|
|
64
|
+
};
|
|
12
65
|
export interface Provider {
|
|
13
66
|
name: string;
|
|
14
67
|
invoke(prompt: string, options: InvokeOptions): Promise<ProviderResponse>;
|
|
15
68
|
isAvailable(): Promise<boolean>;
|
|
16
69
|
getModels(): string[];
|
|
17
70
|
getEffortLevels(): string[];
|
|
71
|
+
/**
|
|
72
|
+
* Probe the underlying CLI to determine whether structured output is
|
|
73
|
+
* supported. Result is cached for the session lifetime. If the probe
|
|
74
|
+
* fails or times out, returns false (use legacy path).
|
|
75
|
+
*/
|
|
76
|
+
checkStructuredOutputSupport(): Promise<boolean>;
|
|
77
|
+
/**
|
|
78
|
+
* Mark this provider as non-capable for the remainder of the session.
|
|
79
|
+
* Called by the state machine after a runtime structured-output failure
|
|
80
|
+
* (capability error or JSON.parse failure) to prevent re-attempting
|
|
81
|
+
* structured output on subsequent rounds.
|
|
82
|
+
*/
|
|
83
|
+
markNonCapable(): void;
|
|
18
84
|
}
|
|
@@ -42,6 +42,7 @@ export declare const PlanpongConfigSchema: z.ZodObject<{
|
|
|
42
42
|
plans_dir: z.ZodDefault<z.ZodString>;
|
|
43
43
|
max_rounds: z.ZodDefault<z.ZodNumber>;
|
|
44
44
|
human_in_loop: z.ZodDefault<z.ZodBoolean>;
|
|
45
|
+
revision_mode: z.ZodDefault<z.ZodEnum<["edits", "full"]>>;
|
|
45
46
|
}, "strip", z.ZodTypeAny, {
|
|
46
47
|
planner: {
|
|
47
48
|
provider: string;
|
|
@@ -56,6 +57,7 @@ export declare const PlanpongConfigSchema: z.ZodObject<{
|
|
|
56
57
|
plans_dir: string;
|
|
57
58
|
max_rounds: number;
|
|
58
59
|
human_in_loop: boolean;
|
|
60
|
+
revision_mode: "edits" | "full";
|
|
59
61
|
}, {
|
|
60
62
|
planner: {
|
|
61
63
|
provider: string;
|
|
@@ -70,6 +72,7 @@ export declare const PlanpongConfigSchema: z.ZodObject<{
|
|
|
70
72
|
plans_dir?: string | undefined;
|
|
71
73
|
max_rounds?: number | undefined;
|
|
72
74
|
human_in_loop?: boolean | undefined;
|
|
75
|
+
revision_mode?: "edits" | "full" | undefined;
|
|
73
76
|
}>;
|
|
74
77
|
export type ProviderConfig = z.infer<typeof ProviderConfigSchema>;
|
|
75
78
|
export type PlanpongConfig = z.infer<typeof PlanpongConfigSchema>;
|
|
@@ -10,5 +10,11 @@ export const PlanpongConfigSchema = z.object({
|
|
|
10
10
|
plans_dir: z.string().default("docs/plans"),
|
|
11
11
|
max_rounds: z.number().int().min(1).max(50).default(10),
|
|
12
12
|
human_in_loop: z.boolean().default(true),
|
|
13
|
+
// Initial release defaults to `"full"`. Edits mode is opt-in via
|
|
14
|
+
// planpong.yaml until benchmark thresholds (first-pass success ≥80%,
|
|
15
|
+
// retry rate <30%, no rounds increase) are confirmed. `"full"` is the
|
|
16
|
+
// kill switch — risk + detail phases use the direction-phase schema and
|
|
17
|
+
// skip the edit applier entirely.
|
|
18
|
+
revision_mode: z.enum(["edits", "full"]).default("full"),
|
|
13
19
|
});
|
|
14
20
|
//# sourceMappingURL=config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/schemas/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,OAAO,EAAE,oBAAoB;IAC7B,QAAQ,EAAE,oBAAoB;IAC9B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC;IAC3C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACvD,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/schemas/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,OAAO,EAAE,oBAAoB;IAC7B,QAAQ,EAAE,oBAAoB;IAC9B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC;IAC3C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACvD,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACxC,iEAAiE;IACjE,qEAAqE;IACrE,sEAAsE;IACtE,wEAAwE;IACxE,kCAAkC;IAClC,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;CACzD,CAAC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReviewPhase } from "../prompts/reviewer.js";
|
|
2
|
+
export declare const DirectionFeedbackJsonSchema: Record<string, unknown>;
|
|
3
|
+
export declare const RiskFeedbackJsonSchema: Record<string, unknown>;
|
|
4
|
+
export declare const ReviewFeedbackJsonSchema: Record<string, unknown>;
|
|
5
|
+
export declare const PlannerRevisionJsonSchema: Record<string, unknown>;
|
|
6
|
+
/**
|
|
7
|
+
* Get the JSON Schema appropriate for a given review phase.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getFeedbackJsonSchemaForPhase(phase: ReviewPhase): Record<string, unknown>;
|
|
10
|
+
/**
|
|
11
|
+
* Get the JSON Schema for a planner revision response, selecting the
|
|
12
|
+
* shape based on phase and the configured revision mode.
|
|
13
|
+
*
|
|
14
|
+
* - Direction phase always emits `updated_plan` (sweeping rewrites are
|
|
15
|
+
* allowed in round 1).
|
|
16
|
+
* - Risk + detail phase with `revisionMode: "edits"` emits an `edits[]`
|
|
17
|
+
* array — the planner cannot fall back to full output.
|
|
18
|
+
* - Risk + detail phase with `revisionMode: "full"` keeps the full-plan
|
|
19
|
+
* shape (kill switch).
|
|
20
|
+
*/
|
|
21
|
+
export declare function getRevisionJsonSchema(phase: ReviewPhase, revisionMode: "edits" | "full"): Record<string, unknown>;
|