@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.
- package/README.md +10 -9
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto-model-selection.js +1 -0
- package/dist/resources/extensions/gsd/auto-start.js +1 -1
- package/dist/resources/extensions/gsd/auto-verification.js +25 -12
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +4 -3
- package/dist/resources/extensions/gsd/db/engine.js +8 -2
- package/dist/resources/extensions/gsd/db-canonical-foundation-schema.js +120 -0
- package/dist/resources/extensions/gsd/db-migration-steps.js +4 -0
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +12 -3
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +16 -4
- 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/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 +7 -7
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- 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 +7 -7
- 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/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 +12 -2
- package/packages/gsd-cloud/dist/background-cli.test.js +157 -80
- package/packages/gsd-cloud/dist/background-cli.test.js.map +1 -1
- package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cli.js +36 -12
- package/packages/gsd-cloud/dist/cli.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts +6 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.js +106 -24
- 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 +5 -0
- package/packages/gsd-cloud/dist/runtime-process.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/runtime-process.js +429 -61
- package/packages/gsd-cloud/dist/runtime-process.js.map +1 -1
- package/packages/gsd-cloud/dist/runtime-process.test.js +493 -47
- package/packages/gsd-cloud/dist/runtime-process.test.js.map +1 -1
- 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/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 +113 -2
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +84 -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/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-model-selection.ts +1 -0
- package/src/resources/extensions/gsd/auto-start.ts +1 -1
- package/src/resources/extensions/gsd/auto-verification.ts +46 -12
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +4 -3
- package/src/resources/extensions/gsd/db/engine.ts +9 -1
- package/src/resources/extensions/gsd/db-canonical-foundation-schema.ts +129 -0
- package/src/resources/extensions/gsd/db-migration-steps.ts +5 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +12 -3
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +15 -4
- 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/db-canonical-foundation.test.ts +459 -0
- package/src/resources/extensions/gsd/tests/db-engine-migrate-guards.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +64 -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/single-writer-invariant.test.ts +1 -0
- 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/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/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/{EAhnhYMt5UVOiG9-iPDjc → 8ub6oiEp2YnlOHQU20LJr}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{EAhnhYMt5UVOiG9-iPDjc → 8ub6oiEp2YnlOHQU20LJr}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Additive v31 authority, operation provenance, domain event, and outbox schema.
|
|
3
|
+
|
|
4
|
+
import type { DbAdapter } from "./db-adapter.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* v31 mapping:
|
|
8
|
+
* - schema_version remains DDL compatibility, not the domain revision.
|
|
9
|
+
* - audit_events remains optional operational telemetry, not domain history.
|
|
10
|
+
* - milestone_commit_attributions remains Git-specific, not operation provenance.
|
|
11
|
+
* - command_queue and runtime_kv remain coordination/cache surfaces, not an outbox.
|
|
12
|
+
*/
|
|
13
|
+
export function createCanonicalFoundationSchemaV31(db: DbAdapter): void {
|
|
14
|
+
db.exec(`
|
|
15
|
+
CREATE TABLE IF NOT EXISTS project_authority (
|
|
16
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
17
|
+
project_id TEXT NOT NULL UNIQUE,
|
|
18
|
+
project_root_realpath TEXT NOT NULL DEFAULT '',
|
|
19
|
+
revision INTEGER NOT NULL DEFAULT 0 CHECK (revision >= 0),
|
|
20
|
+
authority_epoch INTEGER NOT NULL DEFAULT 0 CHECK (authority_epoch >= 0),
|
|
21
|
+
created_at TEXT NOT NULL DEFAULT '',
|
|
22
|
+
updated_at TEXT NOT NULL DEFAULT ''
|
|
23
|
+
)
|
|
24
|
+
`);
|
|
25
|
+
|
|
26
|
+
db.exec(`
|
|
27
|
+
CREATE TABLE IF NOT EXISTS workflow_operations (
|
|
28
|
+
operation_id TEXT PRIMARY KEY,
|
|
29
|
+
project_id TEXT NOT NULL,
|
|
30
|
+
operation_type TEXT NOT NULL,
|
|
31
|
+
idempotency_key TEXT NOT NULL,
|
|
32
|
+
expected_revision INTEGER NOT NULL CHECK (expected_revision >= 0),
|
|
33
|
+
resulting_revision INTEGER NOT NULL CHECK (resulting_revision = expected_revision + 1),
|
|
34
|
+
expected_authority_epoch INTEGER NOT NULL CHECK (expected_authority_epoch >= 0),
|
|
35
|
+
resulting_authority_epoch INTEGER NOT NULL CHECK (
|
|
36
|
+
resulting_authority_epoch = expected_authority_epoch OR
|
|
37
|
+
resulting_authority_epoch = expected_authority_epoch + 1
|
|
38
|
+
),
|
|
39
|
+
actor_type TEXT NOT NULL,
|
|
40
|
+
actor_id TEXT DEFAULT NULL,
|
|
41
|
+
source_transport TEXT NOT NULL,
|
|
42
|
+
trace_id TEXT DEFAULT NULL,
|
|
43
|
+
turn_id TEXT DEFAULT NULL,
|
|
44
|
+
request_hash TEXT NOT NULL,
|
|
45
|
+
created_at TEXT NOT NULL,
|
|
46
|
+
UNIQUE (project_id, idempotency_key),
|
|
47
|
+
UNIQUE (project_id, resulting_revision),
|
|
48
|
+
UNIQUE (operation_id, project_id, resulting_revision, resulting_authority_epoch),
|
|
49
|
+
FOREIGN KEY (project_id) REFERENCES project_authority(project_id)
|
|
50
|
+
)
|
|
51
|
+
`);
|
|
52
|
+
|
|
53
|
+
db.exec(`
|
|
54
|
+
CREATE TABLE IF NOT EXISTS workflow_domain_events (
|
|
55
|
+
event_id TEXT PRIMARY KEY,
|
|
56
|
+
operation_id TEXT NOT NULL,
|
|
57
|
+
event_index INTEGER NOT NULL DEFAULT 0 CHECK (event_index >= 0),
|
|
58
|
+
project_id TEXT NOT NULL,
|
|
59
|
+
project_revision INTEGER NOT NULL CHECK (project_revision > 0),
|
|
60
|
+
authority_epoch INTEGER NOT NULL CHECK (authority_epoch >= 0),
|
|
61
|
+
event_type TEXT NOT NULL,
|
|
62
|
+
entity_type TEXT NOT NULL,
|
|
63
|
+
entity_id TEXT NOT NULL,
|
|
64
|
+
caused_by_event_id TEXT DEFAULT NULL,
|
|
65
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
66
|
+
created_at TEXT NOT NULL,
|
|
67
|
+
UNIQUE (operation_id, event_index),
|
|
68
|
+
FOREIGN KEY (operation_id, project_id, project_revision, authority_epoch)
|
|
69
|
+
REFERENCES workflow_operations(
|
|
70
|
+
operation_id, project_id, resulting_revision, resulting_authority_epoch
|
|
71
|
+
),
|
|
72
|
+
FOREIGN KEY (caused_by_event_id) REFERENCES workflow_domain_events(event_id)
|
|
73
|
+
)
|
|
74
|
+
`);
|
|
75
|
+
|
|
76
|
+
db.exec(`
|
|
77
|
+
CREATE TRIGGER IF NOT EXISTS trg_workflow_domain_events_immutable_update
|
|
78
|
+
BEFORE UPDATE ON workflow_domain_events
|
|
79
|
+
BEGIN
|
|
80
|
+
SELECT RAISE(ABORT, 'workflow domain events are immutable');
|
|
81
|
+
END
|
|
82
|
+
`);
|
|
83
|
+
db.exec(`
|
|
84
|
+
CREATE TRIGGER IF NOT EXISTS trg_workflow_domain_events_immutable_delete
|
|
85
|
+
BEFORE DELETE ON workflow_domain_events
|
|
86
|
+
BEGIN
|
|
87
|
+
SELECT RAISE(ABORT, 'workflow domain events are immutable');
|
|
88
|
+
END
|
|
89
|
+
`);
|
|
90
|
+
|
|
91
|
+
db.exec(`
|
|
92
|
+
CREATE TABLE IF NOT EXISTS workflow_outbox (
|
|
93
|
+
outbox_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
94
|
+
event_id TEXT NOT NULL,
|
|
95
|
+
destination TEXT NOT NULL,
|
|
96
|
+
available_at TEXT NOT NULL DEFAULT '',
|
|
97
|
+
attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
|
|
98
|
+
claimed_by TEXT DEFAULT NULL,
|
|
99
|
+
claim_expires_at TEXT DEFAULT NULL,
|
|
100
|
+
delivered_at TEXT DEFAULT NULL,
|
|
101
|
+
last_error TEXT DEFAULT NULL,
|
|
102
|
+
UNIQUE (event_id, destination),
|
|
103
|
+
FOREIGN KEY (event_id) REFERENCES workflow_domain_events(event_id)
|
|
104
|
+
)
|
|
105
|
+
`);
|
|
106
|
+
|
|
107
|
+
db.exec(`
|
|
108
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_operations_created
|
|
109
|
+
ON workflow_operations(project_id, created_at, operation_id)
|
|
110
|
+
`);
|
|
111
|
+
db.exec(`
|
|
112
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_domain_events_entity
|
|
113
|
+
ON workflow_domain_events(project_id, entity_type, entity_id, project_revision, event_index)
|
|
114
|
+
`);
|
|
115
|
+
db.exec(`
|
|
116
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_outbox_pending
|
|
117
|
+
ON workflow_outbox(delivered_at, available_at, outbox_id)
|
|
118
|
+
`);
|
|
119
|
+
|
|
120
|
+
db.exec(`
|
|
121
|
+
INSERT OR IGNORE INTO project_authority (
|
|
122
|
+
singleton, project_id, project_root_realpath,
|
|
123
|
+
revision, authority_epoch, created_at, updated_at
|
|
124
|
+
) VALUES (
|
|
125
|
+
1, lower(hex(randomblob(16))), '',
|
|
126
|
+
0, 0, '', ''
|
|
127
|
+
)
|
|
128
|
+
`);
|
|
129
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// File Purpose: Schema migration DDL steps for the GSD database facade.
|
|
3
3
|
|
|
4
4
|
import type { DbAdapter } from "./db-adapter.js";
|
|
5
|
+
import { createCanonicalFoundationSchemaV31 } from "./db-canonical-foundation-schema.js";
|
|
5
6
|
import { ensureColumn } from "./db-schema-metadata.js";
|
|
6
7
|
|
|
7
8
|
export function applyMigrationV2Artifacts(db: DbAdapter): void {
|
|
@@ -494,3 +495,7 @@ export function applyMigrationV30ReworkBriefs(db: DbAdapter): void {
|
|
|
494
495
|
db.exec("CREATE INDEX IF NOT EXISTS idx_rework_briefs_task ON rework_briefs(milestone_id, slice_id, task_id)");
|
|
495
496
|
db.exec("CREATE INDEX IF NOT EXISTS idx_rework_findings_status ON rework_brief_findings(brief_id, severity, status)");
|
|
496
497
|
}
|
|
498
|
+
|
|
499
|
+
export function applyMigrationV31CanonicalFoundation(db: DbAdapter): void {
|
|
500
|
+
createCanonicalFoundationSchemaV31(db);
|
|
501
|
+
}
|
|
@@ -124,11 +124,11 @@ Diagnostics record the file path, scope (global/project), severity (error/warnin
|
|
|
124
124
|
- `validation` — used for gate evaluation, roadmap reassessment, milestone validation, and doc rewrites. Falls back to `planning` if unset.
|
|
125
125
|
- `uat` — used for UAT runs. Falls back to `completion` if unset.
|
|
126
126
|
|
|
127
|
-
- `thinking`: per-phase reasoning effort (ADR-026), separate from `models`. Same phase keys as `models`. Values: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`. Thinking travels with the model — model choice and reasoning effort are independent controls, so you can run one model across phases at different reasoning levels.
|
|
127
|
+
- `thinking`: per-phase reasoning effort (ADR-026), separate from `models`. Same phase keys as `models`. Values: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. Thinking travels with the model — model choice and reasoning effort are independent controls, so you can run one model across phases at different reasoning levels.
|
|
128
128
|
- Two equivalent ways to set it: inline as `models.<phase>.thinking`, or as a separate `thinking:` block keyed by phase. For the same phase, the inline value wins over the block; project preferences win over global; a phase that's unset inherits via the same sibling chain as `models` (e.g. `discuss → planning`).
|
|
129
129
|
- If no thinking is configured for a phase, the session level (set via `/model`) is used, exactly as before — this is fully backward-compatible.
|
|
130
130
|
- `execute-task` (code-writing) has a measured reasoning floor of `medium`: at lower levels models stop planning edits and thrash on file re-reads. The floor applies to the session/default path. An **explicit** `execution` thinking level bypasses the floor and is honored verbatim (with a one-time advisory) — set `execution: low` deliberately if you want it.
|
|
131
|
-
- Levels a model can't support are clamped to the nearest supported level at dispatch and never sent to the provider, so a model/level mismatch never fails a unit mid-run. `xhigh` support
|
|
131
|
+
- Levels a model can't support are clamped to the nearest supported level at dispatch and never sent to the provider, so a model/level mismatch never fails a unit mid-run. `xhigh` and `max` support are model-dependent; current examples include `gpt-5.6-sol`, `gpt-5.6-terra`, and `gpt-5.6-luna`, which advertise both.
|
|
132
132
|
|
|
133
133
|
- `skill_staleness_days`: number — skills unused for this many days get deprioritized during discovery. Set to `0` to disable staleness tracking. Default: `60`.
|
|
134
134
|
|
|
@@ -357,24 +357,33 @@ In `"parent"` mode, slice/task `targetRepositories` default to the declared chil
|
|
|
357
357
|
- `"replace"` requires `prompt`.
|
|
358
358
|
- `"skip"` is valid with no additional fields.
|
|
359
359
|
|
|
360
|
+
- `planning_subagent_registry`: object — optional classification for custom planning agents. Each key is an agent id and must set:
|
|
361
|
+
- `read_only_specialist`: boolean — set to `true` to classify the custom agent as safe for planning dispatch.
|
|
362
|
+
|
|
360
363
|
- `planning_subagents`: object — project-local widening for read-only planning subagent dispatch. Supported unit keys are `plan-milestone` and `plan-slice`; each has:
|
|
361
364
|
- `allowed`: string[] — additional planning-safe agents for that unit.
|
|
362
365
|
|
|
363
|
-
Agents must
|
|
366
|
+
Agents must be built-in read-only planning specialists (`mnemo`, `scout`, `planner`, `reviewer`, `security`, or `tester`) or registered in `planning_subagent_registry` with `read_only_specialist: true`. The write gate still blocks source writes outside `.gsd/**`, unrestricted bash, stale subagent calls without agent identities, agents outside the read-only registry, and agents not listed for the active unit.
|
|
364
367
|
|
|
365
368
|
Example:
|
|
366
369
|
|
|
367
370
|
```yaml
|
|
371
|
+
planning_subagent_registry:
|
|
372
|
+
my-custom-planner:
|
|
373
|
+
read_only_specialist: true
|
|
374
|
+
|
|
368
375
|
planning_subagents:
|
|
369
376
|
plan-milestone:
|
|
370
377
|
allowed:
|
|
371
378
|
- scout
|
|
372
379
|
- planner
|
|
380
|
+
- my-custom-planner
|
|
373
381
|
- security
|
|
374
382
|
plan-slice:
|
|
375
383
|
allowed:
|
|
376
384
|
- scout
|
|
377
385
|
- planner
|
|
386
|
+
- my-custom-planner
|
|
378
387
|
- reviewer
|
|
379
388
|
- security
|
|
380
389
|
```
|
|
@@ -32,6 +32,7 @@ import { parseRoadmapSlices } from "./roadmap-slices.js";
|
|
|
32
32
|
import { parsePlan } from "./parsers-legacy.js";
|
|
33
33
|
import { parseSummary } from "./files.js";
|
|
34
34
|
import { LAYOUT_SEGMENTS } from "./layout-policy.js";
|
|
35
|
+
import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
|
|
35
36
|
|
|
36
37
|
const USER_AUTHORED_ARTIFACT_TYPES = new Set(["CONTEXT", "RESEARCH"]);
|
|
37
38
|
|
|
@@ -116,7 +117,8 @@ function checkboxDbStatusMilestoneIds(basePath: string, milestoneIds: string[]):
|
|
|
116
117
|
|
|
117
118
|
function checkProjectionCheckboxDbStatus(basePath: string, milestoneIds: string[], issues: DoctorIssue[]): void {
|
|
118
119
|
for (const milestoneId of milestoneIds) {
|
|
119
|
-
const
|
|
120
|
+
const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, milestoneId);
|
|
121
|
+
const roadmapPath = resolveMilestoneFile(artifactBasePath, milestoneId, "ROADMAP");
|
|
120
122
|
const slices = getMilestoneSlices(milestoneId);
|
|
121
123
|
|
|
122
124
|
if (roadmapPath && existsSync(roadmapPath)) {
|
|
@@ -142,7 +144,7 @@ function checkProjectionCheckboxDbStatus(basePath: string, milestoneIds: string[
|
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
for (const slice of slices) {
|
|
145
|
-
const planPath = resolveSliceFile(
|
|
147
|
+
const planPath = resolveSliceFile(artifactBasePath, milestoneId, slice.id, "PLAN");
|
|
146
148
|
if (!planPath || !existsSync(planPath)) continue;
|
|
147
149
|
try {
|
|
148
150
|
const plan = readFileSync(planPath, "utf-8");
|
|
@@ -192,7 +194,7 @@ function artifactExistsOnDisk(basePath: string, artifactPath: string, row?: Arti
|
|
|
192
194
|
return resolveArtifactDiskPath(basePath, artifactPath, row) !== null;
|
|
193
195
|
}
|
|
194
196
|
|
|
195
|
-
function resolveLiteralArtifactDiskPath(basePath: string, artifactPath: string): string | null {
|
|
197
|
+
function resolveLiteralArtifactDiskPath(basePath: string, artifactPath: string, row?: ArtifactRow): string | null {
|
|
196
198
|
const relativeArtifactPath = artifactPathRelativeToGsd(artifactPath);
|
|
197
199
|
if (isAbsolute(relativeArtifactPath)) {
|
|
198
200
|
return existsSync(relativeArtifactPath) ? relativeArtifactPath : null;
|
|
@@ -201,6 +203,15 @@ function resolveLiteralArtifactDiskPath(basePath: string, artifactPath: string):
|
|
|
201
203
|
const candidate = join(root, relativeArtifactPath);
|
|
202
204
|
if (isPathInside(root, candidate) && existsSync(candidate)) return candidate;
|
|
203
205
|
}
|
|
206
|
+
if (row?.milestone_id) {
|
|
207
|
+
const artifactBasePath = resolveCanonicalMilestoneRoot(basePath, row.milestone_id);
|
|
208
|
+
if (artifactBasePath !== basePath) {
|
|
209
|
+
for (const root of [gsdProjectionRoot(artifactBasePath), gsdRoot(artifactBasePath)]) {
|
|
210
|
+
const candidate = join(root, relativeArtifactPath);
|
|
211
|
+
if (isPathInside(root, candidate) && existsSync(candidate)) return candidate;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
204
215
|
return null;
|
|
205
216
|
}
|
|
206
217
|
|
|
@@ -243,7 +254,7 @@ function resolveArtifactDiskPath(basePath: string, artifactPath: string, row?: A
|
|
|
243
254
|
const flatPath = resolveFlatPhaseArtifactDiskPath(basePath, row);
|
|
244
255
|
if (flatPath) return flatPath;
|
|
245
256
|
}
|
|
246
|
-
return resolveLiteralArtifactDiskPath(basePath, artifactPath);
|
|
257
|
+
return resolveLiteralArtifactDiskPath(basePath, artifactPath, row);
|
|
247
258
|
}
|
|
248
259
|
|
|
249
260
|
function taskExistsInPlan(basePath: string, milestoneId: string, sliceId: string, taskId: string): boolean {
|
|
@@ -12,6 +12,12 @@ export interface ModelCostEntry {
|
|
|
12
12
|
inputPer1k: number;
|
|
13
13
|
/** Approximate cost per 1K output tokens in USD */
|
|
14
14
|
outputPer1k: number;
|
|
15
|
+
/** Input-token-based long-context pricing tiers, when published */
|
|
16
|
+
tiers?: Array<{
|
|
17
|
+
inputTokensAbove: number;
|
|
18
|
+
inputPer1k: number;
|
|
19
|
+
outputPer1k: number;
|
|
20
|
+
}>;
|
|
15
21
|
/** Last updated date */
|
|
16
22
|
updatedAt: string;
|
|
17
23
|
}
|
|
@@ -62,8 +68,9 @@ export const BUNDLED_COST_TABLE: ModelCostEntry[] = [
|
|
|
62
68
|
// GPT-5.5 API list price, also used for live Codex OAuth routing.
|
|
63
69
|
// Source: https://openai.com/api/pricing/
|
|
64
70
|
{ id: "gpt-5.5", inputPer1k: 0.005, outputPer1k: 0.03, updatedAt: "2026-04-23" },
|
|
65
|
-
|
|
66
|
-
{ id: "gpt-5.6", inputPer1k: 0.005, outputPer1k: 0.
|
|
71
|
+
{ id: "gpt-5.6-sol", inputPer1k: 0.005, outputPer1k: 0.03, tiers: [{ inputTokensAbove: 272000, inputPer1k: 0.01, outputPer1k: 0.045 }], updatedAt: "2026-07-11" },
|
|
72
|
+
{ id: "gpt-5.6-terra", inputPer1k: 0.0025, outputPer1k: 0.015, tiers: [{ inputTokensAbove: 272000, inputPer1k: 0.005, outputPer1k: 0.0225 }], updatedAt: "2026-07-11" },
|
|
73
|
+
{ id: "gpt-5.6-luna", inputPer1k: 0.001, outputPer1k: 0.006, tiers: [{ inputTokensAbove: 272000, inputPer1k: 0.002, outputPer1k: 0.009 }], updatedAt: "2026-07-11" },
|
|
67
74
|
|
|
68
75
|
// Google
|
|
69
76
|
{ id: "gemini-2.0-flash", inputPer1k: 0.0001, outputPer1k: 0.0004, updatedAt: "2025-03-15" },
|
|
@@ -80,7 +87,11 @@ export const BUNDLED_COST_TABLE: ModelCostEntry[] = [
|
|
|
80
87
|
export function lookupModelCost(modelId: string): ModelCostEntry | undefined {
|
|
81
88
|
const bareId = modelId.includes("/") ? modelId.split("/").pop()! : modelId;
|
|
82
89
|
return BUNDLED_COST_TABLE.find(e => e.id === bareId)
|
|
83
|
-
?? BUNDLED_COST_TABLE.find(e =>
|
|
90
|
+
?? BUNDLED_COST_TABLE.find(e =>
|
|
91
|
+
bareId.startsWith(`${e.id}-`) ||
|
|
92
|
+
bareId.startsWith(`${e.id}:`) ||
|
|
93
|
+
bareId.startsWith(`${e.id}@`)
|
|
94
|
+
);
|
|
84
95
|
}
|
|
85
96
|
|
|
86
97
|
/**
|
|
@@ -111,7 +111,9 @@ export const MODEL_CAPABILITY_TIER: Record<string, ComplexityTier> = {
|
|
|
111
111
|
"gpt-5.3-codex": "heavy",
|
|
112
112
|
"gpt-5.4": "heavy",
|
|
113
113
|
"gpt-5.5": "heavy",
|
|
114
|
-
"gpt-5.6": "heavy",
|
|
114
|
+
"gpt-5.6-sol": "heavy",
|
|
115
|
+
"gpt-5.6-terra": "heavy",
|
|
116
|
+
"gpt-5.6-luna": "heavy",
|
|
115
117
|
"o1": "heavy",
|
|
116
118
|
"o3": "heavy",
|
|
117
119
|
"o4-mini": "heavy",
|
|
@@ -151,7 +153,9 @@ const MODEL_COST_PER_1K_INPUT: Record<string, number> = {
|
|
|
151
153
|
"gpt-5.3-codex-spark": 0.0003,
|
|
152
154
|
"gpt-5.4": 0.005,
|
|
153
155
|
"gpt-5.5": 0.005,
|
|
154
|
-
"gpt-5.6": 0.005,
|
|
156
|
+
"gpt-5.6-sol": 0.005,
|
|
157
|
+
"gpt-5.6-terra": 0.0025,
|
|
158
|
+
"gpt-5.6-luna": 0.001,
|
|
155
159
|
"o4-mini": 0.005,
|
|
156
160
|
"o4-mini-deep-research": 0.005,
|
|
157
161
|
"gemini-2.0-flash": 0.0001,
|
|
@@ -202,9 +206,10 @@ export const MODEL_CAPABILITY_PROFILES: Record<string, ModelCapabilities> = {
|
|
|
202
206
|
// OpenAI's 2026-04-23 published eval deltas across coding, tool use, and long context.
|
|
203
207
|
// Source: https://openai.com/index/introducing-gpt-5-5/
|
|
204
208
|
"gpt-5.5": { coding: 96, debugging: 93, research: 89, reasoning: 95, speed: 42, longContext: 90, instruction: 93 },
|
|
205
|
-
// GPT-5.6
|
|
206
|
-
|
|
207
|
-
"gpt-5.6":
|
|
209
|
+
// GPT-5.6 profiles are provisional until official eval results are available.
|
|
210
|
+
"gpt-5.6-sol": { coding: 97, debugging: 94, research: 90, reasoning: 96, speed: 42, longContext: 91, instruction: 94 },
|
|
211
|
+
"gpt-5.6-terra": { coding: 96, debugging: 93, research: 89, reasoning: 95, speed: 48, longContext: 91, instruction: 93 },
|
|
212
|
+
"gpt-5.6-luna": { coding: 92, debugging: 88, research: 84, reasoning: 91, speed: 72, longContext: 91, instruction: 90 },
|
|
208
213
|
|
|
209
214
|
// ── OpenAI o-series (reasoning-first) ──────────────────────────────────────
|
|
210
215
|
"o1": { coding: 78, debugging: 82, research: 78, reasoning: 90, speed: 20, longContext: 65, instruction: 82 },
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* preference validation before any configured planning allowlist takes effect.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import type { PlanningSubagentRegistryConfig } from "./preferences-types.js";
|
|
10
|
+
|
|
9
11
|
const PLANNING_DISPATCH_AGENT_REGISTRY = {
|
|
10
12
|
mnemo: { readOnlySpecialist: true },
|
|
11
13
|
scout: { readOnlySpecialist: true },
|
|
@@ -21,11 +23,23 @@ export const ALLOWED_PLANNING_DISPATCH_AGENTS = new Set<string>(
|
|
|
21
23
|
.map(([agentId]) => agentId),
|
|
22
24
|
);
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
function configuredPlanningDispatchAgents(registry?: PlanningSubagentRegistryConfig): string[] {
|
|
27
|
+
return Object.entries(registry ?? {})
|
|
28
|
+
.filter(([, metadata]) => metadata.read_only_specialist === true)
|
|
29
|
+
.map(([agentId]) => agentId);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isReadOnlyPlanningDispatchAgent(
|
|
33
|
+
agentId: string,
|
|
34
|
+
registry?: PlanningSubagentRegistryConfig,
|
|
35
|
+
): boolean {
|
|
25
36
|
const metadata = PLANNING_DISPATCH_AGENT_REGISTRY[agentId as keyof typeof PLANNING_DISPATCH_AGENT_REGISTRY];
|
|
26
|
-
return metadata?.readOnlySpecialist === true;
|
|
37
|
+
return metadata?.readOnlySpecialist === true || registry?.[agentId]?.read_only_specialist === true;
|
|
27
38
|
}
|
|
28
39
|
|
|
29
|
-
export function allowedPlanningDispatchAgentsList(): string {
|
|
30
|
-
return
|
|
40
|
+
export function allowedPlanningDispatchAgentsList(registry?: PlanningSubagentRegistryConfig): string {
|
|
41
|
+
return Array.from(new Set([
|
|
42
|
+
...ALLOWED_PLANNING_DISPATCH_AGENTS,
|
|
43
|
+
...configuredPlanningDispatchAgents(registry),
|
|
44
|
+
])).join(", ");
|
|
31
45
|
}
|
|
@@ -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]
|