gsd-pi 2.59.0 → 2.60.0-dev.d9052f5
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/extensions/gsd/auto/phases.js +54 -1
- package/dist/resources/extensions/gsd/auto-model-selection.js +8 -3
- package/dist/resources/extensions/gsd/auto-post-unit.js +40 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +13 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +70 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +51 -5
- package/dist/resources/extensions/gsd/captures.js +54 -1
- package/dist/resources/extensions/gsd/complexity-classifier.js +1 -1
- package/dist/resources/extensions/gsd/context-masker.js +68 -0
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +7 -0
- package/dist/resources/extensions/gsd/gsd-db.js +2 -2
- package/dist/resources/extensions/gsd/model-router.js +123 -4
- package/dist/resources/extensions/gsd/phase-anchor.js +56 -0
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +46 -0
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/dist/resources/extensions/gsd/prompts/rethink.md +7 -0
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +6 -1
- package/dist/resources/extensions/gsd/rethink.js +5 -2
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/status-guards.js +4 -3
- package/dist/resources/extensions/gsd/triage-resolution.js +128 -1
- package/dist/resources/extensions/gsd/triage-ui.js +12 -3
- package/dist/resources/skills/btw/SKILL.md +42 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- 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/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- 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 +3 -3
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
- package/dist/web/standalone/.next/server/chunks/2229.js +1 -1
- package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +122 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +30 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +1 -7
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +156 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +38 -0
- package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +1 -8
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/phases.ts +60 -1
- package/src/resources/extensions/gsd/auto-model-selection.ts +12 -3
- package/src/resources/extensions/gsd/auto-post-unit.ts +48 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +17 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +78 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +53 -4
- package/src/resources/extensions/gsd/captures.ts +71 -2
- package/src/resources/extensions/gsd/complexity-classifier.ts +1 -1
- package/src/resources/extensions/gsd/context-masker.ts +74 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +7 -0
- package/src/resources/extensions/gsd/gsd-db.ts +2 -2
- package/src/resources/extensions/gsd/model-router.ts +171 -8
- package/src/resources/extensions/gsd/phase-anchor.ts +71 -0
- package/src/resources/extensions/gsd/preferences-types.ts +9 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +38 -0
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/src/resources/extensions/gsd/prompts/rethink.md +7 -0
- package/src/resources/extensions/gsd/prompts/triage-captures.md +6 -1
- package/src/resources/extensions/gsd/rethink.ts +5 -2
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/status-guards.ts +4 -3
- package/src/resources/extensions/gsd/tests/context-masker.test.ts +122 -0
- package/src/resources/extensions/gsd/tests/model-router.test.ts +87 -1
- package/src/resources/extensions/gsd/tests/phase-anchor.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/stop-backtrack.test.ts +216 -0
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +1 -1
- package/src/resources/extensions/gsd/triage-resolution.ts +144 -1
- package/src/resources/extensions/gsd/triage-ui.ts +12 -3
- package/src/resources/skills/btw/SKILL.md +42 -0
- package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
- /package/dist/web/standalone/.next/static/{DGvT_c5Vb7Wu3X-fEOVUU → JVkoVYumy0cDhOQISEYdG}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{DGvT_c5Vb7Wu3X-fEOVUU → JVkoVYumy0cDhOQISEYdG}/_ssgManifest.js +0 -0
|
@@ -9,7 +9,7 @@ import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
|
|
|
9
9
|
import type { GSDPreferences } from "./preferences.js";
|
|
10
10
|
import { resolveModelWithFallbacksForUnit, resolveDynamicRoutingConfig } from "./preferences.js";
|
|
11
11
|
import type { ComplexityTier } from "./complexity-classifier.js";
|
|
12
|
-
import { classifyUnitComplexity, tierLabel } from "./complexity-classifier.js";
|
|
12
|
+
import { classifyUnitComplexity, tierLabel, extractTaskMetadata } from "./complexity-classifier.js";
|
|
13
13
|
import { resolveModelForComplexity, escalateTier } from "./model-router.js";
|
|
14
14
|
import { getLedger, getProjectTotals } from "./metrics.js";
|
|
15
15
|
import { unitPhaseLabel } from "./auto-dashboard.js";
|
|
@@ -107,7 +107,15 @@ export async function selectAndApplyModel(
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
// Extract task metadata for capability scoring
|
|
111
|
+
const taskMeta = unitType === "execute-task"
|
|
112
|
+
? extractTaskMetadata(unitId, basePath)
|
|
113
|
+
: undefined;
|
|
114
|
+
|
|
115
|
+
const routingResult = resolveModelForComplexity(
|
|
116
|
+
classification, modelConfig, routingConfig, availableModelIds,
|
|
117
|
+
unitType, taskMeta,
|
|
118
|
+
);
|
|
111
119
|
|
|
112
120
|
if (routingResult.wasDowngraded) {
|
|
113
121
|
effectiveModelConfig = {
|
|
@@ -115,8 +123,9 @@ export async function selectAndApplyModel(
|
|
|
115
123
|
fallbacks: routingResult.fallbacks,
|
|
116
124
|
};
|
|
117
125
|
if (verbose) {
|
|
126
|
+
const method = routingResult.selectionMethod === "capability-scored" ? "capability-scored" : "tier-only";
|
|
118
127
|
ctx.ui.notify(
|
|
119
|
-
`Dynamic routing [${tierLabel(classification.tier)}]: ${routingResult.modelId} (${classification.reason})`,
|
|
128
|
+
`Dynamic routing [${tierLabel(classification.tier)}]: ${routingResult.modelId} (${method} — ${classification.reason})`,
|
|
120
129
|
"info",
|
|
121
130
|
);
|
|
122
131
|
}
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
persistHookState,
|
|
47
47
|
resolveHookArtifactPath,
|
|
48
48
|
} from "./post-unit-hooks.js";
|
|
49
|
-
import { hasPendingCaptures, loadPendingCaptures } from "./captures.js";
|
|
49
|
+
import { hasPendingCaptures, loadPendingCaptures, revertExecutorResolvedCaptures } from "./captures.js";
|
|
50
50
|
import { debugLog } from "./debug-logger.js";
|
|
51
51
|
import { runSafely } from "./auto-utils.js";
|
|
52
52
|
import type { AutoSession, SidecarItem } from "./auto/session.js";
|
|
@@ -594,6 +594,53 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
594
594
|
}
|
|
595
595
|
}
|
|
596
596
|
|
|
597
|
+
// ── Fast-path stop detection (#3487) ──
|
|
598
|
+
// Before waiting for triage, check if any PENDING captures contain explicit
|
|
599
|
+
// stop/halt language. If so, pause immediately — don't wait for triage.
|
|
600
|
+
if (s.currentUnit && s.currentUnit.type !== "triage-captures") {
|
|
601
|
+
try {
|
|
602
|
+
const pending = loadPendingCaptures(s.basePath);
|
|
603
|
+
// Match only when the capture text starts with a stop/halt directive word,
|
|
604
|
+
// or the entire text is short and dominated by such a word. This avoids
|
|
605
|
+
// false positives on captures like "add a pause button" or "stop the timer
|
|
606
|
+
// from re-rendering" — those are feature descriptions, not halt directives.
|
|
607
|
+
const STOP_PATTERN = /^(stop|halt|abort|don'?t continue|pause|cease)\b/i;
|
|
608
|
+
const stopCapture = pending.find(c => STOP_PATTERN.test(c.text.trim()));
|
|
609
|
+
if (stopCapture) {
|
|
610
|
+
ctx.ui.notify(
|
|
611
|
+
`Stop directive detected in pending capture ${stopCapture.id}: "${stopCapture.text}" — pausing auto-mode.`,
|
|
612
|
+
"warning",
|
|
613
|
+
);
|
|
614
|
+
debugLog("postUnit", { phase: "fast-stop", captureId: stopCapture.id });
|
|
615
|
+
await pauseAuto(ctx, pi);
|
|
616
|
+
return "stopped";
|
|
617
|
+
}
|
|
618
|
+
} catch (e) {
|
|
619
|
+
debugLog("postUnit", { phase: "fast-stop-error", error: String(e) });
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// ── Capture protection: revert executor-silenced captures (#3487) ──
|
|
624
|
+
// Non-triage agents can write **Status:** resolved to CAPTURES.md, bypassing
|
|
625
|
+
// the triage pipeline. Revert those to pending before the triage check.
|
|
626
|
+
if (
|
|
627
|
+
s.currentUnit &&
|
|
628
|
+
s.currentUnit.type !== "triage-captures"
|
|
629
|
+
) {
|
|
630
|
+
try {
|
|
631
|
+
const reverted = revertExecutorResolvedCaptures(s.basePath);
|
|
632
|
+
if (reverted > 0) {
|
|
633
|
+
debugLog("postUnit", { phase: "capture-protection", reverted });
|
|
634
|
+
ctx.ui.notify(
|
|
635
|
+
`Reverted ${reverted} capture${reverted === 1 ? "" : "s"} silenced by executor — re-queuing for triage.`,
|
|
636
|
+
"warning",
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
} catch (e) {
|
|
640
|
+
debugLog("postUnit", { phase: "capture-protection-error", error: String(e) });
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
597
644
|
// ── Triage check ──
|
|
598
645
|
if (
|
|
599
646
|
!s.stepMode &&
|
|
@@ -26,6 +26,7 @@ import { existsSync } from "node:fs";
|
|
|
26
26
|
import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
|
|
27
27
|
import { getPendingGates } from "./gsd-db.js";
|
|
28
28
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
29
|
+
import { readPhaseAnchor, formatAnchorForPrompt } from "./phase-anchor.js";
|
|
29
30
|
|
|
30
31
|
// ─── Preamble Cap ─────────────────────────────────────────────────────────────
|
|
31
32
|
|
|
@@ -906,6 +907,11 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
|
|
|
906
907
|
const researchRel = relMilestoneFile(base, mid, "RESEARCH");
|
|
907
908
|
|
|
908
909
|
const inlined: string[] = [];
|
|
910
|
+
|
|
911
|
+
// Inject phase handoff anchor from research phase (if available)
|
|
912
|
+
const researchAnchor = readPhaseAnchor(base, mid, "research-milestone");
|
|
913
|
+
if (researchAnchor) inlined.push(formatAnchorForPrompt(researchAnchor));
|
|
914
|
+
|
|
909
915
|
inlined.push(await inlineFile(contextPath, contextRel, "Milestone Context"));
|
|
910
916
|
const researchInline = await inlineFileOptional(researchPath, researchRel, "Milestone Research");
|
|
911
917
|
if (researchInline) inlined.push(researchInline);
|
|
@@ -1033,6 +1039,11 @@ export async function buildPlanSlicePrompt(
|
|
|
1033
1039
|
const researchRel = relSliceFile(base, mid, sid, "RESEARCH");
|
|
1034
1040
|
|
|
1035
1041
|
const inlined: string[] = [];
|
|
1042
|
+
|
|
1043
|
+
// Inject phase handoff anchor from research phase (if available)
|
|
1044
|
+
const researchSliceAnchor = readPhaseAnchor(base, mid, "research-slice");
|
|
1045
|
+
if (researchSliceAnchor) inlined.push(formatAnchorForPrompt(researchSliceAnchor));
|
|
1046
|
+
|
|
1036
1047
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
|
|
1037
1048
|
const researchInline = await inlineFileOptional(researchPath, researchRel, "Slice Research");
|
|
1038
1049
|
if (researchInline) inlined.push(researchInline);
|
|
@@ -1100,6 +1111,9 @@ export async function buildExecuteTaskPrompt(
|
|
|
1100
1111
|
: { level: level as InlineLevel | undefined };
|
|
1101
1112
|
const inlineLevel = opts.level ?? resolveInlineLevel();
|
|
1102
1113
|
|
|
1114
|
+
// Inject phase handoff anchor from planning phase (if available)
|
|
1115
|
+
const planAnchor = readPhaseAnchor(base, mid, "plan-slice");
|
|
1116
|
+
|
|
1103
1117
|
const priorSummaries = opts.carryForwardPaths ?? await getPriorTaskSummaryPaths(mid, sid, tid, base);
|
|
1104
1118
|
const priorLines = priorSummaries.length > 0
|
|
1105
1119
|
? priorSummaries.map(p => `- \`${p}\``).join("\n")
|
|
@@ -1190,9 +1204,12 @@ export async function buildExecuteTaskPrompt(
|
|
|
1190
1204
|
? `### Runtime Context\nSource: \`.gsd/RUNTIME.md\`\n\n${runtimeContent.trim()}`
|
|
1191
1205
|
: "";
|
|
1192
1206
|
|
|
1207
|
+
const phaseAnchorSection = planAnchor ? formatAnchorForPrompt(planAnchor) : "";
|
|
1208
|
+
|
|
1193
1209
|
return loadPrompt("execute-task", {
|
|
1194
1210
|
overridesSection,
|
|
1195
1211
|
runtimeContext,
|
|
1212
|
+
phaseAnchorSection,
|
|
1196
1213
|
workingDirectory: base,
|
|
1197
1214
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle, taskId: tid, taskTitle: tTitle,
|
|
1198
1215
|
planPath: join(base, relSliceFile(base, mid, sid, "PLAN")),
|
|
@@ -883,6 +883,84 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
883
883
|
pi.registerTool(sliceCompleteTool);
|
|
884
884
|
registerAlias(pi, sliceCompleteTool, "gsd_complete_slice", "gsd_slice_complete");
|
|
885
885
|
|
|
886
|
+
// ─── gsd_skip_slice (#3477 / #3487) ───────────────────────────────────
|
|
887
|
+
|
|
888
|
+
const skipSliceExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
|
|
889
|
+
const dbAvailable = await ensureDbOpen();
|
|
890
|
+
if (!dbAvailable) {
|
|
891
|
+
return {
|
|
892
|
+
content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot skip slice." }],
|
|
893
|
+
details: { operation: "skip_slice", error: "db_unavailable" } as any,
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
try {
|
|
897
|
+
const { getSlice, updateSliceStatus } = await import("../gsd-db.js");
|
|
898
|
+
const { invalidateStateCache } = await import("../state.js");
|
|
899
|
+
|
|
900
|
+
const slice = getSlice(params.milestoneId, params.sliceId);
|
|
901
|
+
if (!slice) {
|
|
902
|
+
return {
|
|
903
|
+
content: [{ type: "text" as const, text: `Error: Slice ${params.sliceId} not found in milestone ${params.milestoneId}` }],
|
|
904
|
+
details: { operation: "skip_slice", error: "slice_not_found" } as any,
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
if (slice.status === "complete" || slice.status === "done") {
|
|
909
|
+
return {
|
|
910
|
+
content: [{ type: "text" as const, text: `Error: Slice ${params.sliceId} is already complete — cannot skip.` }],
|
|
911
|
+
details: { operation: "skip_slice", error: "already_complete" } as any,
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (slice.status === "skipped") {
|
|
916
|
+
return {
|
|
917
|
+
content: [{ type: "text" as const, text: `Slice ${params.sliceId} is already skipped.` }],
|
|
918
|
+
details: { operation: "skip_slice", sliceId: params.sliceId, milestoneId: params.milestoneId } as any,
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
updateSliceStatus(params.milestoneId, params.sliceId, "skipped");
|
|
923
|
+
invalidateStateCache();
|
|
924
|
+
|
|
925
|
+
return {
|
|
926
|
+
content: [{ type: "text" as const, text: `Skipped slice ${params.sliceId} (${params.milestoneId}). Reason: ${params.reason ?? "User-directed skip"}. Auto-mode will advance past this slice.` }],
|
|
927
|
+
details: {
|
|
928
|
+
operation: "skip_slice",
|
|
929
|
+
sliceId: params.sliceId,
|
|
930
|
+
milestoneId: params.milestoneId,
|
|
931
|
+
reason: params.reason,
|
|
932
|
+
} as any,
|
|
933
|
+
};
|
|
934
|
+
} catch (err) {
|
|
935
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
936
|
+
logError("tool", `skip_slice tool failed: ${msg}`, { tool: "gsd_skip_slice", error: String(err) });
|
|
937
|
+
return {
|
|
938
|
+
content: [{ type: "text" as const, text: `Error skipping slice: ${msg}` }],
|
|
939
|
+
details: { operation: "skip_slice", error: msg } as any,
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
pi.registerTool({
|
|
945
|
+
name: "gsd_skip_slice",
|
|
946
|
+
label: "Skip Slice",
|
|
947
|
+
description:
|
|
948
|
+
"Mark a slice as skipped so auto-mode advances past it without executing. " +
|
|
949
|
+
"The slice data is preserved for reference. The state machine treats skipped slices like completed ones for dependency satisfaction.",
|
|
950
|
+
promptSnippet: "Skip a GSD slice (mark as skipped, auto-mode will advance past it)",
|
|
951
|
+
promptGuidelines: [
|
|
952
|
+
"Use gsd_skip_slice when a slice should be bypassed — descoped, superseded, or no longer relevant.",
|
|
953
|
+
"Cannot skip a slice that is already complete.",
|
|
954
|
+
"Skipped slices satisfy downstream dependencies just like completed slices.",
|
|
955
|
+
],
|
|
956
|
+
parameters: Type.Object({
|
|
957
|
+
sliceId: Type.String({ description: "Slice ID (e.g. S02)" }),
|
|
958
|
+
milestoneId: Type.String({ description: "Milestone ID (e.g. M003)" }),
|
|
959
|
+
reason: Type.Optional(Type.String({ description: "Reason for skipping this slice" })),
|
|
960
|
+
}),
|
|
961
|
+
execute: skipSliceExecute,
|
|
962
|
+
});
|
|
963
|
+
|
|
886
964
|
// ─── gsd_complete_milestone ────────────────────────────────────────────
|
|
887
965
|
|
|
888
966
|
const milestoneCompleteExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
|
|
@@ -263,13 +263,62 @@ export function registerHooks(pi: ExtensionAPI): void {
|
|
|
263
263
|
});
|
|
264
264
|
|
|
265
265
|
pi.on("before_provider_request", async (event) => {
|
|
266
|
+
const payload = event.payload as Record<string, unknown> | null;
|
|
267
|
+
if (!payload || typeof payload !== "object") return;
|
|
268
|
+
|
|
269
|
+
// ── Observation Masking ─────────────────────────────────────────────
|
|
270
|
+
// Replace old tool results with placeholders to reduce context bloat.
|
|
271
|
+
// Only active during auto-mode when context_management.observation_masking is enabled.
|
|
272
|
+
if (isAutoActive()) {
|
|
273
|
+
try {
|
|
274
|
+
const { loadEffectiveGSDPreferences } = await import("../preferences.js");
|
|
275
|
+
const prefs = loadEffectiveGSDPreferences();
|
|
276
|
+
const cmConfig = prefs?.preferences.context_management;
|
|
277
|
+
|
|
278
|
+
// Observation masking: replace old tool results with placeholders
|
|
279
|
+
if (cmConfig?.observation_masking !== false) {
|
|
280
|
+
const keepTurns = cmConfig?.observation_mask_turns ?? 8;
|
|
281
|
+
const { createObservationMask } = await import("../context-masker.js");
|
|
282
|
+
const mask = createObservationMask(keepTurns);
|
|
283
|
+
const messages = payload.messages;
|
|
284
|
+
if (Array.isArray(messages)) {
|
|
285
|
+
payload.messages = mask(messages);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Tool result truncation: cap individual tool result content length.
|
|
290
|
+
// In pi-ai format, toolResult messages have role: "toolResult" and content: TextContent[].
|
|
291
|
+
// Creates new objects to avoid mutating shared conversation state.
|
|
292
|
+
const maxChars = cmConfig?.tool_result_max_chars ?? 800;
|
|
293
|
+
const msgs = payload.messages;
|
|
294
|
+
if (Array.isArray(msgs)) {
|
|
295
|
+
payload.messages = msgs.map((msg: Record<string, unknown>) => {
|
|
296
|
+
// Match toolResult messages (role: "toolResult", content is array of content blocks)
|
|
297
|
+
if (msg?.role === "toolResult" && Array.isArray(msg.content)) {
|
|
298
|
+
const blocks = msg.content as Array<Record<string, unknown>>;
|
|
299
|
+
const totalLen = blocks.reduce((sum: number, b) => sum + (typeof b.text === "string" ? b.text.length : 0), 0);
|
|
300
|
+
if (totalLen > maxChars) {
|
|
301
|
+
const truncated = blocks.map(b => {
|
|
302
|
+
if (typeof b.text === "string" && b.text.length > maxChars) {
|
|
303
|
+
return { ...b, text: b.text.slice(0, maxChars) + "\n…[truncated]" };
|
|
304
|
+
}
|
|
305
|
+
return b;
|
|
306
|
+
});
|
|
307
|
+
return { ...msg, content: truncated };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return msg;
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
} catch { /* non-fatal */ }
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ── Service Tier ────────────────────────────────────────────────────
|
|
266
317
|
const modelId = event.model?.id;
|
|
267
|
-
if (!modelId) return;
|
|
318
|
+
if (!modelId) return payload;
|
|
268
319
|
const { getEffectiveServiceTier, supportsServiceTier } = await import("../service-tier.js");
|
|
269
320
|
const tier = getEffectiveServiceTier();
|
|
270
|
-
if (!tier || !supportsServiceTier(modelId)) return;
|
|
271
|
-
const payload = event.payload as Record<string, unknown> | null;
|
|
272
|
-
if (!payload || typeof payload !== "object") return;
|
|
321
|
+
if (!tier || !supportsServiceTier(modelId)) return payload;
|
|
273
322
|
payload.service_tier = tier;
|
|
274
323
|
return payload;
|
|
275
324
|
});
|
|
@@ -15,7 +15,7 @@ import { gsdRoot } from "./paths.js";
|
|
|
15
15
|
|
|
16
16
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
17
17
|
|
|
18
|
-
export type Classification = "quick-task" | "inject" | "defer" | "replan" | "note";
|
|
18
|
+
export type Classification = "quick-task" | "inject" | "defer" | "replan" | "note" | "stop" | "backtrack";
|
|
19
19
|
|
|
20
20
|
export interface CaptureEntry {
|
|
21
21
|
id: string;
|
|
@@ -42,7 +42,7 @@ export interface TriageResult {
|
|
|
42
42
|
|
|
43
43
|
const CAPTURES_FILENAME = "CAPTURES.md";
|
|
44
44
|
const VALID_CLASSIFICATIONS: readonly string[] = [
|
|
45
|
-
"quick-task", "inject", "defer", "replan", "note",
|
|
45
|
+
"quick-task", "inject", "defer", "replan", "note", "stop", "backtrack",
|
|
46
46
|
];
|
|
47
47
|
|
|
48
48
|
// ─── Path Resolution ──────────────────────────────────────────────────────────
|
|
@@ -285,6 +285,75 @@ export function loadActionableCaptures(basePath: string, currentMilestoneId?: st
|
|
|
285
285
|
);
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Load unexecuted stop captures — user directives to halt auto-mode.
|
|
290
|
+
* These are checked in the pre-dispatch guard pipeline (runGuards) to
|
|
291
|
+
* pause auto-mode before the next unit is dispatched.
|
|
292
|
+
*/
|
|
293
|
+
export function loadStopCaptures(basePath: string): CaptureEntry[] {
|
|
294
|
+
return loadAllCaptures(basePath).filter(
|
|
295
|
+
c => c.status === "resolved" && !c.executed &&
|
|
296
|
+
(c.classification === "stop" || c.classification === "backtrack"),
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Load unexecuted backtrack captures specifically — captures directing
|
|
302
|
+
* auto-mode to abandon current milestone and return to a previous one.
|
|
303
|
+
*/
|
|
304
|
+
export function loadBacktrackCaptures(basePath: string): CaptureEntry[] {
|
|
305
|
+
return loadAllCaptures(basePath).filter(
|
|
306
|
+
c => c.status === "resolved" && !c.executed && c.classification === "backtrack",
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Revert captures that were silenced by non-triage agents.
|
|
312
|
+
*
|
|
313
|
+
* When an execute-task or other non-triage agent writes `**Status:** resolved`
|
|
314
|
+
* to CAPTURES.md, it bypasses the triage pipeline entirely. This function
|
|
315
|
+
* detects such captures (resolved but missing the Classification field that
|
|
316
|
+
* triage always writes) and reverts them to pending so the triage sidecar
|
|
317
|
+
* picks them up properly.
|
|
318
|
+
*
|
|
319
|
+
* Returns the number of captures reverted.
|
|
320
|
+
*/
|
|
321
|
+
export function revertExecutorResolvedCaptures(basePath: string): number {
|
|
322
|
+
const filePath = resolveCapturesPath(basePath);
|
|
323
|
+
if (!existsSync(filePath)) return 0;
|
|
324
|
+
|
|
325
|
+
let content = readFileSync(filePath, "utf-8");
|
|
326
|
+
let reverted = 0;
|
|
327
|
+
|
|
328
|
+
const all = loadAllCaptures(basePath);
|
|
329
|
+
for (const capture of all) {
|
|
330
|
+
// A properly triaged capture has both resolved status AND a classification.
|
|
331
|
+
// An executor-silenced capture has resolved status but NO classification.
|
|
332
|
+
if (capture.status === "resolved" && !capture.classification) {
|
|
333
|
+
const sectionRegex = new RegExp(
|
|
334
|
+
`(### ${escapeRegex(capture.id)}\\n(?:(?!### ).)*?)(?=### |$)`,
|
|
335
|
+
"s",
|
|
336
|
+
);
|
|
337
|
+
const match = sectionRegex.exec(content);
|
|
338
|
+
if (match) {
|
|
339
|
+
let section = match[1];
|
|
340
|
+
section = section.replace(
|
|
341
|
+
/\*\*Status:\*\*\s*resolved/i,
|
|
342
|
+
"**Status:** pending",
|
|
343
|
+
);
|
|
344
|
+
content = content.replace(sectionRegex, section);
|
|
345
|
+
reverted++;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (reverted > 0) {
|
|
351
|
+
writeFileSync(filePath, content, "utf-8");
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return reverted;
|
|
355
|
+
}
|
|
356
|
+
|
|
288
357
|
/**
|
|
289
358
|
* Retroactively stamp a capture with a milestone ID.
|
|
290
359
|
*
|
|
@@ -212,7 +212,7 @@ function analyzePlanComplexity(
|
|
|
212
212
|
/**
|
|
213
213
|
* Extract task metadata from the task plan file on disk.
|
|
214
214
|
*/
|
|
215
|
-
function extractTaskMetadata(unitId: string, basePath: string): TaskMetadata {
|
|
215
|
+
export function extractTaskMetadata(unitId: string, basePath: string): TaskMetadata {
|
|
216
216
|
const meta: TaskMetadata = {};
|
|
217
217
|
const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
|
|
218
218
|
if (!mid || !sid || !tid) return meta;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observation masking for GSD auto-mode sessions.
|
|
3
|
+
*
|
|
4
|
+
* Replaces tool result content older than N turns with a placeholder.
|
|
5
|
+
* Reduces context bloat between compactions with zero LLM overhead.
|
|
6
|
+
* Preserves message ordering, roles, and all assistant/user messages.
|
|
7
|
+
*
|
|
8
|
+
* Operates on the pi-ai Message[] format (post-convertToLlm, pre-provider):
|
|
9
|
+
* - toolResult messages: { role: "toolResult", content: TextContent[] }
|
|
10
|
+
* - bash results are already converted to: { role: "user", content: [{type:"text",text:"..."}] }
|
|
11
|
+
* and start with "Ran `" from bashExecutionToText.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
interface MaskableMessage {
|
|
15
|
+
role: string;
|
|
16
|
+
content: unknown;
|
|
17
|
+
type?: string;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const MASK_PLACEHOLDER = "[result masked — within summarized history]";
|
|
22
|
+
const MASK_CONTENT_BLOCK = [{ type: "text" as const, text: MASK_PLACEHOLDER }];
|
|
23
|
+
|
|
24
|
+
function findTurnBoundary(messages: MaskableMessage[], keepRecentTurns: number): number {
|
|
25
|
+
let turnsSeen = 0;
|
|
26
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
27
|
+
const m = messages[i];
|
|
28
|
+
// In the LLM payload, genuine user turns have role "user".
|
|
29
|
+
// Tool results have role "toolResult" and are excluded by this check.
|
|
30
|
+
if (m.role === "user") {
|
|
31
|
+
// Skip bash-result user messages (converted from bashExecution) — these aren't real user turns
|
|
32
|
+
if (isBashResultUserMessage(m)) continue;
|
|
33
|
+
turnsSeen++;
|
|
34
|
+
if (turnsSeen >= keepRecentTurns) return i;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Detect user messages that originated from bashExecution.
|
|
42
|
+
* After convertToLlm, these are {role: "user", content: [{type:"text", text:"Ran `cmd`\n..."}]}.
|
|
43
|
+
* The bashExecutionToText format always starts with "Ran `".
|
|
44
|
+
*/
|
|
45
|
+
function isBashResultUserMessage(m: MaskableMessage): boolean {
|
|
46
|
+
if (m.role !== "user" || !Array.isArray(m.content)) return false;
|
|
47
|
+
const first = m.content[0];
|
|
48
|
+
return first && typeof first === "object" && "text" in first &&
|
|
49
|
+
typeof first.text === "string" && first.text.startsWith("Ran `");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isMaskableMessage(m: MaskableMessage): boolean {
|
|
53
|
+
// Tool result messages (role: "toolResult" in pi-ai format)
|
|
54
|
+
if (m.role === "toolResult") return true;
|
|
55
|
+
// Bash-result user messages (converted from bashExecution by convertToLlm)
|
|
56
|
+
if (isBashResultUserMessage(m)) return true;
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createObservationMask(keepRecentTurns: number = 8) {
|
|
61
|
+
return (messages: MaskableMessage[]): MaskableMessage[] => {
|
|
62
|
+
const boundary = findTurnBoundary(messages, keepRecentTurns);
|
|
63
|
+
if (boundary === 0) return messages;
|
|
64
|
+
|
|
65
|
+
return messages.map((m, i) => {
|
|
66
|
+
if (i >= boundary) return m;
|
|
67
|
+
if (isMaskableMessage(m)) {
|
|
68
|
+
// Content may be string or array of content blocks — always replace with array
|
|
69
|
+
return { ...m, content: MASK_CONTENT_BLOCK };
|
|
70
|
+
}
|
|
71
|
+
return m;
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -189,6 +189,13 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
|
|
|
189
189
|
- `budget_pressure`: boolean — downgrade model tier when budget is under pressure. Default: `true`.
|
|
190
190
|
- `cross_provider`: boolean — allow routing across different providers. Default: `true`.
|
|
191
191
|
- `hooks`: boolean — enable routing hooks. Default: `true`.
|
|
192
|
+
- `capability_routing`: boolean — enable capability-profile scoring for model selection within a tier. Requires `enabled: true`. Default: `false`.
|
|
193
|
+
|
|
194
|
+
- `context_management`: configures context hygiene for auto-mode sessions. Keys:
|
|
195
|
+
- `observation_masking`: boolean — mask old tool results to reduce context bloat. Default: `true`.
|
|
196
|
+
- `observation_mask_turns`: number — keep this many recent turns verbatim (1-50). Default: `8`.
|
|
197
|
+
- `compaction_threshold_percent`: number — trigger compaction at this % of context window (0.5-0.95). Lower values fire compaction earlier, reducing drift. Default: `0.70`.
|
|
198
|
+
- `tool_result_max_chars`: number — max chars per tool result in GSD sessions (200-10000). Default: `800`.
|
|
192
199
|
|
|
193
200
|
- `auto_visualize`: boolean — show a visualizer hint after each milestone completion in auto-mode. Default: `false`.
|
|
194
201
|
|
|
@@ -1661,11 +1661,11 @@ export function getActiveSliceFromDb(milestoneId: string): SliceRow | null {
|
|
|
1661
1661
|
const row = currentDb.prepare(
|
|
1662
1662
|
`SELECT s.* FROM slices s
|
|
1663
1663
|
WHERE s.milestone_id = :mid
|
|
1664
|
-
AND s.status NOT IN ('complete', 'done')
|
|
1664
|
+
AND s.status NOT IN ('complete', 'done', 'skipped')
|
|
1665
1665
|
AND NOT EXISTS (
|
|
1666
1666
|
SELECT 1 FROM json_each(s.depends) AS dep
|
|
1667
1667
|
WHERE dep.value NOT IN (
|
|
1668
|
-
SELECT id FROM slices WHERE milestone_id = :mid AND status IN ('complete', 'done')
|
|
1668
|
+
SELECT id FROM slices WHERE milestone_id = :mid AND status IN ('complete', 'done', 'skipped')
|
|
1669
1669
|
)
|
|
1670
1670
|
)
|
|
1671
1671
|
ORDER BY s.sequence, s.id
|