@mgiles/perk 2.3.0 → 3.0.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/README.md +1 -1
- package/extension/adapters/planAdapterPlannotator.ts +132 -91
- package/extension/adapters/planAdapterTombell.ts +5 -3
- package/extension/doors/address.ts +235 -37
- package/extension/doors/annotationPush.ts +924 -0
- package/extension/doors/auditWaveTools.ts +352 -0
- package/extension/doors/ciExecutor.ts +220 -55
- package/extension/doors/draftReviewWaveTools.ts +384 -0
- package/extension/doors/harvestWaveTools.ts +279 -0
- package/extension/doors/land.ts +1 -0
- package/extension/doors/learn.ts +24 -6
- package/extension/doors/learnFactory.ts +33 -7
- package/extension/doors/lifecycleGates.ts +1 -1
- package/extension/doors/objectiveReviewBrowser.ts +514 -0
- package/extension/doors/objectiveStack.ts +1143 -0
- package/extension/doors/planReviewBrowser.ts +453 -0
- package/extension/doors/plannotatorHandoff.ts +120 -49
- package/extension/doors/prReview.ts +26 -16
- package/extension/doors/prReviewBrowser.ts +31 -23
- package/extension/doors/prReviewDynamic.ts +25 -18
- package/extension/doors/prReviewTerminal.ts +6 -11
- package/extension/doors/reviewWaveTools.ts +374 -0
- package/extension/doors/selfcheck.ts +1 -1
- package/extension/doors/submit.ts +82 -7
- package/extension/factories/gistAuthor.ts +4 -4
- package/extension/factories/gistDraft.ts +1 -1
- package/extension/factories/gistSave.ts +2 -2
- package/extension/factories/objective.ts +7 -7
- package/extension/factories/objectiveAuthor.ts +5 -4
- package/extension/factories/objectiveDraft.ts +61 -6
- package/extension/factories/objectivePlan.ts +163 -9
- package/extension/factories/objectiveSave.ts +16 -1
- package/extension/factories/planMode.ts +5 -3
- package/extension/factories/planReview.ts +131 -36
- package/extension/factories/planTitle.ts +27 -9
- package/extension/hunkFeedback/inbox.ts +389 -0
- package/extension/hunkFeedback/perkFeedback.ts +364 -0
- package/extension/hunkFeedback/receiver.ts +296 -0
- package/extension/hunkFeedback/store.ts +533 -0
- package/extension/index.ts +109 -44
- package/extension/substrate/cache.ts +75 -22
- package/extension/substrate/coldDoor.ts +2 -3
- package/extension/substrate/config.ts +36 -12
- package/extension/substrate/git.ts +10 -0
- package/extension/substrate/providers.ts +4 -33
- package/extension/substrate/resources.ts +11 -0
- package/extension/substrate/result.ts +13 -5
- package/extension/substrate/sessionData.ts +3 -3
- package/extension/substrate/sessionPointers.ts +3 -3
- package/extension/substrate/structuredOutput.ts +78 -19
- package/extension/substrate/toolGating.ts +170 -23
- package/extension/substrate/workflowState.ts +6 -0
- package/extension/surfaces/footerProvider.ts +6 -6
- package/extension/surfaces/surfaces.ts +98 -282
- package/extension/vendor/btw/btw.ts +57 -12
- package/extension/waves/adversarialReviewWave.ts +164 -0
- package/extension/waves/auditWave.ts +312 -0
- package/extension/waves/draftReviewWave.ts +189 -0
- package/extension/waves/harvestWave.ts +399 -0
- package/extension/waves/memoryAdapter.ts +14 -1
- package/extension/waves/objectiveExplorerWave.ts +120 -0
- package/extension/waves/prReviewDynamicWave.ts +291 -61
- package/extension/waves/prReviewWave.ts +58 -13
- package/extension/waves/reportWave.ts +406 -106
- package/extension/waves/reviewClassifierWave.ts +109 -0
- package/extension/waves/rpcAdapter.ts +65 -2
- package/extension/worker/readOnlySession.ts +3 -3
- package/extension/worker/worker.ts +71 -93
- package/extension/workerMain.ts +4 -6
- package/package.json +15 -5
- package/prompts/README.md +19 -0
- package/prompts/_fixtures/live.yaml +66 -65
- package/prompts/common/resume-advisory.md +1 -0
- package/prompts/contexts/adapters/plannotator-gist.md +13 -0
- package/prompts/contexts/adapters/plannotator-objective.md +1 -2
- package/prompts/contexts/adapters/plannotator-plan.md +1 -2
- package/prompts/contexts/gist-authoring.md +6 -20
- package/prompts/contexts/objective-authoring.md +6 -18
- package/prompts/stages/address/action.md +3 -14
- package/prompts/stages/address/preview.md +2 -13
- package/prompts/stages/audit.md +18 -0
- package/prompts/stages/gist-author/seed.md +4 -4
- package/prompts/stages/gist-save.md +1 -1
- package/prompts/stages/implement.md +3 -1
- package/prompts/stages/learn-code.md +1 -1
- package/prompts/stages/learn-docs.md +2 -2
- package/prompts/stages/learn-harvest.md +15 -0
- package/prompts/stages/learn-orchestrate.md +1 -1
- package/prompts/stages/objective-author/adopt.md +5 -3
- package/prompts/stages/objective-author/file.md +5 -4
- package/prompts/stages/objective-author/seed.md +5 -4
- package/prompts/stages/objective-land.md +7 -0
- package/prompts/stages/objective-plan/guidance.md +2 -13
- package/prompts/stages/objective-plan/seed.md +5 -12
- package/prompts/stages/objective-recover.md +8 -0
- package/prompts/stages/objective-replan.md +4 -2
- package/prompts/stages/objective-review-browser.md +9 -0
- package/prompts/stages/objective-sync.md +6 -0
- package/prompts/stages/plan-from/adopt.md +2 -2
- package/prompts/stages/plan-from/file.md +2 -2
- package/prompts/stages/plan-review-browser.md +9 -0
- package/prompts/stages/pr-review-browser/active.md +6 -14
- package/prompts/stages/pr-review-browser/foreign.md +6 -14
- package/prompts/stages/pr-review-dynamic.md +3 -3
- package/prompts/stages/pr-review-terminal/active.md +7 -15
- package/prompts/stages/pr-review-terminal/foreign.md +7 -15
- package/prompts/stages/pr-review.md +2 -2
- package/prompts/stages/replan.md +3 -3
- package/shared/README.md +2 -2
- package/shared/bindings.yaml +12 -0
- package/shared/contracts-history.md +11 -0
- package/shared/contracts.md +3652 -236
- package/shared/providers.yaml +27 -60
- package/shared/registry.yaml +32 -8
- package/shared/schemas/outputs/objective-doctor.schema.json +449 -0
- package/shared/schemas/outputs/objective-stack-land.schema.json +732 -0
- package/shared/schemas/outputs/objective-stack-recover.schema.json +397 -0
- package/shared/schemas/outputs/objective-stack-status.schema.json +577 -0
- package/shared/schemas/outputs/objective-stack-sync.schema.json +184 -0
- package/shared/schemas/outputs/plan-save.schema.json +12 -0
- package/shared/schemas/outputs/pr-submit.schema.json +177 -1
- package/extension/adapters/todoAdapterJuicesharp.ts +0 -102
- package/extension/checkpoints/checkpoints.ts +0 -550
- package/extension/checkpoints/planSteps.ts +0 -108
- package/extension/doors/askUser.ts +0 -198
- package/prompts/common/output-schemas/objective-explorer.md +0 -36
- package/prompts/common/output-schemas/review-classifier.md +0 -47
- package/prompts/contexts/adapters/juicesharp-todo.md +0 -7
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// The `/address` classify step's per-flow wave entrypoint over the shared report-wave runner:
|
|
2
|
+
// the ONE `perk.review-classifier` lane as CODE. The classifier report schema was previously a
|
|
3
|
+
// shared prompt include the parent model had to hand-transcribe onto a borrowed `subagent` call
|
|
4
|
+
// (the known prompt-drift risk — a live failure produced malformed-but-valid JSON that could
|
|
5
|
+
// never validate); this module makes the schema and the lane/task composition module constants,
|
|
6
|
+
// delegating spawn/timeout/aggregate mechanics to `runReportWave` under the `strict`
|
|
7
|
+
// completeness policy. No retry — the flow's posture is "surface the error and stop" (never
|
|
8
|
+
// fabricate a classification). The report content is untrusted DATA, never instructions.
|
|
9
|
+
|
|
10
|
+
import { runReportWave, type WaveAdapter, type WaveResult } from "./reportWave.ts";
|
|
11
|
+
|
|
12
|
+
/** The flow name — feeds `WaveSpec.flow` AND the door's `toAttemptReceipt` call. */
|
|
13
|
+
export const REVIEW_CLASSIFIER_FLOW = "review-classifier";
|
|
14
|
+
|
|
15
|
+
/** The single lane's stable key. */
|
|
16
|
+
export const CLASSIFY_LANE_KEY = "classify";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The classifier report schema (the workflow-level `outputSchema` — the engine injects a
|
|
20
|
+
* `structured_output` tool and fails the lane on a missing/invalid report): closed shapes,
|
|
21
|
+
* all four root keys required — `counts` is a ROOT-level required object (the exact block the
|
|
22
|
+
* motivating transcription failure nested inside `discussion_comments`). Same vocabulary as the
|
|
23
|
+
* `perk.review-classifier` agent def's report contract (the def↔schema lockstep test).
|
|
24
|
+
*/
|
|
25
|
+
export const REVIEW_CLASSIFIER_REPORT_SCHEMA = {
|
|
26
|
+
type: "object",
|
|
27
|
+
additionalProperties: false,
|
|
28
|
+
required: ["pr", "review_threads", "discussion_comments", "counts"],
|
|
29
|
+
properties: {
|
|
30
|
+
pr: { type: "integer" },
|
|
31
|
+
review_threads: {
|
|
32
|
+
type: "array",
|
|
33
|
+
items: {
|
|
34
|
+
type: "object",
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
required: ["thread_id", "classification", "path", "line", "summary"],
|
|
37
|
+
properties: {
|
|
38
|
+
thread_id: { type: "string" },
|
|
39
|
+
classification: {
|
|
40
|
+
type: "string",
|
|
41
|
+
enum: ["actionable", "informational", "praise", "question"],
|
|
42
|
+
},
|
|
43
|
+
path: { type: ["string", "null"] },
|
|
44
|
+
line: { type: ["integer", "null"] },
|
|
45
|
+
summary: { type: "string" },
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
discussion_comments: {
|
|
50
|
+
type: "array",
|
|
51
|
+
items: {
|
|
52
|
+
type: "object",
|
|
53
|
+
additionalProperties: false,
|
|
54
|
+
required: ["comment_id", "classification", "summary"],
|
|
55
|
+
properties: {
|
|
56
|
+
comment_id: { type: "integer" },
|
|
57
|
+
classification: {
|
|
58
|
+
type: "string",
|
|
59
|
+
enum: ["actionable", "informational", "praise", "question"],
|
|
60
|
+
},
|
|
61
|
+
summary: { type: "string" },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
counts: {
|
|
66
|
+
type: "object",
|
|
67
|
+
additionalProperties: false,
|
|
68
|
+
required: ["actionable", "informational", "praise", "question"],
|
|
69
|
+
properties: {
|
|
70
|
+
actionable: { type: "integer" },
|
|
71
|
+
informational: { type: "integer" },
|
|
72
|
+
praise: { type: "integer" },
|
|
73
|
+
question: { type: "integer" },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Run the review-classifier wave: ONE fresh-context `perk.review-classifier` lane with the fixed
|
|
81
|
+
* code-owned task (the child fetches the feedback itself via `perk pr feedback --json` — nothing
|
|
82
|
+
* model-relayed enters the task), `strict` completeness, no retry, module-default timeout.
|
|
83
|
+
* Returns the runner's `WaveResult` unchanged — the only projection lives in the door.
|
|
84
|
+
*/
|
|
85
|
+
export async function runReviewClassifierWave(
|
|
86
|
+
adapter: WaveAdapter,
|
|
87
|
+
opts: { model?: string; timeoutMs?: number; signal?: AbortSignal } = {},
|
|
88
|
+
): Promise<WaveResult> {
|
|
89
|
+
return await runReportWave(
|
|
90
|
+
adapter,
|
|
91
|
+
{
|
|
92
|
+
flow: REVIEW_CLASSIFIER_FLOW,
|
|
93
|
+
lanes: [
|
|
94
|
+
{
|
|
95
|
+
key: CLASSIFY_LANE_KEY,
|
|
96
|
+
label: CLASSIFY_LANE_KEY,
|
|
97
|
+
agent: "perk.review-classifier",
|
|
98
|
+
phase: "address",
|
|
99
|
+
task: "Fetch + classify the review feedback on this plan's PR.",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
outputSchema: REVIEW_CLASSIFIER_REPORT_SCHEMA,
|
|
103
|
+
completeness: "strict",
|
|
104
|
+
...(opts.model !== undefined ? { model: opts.model } : {}),
|
|
105
|
+
...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
|
|
106
|
+
},
|
|
107
|
+
opts.signal,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// in-process event bus (unit-testable offline with a fake bus + a fake RPC responder, exactly
|
|
3
3
|
// like the plannotator bridge).
|
|
4
4
|
//
|
|
5
|
-
// ENVELOPE (pinned against pi-subagents 0.43.0, `src/extension/rpc.ts`):
|
|
5
|
+
// ENVELOPE (pinned against pi-subagents 0.43.0, re-verified at 0.45.0; `src/extension/rpc.ts`):
|
|
6
|
+
// requests are emitted on
|
|
6
7
|
// `subagents:rpc:v1:request` as `{version: 1, requestId, method, params?, source?}`; the reply
|
|
7
8
|
// arrives once on `subagents:rpc:v1:reply:<requestId>` as
|
|
8
9
|
// `{version, requestId, method?, success: true, data} | {…, success: false, error: {code, message}}`.
|
|
@@ -12,6 +13,14 @@
|
|
|
12
13
|
// envelope is for). `pi-subagents` is not an allowed bare import (`bareImportGuard.test.ts`), so
|
|
13
14
|
// its constants/types cannot be imported — the doctor `subagent-compat` probes are the drift
|
|
14
15
|
// tripwire, and every pi-subagents bump warrants an adapter re-verify.
|
|
16
|
+
//
|
|
17
|
+
// COMPLETION PAYLOAD (source-read-derived, 0.45.0 `src/runs/background/result-watcher.ts` +
|
|
18
|
+
// `src/runs/foreground/subagent-executor.ts`): the async-complete event spreads the result-file
|
|
19
|
+
// data plus a normalized per-child `results` array; on workflow rows the `agent` field carries
|
|
20
|
+
// the workflow LANE KEY (the overloaded upstream field — mapped to `WaveChildReceipt.key` here,
|
|
21
|
+
// never exposed). Normalization is defensively output-free: `output`/`summary`/
|
|
22
|
+
// `structuredOutput` never enter a receipt child, unknown fields are ignored, and malformed
|
|
23
|
+
// rows are dropped without failing the wave (receipt absence degrades correlation only).
|
|
15
24
|
|
|
16
25
|
import { randomUUID } from "node:crypto";
|
|
17
26
|
import { readFileSync } from "node:fs";
|
|
@@ -19,6 +28,7 @@ import { join } from "node:path";
|
|
|
19
28
|
import type {
|
|
20
29
|
WaveAdapter,
|
|
21
30
|
WaveBus,
|
|
31
|
+
WaveChildReceipt,
|
|
22
32
|
WaveCompletion,
|
|
23
33
|
WavePing,
|
|
24
34
|
WaveRunHandle,
|
|
@@ -111,6 +121,49 @@ async function request(
|
|
|
111
121
|
});
|
|
112
122
|
}
|
|
113
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Narrow one completion-payload `results` row's `artifactPaths` to its string-valued own
|
|
126
|
+
* properties; when none survive, fall back to the watcher-normalized `artifactPath` as
|
|
127
|
+
* `{ outputPath }`. Undefined when neither yields a path.
|
|
128
|
+
*/
|
|
129
|
+
function narrowArtifactPaths(row: Record<string, unknown>): Record<string, string> | undefined {
|
|
130
|
+
if (isRecord(row.artifactPaths)) {
|
|
131
|
+
const paths: Record<string, string> = {};
|
|
132
|
+
for (const [key, value] of Object.entries(row.artifactPaths)) {
|
|
133
|
+
if (typeof value === "string") paths[key] = value;
|
|
134
|
+
}
|
|
135
|
+
if (Object.keys(paths).length > 0) return paths;
|
|
136
|
+
}
|
|
137
|
+
if (typeof row.artifactPath === "string" && row.artifactPath !== "") {
|
|
138
|
+
return { outputPath: row.artifactPath };
|
|
139
|
+
}
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Narrow one `results` row into an output-free receipt child; null ⇒ the row is dropped (a
|
|
145
|
+
* malformed row never fails the wave). The upstream `agent` field carries the workflow lane key
|
|
146
|
+
* — it becomes `key`; `agent` is deliberately left unset (enriched from Perk-owned lane specs
|
|
147
|
+
* upstream). `output`/`summary`/`structuredOutput` and unknown fields are NEVER copied.
|
|
148
|
+
*/
|
|
149
|
+
function narrowReceiptChild(row: unknown): WaveChildReceipt | null {
|
|
150
|
+
if (!isRecord(row)) return null;
|
|
151
|
+
const key = row.agent;
|
|
152
|
+
if (typeof key !== "string" || key === "") return null;
|
|
153
|
+
const artifactPaths = narrowArtifactPaths(row);
|
|
154
|
+
return {
|
|
155
|
+
key,
|
|
156
|
+
...(typeof row.runId === "string" && row.runId !== "" ? { runId: row.runId } : {}),
|
|
157
|
+
...(typeof row.success === "boolean" ? { success: row.success } : {}),
|
|
158
|
+
...(row.outputState === "present" ||
|
|
159
|
+
row.outputState === "absent" ||
|
|
160
|
+
row.outputState === "unknown"
|
|
161
|
+
? { outputState: row.outputState }
|
|
162
|
+
: {}),
|
|
163
|
+
...(artifactPaths !== undefined ? { artifactPaths } : {}),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
114
167
|
/** Narrow a ping reply to the advertised async-complete channel; any miss ⇒ null (unavailable). */
|
|
115
168
|
function narrowPing(data: unknown): WavePing | null {
|
|
116
169
|
if (!isRecord(data)) return null;
|
|
@@ -166,10 +219,20 @@ export function createRpcWaveAdapter(bus: WaveBus): WaveAdapter {
|
|
|
166
219
|
return bus.on(advertised.asyncCompleteEvent, (data) => {
|
|
167
220
|
if (!isRecord(data)) return;
|
|
168
221
|
// The payload spreads the result-file data: `id` is the async run id; `asyncDir` the
|
|
169
|
-
// durable run directory. At least one is present on real payloads.
|
|
222
|
+
// durable run directory. At least one is present on real payloads. The observability
|
|
223
|
+
// fields (state/success/results) are optional — identity-only payloads stay valid.
|
|
224
|
+
const children = Array.isArray(data.results)
|
|
225
|
+
? data.results.flatMap((row) => {
|
|
226
|
+
const child = narrowReceiptChild(row);
|
|
227
|
+
return child === null ? [] : [child];
|
|
228
|
+
})
|
|
229
|
+
: undefined;
|
|
170
230
|
handler({
|
|
171
231
|
...(typeof data.id === "string" ? { asyncId: data.id } : {}),
|
|
172
232
|
...(typeof data.asyncDir === "string" ? { asyncDir: data.asyncDir } : {}),
|
|
233
|
+
...(typeof data.state === "string" && data.state !== "" ? { state: data.state } : {}),
|
|
234
|
+
...(typeof data.success === "boolean" ? { success: data.success } : {}),
|
|
235
|
+
...(children !== undefined ? { children } : {}),
|
|
173
236
|
});
|
|
174
237
|
});
|
|
175
238
|
},
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// loading perk's own extension into the child). The `no*` flags keep perk's machinery out of the
|
|
20
20
|
// child and keep the path offline/deterministic. A custom loader is reloaded by the caller.
|
|
21
21
|
|
|
22
|
-
import { existsSync, mkdirSync, mkdtempSync
|
|
22
|
+
import { existsSync, mkdirSync, mkdtempSync } from "node:fs";
|
|
23
23
|
import { tmpdir } from "node:os";
|
|
24
24
|
import { join } from "node:path";
|
|
25
25
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
SessionManager,
|
|
31
31
|
SettingsManager,
|
|
32
32
|
} from "@earendil-works/pi-coding-agent";
|
|
33
|
-
import { runScratchDir, scratchDir } from "../substrate/cache.ts";
|
|
33
|
+
import { atomicWriteFileSync, runScratchDir, scratchDir } from "../substrate/cache.ts";
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
36
|
* The SDK-level read-only allowlist (no `bash`; stricter than the in-session READ_ONLY_TOOLS).
|
|
@@ -267,7 +267,7 @@ export async function runReadOnlyChild(
|
|
|
267
267
|
|
|
268
268
|
// write → verify → pass-path: persist the full result, then confirm it landed.
|
|
269
269
|
const scratchPath = resolveScratchPath(opts.cwd, opts.runId, opts.step);
|
|
270
|
-
|
|
270
|
+
atomicWriteFileSync(scratchPath, output);
|
|
271
271
|
if (!existsSync(scratchPath)) {
|
|
272
272
|
return failure("read-only child: scratch write could not be verified.", "scratch-verify");
|
|
273
273
|
}
|
|
@@ -32,19 +32,17 @@ import { env } from "node:process";
|
|
|
32
32
|
// re-export a thinking-level type (only `ThinkingLevelChangeEntry`).
|
|
33
33
|
import type { Api, Model, ModelThinkingLevel as ThinkingLevel } from "@earendil-works/pi-ai";
|
|
34
34
|
import {
|
|
35
|
-
AuthStorage,
|
|
36
35
|
type CreateAgentSessionRuntimeFactory,
|
|
37
36
|
createAgentSessionFromServices,
|
|
38
37
|
createAgentSessionRuntime,
|
|
39
38
|
createAgentSessionServices,
|
|
40
|
-
|
|
39
|
+
ModelRuntime,
|
|
41
40
|
resolveCliModel,
|
|
42
41
|
SessionManager,
|
|
43
42
|
SettingsManager,
|
|
44
43
|
} from "@earendil-works/pi-coding-agent";
|
|
45
44
|
import { planReadInstruction } from "../doors/lifecycleGates.ts";
|
|
46
45
|
import { ensureRunScratch, type PlanRef, readPlanRef, runEventsPath } from "../substrate/cache.ts";
|
|
47
|
-
import { loadPerkConfig } from "../substrate/config.ts";
|
|
48
46
|
import { render } from "../substrate/prompts.ts";
|
|
49
47
|
import { captureSessionPointer } from "../substrate/sessionPointers.ts";
|
|
50
48
|
import { rebuildWorkflowState } from "../substrate/workflowState.ts";
|
|
@@ -94,10 +92,14 @@ export interface RunOutcome {
|
|
|
94
92
|
* The structured run-event stream (contracts §8.12). A small, JSON-serializable,
|
|
95
93
|
* **additive-stable** discriminated union keyed on `kind` (distinct from `DriveEvent.type`). Every
|
|
96
94
|
* event carries a monotonic `seq` (0-based) and `t` (elapsed ms, same basis as
|
|
97
|
-
* `RunOutcome.budget.elapsed_ms`). Future nodes may add variants/fields; existing ones keep
|
|
95
|
+
* `RunOutcome.budget.elapsed_ms`). Future nodes may add variants/fields; existing ones keep
|
|
96
|
+
* meaning — including deprecated variants that are no longer emitted (see `step_marker`).
|
|
98
97
|
*/
|
|
99
98
|
export type RunEvent =
|
|
100
99
|
| { kind: "run_started"; seq: number; t: number; run_id: string; stage: DriveStage }
|
|
100
|
+
// DEPRECATED — never emitted: the `[WIP:n]`/`[DONE:n]` marker protocol died with the
|
|
101
|
+
// checkpoints removal. Kept for additive-stable grammar — historical `events.ndjson` files
|
|
102
|
+
// may carry the variant (contracts §8.12).
|
|
101
103
|
| { kind: "step_marker"; seq: number; t: number; marker: "wip" | "done"; step: number }
|
|
102
104
|
| {
|
|
103
105
|
kind: "tool_outcome";
|
|
@@ -137,8 +139,8 @@ export interface DriveStageOptions {
|
|
|
137
139
|
* `undefined` ⇒ the SDK's settings-default resolution — unchanged behavior.
|
|
138
140
|
*/
|
|
139
141
|
thinkingLevel?: ThinkingLevel;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
/** The canonical model/auth runtime (pi 0.84 `ModelRuntime`); default-created when absent. */
|
|
143
|
+
modelRuntime?: ModelRuntime;
|
|
142
144
|
budget: DriveBudget;
|
|
143
145
|
/** External cancellation; OR'd with the budget watchdog. */
|
|
144
146
|
signal?: AbortSignal;
|
|
@@ -176,8 +178,6 @@ export interface DriveEvent {
|
|
|
176
178
|
* sum: adding it would double-count.
|
|
177
179
|
*/
|
|
178
180
|
usage?: { input?: number; output?: number; reasoning?: number };
|
|
179
|
-
/** Assistant text/content blocks (where `[WIP:n]`/`[DONE:n]` markers live). */
|
|
180
|
-
content?: unknown;
|
|
181
181
|
};
|
|
182
182
|
}
|
|
183
183
|
|
|
@@ -207,13 +207,14 @@ export interface DriveRuntimeLike {
|
|
|
207
207
|
export interface DriveCounters {
|
|
208
208
|
turns: number;
|
|
209
209
|
tokens: number;
|
|
210
|
+
/** Latest submit-bearing evidence (standalone submit or the nested finalizer submit). */
|
|
210
211
|
submitDetails: Record<string, unknown> | null;
|
|
211
|
-
|
|
212
|
+
finalizeDetails: Record<string, unknown> | null;
|
|
212
213
|
modelError: { message: string } | null;
|
|
213
214
|
}
|
|
214
215
|
|
|
215
216
|
export function freshCounters(): DriveCounters {
|
|
216
|
-
return { turns: 0, tokens: 0, submitDetails: null,
|
|
217
|
+
return { turns: 0, tokens: 0, submitDetails: null, finalizeDetails: null, modelError: null };
|
|
217
218
|
}
|
|
218
219
|
|
|
219
220
|
/** The natural-idle terminal classification (before watchdog/abort overrides). */
|
|
@@ -239,7 +240,7 @@ function detailsOf(result: unknown): Record<string, unknown> | null {
|
|
|
239
240
|
/**
|
|
240
241
|
* Fold one agent-session event into the running counters (pure). Counts `turn_end` turns, sums
|
|
241
242
|
* assistant token usage (the `sumAssistantTokens` pattern in objective.ts), captures the `submit`
|
|
242
|
-
* /`
|
|
243
|
+
* /`finalize_address` terminal tool details, and records a post-acceptance model error
|
|
243
244
|
* (assistant `message_end` with `stopReason:"error"`, surfaced with retry off — audit §B #4).
|
|
244
245
|
*
|
|
245
246
|
* The token sum is `input + output` ONLY: `usage.reasoning` is a subset of `output` on every
|
|
@@ -255,8 +256,19 @@ export function applyEvent(counters: DriveCounters, event: DriveEvent): void {
|
|
|
255
256
|
}
|
|
256
257
|
if (event.type === "tool_execution_end") {
|
|
257
258
|
if (event.toolName === "submit") counters.submitDetails = detailsOf(event.result);
|
|
258
|
-
else if (event.toolName === "
|
|
259
|
-
|
|
259
|
+
else if (event.toolName === "finalize_address") {
|
|
260
|
+
const details = detailsOf(event.result);
|
|
261
|
+
counters.finalizeDetails = details;
|
|
262
|
+
// A finalizer carries the submit that immediately preceded resolution. Recording it into the
|
|
263
|
+
// same latest-evidence slot means a later standalone submit naturally supersedes it after a
|
|
264
|
+
// conflict-resolver re-drive.
|
|
265
|
+
const nestedSubmit = details?.submit;
|
|
266
|
+
if (nestedSubmit && typeof nestedSubmit === "object" && !Array.isArray(nestedSubmit)) {
|
|
267
|
+
// The finalizer only exposes this nested block after submit succeeded; restore the
|
|
268
|
+
// success marker stripped from its nested public shape so a later failed standalone
|
|
269
|
+
// submit cannot accidentally satisfy the address completion predicate.
|
|
270
|
+
counters.submitDetails = { ok: true, ...(nestedSubmit as Record<string, unknown>) };
|
|
271
|
+
}
|
|
260
272
|
}
|
|
261
273
|
return;
|
|
262
274
|
}
|
|
@@ -276,13 +288,14 @@ export function budgetTripped(counters: DriveCounters, budget: DriveBudget): boo
|
|
|
276
288
|
* Classify a natural-idle terminal from the captured state (pure). `modelError` wins (post-
|
|
277
289
|
* acceptance error, §B #4); else the stage success predicate:
|
|
278
290
|
* - implement: a successful `submit` carrying a `pr` → completed/submit_tool;
|
|
279
|
-
* - address: `
|
|
291
|
+
* - address: `finalize_address` ok, `last_review_batch` appended, and the latest submit-bearing
|
|
292
|
+
* evidence is successful and not definitively unmergeable → completed/address_resolved;
|
|
280
293
|
* - otherwise the agent went idle without completing the stage → failed/agent_idle_incomplete.
|
|
281
294
|
*/
|
|
282
295
|
export function evaluateTerminal(args: {
|
|
283
296
|
stage: DriveStage;
|
|
284
297
|
submitDetails: Record<string, unknown> | null;
|
|
285
|
-
|
|
298
|
+
finalizeDetails: Record<string, unknown> | null;
|
|
286
299
|
lastReviewBatchPresent: boolean;
|
|
287
300
|
modelError: { message: string } | null;
|
|
288
301
|
}): TerminalVerdict {
|
|
@@ -331,8 +344,20 @@ export function evaluateTerminal(args: {
|
|
|
331
344
|
};
|
|
332
345
|
}
|
|
333
346
|
|
|
334
|
-
// address
|
|
335
|
-
|
|
347
|
+
// address. `applyEvent` keeps submitDetails as the latest submit-bearing evidence: the nested
|
|
348
|
+
// finalizer submit first, then a later standalone submit from the conflict-resolution re-drive.
|
|
349
|
+
const nestedSubmit = args.finalizeDetails?.submit;
|
|
350
|
+
const fallbackSubmit: Record<string, unknown> | null =
|
|
351
|
+
nestedSubmit && typeof nestedSubmit === "object" && !Array.isArray(nestedSubmit)
|
|
352
|
+
? { ok: true, ...(nestedSubmit as Record<string, unknown>) }
|
|
353
|
+
: null;
|
|
354
|
+
const effectiveSubmit = args.submitDetails ?? fallbackSubmit;
|
|
355
|
+
if (
|
|
356
|
+
args.finalizeDetails?.ok === true &&
|
|
357
|
+
args.lastReviewBatchPresent &&
|
|
358
|
+
effectiveSubmit?.ok === true &&
|
|
359
|
+
effectiveSubmit.mergeable !== false
|
|
360
|
+
) {
|
|
336
361
|
return {
|
|
337
362
|
status: "completed",
|
|
338
363
|
terminal_signal: "address_resolved",
|
|
@@ -346,18 +371,20 @@ export function evaluateTerminal(args: {
|
|
|
346
371
|
terminal_signal: "agent_idle_incomplete",
|
|
347
372
|
pr: null,
|
|
348
373
|
errorType: "incomplete",
|
|
349
|
-
errorMessage:
|
|
374
|
+
errorMessage:
|
|
375
|
+
"address drive went idle without fully finalizing feedback " +
|
|
376
|
+
"(publication, thread resolution, and last_review_batch are required).",
|
|
350
377
|
};
|
|
351
378
|
}
|
|
352
379
|
|
|
353
380
|
/**
|
|
354
381
|
* The post-bind preflight rule (pure): the stage's terminating perk tool must be registered —
|
|
355
|
-
* `implement` → `submit`, `address` → `
|
|
382
|
+
* `implement` → `submit`, `address` → `finalize_address`. Returns the required tool name
|
|
356
383
|
* when absent, else `null`. Deliberately does NOT require the `subagent` tool for `address` — the
|
|
357
384
|
* subagent-under-worker live smoke stays the §8.11 carried risk.
|
|
358
385
|
*/
|
|
359
386
|
export function missingTerminatingTool(stage: DriveStage, toolNames: string[]): string | null {
|
|
360
|
-
const required = stage === "implement" ? "submit" : "
|
|
387
|
+
const required = stage === "implement" ? "submit" : "finalize_address";
|
|
361
388
|
return toolNames.includes(required) ? null : required;
|
|
362
389
|
}
|
|
363
390
|
|
|
@@ -406,39 +433,6 @@ export function assembleOutcome(args: {
|
|
|
406
433
|
|
|
407
434
|
// --- run-event helpers (offline-testable) ---------------------------------------------
|
|
408
435
|
|
|
409
|
-
/**
|
|
410
|
-
* Extract `[WIP:n]`/`[DONE:n]` markers from assistant text in **textual appearance order** (pure).
|
|
411
|
-
* A single combined, case-insensitive regex so interleaved markers (`[WIP:2]` before `[DONE:1]` in
|
|
412
|
-
* the same message) emit in that order — unlike checkpoints.ts's separate `extractWip/DoneSteps`
|
|
413
|
-
* lists, which lose cross-marker order. Returns `[]` when there are no markers.
|
|
414
|
-
*/
|
|
415
|
-
export function extractStepMarkers(text: string): { marker: "wip" | "done"; step: number }[] {
|
|
416
|
-
const out: { marker: "wip" | "done"; step: number }[] = [];
|
|
417
|
-
for (const m of text.matchAll(/\[(WIP|DONE):(\d+)\]/gi)) {
|
|
418
|
-
const step = Number(m[2]);
|
|
419
|
-
if (Number.isFinite(step)) {
|
|
420
|
-
out.push({ marker: (m[1] ?? "").toLowerCase() === "done" ? "done" : "wip", step });
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return out;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/** Flatten a `DriveEvent`'s assistant `message.content` (string | `{type:'text',text}[]`) to text. */
|
|
427
|
-
export function assistantText(event: DriveEvent): string {
|
|
428
|
-
const content = event.message?.content;
|
|
429
|
-
if (typeof content === "string") return content;
|
|
430
|
-
if (Array.isArray(content)) {
|
|
431
|
-
return content
|
|
432
|
-
.map((b) => {
|
|
433
|
-
const block = b as { type?: string; text?: string };
|
|
434
|
-
return block.type === "text" && typeof block.text === "string" ? block.text : "";
|
|
435
|
-
})
|
|
436
|
-
.filter(Boolean)
|
|
437
|
-
.join("\n");
|
|
438
|
-
}
|
|
439
|
-
return "";
|
|
440
|
-
}
|
|
441
|
-
|
|
442
436
|
/**
|
|
443
437
|
* Compute a `tool_outcome` `{ tool, ok, summary }` from a `tool_execution_end` `DriveEvent` (pure).
|
|
444
438
|
* `ok` = `details.ok === true` when the result carries a `details.ok` boolean, else `!isError`.
|
|
@@ -524,14 +518,11 @@ export function defaultEventSink(worktree: string, runId: string): RunEventSink
|
|
|
524
518
|
*
|
|
525
519
|
* The `address` wording lives in the shared canonical template `prompts/stages/address/action.md`
|
|
526
520
|
* rendered via the cross-plane render seam (contracts.md §8.31); the worker has no preview path
|
|
527
|
-
* (preview is a warm/cold flag only), so it always renders the action body. The
|
|
528
|
-
*
|
|
521
|
+
* (preview is a warm/cold flag only), so it always renders the action body. The classify step is
|
|
522
|
+
* the `classify_review_feedback` tool, which reads the configured classifier model at execute
|
|
523
|
+
* time — nothing model-shaped rides the prompt.
|
|
529
524
|
*/
|
|
530
|
-
export function initialPromptFor(
|
|
531
|
-
stage: DriveStage,
|
|
532
|
-
planRef: PlanRef | null,
|
|
533
|
-
classifierModel?: string,
|
|
534
|
-
): string | null {
|
|
525
|
+
export function initialPromptFor(stage: DriveStage, planRef: PlanRef | null): string | null {
|
|
535
526
|
if (planRef === null) return null;
|
|
536
527
|
const provider = String(planRef.provider ?? "");
|
|
537
528
|
const prId = String(planRef.pr_id ?? "");
|
|
@@ -541,15 +532,7 @@ export function initialPromptFor(
|
|
|
541
532
|
return render("stages/implement.md", { provider, pr_id: prId, url, read_cmd: readCmd });
|
|
542
533
|
}
|
|
543
534
|
// address
|
|
544
|
-
|
|
545
|
-
? `, passing \`model: "${classifierModel}"\` on that call (the configured [models.subagents] review-classifier model)`
|
|
546
|
-
: "";
|
|
547
|
-
return render("stages/address/action.md", {
|
|
548
|
-
provider,
|
|
549
|
-
pr_id: prId,
|
|
550
|
-
url,
|
|
551
|
-
model_clause: modelClause,
|
|
552
|
-
});
|
|
535
|
+
return render("stages/address/action.md", { provider, pr_id: prId, url });
|
|
553
536
|
}
|
|
554
537
|
|
|
555
538
|
// --- bind / subscribe management (Gap 1) --------------------------------------------------------
|
|
@@ -619,9 +602,8 @@ async function defaultCreateRuntime(
|
|
|
619
602
|
const services = await createAgentSessionServices({
|
|
620
603
|
cwd: factoryOpts.cwd,
|
|
621
604
|
agentDir: factoryOpts.agentDir,
|
|
622
|
-
authStorage: resolved.authStorage,
|
|
623
605
|
settingsManager,
|
|
624
|
-
|
|
606
|
+
modelRuntime: resolved.modelRuntime,
|
|
625
607
|
});
|
|
626
608
|
const result = await createAgentSessionFromServices({
|
|
627
609
|
services,
|
|
@@ -660,24 +642,23 @@ async function defaultCreateRuntime(
|
|
|
660
642
|
// --- model/auth resolution (Gap 5) --------------------------------------------------------------
|
|
661
643
|
|
|
662
644
|
export interface ResolvedAuth {
|
|
663
|
-
|
|
664
|
-
modelRegistry: ModelRegistry;
|
|
645
|
+
modelRuntime: ModelRuntime;
|
|
665
646
|
/** The EXPLICIT model only; `undefined` defers the pick to the SDK at session creation. */
|
|
666
647
|
model: Model<Api> | undefined;
|
|
667
648
|
}
|
|
668
649
|
|
|
669
650
|
/**
|
|
670
|
-
* Resolve auth; returns null (never throws) when no model is available at all.
|
|
671
|
-
* pre-pinned from the
|
|
672
|
-
* initial-model resolution (settings `defaultModel` → pi's curated per-provider
|
|
673
|
-
* available), which picks a current-generation model instead of the
|
|
674
|
-
* alphabetically-first (= oldest) entry.
|
|
651
|
+
* Resolve auth; returns null (never throws a domain error) when no model is available at all.
|
|
652
|
+
* The model is NOT pre-pinned from the runtime: an `undefined` model lets `createAgentSession`
|
|
653
|
+
* run its own initial-model resolution (settings `defaultModel` → pi's curated per-provider
|
|
654
|
+
* defaults → first available), which picks a current-generation model instead of the catalogue's
|
|
655
|
+
* alphabetically-first (= oldest) entry. Async because pi 0.84's `ModelRuntime.create` is async
|
|
656
|
+
* (the default creation stays offline — `allowModelNetwork` defaults false).
|
|
675
657
|
*/
|
|
676
|
-
export function resolveAuth(opts: DriveStageOptions): ResolvedAuth | null {
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
return { authStorage, modelRegistry, model: opts.model };
|
|
658
|
+
export async function resolveAuth(opts: DriveStageOptions): Promise<ResolvedAuth | null> {
|
|
659
|
+
const modelRuntime = opts.modelRuntime ?? (await ModelRuntime.create());
|
|
660
|
+
if (!opts.model && modelRuntime.getAvailableSnapshot().length === 0) return null;
|
|
661
|
+
return { modelRuntime, model: opts.model };
|
|
681
662
|
}
|
|
682
663
|
|
|
683
664
|
/** What an explicit `--model` flag resolves to (a thin projection of `ResolveCliModelResult`). */
|
|
@@ -700,12 +681,12 @@ export interface ResolvedWorkerModel {
|
|
|
700
681
|
*/
|
|
701
682
|
export function resolveWorkerModel(
|
|
702
683
|
raw: string | undefined,
|
|
703
|
-
|
|
684
|
+
modelRuntime: ModelRuntime,
|
|
704
685
|
): ResolvedWorkerModel {
|
|
705
686
|
if (!raw) {
|
|
706
687
|
return { model: undefined, thinkingLevel: undefined, warning: undefined, error: undefined };
|
|
707
688
|
}
|
|
708
|
-
const result = resolveCliModel({ cliModel: raw,
|
|
689
|
+
const result = resolveCliModel({ cliModel: raw, modelRuntime });
|
|
709
690
|
if (result.model === undefined && result.error === undefined) {
|
|
710
691
|
return {
|
|
711
692
|
model: undefined,
|
|
@@ -753,7 +734,9 @@ export async function driveStage(
|
|
|
753
734
|
return outcome;
|
|
754
735
|
};
|
|
755
736
|
|
|
756
|
-
|
|
737
|
+
// Auth/model resolution is a production-path concern only: with an injected runtime factory
|
|
738
|
+
// (tests) the drive never touches the default `ModelRuntime.create` (no host file reads).
|
|
739
|
+
const resolved = deps.createRuntime ? null : await resolveAuth(opts);
|
|
757
740
|
if (resolved === null && !deps.createRuntime) {
|
|
758
741
|
// A zero-turn run is still observable: emit a `run_started` + `run_finished` pair.
|
|
759
742
|
emitter.emit({ kind: "run_started", run_id: runId, stage: opts.stage });
|
|
@@ -772,10 +755,6 @@ export async function driveStage(
|
|
|
772
755
|
const bindManager = createBindManager(headlessBinding(), (event) => {
|
|
773
756
|
applyEvent(counters, event);
|
|
774
757
|
if (event.type === "turn_end") {
|
|
775
|
-
// Emit this turn's `[WIP:n]`/`[DONE:n]` markers in textual appearance order (one event each).
|
|
776
|
-
for (const m of extractStepMarkers(assistantText(event))) {
|
|
777
|
-
emitter.emit({ kind: "step_marker", marker: m.marker, step: m.step });
|
|
778
|
-
}
|
|
779
758
|
if (budgetTripped(counters, opts.budget)) trip("budget");
|
|
780
759
|
} else if (event.type === "tool_execution_end") {
|
|
781
760
|
const o = toolOutcomeOf(event);
|
|
@@ -908,7 +887,7 @@ function classify(
|
|
|
908
887
|
return evaluateTerminal({
|
|
909
888
|
stage: opts.stage,
|
|
910
889
|
submitDetails: counters.submitDetails,
|
|
911
|
-
|
|
890
|
+
finalizeDetails: counters.finalizeDetails,
|
|
912
891
|
lastReviewBatchPresent,
|
|
913
892
|
modelError: counters.modelError,
|
|
914
893
|
});
|
|
@@ -916,6 +895,5 @@ function classify(
|
|
|
916
895
|
|
|
917
896
|
/** Convenience: re-derive the initial prompt for a prepared worktree (reads its `cache.plan-ref`). */
|
|
918
897
|
export function initialPromptForWorktree(worktree: string, stage: DriveStage): string | null {
|
|
919
|
-
|
|
920
|
-
return initialPromptFor(stage, readPlanRef(worktree), classifierModel);
|
|
898
|
+
return initialPromptFor(stage, readPlanRef(worktree));
|
|
921
899
|
}
|
package/extension/workerMain.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// stderr), and exits 0 on `completed` else non-zero. Runs as `.ts` under node 22 type-stripping.
|
|
10
10
|
|
|
11
11
|
import { argv, env, exit, stderr, stdout } from "node:process";
|
|
12
|
-
import {
|
|
12
|
+
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import { runEventsPath, workflowDir } from "./substrate/cache.ts";
|
|
14
14
|
import {
|
|
15
15
|
type DriveBudget,
|
|
@@ -99,9 +99,8 @@ async function main(): Promise<number> {
|
|
|
99
99
|
// pi's CLI semantics (fuzzy matching, `provider/pattern`, a `:thinking` suffix —
|
|
100
100
|
// `resolveWorkerModel`), else the SDK's default resolution at session creation (settings
|
|
101
101
|
// default → pi's per-provider defaults → first available) — the deferral is unchanged.
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const resolved = resolveWorkerModel(parsed.model, modelRegistry);
|
|
102
|
+
const modelRuntime = await ModelRuntime.create();
|
|
103
|
+
const resolved = resolveWorkerModel(parsed.model, modelRuntime);
|
|
105
104
|
if (resolved.error) {
|
|
106
105
|
stderr.write(`perk worker: ${resolved.error}\n`);
|
|
107
106
|
return 2;
|
|
@@ -121,8 +120,7 @@ async function main(): Promise<number> {
|
|
|
121
120
|
initialPrompt,
|
|
122
121
|
model: resolved.model,
|
|
123
122
|
thinkingLevel: resolved.thinkingLevel,
|
|
124
|
-
|
|
125
|
-
modelRegistry,
|
|
123
|
+
modelRuntime,
|
|
126
124
|
budget: parsed.budget,
|
|
127
125
|
signal: controller.signal,
|
|
128
126
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mgiles/perk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "perk Pi extension (session interior) for the plan-oriented workflow.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -29,10 +29,18 @@
|
|
|
29
29
|
"./extension/index.ts"
|
|
30
30
|
]
|
|
31
31
|
},
|
|
32
|
+
"workspaces": [
|
|
33
|
+
"docs/site"
|
|
34
|
+
],
|
|
32
35
|
"scripts": {
|
|
33
36
|
"typecheck": "tsc --noEmit",
|
|
34
|
-
"lint": "biome check extension",
|
|
35
|
-
"format": "biome format --write extension"
|
|
37
|
+
"lint": "biome check extension docs/site tools",
|
|
38
|
+
"format": "biome format --write extension docs/site tools",
|
|
39
|
+
"docs:dev": "npm run dev --workspace docs/site",
|
|
40
|
+
"docs:build": "npm run build --workspace docs/site",
|
|
41
|
+
"docs:preview": "npm run preview --workspace docs/site",
|
|
42
|
+
"docs:typecheck": "npm run typecheck --workspace docs/site",
|
|
43
|
+
"docs:check": "npm run check --workspace docs/site"
|
|
36
44
|
},
|
|
37
45
|
"peerDependencies": {
|
|
38
46
|
"@earendil-works/pi-ai": "*",
|
|
@@ -42,10 +50,12 @@
|
|
|
42
50
|
},
|
|
43
51
|
"devDependencies": {
|
|
44
52
|
"@biomejs/biome": "2.4.16",
|
|
45
|
-
"@earendil-works/pi-ai": "0.
|
|
46
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
53
|
+
"@earendil-works/pi-ai": "0.84.1",
|
|
54
|
+
"@earendil-works/pi-coding-agent": "0.84.1",
|
|
55
|
+
"@earendil-works/pi-tui": "0.84.1",
|
|
47
56
|
"@types/node": "22.19.19",
|
|
48
57
|
"diff": "8.0.4",
|
|
58
|
+
"typebox": "1.3.7",
|
|
49
59
|
"typescript": "6.0.3",
|
|
50
60
|
"yaml": "2.9.0"
|
|
51
61
|
},
|