@opengsd/gsd-pi 1.5.0-dev.4fb2d460 → 1.5.0-dev.c4c8885f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +114 -36
- package/dist/resources/extensions/gsd/activity-log.js +1 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +16 -10
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +53 -15
- package/dist/resources/extensions/gsd/auto-model-selection.js +28 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +4 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +29 -117
- package/dist/resources/extensions/gsd/auto-start.js +53 -30
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +12 -2
- package/dist/resources/extensions/gsd/auto-timers.js +9 -2
- package/dist/resources/extensions/gsd/auto.js +97 -31
- package/dist/resources/extensions/gsd/compat/compat-marker.js +44 -0
- package/dist/resources/extensions/gsd/config-overlay.js +7 -2
- package/dist/resources/extensions/gsd/db/engine.js +33 -0
- package/dist/resources/extensions/gsd/db-adapter.js +30 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/dist/resources/extensions/gsd/journal.js +1 -1
- package/dist/resources/extensions/gsd/markdown-renderer.js +34 -91
- package/dist/resources/extensions/gsd/metrics.js +2 -2
- package/dist/resources/extensions/gsd/paths.js +70 -49
- package/dist/resources/extensions/gsd/post-unit-hooks.js +27 -0
- package/dist/resources/extensions/gsd/preferences-models.js +66 -15
- package/dist/resources/extensions/gsd/preferences-validation.js +66 -10
- package/dist/resources/extensions/gsd/rule-registry.js +106 -10
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +1 -10
- package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +3 -24
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +10 -0
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -5
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +16 -10
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -8
- package/dist/resources/extensions/gsd/uat-dispatch.js +94 -0
- package/dist/resources/extensions/gsd/uok/audit-toggle.js +31 -2
- package/dist/resources/extensions/gsd/uok/audit.js +3 -0
- package/dist/resources/extensions/gsd/uok/gate-runner.js +22 -30
- package/dist/resources/extensions/gsd/uok/gitops.js +18 -15
- package/dist/resources/extensions/gsd/uok/kernel.js +120 -46
- package/dist/resources/extensions/gsd/uok/model-policy.js +17 -14
- package/dist/resources/extensions/gsd/workflow-event-ledger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
- package/dist/resources/extensions/gsd/workflow-projections.js +9 -26
- package/dist/resources/extensions/gsd/workspace.js +5 -14
- package/dist/resources/extensions/shared/gsd-browser-cli.js +29 -0
- 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 +10 -10
- 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/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +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 +10 -10
- package/dist/web/standalone/.next/server/chunks/{1128.js → 1991.js} +1 -1
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- 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/worktree-cli-create.d.ts +10 -0
- package/dist/worktree-cli-create.js +11 -0
- package/dist/worktree-cli-format.d.ts +12 -0
- package/dist/worktree-cli-format.js +38 -0
- package/dist/worktree-cli-plan.d.ts +16 -0
- package/dist/worktree-cli-plan.js +19 -0
- package/dist/worktree-cli-session.d.ts +13 -0
- package/dist/worktree-cli-session.js +18 -0
- package/dist/worktree-cli-status.d.ts +35 -0
- package/dist/worktree-cli-status.js +54 -0
- package/dist/worktree-cli.d.ts +1 -16
- package/dist/worktree-cli.js +28 -122
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
- package/integrations/hermes/pyproject.toml +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/dist/orchestrator-agent.d.ts +21 -0
- package/packages/daemon/dist/orchestrator-agent.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator-agent.js +113 -0
- package/packages/daemon/dist/orchestrator-agent.js.map +1 -0
- package/packages/daemon/dist/orchestrator-tools.d.ts +12 -0
- package/packages/daemon/dist/orchestrator-tools.d.ts.map +1 -0
- package/packages/daemon/dist/orchestrator-tools.js +137 -0
- package/packages/daemon/dist/orchestrator-tools.js.map +1 -0
- package/packages/daemon/dist/orchestrator.d.ts +4 -56
- package/packages/daemon/dist/orchestrator.d.ts.map +1 -1
- package/packages/daemon/dist/orchestrator.js +13 -303
- package/packages/daemon/dist/orchestrator.js.map +1 -1
- package/packages/daemon/dist/orchestrator.test.js +11 -1
- package/packages/daemon/dist/orchestrator.test.js.map +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +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/package.json +1 -1
- package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +146 -33
- package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +25 -0
- package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +93 -0
- package/src/resources/extensions/gsd/activity-log.ts +1 -1
- package/src/resources/extensions/gsd/auto/orchestrator.ts +17 -11
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +59 -14
- package/src/resources/extensions/gsd/auto-model-selection.ts +33 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +4 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +53 -113
- package/src/resources/extensions/gsd/auto-start.ts +57 -28
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -2
- package/src/resources/extensions/gsd/auto-timers.ts +9 -2
- package/src/resources/extensions/gsd/auto.ts +103 -30
- package/src/resources/extensions/gsd/compat/compat-marker.ts +47 -0
- package/src/resources/extensions/gsd/config-overlay.ts +7 -1
- package/src/resources/extensions/gsd/db/engine.ts +36 -0
- package/src/resources/extensions/gsd/db-adapter.ts +29 -3
- package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
- package/src/resources/extensions/gsd/journal.ts +1 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +52 -103
- package/src/resources/extensions/gsd/metrics.ts +2 -2
- package/src/resources/extensions/gsd/paths.ts +87 -48
- package/src/resources/extensions/gsd/post-unit-hooks.ts +32 -0
- package/src/resources/extensions/gsd/preferences-models.ts +74 -20
- package/src/resources/extensions/gsd/preferences-types.ts +24 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +77 -9
- package/src/resources/extensions/gsd/rule-registry.ts +111 -9
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +1 -9
- package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +2 -22
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +14 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +41 -2
- package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +48 -1
- package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +29 -1
- package/src/resources/extensions/gsd/tests/auto-timeout-recovery-supervisor-inflight-guard.test.ts +159 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/compat-marker.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/db-adapter.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/db-writable-probe.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +72 -32
- package/src/resources/extensions/gsd/tests/integration/paths.test.ts +88 -1
- package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +269 -0
- package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +49 -1
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +95 -1
- package/src/resources/extensions/gsd/tests/sibling-model-fallbacks.test.ts +189 -0
- package/src/resources/extensions/gsd/tests/single-field-model-thinking.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/supervisor-model-swap-inflight-guard.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/supervisor-model-swap-thinking-clamp.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/uat-dispatch.test.ts +211 -0
- package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +226 -2
- package/src/resources/extensions/gsd/tests/uok-model-policy.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +213 -2
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +9 -5
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +19 -8
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +13 -9
- package/src/resources/extensions/gsd/types.ts +23 -4
- package/src/resources/extensions/gsd/uat-dispatch.ts +176 -0
- package/src/resources/extensions/gsd/uok/audit-toggle.ts +32 -2
- package/src/resources/extensions/gsd/uok/audit.ts +2 -0
- package/src/resources/extensions/gsd/uok/gate-runner.ts +26 -36
- package/src/resources/extensions/gsd/uok/gitops.ts +21 -18
- package/src/resources/extensions/gsd/uok/kernel.ts +169 -48
- package/src/resources/extensions/gsd/uok/model-policy.ts +20 -17
- package/src/resources/extensions/gsd/workflow-event-ledger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
- package/src/resources/extensions/gsd/workflow-projections.ts +9 -24
- package/src/resources/extensions/gsd/workspace.ts +5 -21
- package/src/resources/extensions/shared/gsd-browser-cli.ts +29 -0
- /package/dist/web/standalone/.next/static/{gXoWec4UNqcR0tkuHwYCr → NPMNi2HV8BuK3baQsEUhk}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{gXoWec4UNqcR0tkuHwYCr → NPMNi2HV8BuK3baQsEUhk}/_ssgManifest.js +0 -0
|
@@ -36,10 +36,10 @@ import { emitWorktreeOrphaned } from "./worktree-telemetry.js";
|
|
|
36
36
|
import { queryJournal } from "./journal.js";
|
|
37
37
|
import { initMetrics } from "./metrics.js";
|
|
38
38
|
import { initRoutingHistory } from "./routing-history.js";
|
|
39
|
-
import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
|
|
39
|
+
import { restoreHookState, resetHookState, reconcileRestoredHookDispatch } from "./post-unit-hooks.js";
|
|
40
40
|
import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
|
|
41
41
|
import { snapshotSkills } from "./skill-discovery.js";
|
|
42
|
-
import { isDbAvailable, getMilestone, getAllMilestones, insertMilestone, updateMilestoneStatus } from "./gsd-db.js";
|
|
42
|
+
import { isDbAvailable, probeDbWritable, getMilestone, getAllMilestones, insertMilestone, updateMilestoneStatus } from "./gsd-db.js";
|
|
43
43
|
import { getWorkflowDatabaseStatus, openExistingWorkflowDatabase, openWorkflowDatabase, resolveProjectRootDbPath, } from "./db-workspace.js";
|
|
44
44
|
import { isClosedStatus } from "./status-guards.js";
|
|
45
45
|
import { classifyMilestoneSummaryContent } from "./milestone-summary-classifier.js";
|
|
@@ -119,36 +119,42 @@ export function reconcileProjectMilestonesFromDisk(basePath) {
|
|
|
119
119
|
export function reconcileMergedMilestonesFromJournal(basePath) {
|
|
120
120
|
if (!isDbAvailable())
|
|
121
121
|
return 0;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
122
|
+
try {
|
|
123
|
+
const mergedAtByMilestone = new Map();
|
|
124
|
+
for (const entry of queryJournal(basePath, { eventType: "worktree-merged" })) {
|
|
125
|
+
const data = entry.data ?? {};
|
|
126
|
+
const milestoneId = typeof data.milestoneId === "string" ? data.milestoneId : null;
|
|
127
|
+
if (!milestoneId)
|
|
128
|
+
continue;
|
|
129
|
+
if (data.conflict === true)
|
|
130
|
+
continue;
|
|
131
|
+
const endedAt = typeof data.endedAt === "string" ? data.endedAt : entry.ts;
|
|
132
|
+
const previous = mergedAtByMilestone.get(milestoneId);
|
|
133
|
+
if (!previous || endedAt > previous)
|
|
134
|
+
mergedAtByMilestone.set(milestoneId, endedAt);
|
|
135
|
+
}
|
|
136
|
+
let closed = 0;
|
|
137
|
+
for (const [milestoneId, completedAt] of mergedAtByMilestone) {
|
|
138
|
+
const existing = getMilestone(milestoneId);
|
|
139
|
+
if (!existing) {
|
|
140
|
+
insertMilestone({ id: milestoneId, title: milestoneId, status: "complete" });
|
|
141
|
+
updateMilestoneStatus(milestoneId, "complete", completedAt);
|
|
142
|
+
closed++;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (!isClosedStatus(existing.status)) {
|
|
146
|
+
updateMilestoneStatus(milestoneId, "complete", completedAt);
|
|
147
|
+
closed++;
|
|
148
|
+
}
|
|
147
149
|
}
|
|
150
|
+
if (closed > 0)
|
|
151
|
+
invalidateAllCaches();
|
|
152
|
+
return closed;
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
logWarning("bootstrap", `merged-milestone journal reconciliation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
156
|
+
return 0;
|
|
148
157
|
}
|
|
149
|
-
if (closed > 0)
|
|
150
|
-
invalidateAllCaches();
|
|
151
|
-
return closed;
|
|
152
158
|
}
|
|
153
159
|
export function decideSurvivorAction(hasSurvivorBranch, phase) {
|
|
154
160
|
if (!hasSurvivorBranch)
|
|
@@ -1254,6 +1260,10 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
1254
1260
|
s.unitLifetimeDispatches.clear();
|
|
1255
1261
|
resetHookState();
|
|
1256
1262
|
restoreHookState(base);
|
|
1263
|
+
// A restored activeHook has no live dispatch (the sidecar queue is not
|
|
1264
|
+
// persisted); re-enqueue it so the hook runs instead of blocking the next
|
|
1265
|
+
// unrelated unit's close-out (#1246).
|
|
1266
|
+
reconcileRestoredHookDispatch(base, s.sidecarQueue);
|
|
1257
1267
|
resetProactiveHealing();
|
|
1258
1268
|
// Notify user on health level transitions (green→yellow→red and back)
|
|
1259
1269
|
setLevelChangeCallback((_from, to, summary) => {
|
|
@@ -1377,6 +1387,19 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
1377
1387
|
ctx.ui.notify(`SQLite database exists but failed to open: ${gsdDbPath}. ${phaseHint}${errorDetail}.${providerHint}`, "error");
|
|
1378
1388
|
return releaseLockAndReturn();
|
|
1379
1389
|
}
|
|
1390
|
+
// Gate: confirm the handle is actually writable, not just open. A
|
|
1391
|
+
// schema-current DB does zero writes during open, so a read-only /
|
|
1392
|
+
// DBMOVED handle otherwise passes the check above and only fails much
|
|
1393
|
+
// later at the first authoritative write (the uok-kernel-enter audit)
|
|
1394
|
+
// with an opaque "readonly database" error (#1234).
|
|
1395
|
+
if (existsSync(gsdDbPath) && isDbAvailable()) {
|
|
1396
|
+
const writable = probeDbWritable();
|
|
1397
|
+
if (!writable.ok) {
|
|
1398
|
+
const detail = writable.detail ? ` (${writable.detail})` : "";
|
|
1399
|
+
ctx.ui.notify(`SQLite database is not writable: ${gsdDbPath}.${detail} Check file/WAL permissions or reopen a stale handle before running auto-mode.`, "error");
|
|
1400
|
+
return releaseLockAndReturn();
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1380
1403
|
// Initialize metrics
|
|
1381
1404
|
initMetrics(s.basePath);
|
|
1382
1405
|
// Initialize routing history
|
|
@@ -8,6 +8,8 @@ import { resolveExpectedArtifactPath, diagnoseExpectedArtifact, verifyExpectedAr
|
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
9
|
import { bumpAndResolveSynthetic } from "./auto/resolve.js";
|
|
10
10
|
import { finalizeProjectResearchTimeout } from "./project-research-policy.js";
|
|
11
|
+
import { applySupervisorModelIfConfigured } from "./auto-model-selection.js";
|
|
12
|
+
import { getInFlightToolCount } from "./auto-tool-tracking.js";
|
|
11
13
|
export async function recoverTimedOutUnit(ctx, pi, unitType, unitId, reason, rctx) {
|
|
12
14
|
// Note on turn epoch: the bump is intentionally NOT unconditional at
|
|
13
15
|
// function entry. Two branches below (the "steering retry" paths) keep
|
|
@@ -79,11 +81,15 @@ export async function recoverTimedOutUnit(ctx, pi, unitType, unitId, reason, rct
|
|
|
79
81
|
"Immediately finish the required durable output for this unit.",
|
|
80
82
|
"If full completion is impossible, write the partial artifact/state needed for recovery and make the blocker explicit.",
|
|
81
83
|
];
|
|
84
|
+
const recoveryTrigger = getInFlightToolCount() === 0;
|
|
85
|
+
if (recoveryTrigger) {
|
|
86
|
+
await applySupervisorModelIfConfigured(ctx, pi, basePath);
|
|
87
|
+
}
|
|
82
88
|
pi.sendMessage({
|
|
83
89
|
customType: "gsd-auto-timeout-recovery",
|
|
84
90
|
display: verbose,
|
|
85
91
|
content: steeringLines.join("\n"),
|
|
86
|
-
}, { triggerTurn:
|
|
92
|
+
}, { triggerTurn: recoveryTrigger, deliverAs: "steer" });
|
|
87
93
|
ctx.ui.notify(`${reason === "idle" ? "Idle" : "Timeout"} recovery: steering ${unitType} ${unitId} to finish durable output (attempt ${attemptNumber}, session ${recoveryAttempts + 1}/${maxRecoveryAttempts}).`, "warning");
|
|
88
94
|
return "recovered";
|
|
89
95
|
}
|
|
@@ -176,11 +182,15 @@ export async function recoverTimedOutUnit(ctx, pi, unitType, unitId, reason, rct
|
|
|
176
182
|
"Write the required artifact now.",
|
|
177
183
|
"If blocked, write the partial artifact and explicitly record the blocker instead of going silent.",
|
|
178
184
|
];
|
|
185
|
+
const recoveryTrigger = getInFlightToolCount() === 0;
|
|
186
|
+
if (recoveryTrigger) {
|
|
187
|
+
await applySupervisorModelIfConfigured(ctx, pi, basePath);
|
|
188
|
+
}
|
|
179
189
|
pi.sendMessage({
|
|
180
190
|
customType: "gsd-auto-timeout-recovery",
|
|
181
191
|
display: verbose,
|
|
182
192
|
content: steeringLines.join("\n"),
|
|
183
|
-
}, { triggerTurn:
|
|
193
|
+
}, { triggerTurn: recoveryTrigger, deliverAs: "steer" });
|
|
184
194
|
ctx.ui.notify(`${reason === "idle" ? "Idle" : "Timeout"} recovery: steering ${unitType} ${unitId} to produce ${expected} (attempt ${attemptNumber}, session ${recoveryAttempts + 1}/${maxRecoveryAttempts}).`, "warning");
|
|
185
195
|
return "recovered";
|
|
186
196
|
}
|
|
@@ -11,6 +11,7 @@ import { resolveAutoSupervisorConfig } from "./preferences.js";
|
|
|
11
11
|
import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
|
|
12
12
|
import { getInFlightToolCount, getOldestInFlightToolStart, clearInFlightTools, hasInteractiveToolInFlight, } from "./auto-tool-tracking.js";
|
|
13
13
|
import { detectWorkingTreeActivity } from "./auto-supervisor.js";
|
|
14
|
+
import { applySupervisorModelIfConfigured } from "./auto-model-selection.js";
|
|
14
15
|
import { closeoutUnit } from "./auto-unit-closeout.js";
|
|
15
16
|
import { saveActivityLog } from "./activity-log.js";
|
|
16
17
|
import { recoverTimedOutUnit } from "./auto-timeout-recovery.js";
|
|
@@ -113,7 +114,7 @@ export function startUnitSupervision(sctx) {
|
|
|
113
114
|
? supervisor.stalled_tool_timeout_minutes * 60 * 1000
|
|
114
115
|
: idleTimeoutMs;
|
|
115
116
|
// ── 1. Soft timeout warning ──
|
|
116
|
-
s.wrapupWarningHandle = setTimeout(() => {
|
|
117
|
+
s.wrapupWarningHandle = setTimeout(async () => {
|
|
117
118
|
s.wrapupWarningHandle = null;
|
|
118
119
|
if (!s.active || !s.currentUnit)
|
|
119
120
|
return;
|
|
@@ -125,6 +126,9 @@ export function startUnitSupervision(sctx) {
|
|
|
125
126
|
// Triggering during active tool calls causes tool results to be skipped
|
|
126
127
|
// with "Skipped due to queued user message", leading to provider errors (#3512).
|
|
127
128
|
const softTrigger = getInFlightToolCount() === 0;
|
|
129
|
+
if (softTrigger) {
|
|
130
|
+
await applySupervisorModelIfConfigured(ctx, pi, s.basePath);
|
|
131
|
+
}
|
|
128
132
|
pi.sendMessage({
|
|
129
133
|
customType: "gsd-auto-wrapup",
|
|
130
134
|
display: s.verbose,
|
|
@@ -291,7 +295,7 @@ export function startUnitSupervision(sctx) {
|
|
|
291
295
|
}
|
|
292
296
|
const executorContextWindow = resolveExecutorContextWindow(ctx.modelRegistry, prefs, ctx.model?.contextWindow, ctx.model?.provider);
|
|
293
297
|
const continueHereThreshold = computeBudgets(executorContextWindow).continueThresholdPercent;
|
|
294
|
-
s.continueHereHandle = setInterval(() => {
|
|
298
|
+
s.continueHereHandle = setInterval(async () => {
|
|
295
299
|
if (!s.active || !s.currentUnit || !s.cmdCtx)
|
|
296
300
|
return;
|
|
297
301
|
const runtime = readUnitRuntimeRecord(s.basePath, unitType, unitId);
|
|
@@ -316,6 +320,9 @@ export function startUnitSupervision(sctx) {
|
|
|
316
320
|
}
|
|
317
321
|
// Only trigger a new turn if no tools are currently in flight (#3512).
|
|
318
322
|
const contextTrigger = getInFlightToolCount() === 0;
|
|
323
|
+
if (contextTrigger) {
|
|
324
|
+
await applySupervisorModelIfConfigured(ctx, pi, s.basePath);
|
|
325
|
+
}
|
|
319
326
|
pi.sendMessage({
|
|
320
327
|
customType: "gsd-auto-wrapup",
|
|
321
328
|
display: s.verbose,
|
|
@@ -30,9 +30,10 @@ import { playNotificationBell, sendDesktopNotification } from "./notifications.j
|
|
|
30
30
|
import { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction, HARD_CONTEXT_REROOT_THRESHOLD_PERCENT, resolveCompactionThresholdPercent, shouldRerootStepSessionForContext, shouldWarnStepSessionForContext, } from "./auto-budget.js";
|
|
31
31
|
import { markToolStart as _markToolStart, markToolEnd as _markToolEnd, getOldestInFlightToolAgeMs as _getOldestInFlightToolAgeMs, clearInFlightTools, isToolInvocationError, isQueuedUserMessageSkip, isDeterministicPolicyError, } from "./auto-tool-tracking.js";
|
|
32
32
|
import { closeoutUnit } from "./auto-unit-closeout.js";
|
|
33
|
-
import { selectAndApplyModel, resolveModelId, clearToolBaseline } from "./auto-model-selection.js";
|
|
33
|
+
import { selectAndApplyModel, resolveModelId, clearToolBaseline, isModelUnavailable, applyThinkingLevelForModel } from "./auto-model-selection.js";
|
|
34
|
+
import { resolveModelWithFallbacksForUnit } from "./preferences-models.js";
|
|
34
35
|
import { resetRoutingHistory, recordOutcome } from "./routing-history.js";
|
|
35
|
-
import { resetHookState, runPreDispatchHooks, restoreHookState, clearPersistedHookState, } from "./post-unit-hooks.js";
|
|
36
|
+
import { resetHookState, runPreDispatchHooks, restoreHookState, reconcileRestoredHookDispatch, clearPersistedHookState, } from "./post-unit-hooks.js";
|
|
36
37
|
import { runGSDDoctor, rebuildState } from "./doctor.js";
|
|
37
38
|
import { preDispatchHealthGate, resetProactiveHealing, setLevelChangeCallback, } from "./doctor-proactive.js";
|
|
38
39
|
import { clearSkillSnapshot } from "./skill-discovery.js";
|
|
@@ -332,11 +333,24 @@ function withDetachedAutoKeepalive(run) {
|
|
|
332
333
|
}
|
|
333
334
|
export const _withDetachedAutoKeepaliveForTest = withDetachedAutoKeepalive;
|
|
334
335
|
export function startAutoDetached(ctx, pi, base, verboseMode, options) {
|
|
335
|
-
void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch((err) => {
|
|
336
|
+
void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch(async (err) => {
|
|
336
337
|
const message = getErrorMessage(err);
|
|
337
338
|
ctx.ui.notify(`Auto-start failed: ${message}`, "error");
|
|
338
339
|
logWarning("engine", `auto start error: ${message}`, { file: "auto.ts" });
|
|
339
340
|
debugLog("auto-start-failed", { error: message });
|
|
341
|
+
// Backstop cleanup (#1235): if startAuto threw after auto-mode was activated
|
|
342
|
+
// (e.g. an exception during bootstrap, before the loop's try/finally could
|
|
343
|
+
// run cleanupAfterLoopExit), s.active and the on-disk auto lock leak, so the
|
|
344
|
+
// re-entry guard silently no-ops every later /gsd auto until restart. Clear
|
|
345
|
+
// the leaked activation here so a failed start is always recoverable.
|
|
346
|
+
if (s.active) {
|
|
347
|
+
try {
|
|
348
|
+
await cleanupAfterLoopExit(ctx);
|
|
349
|
+
}
|
|
350
|
+
catch (cleanupErr) {
|
|
351
|
+
logWarning("engine", `auto start cleanup failed: ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}`, { file: "auto.ts" });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
340
354
|
});
|
|
341
355
|
}
|
|
342
356
|
/** Returns true if the project is configured for `isolation:worktree` mode. */
|
|
@@ -2175,6 +2189,10 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
2175
2189
|
ctx.ui.setWidget("gsd-health", undefined);
|
|
2176
2190
|
ctx.ui.notify(s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "info");
|
|
2177
2191
|
restoreHookState(s.basePath);
|
|
2192
|
+
// A restored activeHook has no live dispatch (the sidecar queue is not
|
|
2193
|
+
// persisted); re-enqueue it so the hook runs instead of blocking the next
|
|
2194
|
+
// unrelated unit's close-out (#1246).
|
|
2195
|
+
reconcileRestoredHookDispatch(s.basePath, s.sidecarQueue);
|
|
2178
2196
|
// Re-sync managed resources on resume so long-lived auto sessions pick up
|
|
2179
2197
|
// bundled extension updates before resume-time verification/state logic runs.
|
|
2180
2198
|
// GSD_PKG_ROOT is set by loader.ts and points to the gsd-pi package root.
|
|
@@ -2228,15 +2246,19 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
2228
2246
|
debugLog("resume-orchestration-resume", { error: err instanceof Error ? err.message : String(err) });
|
|
2229
2247
|
}
|
|
2230
2248
|
startAutoCommandPolling(s.basePath);
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2249
|
+
try {
|
|
2250
|
+
await runAutoLoopWithUok({
|
|
2251
|
+
ctx,
|
|
2252
|
+
pi,
|
|
2253
|
+
s,
|
|
2254
|
+
deps: loopDeps,
|
|
2255
|
+
runKernelLoop: runUokKernelLoop,
|
|
2256
|
+
runLegacyLoop: runLegacyAutoLoop,
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
finally {
|
|
2260
|
+
await cleanupAfterLoopExit(ctx);
|
|
2261
|
+
}
|
|
2240
2262
|
return;
|
|
2241
2263
|
}
|
|
2242
2264
|
// ── Fresh start path — delegated to auto-start.ts ──
|
|
@@ -2252,8 +2274,16 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
2252
2274
|
// for crash-recovery fidelity (#5405).
|
|
2253
2275
|
registerAutoWorkerForSession(s, base);
|
|
2254
2276
|
const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps, freshStartAssessment);
|
|
2255
|
-
if (!ready)
|
|
2277
|
+
if (!ready) {
|
|
2278
|
+
// bootstrapAutoSession sets s.active = true (auto-start.ts) before several
|
|
2279
|
+
// post-activation bail-outs return false without a loop ever running (e.g.
|
|
2280
|
+
// the SQLite-unavailable gate). Without this, s.active and the auto lock
|
|
2281
|
+
// leak and every later /gsd auto silently no-ops via the re-entry guard
|
|
2282
|
+
// (#1235). Run the canonical cleanup so a failed bootstrap stays recoverable.
|
|
2283
|
+
if (s.active)
|
|
2284
|
+
await cleanupAfterLoopExit(ctx);
|
|
2256
2285
|
return;
|
|
2286
|
+
}
|
|
2257
2287
|
// Build scope after bootstrap has populated s.basePath / s.originalBasePath /
|
|
2258
2288
|
// s.currentMilestoneId (including worktree setup inside bootstrapAutoSession).
|
|
2259
2289
|
rebuildScope(s.basePath, s.currentMilestoneId);
|
|
@@ -2277,15 +2307,19 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
2277
2307
|
}
|
|
2278
2308
|
startAutoCommandPolling(s.basePath);
|
|
2279
2309
|
// Dispatch the first unit
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2310
|
+
try {
|
|
2311
|
+
await runAutoLoopWithUok({
|
|
2312
|
+
ctx,
|
|
2313
|
+
pi,
|
|
2314
|
+
s,
|
|
2315
|
+
deps: loopDeps,
|
|
2316
|
+
runKernelLoop: runUokKernelLoop,
|
|
2317
|
+
runLegacyLoop: runLegacyAutoLoop,
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
finally {
|
|
2321
|
+
await cleanupAfterLoopExit(ctx);
|
|
2322
|
+
}
|
|
2289
2323
|
}
|
|
2290
2324
|
// describeNextUnit is imported from auto-dashboard.ts and re-exported
|
|
2291
2325
|
export { describeNextUnit } from "./auto-dashboard.js";
|
|
@@ -2402,20 +2436,52 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
|
|
|
2402
2436
|
startedAt: hookStartedAt,
|
|
2403
2437
|
workspaceRoot: s.basePath,
|
|
2404
2438
|
});
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2439
|
+
// Resolve the hook's model honoring the configured `fallbacks[]` chain
|
|
2440
|
+
// (#1229). The manual trigger path bypasses selectAndApplyModel, so resolve
|
|
2441
|
+
// the primary→fallbacks order here and apply the first candidate available in
|
|
2442
|
+
// the registry — a blocked or unconfigured primary then transparently drops
|
|
2443
|
+
// to a fallback instead of silently reverting to the session model.
|
|
2444
|
+
const availableModels = ctx.modelRegistry.getAvailable();
|
|
2445
|
+
const availableModelIds = availableModels.map((m) => `${m.provider}/${m.id}`);
|
|
2446
|
+
const hookModelConfig = resolveModelWithFallbacksForUnit(hookUnitType, targetBasePath, availableModelIds);
|
|
2447
|
+
const modelCandidates = hookModelConfig
|
|
2448
|
+
? [hookModelConfig.primary, ...hookModelConfig.fallbacks]
|
|
2449
|
+
: hookModel
|
|
2450
|
+
? [hookModel]
|
|
2451
|
+
: [];
|
|
2452
|
+
if (modelCandidates.length > 0) {
|
|
2453
|
+
let applied = false;
|
|
2454
|
+
for (const candidate of modelCandidates) {
|
|
2455
|
+
const match = resolveModelId(candidate, availableModels, ctx.model?.provider);
|
|
2456
|
+
if (!match)
|
|
2457
|
+
continue;
|
|
2458
|
+
// Skip models the runtime has marked blocked or temporarily unavailable
|
|
2459
|
+
// (e.g. a primary that just tripped a provider limit) so the configured
|
|
2460
|
+
// fallbacks[] chain actually engages — parity with auto-mode's
|
|
2461
|
+
// selectAndApplyModel, which consults the same blocked-models store (#1229).
|
|
2462
|
+
if (isModelUnavailable(targetBasePath, match.provider, match.id))
|
|
2463
|
+
continue;
|
|
2409
2464
|
try {
|
|
2410
|
-
await pi.setModel(match)
|
|
2465
|
+
if (await pi.setModel(match)) {
|
|
2466
|
+
// The manual trigger path bypasses selectAndApplyModel, so apply the
|
|
2467
|
+
// hook's per-field `thinking` (from `post_unit_hooks[].model`'s object
|
|
2468
|
+
// form) here against the just-set model rather than leaving the hook at
|
|
2469
|
+
// the session level (#1269). Absent → session level, unchanged.
|
|
2470
|
+
if (hookModelConfig?.thinking) {
|
|
2471
|
+
applyThinkingLevelForModel(pi, hookModelConfig.thinking, match, ctx);
|
|
2472
|
+
}
|
|
2473
|
+
applied = true;
|
|
2474
|
+
break;
|
|
2475
|
+
}
|
|
2411
2476
|
}
|
|
2412
2477
|
catch (err) {
|
|
2413
|
-
/* non-fatal */
|
|
2414
|
-
logWarning("dispatch", `hook model set failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
2478
|
+
/* non-fatal — try the next fallback */
|
|
2479
|
+
logWarning("dispatch", `hook model set failed for ${candidate}: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
2415
2480
|
}
|
|
2416
2481
|
}
|
|
2417
|
-
|
|
2418
|
-
ctx.ui.notify(`Hook model
|
|
2482
|
+
if (!applied) {
|
|
2483
|
+
ctx.ui.notify(`Hook model${modelCandidates.length > 1 ? "s" : ""} "${modelCandidates.join(", ")}" not available. ` +
|
|
2484
|
+
`Falling back to current session model. ` +
|
|
2419
2485
|
`Ensure the model is defined in models.json and has auth configured.`, "warning");
|
|
2420
2486
|
}
|
|
2421
2487
|
}
|
|
@@ -98,6 +98,50 @@ export function writeCompatMarker(basePath, marker) {
|
|
|
98
98
|
writeFileSync(tmp, JSON.stringify(marker, null, 2), "utf-8");
|
|
99
99
|
renameSync(tmp, path);
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Remove projection entries whose backing file no longer exists on disk.
|
|
103
|
+
*
|
|
104
|
+
* gsd-pi never deletes marker entries on its own: both drift detectors skip
|
|
105
|
+
* files missing from disk (`if (!existsSync(abs)) continue;`) and the write-time
|
|
106
|
+
* flush only ever adds or refreshes entries. So when a phase directory is
|
|
107
|
+
* renamed or removed (e.g. `phases/29-new-milestone-m029/` →
|
|
108
|
+
* `phases/29-frontend-code-debt-cleanup/`), the old projection paths linger in
|
|
109
|
+
* `.compat.json` forever as phantom entries pointing at directories that no
|
|
110
|
+
* longer exist (#1257). They never reconcile, and `.compat.json` keeps drifting
|
|
111
|
+
* from disk reality so `git status` stops being a reliable proxy for "did the
|
|
112
|
+
* engine touch my plans?".
|
|
113
|
+
*
|
|
114
|
+
* This prunes those orphaned entries across the `.gsd/` projections and the
|
|
115
|
+
* `.planning/` projections/passthrough maps. It is safe: a missing-file entry is
|
|
116
|
+
* inert (every detector already ignores it), and if the file is later
|
|
117
|
+
* re-projected the write-time flush / unseeded-file detection re-seeds an
|
|
118
|
+
* accurate baseline.
|
|
119
|
+
*
|
|
120
|
+
* Returns the number of entries removed; when nothing is orphaned the marker is
|
|
121
|
+
* left untouched (no needless write, no `lastProjectedAt` churn).
|
|
122
|
+
*/
|
|
123
|
+
export function pruneOrphanedProjectionEntries(basePath) {
|
|
124
|
+
if (!existsSync(compatMarkerPath(basePath)))
|
|
125
|
+
return 0;
|
|
126
|
+
const marker = readCompatMarker(basePath);
|
|
127
|
+
let removed = 0;
|
|
128
|
+
const pruneMap = (map, root) => {
|
|
129
|
+
for (const relPath of Object.keys(map)) {
|
|
130
|
+
if (!existsSync(join(basePath, root, relPath))) {
|
|
131
|
+
delete map[relPath];
|
|
132
|
+
removed++;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
pruneMap(marker.projections, ".gsd");
|
|
137
|
+
if (marker.planning) {
|
|
138
|
+
pruneMap(marker.planning.projections, ".planning");
|
|
139
|
+
pruneMap(marker.planning.passthrough, ".planning");
|
|
140
|
+
}
|
|
141
|
+
if (removed > 0)
|
|
142
|
+
writeCompatMarker(basePath, marker);
|
|
143
|
+
return removed;
|
|
144
|
+
}
|
|
101
145
|
function quarantine(basePath, raw) {
|
|
102
146
|
const path = compatMarkerPath(basePath);
|
|
103
147
|
const badPath = `${path}.bad-${Date.now()}`;
|
|
@@ -121,8 +121,13 @@ function collectConfigSections(options) {
|
|
|
121
121
|
if (prefs?.auto_supervisor) {
|
|
122
122
|
const sup = resolveAutoSupervisorConfig();
|
|
123
123
|
const supRows = [];
|
|
124
|
-
if (sup.model)
|
|
125
|
-
|
|
124
|
+
if (sup.model) {
|
|
125
|
+
let modelVal = sup.model;
|
|
126
|
+
if (sup.modelFallbacks && sup.modelFallbacks.length > 0) {
|
|
127
|
+
modelVal += ` \u2192 ${sup.modelFallbacks.join(" \u2192 ")}`;
|
|
128
|
+
}
|
|
129
|
+
supRows.push({ label: "Model", value: modelVal });
|
|
130
|
+
}
|
|
126
131
|
supRows.push({ label: "Soft timeout", value: `${sup.soft_timeout_minutes}m` });
|
|
127
132
|
supRows.push({ label: "Idle timeout", value: `${sup.idle_timeout_minutes}m` });
|
|
128
133
|
supRows.push({ label: "Stalled tool timeout", value: `${sup.stalled_tool_timeout_minutes}m` });
|
|
@@ -801,3 +801,36 @@ export function getDb() {
|
|
|
801
801
|
export function getDbOrNull() {
|
|
802
802
|
return currentDb;
|
|
803
803
|
}
|
|
804
|
+
/**
|
|
805
|
+
* Confirm the open handle can actually write, not just that it opened.
|
|
806
|
+
*
|
|
807
|
+
* A schema-current database performs zero writes during open, so a handle that
|
|
808
|
+
* opened successfully but is not writable (read-only file, WAL/SHM permission
|
|
809
|
+
* mismatch, or a stale/moved handle → SQLITE_READONLY_DBMOVED) otherwise passes
|
|
810
|
+
* the open-only availability check and only fails much later at the first real
|
|
811
|
+
* write. The probe forces a genuine page write by re-writing the current
|
|
812
|
+
* `PRAGMA user_version` value back inside an IMMEDIATE transaction: a bare
|
|
813
|
+
* `BEGIN IMMEDIATE; ROLLBACK` is not sufficient because a moved handle does not
|
|
814
|
+
* fail until a page is actually dirtied. The value is unchanged, so the probe
|
|
815
|
+
* is idempotent, and the transaction is rolled back so nothing persists.
|
|
816
|
+
*/
|
|
817
|
+
export function probeDbWritable() {
|
|
818
|
+
const db = currentDb;
|
|
819
|
+
if (!db)
|
|
820
|
+
return { ok: false, detail: "No database is open." };
|
|
821
|
+
try {
|
|
822
|
+
const row = db.prepare("PRAGMA user_version").get();
|
|
823
|
+
const current = typeof row?.user_version === "number" ? row.user_version : 0;
|
|
824
|
+
db.exec("BEGIN IMMEDIATE");
|
|
825
|
+
try {
|
|
826
|
+
db.exec(`PRAGMA user_version = ${current}`);
|
|
827
|
+
}
|
|
828
|
+
finally {
|
|
829
|
+
db.exec("ROLLBACK");
|
|
830
|
+
}
|
|
831
|
+
return { ok: true };
|
|
832
|
+
}
|
|
833
|
+
catch (err) {
|
|
834
|
+
return { ok: false, detail: err.message };
|
|
835
|
+
}
|
|
836
|
+
}
|
|
@@ -11,19 +11,46 @@ export function normalizeDbRow(row) {
|
|
|
11
11
|
export function normalizeDbRows(rows) {
|
|
12
12
|
return rows.map((row) => normalizeDbRow(row));
|
|
13
13
|
}
|
|
14
|
+
function isPlainObject(value) {
|
|
15
|
+
if (value === null || typeof value !== "object")
|
|
16
|
+
return false;
|
|
17
|
+
const proto = Object.getPrototypeOf(value);
|
|
18
|
+
return proto === Object.prototype || proto === null;
|
|
19
|
+
}
|
|
20
|
+
// node:sqlite accepts binding-object keys with or without the ':'/'@'/'$'
|
|
21
|
+
// prefix, but better-sqlite3 accepts only bare keys — strip the prefix so
|
|
22
|
+
// call sites written against node:sqlite also work on the fallback provider.
|
|
23
|
+
function normalizeBindParams(params) {
|
|
24
|
+
return params.map((param) => {
|
|
25
|
+
if (!isPlainObject(param))
|
|
26
|
+
return param;
|
|
27
|
+
let changed = false;
|
|
28
|
+
const normalized = {};
|
|
29
|
+
for (const [key, value] of Object.entries(param)) {
|
|
30
|
+
if (key.length > 1 && (key[0] === ":" || key[0] === "@" || key[0] === "$")) {
|
|
31
|
+
normalized[key.slice(1)] = value;
|
|
32
|
+
changed = true;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
normalized[key] = value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return changed ? normalized : param;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
14
41
|
export function createDbAdapter(rawDb) {
|
|
15
42
|
const db = rawDb;
|
|
16
43
|
const stmtCache = new Map();
|
|
17
44
|
function wrapStmt(raw) {
|
|
18
45
|
return {
|
|
19
46
|
run(...params) {
|
|
20
|
-
return raw.run(...params);
|
|
47
|
+
return raw.run(...normalizeBindParams(params));
|
|
21
48
|
},
|
|
22
49
|
get(...params) {
|
|
23
|
-
return normalizeDbRow(raw.get(...params));
|
|
50
|
+
return normalizeDbRow(raw.get(...normalizeBindParams(params)));
|
|
24
51
|
},
|
|
25
52
|
all(...params) {
|
|
26
|
-
return normalizeDbRows(raw.all(...params));
|
|
53
|
+
return normalizeDbRows(raw.all(...normalizeBindParams(params)));
|
|
27
54
|
},
|
|
28
55
|
};
|
|
29
56
|
}
|
|
@@ -138,7 +138,7 @@ Diagnostics record the file path, scope (global/project), severity (error/warnin
|
|
|
138
138
|
- `off` — skill discovery is disabled entirely.
|
|
139
139
|
|
|
140
140
|
- `auto_supervisor`: configures the auto-mode supervisor that monitors agent progress and enforces timeouts. Keys:
|
|
141
|
-
- `model`: model
|
|
141
|
+
- `model`: model to use for the supervisor process (defaults to the currently active model). Accepts a bare model ID **or** the same `{ model, provider?, fallbacks? }` object form as `models.<phase>`, so it honors `fallbacks[]` on a transient provider trip.
|
|
142
142
|
- `soft_timeout_minutes`: minutes before the supervisor issues a soft warning (default: 20).
|
|
143
143
|
- `idle_timeout_minutes`: minutes of inactivity before the supervisor intervenes (default: 10).
|
|
144
144
|
- `hard_timeout_minutes`: minutes before the supervisor forces termination (default: 30).
|
|
@@ -228,7 +228,7 @@ In `"parent"` mode, slice/task `targetRepositories` default to the declared chil
|
|
|
228
228
|
- `enabled`: boolean — set `false` to force sequential task execution. Default: `true`.
|
|
229
229
|
- `max_parallel`: number — maximum tasks to dispatch in one batch, range `1`-`8`. Default: `2`.
|
|
230
230
|
- `isolation_mode`: `"same-tree"` — currently the only supported value.
|
|
231
|
-
- `subagent_model`:
|
|
231
|
+
- `subagent_model`: optional model override for reactive task subagents. Accepts a bare model ID **or** the `{ model, provider?, fallbacks? }` object form (parity with `models.<phase>`). Falls back to the `models.subagent` routing when omitted.
|
|
232
232
|
|
|
233
233
|
- `remote_questions`: route interactive questions to Slack/Discord for headless auto-mode. Keys:
|
|
234
234
|
- `channel`: `"slack"` or `"discord"` — channel type.
|
|
@@ -326,7 +326,7 @@ In `"parent"` mode, slice/task `targetRepositories` default to the declared chil
|
|
|
326
326
|
- `after`: string[] — unit types that trigger this hook (e.g., `["execute-task"]`).
|
|
327
327
|
- `prompt`: string — prompt sent to the LLM. Supports `{milestoneId}`, `{sliceId}`, `{taskId}` substitutions.
|
|
328
328
|
- `max_cycles`: number — max times this hook fires per trigger (default: 1, max: 10).
|
|
329
|
-
- `model`:
|
|
329
|
+
- `model`: optional model override. Accepts a bare model ID **or** the same `{ model, provider?, fallbacks? }` object form as `models.<phase>`. With `fallbacks[]`, a transient trip of the primary provider falls back to the next entry instead of hard-failing the hook — which, for a `blocking` hook with `on_block: { action: pause }`, would otherwise pause the whole run.
|
|
330
330
|
- `artifact`: string — expected output file name (relative to task/slice dir). Hook is skipped if file already exists (idempotent).
|
|
331
331
|
- `criticality`: `"advisory"` or `"blocking"` — advisory preserves current best-effort behavior; blocking requires clean hook completion plus a valid outcome verdict before auto-mode advances. Default: `"advisory"`.
|
|
332
332
|
- `retry_on`: string — if this file is produced instead of the artifact, re-run the trigger unit then re-run hooks.
|