gsd-pi 2.73.0-dev.1cfd50c → 2.73.0-dev.e1c09f2

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.
Files changed (56) hide show
  1. package/dist/cli.js +0 -47
  2. package/dist/help-text.js +1 -1
  3. package/dist/resources/extensions/gsd/auto-dispatch.js +5 -3
  4. package/dist/resources/extensions/gsd/auto-prompts.js +9 -6
  5. package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -1
  6. package/dist/startup-model-validation.js +8 -5
  7. package/dist/web/standalone/.next/BUILD_ID +1 -1
  8. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  9. package/dist/web/standalone/.next/build-manifest.json +2 -2
  10. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  11. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  12. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  13. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  16. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  20. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/index.html +1 -1
  28. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  35. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  36. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  37. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  38. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  39. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  40. package/package.json +1 -1
  41. package/packages/pi-coding-agent/dist/core/auth-storage.js +1 -1
  42. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  43. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +27 -0
  44. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  45. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  46. package/packages/pi-coding-agent/dist/core/model-resolver.js +25 -68
  47. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  48. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +38 -0
  49. package/packages/pi-coding-agent/src/core/auth-storage.ts +1 -1
  50. package/packages/pi-coding-agent/src/core/model-resolver.ts +26 -70
  51. package/src/resources/extensions/gsd/auto-dispatch.ts +5 -0
  52. package/src/resources/extensions/gsd/auto-prompts.ts +9 -3
  53. package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -1
  54. package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +267 -0
  55. /package/dist/web/standalone/.next/static/{uNGVqSkAnszMl0okA4nnp → _XD_gUDcZNBbWV5rI8RgS}/_buildManifest.js +0 -0
  56. /package/dist/web/standalone/.next/static/{uNGVqSkAnszMl0okA4nnp → _XD_gUDcZNBbWV5rI8RgS}/_ssgManifest.js +0 -0
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Regression tests for subagent model preference wiring.
3
+ *
4
+ * Fixes: subagent_model config in reactive_execution was validated and stored
5
+ * but never passed through to subagent dispatch instruction strings, so the
6
+ * executing agent autonomously chose "sonnet" instead of the configured model.
7
+ *
8
+ * Issue: gsd-build/gsd-2#4078
9
+ */
10
+
11
+ import test from "node:test";
12
+ import assert from "node:assert/strict";
13
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync, readFileSync } from "node:fs";
14
+ import { join, dirname } from "node:path";
15
+ import { tmpdir } from "node:os";
16
+ import { fileURLToPath } from "node:url";
17
+ import { validatePreferences } from "../preferences-validation.ts";
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const promptsSrc = readFileSync(join(__dirname, "..", "auto-prompts.ts"), "utf-8");
21
+ const dispatchSrc = readFileSync(join(__dirname, "..", "auto-dispatch.ts"), "utf-8");
22
+
23
+ // ─── Preference Validation ────────────────────────────────────────────────
24
+
25
+ test("reactive_execution: subagent_model is preserved in validated preferences", () => {
26
+ const result = validatePreferences({
27
+ reactive_execution: {
28
+ enabled: true,
29
+ max_parallel: 2,
30
+ isolation_mode: "same-tree",
31
+ subagent_model: "claude-opus-4-6",
32
+ },
33
+ });
34
+ assert.equal(result.errors.length, 0);
35
+ assert.equal(
36
+ result.preferences.reactive_execution?.subagent_model,
37
+ "claude-opus-4-6",
38
+ "subagent_model should be preserved through validation",
39
+ );
40
+ });
41
+
42
+ test("reactive_execution: subagent_model rejects empty string", () => {
43
+ const result = validatePreferences({
44
+ reactive_execution: {
45
+ enabled: true,
46
+ max_parallel: 2,
47
+ isolation_mode: "same-tree",
48
+ subagent_model: "",
49
+ } as any,
50
+ });
51
+ assert.ok(
52
+ result.errors.some((e) => e.includes("subagent_model")),
53
+ "empty subagent_model should produce a validation error",
54
+ );
55
+ });
56
+
57
+ // ─── Structural: Prompt Builders Accept subagentModel ────────────────────
58
+
59
+ test("buildReactiveExecutePrompt: accepts subagentModel parameter", () => {
60
+ const fnStart = promptsSrc.indexOf("export async function buildReactiveExecutePrompt");
61
+ assert.ok(fnStart !== -1, "buildReactiveExecutePrompt should be exported");
62
+ const signature = promptsSrc.slice(fnStart, fnStart + 300);
63
+ assert.ok(
64
+ signature.includes("subagentModel"),
65
+ "buildReactiveExecutePrompt should accept a subagentModel parameter",
66
+ );
67
+ });
68
+
69
+ test("buildParallelResearchSlicesPrompt: accepts subagentModel parameter", () => {
70
+ const fnStart = promptsSrc.indexOf("export async function buildParallelResearchSlicesPrompt");
71
+ assert.ok(fnStart !== -1, "buildParallelResearchSlicesPrompt should be exported");
72
+ const signature = promptsSrc.slice(fnStart, fnStart + 300);
73
+ assert.ok(
74
+ signature.includes("subagentModel"),
75
+ "buildParallelResearchSlicesPrompt should accept a subagentModel parameter",
76
+ );
77
+ });
78
+
79
+ test("buildGateEvaluatePrompt: accepts subagentModel parameter", () => {
80
+ const fnStart = promptsSrc.indexOf("export async function buildGateEvaluatePrompt");
81
+ assert.ok(fnStart !== -1, "buildGateEvaluatePrompt should be exported");
82
+ const signature = promptsSrc.slice(fnStart, fnStart + 300);
83
+ assert.ok(
84
+ signature.includes("subagentModel"),
85
+ "buildGateEvaluatePrompt should accept a subagentModel parameter",
86
+ );
87
+ });
88
+
89
+ // ─── Structural: Instruction Strings Inject Model ────────────────────────
90
+
91
+ test("buildReactiveExecutePrompt: instruction string uses subagentModel when set", () => {
92
+ const fnStart = promptsSrc.indexOf("export async function buildReactiveExecutePrompt");
93
+ const fnEnd = promptsSrc.indexOf("\nexport async function", fnStart + 1);
94
+ const fnBody = promptsSrc.slice(fnStart, fnEnd);
95
+ assert.ok(
96
+ fnBody.includes("subagentModel"),
97
+ "buildReactiveExecutePrompt body should reference subagentModel",
98
+ );
99
+ // The instruction line must be dynamic (not a plain string literal)
100
+ assert.ok(
101
+ !fnBody.includes('"Use this as the prompt for a `subagent` call:"'),
102
+ "instruction should not be a plain static string — model must be injectable",
103
+ );
104
+ });
105
+
106
+ test("buildParallelResearchSlicesPrompt: instruction string uses subagentModel when set", () => {
107
+ const fnStart = promptsSrc.indexOf("export async function buildParallelResearchSlicesPrompt");
108
+ const fnEnd = promptsSrc.indexOf("\nexport async function", fnStart + 1);
109
+ const fnBody = promptsSrc.slice(fnStart, fnEnd);
110
+ assert.ok(
111
+ fnBody.includes("subagentModel"),
112
+ "buildParallelResearchSlicesPrompt body should reference subagentModel",
113
+ );
114
+ });
115
+
116
+ test("buildGateEvaluatePrompt: instruction string uses subagentModel when set", () => {
117
+ const fnStart = promptsSrc.indexOf("export async function buildGateEvaluatePrompt");
118
+ const fnEnd = promptsSrc.indexOf("\nexport async function", fnStart + 1);
119
+ const fnBody = promptsSrc.slice(fnStart, fnEnd);
120
+ assert.ok(
121
+ fnBody.includes("subagentModel"),
122
+ "buildGateEvaluatePrompt body should reference subagentModel",
123
+ );
124
+ });
125
+
126
+ // ─── Structural: Dispatch Wires Model to Prompt Builders ─────────────────
127
+
128
+ test("auto-dispatch: passes model to buildReactiveExecutePrompt", () => {
129
+ // Find the reactive-execute dispatch rule
130
+ const ruleStart = dispatchSrc.indexOf("reactive-execute (parallel dispatch)");
131
+ assert.ok(ruleStart !== -1, "reactive-execute dispatch rule should exist");
132
+ const ruleBlock = dispatchSrc.slice(ruleStart, ruleStart + 1000);
133
+ assert.ok(
134
+ ruleBlock.includes("subagent_model") || ruleBlock.includes("subagentModel"),
135
+ "reactive-execute rule should resolve and pass the subagent model",
136
+ );
137
+ });
138
+
139
+ test("auto-dispatch: passes model to buildParallelResearchSlicesPrompt", () => {
140
+ const callIdx = dispatchSrc.indexOf("buildParallelResearchSlicesPrompt(");
141
+ assert.ok(callIdx !== -1, "buildParallelResearchSlicesPrompt call should exist");
142
+ // The call site should pass a model argument (not just 4 args)
143
+ const callSite = dispatchSrc.slice(callIdx, callIdx + 300);
144
+ assert.ok(
145
+ callSite.includes("subagentModel") || callSite.includes("resolveModelWithFallbacksForUnit"),
146
+ "buildParallelResearchSlicesPrompt call should include model argument",
147
+ );
148
+ });
149
+
150
+ test("auto-dispatch: passes model to buildGateEvaluatePrompt", () => {
151
+ const callIdx = dispatchSrc.indexOf("buildGateEvaluatePrompt(");
152
+ assert.ok(callIdx !== -1, "buildGateEvaluatePrompt call should exist");
153
+ const callSite = dispatchSrc.slice(callIdx, callIdx + 300);
154
+ assert.ok(
155
+ callSite.includes("subagentModel") || callSite.includes("resolveModelWithFallbacksForUnit"),
156
+ "buildGateEvaluatePrompt call should include model argument",
157
+ );
158
+ });
159
+
160
+ // ─── Integration: Prompt Output Contains Model String ────────────────────
161
+
162
+ test("buildReactiveExecutePrompt: output contains model string when subagentModel provided", async (t) => {
163
+ const { buildReactiveExecutePrompt } = await import("../auto-prompts.ts");
164
+ const repo = mkdtempSync(join(tmpdir(), "gsd-subagent-model-reactive-"));
165
+ t.after(() => rmSync(repo, { recursive: true, force: true }));
166
+
167
+ const gsd = join(repo, ".gsd", "milestones", "M001", "slices", "S01");
168
+ mkdirSync(join(gsd, "tasks"), { recursive: true });
169
+
170
+ writeFileSync(
171
+ join(gsd, "S01-PLAN.md"),
172
+ [
173
+ "# S01: Test Slice",
174
+ "",
175
+ "**Goal:** Verify model injection",
176
+ "**Demo:** Model appears in subagent prompt",
177
+ "",
178
+ "## Tasks",
179
+ "",
180
+ "- [ ] **T01: Task One** `est:15m`",
181
+ " Do something.",
182
+ "",
183
+ ].join("\n"),
184
+ );
185
+
186
+ writeFileSync(
187
+ join(gsd, "tasks", "T01-PLAN.md"),
188
+ [
189
+ "# T01: Task One",
190
+ "",
191
+ "## Description",
192
+ "Do something.",
193
+ "",
194
+ "## Inputs",
195
+ "",
196
+ "- `src/config.json` — Config",
197
+ "",
198
+ "## Expected Output",
199
+ "",
200
+ "- `src/out.ts` — Result",
201
+ ].join("\n"),
202
+ );
203
+
204
+ const prompt = await buildReactiveExecutePrompt(
205
+ "M001", "Test Milestone", "S01", "Test Slice",
206
+ ["T01"], repo, "claude-opus-4-6",
207
+ );
208
+
209
+ assert.ok(
210
+ prompt.includes('model: "claude-opus-4-6"'),
211
+ `Prompt should contain model instruction. Got:\n${prompt.slice(0, 500)}`,
212
+ );
213
+ });
214
+
215
+ test("buildReactiveExecutePrompt: no model instruction when subagentModel omitted", async (t) => {
216
+ const { buildReactiveExecutePrompt } = await import("../auto-prompts.ts");
217
+ const repo = mkdtempSync(join(tmpdir(), "gsd-subagent-model-none-"));
218
+ t.after(() => rmSync(repo, { recursive: true, force: true }));
219
+
220
+ const gsd = join(repo, ".gsd", "milestones", "M001", "slices", "S01");
221
+ mkdirSync(join(gsd, "tasks"), { recursive: true });
222
+
223
+ writeFileSync(
224
+ join(gsd, "S01-PLAN.md"),
225
+ [
226
+ "# S01: Test Slice",
227
+ "",
228
+ "**Goal:** Verify no model when omitted",
229
+ "**Demo:** No model string",
230
+ "",
231
+ "## Tasks",
232
+ "",
233
+ "- [ ] **T01: Task One** `est:15m`",
234
+ " Do something.",
235
+ "",
236
+ ].join("\n"),
237
+ );
238
+
239
+ writeFileSync(
240
+ join(gsd, "tasks", "T01-PLAN.md"),
241
+ [
242
+ "# T01: Task One",
243
+ "",
244
+ "## Description",
245
+ "Do something.",
246
+ "",
247
+ "## Inputs",
248
+ "",
249
+ "- `src/config.json` — Config",
250
+ "",
251
+ "## Expected Output",
252
+ "",
253
+ "- `src/out.ts` — Result",
254
+ ].join("\n"),
255
+ );
256
+
257
+ const prompt = await buildReactiveExecutePrompt(
258
+ "M001", "Test Milestone", "S01", "Test Slice",
259
+ ["T01"], repo,
260
+ // no subagentModel
261
+ );
262
+
263
+ assert.ok(
264
+ !prompt.includes('with model:'),
265
+ "Prompt should not contain model instruction when subagentModel is omitted",
266
+ );
267
+ });