@opengsd/gsd-pi 1.10.0 → 1.11.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.
Files changed (218) hide show
  1. package/README.md +10 -9
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto-model-selection.js +1 -0
  4. package/dist/resources/extensions/gsd/auto-start.js +1 -1
  5. package/dist/resources/extensions/gsd/auto-verification.js +25 -12
  6. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +4 -3
  7. package/dist/resources/extensions/gsd/db/engine.js +8 -2
  8. package/dist/resources/extensions/gsd/db-canonical-foundation-schema.js +120 -0
  9. package/dist/resources/extensions/gsd/db-migration-steps.js +4 -0
  10. package/dist/resources/extensions/gsd/docs/preferences-reference.md +12 -3
  11. package/dist/resources/extensions/gsd/doctor-engine-checks.js +16 -4
  12. package/dist/resources/extensions/gsd/model-cost-table.js +6 -3
  13. package/dist/resources/extensions/gsd/model-router.js +10 -5
  14. package/dist/resources/extensions/gsd/planning-subagent-registry.js +12 -4
  15. package/dist/resources/extensions/gsd/preferences-models.js +50 -10
  16. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  17. package/dist/resources/extensions/gsd/preferences-validation.js +41 -6
  18. package/dist/resources/extensions/gsd/preferences.js +7 -0
  19. package/dist/resources/extensions/gsd/templates/PREFERENCES.md +3 -0
  20. package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -1
  21. package/dist/resources/extensions/gsd/verification-evidence.js +5 -3
  22. package/dist/resources/extensions/ollama/ollama-chat-provider.js +2 -2
  23. package/dist/resources/extensions/subagent/index.js +1 -1
  24. package/dist/startup-model-validation.d.ts +1 -1
  25. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  26. package/dist/web/standalone/.next/BUILD_ID +1 -1
  27. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  28. package/dist/web/standalone/.next/build-manifest.json +2 -2
  29. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/index.html +1 -1
  46. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  53. package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
  54. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  56. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  57. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  58. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  59. package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
  60. package/integrations/hermes/pyproject.toml +1 -1
  61. package/package.json +7 -6
  62. package/packages/cloud-mcp-gateway/package.json +2 -2
  63. package/packages/contracts/dist/rpc.d.ts +1 -1
  64. package/packages/contracts/dist/rpc.d.ts.map +1 -1
  65. package/packages/contracts/dist/rpc.js +1 -1
  66. package/packages/contracts/dist/rpc.js.map +1 -1
  67. package/packages/contracts/dist/rpc.test.js +1 -1
  68. package/packages/contracts/dist/rpc.test.js.map +1 -1
  69. package/packages/contracts/package.json +1 -1
  70. package/packages/daemon/package.json +4 -4
  71. package/packages/gsd-agent-core/package.json +5 -5
  72. package/packages/gsd-agent-modes/dist/cli/args.js +2 -2
  73. package/packages/gsd-agent-modes/dist/cli/args.js.map +1 -1
  74. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  75. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +1 -0
  76. package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
  77. package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  78. package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.js +1 -0
  79. package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  80. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.d.ts.map +1 -1
  81. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.js +1 -0
  82. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
  83. package/packages/gsd-agent-modes/package.json +7 -7
  84. package/packages/gsd-cloud/README.md +12 -2
  85. package/packages/gsd-cloud/dist/background-cli.test.js +157 -80
  86. package/packages/gsd-cloud/dist/background-cli.test.js.map +1 -1
  87. package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
  88. package/packages/gsd-cloud/dist/cli.js +36 -12
  89. package/packages/gsd-cloud/dist/cli.js.map +1 -1
  90. package/packages/gsd-cloud/dist/cloud-runtime.d.ts +6 -1
  91. package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
  92. package/packages/gsd-cloud/dist/cloud-runtime.js +106 -24
  93. package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
  94. package/packages/gsd-cloud/dist/cloud-runtime.test.js +224 -59
  95. package/packages/gsd-cloud/dist/cloud-runtime.test.js.map +1 -1
  96. package/packages/gsd-cloud/dist/runtime-artifacts.d.ts +3 -0
  97. package/packages/gsd-cloud/dist/runtime-artifacts.d.ts.map +1 -0
  98. package/packages/gsd-cloud/dist/runtime-artifacts.js +36 -0
  99. package/packages/gsd-cloud/dist/runtime-artifacts.js.map +1 -0
  100. package/packages/gsd-cloud/dist/runtime-process.d.ts +5 -0
  101. package/packages/gsd-cloud/dist/runtime-process.d.ts.map +1 -1
  102. package/packages/gsd-cloud/dist/runtime-process.js +429 -61
  103. package/packages/gsd-cloud/dist/runtime-process.js.map +1 -1
  104. package/packages/gsd-cloud/dist/runtime-process.test.js +493 -47
  105. package/packages/gsd-cloud/dist/runtime-process.test.js.map +1 -1
  106. package/packages/gsd-cloud/dist/runtime-telemetry.d.ts +117 -0
  107. package/packages/gsd-cloud/dist/runtime-telemetry.d.ts.map +1 -0
  108. package/packages/gsd-cloud/dist/runtime-telemetry.js +291 -0
  109. package/packages/gsd-cloud/dist/runtime-telemetry.js.map +1 -0
  110. package/packages/gsd-cloud/dist/runtime-telemetry.test.d.ts +2 -0
  111. package/packages/gsd-cloud/dist/runtime-telemetry.test.d.ts.map +1 -0
  112. package/packages/gsd-cloud/dist/runtime-telemetry.test.js +434 -0
  113. package/packages/gsd-cloud/dist/runtime-telemetry.test.js.map +1 -0
  114. package/packages/gsd-cloud/package.json +2 -2
  115. package/packages/mcp-server/package.json +4 -4
  116. package/packages/native/package.json +1 -1
  117. package/packages/pi-agent-core/dist/types.d.ts +2 -2
  118. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  119. package/packages/pi-agent-core/dist/types.js.map +1 -1
  120. package/packages/pi-agent-core/package.json +1 -1
  121. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  122. package/packages/pi-ai/dist/models.generated.d.ts +113 -2
  123. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  124. package/packages/pi-ai/dist/models.generated.js +84 -9
  125. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  126. package/packages/pi-ai/dist/models.js +18 -6
  127. package/packages/pi-ai/dist/models.js.map +1 -1
  128. package/packages/pi-ai/dist/providers/amazon-bedrock.js +3 -2
  129. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  130. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts +1 -1
  131. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  132. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  133. package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
  134. package/packages/pi-ai/dist/providers/google.js.map +1 -1
  135. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +1 -1
  136. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  137. package/packages/pi-ai/dist/providers/openai-codex-responses.js +1 -1
  138. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  139. package/packages/pi-ai/dist/providers/openai-completions.d.ts +1 -1
  140. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  141. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  142. package/packages/pi-ai/dist/providers/openai-responses.d.ts +1 -1
  143. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  144. package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
  145. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  146. package/packages/pi-ai/dist/providers/simple-options.d.ts +1 -1
  147. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  148. package/packages/pi-ai/dist/providers/simple-options.js +1 -1
  149. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  150. package/packages/pi-ai/dist/types.d.ts +9 -1
  151. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  152. package/packages/pi-ai/dist/types.js.map +1 -1
  153. package/packages/pi-ai/package.json +1 -1
  154. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  155. package/packages/pi-coding-agent/dist/core/model-registry.js +1 -0
  156. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -1
  158. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -3
  160. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  161. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  162. package/packages/pi-coding-agent/dist/theme/theme.d.ts +1 -1
  163. package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
  164. package/packages/pi-coding-agent/dist/theme/theme.js +1 -0
  165. package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
  166. package/packages/pi-coding-agent/package.json +7 -7
  167. package/packages/pi-tui/package.json +2 -2
  168. package/packages/rpc-client/package.json +2 -2
  169. package/pkg/dist/theme/theme.d.ts +1 -1
  170. package/pkg/dist/theme/theme.d.ts.map +1 -1
  171. package/pkg/dist/theme/theme.js +1 -0
  172. package/pkg/dist/theme/theme.js.map +1 -1
  173. package/pkg/package.json +1 -1
  174. package/src/resources/extensions/gsd/auto-model-selection.ts +1 -0
  175. package/src/resources/extensions/gsd/auto-start.ts +1 -1
  176. package/src/resources/extensions/gsd/auto-verification.ts +46 -12
  177. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +4 -3
  178. package/src/resources/extensions/gsd/db/engine.ts +9 -1
  179. package/src/resources/extensions/gsd/db-canonical-foundation-schema.ts +129 -0
  180. package/src/resources/extensions/gsd/db-migration-steps.ts +5 -0
  181. package/src/resources/extensions/gsd/docs/preferences-reference.md +12 -3
  182. package/src/resources/extensions/gsd/doctor-engine-checks.ts +15 -4
  183. package/src/resources/extensions/gsd/model-cost-table.ts +14 -3
  184. package/src/resources/extensions/gsd/model-router.ts +10 -5
  185. package/src/resources/extensions/gsd/planning-subagent-registry.ts +18 -4
  186. package/src/resources/extensions/gsd/preferences-models.ts +54 -10
  187. package/src/resources/extensions/gsd/preferences-types.ts +10 -2
  188. package/src/resources/extensions/gsd/preferences-validation.ts +49 -6
  189. package/src/resources/extensions/gsd/preferences.ts +13 -0
  190. package/src/resources/extensions/gsd/templates/PREFERENCES.md +3 -0
  191. package/src/resources/extensions/gsd/tests/db-canonical-foundation.test.ts +459 -0
  192. package/src/resources/extensions/gsd/tests/db-engine-migrate-guards.test.ts +3 -3
  193. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +64 -0
  194. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +22 -7
  195. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +55 -0
  196. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  197. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +24 -0
  198. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +38 -1
  199. package/src/resources/extensions/gsd/tests/preferences.test.ts +71 -4
  200. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +3 -0
  201. package/src/resources/extensions/gsd/tests/service-tier.test.ts +2 -2
  202. package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +1 -0
  203. package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +1 -1
  204. package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +12 -0
  205. package/src/resources/extensions/gsd/tests/workflow-authority-faults.test.ts +238 -0
  206. package/src/resources/extensions/gsd/tests/workflow-authority-fixture.test.ts +48 -0
  207. package/src/resources/extensions/gsd/tests/workflow-authority-fixture.ts +141 -0
  208. package/src/resources/extensions/gsd/tests/workflow-authority-projection-conflict.test.ts +177 -0
  209. package/src/resources/extensions/gsd/tests/workflow-fault-harness.test.ts +39 -0
  210. package/src/resources/extensions/gsd/tests/workflow-fault-harness.ts +72 -0
  211. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +40 -0
  212. package/src/resources/extensions/gsd/tools/plan-slice.ts +5 -1
  213. package/src/resources/extensions/gsd/verification-evidence.ts +5 -2
  214. package/src/resources/extensions/ollama/ollama-chat-provider.ts +2 -2
  215. package/src/resources/extensions/ollama/tests/ollama-chat-provider-think.test.ts +4 -0
  216. package/src/resources/extensions/subagent/index.ts +1 -1
  217. /package/dist/web/standalone/.next/static/{EAhnhYMt5UVOiG9-iPDjc → 8ub6oiEp2YnlOHQU20LJr}/_buildManifest.js +0 -0
  218. /package/dist/web/standalone/.next/static/{EAhnhYMt5UVOiG9-iPDjc → 8ub6oiEp2YnlOHQU20LJr}/_ssgManifest.js +0 -0
