@jwilger/pi-development-system 0.36.0 → 0.37.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/package.json +1 -1
- package/src/context/turn-verifier.ts +10 -3
- package/src/core/model-advice.ts +13 -11
- package/src/core/test-runner.ts +3 -2
package/package.json
CHANGED
|
@@ -29,14 +29,19 @@ export function assistantParts(message: AgentMessage): Parts | undefined {
|
|
|
29
29
|
return { text, callsTools: message.content.some((c) => c.type === "toolCall") };
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
const LIST_ITEM = /^(?:\d+[.)]|[-*+])\s/;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* True when the message ends by asking the user something: the last non-empty line that is not an
|
|
36
|
+
* option-list item ends with a question mark, optionally followed by a short parenthetical ("(y/n)").
|
|
37
|
+
*/
|
|
33
38
|
export function asksUser(text: string): boolean {
|
|
34
39
|
const last = text
|
|
35
40
|
.split("\n")
|
|
36
41
|
.map((l) => l.trim())
|
|
37
|
-
.filter((l) => l !== "")
|
|
42
|
+
.filter((l) => l !== "" && !LIST_ITEM.test(l))
|
|
38
43
|
.at(-1);
|
|
39
|
-
return last !== undefined && /\?[\s)"'*_`]*$/.test(last);
|
|
44
|
+
return last !== undefined && /\?(?:\s*\([^()]{0,40}\))?[\s)"'*_`]*$/.test(last);
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
const claimMessage = (): string =>
|
|
@@ -83,6 +88,8 @@ export function registerTurnVerifier(deps: TurnVerifierDeps): void {
|
|
|
83
88
|
});
|
|
84
89
|
|
|
85
90
|
deps.pi.on("turn_end", async (event, ctx) => {
|
|
91
|
+
// An aborted or errored turn is dropped by pi; judging it would delay the abort and spend a correction.
|
|
92
|
+
if (event.outcome !== "completed") return undefined;
|
|
86
93
|
const state = deps.state.get();
|
|
87
94
|
if (!VERIFIED_PHASES.has(state.phase)) return undefined;
|
|
88
95
|
const parts = assistantParts(event.message);
|
package/src/core/model-advice.ts
CHANGED
|
@@ -38,19 +38,21 @@ const matches = (candidates: readonly string[], model: string): boolean => {
|
|
|
38
38
|
);
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
+
const direct = (matrix: ModelMatrix, tier: Tier): string[] =>
|
|
42
|
+
// `fast` falls back to `@strong`; expanding it would make every strong model fast.
|
|
43
|
+
matrix[tier].filter((c) => !c.startsWith("@"));
|
|
44
|
+
|
|
41
45
|
/**
|
|
42
|
-
* The
|
|
43
|
-
*
|
|
44
|
-
* the model is in none of them.
|
|
46
|
+
* The tier a model natively belongs to: the lowest tier that lists it (sol is a fallback in
|
|
47
|
+
* frontier but lives in strong). Undefined when it is in none of them.
|
|
45
48
|
*/
|
|
46
49
|
export function tierOf(matrix: ModelMatrix, model: string): Tier | undefined {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
);
|
|
50
|
+
return [...TIERS].reverse().find((tier) => matches(direct(matrix, tier), model));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The best tier that lists the model at all: what a slot resolving to it is asking for. */
|
|
54
|
+
function bestTierOf(matrix: ModelMatrix, model: string): Tier | undefined {
|
|
55
|
+
return TIERS.find((tier) => matches(direct(matrix, tier), model));
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
export type AdviceInput = {
|
|
@@ -74,7 +76,7 @@ export function adviseModel(input: AdviceInput): string | undefined {
|
|
|
74
76
|
// Anything the slot itself lists is acceptable for the phase, whatever tier it natively sits in.
|
|
75
77
|
if (matches(flattenSlot(input.matrix, slot), input.model)) return undefined;
|
|
76
78
|
const have = tierOf(input.matrix, input.model);
|
|
77
|
-
const need =
|
|
79
|
+
const need = bestTierOf(input.matrix, wanted.value.model);
|
|
78
80
|
if (have === undefined || need === undefined) return undefined;
|
|
79
81
|
if (TIERS.indexOf(have) <= TIERS.indexOf(need)) return undefined;
|
|
80
82
|
return `${input.phase} on ${input.model} (${have} tier) — the matrix prefers ${wanted.value.model} (${need} tier) for the ${slot} slot. Consider asking an advisor subagent for the hard decisions, or continue and record a departure (gate models.phase-mismatch). The system never switches your model for you.`;
|
package/src/core/test-runner.ts
CHANGED
|
@@ -86,9 +86,10 @@ function reportedExit(result: ResultLike): number {
|
|
|
86
86
|
|
|
87
87
|
/** The last few non-empty output lines, redacted and bounded: what the runner said at the end. */
|
|
88
88
|
export function summarizeOutput(output: string): string {
|
|
89
|
-
|
|
89
|
+
// Redact first: a multi-line secret (PEM key) must be recognised before the tail is cut off its BEGIN line.
|
|
90
|
+
const lines = redactSecrets(output)
|
|
90
91
|
.split("\n")
|
|
91
92
|
.map((l) => l.trim())
|
|
92
93
|
.filter((l) => l !== "" && !/^Command exited with code \d+$/.test(l));
|
|
93
|
-
return
|
|
94
|
+
return lines.slice(-SUMMARY_LINES).join(" | ").slice(0, SUMMARY_MAX);
|
|
94
95
|
}
|