@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
|
@@ -73,10 +73,10 @@ import { emitWorktreeOrphaned } from "./worktree-telemetry.js";
|
|
|
73
73
|
import { queryJournal } from "./journal.js";
|
|
74
74
|
import { initMetrics } from "./metrics.js";
|
|
75
75
|
import { initRoutingHistory } from "./routing-history.js";
|
|
76
|
-
import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
|
|
76
|
+
import { restoreHookState, resetHookState, reconcileRestoredHookDispatch } from "./post-unit-hooks.js";
|
|
77
77
|
import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
|
|
78
78
|
import { snapshotSkills } from "./skill-discovery.js";
|
|
79
|
-
import { isDbAvailable, getMilestone, getAllMilestones, insertMilestone, updateMilestoneStatus } from "./gsd-db.js";
|
|
79
|
+
import { isDbAvailable, probeDbWritable, getMilestone, getAllMilestones, insertMilestone, updateMilestoneStatus } from "./gsd-db.js";
|
|
80
80
|
import {
|
|
81
81
|
getWorkflowDatabaseStatus,
|
|
82
82
|
openExistingWorkflowDatabase,
|
|
@@ -205,35 +205,43 @@ export function reconcileProjectMilestonesFromDisk(basePath: string): number {
|
|
|
205
205
|
export function reconcileMergedMilestonesFromJournal(basePath: string): number {
|
|
206
206
|
if (!isDbAvailable()) return 0;
|
|
207
207
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
let closed = 0;
|
|
221
|
-
for (const [milestoneId, completedAt] of mergedAtByMilestone) {
|
|
222
|
-
const existing = getMilestone(milestoneId);
|
|
223
|
-
if (!existing) {
|
|
224
|
-
insertMilestone({ id: milestoneId, title: milestoneId, status: "complete" });
|
|
225
|
-
updateMilestoneStatus(milestoneId, "complete", completedAt);
|
|
226
|
-
closed++;
|
|
227
|
-
continue;
|
|
208
|
+
try {
|
|
209
|
+
const mergedAtByMilestone = new Map<string, string>();
|
|
210
|
+
for (const entry of queryJournal(basePath, { eventType: "worktree-merged" })) {
|
|
211
|
+
const data = entry.data ?? {};
|
|
212
|
+
const milestoneId = typeof data.milestoneId === "string" ? data.milestoneId : null;
|
|
213
|
+
if (!milestoneId) continue;
|
|
214
|
+
if (data.conflict === true) continue;
|
|
215
|
+
|
|
216
|
+
const endedAt = typeof data.endedAt === "string" ? data.endedAt : entry.ts;
|
|
217
|
+
const previous = mergedAtByMilestone.get(milestoneId);
|
|
218
|
+
if (!previous || endedAt > previous) mergedAtByMilestone.set(milestoneId, endedAt);
|
|
228
219
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
220
|
+
|
|
221
|
+
let closed = 0;
|
|
222
|
+
for (const [milestoneId, completedAt] of mergedAtByMilestone) {
|
|
223
|
+
const existing = getMilestone(milestoneId);
|
|
224
|
+
if (!existing) {
|
|
225
|
+
insertMilestone({ id: milestoneId, title: milestoneId, status: "complete" });
|
|
226
|
+
updateMilestoneStatus(milestoneId, "complete", completedAt);
|
|
227
|
+
closed++;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (!isClosedStatus(existing.status)) {
|
|
231
|
+
updateMilestoneStatus(milestoneId, "complete", completedAt);
|
|
232
|
+
closed++;
|
|
233
|
+
}
|
|
232
234
|
}
|
|
233
|
-
}
|
|
234
235
|
|
|
235
|
-
|
|
236
|
-
|
|
236
|
+
if (closed > 0) invalidateAllCaches();
|
|
237
|
+
return closed;
|
|
238
|
+
} catch (err) {
|
|
239
|
+
logWarning(
|
|
240
|
+
"bootstrap",
|
|
241
|
+
`merged-milestone journal reconciliation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
242
|
+
);
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
237
245
|
}
|
|
238
246
|
|
|
239
247
|
/**
|
|
@@ -1624,6 +1632,10 @@ export async function bootstrapAutoSession(
|
|
|
1624
1632
|
s.unitLifetimeDispatches.clear();
|
|
1625
1633
|
resetHookState();
|
|
1626
1634
|
restoreHookState(base);
|
|
1635
|
+
// A restored activeHook has no live dispatch (the sidecar queue is not
|
|
1636
|
+
// persisted); re-enqueue it so the hook runs instead of blocking the next
|
|
1637
|
+
// unrelated unit's close-out (#1246).
|
|
1638
|
+
reconcileRestoredHookDispatch(base, s.sidecarQueue);
|
|
1627
1639
|
resetProactiveHealing();
|
|
1628
1640
|
// Notify user on health level transitions (green→yellow→red and back)
|
|
1629
1641
|
setLevelChangeCallback((_from, to, summary) => {
|
|
@@ -1778,6 +1790,23 @@ export async function bootstrapAutoSession(
|
|
|
1778
1790
|
return releaseLockAndReturn();
|
|
1779
1791
|
}
|
|
1780
1792
|
|
|
1793
|
+
// Gate: confirm the handle is actually writable, not just open. A
|
|
1794
|
+
// schema-current DB does zero writes during open, so a read-only /
|
|
1795
|
+
// DBMOVED handle otherwise passes the check above and only fails much
|
|
1796
|
+
// later at the first authoritative write (the uok-kernel-enter audit)
|
|
1797
|
+
// with an opaque "readonly database" error (#1234).
|
|
1798
|
+
if (existsSync(gsdDbPath) && isDbAvailable()) {
|
|
1799
|
+
const writable = probeDbWritable();
|
|
1800
|
+
if (!writable.ok) {
|
|
1801
|
+
const detail = writable.detail ? ` (${writable.detail})` : "";
|
|
1802
|
+
ctx.ui.notify(
|
|
1803
|
+
`SQLite database is not writable: ${gsdDbPath}.${detail} Check file/WAL permissions or reopen a stale handle before running auto-mode.`,
|
|
1804
|
+
"error",
|
|
1805
|
+
);
|
|
1806
|
+
return releaseLockAndReturn();
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1781
1810
|
// Initialize metrics
|
|
1782
1811
|
initMetrics(s.basePath);
|
|
1783
1812
|
|
|
@@ -21,6 +21,8 @@ import { existsSync } from "node:fs";
|
|
|
21
21
|
|
|
22
22
|
import { bumpAndResolveSynthetic } from "./auto/resolve.js";
|
|
23
23
|
import { finalizeProjectResearchTimeout } from "./project-research-policy.js";
|
|
24
|
+
import { applySupervisorModelIfConfigured } from "./auto-model-selection.js";
|
|
25
|
+
import { getInFlightToolCount } from "./auto-tool-tracking.js";
|
|
24
26
|
|
|
25
27
|
export interface RecoveryContext {
|
|
26
28
|
basePath: string;
|
|
@@ -122,13 +124,17 @@ export async function recoverTimedOutUnit(
|
|
|
122
124
|
"If full completion is impossible, write the partial artifact/state needed for recovery and make the blocker explicit.",
|
|
123
125
|
];
|
|
124
126
|
|
|
127
|
+
const recoveryTrigger = getInFlightToolCount() === 0;
|
|
128
|
+
if (recoveryTrigger) {
|
|
129
|
+
await applySupervisorModelIfConfigured(ctx, pi, basePath);
|
|
130
|
+
}
|
|
125
131
|
pi.sendMessage(
|
|
126
132
|
{
|
|
127
133
|
customType: "gsd-auto-timeout-recovery",
|
|
128
134
|
display: verbose,
|
|
129
135
|
content: steeringLines.join("\n"),
|
|
130
136
|
},
|
|
131
|
-
{ triggerTurn:
|
|
137
|
+
{ triggerTurn: recoveryTrigger, deliverAs: "steer" },
|
|
132
138
|
);
|
|
133
139
|
ctx.ui.notify(
|
|
134
140
|
`${reason === "idle" ? "Idle" : "Timeout"} recovery: steering ${unitType} ${unitId} to finish durable output (attempt ${attemptNumber}, session ${recoveryAttempts + 1}/${maxRecoveryAttempts}).`,
|
|
@@ -249,13 +255,17 @@ export async function recoverTimedOutUnit(
|
|
|
249
255
|
"If blocked, write the partial artifact and explicitly record the blocker instead of going silent.",
|
|
250
256
|
];
|
|
251
257
|
|
|
258
|
+
const recoveryTrigger = getInFlightToolCount() === 0;
|
|
259
|
+
if (recoveryTrigger) {
|
|
260
|
+
await applySupervisorModelIfConfigured(ctx, pi, basePath);
|
|
261
|
+
}
|
|
252
262
|
pi.sendMessage(
|
|
253
263
|
{
|
|
254
264
|
customType: "gsd-auto-timeout-recovery",
|
|
255
265
|
display: verbose,
|
|
256
266
|
content: steeringLines.join("\n"),
|
|
257
267
|
},
|
|
258
|
-
{ triggerTurn:
|
|
268
|
+
{ triggerTurn: recoveryTrigger, deliverAs: "steer" },
|
|
259
269
|
);
|
|
260
270
|
ctx.ui.notify(
|
|
261
271
|
`${reason === "idle" ? "Idle" : "Timeout"} recovery: steering ${unitType} ${unitId} to produce ${expected} (attempt ${attemptNumber}, session ${recoveryAttempts + 1}/${maxRecoveryAttempts}).`,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
hasInteractiveToolInFlight,
|
|
20
20
|
} from "./auto-tool-tracking.js";
|
|
21
21
|
import { detectWorkingTreeActivity } from "./auto-supervisor.js";
|
|
22
|
+
import { applySupervisorModelIfConfigured } from "./auto-model-selection.js";
|
|
22
23
|
import { closeoutUnit, type CloseoutOptions } from "./auto-unit-closeout.js";
|
|
23
24
|
import { saveActivityLog } from "./activity-log.js";
|
|
24
25
|
import { recoverTimedOutUnit, type RecoveryContext } from "./auto-timeout-recovery.js";
|
|
@@ -158,7 +159,7 @@ export function startUnitSupervision(sctx: SupervisionContext): void {
|
|
|
158
159
|
: idleTimeoutMs;
|
|
159
160
|
|
|
160
161
|
// ── 1. Soft timeout warning ──
|
|
161
|
-
s.wrapupWarningHandle = setTimeout(() => {
|
|
162
|
+
s.wrapupWarningHandle = setTimeout(async () => {
|
|
162
163
|
s.wrapupWarningHandle = null;
|
|
163
164
|
if (!s.active || !s.currentUnit) return;
|
|
164
165
|
writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
|
|
@@ -169,6 +170,9 @@ export function startUnitSupervision(sctx: SupervisionContext): void {
|
|
|
169
170
|
// Triggering during active tool calls causes tool results to be skipped
|
|
170
171
|
// with "Skipped due to queued user message", leading to provider errors (#3512).
|
|
171
172
|
const softTrigger = getInFlightToolCount() === 0;
|
|
173
|
+
if (softTrigger) {
|
|
174
|
+
await applySupervisorModelIfConfigured(ctx, pi, s.basePath);
|
|
175
|
+
}
|
|
172
176
|
pi.sendMessage(
|
|
173
177
|
{
|
|
174
178
|
customType: "gsd-auto-wrapup",
|
|
@@ -351,7 +355,7 @@ export function startUnitSupervision(sctx: SupervisionContext): void {
|
|
|
351
355
|
ctx.model?.provider,
|
|
352
356
|
);
|
|
353
357
|
const continueHereThreshold = computeBudgets(executorContextWindow).continueThresholdPercent;
|
|
354
|
-
s.continueHereHandle = setInterval(() => {
|
|
358
|
+
s.continueHereHandle = setInterval(async () => {
|
|
355
359
|
if (!s.active || !s.currentUnit || !s.cmdCtx) return;
|
|
356
360
|
const runtime = readUnitRuntimeRecord(s.basePath, unitType, unitId);
|
|
357
361
|
if (runtime?.continueHereFired) return;
|
|
@@ -382,6 +386,9 @@ export function startUnitSupervision(sctx: SupervisionContext): void {
|
|
|
382
386
|
|
|
383
387
|
// Only trigger a new turn if no tools are currently in flight (#3512).
|
|
384
388
|
const contextTrigger = getInFlightToolCount() === 0;
|
|
389
|
+
if (contextTrigger) {
|
|
390
|
+
await applySupervisorModelIfConfigured(ctx, pi, s.basePath);
|
|
391
|
+
}
|
|
385
392
|
pi.sendMessage(
|
|
386
393
|
{
|
|
387
394
|
customType: "gsd-auto-wrapup",
|
|
@@ -116,7 +116,8 @@ import {
|
|
|
116
116
|
} from "./auto-tool-tracking.js";
|
|
117
117
|
import { closeoutUnit } from "./auto-unit-closeout.js";
|
|
118
118
|
import { recoverTimedOutUnit } from "./auto-timeout-recovery.js";
|
|
119
|
-
import { selectAndApplyModel, resolveModelId, clearToolBaseline } from "./auto-model-selection.js";
|
|
119
|
+
import { selectAndApplyModel, resolveModelId, clearToolBaseline, isModelUnavailable, applyThinkingLevelForModel } from "./auto-model-selection.js";
|
|
120
|
+
import { resolveModelWithFallbacksForUnit } from "./preferences-models.js";
|
|
120
121
|
import { resetRoutingHistory, recordOutcome } from "./routing-history.js";
|
|
121
122
|
import {
|
|
122
123
|
checkPostUnitHooks,
|
|
@@ -127,6 +128,7 @@ import {
|
|
|
127
128
|
runPreDispatchHooks,
|
|
128
129
|
persistHookState,
|
|
129
130
|
restoreHookState,
|
|
131
|
+
reconcileRestoredHookDispatch,
|
|
130
132
|
clearPersistedHookState,
|
|
131
133
|
} from "./post-unit-hooks.js";
|
|
132
134
|
import { runGSDDoctor, rebuildState } from "./doctor.js";
|
|
@@ -615,11 +617,27 @@ export function startAutoDetached(
|
|
|
615
617
|
milestoneLock?: string | null;
|
|
616
618
|
},
|
|
617
619
|
): void {
|
|
618
|
-
void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch((err) => {
|
|
620
|
+
void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch(async (err) => {
|
|
619
621
|
const message = getErrorMessage(err);
|
|
620
622
|
ctx.ui.notify(`Auto-start failed: ${message}`, "error");
|
|
621
623
|
logWarning("engine", `auto start error: ${message}`, { file: "auto.ts" });
|
|
622
624
|
debugLog("auto-start-failed", { error: message });
|
|
625
|
+
// Backstop cleanup (#1235): if startAuto threw after auto-mode was activated
|
|
626
|
+
// (e.g. an exception during bootstrap, before the loop's try/finally could
|
|
627
|
+
// run cleanupAfterLoopExit), s.active and the on-disk auto lock leak, so the
|
|
628
|
+
// re-entry guard silently no-ops every later /gsd auto until restart. Clear
|
|
629
|
+
// the leaked activation here so a failed start is always recoverable.
|
|
630
|
+
if (s.active) {
|
|
631
|
+
try {
|
|
632
|
+
await cleanupAfterLoopExit(ctx);
|
|
633
|
+
} catch (cleanupErr) {
|
|
634
|
+
logWarning(
|
|
635
|
+
"engine",
|
|
636
|
+
`auto start cleanup failed: ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}`,
|
|
637
|
+
{ file: "auto.ts" },
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
623
641
|
});
|
|
624
642
|
}
|
|
625
643
|
|
|
@@ -2827,6 +2845,10 @@ export async function startAuto(
|
|
|
2827
2845
|
"info",
|
|
2828
2846
|
);
|
|
2829
2847
|
restoreHookState(s.basePath);
|
|
2848
|
+
// A restored activeHook has no live dispatch (the sidecar queue is not
|
|
2849
|
+
// persisted); re-enqueue it so the hook runs instead of blocking the next
|
|
2850
|
+
// unrelated unit's close-out (#1246).
|
|
2851
|
+
reconcileRestoredHookDispatch(s.basePath, s.sidecarQueue);
|
|
2830
2852
|
// Re-sync managed resources on resume so long-lived auto sessions pick up
|
|
2831
2853
|
// bundled extension updates before resume-time verification/state logic runs.
|
|
2832
2854
|
// GSD_PKG_ROOT is set by loader.ts and points to the gsd-pi package root.
|
|
@@ -2895,15 +2917,18 @@ export async function startAuto(
|
|
|
2895
2917
|
debugLog("resume-orchestration-resume", { error: err instanceof Error ? err.message : String(err) });
|
|
2896
2918
|
}
|
|
2897
2919
|
startAutoCommandPolling(s.basePath);
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2920
|
+
try {
|
|
2921
|
+
await runAutoLoopWithUok({
|
|
2922
|
+
ctx,
|
|
2923
|
+
pi,
|
|
2924
|
+
s,
|
|
2925
|
+
deps: loopDeps,
|
|
2926
|
+
runKernelLoop: runUokKernelLoop,
|
|
2927
|
+
runLegacyLoop: runLegacyAutoLoop,
|
|
2928
|
+
});
|
|
2929
|
+
} finally {
|
|
2930
|
+
await cleanupAfterLoopExit(ctx);
|
|
2931
|
+
}
|
|
2907
2932
|
return;
|
|
2908
2933
|
}
|
|
2909
2934
|
|
|
@@ -2931,7 +2956,15 @@ export async function startAuto(
|
|
|
2931
2956
|
bootstrapDeps,
|
|
2932
2957
|
freshStartAssessment,
|
|
2933
2958
|
);
|
|
2934
|
-
if (!ready)
|
|
2959
|
+
if (!ready) {
|
|
2960
|
+
// bootstrapAutoSession sets s.active = true (auto-start.ts) before several
|
|
2961
|
+
// post-activation bail-outs return false without a loop ever running (e.g.
|
|
2962
|
+
// the SQLite-unavailable gate). Without this, s.active and the auto lock
|
|
2963
|
+
// leak and every later /gsd auto silently no-ops via the re-entry guard
|
|
2964
|
+
// (#1235). Run the canonical cleanup so a failed bootstrap stays recoverable.
|
|
2965
|
+
if (s.active) await cleanupAfterLoopExit(ctx);
|
|
2966
|
+
return;
|
|
2967
|
+
}
|
|
2935
2968
|
|
|
2936
2969
|
// Build scope after bootstrap has populated s.basePath / s.originalBasePath /
|
|
2937
2970
|
// s.currentMilestoneId (including worktree setup inside bootstrapAutoSession).
|
|
@@ -2957,15 +2990,18 @@ export async function startAuto(
|
|
|
2957
2990
|
startAutoCommandPolling(s.basePath);
|
|
2958
2991
|
|
|
2959
2992
|
// Dispatch the first unit
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2993
|
+
try {
|
|
2994
|
+
await runAutoLoopWithUok({
|
|
2995
|
+
ctx,
|
|
2996
|
+
pi,
|
|
2997
|
+
s,
|
|
2998
|
+
deps: loopDeps,
|
|
2999
|
+
runKernelLoop: runUokKernelLoop,
|
|
3000
|
+
runLegacyLoop: runLegacyAutoLoop,
|
|
3001
|
+
});
|
|
3002
|
+
} finally {
|
|
3003
|
+
await cleanupAfterLoopExit(ctx);
|
|
3004
|
+
}
|
|
2969
3005
|
}
|
|
2970
3006
|
|
|
2971
3007
|
// describeNextUnit is imported from auto-dashboard.ts and re-exported
|
|
@@ -3123,19 +3159,56 @@ export async function dispatchHookUnit(
|
|
|
3123
3159
|
workspaceRoot: s.basePath,
|
|
3124
3160
|
});
|
|
3125
3161
|
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3162
|
+
// Resolve the hook's model honoring the configured `fallbacks[]` chain
|
|
3163
|
+
// (#1229). The manual trigger path bypasses selectAndApplyModel, so resolve
|
|
3164
|
+
// the primary→fallbacks order here and apply the first candidate available in
|
|
3165
|
+
// the registry — a blocked or unconfigured primary then transparently drops
|
|
3166
|
+
// to a fallback instead of silently reverting to the session model.
|
|
3167
|
+
const availableModels = ctx.modelRegistry.getAvailable();
|
|
3168
|
+
const availableModelIds = availableModels.map(
|
|
3169
|
+
(m: { provider: string; id: string }) => `${m.provider}/${m.id}`,
|
|
3170
|
+
);
|
|
3171
|
+
const hookModelConfig = resolveModelWithFallbacksForUnit(
|
|
3172
|
+
hookUnitType,
|
|
3173
|
+
targetBasePath,
|
|
3174
|
+
availableModelIds,
|
|
3175
|
+
);
|
|
3176
|
+
const modelCandidates = hookModelConfig
|
|
3177
|
+
? [hookModelConfig.primary, ...hookModelConfig.fallbacks]
|
|
3178
|
+
: hookModel
|
|
3179
|
+
? [hookModel]
|
|
3180
|
+
: [];
|
|
3181
|
+
if (modelCandidates.length > 0) {
|
|
3182
|
+
let applied = false;
|
|
3183
|
+
for (const candidate of modelCandidates) {
|
|
3184
|
+
const match = resolveModelId(candidate, availableModels, ctx.model?.provider);
|
|
3185
|
+
if (!match) continue;
|
|
3186
|
+
// Skip models the runtime has marked blocked or temporarily unavailable
|
|
3187
|
+
// (e.g. a primary that just tripped a provider limit) so the configured
|
|
3188
|
+
// fallbacks[] chain actually engages — parity with auto-mode's
|
|
3189
|
+
// selectAndApplyModel, which consults the same blocked-models store (#1229).
|
|
3190
|
+
if (isModelUnavailable(targetBasePath, match.provider, match.id)) continue;
|
|
3130
3191
|
try {
|
|
3131
|
-
await pi.setModel(match)
|
|
3192
|
+
if (await pi.setModel(match)) {
|
|
3193
|
+
// The manual trigger path bypasses selectAndApplyModel, so apply the
|
|
3194
|
+
// hook's per-field `thinking` (from `post_unit_hooks[].model`'s object
|
|
3195
|
+
// form) here against the just-set model rather than leaving the hook at
|
|
3196
|
+
// the session level (#1269). Absent → session level, unchanged.
|
|
3197
|
+
if (hookModelConfig?.thinking) {
|
|
3198
|
+
applyThinkingLevelForModel(pi, hookModelConfig.thinking, match, ctx);
|
|
3199
|
+
}
|
|
3200
|
+
applied = true;
|
|
3201
|
+
break;
|
|
3202
|
+
}
|
|
3132
3203
|
} catch (err) {
|
|
3133
|
-
/* non-fatal */
|
|
3134
|
-
logWarning("dispatch", `hook model set failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
3204
|
+
/* non-fatal — try the next fallback */
|
|
3205
|
+
logWarning("dispatch", `hook model set failed for ${candidate}: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
3135
3206
|
}
|
|
3136
|
-
}
|
|
3207
|
+
}
|
|
3208
|
+
if (!applied) {
|
|
3137
3209
|
ctx.ui.notify(
|
|
3138
|
-
`Hook model
|
|
3210
|
+
`Hook model${modelCandidates.length > 1 ? "s" : ""} "${modelCandidates.join(", ")}" not available. ` +
|
|
3211
|
+
`Falling back to current session model. ` +
|
|
3139
3212
|
`Ensure the model is defined in models.json and has auth configured.`,
|
|
3140
3213
|
"warning",
|
|
3141
3214
|
);
|
|
@@ -148,6 +148,53 @@ export function writeCompatMarker(basePath: string, marker: CompatMarker): void
|
|
|
148
148
|
renameSync(tmp, path);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Remove projection entries whose backing file no longer exists on disk.
|
|
153
|
+
*
|
|
154
|
+
* gsd-pi never deletes marker entries on its own: both drift detectors skip
|
|
155
|
+
* files missing from disk (`if (!existsSync(abs)) continue;`) and the write-time
|
|
156
|
+
* flush only ever adds or refreshes entries. So when a phase directory is
|
|
157
|
+
* renamed or removed (e.g. `phases/29-new-milestone-m029/` →
|
|
158
|
+
* `phases/29-frontend-code-debt-cleanup/`), the old projection paths linger in
|
|
159
|
+
* `.compat.json` forever as phantom entries pointing at directories that no
|
|
160
|
+
* longer exist (#1257). They never reconcile, and `.compat.json` keeps drifting
|
|
161
|
+
* from disk reality so `git status` stops being a reliable proxy for "did the
|
|
162
|
+
* engine touch my plans?".
|
|
163
|
+
*
|
|
164
|
+
* This prunes those orphaned entries across the `.gsd/` projections and the
|
|
165
|
+
* `.planning/` projections/passthrough maps. It is safe: a missing-file entry is
|
|
166
|
+
* inert (every detector already ignores it), and if the file is later
|
|
167
|
+
* re-projected the write-time flush / unseeded-file detection re-seeds an
|
|
168
|
+
* accurate baseline.
|
|
169
|
+
*
|
|
170
|
+
* Returns the number of entries removed; when nothing is orphaned the marker is
|
|
171
|
+
* left untouched (no needless write, no `lastProjectedAt` churn).
|
|
172
|
+
*/
|
|
173
|
+
export function pruneOrphanedProjectionEntries(basePath: string): number {
|
|
174
|
+
if (!existsSync(compatMarkerPath(basePath))) return 0;
|
|
175
|
+
|
|
176
|
+
const marker = readCompatMarker(basePath);
|
|
177
|
+
let removed = 0;
|
|
178
|
+
|
|
179
|
+
const pruneMap = (map: Record<string, ProjectionEntry>, root: string): void => {
|
|
180
|
+
for (const relPath of Object.keys(map)) {
|
|
181
|
+
if (!existsSync(join(basePath, root, relPath))) {
|
|
182
|
+
delete map[relPath];
|
|
183
|
+
removed++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
pruneMap(marker.projections, ".gsd");
|
|
189
|
+
if (marker.planning) {
|
|
190
|
+
pruneMap(marker.planning.projections, ".planning");
|
|
191
|
+
pruneMap(marker.planning.passthrough, ".planning");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (removed > 0) writeCompatMarker(basePath, marker);
|
|
195
|
+
return removed;
|
|
196
|
+
}
|
|
197
|
+
|
|
151
198
|
function quarantine(basePath: string, raw: string): void {
|
|
152
199
|
const path = compatMarkerPath(basePath);
|
|
153
200
|
const badPath = `${path}.bad-${Date.now()}`;
|
|
@@ -150,7 +150,13 @@ function collectConfigSections(options?: CollectConfigOptions): ConfigSection[]
|
|
|
150
150
|
if (prefs?.auto_supervisor) {
|
|
151
151
|
const sup = resolveAutoSupervisorConfig();
|
|
152
152
|
const supRows: ConfigSection["rows"] = [];
|
|
153
|
-
if (sup.model)
|
|
153
|
+
if (sup.model) {
|
|
154
|
+
let modelVal = sup.model;
|
|
155
|
+
if (sup.modelFallbacks && sup.modelFallbacks.length > 0) {
|
|
156
|
+
modelVal += ` \u2192 ${sup.modelFallbacks.join(" \u2192 ")}`;
|
|
157
|
+
}
|
|
158
|
+
supRows.push({ label: "Model", value: modelVal });
|
|
159
|
+
}
|
|
154
160
|
supRows.push({ label: "Soft timeout", value: `${sup.soft_timeout_minutes}m` });
|
|
155
161
|
supRows.push({ label: "Idle timeout", value: `${sup.idle_timeout_minutes}m` });
|
|
156
162
|
supRows.push({ label: "Stalled tool timeout", value: `${sup.stalled_tool_timeout_minutes}m` });
|
|
@@ -860,3 +860,39 @@ export function getDb(): DbAdapter {
|
|
|
860
860
|
export function getDbOrNull(): DbAdapter | null {
|
|
861
861
|
return currentDb;
|
|
862
862
|
}
|
|
863
|
+
|
|
864
|
+
export interface DbWritableProbeResult {
|
|
865
|
+
ok: boolean;
|
|
866
|
+
detail?: string;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Confirm the open handle can actually write, not just that it opened.
|
|
871
|
+
*
|
|
872
|
+
* A schema-current database performs zero writes during open, so a handle that
|
|
873
|
+
* opened successfully but is not writable (read-only file, WAL/SHM permission
|
|
874
|
+
* mismatch, or a stale/moved handle → SQLITE_READONLY_DBMOVED) otherwise passes
|
|
875
|
+
* the open-only availability check and only fails much later at the first real
|
|
876
|
+
* write. The probe forces a genuine page write by re-writing the current
|
|
877
|
+
* `PRAGMA user_version` value back inside an IMMEDIATE transaction: a bare
|
|
878
|
+
* `BEGIN IMMEDIATE; ROLLBACK` is not sufficient because a moved handle does not
|
|
879
|
+
* fail until a page is actually dirtied. The value is unchanged, so the probe
|
|
880
|
+
* is idempotent, and the transaction is rolled back so nothing persists.
|
|
881
|
+
*/
|
|
882
|
+
export function probeDbWritable(): DbWritableProbeResult {
|
|
883
|
+
const db = currentDb;
|
|
884
|
+
if (!db) return { ok: false, detail: "No database is open." };
|
|
885
|
+
try {
|
|
886
|
+
const row = db.prepare("PRAGMA user_version").get() as { user_version?: number } | undefined;
|
|
887
|
+
const current = typeof row?.user_version === "number" ? row.user_version : 0;
|
|
888
|
+
db.exec("BEGIN IMMEDIATE");
|
|
889
|
+
try {
|
|
890
|
+
db.exec(`PRAGMA user_version = ${current}`);
|
|
891
|
+
} finally {
|
|
892
|
+
db.exec("ROLLBACK");
|
|
893
|
+
}
|
|
894
|
+
return { ok: true };
|
|
895
|
+
} catch (err) {
|
|
896
|
+
return { ok: false, detail: (err as Error).message };
|
|
897
|
+
}
|
|
898
|
+
}
|
|
@@ -25,6 +25,32 @@ export function normalizeDbRows(rows: unknown[]): Record<string, unknown>[] {
|
|
|
25
25
|
return rows.map((row) => normalizeDbRow(row)!);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
29
|
+
if (value === null || typeof value !== "object") return false;
|
|
30
|
+
const proto = Object.getPrototypeOf(value);
|
|
31
|
+
return proto === Object.prototype || proto === null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// node:sqlite accepts binding-object keys with or without the ':'/'@'/'$'
|
|
35
|
+
// prefix, but better-sqlite3 accepts only bare keys — strip the prefix so
|
|
36
|
+
// call sites written against node:sqlite also work on the fallback provider.
|
|
37
|
+
function normalizeBindParams(params: unknown[]): unknown[] {
|
|
38
|
+
return params.map((param) => {
|
|
39
|
+
if (!isPlainObject(param)) return param;
|
|
40
|
+
let changed = false;
|
|
41
|
+
const normalized: Record<string, unknown> = {};
|
|
42
|
+
for (const [key, value] of Object.entries(param)) {
|
|
43
|
+
if (key.length > 1 && (key[0] === ":" || key[0] === "@" || key[0] === "$")) {
|
|
44
|
+
normalized[key.slice(1)] = value;
|
|
45
|
+
changed = true;
|
|
46
|
+
} else {
|
|
47
|
+
normalized[key] = value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return changed ? normalized : param;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
28
54
|
export function createDbAdapter(rawDb: unknown): DbAdapter {
|
|
29
55
|
const db = rawDb as {
|
|
30
56
|
exec(sql: string): void;
|
|
@@ -45,13 +71,13 @@ export function createDbAdapter(rawDb: unknown): DbAdapter {
|
|
|
45
71
|
}): DbStatement {
|
|
46
72
|
return {
|
|
47
73
|
run(...params: unknown[]): unknown {
|
|
48
|
-
return raw.run(...params);
|
|
74
|
+
return raw.run(...normalizeBindParams(params));
|
|
49
75
|
},
|
|
50
76
|
get(...params: unknown[]): Record<string, unknown> | undefined {
|
|
51
|
-
return normalizeDbRow(raw.get(...params));
|
|
77
|
+
return normalizeDbRow(raw.get(...normalizeBindParams(params)));
|
|
52
78
|
},
|
|
53
79
|
all(...params: unknown[]): Record<string, unknown>[] {
|
|
54
|
-
return normalizeDbRows(raw.all(...params));
|
|
80
|
+
return normalizeDbRows(raw.all(...normalizeBindParams(params)));
|
|
55
81
|
},
|
|
56
82
|
};
|
|
57
83
|
}
|
|
@@ -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.
|
|
@@ -142,7 +142,7 @@ export function emitJournalEvent(basePath: string, entry: JournalEntry): void {
|
|
|
142
142
|
// Silent failure — journal must never break auto-mode
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
if (!isUnifiedAuditEnabled()) return;
|
|
145
|
+
if (!isUnifiedAuditEnabled(basePath)) return;
|
|
146
146
|
try {
|
|
147
147
|
const causedBy = entry.causedBy
|
|
148
148
|
? `${entry.causedBy.flowId}:${entry.causedBy.seq}`
|