@@ -8,10 +8,31 @@
8
8
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
9
9
  import { homedir } from "node:os";
10
10
  import { join } from "node:path";
11
+ import { getProviders } from "@gsd/pi-ai";
11
12
  import { gsdHome } from "./gsd-home.js";
12
13
  import { canonicalModelForTier, defaultRoutingConfig, resolveModelForTier } from "./model-router.js";
13
14
  import { clearGSDPreferencesCache, loadEffectiveGSDPreferences, getGlobalGSDPreferencesPath } from "./preferences.js";
14
15
  import { getUnitPhaseChain } from "./unit-registry.js";
16
+ /**
17
+ * First-party providers shipped as bundled extensions that delegate to a
18
+ * vendor CLI (`authMode: "externalCli"`). They are registered into the live
19
+ * model registry at runtime, so they are absent from the generated `@gsd/pi-ai`
20
+ * catalog (`getProviders()`), but they are NOT custom: `PREFERENCES.md` can
21
+ * route to them, so they must defer to `PREFERENCES.md` like any other built-in
22
+ * provider. Without this exclusion the registry check below would classify the
23
+ * default `claude-code` provider (and the other CLI providers) as custom and
24
+ * silently skip `PREFERENCES.md` in auto-mode (#801, extends #4122).
25
+ *
26
+ * Kept in sync with the `registerProvider(...)` calls in the bundled CLI
27
+ * extensions (claude-code-cli, cursor-cli, google-cli). Names are lowercase for
28
+ * direct comparison against normalized provider ids.
29
+ */
30
+ const BUILTIN_EXTENSION_PROVIDERS = new Set([
31
+ "claude-code",
32
+ "cursor-agent",
33
+ "google-gemini-cli",
34
+ "google-antigravity",
35
+ ]);
15
36
  /**
16
37
  * Resolve which model ID to use for a given auto-mode unit type.
17
38
  * Returns undefined if no model preference is set for this unit type.
@@ -281,9 +302,8 @@ export function resolveDefaultSessionModel(sessionProvider, basePath, availableM
281
302
  return undefined;
282
303
  }
283
304
  /**
284
- * Returns true if `provider` is defined as a custom provider in the user's
285
- * `~/.gsd/agent/models.json` (Ollama, vLLM, LM Studio, OpenAI-compatible
286
- * proxies, etc.).
305
+ * Returns true if `provider` is a custom provider. Custom providers may be
306
+ * declared in `models.json` or registered dynamically by a Pi extension.
287
307
  *
288
308
  * Used by auto-mode bootstrap to decide whether the session model
289
309
  * (set via `/gsd model`) should override `PREFERENCES.md`. Custom providers
@@ -292,13 +312,16 @@ export function resolveDefaultSessionModel(sessionProvider, basePath, availableM
292
312
  * priority — otherwise auto-mode tries to start the built-in provider from
293
313
  * PREFERENCES.md and fails with "Not logged in · Please run /login" (#4122).
294
314
  *
295
- * Reads models.json directly with a lightweight JSON parse to avoid
296
- * pulling in the full model-registry at this call site. Falls back to
297
- * `~/.pi/agent/models.json` for parity with `resolveModelsJsonPath()`.
298
- * Any read or parse error yields `false` (treat as not-custom) so a
299
- * malformed models.json never breaks the session bootstrap.
315
+ * Reads models.json directly with a lightweight JSON parse, then checks the
316
+ * live model registry for extension-registered providers that are not in the
317
+ * generated built-in provider catalog (`getProviders()`) and not one of the
318
+ * bundled external-CLI providers (`BUILTIN_EXTENSION_PROVIDERS`, e.g.
319
+ * `claude-code`), which register at runtime but are still built-in. Falls back
320
+ * to `~/.pi/agent/models.json` for parity with `resolveModelsJsonPath()`. Any
321
+ * read, parse, or registry error yields `false` (treat as not-custom) so
322
+ * bootstrap stays non-fatal.
300
323
  */
