gsd-pi 2.76.0-dev.4100bd590 → 2.76.0-dev.82e249f7b
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/resources/extensions/gsd/auto/phases.js +4 -1
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto-model-selection.js +13 -2
- package/dist/resources/extensions/gsd/auto-start.js +12 -7
- package/dist/resources/extensions/gsd/auto.js +4 -1
- package/dist/resources/extensions/gsd/complexity-classifier.js +5 -3
- package/dist/resources/extensions/gsd/prompt-loader.js +22 -7
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +60 -15
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/think-tag-parser.d.ts +17 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.js +75 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.js.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.js +41 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.js.map +1 -0
- package/packages/pi-ai/src/providers/openai-completions.ts +57 -16
- package/packages/pi-ai/src/providers/think-tag-parser.test.ts +44 -0
- package/packages/pi-ai/src/providers/think-tag-parser.ts +94 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.js +92 -12
- package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js +16 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +61 -1
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +76 -10
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +13 -7
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/src/core/model-discovery.test.ts +19 -0
- package/packages/pi-coding-agent/src/core/model-discovery.ts +99 -12
- package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +75 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +86 -10
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -7
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/scripts/link-workspace-packages.cjs +1 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/phases.ts +4 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -1
- package/src/resources/extensions/gsd/auto-model-selection.ts +16 -1
- package/src/resources/extensions/gsd/auto-start.ts +12 -7
- package/src/resources/extensions/gsd/auto.ts +4 -1
- package/src/resources/extensions/gsd/complexity-classifier.ts +5 -3
- package/src/resources/extensions/gsd/prompt-loader.ts +30 -7
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +33 -3
- package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +49 -0
- /package/dist/web/standalone/.next/static/{YnUwu2WWaT0_hyTLUF4nq → ecSsu49rxxcpbNmVP4mLD}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{YnUwu2WWaT0_hyTLUF4nq → ecSsu49rxxcpbNmVP4mLD}/_ssgManifest.js +0 -0
|
@@ -34,6 +34,7 @@ const packageMap = {
|
|
|
34
34
|
'pi-coding-agent': { scope: '@gsd', name: 'pi-coding-agent' },
|
|
35
35
|
'pi-tui': { scope: '@gsd', name: 'pi-tui' },
|
|
36
36
|
'rpc-client': { scope: '@gsd-build', name: 'rpc-client' },
|
|
37
|
+
'mcp-server': { scope: '@gsd-build', name: 'mcp-server' },
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
for (const scopeDir of Object.values(scopeDirs)) {
|
|
@@ -213,6 +213,7 @@ export interface LoopDeps {
|
|
|
213
213
|
retryContext?: { isRetry: boolean; previousTier?: string },
|
|
214
214
|
isAutoMode?: boolean,
|
|
215
215
|
sessionModelOverride?: { provider: string; id: string } | null,
|
|
216
|
+
autoModeStartThinkingLevel?: ReturnType<ExtensionAPI["getThinkingLevel"]> | null,
|
|
216
217
|
) => Promise<{
|
|
217
218
|
routing: { tier: string; modelDowngraded: boolean } | null;
|
|
218
219
|
appliedModel: { provider: string; id: string } | null;
|
|
@@ -1471,6 +1471,7 @@ export async function runUnitPhase(
|
|
|
1471
1471
|
sidecarItem ? undefined : { isRetry, previousTier },
|
|
1472
1472
|
undefined,
|
|
1473
1473
|
s.manualSessionModelOverride,
|
|
1474
|
+
s.autoModeStartThinkingLevel,
|
|
1474
1475
|
);
|
|
1475
1476
|
s.currentUnitRouting =
|
|
1476
1477
|
modelResult.routing as AutoSession["currentUnitRouting"];
|
|
@@ -1485,6 +1486,9 @@ export async function runUnitPhase(
|
|
|
1485
1486
|
if (match) {
|
|
1486
1487
|
const ok = await pi.setModel(match, { persist: false });
|
|
1487
1488
|
if (ok) {
|
|
1489
|
+
if (s.autoModeStartThinkingLevel) {
|
|
1490
|
+
pi.setThinkingLevel(s.autoModeStartThinkingLevel);
|
|
1491
|
+
}
|
|
1488
1492
|
s.currentUnitModel = match as AutoSession["currentUnitModel"];
|
|
1489
1493
|
ctx.ui.notify(`Hook model override: ${match.provider}/${match.id}`, "info");
|
|
1490
1494
|
} else {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import type { Api, Model } from "@gsd/pi-ai";
|
|
20
|
-
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
20
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
21
21
|
import type { GitServiceImpl } from "../git-service.js";
|
|
22
22
|
import type { CaptureEntry } from "../captures.js";
|
|
23
23
|
import type { BudgetAlertLevel } from "../auto-budget.js";
|
|
@@ -40,6 +40,8 @@ export interface StartModel {
|
|
|
40
40
|
id: string;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export type ThinkingLevelSnapshot = ReturnType<ExtensionAPI["getThinkingLevel"]>;
|
|
44
|
+
|
|
43
45
|
export interface PendingVerificationRetry {
|
|
44
46
|
unitId: string;
|
|
45
47
|
failureContext: string;
|
|
@@ -120,6 +122,8 @@ export class AutoSession {
|
|
|
120
122
|
currentDispatchedModelId: string | null = null;
|
|
121
123
|
originalModelId: string | null = null;
|
|
122
124
|
originalModelProvider: string | null = null;
|
|
125
|
+
autoModeStartThinkingLevel: ThinkingLevelSnapshot | null = null;
|
|
126
|
+
originalThinkingLevel: ThinkingLevelSnapshot | null = null;
|
|
123
127
|
lastBudgetAlertLevel: BudgetAlertLevel = 0;
|
|
124
128
|
|
|
125
129
|
// ── Recovery ─────────────────────────────────────────────────────────────
|
|
@@ -241,6 +245,8 @@ export class AutoSession {
|
|
|
241
245
|
this.currentDispatchedModelId = null;
|
|
242
246
|
this.originalModelId = null;
|
|
243
247
|
this.originalModelProvider = null;
|
|
248
|
+
this.autoModeStartThinkingLevel = null;
|
|
249
|
+
this.originalThinkingLevel = null;
|
|
244
250
|
this.lastBudgetAlertLevel = 0;
|
|
245
251
|
|
|
246
252
|
// Recovery
|
|
@@ -33,6 +33,14 @@ export interface PreferredModelConfig {
|
|
|
33
33
|
source: "explicit" | "synthesized";
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function reapplyThinkingLevel(
|
|
37
|
+
pi: ExtensionAPI,
|
|
38
|
+
level: ReturnType<ExtensionAPI["getThinkingLevel"]> | null | undefined,
|
|
39
|
+
): void {
|
|
40
|
+
if (!level) return;
|
|
41
|
+
pi.setThinkingLevel(level);
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
export function resolvePreferredModelConfig(
|
|
37
45
|
unitType: string,
|
|
38
46
|
autoModeStartModel: { provider: string; id: string; flatRateCtx?: FlatRateContext } | null,
|
|
@@ -97,6 +105,8 @@ export async function selectAndApplyModel(
|
|
|
97
105
|
isAutoMode = true,
|
|
98
106
|
/** Explicit /gsd model pin captured at bootstrap for long-running auto loops. */
|
|
99
107
|
sessionModelOverride?: { provider: string; id: string } | null,
|
|
108
|
+
/** Thinking level captured at auto-mode start and re-applied after model swaps. */
|
|
109
|
+
autoModeStartThinkingLevel?: ReturnType<ExtensionAPI["getThinkingLevel"]> | null,
|
|
100
110
|
): Promise<ModelSelectionResult> {
|
|
101
111
|
const uokFlags = resolveUokFlags(prefs);
|
|
102
112
|
const effectiveSessionModelOverride = sessionModelOverride === undefined
|
|
@@ -380,6 +390,7 @@ export async function selectAndApplyModel(
|
|
|
380
390
|
const ok = await pi.setModel(model, { persist: false });
|
|
381
391
|
if (ok) {
|
|
382
392
|
appliedModel = model;
|
|
393
|
+
reapplyThinkingLevel(pi, autoModeStartThinkingLevel);
|
|
383
394
|
|
|
384
395
|
// ADR-005: Adjust active tool set for the selected model's provider capabilities.
|
|
385
396
|
// Hard-filter incompatible tools, then let extensions override via adjust_tool_set hook.
|
|
@@ -456,10 +467,14 @@ export async function selectAndApplyModel(
|
|
|
456
467
|
);
|
|
457
468
|
if (byId) {
|
|
458
469
|
const fallbackOk = await pi.setModel(byId, { persist: false });
|
|
459
|
-
if (fallbackOk)
|
|
470
|
+
if (fallbackOk) {
|
|
471
|
+
appliedModel = byId;
|
|
472
|
+
reapplyThinkingLevel(pi, autoModeStartThinkingLevel);
|
|
473
|
+
}
|
|
460
474
|
}
|
|
461
475
|
} else {
|
|
462
476
|
appliedModel = startModel;
|
|
477
|
+
reapplyThinkingLevel(pi, autoModeStartThinkingLevel);
|
|
463
478
|
}
|
|
464
479
|
}
|
|
465
480
|
}
|
|
@@ -273,8 +273,8 @@ export async function bootstrapAutoSession(
|
|
|
273
273
|
//
|
|
274
274
|
// Precedence:
|
|
275
275
|
// 1) Explicit session override via /gsd model (this session)
|
|
276
|
-
// 2)
|
|
277
|
-
// 3)
|
|
276
|
+
// 2) Current session model from settings/session restore (if provider ready)
|
|
277
|
+
// 3) GSD model preferences from PREFERENCES.md (validated against live auth)
|
|
278
278
|
//
|
|
279
279
|
// This preserves #3517 defaults while honoring explicit runtime model
|
|
280
280
|
// selection for subsequent /gsd runs in the same session.
|
|
@@ -314,11 +314,14 @@ export async function bootstrapAutoSession(
|
|
|
314
314
|
}
|
|
315
315
|
const sessionModelReady =
|
|
316
316
|
ctx.model && ctx.modelRegistry.isProviderRequestReady(ctx.model.provider);
|
|
317
|
+
const currentSessionModel = (sessionModelReady && ctx.model)
|
|
318
|
+
? { provider: ctx.model.provider, id: ctx.model.id }
|
|
319
|
+
: null;
|
|
320
|
+
const startThinkingSnapshot = pi.getThinkingLevel();
|
|
317
321
|
const startModelSnapshot = manualSessionOverride
|
|
322
|
+
?? currentSessionModel
|
|
318
323
|
?? validatedPreferredModel
|
|
319
|
-
??
|
|
320
|
-
? { provider: ctx.model.provider, id: ctx.model.id }
|
|
321
|
-
: null);
|
|
324
|
+
?? null;
|
|
322
325
|
|
|
323
326
|
try {
|
|
324
327
|
// Validate GSD_PROJECT_ID early so the user gets immediate feedback
|
|
@@ -664,8 +667,9 @@ export async function bootstrapAutoSession(
|
|
|
664
667
|
s.pendingQuickTasks = [];
|
|
665
668
|
s.currentUnit = null;
|
|
666
669
|
s.currentMilestoneId = state.activeMilestone?.id ?? null;
|
|
667
|
-
s.originalModelId = ctx.model?.id ?? null;
|
|
668
|
-
s.originalModelProvider = ctx.model?.provider ?? null;
|
|
670
|
+
s.originalModelId = startModelSnapshot?.id ?? ctx.model?.id ?? null;
|
|
671
|
+
s.originalModelProvider = startModelSnapshot?.provider ?? ctx.model?.provider ?? null;
|
|
672
|
+
s.originalThinkingLevel = startThinkingSnapshot ?? null;
|
|
669
673
|
|
|
670
674
|
// Register SIGTERM handler
|
|
671
675
|
registerSigtermHandler(base);
|
|
@@ -779,6 +783,7 @@ export async function bootstrapAutoSession(
|
|
|
779
783
|
id: startModelSnapshot.id,
|
|
780
784
|
};
|
|
781
785
|
}
|
|
786
|
+
s.autoModeStartThinkingLevel = startThinkingSnapshot ?? null;
|
|
782
787
|
s.manualSessionModelOverride = manualSessionOverride ?? null;
|
|
783
788
|
|
|
784
789
|
// Apply worker model override from parallel orchestrator (#worker-model).
|
|
@@ -969,7 +969,7 @@ export async function stopAuto(
|
|
|
969
969
|
logWarning("engine", `file unlink failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
970
970
|
}
|
|
971
971
|
|
|
972
|
-
// ── Step 13: Restore original model (before reset clears IDs) ──
|
|
972
|
+
// ── Step 13: Restore original model + thinking (before reset clears IDs) ──
|
|
973
973
|
try {
|
|
974
974
|
if (pi && ctx && s.originalModelId && s.originalModelProvider) {
|
|
975
975
|
const original = ctx.modelRegistry.find(
|
|
@@ -978,6 +978,9 @@ export async function stopAuto(
|
|
|
978
978
|
);
|
|
979
979
|
if (original) await pi.setModel(original);
|
|
980
980
|
}
|
|
981
|
+
if (pi && s.originalThinkingLevel) {
|
|
982
|
+
pi.setThinkingLevel(s.originalThinkingLevel);
|
|
983
|
+
}
|
|
981
984
|
} catch (e) {
|
|
982
985
|
debugLog("stop-cleanup-model", { error: e instanceof Error ? e.message : String(e) });
|
|
983
986
|
}
|
|
@@ -32,11 +32,13 @@ export interface TaskMetadata {
|
|
|
32
32
|
// ─── Unit Type → Default Tier Mapping ────────────────────────────────────────
|
|
33
33
|
|
|
34
34
|
const UNIT_TYPE_TIERS: Record<string, ComplexityTier> = {
|
|
35
|
-
// Tier 1 — Light:
|
|
36
|
-
"complete-slice": "light",
|
|
35
|
+
// Tier 1 — Light: compact verification turns
|
|
37
36
|
"run-uat": "light",
|
|
38
37
|
|
|
39
|
-
// Tier 2 — Standard: research, routine discussion
|
|
38
|
+
// Tier 2 — Standard: research, routine discussion, slice completion
|
|
39
|
+
// complete-slice can carry large inlined context; avoid routing it to the
|
|
40
|
+
// cheapest "light" model by default (#4520).
|
|
41
|
+
"complete-slice": "standard",
|
|
40
42
|
"discuss-milestone": "standard",
|
|
41
43
|
"discuss-slice": "standard",
|
|
42
44
|
"research-milestone": "standard",
|
|
@@ -24,6 +24,35 @@ import { fileURLToPath } from "node:url";
|
|
|
24
24
|
import { homedir } from "node:os";
|
|
25
25
|
import { logWarning } from "./workflow-logger.js";
|
|
26
26
|
|
|
27
|
+
type ExistsFn = (path: string) => boolean;
|
|
28
|
+
|
|
29
|
+
function hasRequiredExtensionAssets(rootDir: string, exists: ExistsFn = existsSync): boolean {
|
|
30
|
+
return (
|
|
31
|
+
exists(join(rootDir, "prompts")) &&
|
|
32
|
+
exists(join(rootDir, "templates", "task-summary.md"))
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function resolveExtensionDirFromCandidates(
|
|
37
|
+
moduleDir: string,
|
|
38
|
+
agentGsdDir: string,
|
|
39
|
+
exists: ExistsFn = existsSync,
|
|
40
|
+
): string {
|
|
41
|
+
const moduleUsable = hasRequiredExtensionAssets(moduleDir, exists);
|
|
42
|
+
const agentUsable = hasRequiredExtensionAssets(agentGsdDir, exists);
|
|
43
|
+
|
|
44
|
+
// Prefer the user-local extension tree when both are valid. This avoids
|
|
45
|
+
// leaking npm/global-install paths into prompts on Windows.
|
|
46
|
+
if (agentUsable) return agentGsdDir;
|
|
47
|
+
if (moduleUsable) return moduleDir;
|
|
48
|
+
|
|
49
|
+
// Degraded fallback: if required template is missing in both locations,
|
|
50
|
+
// keep previous behavior and prefer whichever still has prompts/.
|
|
51
|
+
if (exists(join(moduleDir, "prompts"))) return moduleDir;
|
|
52
|
+
if (exists(join(agentGsdDir, "prompts"))) return agentGsdDir;
|
|
53
|
+
return moduleDir;
|
|
54
|
+
}
|
|
55
|
+
|
|
27
56
|
/**
|
|
28
57
|
* Resolve the GSD extension directory.
|
|
29
58
|
*
|
|
@@ -36,15 +65,9 @@ import { logWarning } from "./workflow-logger.js";
|
|
|
36
65
|
*/
|
|
37
66
|
function resolveExtensionDir(): string {
|
|
38
67
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
39
|
-
if (existsSync(join(moduleDir, "prompts"))) return moduleDir;
|
|
40
|
-
|
|
41
|
-
// Fallback: user-local agent directory
|
|
42
68
|
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
43
69
|
const agentGsdDir = join(gsdHome, "agent", "extensions", "gsd");
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// Last resort: return the module dir (warmCache will silently handle the miss)
|
|
47
|
-
return moduleDir;
|
|
70
|
+
return resolveExtensionDirFromCandidates(moduleDir, agentGsdDir);
|
|
48
71
|
}
|
|
49
72
|
|
|
50
73
|
const __extensionDir = resolveExtensionDir();
|
|
@@ -341,6 +341,18 @@ test("dynamic routing passes provider-qualified model keys to the router", () =>
|
|
|
341
341
|
);
|
|
342
342
|
});
|
|
343
343
|
|
|
344
|
+
test("selectAndApplyModel re-applies captured thinking level after setModel success", () => {
|
|
345
|
+
const src = readFileSync(join(__dirname, "..", "auto-model-selection.ts"), "utf-8");
|
|
346
|
+
assert.ok(
|
|
347
|
+
src.includes("autoModeStartThinkingLevel?: ReturnType<ExtensionAPI[\"getThinkingLevel\"]> | null"),
|
|
348
|
+
"selectAndApplyModel should accept an autoModeStartThinkingLevel parameter",
|
|
349
|
+
);
|
|
350
|
+
assert.ok(
|
|
351
|
+
src.includes("reapplyThinkingLevel(pi, autoModeStartThinkingLevel)"),
|
|
352
|
+
"selectAndApplyModel should re-apply captured thinking level after model changes",
|
|
353
|
+
);
|
|
354
|
+
});
|
|
355
|
+
|
|
344
356
|
test("resolveModelId: anthropic wins over claude-code when session provider is not claude-code", () => {
|
|
345
357
|
const availableModels = [
|
|
346
358
|
{ id: "claude-sonnet-4-6", provider: "claude-code" },
|
|
@@ -52,9 +52,7 @@ test("bootstrapAutoSession checks manual session override before preferences", (
|
|
|
52
52
|
"manual override and preference fallback must be resolved before building startModelSnapshot",
|
|
53
53
|
);
|
|
54
54
|
|
|
55
|
-
//
|
|
56
|
-
// sources so PREFERENCES.md continues to win over a stale settings.json
|
|
57
|
-
// default for built-in providers.
|
|
55
|
+
// Preferred model should still be part of fallback resolution.
|
|
58
56
|
const snapshotBlock = source.slice(snapshotIdx, snapshotIdx + 400);
|
|
59
57
|
assert.ok(
|
|
60
58
|
snapshotBlock.includes("validatedPreferredModel") || snapshotBlock.includes("preferredModel"),
|
|
@@ -62,6 +60,22 @@ test("bootstrapAutoSession checks manual session override before preferences", (
|
|
|
62
60
|
);
|
|
63
61
|
});
|
|
64
62
|
|
|
63
|
+
test("bootstrapAutoSession prioritizes current session model over PREFERENCES.md default", () => {
|
|
64
|
+
const snapshotIdx = source.indexOf("const startModelSnapshot = manualSessionOverride");
|
|
65
|
+
assert.ok(snapshotIdx > -1, "auto-start.ts should build startModelSnapshot");
|
|
66
|
+
|
|
67
|
+
const snapshotBlock = source.slice(snapshotIdx, snapshotIdx + 500);
|
|
68
|
+
const currentIdx = snapshotBlock.indexOf("currentSessionModel");
|
|
69
|
+
const preferredIdx = snapshotBlock.indexOf("validatedPreferredModel");
|
|
70
|
+
|
|
71
|
+
assert.ok(currentIdx > -1, "startModelSnapshot should include currentSessionModel");
|
|
72
|
+
assert.ok(preferredIdx > -1, "startModelSnapshot should include validatedPreferredModel");
|
|
73
|
+
assert.ok(
|
|
74
|
+
currentIdx < preferredIdx,
|
|
75
|
+
"startModelSnapshot should prefer currentSessionModel before validatedPreferredModel",
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
65
79
|
test("bootstrapAutoSession prefers session model over PREFERENCES.md when provider is custom (#4122)", () => {
|
|
66
80
|
// Custom providers (Ollama, vLLM, OpenAI-compatible proxies) live in
|
|
67
81
|
// ~/.gsd/agent/models.json, not PREFERENCES.md. When the user picks one
|
|
@@ -111,3 +125,19 @@ test("bootstrapAutoSession validates preferred model against live registry auth
|
|
|
111
125
|
const warningIdx = source.indexOf("is not configured; falling back to session default");
|
|
112
126
|
assert.ok(warningIdx > -1, "auto-start.ts should warn when preferred model is unconfigured");
|
|
113
127
|
});
|
|
128
|
+
|
|
129
|
+
test("bootstrapAutoSession snapshots and persists thinking level for auto-mode lifecycle", () => {
|
|
130
|
+
const captureIdx = source.indexOf("const startThinkingSnapshot = pi.getThinkingLevel()");
|
|
131
|
+
assert.ok(captureIdx > -1, "auto-start.ts should snapshot thinking level at bootstrap start");
|
|
132
|
+
|
|
133
|
+
const originalThinkingIdx = source.indexOf("s.originalThinkingLevel = startThinkingSnapshot ?? null");
|
|
134
|
+
assert.ok(originalThinkingIdx > -1, "auto-start.ts should store originalThinkingLevel from snapshot");
|
|
135
|
+
|
|
136
|
+
const autoThinkingIdx = source.indexOf("s.autoModeStartThinkingLevel = startThinkingSnapshot ?? null");
|
|
137
|
+
assert.ok(autoThinkingIdx > -1, "auto-start.ts should store autoModeStartThinkingLevel from snapshot");
|
|
138
|
+
|
|
139
|
+
assert.ok(
|
|
140
|
+
captureIdx < originalThinkingIdx && captureIdx < autoThinkingIdx,
|
|
141
|
+
"thinking snapshot must be captured before session state assignment",
|
|
142
|
+
);
|
|
143
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
const autoSrc = readFileSync(join(import.meta.dirname, "..", "auto.ts"), "utf-8");
|
|
7
|
+
const phasesSrc = readFileSync(join(import.meta.dirname, "..", "auto", "phases.ts"), "utf-8");
|
|
8
|
+
|
|
9
|
+
test("stopAuto restores original thinking level", () => {
|
|
10
|
+
assert.ok(
|
|
11
|
+
autoSrc.includes("if (pi && s.originalThinkingLevel)"),
|
|
12
|
+
"auto.ts should conditionally restore original thinking level in stopAuto",
|
|
13
|
+
);
|
|
14
|
+
assert.ok(
|
|
15
|
+
autoSrc.includes("pi.setThinkingLevel(s.originalThinkingLevel)"),
|
|
16
|
+
"auto.ts should call pi.setThinkingLevel with originalThinkingLevel",
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("runUnitPhase threads captured thinking level into selectAndApplyModel", () => {
|
|
21
|
+
const callIdx = phasesSrc.indexOf("deps.selectAndApplyModel(");
|
|
22
|
+
assert.ok(callIdx > -1, "phases.ts should call selectAndApplyModel");
|
|
23
|
+
const callBlock = phasesSrc.slice(callIdx, callIdx + 600);
|
|
24
|
+
assert.ok(
|
|
25
|
+
callBlock.includes("s.autoModeStartThinkingLevel"),
|
|
26
|
+
"runUnitPhase should pass autoModeStartThinkingLevel to selectAndApplyModel",
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("hook model override preserves captured thinking level", () => {
|
|
31
|
+
const hookIdx = phasesSrc.indexOf("const hookModelOverride = sidecarItem?.model ?? iterData.hookModelOverride;");
|
|
32
|
+
assert.ok(hookIdx > -1, "phases.ts should include hook model override handling");
|
|
33
|
+
const hookBlock = phasesSrc.slice(hookIdx, hookIdx + 600);
|
|
34
|
+
assert.ok(
|
|
35
|
+
hookBlock.includes("pi.setThinkingLevel(s.autoModeStartThinkingLevel)"),
|
|
36
|
+
"hook model override should re-apply captured thinking level after setModel",
|
|
37
|
+
);
|
|
38
|
+
});
|
|
@@ -21,9 +21,9 @@ test("tierOrdinal returns correct ordering", () => {
|
|
|
21
21
|
|
|
22
22
|
// ─── Unit Type Classification ────────────────────────────────────────────────
|
|
23
23
|
|
|
24
|
-
test("complete-slice classifies as
|
|
24
|
+
test("complete-slice classifies as standard", () => {
|
|
25
25
|
const result = classifyUnitComplexity("complete-slice", "M001/S01", "/tmp/fake");
|
|
26
|
-
assert.equal(result.tier, "
|
|
26
|
+
assert.equal(result.tier, "standard");
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
test("run-uat classifies as light", () => {
|
|
@@ -145,7 +145,7 @@ test("budget pressure at 90% downgrades standard to light", () => {
|
|
|
145
145
|
assert.equal(result.downgraded, true);
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
-
test("budget pressure at 90% downgrades
|
|
148
|
+
test("budget pressure at 90% downgrades complete-slice standard to light", () => {
|
|
149
149
|
const result = classifyUnitComplexity("complete-slice", "M001/S01", "/tmp/fake", 0.95);
|
|
150
150
|
assert.equal(result.tier, "light");
|
|
151
151
|
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { resolveExtensionDirFromCandidates } from "../prompt-loader.ts";
|
|
6
|
+
|
|
7
|
+
function makeExists(paths: Set<string>): (path: string) => boolean {
|
|
8
|
+
return (path: string) => paths.has(path);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
test("resolveExtensionDirFromCandidates prefers user-local dir when both trees are valid", () => {
|
|
12
|
+
const moduleDir = "/npm/global/gsd";
|
|
13
|
+
const agentDir = "/home/user/.gsd/agent/extensions/gsd";
|
|
14
|
+
const paths = new Set<string>([
|
|
15
|
+
join(moduleDir, "prompts"),
|
|
16
|
+
join(moduleDir, "templates", "task-summary.md"),
|
|
17
|
+
join(agentDir, "prompts"),
|
|
18
|
+
join(agentDir, "templates", "task-summary.md"),
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const resolved = resolveExtensionDirFromCandidates(moduleDir, agentDir, makeExists(paths));
|
|
22
|
+
assert.equal(resolved, agentDir);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("resolveExtensionDirFromCandidates rejects module dir missing task-summary template", () => {
|
|
26
|
+
const moduleDir = "/npm/global/gsd";
|
|
27
|
+
const agentDir = "/home/user/.gsd/agent/extensions/gsd";
|
|
28
|
+
const paths = new Set<string>([
|
|
29
|
+
join(moduleDir, "prompts"),
|
|
30
|
+
// Missing module templates/task-summary.md on purpose.
|
|
31
|
+
join(agentDir, "prompts"),
|
|
32
|
+
join(agentDir, "templates", "task-summary.md"),
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const resolved = resolveExtensionDirFromCandidates(moduleDir, agentDir, makeExists(paths));
|
|
36
|
+
assert.equal(resolved, agentDir);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("resolveExtensionDirFromCandidates falls back to prompts-only dir when neither tree is fully valid", () => {
|
|
40
|
+
const moduleDir = "/npm/global/gsd";
|
|
41
|
+
const agentDir = "/home/user/.gsd/agent/extensions/gsd";
|
|
42
|
+
const paths = new Set<string>([
|
|
43
|
+
join(moduleDir, "prompts"),
|
|
44
|
+
// Neither side has templates/task-summary.md.
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
const resolved = resolveExtensionDirFromCandidates(moduleDir, agentDir, makeExists(paths));
|
|
48
|
+
assert.equal(resolved, moduleDir);
|
|
49
|
+
});
|
|
File without changes
|
|
File without changes
|