gsd-pi 2.72.0-dev.3118184 → 2.72.0-dev.4f3264a
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/async-jobs/await-tool.js +4 -7
- package/dist/resources/extensions/async-jobs/job-manager.js +3 -28
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +26 -27
- package/dist/resources/extensions/gsd/auto/loop.js +1 -84
- package/dist/resources/extensions/gsd/auto-post-unit.js +0 -6
- package/dist/resources/extensions/gsd/auto.js +19 -25
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -9
- package/dist/resources/extensions/gsd/commands-handlers.js +1 -4
- package/dist/resources/extensions/gsd/context-injector.js +1 -1
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +7 -3
- package/dist/resources/extensions/gsd/gsd-db.js +5 -47
- package/dist/resources/extensions/gsd/key-manager.js +0 -2
- package/dist/resources/extensions/gsd/preferences-skills.js +34 -2
- package/dist/resources/extensions/gsd/preferences-types.js +0 -15
- package/dist/resources/extensions/gsd/preferences.js +3 -16
- package/dist/resources/extensions/gsd/prompt-loader.js +1 -4
- package/dist/resources/extensions/gsd/state.js +1 -21
- package/dist/resources/extensions/gsd/write-intercept.js +1 -10
- package/dist/resources/extensions/ollama/index.js +5 -4
- package/dist/resources/extensions/ollama/ollama-client.js +6 -35
- package/dist/resources/extensions/ollama/ollama-discovery.js +6 -32
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- 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/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- 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/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +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/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +3 -3
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +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/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
- package/dist/web/standalone/.next/server/chunks/2331.js +16 -16
- package/dist/web/standalone/.next/server/chunks/4741.js +12 -12
- package/dist/web/standalone/.next/server/chunks/5822.js +2 -2
- package/dist/web/standalone/.next/server/chunks/63.js +8 -8
- package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
- package/dist/web/standalone/.next/server/functions-config-manifest.json +9 -0
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +2 -29
- package/dist/web/standalone/.next/server/middleware.js +12 -4
- 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/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/env-api-keys.js +0 -1
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.custom.d.ts +0 -105
- package/packages/pi-ai/dist/models.custom.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.custom.js +0 -97
- package/packages/pi-ai/dist/models.custom.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +140 -648
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +364 -861
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.test.js +0 -105
- package/packages/pi-ai/dist/models.test.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +1 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/env-api-keys.ts +0 -1
- package/packages/pi-ai/src/models.custom.ts +0 -98
- package/packages/pi-ai/src/models.generated.ts +364 -861
- package/packages/pi-ai/src/models.test.ts +0 -135
- package/packages/pi-ai/src/types.ts +0 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +0 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/src/core/model-resolver.ts +0 -1
- package/src/resources/extensions/async-jobs/await-tool.test.ts +7 -40
- package/src/resources/extensions/async-jobs/await-tool.ts +4 -7
- package/src/resources/extensions/async-jobs/job-manager.ts +3 -33
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +26 -27
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +2 -20
- package/src/resources/extensions/gsd/auto/loop.ts +1 -89
- package/src/resources/extensions/gsd/auto-post-unit.ts +0 -7
- package/src/resources/extensions/gsd/auto.ts +20 -25
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +10 -8
- package/src/resources/extensions/gsd/commands-handlers.ts +1 -5
- package/src/resources/extensions/gsd/context-injector.ts +1 -1
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +8 -4
- package/src/resources/extensions/gsd/gsd-db.ts +5 -52
- package/src/resources/extensions/gsd/key-manager.ts +0 -2
- package/src/resources/extensions/gsd/preferences-skills.ts +36 -2
- package/src/resources/extensions/gsd/preferences-types.ts +0 -16
- package/src/resources/extensions/gsd/preferences.ts +6 -19
- package/src/resources/extensions/gsd/prompt-loader.ts +1 -6
- package/src/resources/extensions/gsd/state.ts +0 -20
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +0 -74
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +0 -63
- package/src/resources/extensions/gsd/tests/preferences.test.ts +0 -53
- package/src/resources/extensions/gsd/write-intercept.ts +1 -10
- package/src/resources/extensions/ollama/index.ts +5 -4
- package/src/resources/extensions/ollama/ollama-client.ts +6 -35
- package/src/resources/extensions/ollama/ollama-discovery.ts +6 -37
- package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -54
- package/dist/resources/extensions/gsd/definition-io.js +0 -15
- package/dist/web/standalone/.next/server/edge-runtime-webpack.js +0 -2
- package/packages/pi-ai/dist/models.generated.test.d.ts +0 -2
- package/packages/pi-ai/dist/models.generated.test.d.ts.map +0 -1
- package/packages/pi-ai/dist/models.generated.test.js +0 -334
- package/packages/pi-ai/dist/models.generated.test.js.map +0 -1
- package/packages/pi-ai/src/models.generated.test.ts +0 -373
- package/src/resources/extensions/gsd/definition-io.ts +0 -18
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +0 -27
- package/src/resources/extensions/gsd/tests/block-db-writes.test.ts +0 -63
- package/src/resources/extensions/gsd/tests/definition-io.test.ts +0 -57
- package/src/resources/extensions/gsd/tests/doctor-heal-fixable-warnings.test.ts +0 -14
- package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +0 -104
- package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +0 -54
- package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +0 -34
- package/src/resources/extensions/gsd/tests/preferences-formatting.test.ts +0 -87
- package/src/resources/extensions/gsd/tests/prompt-loader-working-directory.test.ts +0 -19
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +0 -97
- package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +0 -41
- /package/dist/web/standalone/.next/static/{NzO79SOz9jHX-VY5-0t2O → vr6Pbde48w4rMUplqDdh_}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{NzO79SOz9jHX-VY5-0t2O → vr6Pbde48w4rMUplqDdh_}/_ssgManifest.js +0 -0
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
import { describe, it } from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { MODELS } from "./models.generated.js";
|
|
4
|
-
import { getModel, getModels, getProviders } from "./models.js";
|
|
5
|
-
|
|
6
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
7
|
-
// Regression: qwen/qwen3.6-plus missing from OpenRouter (issue #3582)
|
|
8
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
9
|
-
|
|
10
|
-
describe("regression #3582 — qwen/qwen3.6-plus available via openrouter", () => {
|
|
11
|
-
it("qwen/qwen3.6-plus exists in MODELS['openrouter']", () => {
|
|
12
|
-
const model = MODELS["openrouter"]["qwen/qwen3.6-plus" as keyof (typeof MODELS)["openrouter"]];
|
|
13
|
-
assert.ok(model, "qwen/qwen3.6-plus must be present in MODELS.openrouter");
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("qwen/qwen3.6-plus is accessible via getModel()", () => {
|
|
17
|
-
const model = getModel("openrouter", "qwen/qwen3.6-plus" as any);
|
|
18
|
-
assert.ok(model, "getModel('openrouter', 'qwen/qwen3.6-plus') must return a model");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("qwen/qwen3.6-plus has id matching its registry key", () => {
|
|
22
|
-
const model = getModel("openrouter", "qwen/qwen3.6-plus" as any);
|
|
23
|
-
assert.equal(model.id, "qwen/qwen3.6-plus");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("qwen/qwen3.6-plus has provider set to openrouter", () => {
|
|
27
|
-
const model = getModel("openrouter", "qwen/qwen3.6-plus" as any);
|
|
28
|
-
assert.equal(model.provider, "openrouter");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("qwen/qwen3.6-plus has reasoning enabled", () => {
|
|
32
|
-
const model = getModel("openrouter", "qwen/qwen3.6-plus" as any);
|
|
33
|
-
assert.equal(model.reasoning, true, "Qwen3.6 Plus is a reasoning model");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("qwen/qwen3.6-plus has 1M context window", () => {
|
|
37
|
-
const model = getModel("openrouter", "qwen/qwen3.6-plus" as any);
|
|
38
|
-
assert.equal(model.contextWindow, 1_000_000);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
43
|
-
// Regression: z-ai/glm-5.1 missing from OpenRouter (issue #4069)
|
|
44
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
45
|
-
|
|
46
|
-
describe("regression #4069 — z-ai/glm-5.1 available via openrouter", () => {
|
|
47
|
-
it("z-ai/glm-5.1 exists in MODELS['openrouter']", () => {
|
|
48
|
-
const model = MODELS["openrouter"]["z-ai/glm-5.1" as keyof (typeof MODELS)["openrouter"]];
|
|
49
|
-
assert.ok(model, "z-ai/glm-5.1 must be present in MODELS.openrouter");
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("z-ai/glm-5.1 is accessible via getModel()", () => {
|
|
53
|
-
const model = getModel("openrouter", "z-ai/glm-5.1" as any);
|
|
54
|
-
assert.ok(model, "getModel('openrouter', 'z-ai/glm-5.1') must return a model");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("z-ai/glm-5.1 has id matching its registry key", () => {
|
|
58
|
-
const model = getModel("openrouter", "z-ai/glm-5.1" as any);
|
|
59
|
-
assert.equal(model.id, "z-ai/glm-5.1");
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("z-ai/glm-5.1 has provider set to openrouter", () => {
|
|
63
|
-
const model = getModel("openrouter", "z-ai/glm-5.1" as any);
|
|
64
|
-
assert.equal(model.provider, "openrouter");
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("z-ai/glm-5.1 has a positive context window", () => {
|
|
68
|
-
const model = getModel("openrouter", "z-ai/glm-5.1" as any);
|
|
69
|
-
assert.ok(model.contextWindow > 0);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("z-ai/glm-5.1 uses the OpenRouter base URL", () => {
|
|
73
|
-
const model = getModel("openrouter", "z-ai/glm-5.1" as any);
|
|
74
|
-
assert.equal(model.baseUrl, "https://openrouter.ai/api/v1");
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
79
|
-
// Structural invariants — every model in MODELS must be well-formed
|
|
80
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
81
|
-
|
|
82
|
-
describe("MODELS structural invariants", () => {
|
|
83
|
-
type ModelEntry = { providerKey: string; modelKey: string; model: Record<string, unknown> };
|
|
84
|
-
|
|
85
|
-
function allModels(): ModelEntry[] {
|
|
86
|
-
const entries: ModelEntry[] = [];
|
|
87
|
-
for (const [providerKey, providerModels] of Object.entries(MODELS)) {
|
|
88
|
-
for (const [modelKey, model] of Object.entries(providerModels)) {
|
|
89
|
-
entries.push({ providerKey, modelKey, model: model as Record<string, unknown> });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return entries;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
it("every model's id field matches its key in MODELS", () => {
|
|
96
|
-
const mismatches: string[] = [];
|
|
97
|
-
for (const { providerKey, modelKey, model } of allModels()) {
|
|
98
|
-
if (model["id"] !== modelKey) {
|
|
99
|
-
mismatches.push(`${providerKey}/${modelKey}: id="${model["id"]}"`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
assert.deepEqual(mismatches, [], `Models where 'id' doesn't match registry key:\n ${mismatches.join("\n ")}`);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("every model's provider field matches its parent provider key", () => {
|
|
106
|
-
const mismatches: string[] = [];
|
|
107
|
-
for (const { providerKey, modelKey, model } of allModels()) {
|
|
108
|
-
if (model["provider"] !== providerKey) {
|
|
109
|
-
mismatches.push(`${providerKey}/${modelKey}: provider="${model["provider"]}"`);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
assert.deepEqual(mismatches, [], `Models where 'provider' doesn't match parent key:\n ${mismatches.join("\n ")}`);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("every model has a non-empty string name", () => {
|
|
116
|
-
const invalid: string[] = [];
|
|
117
|
-
for (const { providerKey, modelKey, model } of allModels()) {
|
|
118
|
-
if (typeof model["name"] !== "string" || model["name"].trim() === "") {
|
|
119
|
-
invalid.push(`${providerKey}/${modelKey}`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
assert.deepEqual(invalid, [], `Models with missing or empty name:\n ${invalid.join("\n ")}`);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it("every model has a non-empty string api", () => {
|
|
126
|
-
const invalid: string[] = [];
|
|
127
|
-
for (const { providerKey, modelKey, model } of allModels()) {
|
|
128
|
-
if (typeof model["api"] !== "string" || model["api"].trim() === "") {
|
|
129
|
-
invalid.push(`${providerKey}/${modelKey}`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
assert.deepEqual(invalid, [], `Models with missing or empty api:\n ${invalid.join("\n ")}`);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("every model's baseUrl starts with https:// (or is empty for azure-openai-responses)", () => {
|
|
136
|
-
const invalid: string[] = [];
|
|
137
|
-
for (const { providerKey, modelKey, model } of allModels()) {
|
|
138
|
-
if (providerKey === "azure-openai-responses") continue;
|
|
139
|
-
const url = model["baseUrl"];
|
|
140
|
-
if (typeof url !== "string" || !url.startsWith("https://")) {
|
|
141
|
-
invalid.push(`${providerKey}/${modelKey}: baseUrl="${url}"`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
assert.deepEqual(invalid, [], `Models with missing or non-HTTPS baseUrl:\n ${invalid.join("\n ")}`);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("azure-openai-responses models have an empty baseUrl (runtime-configured)", () => {
|
|
148
|
-
const models = getModels("azure-openai-responses");
|
|
149
|
-
assert.ok(models.length > 0, "azure-openai-responses must have at least one model");
|
|
150
|
-
for (const model of models) {
|
|
151
|
-
assert.equal(model.baseUrl, "", `azure-openai-responses/${model.id} should have empty baseUrl`);
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it("every model has a boolean reasoning field", () => {
|
|
156
|
-
const invalid: string[] = [];
|
|
157
|
-
for (const { providerKey, modelKey, model } of allModels()) {
|
|
158
|
-
if (typeof model["reasoning"] !== "boolean") {
|
|
159
|
-
invalid.push(`${providerKey}/${modelKey}: reasoning=${model["reasoning"]}`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
assert.deepEqual(invalid, [], `Models with non-boolean reasoning:\n ${invalid.join("\n ")}`);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it("every model has a non-empty input array", () => {
|
|
166
|
-
const invalid: string[] = [];
|
|
167
|
-
for (const { providerKey, modelKey, model } of allModels()) {
|
|
168
|
-
const input = model["input"];
|
|
169
|
-
if (!Array.isArray(input) || input.length === 0) {
|
|
170
|
-
invalid.push(`${providerKey}/${modelKey}`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
assert.deepEqual(invalid, [], `Models with missing or empty input array:\n ${invalid.join("\n ")}`);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("every model has a positive contextWindow", () => {
|
|
177
|
-
const invalid: string[] = [];
|
|
178
|
-
for (const { providerKey, modelKey, model } of allModels()) {
|
|
179
|
-
const cw = model["contextWindow"];
|
|
180
|
-
if (typeof cw !== "number" || cw <= 0 || !Number.isFinite(cw)) {
|
|
181
|
-
invalid.push(`${providerKey}/${modelKey}: contextWindow=${cw}`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
assert.deepEqual(invalid, [], `Models with invalid contextWindow:\n ${invalid.join("\n ")}`);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it("every model has a positive maxTokens", () => {
|
|
188
|
-
const invalid: string[] = [];
|
|
189
|
-
for (const { providerKey, modelKey, model } of allModels()) {
|
|
190
|
-
const mt = model["maxTokens"];
|
|
191
|
-
if (typeof mt !== "number" || mt <= 0 || !Number.isFinite(mt)) {
|
|
192
|
-
invalid.push(`${providerKey}/${modelKey}: maxTokens=${mt}`);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
assert.deepEqual(invalid, [], `Models with invalid maxTokens:\n ${invalid.join("\n ")}`);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("every model's maxTokens does not exceed contextWindow", () => {
|
|
199
|
-
const knownExceptions = new Set([
|
|
200
|
-
"openrouter/meta-llama/llama-3-8b-instruct",
|
|
201
|
-
"openrouter/nex-agi/deepseek-v3.1-nex-n1",
|
|
202
|
-
"openrouter/openai/gpt-3.5-turbo-0613",
|
|
203
|
-
"openrouter/z-ai/glm-5",
|
|
204
|
-
]);
|
|
205
|
-
|
|
206
|
-
const invalid: string[] = [];
|
|
207
|
-
for (const { providerKey, modelKey, model } of allModels()) {
|
|
208
|
-
if (knownExceptions.has(`${providerKey}/${modelKey}`)) continue;
|
|
209
|
-
const cw = model["contextWindow"] as number;
|
|
210
|
-
const mt = model["maxTokens"] as number;
|
|
211
|
-
if (typeof cw === "number" && typeof mt === "number" && mt > cw) {
|
|
212
|
-
invalid.push(`${providerKey}/${modelKey}: maxTokens(${mt}) > contextWindow(${cw})`);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
assert.deepEqual(invalid, [], `Models where maxTokens exceeds contextWindow:\n ${invalid.join("\n ")}`);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it("every model has a cost object with non-negative numeric fields", () => {
|
|
219
|
-
const knownNegativeCostModels = new Set([
|
|
220
|
-
"openrouter/openrouter/auto",
|
|
221
|
-
]);
|
|
222
|
-
|
|
223
|
-
const invalid: string[] = [];
|
|
224
|
-
for (const { providerKey, modelKey, model } of allModels()) {
|
|
225
|
-
if (knownNegativeCostModels.has(`${providerKey}/${modelKey}`)) continue;
|
|
226
|
-
const cost = model["cost"] as Record<string, unknown> | undefined;
|
|
227
|
-
if (!cost || typeof cost !== "object") {
|
|
228
|
-
invalid.push(`${providerKey}/${modelKey}: missing cost object`);
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
for (const field of ["input", "output", "cacheRead", "cacheWrite"] as const) {
|
|
232
|
-
const val = cost[field];
|
|
233
|
-
if (typeof val !== "number" || val < 0 || !Number.isFinite(val)) {
|
|
234
|
-
invalid.push(`${providerKey}/${modelKey}: cost.${field}=${val}`);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
assert.deepEqual(invalid, [], `Models with invalid cost fields:\n ${invalid.join("\n ")}`);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it("no provider has duplicate model IDs", () => {
|
|
242
|
-
const duplicates: string[] = [];
|
|
243
|
-
for (const [providerKey, providerModels] of Object.entries(MODELS)) {
|
|
244
|
-
const ids = Object.values(providerModels).map((m) => (m as Record<string, unknown>)["id"] as string);
|
|
245
|
-
const seen = new Set<string>();
|
|
246
|
-
for (const id of ids) {
|
|
247
|
-
if (seen.has(id)) duplicates.push(`${providerKey}/${id}`);
|
|
248
|
-
seen.add(id);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
assert.deepEqual(duplicates, [], `Duplicate model IDs within a provider:\n ${duplicates.join("\n ")}`);
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
256
|
-
// Registry shape
|
|
257
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
258
|
-
|
|
259
|
-
describe("MODELS registry shape", () => {
|
|
260
|
-
it("has exactly 23 providers", () => {
|
|
261
|
-
const count = Object.keys(MODELS).length;
|
|
262
|
-
assert.equal(count, 23, `Expected 23 providers, got ${count}: ${Object.keys(MODELS).join(", ")}`);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it("has at least 200 models in total (sanity check)", () => {
|
|
266
|
-
let total = 0;
|
|
267
|
-
for (const providerModels of Object.values(MODELS)) {
|
|
268
|
-
total += Object.keys(providerModels).length;
|
|
269
|
-
}
|
|
270
|
-
assert.ok(total >= 200, `Registry has only ${total} models — unexpectedly small`);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it("all 23 expected providers are present", () => {
|
|
274
|
-
const expected = [
|
|
275
|
-
"amazon-bedrock",
|
|
276
|
-
"anthropic",
|
|
277
|
-
"azure-openai-responses",
|
|
278
|
-
"cerebras",
|
|
279
|
-
"github-copilot",
|
|
280
|
-
"google",
|
|
281
|
-
"google-antigravity",
|
|
282
|
-
"google-gemini-cli",
|
|
283
|
-
"google-vertex",
|
|
284
|
-
"groq",
|
|
285
|
-
"huggingface",
|
|
286
|
-
"kimi-coding",
|
|
287
|
-
"minimax",
|
|
288
|
-
"minimax-cn",
|
|
289
|
-
"mistral",
|
|
290
|
-
"openai",
|
|
291
|
-
"openai-codex",
|
|
292
|
-
"opencode",
|
|
293
|
-
"opencode-go",
|
|
294
|
-
"openrouter",
|
|
295
|
-
"vercel-ai-gateway",
|
|
296
|
-
"xai",
|
|
297
|
-
"zai",
|
|
298
|
-
];
|
|
299
|
-
const actual = Object.keys(MODELS).sort();
|
|
300
|
-
assert.deepEqual(actual, expected.sort());
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it("getProviders() returns all generated providers", () => {
|
|
304
|
-
const providers = getProviders();
|
|
305
|
-
for (const p of Object.keys(MODELS)) {
|
|
306
|
-
assert.ok(providers.includes(p as any), `getProviders() missing generated provider: ${p}`);
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
312
|
-
// Removed models must not exist
|
|
313
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
314
|
-
|
|
315
|
-
describe("removed models are absent from the registry", () => {
|
|
316
|
-
const removedModels: Array<{ provider: string; id: string }> = [
|
|
317
|
-
{ provider: "openrouter", id: "anthropic/claude-3.5-sonnet" },
|
|
318
|
-
{ provider: "openrouter", id: "anthropic/claude-3.5-sonnet-20240620" },
|
|
319
|
-
{ provider: "openrouter", id: "mistralai/mistral-small-24b-instruct-2501" },
|
|
320
|
-
{ provider: "openrouter", id: "mistralai/mistral-small-3.1-24b-instruct:free" },
|
|
321
|
-
{ provider: "openrouter", id: "qwen/qwen3-4b:free" },
|
|
322
|
-
{ provider: "openrouter", id: "stepfun/step-3.5-flash:free" },
|
|
323
|
-
{ provider: "openrouter", id: "x-ai/grok-4.20-beta" },
|
|
324
|
-
{ provider: "openrouter", id: "arcee-ai/trinity-mini:free" },
|
|
325
|
-
{ provider: "openrouter", id: "google/gemini-3-pro-preview" },
|
|
326
|
-
{ provider: "openrouter", id: "kwaipilot/kat-coder-pro" },
|
|
327
|
-
{ provider: "openrouter", id: "meituan/longcat-flash-thinking" },
|
|
328
|
-
{ provider: "vercel-ai-gateway", id: "xai/grok-2-vision" },
|
|
329
|
-
{ provider: "anthropic", id: "claude-3-7-sonnet-latest" },
|
|
330
|
-
];
|
|
331
|
-
|
|
332
|
-
for (const { provider, id } of removedModels) {
|
|
333
|
-
it(`${provider}/${id} has been removed`, () => {
|
|
334
|
-
const model = getModel(provider as any, id as any);
|
|
335
|
-
assert.equal(model, undefined, `${provider}/${id} should be removed but is still present`);
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
341
|
-
// Spot-checks for notable models added in this regeneration
|
|
342
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
343
|
-
|
|
344
|
-
describe("spot-checks for models added in this regeneration", () => {
|
|
345
|
-
const newModels: Array<{ provider: string; id: string; reasoning?: boolean }> = [
|
|
346
|
-
{ provider: "openrouter", id: "z-ai/glm-5.1" },
|
|
347
|
-
{ provider: "openrouter", id: "z-ai/glm-5v-turbo" },
|
|
348
|
-
{ provider: "openrouter", id: "google/gemma-4-31b-it" },
|
|
349
|
-
{ provider: "openrouter", id: "google/gemma-4-26b-a4b-it" },
|
|
350
|
-
{ provider: "openrouter", id: "arcee-ai/trinity-large-thinking", reasoning: true },
|
|
351
|
-
{ provider: "openrouter", id: "openai/gpt-audio" },
|
|
352
|
-
{ provider: "openrouter", id: "anthropic/claude-opus-4.6-fast" },
|
|
353
|
-
{ provider: "openrouter", id: "qwen/qwen3.6-plus" },
|
|
354
|
-
{ provider: "groq", id: "groq/compound" },
|
|
355
|
-
{ provider: "groq", id: "groq/compound-mini" },
|
|
356
|
-
{ provider: "huggingface", id: "zai-org/GLM-5.1" },
|
|
357
|
-
{ provider: "openai", id: "gpt-5.3-chat-latest" },
|
|
358
|
-
{ provider: "mistral", id: "mistral-small-2603" },
|
|
359
|
-
{ provider: "zai", id: "glm-5.1" },
|
|
360
|
-
];
|
|
361
|
-
|
|
362
|
-
for (const { provider, id, reasoning } of newModels) {
|
|
363
|
-
it(`${provider}/${id} is present in the registry`, () => {
|
|
364
|
-
const model = getModel(provider as any, id as any);
|
|
365
|
-
assert.ok(model, `Expected ${provider}/${id} to be present after regeneration`);
|
|
366
|
-
assert.equal(model.id, id);
|
|
367
|
-
assert.equal(model.provider, provider);
|
|
368
|
-
if (reasoning !== undefined) {
|
|
369
|
-
assert.equal(model.reasoning, reasoning, `${id} reasoning should be ${reasoning}`);
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* definition-io.ts — Read frozen DEFINITION.yaml from a run directory.
|
|
3
|
-
*
|
|
4
|
-
* Extracted from custom-workflow-engine.ts to break the circular dependency
|
|
5
|
-
* between context-injector.ts and custom-workflow-engine.ts.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync } from "node:fs";
|
|
9
|
-
import { join } from "node:path";
|
|
10
|
-
import { parse } from "yaml";
|
|
11
|
-
import type { WorkflowDefinition } from "./definition-loader.js";
|
|
12
|
-
|
|
13
|
-
/** Read and parse the frozen DEFINITION.yaml from a run directory. */
|
|
14
|
-
export function readFrozenDefinition(runDir: string): WorkflowDefinition {
|
|
15
|
-
const defPath = join(runDir, "DEFINITION.yaml");
|
|
16
|
-
const raw = readFileSync(defPath, "utf-8");
|
|
17
|
-
return parse(raw, { schema: "core" }) as WorkflowDefinition;
|
|
18
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { test } from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const autoSource = readFileSync(join(__dirname, "..", "auto.ts"), "utf-8");
|
|
9
|
-
|
|
10
|
-
test("#3370: cleanupAfterLoopExit preserves paused auto badge after provider pause", () => {
|
|
11
|
-
const cleanupIdx = autoSource.indexOf("function cleanupAfterLoopExit");
|
|
12
|
-
assert.ok(cleanupIdx > -1, "auto.ts should define cleanupAfterLoopExit");
|
|
13
|
-
|
|
14
|
-
const dispatchIdx = autoSource.indexOf("export async function dispatchHookUnit", cleanupIdx);
|
|
15
|
-
assert.ok(dispatchIdx > cleanupIdx, "cleanupAfterLoopExit body should be bounded by the next export");
|
|
16
|
-
|
|
17
|
-
const cleanupBody = autoSource.slice(cleanupIdx, dispatchIdx);
|
|
18
|
-
const pausedGuardIdx = cleanupBody.indexOf("if (!s.paused) {");
|
|
19
|
-
const clearStatusIdx = cleanupBody.indexOf('ctx.ui.setStatus("gsd-auto", undefined);');
|
|
20
|
-
|
|
21
|
-
assert.ok(pausedGuardIdx > -1, "loop-exit cleanup must guard UI clearing when auto is paused");
|
|
22
|
-
assert.ok(clearStatusIdx > pausedGuardIdx, "status clearing must live behind the paused guard");
|
|
23
|
-
assert.ok(
|
|
24
|
-
autoSource.includes('ctx?.ui.setStatus("gsd-auto", "paused");'),
|
|
25
|
-
"pauseAuto must still set the paused badge for transient provider pauses",
|
|
26
|
-
);
|
|
27
|
-
});
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Regression test for #3674 — block direct writes to gsd.db
|
|
3
|
-
*
|
|
4
|
-
* When gsd_complete_task was unavailable, agents fell back to shell-based
|
|
5
|
-
* sqlite3 writes, corrupting the WAL-backed database. The fix extends
|
|
6
|
-
* write-intercept to block file writes and bash commands targeting gsd.db.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, test } from 'node:test';
|
|
10
|
-
import assert from 'node:assert/strict';
|
|
11
|
-
import { isBlockedStateFile, isBashWriteToStateFile } from '../write-intercept.ts';
|
|
12
|
-
|
|
13
|
-
describe('isBlockedStateFile blocks gsd.db paths (#3674)', () => {
|
|
14
|
-
test('blocks .gsd/gsd.db', () => {
|
|
15
|
-
assert.ok(isBlockedStateFile('/project/.gsd/gsd.db'));
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test('blocks .gsd/gsd.db-wal', () => {
|
|
19
|
-
assert.ok(isBlockedStateFile('/project/.gsd/gsd.db-wal'));
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('blocks .gsd/gsd.db-shm', () => {
|
|
23
|
-
assert.ok(isBlockedStateFile('/project/.gsd/gsd.db-shm'));
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('blocks resolved symlink path under .gsd/projects/', () => {
|
|
27
|
-
assert.ok(isBlockedStateFile('/home/user/.gsd/projects/myproj/gsd.db'));
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('still blocks STATE.md', () => {
|
|
31
|
-
assert.ok(isBlockedStateFile('/project/.gsd/STATE.md'));
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('does not block other .gsd files', () => {
|
|
35
|
-
assert.ok(!isBlockedStateFile('/project/.gsd/DECISIONS.md'));
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe('isBashWriteToStateFile blocks DB shell commands (#3674)', () => {
|
|
40
|
-
test('blocks sqlite3 targeting gsd.db', () => {
|
|
41
|
-
assert.ok(isBashWriteToStateFile('sqlite3 .gsd/gsd.db "INSERT INTO ..."'));
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('blocks better-sqlite3 targeting gsd.db', () => {
|
|
45
|
-
assert.ok(isBashWriteToStateFile('node -e "require(\'better-sqlite3\')(\'.gsd/gsd.db\')"'));
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('blocks shell redirect to gsd.db', () => {
|
|
49
|
-
assert.ok(isBashWriteToStateFile('echo data > .gsd/gsd.db'));
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('blocks cp to gsd.db', () => {
|
|
53
|
-
assert.ok(isBashWriteToStateFile('cp backup.db .gsd/gsd.db'));
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('blocks mv to gsd.db', () => {
|
|
57
|
-
assert.ok(isBashWriteToStateFile('mv temp.db .gsd/gsd.db'));
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('does not block reading gsd.db with cat', () => {
|
|
61
|
-
assert.ok(!isBashWriteToStateFile('cat .gsd/gsd.db'));
|
|
62
|
-
});
|
|
63
|
-
});
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* definition-io.ts — unit tests for readFrozenDefinition.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, test, beforeEach, afterEach } from "node:test";
|
|
6
|
-
import assert from "node:assert/strict";
|
|
7
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, realpathSync } from "node:fs";
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
import { tmpdir } from "node:os";
|
|
10
|
-
|
|
11
|
-
import { readFrozenDefinition } from "../definition-io.ts";
|
|
12
|
-
|
|
13
|
-
function createTmpDir(): string {
|
|
14
|
-
return realpathSync(mkdtempSync(join(tmpdir(), "gsd-defio-test-")));
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe("readFrozenDefinition", () => {
|
|
18
|
-
let runDir: string;
|
|
19
|
-
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
runDir = createTmpDir();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
afterEach(() => {
|
|
25
|
-
rmSync(runDir, { recursive: true, force: true });
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("parses a valid DEFINITION.yaml", () => {
|
|
29
|
-
const yaml = [
|
|
30
|
-
"version: 1",
|
|
31
|
-
"name: test-workflow",
|
|
32
|
-
"description: A test workflow",
|
|
33
|
-
"steps:",
|
|
34
|
-
" - id: step-1",
|
|
35
|
-
" prompt: do the thing",
|
|
36
|
-
].join("\n");
|
|
37
|
-
writeFileSync(join(runDir, "DEFINITION.yaml"), yaml, "utf-8");
|
|
38
|
-
|
|
39
|
-
const def = readFrozenDefinition(runDir);
|
|
40
|
-
assert.equal(def.version, 1);
|
|
41
|
-
assert.equal(def.name, "test-workflow");
|
|
42
|
-
assert.equal(def.description, "A test workflow");
|
|
43
|
-
assert.equal(def.steps.length, 1);
|
|
44
|
-
assert.equal(def.steps[0].id, "step-1");
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("throws when DEFINITION.yaml is missing", () => {
|
|
48
|
-
assert.throws(() => readFrozenDefinition(runDir), {
|
|
49
|
-
code: "ENOENT",
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("throws on malformed YAML", () => {
|
|
54
|
-
writeFileSync(join(runDir, "DEFINITION.yaml"), ": : : not valid yaml [", "utf-8");
|
|
55
|
-
assert.throws(() => readFrozenDefinition(runDir));
|
|
56
|
-
});
|
|
57
|
-
});
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { isDoctorHealActionable } from "../commands-handlers.js";
|
|
4
|
-
|
|
5
|
-
test("doctor heal actionable filter keeps fixable warnings and errors", () => {
|
|
6
|
-
assert.equal(isDoctorHealActionable({ fixable: true, severity: "warning" }), true);
|
|
7
|
-
assert.equal(isDoctorHealActionable({ fixable: true, severity: "error" }), true);
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
test("doctor heal actionable filter excludes info and non-fixable issues", () => {
|
|
11
|
-
assert.equal(isDoctorHealActionable({ fixable: true, severity: "info" }), false);
|
|
12
|
-
assert.equal(isDoctorHealActionable({ fixable: false, severity: "warning" }), false);
|
|
13
|
-
assert.equal(isDoctorHealActionable({ fixable: false, severity: "error" }), false);
|
|
14
|
-
});
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* false-degraded-mode-warning.test.ts — Regression tests for #3922.
|
|
3
|
-
*
|
|
4
|
-
* Before this fix, deriveState() logged a "DB unavailable — degraded mode"
|
|
5
|
-
* warning even when the DB simply hadn't been opened yet (e.g. during
|
|
6
|
-
* before_agent_start context injection). The fix introduces wasDbOpenAttempted()
|
|
7
|
-
* to distinguish "not yet initialized" from "genuinely unavailable."
|
|
8
|
-
*
|
|
9
|
-
* Two aspects:
|
|
10
|
-
* 1. gsd-db: wasDbOpenAttempted() tracks whether openDatabase() was ever called.
|
|
11
|
-
* 2. state: the degraded-mode warning is gated behind wasDbOpenAttempted().
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { describe, test } from "node:test";
|
|
15
|
-
import assert from "node:assert/strict";
|
|
16
|
-
import { readFileSync } from "node:fs";
|
|
17
|
-
import { dirname, join } from "node:path";
|
|
18
|
-
import { fileURLToPath } from "node:url";
|
|
19
|
-
import {
|
|
20
|
-
openDatabase,
|
|
21
|
-
closeDatabase,
|
|
22
|
-
isDbAvailable,
|
|
23
|
-
wasDbOpenAttempted,
|
|
24
|
-
} from "../gsd-db.ts";
|
|
25
|
-
|
|
26
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
|
-
const stateSource = readFileSync(join(__dirname, "..", "state.ts"), "utf-8");
|
|
28
|
-
|
|
29
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
30
|
-
// 1. gsd-db: wasDbOpenAttempted flag
|
|
31
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
32
|
-
|
|
33
|
-
describe("wasDbOpenAttempted (#3922)", () => {
|
|
34
|
-
|
|
35
|
-
test("wasDbOpenAttempted returns true after openDatabase is called", () => {
|
|
36
|
-
// By this point in the test suite, openDatabase may or may not have been
|
|
37
|
-
// called by other tests. So we call it explicitly and verify it returns true.
|
|
38
|
-
openDatabase(":memory:");
|
|
39
|
-
assert.strictEqual(wasDbOpenAttempted(), true,
|
|
40
|
-
"wasDbOpenAttempted should be true after openDatabase call");
|
|
41
|
-
closeDatabase();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("openDatabase sets the flag even if it fails on invalid path", () => {
|
|
45
|
-
// openDatabase with an unreachable path may fail, but the flag should
|
|
46
|
-
// still be set because the attempt was made.
|
|
47
|
-
try { openDatabase("/nonexistent/path/that/will/fail.db"); } catch { /* expected */ }
|
|
48
|
-
assert.strictEqual(wasDbOpenAttempted(), true,
|
|
49
|
-
"wasDbOpenAttempted should be true even after a failed open attempt");
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
54
|
-
// 2. state.ts: degraded-mode warning is gated behind wasDbOpenAttempted
|
|
55
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
56
|
-
|
|
57
|
-
describe("degraded-mode warning guard (#3922)", () => {
|
|
58
|
-
|
|
59
|
-
test("state.ts imports wasDbOpenAttempted from gsd-db", () => {
|
|
60
|
-
assert.ok(
|
|
61
|
-
stateSource.includes("wasDbOpenAttempted"),
|
|
62
|
-
"state.ts must import wasDbOpenAttempted to gate the degraded-mode warning",
|
|
63
|
-
);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test("degraded-mode warning is inside a wasDbOpenAttempted() guard", () => {
|
|
67
|
-
// Find the degraded-mode warning string
|
|
68
|
-
const warningStr = 'DB unavailable — using filesystem state derivation (degraded mode)';
|
|
69
|
-
const warningIdx = stateSource.indexOf(warningStr);
|
|
70
|
-
assert.ok(warningIdx > 0, "degraded-mode warning string must exist in state.ts");
|
|
71
|
-
|
|
72
|
-
// The wasDbOpenAttempted() check must appear BEFORE the warning,
|
|
73
|
-
// within the same else-branch (i.e. within a reasonable distance).
|
|
74
|
-
// Look backwards from the warning for the guard.
|
|
75
|
-
const searchWindow = stateSource.slice(Math.max(0, warningIdx - 300), warningIdx);
|
|
76
|
-
assert.ok(
|
|
77
|
-
searchWindow.includes("wasDbOpenAttempted()"),
|
|
78
|
-
"wasDbOpenAttempted() guard must appear shortly before the degraded-mode warning " +
|
|
79
|
-
"to prevent false warnings when DB has not been initialized yet",
|
|
80
|
-
);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("warning is NOT emitted unconditionally in the else branch", () => {
|
|
84
|
-
// The old code had `logWarning(...)` directly in the else branch.
|
|
85
|
-
// The fix wraps it in `if (wasDbOpenAttempted())`.
|
|
86
|
-
// Verify the logWarning call is inside a conditional, not bare.
|
|
87
|
-
const lines = stateSource.split("\n");
|
|
88
|
-
for (let i = 0; i < lines.length; i++) {
|
|
89
|
-
if (lines[i]!.includes("DB unavailable") && lines[i]!.includes("degraded mode")) {
|
|
90
|
-
// This line has the warning. Check that the preceding non-empty line
|
|
91
|
-
// contains an if-condition (wasDbOpenAttempted), not a bare else.
|
|
92
|
-
let prev = i - 1;
|
|
93
|
-
while (prev >= 0 && lines[prev]!.trim() === "") prev--;
|
|
94
|
-
const prevLine = lines[prev]!.trim();
|
|
95
|
-
assert.ok(
|
|
96
|
-
prevLine.includes("wasDbOpenAttempted"),
|
|
97
|
-
`Line ${i + 1} emits degraded-mode warning — preceding line ${prev + 1} must ` +
|
|
98
|
-
`contain wasDbOpenAttempted guard, but found: "${prevLine}"`,
|
|
99
|
-
);
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
});
|