301
- export function isCustomProvider(provider) {
324
+ export function isCustomProvider(provider, registry) {
302
325
  if (!provider)
303
326
  return false;
304
327
  const candidates = [
@@ -319,7 +342,24 @@ export function isCustomProvider(provider) {
319
342
  // Ignore — malformed models.json must not break bootstrap.
320
343
  }
321
344
  }
322
- return false;
345
+ try {
346
+ const normalizedProvider = provider.trim().toLowerCase();
347
+ if (!normalizedProvider)
348
+ return false;
349
+ if (BUILTIN_EXTENSION_PROVIDERS.has(normalizedProvider))
350
+ return false;
351
+ const builtInProviders = new Set(getProviders().map((p) => p.toLowerCase()));
352
+ if (builtInProviders.has(normalizedProvider))
353
+ return false;
354
+ const registryModels = registry?.getAllWithDiscovered?.()
355
+ ?? registry?.getAll?.()
356
+ ?? registry?.getAvailable?.()
357
+ ?? [];
358
+ return registryModels.some((m) => m.provider.toLowerCase() === normalizedProvider);
359
+ }
360
+ catch {
361
+ return false;
362
+ }
323
363
  }
324
364
  /**
325
365
  * Determines the next fallback model to try when the current model fails.
@@ -61,6 +61,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set([
61
61
  "git",
62
62
  "post_unit_hooks",
63
63
  "pre_dispatch_hooks",
64
+ "planning_subagent_registry",
64
65
  "planning_subagents",
65
66
  "dynamic_routing",
66
67
  "disabled_model_providers",
@@ -10,7 +10,7 @@ import { VALID_BRANCH_NAME } from "./git-service.js";
10
10
  import { normalizeStringArray } from "../shared/format-utils.js";
11
11
  import { getGateIdsForTurn } from "./gate-registry.js";
12
12
  import { KNOWN_PREFERENCE_KEYS, KNOWN_UNIT_LABELS, CONFIGURABLE_PLANNING_SUBAGENT_UNITS, GSD_MODEL_PHASE_KEYS, SKILL_ACTIONS, } from "./preferences-types.js";
13
- import { ALLOWED_PLANNING_DISPATCH_AGENTS, allowedPlanningDispatchAgentsList, } from "./planning-subagent-registry.js";
13
+ import { allowedPlanningDispatchAgentsList, isReadOnlyPlanningDispatchAgent, } from "./planning-subagent-registry.js";
14
14
  const VALID_TOKEN_PROFILES = new Set(["budget", "balanced", "quality", "burn-max"]);
15
15
  const VALID_THINKING_LEVELS = new Set([
16
16
  "off",
@@ -19,6 +19,7 @@ const VALID_THINKING_LEVELS = new Set([
19
19
  "medium",
20
20
  "high",
21
21
  "xhigh",
22
+ "max",
22
23
  ]);
23
24
  const KNOWN_MODEL_PHASE_KEYS = new Set(GSD_MODEL_PHASE_KEYS);
24
25
  const VALID_UOK_TURN_ACTIONS = new Set([
@@ -69,7 +70,7 @@ function sanitizeModelField(raw, label, errors, warnings) {
69
70
  }
70
71
  else {
71
72
  warnings?.push(`${label} thinking "${String(obj.thinking)}" is not a valid thinking level ` +
72
- `(off, minimal, low, medium, high, xhigh) — ignored`);
73
+ `(off, minimal, low, medium, high, xhigh, max) — ignored`);
73
74
  }
74
75
  }
75
76
  return sanitized;
@@ -457,7 +458,7 @@ export function validatePreferences(preferences) {
457
458
  const level = entry.thinking;
458
459
  if (level !== undefined && !VALID_THINKING_LEVELS.has(level)) {
459
460
  warnings.push(`models.${phase}.thinking "${String(level)}" is not a valid thinking level ` +
460
- `(off, minimal, low, medium, high, xhigh) — ignored`);
461
+ `(off, minimal, low, medium, high, xhigh, max) — ignored`);
461
462
  const { thinking: _ignored, ...rest } = entry;
462
463
  // If stripping the bad thinking leaves no usable model, drop the
463
464
  // phase entirely rather than storing a hollow `{}` / `{ provider }`
@@ -488,7 +489,7 @@ export function validatePreferences(preferences) {
488
489
  }
489
490
  if (!VALID_THINKING_LEVELS.has(level)) {
490
491
  warnings.push(`thinking.${phase} "${String(level)}" is not a valid thinking level ` +
491
- `(off, minimal, low, medium, high, xhigh) — ignored`);
492
+ `(off, minimal, low, medium, high, xhigh, max) — ignored`);
492
493
  continue;
493
494
  }
494
495
  validatedThinking[phase] = level;
@@ -728,6 +729,38 @@ export function validatePreferences(preferences) {
728
729
  validated.pre_dispatch_hooks = validPreHooks;
729
730
  }
730
731
  }
732
+ // ─── Planning Subagent Registry ──────────────────────────────────────────
733
+ if (preferences.planning_subagent_registry !== undefined) {
734
+ if (!preferences.planning_subagent_registry ||
735
+ typeof preferences.planning_subagent_registry !== "object" ||
736
+ Array.isArray(preferences.planning_subagent_registry)) {
737
+ errors.push("planning_subagent_registry must be an object keyed by agent id");
738
+ }
739
+ else {
740
+ const validRegistry = {};
741
+ for (const [rawAgentId, rawConfig] of Object.entries(preferences.planning_subagent_registry)) {
742
+ const agentId = rawAgentId.trim();
743
+ if (!agentId) {
744
+ errors.push("planning_subagent_registry includes an empty agent id");
745
+ continue;
746
+ }
747
+ if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
748
+ errors.push(`planning_subagent_registry.${agentId} must be an object with read_only_specialist: true`);
749
+ continue;
750
+ }
751
+ const readOnlySpecialist = rawConfig.read_only_specialist;
752
+ if (readOnlySpecialist !== true) {
753
+ errors.push(`planning_subagent_registry.${agentId}.read_only_specialist must be true to classify ` +
754
+ `"${agentId}" as a read-only planning specialist`);
755
+ continue;
756
+ }
757
+ validRegistry[agentId] = { read_only_specialist: true };
758
+ }
759
+ if (Object.keys(validRegistry).length > 0) {
760
+ validated.planning_subagent_registry = validRegistry;
761
+ }
762
+ }
763
+ }
731
764
  // ─── Planning Subagents ─────────────────────────────────────────────────
732
765
  if (preferences.planning_subagents !== undefined) {
733
766
  if (!preferences.planning_subagents ||
@@ -755,9 +788,11 @@ export function validatePreferences(preferences) {
755
788
  }
756
789
  const sanitizedAllowed = [];
757
790
  for (const agentId of allowed) {
758
- if (!ALLOWED_PLANNING_DISPATCH_AGENTS.has(agentId)) {
791
+ if (!isReadOnlyPlanningDispatchAgent(agentId, validated.planning_subagent_registry)) {
759
792
  errors.push(`planning_subagents.${unitType}.allowed includes "${agentId}", which is not classified as a ` +
760
- `read-only planning specialist. Allowed agents: ${allowedPlanningDispatchAgentsList()}`);
793
+ `read-only planning specialist. Allowed agents: ` +
794
+ `${allowedPlanningDispatchAgentsList(validated.planning_subagent_registry)}. ` +
795
+ `For custom planning agents, add planning_subagent_registry.${agentId}.read_only_specialist: true`);
761
796
  continue;
762
797
  }
763
798
  sanitizedAllowed.push(agentId);
@@ -635,6 +635,7 @@ function mergePreferences(base, override) {
635
635
  : undefined,
636
636
  post_unit_hooks: mergePostUnitHooks(base.post_unit_hooks, override.post_unit_hooks),
637
637
  pre_dispatch_hooks: mergePreDispatchHooks(base.pre_dispatch_hooks, override.pre_dispatch_hooks),
638
+ planning_subagent_registry: mergePlanningSubagentRegistry(base.planning_subagent_registry, override.planning_subagent_registry),
638
639
  planning_subagents: mergePlanningSubagents(base.planning_subagents, override.planning_subagents),
639
640
  dynamic_routing: (base.dynamic_routing || override.dynamic_routing)
640
641
  ? { ...(base.dynamic_routing ?? {}), ...(override.dynamic_routing ?? {}) }
@@ -769,6 +770,12 @@ function mergePlanningSubagents(base, override) {
769
770
  }
770
771
  return Object.keys(merged).length > 0 ? merged : undefined;
771
772
  }
773
+ function mergePlanningSubagentRegistry(base, override) {
774
+ if (!base && !override)
775
+ return undefined;
776
+ const merged = { ...(base ?? {}), ...(override ?? {}) };
777
+ return Object.keys(merged).length > 0 ? merged : undefined;
778
+ }
772
779
  // ─── System Prompt Rendering ──────────────────────────────────────────────────
773
780
  export function renderPreferencesForSystemPrompt(preferences, resolutions, options) {
774
781
  const validated = validatePreferences(preferences);
@@ -92,6 +92,9 @@ remote_questions:
92
92
  uat_dispatch:
93
93
  post_unit_hooks: []
94
94
  pre_dispatch_hooks: []
95
+ # planning_subagent_registry:
96
+ # my-custom-planner:
97
+ # read_only_specialist: true
95
98
  # planning_subagents:
96
99
  # plan-milestone:
97
100
  # allowed: [scout, planner, security]
@@ -6,7 +6,7 @@ import { isNonEmptyString, validateStringArray } from "../validation.js";
6
6
  import { getGateIdsForTurn } from "../gate-registry.js";
7
7
  import { transaction, getMilestone, getSlice, getSliceTasks, insertTask, upsertSlicePlanning, upsertTaskPlanning, insertGateRow, updateSliceStatus, setSliceSketchFlag, deleteTask, deleteArtifactByPath, } from "../gsd-db.js";
8
8
  import { invalidateStateCache } from "../state.js";
9
- import { renderPlanFromDb } from "../markdown-renderer.js";
9
+ import { renderPlanCheckboxes, renderPlanFromDb } from "../markdown-renderer.js";
10
10
  import { writeManifest } from "../workflow-manifest.js";
11
11
  import { appendEvent } from "../workflow-events.js";
12
12
  import { logWarning } from "../workflow-logger.js";
@@ -371,9 +371,13 @@ export async function handlePlanSlice(rawParams, basePath) {
371
371
  deleteArtifactByPath(artifactPath);
372
372
  }
373
373
  const sliceTasks = getSliceTasks(params.milestoneId, params.sliceId);
374
+ const hasClosedTasks = sliceTasks.some((task) => isClosedStatus(task.status));
374
375
  const renderResult = sliceTasks.length === 0
375
376
  ? { planPath: "", taskPlanPaths: [] }
376
377
  : await renderPlanFromDb(basePath, params.milestoneId, params.sliceId);
378
+ if (sliceTasks.length > 0 && hasClosedTasks) {
379
+ await renderPlanCheckboxes(basePath, params.milestoneId, params.sliceId);
380
+ }
377
381
  if (sliceTasks.length > 0) {
378
382
  setSliceSketchFlag(params.milestoneId, params.sliceId, false);
379
383
  }
@@ -11,13 +11,14 @@
11
11
  import { mkdirSync, writeFileSync } from "node:fs";
12
12
  import { join } from "node:path";
13
13
  /**
14
- * Write a T##-VERIFY.json artifact to the tasks directory.
14
+ * Write a T##-VERIFY.json artifact to the evidence directory.
15
15
  * Creates the directory with mkdirSync({ recursive: true }) if it doesn't exist.
16
+ * Flat-phase callers can pass sliceId to write S##-T##-VERIFY.json.
16
17
  *
17
18
  * stdout/stderr are excluded from the JSON — the full output lives in VerificationResult
18
19
  * in memory and is logged to stderr during the gate run.
19
20
  */
20
- export function writeVerificationJSON(result, tasksDir, taskId, unitId, retryAttempt, maxRetries) {
21
+ export function writeVerificationJSON(result, tasksDir, taskId, unitId, retryAttempt, maxRetries, sliceId) {
21
22
  mkdirSync(tasksDir, { recursive: true });
22
23
  const evidence = {
23
24
  schemaVersion: 1,
@@ -52,7 +53,8 @@ export function writeVerificationJSON(result, tasksDir, taskId, unitId, retryAtt
52
53
  fixAvailable: w.fixAvailable,
53
54
  }));
54
55
  }
55
- const filePath = join(tasksDir, `${taskId}-VERIFY.json`);
56
+ const fileName = sliceId ? `${sliceId}-${taskId}-VERIFY.json` : `${taskId}-VERIFY.json`;
57
+ const filePath = join(tasksDir, fileName);
56
58
  writeFileSync(filePath, JSON.stringify(evidence, null, 2) + "\n", "utf-8");
57
59
  }
