@opengsd/gsd-pi 1.9.0-dev.e10b60d4 → 1.10.0-dev.a5e42e99
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/README.md +10 -9
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +24 -4
- package/dist/resources/extensions/gsd/auto/run-unit.js +19 -1
- package/dist/resources/extensions/gsd/auto-model-selection.js +1 -0
- package/dist/resources/extensions/gsd/auto-start.js +30 -10
- package/dist/resources/extensions/gsd/auto-verification.js +25 -12
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +7 -3
- package/dist/resources/extensions/gsd/auto.js +12 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +4 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +12 -3
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +37 -6
- package/dist/resources/extensions/gsd/flat-phase-migration.js +12 -0
- package/dist/resources/extensions/gsd/gsd-db.js +75 -31
- package/dist/resources/extensions/gsd/model-cost-table.js +6 -3
- package/dist/resources/extensions/gsd/model-router.js +10 -5
- package/dist/resources/extensions/gsd/planning-subagent-registry.js +12 -4
- package/dist/resources/extensions/gsd/preferences-models.js +50 -10
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +41 -6
- package/dist/resources/extensions/gsd/preferences.js +7 -0
- package/dist/resources/extensions/gsd/templates/PREFERENCES.md +3 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -1
- package/dist/resources/extensions/gsd/verification-evidence.js +5 -3
- package/dist/resources/extensions/gsd/worktree-state-projection.js +20 -18
- package/dist/resources/extensions/ollama/ollama-chat-provider.js +2 -2
- package/dist/resources/extensions/subagent/index.js +1 -1
- package/dist/startup-model-validation.d.ts +1 -1
- 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 +6 -6
- 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 +6 -6
- package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
- package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- 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/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
- package/integrations/hermes/pyproject.toml +1 -1
- package/package.json +7 -6
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/rpc.d.ts +1 -1
- package/packages/contracts/dist/rpc.d.ts.map +1 -1
- package/packages/contracts/dist/rpc.js +1 -1
- package/packages/contracts/dist/rpc.js.map +1 -1
- package/packages/contracts/dist/rpc.test.js +1 -1
- package/packages/contracts/dist/rpc.test.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/cli/args.js +2 -2
- package/packages/gsd-agent-modes/dist/cli/args.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/README.md +21 -5
- package/packages/gsd-cloud/dist/background-cli.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/background-cli.test.js +325 -0
- package/packages/gsd-cloud/dist/background-cli.test.js.map +1 -0
- package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cli.js +146 -16
- package/packages/gsd-cloud/dist/cli.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.js +7 -1
- package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts +7 -4
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.js +115 -31
- package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.test.js +224 -59
- package/packages/gsd-cloud/dist/cloud-runtime.test.js.map +1 -1
- package/packages/gsd-cloud/dist/runtime-artifacts.d.ts +3 -0
- package/packages/gsd-cloud/dist/runtime-artifacts.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-artifacts.js +36 -0
- package/packages/gsd-cloud/dist/runtime-artifacts.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.d.ts +32 -0
- package/packages/gsd-cloud/dist/runtime-process.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.js +640 -0
- package/packages/gsd-cloud/dist/runtime-process.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js +543 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.d.ts +117 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.js +291 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.test.js +434 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.test.js.map +1 -0
- package/packages/gsd-cloud/package.json +2 -2
- package/packages/mcp-server/dist/cli-runner.d.ts +3 -0
- package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
- package/packages/mcp-server/dist/cli-runner.js +119 -33
- package/packages/mcp-server/dist/cli-runner.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +2 -2
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +130 -2
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +101 -9
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.js +18 -6
- package/packages/pi-ai/dist/models.js.map +1 -1
- package/packages/pi-ai/dist/providers/amazon-bedrock.js +3 -2
- package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
- package/packages/pi-ai/dist/providers/google.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.d.ts +1 -1
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +9 -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/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -3
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +54 -0
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.d.ts +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.js +1 -0
- package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/theme/theme.d.ts +1 -1
- package/pkg/dist/theme/theme.d.ts.map +1 -1
- package/pkg/dist/theme/theme.js +1 -0
- package/pkg/dist/theme/theme.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop.ts +48 -4
- package/src/resources/extensions/gsd/auto/run-unit.ts +20 -1
- package/src/resources/extensions/gsd/auto-model-selection.ts +1 -0
- package/src/resources/extensions/gsd/auto-start.ts +32 -9
- package/src/resources/extensions/gsd/auto-verification.ts +46 -12
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +10 -3
- package/src/resources/extensions/gsd/auto.ts +13 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +4 -3
- package/src/resources/extensions/gsd/docs/preferences-reference.md +12 -3
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +42 -6
- package/src/resources/extensions/gsd/flat-phase-migration.ts +14 -0
- package/src/resources/extensions/gsd/gsd-db.ts +85 -33
- package/src/resources/extensions/gsd/model-cost-table.ts +14 -3
- package/src/resources/extensions/gsd/model-router.ts +10 -5
- package/src/resources/extensions/gsd/planning-subagent-registry.ts +18 -4
- package/src/resources/extensions/gsd/preferences-models.ts +54 -10
- package/src/resources/extensions/gsd/preferences-types.ts +10 -2
- package/src/resources/extensions/gsd/preferences-validation.ts +49 -6
- package/src/resources/extensions/gsd/preferences.ts +13 -0
- package/src/resources/extensions/gsd/templates/PREFERENCES.md +3 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +74 -1
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +147 -0
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +8 -9
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +22 -7
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +38 -1
- package/src/resources/extensions/gsd/tests/preferences.test.ts +71 -4
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/workflow-authority-faults.test.ts +238 -0
- package/src/resources/extensions/gsd/tests/workflow-authority-fixture.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/workflow-authority-fixture.ts +141 -0
- package/src/resources/extensions/gsd/tests/workflow-authority-projection-conflict.test.ts +177 -0
- package/src/resources/extensions/gsd/tests/workflow-fault-harness.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/workflow-fault-harness.ts +72 -0
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +40 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +5 -1
- package/src/resources/extensions/gsd/verification-evidence.ts +5 -2
- package/src/resources/extensions/gsd/worktree-state-projection.ts +21 -24
- package/src/resources/extensions/ollama/ollama-chat-provider.ts +2 -2
- package/src/resources/extensions/ollama/tests/ollama-chat-provider-think.test.ts +4 -0
- package/src/resources/extensions/subagent/index.ts +1 -1
- /package/dist/web/standalone/.next/static/{V9MAVyYVPboQUB9KTTAMi → xwUaOqui04jIIFVsIBLiL}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{V9MAVyYVPboQUB9KTTAMi → xwUaOqui04jIIFVsIBLiL}/_ssgManifest.js +0 -0
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { join } from "node:path";
|
|
12
|
+
import { getProviders } from "@gsd/pi-ai";
|
|
12
13
|
import { gsdHome } from "./gsd-home.js";
|
|
13
14
|
import type { DynamicRoutingConfig } from "./model-router.js";
|
|
14
15
|
import { canonicalModelForTier, defaultRoutingConfig, resolveModelForTier } from "./model-router.js";
|
|
@@ -31,6 +32,33 @@ import { getUnitPhaseChain } from "./unit-registry.js";
|
|
|
31
32
|
// Re-export types so existing consumers of ./preferences-models.js keep working
|
|
32
33
|
export type { GSDPhaseModelConfig, GSDModelConfig, GSDModelConfigV2, ResolvedModelConfig } from "./preferences-types.js";
|
|
33
34
|
|
|
35
|
+
type ProviderModelRegistry = {
|
|
36
|
+
getAll?: () => ReadonlyArray<{ provider: string }>;
|
|
37
|
+
getAvailable?: () => ReadonlyArray<{ provider: string }>;
|
|
38
|
+
getAllWithDiscovered?: () => ReadonlyArray<{ provider: string }>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* First-party providers shipped as bundled extensions that delegate to a
|
|
43
|
+
* vendor CLI (`authMode: "externalCli"`). They are registered into the live
|
|
44
|
+
* model registry at runtime, so they are absent from the generated `@gsd/pi-ai`
|
|
45
|
+
* catalog (`getProviders()`), but they are NOT custom: `PREFERENCES.md` can
|
|
46
|
+
* route to them, so they must defer to `PREFERENCES.md` like any other built-in
|
|
47
|
+
* provider. Without this exclusion the registry check below would classify the
|
|
48
|
+
* default `claude-code` provider (and the other CLI providers) as custom and
|
|
49
|
+
* silently skip `PREFERENCES.md` in auto-mode (#801, extends #4122).
|
|
50
|
+
*
|
|
51
|
+
* Kept in sync with the `registerProvider(...)` calls in the bundled CLI
|
|
52
|
+
* extensions (claude-code-cli, cursor-cli, google-cli). Names are lowercase for
|
|
53
|
+
* direct comparison against normalized provider ids.
|
|
54
|
+
*/
|
|
55
|
+
const BUILTIN_EXTENSION_PROVIDERS: ReadonlySet<string> = new Set([
|
|
56
|
+
"claude-code",
|
|
57
|
+
"cursor-agent",
|
|
58
|
+
"google-gemini-cli",
|
|
59
|
+
"google-antigravity",
|
|
60
|
+
]);
|
|
61
|
+
|
|
34
62
|
/**
|
|
35
63
|
* Resolve which model ID to use for a given auto-mode unit type.
|
|
36
64
|
* Returns undefined if no model preference is set for this unit type.
|
|
@@ -321,9 +349,8 @@ export function resolveDefaultSessionModel(
|
|
|
321
349
|
}
|
|
322
350
|
|
|
323
351
|
/**
|
|
324
|
-
* Returns true if `provider` is
|
|
325
|
-
*
|
|
326
|
-
* proxies, etc.).
|
|
352
|
+
* Returns true if `provider` is a custom provider. Custom providers may be
|
|
353
|
+
* declared in `models.json` or registered dynamically by a Pi extension.
|
|
327
354
|
*
|
|
328
355
|
* Used by auto-mode bootstrap to decide whether the session model
|
|
329
356
|
* (set via `/gsd model`) should override `PREFERENCES.md`. Custom providers
|
|
@@ -332,13 +359,16 @@ export function resolveDefaultSessionModel(
|
|
|
332
359
|
* priority — otherwise auto-mode tries to start the built-in provider from
|
|
333
360
|
* PREFERENCES.md and fails with "Not logged in · Please run /login" (#4122).
|
|
334
361
|
*
|
|
335
|
-
* Reads models.json directly with a lightweight JSON parse
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
362
|
+
* Reads models.json directly with a lightweight JSON parse, then checks the
|
|
363
|
+
* live model registry for extension-registered providers that are not in the
|
|
364
|
+
* generated built-in provider catalog (`getProviders()`) and not one of the
|
|
365
|
+
* bundled external-CLI providers (`BUILTIN_EXTENSION_PROVIDERS`, e.g.
|
|
366
|
+
* `claude-code`), which register at runtime but are still built-in. Falls back
|
|
367
|
+
* to `~/.pi/agent/models.json` for parity with `resolveModelsJsonPath()`. Any
|
|
368
|
+
* read, parse, or registry error yields `false` (treat as not-custom) so
|
|
369
|
+
* bootstrap stays non-fatal.
|
|
340
370
|
*/
|
|
341
|
-
export function isCustomProvider(provider: string | undefined): boolean {
|
|
371
|
+
export function isCustomProvider(provider: string | undefined, registry?: ProviderModelRegistry): boolean {
|
|
342
372
|
if (!provider) return false;
|
|
343
373
|
const candidates = [
|
|
344
374
|
join(gsdHome(), "agent", "models.json"),
|
|
@@ -356,7 +386,21 @@ export function isCustomProvider(provider: string | undefined): boolean {
|
|
|
356
386
|
// Ignore — malformed models.json must not break bootstrap.
|
|
357
387
|
}
|
|
358
388
|
}
|
|
359
|
-
|
|
389
|
+
try {
|
|
390
|
+
const normalizedProvider = provider.trim().toLowerCase();
|
|
391
|
+
if (!normalizedProvider) return false;
|
|
392
|
+
if (BUILTIN_EXTENSION_PROVIDERS.has(normalizedProvider)) return false;
|
|
393
|
+
const builtInProviders = new Set(getProviders().map((p) => p.toLowerCase()));
|
|
394
|
+
if (builtInProviders.has(normalizedProvider)) return false;
|
|
395
|
+
const registryModels =
|
|
396
|
+
registry?.getAllWithDiscovered?.()
|
|
397
|
+
?? registry?.getAll?.()
|
|
398
|
+
?? registry?.getAvailable?.()
|
|
399
|
+
?? [];
|
|
400
|
+
return registryModels.some((m) => m.provider.toLowerCase() === normalizedProvider);
|
|
401
|
+
} catch {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
360
404
|
}
|
|
361
405
|
|
|
362
406
|
/**
|
|
@@ -141,6 +141,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set<string>([
|
|
|
141
141
|
"git",
|
|
142
142
|
"post_unit_hooks",
|
|
143
143
|
"pre_dispatch_hooks",
|
|
144
|
+
"planning_subagent_registry",
|
|
144
145
|
"planning_subagents",
|
|
145
146
|
"dynamic_routing",
|
|
146
147
|
"disabled_model_providers",
|
|
@@ -232,6 +233,12 @@ export type PlanningSubagentsConfig = Partial<
|
|
|
232
233
|
Record<ConfigurablePlanningSubagentUnit, PlanningSubagentUnitConfig>
|
|
233
234
|
>;
|
|
234
235
|
|
|
236
|
+
export interface PlanningSubagentRegistryEntry {
|
|
237
|
+
read_only_specialist?: boolean;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export type PlanningSubagentRegistryConfig = Record<string, PlanningSubagentRegistryEntry>;
|
|
241
|
+
|
|
235
242
|
|
|
236
243
|
export const SKILL_ACTIONS = new Set(["use", "prefer", "avoid"]);
|
|
237
244
|
|
|
@@ -245,10 +252,10 @@ export interface GSDSkillRule {
|
|
|
245
252
|
/**
|
|
246
253
|
* Reasoning effort levels. Mirrors `ThinkingLevel` in `@gsd/pi-agent-core`;
|
|
247
254
|
* declared locally so preference parsing/validation does not import the agent
|
|
248
|
-
* core package. `xhigh`
|
|
255
|
+
* core package. `xhigh` and `max` are only honored by models whose `thinkingLevelMap`
|
|
249
256
|
* advertises it — unsupported levels are clamped at dispatch (ADR-026).
|
|
250
257
|
*/
|
|
251
|
-
export type GSDThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
258
|
+
export type GSDThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
252
259
|
|
|
253
260
|
/** The nine model-routing phase buckets. */
|
|
254
261
|
export const GSD_MODEL_PHASE_KEYS = [
|
|
@@ -491,6 +498,7 @@ export interface GSDPreferences {
|
|
|
491
498
|
git?: GitPreferences;
|
|
492
499
|
post_unit_hooks?: PostUnitHookConfig[];
|
|
493
500
|
pre_dispatch_hooks?: PreDispatchHookConfig[];
|
|
501
|
+
planning_subagent_registry?: PlanningSubagentRegistryConfig;
|
|
494
502
|
planning_subagents?: PlanningSubagentsConfig;
|
|
495
503
|
dynamic_routing?: DynamicRoutingConfig;
|
|
496
504
|
/** Provider IDs to exclude from /model and automatic model routing while leaving tool auth intact. */
|
|
@@ -28,8 +28,8 @@ import {
|
|
|
28
28
|
type GSDThinkingLevel,
|
|
29
29
|
} from "./preferences-types.js";
|
|
30
30
|
import {
|
|
31
|
-
ALLOWED_PLANNING_DISPATCH_AGENTS,
|
|
32
31
|
allowedPlanningDispatchAgentsList,
|
|
32
|
+
isReadOnlyPlanningDispatchAgent,
|
|
33
33
|
} from "./planning-subagent-registry.js";
|
|
34
34
|
|
|
35
35
|
const VALID_TOKEN_PROFILES = new Set<TokenProfile>(["budget", "balanced", "quality", "burn-max"]);
|
|
@@ -40,6 +40,7 @@ const VALID_THINKING_LEVELS = new Set<GSDThinkingLevel>([
|
|
|
40
40
|
"medium",
|
|
41
41
|
"high",
|
|
42
42
|
"xhigh",
|
|
43
|
+
"max",
|
|
43
44
|
]);
|
|
44
45
|
const KNOWN_MODEL_PHASE_KEYS = new Set<string>(GSD_MODEL_PHASE_KEYS);
|
|
45
46
|
const VALID_UOK_TURN_ACTIONS = new Set<"commit" | "snapshot" | "status-only">([
|
|
@@ -93,7 +94,7 @@ function sanitizeModelField(
|
|
|
93
94
|
} else {
|
|
94
95
|
warnings?.push(
|
|
95
96
|
`${label} thinking "${String(obj.thinking)}" is not a valid thinking level ` +
|
|
96
|
-
`(off, minimal, low, medium, high, xhigh) — ignored`,
|
|
97
|
+
`(off, minimal, low, medium, high, xhigh, max) — ignored`,
|
|
97
98
|
);
|
|
98
99
|
}
|
|
99
100
|
}
|
|
@@ -486,7 +487,7 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
486
487
|
if (level !== undefined && !VALID_THINKING_LEVELS.has(level as GSDThinkingLevel)) {
|
|
487
488
|
warnings.push(
|
|
488
489
|
`models.${phase}.thinking "${String(level)}" is not a valid thinking level ` +
|
|
489
|
-
`(off, minimal, low, medium, high, xhigh) — ignored`,
|
|
490
|
+
`(off, minimal, low, medium, high, xhigh, max) — ignored`,
|
|
490
491
|
);
|
|
491
492
|
const { thinking: _ignored, ...rest } = entry as Record<string, unknown>;
|
|
492
493
|
// If stripping the bad thinking leaves no usable model, drop the
|
|
@@ -521,7 +522,7 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
521
522
|
if (!VALID_THINKING_LEVELS.has(level as GSDThinkingLevel)) {
|
|
522
523
|
warnings.push(
|
|
523
524
|
`thinking.${phase} "${String(level)}" is not a valid thinking level ` +
|
|
524
|
-
`(off, minimal, low, medium, high, xhigh) — ignored`,
|
|
525
|
+
`(off, minimal, low, medium, high, xhigh, max) — ignored`,
|
|
525
526
|
);
|
|
526
527
|
continue;
|
|
527
528
|
}
|
|
@@ -752,6 +753,46 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
752
753
|
}
|
|
753
754
|
}
|
|
754
755
|
|
|
756
|
+
// ─── Planning Subagent Registry ──────────────────────────────────────────
|
|
757
|
+
if (preferences.planning_subagent_registry !== undefined) {
|
|
758
|
+
if (
|
|
759
|
+
!preferences.planning_subagent_registry ||
|
|
760
|
+
typeof preferences.planning_subagent_registry !== "object" ||
|
|
761
|
+
Array.isArray(preferences.planning_subagent_registry)
|
|
762
|
+
) {
|
|
763
|
+
errors.push("planning_subagent_registry must be an object keyed by agent id");
|
|
764
|
+
} else {
|
|
765
|
+
const validRegistry: NonNullable<GSDPreferences["planning_subagent_registry"]> = {};
|
|
766
|
+
|
|
767
|
+
for (const [rawAgentId, rawConfig] of Object.entries(preferences.planning_subagent_registry as Record<string, unknown>)) {
|
|
768
|
+
const agentId = rawAgentId.trim();
|
|
769
|
+
if (!agentId) {
|
|
770
|
+
errors.push("planning_subagent_registry includes an empty agent id");
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
if (!rawConfig || typeof rawConfig !== "object" || Array.isArray(rawConfig)) {
|
|
774
|
+
errors.push(`planning_subagent_registry.${agentId} must be an object with read_only_specialist: true`);
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const readOnlySpecialist = (rawConfig as Record<string, unknown>).read_only_specialist;
|
|
779
|
+
if (readOnlySpecialist !== true) {
|
|
780
|
+
errors.push(
|
|
781
|
+
`planning_subagent_registry.${agentId}.read_only_specialist must be true to classify ` +
|
|
782
|
+
`"${agentId}" as a read-only planning specialist`,
|
|
783
|
+
);
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
validRegistry[agentId] = { read_only_specialist: true };
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (Object.keys(validRegistry).length > 0) {
|
|
791
|
+
validated.planning_subagent_registry = validRegistry;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
755
796
|
// ─── Planning Subagents ─────────────────────────────────────────────────
|
|
756
797
|
if (preferences.planning_subagents !== undefined) {
|
|
757
798
|
if (
|
|
@@ -785,10 +826,12 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
785
826
|
|
|
786
827
|
const sanitizedAllowed: string[] = [];
|
|
787
828
|
for (const agentId of allowed) {
|
|
788
|
-
if (!
|
|
829
|
+
if (!isReadOnlyPlanningDispatchAgent(agentId, validated.planning_subagent_registry)) {
|
|
789
830
|
errors.push(
|
|
790
831
|
`planning_subagents.${unitType}.allowed includes "${agentId}", which is not classified as a ` +
|
|
791
|
-
`read-only planning specialist. Allowed agents:
|
|
832
|
+
`read-only planning specialist. Allowed agents: ` +
|
|
833
|
+
`${allowedPlanningDispatchAgentsList(validated.planning_subagent_registry)}. ` +
|
|
834
|
+
`For custom planning agents, add planning_subagent_registry.${agentId}.read_only_specialist: true`,
|
|
792
835
|
);
|
|
793
836
|
continue;
|
|
794
837
|
}
|
|
@@ -817,6 +817,10 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
|
|
|
817
817
|
: undefined,
|
|
818
818
|
post_unit_hooks: mergePostUnitHooks(base.post_unit_hooks, override.post_unit_hooks),
|
|
819
819
|
pre_dispatch_hooks: mergePreDispatchHooks(base.pre_dispatch_hooks, override.pre_dispatch_hooks),
|
|
820
|
+
planning_subagent_registry: mergePlanningSubagentRegistry(
|
|
821
|
+
base.planning_subagent_registry,
|
|
822
|
+
override.planning_subagent_registry,
|
|
823
|
+
),
|
|
820
824
|
planning_subagents: mergePlanningSubagents(base.planning_subagents, override.planning_subagents),
|
|
821
825
|
dynamic_routing: (base.dynamic_routing || override.dynamic_routing)
|
|
822
826
|
? { ...(base.dynamic_routing ?? {}), ...(override.dynamic_routing ?? {}) } as DynamicRoutingConfig
|
|
@@ -968,6 +972,15 @@ function mergePlanningSubagents(
|
|
|
968
972
|
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
969
973
|
}
|
|
970
974
|
|
|
975
|
+
function mergePlanningSubagentRegistry(
|
|
976
|
+
base?: GSDPreferences["planning_subagent_registry"],
|
|
977
|
+
override?: GSDPreferences["planning_subagent_registry"],
|
|
978
|
+
): GSDPreferences["planning_subagent_registry"] | undefined {
|
|
979
|
+
if (!base && !override) return undefined;
|
|
980
|
+
const merged = { ...(base ?? {}), ...(override ?? {}) };
|
|
981
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
982
|
+
}
|
|
983
|
+
|
|
971
984
|
// ─── System Prompt Rendering ──────────────────────────────────────────────────
|
|
972
985
|
|
|
973
986
|
export function renderPreferencesForSystemPrompt(
|
|
@@ -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]
|
|
@@ -41,6 +41,7 @@ import { claimMilestoneLease } from "../db/milestone-leases.js";
|
|
|
41
41
|
import { recordDispatchClaim, markCanceled } from "../db/unit-dispatches.js";
|
|
42
42
|
import { setRuntimeKv, getRuntimeKv } from "../db/runtime-kv.js";
|
|
43
43
|
import { SourceObservationStore } from "../source-observations.js";
|
|
44
|
+
import { autoCommitCurrentBranch } from "../worktree.js";
|
|
44
45
|
|
|
45
46
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
46
47
|
|
|
@@ -832,6 +833,37 @@ test("runUnit returns cancelled when session creation fails", async () => {
|
|
|
832
833
|
assert.equal(pi.calls.length, 0);
|
|
833
834
|
});
|
|
834
835
|
|
|
836
|
+
test("runUnit returns cancelled when command context lacks newSession", async () => {
|
|
837
|
+
_resetPendingResolve();
|
|
838
|
+
|
|
839
|
+
const ctx = {
|
|
840
|
+
...makeMockCtx(),
|
|
841
|
+
ui: {
|
|
842
|
+
notify: () => {},
|
|
843
|
+
setStatus: () => {},
|
|
844
|
+
setWorkingMessage: () => {},
|
|
845
|
+
},
|
|
846
|
+
sessionManager: {
|
|
847
|
+
getEntries: () => [],
|
|
848
|
+
},
|
|
849
|
+
modelRegistry: {
|
|
850
|
+
getProviderAuthMode: () => undefined,
|
|
851
|
+
isProviderRequestReady: () => true,
|
|
852
|
+
},
|
|
853
|
+
} as any;
|
|
854
|
+
const pi = makeMockPi();
|
|
855
|
+
const s = makeMockSession();
|
|
856
|
+
s.cmdCtx = {} as any;
|
|
857
|
+
|
|
858
|
+
const result = await runUnit(ctx, pi, s, "task", "T01", "prompt");
|
|
859
|
+
|
|
860
|
+
assert.equal(result.status, "cancelled");
|
|
861
|
+
assert.equal(result.errorContext?.category, "session-failed");
|
|
862
|
+
assert.equal(result.errorContext?.isTransient, false);
|
|
863
|
+
assert.match(result.errorContext?.message ?? "", /missing newSession/);
|
|
864
|
+
assert.equal(pi.calls.length, 0);
|
|
865
|
+
});
|
|
866
|
+
|
|
835
867
|
test("runUnit: TypeError from newSession is classified as structural (isTransient: false)", async () => {
|
|
836
868
|
// Regression for #572: a TypeError thrown from newSession (e.g. "something is
|
|
837
869
|
// not a function") indicates a programming error, not a transient provider
|
|
@@ -1582,6 +1614,130 @@ test("autoLoop exits when s.active is set to false", async (t) => {
|
|
|
1582
1614
|
);
|
|
1583
1615
|
});
|
|
1584
1616
|
|
|
1617
|
+
test("autoLoop stops before dispatch when command context lacks newSession", async () => {
|
|
1618
|
+
_resetPendingResolve();
|
|
1619
|
+
|
|
1620
|
+
const ctx = makeMockCtx();
|
|
1621
|
+
ctx.ui.setStatus = () => {};
|
|
1622
|
+
const pi = makeMockPi();
|
|
1623
|
+
const s = makeLoopSession({
|
|
1624
|
+
cmdCtx: {
|
|
1625
|
+
getContextUsage: () => ({ percent: 10, tokens: 1000, limit: 10000 }),
|
|
1626
|
+
},
|
|
1627
|
+
});
|
|
1628
|
+
let stopReason: string | undefined;
|
|
1629
|
+
let preserveWorktree: boolean | undefined;
|
|
1630
|
+
let deriveCalled = false;
|
|
1631
|
+
|
|
1632
|
+
try {
|
|
1633
|
+
const deps = makeMockDeps({
|
|
1634
|
+
stopAuto: async (_ctx, _pi, reason, options) => {
|
|
1635
|
+
stopReason = reason;
|
|
1636
|
+
preserveWorktree = options?.preserveWorktree;
|
|
1637
|
+
s.active = false;
|
|
1638
|
+
},
|
|
1639
|
+
deriveState: async () => {
|
|
1640
|
+
deriveCalled = true;
|
|
1641
|
+
throw new Error("deriveState should not run without command session support");
|
|
1642
|
+
},
|
|
1643
|
+
});
|
|
1644
|
+
|
|
1645
|
+
await autoLoop(ctx, pi, s, deps);
|
|
1646
|
+
|
|
1647
|
+
assert.equal(stopReason, "Auto-mode has no command context for dispatch.");
|
|
1648
|
+
assert.equal(preserveWorktree, true);
|
|
1649
|
+
assert.equal(deriveCalled, false);
|
|
1650
|
+
assert.equal(pi.calls.length, 0);
|
|
1651
|
+
} finally {
|
|
1652
|
+
rmSync(s.basePath, { recursive: true, force: true });
|
|
1653
|
+
}
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
test("autoLoop commits open unit work when command context lacks newSession", async () => {
|
|
1657
|
+
_resetPendingResolve();
|
|
1658
|
+
|
|
1659
|
+
const ctx = makeMockCtx();
|
|
1660
|
+
ctx.ui.setStatus = () => {};
|
|
1661
|
+
const pi = makeMockPi();
|
|
1662
|
+
const s = makeLoopSession({
|
|
1663
|
+
cmdCtx: {
|
|
1664
|
+
getContextUsage: () => ({ percent: 10, tokens: 1000, limit: 10000 }),
|
|
1665
|
+
},
|
|
1666
|
+
currentUnit: { type: "execute-task", id: "T01", startedAt: Date.now() },
|
|
1667
|
+
});
|
|
1668
|
+
const outputPath = join(s.basePath, "executor-output.txt");
|
|
1669
|
+
let autoCommitArgs: { unitType: string; unitId: string } | undefined;
|
|
1670
|
+
let preserveWorktree: boolean | undefined;
|
|
1671
|
+
|
|
1672
|
+
try {
|
|
1673
|
+
const deps = makeMockDeps({
|
|
1674
|
+
autoCommitUnit: async (basePath, unitType, unitId) => {
|
|
1675
|
+
autoCommitArgs = { unitType, unitId };
|
|
1676
|
+
return autoCommitCurrentBranch(basePath, unitType, unitId);
|
|
1677
|
+
},
|
|
1678
|
+
stopAuto: async (_ctx, _pi, _reason, options) => {
|
|
1679
|
+
preserveWorktree = options?.preserveWorktree;
|
|
1680
|
+
s.active = false;
|
|
1681
|
+
},
|
|
1682
|
+
deriveState: async () => {
|
|
1683
|
+
throw new Error("deriveState should not run without command session support");
|
|
1684
|
+
},
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
writeFileSync(outputPath, "open unit work before stop\n", "utf-8");
|
|
1688
|
+
|
|
1689
|
+
await autoLoop(ctx, pi, s, deps);
|
|
1690
|
+
|
|
1691
|
+
assert.deepEqual(autoCommitArgs, { unitType: "execute-task", unitId: "T01" });
|
|
1692
|
+
assert.equal(preserveWorktree, true);
|
|
1693
|
+
const committed = execSync("git show HEAD:executor-output.txt", {
|
|
1694
|
+
cwd: s.basePath,
|
|
1695
|
+
encoding: "utf-8",
|
|
1696
|
+
});
|
|
1697
|
+
assert.equal(committed, "open unit work before stop\n");
|
|
1698
|
+
assert.equal(pi.calls.length, 0);
|
|
1699
|
+
} finally {
|
|
1700
|
+
rmSync(s.basePath, { recursive: true, force: true });
|
|
1701
|
+
}
|
|
1702
|
+
});
|
|
1703
|
+
|
|
1704
|
+
test("autoLoop snapshots dirty work when unit dispatch crashes", async () => {
|
|
1705
|
+
_resetPendingResolve();
|
|
1706
|
+
|
|
1707
|
+
const ctx = makeMockCtx();
|
|
1708
|
+
ctx.ui.setStatus = () => {};
|
|
1709
|
+
const pi = makeMockPi();
|
|
1710
|
+
const s = makeLoopSession();
|
|
1711
|
+
const outputPath = join(s.basePath, "executor-output.txt");
|
|
1712
|
+
let autoCommitCalls = 0;
|
|
1713
|
+
|
|
1714
|
+
try {
|
|
1715
|
+
const deps = makeMockDeps({
|
|
1716
|
+
ensurePreconditions: () => {
|
|
1717
|
+
writeFileSync(outputPath, "saved before crash\n", "utf-8");
|
|
1718
|
+
throw new Error("dispatch crash after file write");
|
|
1719
|
+
},
|
|
1720
|
+
autoCommitUnit: async (basePath, unitType, unitId) => {
|
|
1721
|
+
autoCommitCalls++;
|
|
1722
|
+
const commitMsg = autoCommitCurrentBranch(basePath, unitType, unitId);
|
|
1723
|
+
s.active = false;
|
|
1724
|
+
return commitMsg;
|
|
1725
|
+
},
|
|
1726
|
+
});
|
|
1727
|
+
|
|
1728
|
+
await autoLoop(ctx, pi, s, deps);
|
|
1729
|
+
|
|
1730
|
+
assert.equal(autoCommitCalls, 1);
|
|
1731
|
+
const committed = execSync("git show HEAD:executor-output.txt", {
|
|
1732
|
+
cwd: s.basePath,
|
|
1733
|
+
encoding: "utf-8",
|
|
1734
|
+
});
|
|
1735
|
+
assert.equal(committed, "saved before crash\n");
|
|
1736
|
+
} finally {
|
|
1737
|
+
rmSync(s.basePath, { recursive: true, force: true });
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
|
|
1585
1741
|
test("autoLoop pauses visibly when Auto Orchestration Module is not wired", async () => {
|
|
1586
1742
|
_resetPendingResolve();
|
|
1587
1743
|
|
|
@@ -4,7 +4,7 @@ import { existsSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
|
|
7
|
-
import { hasGitIndexLockForTest } from "../auto-start.ts";
|
|
7
|
+
import { hasGitIndexLockForTest, reconcileFlatPhaseBootstrapLayout } from "../auto-start.ts";
|
|
8
8
|
|
|
9
9
|
test("bootstrapAutoSession detects .git/index.lock without deleting it", (t) => {
|
|
10
10
|
const base = mkdtempSync(join(tmpdir(), "gsd-index-lock-"));
|
|
@@ -24,3 +24,23 @@ test("bootstrapAutoSession reports no index lock when the lock file is absent",
|
|
|
24
24
|
|
|
25
25
|
assert.equal(hasGitIndexLockForTest(base), false);
|
|
26
26
|
});
|
|
27
|
+
|
|
28
|
+
test("bootstrapAutoSession removes empty legacy milestones dir in flat-phase projects", (t) => {
|
|
29
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-flat-bootstrap-clean-"));
|
|
30
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
31
|
+
mkdirSync(join(base, ".gsd", "phases"), { recursive: true });
|
|
32
|
+
mkdirSync(join(base, ".gsd", "milestones"), { recursive: true });
|
|
33
|
+
|
|
34
|
+
assert.equal(reconcileFlatPhaseBootstrapLayout(base), true);
|
|
35
|
+
assert.equal(existsSync(join(base, ".gsd", "phases")), true);
|
|
36
|
+
assert.equal(existsSync(join(base, ".gsd", "milestones")), false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("bootstrapAutoSession creates phases instead of milestones for empty GSD state", (t) => {
|
|
40
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-flat-bootstrap-init-"));
|
|
41
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
42
|
+
|
|
43
|
+
assert.equal(reconcileFlatPhaseBootstrapLayout(base), true);
|
|
44
|
+
assert.equal(existsSync(join(base, ".gsd", "phases")), true);
|
|
45
|
+
assert.equal(existsSync(join(base, ".gsd", "milestones")), false);
|
|
46
|
+
});
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
import { describe, test } from "node:test";
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
|
-
import { mkdtempSync, mkdirSync, realpathSync, rmSync } from "node:fs";
|
|
6
|
+
import { mkdtempSync, mkdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { execSync } from "node:child_process";
|
|
7
8
|
import { join } from "node:path";
|
|
8
9
|
import { tmpdir } from "node:os";
|
|
9
10
|
|
|
@@ -96,6 +97,78 @@ describe("hook dispatch session workspace root", () => {
|
|
|
96
97
|
assert.equal(dispatched, true);
|
|
97
98
|
assert.deepEqual(newSessionOptions, { workspaceRoot: basePath });
|
|
98
99
|
});
|
|
100
|
+
|
|
101
|
+
test("dispatchHookUnit commits in-flight work when command context lacks newSession", async (t) => {
|
|
102
|
+
const originalCwd = process.cwd();
|
|
103
|
+
const basePath = realpathSync(mkdtempSync(join(tmpdir(), "gsd-hook-nosnap-")));
|
|
104
|
+
mkdirSync(join(basePath, ".gsd"), { recursive: true });
|
|
105
|
+
execSync("git init --initial-branch=main", { cwd: basePath, stdio: "ignore" });
|
|
106
|
+
execSync("git config user.email test@test.com", { cwd: basePath, stdio: "ignore" });
|
|
107
|
+
execSync("git config user.name Test", { cwd: basePath, stdio: "ignore" });
|
|
108
|
+
execSync("git commit --allow-empty -m init", { cwd: basePath, stdio: "ignore" });
|
|
109
|
+
|
|
110
|
+
autoSession.reset();
|
|
111
|
+
autoSession.active = true;
|
|
112
|
+
autoSession.basePath = basePath;
|
|
113
|
+
autoSession.originalBasePath = basePath;
|
|
114
|
+
// Simulate an in-flight unit whose executor already wrote files, then a hook
|
|
115
|
+
// fires with a command context missing newSession.
|
|
116
|
+
autoSession.currentUnit = {
|
|
117
|
+
type: "execute-task",
|
|
118
|
+
id: "M001/S01/T01",
|
|
119
|
+
startedAt: Date.now(),
|
|
120
|
+
};
|
|
121
|
+
t.after(() => {
|
|
122
|
+
try {
|
|
123
|
+
process.chdir(originalCwd);
|
|
124
|
+
} catch {
|
|
125
|
+
// best effort cleanup after cwd-sensitive dispatch tests
|
|
126
|
+
}
|
|
127
|
+
autoSession.reset();
|
|
128
|
+
rmSync(basePath, { recursive: true, force: true });
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const outputPath = join(basePath, "executor-output.txt");
|
|
132
|
+
writeFileSync(outputPath, "in-flight executor work\n", "utf-8");
|
|
133
|
+
|
|
134
|
+
// Command context intentionally lacks newSession — the guard must snapshot
|
|
135
|
+
// the dirty work before stopping instead of discarding it.
|
|
136
|
+
const ctx = {
|
|
137
|
+
ui: {
|
|
138
|
+
notify: () => {},
|
|
139
|
+
setStatus: () => {},
|
|
140
|
+
setWidget: () => {},
|
|
141
|
+
},
|
|
142
|
+
modelRegistry: {
|
|
143
|
+
getAvailable: () => [],
|
|
144
|
+
},
|
|
145
|
+
sessionManager: {
|
|
146
|
+
getSessionFile: () => join(basePath, "session.jsonl"),
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
const pi = {
|
|
150
|
+
sendMessage: () => {},
|
|
151
|
+
setModel: async () => true,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const dispatched = await dispatchHookUnit(
|
|
155
|
+
ctx as any,
|
|
156
|
+
pi as any,
|
|
157
|
+
"review",
|
|
158
|
+
"execute-task",
|
|
159
|
+
"M001/S01/T01",
|
|
160
|
+
"review the completed unit",
|
|
161
|
+
undefined,
|
|
162
|
+
basePath,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
assert.equal(dispatched, false);
|
|
166
|
+
const committed = execSync("git show HEAD:executor-output.txt", {
|
|
167
|
+
cwd: basePath,
|
|
168
|
+
encoding: "utf-8",
|
|
169
|
+
});
|
|
170
|
+
assert.equal(committed, "in-flight executor work\n");
|
|
171
|
+
});
|
|
99
172
|
});
|
|
100
173
|
|
|
101
174
|
describe("deep setup approval questions pause immediately", () => {
|