58
60
  /**
@@ -160,7 +160,7 @@ export function streamOllamaChat(model, context, options) {
160
160
  * - `"low" | "medium" | "high"` are passed through as strings; gpt-oss honors
161
161
  * them as strength levels, other thinking models treat any present value
162
162
  * as "thinking enabled".
163
- * - `"xhigh"` is collapsed to `"high"` (ollama caps strength at high).
163
+ * - `"xhigh"` and `"max"` are collapsed to `"high"` (ollama caps strength at high).
164
164
  */
165
165
  export function buildThinkParam(model, options) {
166
166
  if (!model.reasoning)
@@ -170,7 +170,7 @@ export function buildThinkParam(model, options) {
170
170
  return undefined;
171
171
  if (level === "minimal")
172
172
  return false;
173
- if (level === "xhigh")
173
+ if (level === "xhigh" || level === "max")
174
174
  return "high";
175
175
  return level;
176
176
  }
@@ -591,7 +591,7 @@ thinkingOverride) {
591
591
  }
592
592
  }
593
593
  }
594
- const ThinkingLevelSchema = StringEnum(["off", "minimal", "low", "medium", "high", "xhigh"], {
594
+ const ThinkingLevelSchema = StringEnum(["off", "minimal", "low", "medium", "high", "xhigh", "max"], {
595
595
  description: "Reasoning effort override for the subagent (forwarded as --thinking). " +
596
596
  "Independent of model choice; unsupported levels are clamped to the model at dispatch.",
597
597
  });
@@ -15,7 +15,7 @@ interface MinimalModel {
15
15
  interface MinimalModelRegistry {
16
16
  getAvailable(): MinimalModel[];
17
17
  }
18
- type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
18
+ type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
19
19
  interface MinimalSettingsManager {
20
20
  getDefaultProvider(): string | undefined;
21
21
  getDefaultModel(): string | undefined;