@opengsd/gsd-pi 1.8.1-dev.897b3c90 → 1.9.0-dev.5c1a3ff2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -8
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/GSD-WORKFLOW.md +2 -2
- package/dist/resources/extensions/gsd/artifact-verification.js +3 -4
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +14 -8
- package/dist/resources/extensions/gsd/auto-prompts.js +90 -17
- package/dist/resources/extensions/gsd/auto-recovery.js +10 -5
- package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +4 -0
- package/dist/resources/extensions/gsd/auto-worktree-teardown.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +31 -5
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +10 -3
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +4 -24
- package/dist/resources/extensions/gsd/constants.js +12 -13
- package/dist/resources/extensions/gsd/context-masker.js +97 -21
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +22 -0
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +3 -6
- package/dist/resources/extensions/gsd/git-service.js +19 -7
- package/dist/resources/extensions/gsd/guided-flow.js +9 -3
- package/dist/resources/extensions/gsd/markdown-renderer.js +2 -12
- package/dist/resources/extensions/gsd/md-importer.js +5 -6
- package/dist/resources/extensions/gsd/model-cost-table.js +2 -0
- package/dist/resources/extensions/gsd/model-router.js +11 -0
- package/dist/resources/extensions/gsd/paths.js +51 -10
- package/dist/resources/extensions/gsd/planning-subagent-policy.js +37 -0
- package/dist/resources/extensions/gsd/planning-subagent-registry.js +25 -0
- package/dist/resources/extensions/gsd/preferences-types.js +5 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +48 -1
- package/dist/resources/extensions/gsd/preferences.js +17 -0
- package/dist/resources/extensions/gsd/provider-payload-policy.js +16 -5
- package/dist/resources/extensions/gsd/safety/destructive-guard.js +1 -1
- package/dist/resources/extensions/gsd/skill-activation.js +17 -3
- package/dist/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
- package/dist/resources/extensions/gsd/tools/complete-task.js +12 -5
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +14 -5
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +15 -5
- package/dist/resources/extensions/gsd/tools/reopen-task.js +16 -10
- package/dist/resources/extensions/gsd/unit-context-composer.js +28 -5
- package/dist/resources/extensions/gsd/workflow-projections.js +2 -3
- package/dist/resources/extensions/gsd/workspace.js +2 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +5 -5
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +5 -5
- 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/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
- package/integrations/hermes/pyproject.toml +1 -1
- package/package.json +6 -6
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/compaction/compaction.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/compaction/compaction.js +36 -4
- package/packages/gsd-agent-core/dist/compaction/compaction.js.map +1 -1
- package/packages/gsd-agent-core/dist/sdk.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/sdk.js +1 -0
- package/packages/gsd-agent-core/dist/sdk.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/package.json +1 -1
- package/packages/mcp-server/README.md +7 -2
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +5 -5
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +4 -3
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +5 -1
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +2 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +59 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +53 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +6 -1
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +11 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/GSD-WORKFLOW.md +2 -2
- package/src/resources/extensions/gsd/artifact-verification.ts +3 -8
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +14 -7
- package/src/resources/extensions/gsd/auto-prompts.ts +104 -15
- package/src/resources/extensions/gsd/auto-recovery.ts +10 -8
- package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +4 -0
- package/src/resources/extensions/gsd/auto-worktree-teardown.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +1 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +39 -10
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -3
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +7 -29
- package/src/resources/extensions/gsd/constants.ts +14 -13
- package/src/resources/extensions/gsd/context-masker.ts +98 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +22 -0
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +3 -5
- package/src/resources/extensions/gsd/git-service.ts +19 -6
- package/src/resources/extensions/gsd/guided-flow.ts +9 -2
- package/src/resources/extensions/gsd/markdown-renderer.ts +2 -14
- package/src/resources/extensions/gsd/md-importer.ts +5 -5
- package/src/resources/extensions/gsd/model-cost-table.ts +2 -0
- package/src/resources/extensions/gsd/model-router.ts +12 -0
- package/src/resources/extensions/gsd/paths.ts +58 -10
- package/src/resources/extensions/gsd/planning-subagent-policy.ts +58 -0
- package/src/resources/extensions/gsd/planning-subagent-registry.ts +31 -0
- package/src/resources/extensions/gsd/preferences-types.ts +16 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +62 -0
- package/src/resources/extensions/gsd/preferences.ts +25 -0
- package/src/resources/extensions/gsd/provider-payload-policy.ts +18 -4
- package/src/resources/extensions/gsd/safety/destructive-guard.ts +1 -1
- package/src/resources/extensions/gsd/skill-activation.ts +17 -3
- package/src/resources/extensions/gsd/templates/PREFERENCES.md +5 -0
- package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +7 -7
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +76 -1
- package/src/resources/extensions/gsd/tests/context-masker.test.ts +204 -8
- package/src/resources/extensions/gsd/tests/db-tools-alias-suppression.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/destructive-confirmation.test.ts +33 -0
- package/src/resources/extensions/gsd/tests/destructive-guard.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/integration/paths.test.ts +22 -4
- package/src/resources/extensions/gsd/tests/integration-branch-meta-location.test.ts +62 -0
- package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
- package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +7 -5
- package/src/resources/extensions/gsd/tests/preferences.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +33 -7
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +20 -16
- package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +13 -7
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +34 -0
- package/src/resources/extensions/gsd/tools/complete-task.ts +20 -5
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +24 -5
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +25 -5
- package/src/resources/extensions/gsd/tools/reopen-task.ts +23 -9
- package/src/resources/extensions/gsd/unit-context-composer.ts +34 -8
- package/src/resources/extensions/gsd/workflow-projections.ts +2 -3
- package/src/resources/extensions/gsd/workspace.ts +2 -1
- /package/dist/web/standalone/.next/static/{TezjdjSHBVRRr54XjAvte → 6YWV7VAPg-jqjfPswrKZb}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{TezjdjSHBVRRr54XjAvte → 6YWV7VAPg-jqjfPswrKZb}/_ssgManifest.js +0 -0
|
@@ -16,6 +16,7 @@ import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
|
16
16
|
import {
|
|
17
17
|
resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
|
|
18
18
|
resolveTasksDir, resolveTaskFiles, resolveTaskFile,
|
|
19
|
+
taskIdFromTaskFileName,
|
|
19
20
|
relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath,
|
|
20
21
|
relTaskFile, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile,
|
|
21
22
|
} from "./paths.js";
|
|
@@ -28,6 +29,7 @@ import type { GSDPreferences } from "./preferences.js";
|
|
|
28
29
|
import { join, basename, relative, sep } from "node:path";
|
|
29
30
|
import { existsSync } from "node:fs";
|
|
30
31
|
import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary, type MinimalModelRegistry } from "./context-budget.js";
|
|
32
|
+
import type { TokenProvider } from "./token-counter.js";
|
|
31
33
|
import { getBlockingReworkFindingsForTask, getGateResults, getPendingGates, getPendingGatesForTurn } from "./gsd-db.js";
|
|
32
34
|
import {
|
|
33
35
|
GATE_REGISTRY,
|
|
@@ -91,13 +93,29 @@ function resolvePromptBudgets(): ReturnType<typeof computeBudgets> {
|
|
|
91
93
|
const prefs = loadEffectiveGSDPreferences();
|
|
92
94
|
const sessionWindow = prefs?.preferences.context_window_override;
|
|
93
95
|
const windowTokens = resolveExecutorContextWindow(undefined, prefs?.preferences, sessionWindow);
|
|
94
|
-
return computeBudgets(windowTokens);
|
|
96
|
+
return computeBudgets(windowTokens, resolveExecutionProvider(prefs?.preferences));
|
|
95
97
|
} catch (e) {
|
|
96
98
|
logWarning("prompt", `resolvePromptBudgets failed: ${(e as Error).message}`);
|
|
97
99
|
return computeBudgets(200_000);
|
|
98
100
|
}
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Best-effort provider for the chars/token ratio: the executor's configured
|
|
105
|
+
* model profile is the only provider source reachable here (the runtime
|
|
106
|
+
* `sessionProvider` that reaches `formatExecutorConstraints` isn't threaded to
|
|
107
|
+
* `capPreamble`/summary callers). Undefined when no explicit provider is set —
|
|
108
|
+
* `computeBudgets` then uses the 4.0 default, i.e. today's behavior.
|
|
109
|
+
*/
|
|
110
|
+
function resolveExecutionProvider(prefs?: { models?: { execution?: unknown } }): TokenProvider | undefined {
|
|
111
|
+
const exec = prefs?.models?.execution;
|
|
112
|
+
if (exec && typeof exec === "object" && "provider" in exec) {
|
|
113
|
+
const provider = (exec as { provider?: unknown }).provider;
|
|
114
|
+
if (typeof provider === "string" && provider) return provider as TokenProvider;
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
101
119
|
/**
|
|
102
120
|
* Character budget for dependency/prior slice summaries injected into dispatch
|
|
103
121
|
* prompts. Scales with the executor's configured context window (issue #4435).
|
|
@@ -236,7 +254,7 @@ function formatCloseoutReviewInstructions(validationContent: string | null, vali
|
|
|
236
254
|
].join("\n");
|
|
237
255
|
}
|
|
238
256
|
|
|
239
|
-
function capPreamble(preamble: string): string {
|
|
257
|
+
export function capPreamble(preamble: string): string {
|
|
240
258
|
// Cap inlined context at min(static ceiling, scaled inline budget).
|
|
241
259
|
// The ceiling bounds repeated auto prompt payloads; the scaled
|
|
242
260
|
// budget tightens the cap for small-window users whose true safe limit is
|
|
@@ -246,6 +264,41 @@ function capPreamble(preamble: string): string {
|
|
|
246
264
|
return truncateAtSectionBoundary(preamble, budget).content;
|
|
247
265
|
}
|
|
248
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Cap the execute-task inline blocks against one shared budget in priority
|
|
269
|
+
* order. Unlike `capPreamble` (single concatenated preamble), execute-task
|
|
270
|
+
* feeds these blocks into separate, non-contiguous template placeholders, so
|
|
271
|
+
* they can't be joined and re-split — the budget is spent block-by-block.
|
|
272
|
+
*
|
|
273
|
+
* The task plan is the authoritative execution contract: it is never truncated
|
|
274
|
+
* here (it consumes budget first, whole). Remaining budget flows to the slice
|
|
275
|
+
* excerpt, then templates, each truncated at section boundaries so the lowest-
|
|
276
|
+
* priority context drops whole trailing sections first. When the task plan
|
|
277
|
+
* alone meets or exceeds the budget, the trailing blocks are dropped rather
|
|
278
|
+
* than the task plan being cut.
|
|
279
|
+
*/
|
|
280
|
+
function capExecuteTaskInlineBlocks(
|
|
281
|
+
taskPlan: string,
|
|
282
|
+
slicePlan: string,
|
|
283
|
+
templates: string,
|
|
284
|
+
budgetChars: number,
|
|
285
|
+
): { taskPlan: string; slicePlan: string; templates: string } {
|
|
286
|
+
let remaining = budgetChars - taskPlan.length;
|
|
287
|
+
if (remaining <= 0) {
|
|
288
|
+
return { taskPlan, slicePlan: "", templates: "" };
|
|
289
|
+
}
|
|
290
|
+
const cappedSlice = slicePlan.length <= remaining
|
|
291
|
+
? slicePlan
|
|
292
|
+
: truncateAtSectionBoundary(slicePlan, remaining).content;
|
|
293
|
+
remaining -= cappedSlice.length;
|
|
294
|
+
const cappedTemplates = remaining <= 0
|
|
295
|
+
? ""
|
|
296
|
+
: templates.length <= remaining
|
|
297
|
+
? templates
|
|
298
|
+
: truncateAtSectionBoundary(templates, remaining).content;
|
|
299
|
+
return { taskPlan, slicePlan: cappedSlice, templates: cappedTemplates };
|
|
300
|
+
}
|
|
301
|
+
|
|
249
302
|
type PromptContextMode = "inline" | "excerpt" | "on-demand" | "skipped";
|
|
250
303
|
|
|
251
304
|
interface PromptContextTelemetryEntry {
|
|
@@ -324,7 +377,7 @@ function prependContextModeToBlock(
|
|
|
324
377
|
block: string,
|
|
325
378
|
renderMode: ContextModeRenderMode = "standalone",
|
|
326
379
|
): string {
|
|
327
|
-
const toolSurface = composeToolSurfaceInstructions(unitType, { renderMode });
|
|
380
|
+
const toolSurface = composeToolSurfaceInstructions(unitType, { renderMode, basePath: base });
|
|
328
381
|
const contextMode = renderContextModeBlockForPrompt(unitType, base, renderMode);
|
|
329
382
|
const guidance = [toolSurface, contextMode].filter(Boolean).join("\n\n");
|
|
330
383
|
if (!guidance) return block;
|
|
@@ -405,7 +458,7 @@ function formatExecutorConstraints(
|
|
|
405
458
|
// so DEFAULT_CONTEXT_WINDOW stays the single source of truth.
|
|
406
459
|
windowTokens = resolveExecutorContextWindow(undefined, undefined, sessionContextWindow, sessionProvider);
|
|
407
460
|
}
|
|
408
|
-
const budgets = computeBudgets(windowTokens);
|
|
461
|
+
const budgets = computeBudgets(windowTokens, sessionProvider as TokenProvider | undefined);
|
|
409
462
|
const { min, max } = budgets.taskCountRange;
|
|
410
463
|
const execWindowK = Math.round(windowTokens / 1000);
|
|
411
464
|
const perTaskBudgetK = Math.round(budgets.inlineContextBudgetChars / 1000);
|
|
@@ -1431,6 +1484,18 @@ function resolveTaskSummariesLocation(
|
|
|
1431
1484
|
return { dir: slicePath, relPrefix: sRel };
|
|
1432
1485
|
}
|
|
1433
1486
|
|
|
1487
|
+
function summaryFileBelongsToSlice(
|
|
1488
|
+
fileName: string,
|
|
1489
|
+
base: string,
|
|
1490
|
+
mid: string,
|
|
1491
|
+
sid: string,
|
|
1492
|
+
): boolean {
|
|
1493
|
+
const tid = taskIdFromTaskFileName(fileName, "SUMMARY");
|
|
1494
|
+
if (!tid) return false;
|
|
1495
|
+
const resolved = resolveTaskFile(base, mid, sid, tid, "SUMMARY");
|
|
1496
|
+
return resolved !== null && basename(resolved) === fileName;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1434
1499
|
export async function getPriorTaskSummaryPaths(
|
|
1435
1500
|
mid: string, sid: string, currentTid: string, base: string,
|
|
1436
1501
|
): Promise<string[]> {
|
|
@@ -1442,7 +1507,10 @@ export async function getPriorTaskSummaryPaths(
|
|
|
1442
1507
|
|
|
1443
1508
|
return summaryFiles
|
|
1444
1509
|
.filter(f => {
|
|
1445
|
-
|
|
1510
|
+
if (!summaryFileBelongsToSlice(f, base, mid, sid)) return false;
|
|
1511
|
+
const tid = taskIdFromTaskFileName(f, "SUMMARY");
|
|
1512
|
+
if (!tid) return false;
|
|
1513
|
+
const num = parseInt(tid.replace(/^T/, ""), 10);
|
|
1446
1514
|
return num < currentNum;
|
|
1447
1515
|
})
|
|
1448
1516
|
.map(f => `${loc.relPrefix}/${f}`);
|
|
@@ -1475,8 +1543,9 @@ export async function getDependencyTaskSummaryPaths(
|
|
|
1475
1543
|
|
|
1476
1544
|
return summaryFiles
|
|
1477
1545
|
.filter((f) => {
|
|
1478
|
-
|
|
1479
|
-
const tid = f
|
|
1546
|
+
if (!summaryFileBelongsToSlice(f, base, mid, sid)) return false;
|
|
1547
|
+
const tid = taskIdFromTaskFileName(f, "SUMMARY");
|
|
1548
|
+
if (!tid) return false;
|
|
1480
1549
|
return depSet.has(tid);
|
|
1481
1550
|
})
|
|
1482
1551
|
.map((f) => `${loc.relPrefix}/${f}`);
|
|
@@ -2725,7 +2794,7 @@ export async function buildExecuteTaskPrompt(
|
|
|
2725
2794
|
// Compute verification budget for the executor's context window (issue #707)
|
|
2726
2795
|
const prefs = loadEffectiveGSDPreferences();
|
|
2727
2796
|
const contextWindow = resolveExecutorContextWindow(opts.modelRegistry, prefs?.preferences, opts.sessionContextWindow, opts.sessionProvider);
|
|
2728
|
-
const budgets = computeBudgets(contextWindow);
|
|
2797
|
+
const budgets = computeBudgets(contextWindow, opts.sessionProvider as TokenProvider | undefined);
|
|
2729
2798
|
const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
|
|
2730
2799
|
|
|
2731
2800
|
// Truncate carry-forward section when it exceeds 40% of inline context budget.
|
|
@@ -2842,10 +2911,26 @@ export async function buildExecuteTaskPrompt(
|
|
|
2842
2911
|
}
|
|
2843
2912
|
},
|
|
2844
2913
|
});
|
|
2845
|
-
const
|
|
2846
|
-
const
|
|
2914
|
+
const rawTaskPlanInline = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "task-plan");
|
|
2915
|
+
const rawSlicePlanExcerpt = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "slice-plan");
|
|
2847
2916
|
const contractedCarryForward = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "prior-task-summaries");
|
|
2848
|
-
const
|
|
2917
|
+
const rawContractedTemplates = requireComposedArtifactBlock(contractedContext.blocks, "execute-task", "templates");
|
|
2918
|
+
// Cap the static inline blocks against one shared budget so a small-window
|
|
2919
|
+
// executor can't overflow (the sibling builders cap via `capPreamble`;
|
|
2920
|
+
// execute-task's blocks feed separate template slots, so they share a budget
|
|
2921
|
+
// block-by-block). Carry-forward already has its own truncation above.
|
|
2922
|
+
const inlineBudget = Math.min(MAX_PREAMBLE_CHARS, budgets.inlineContextBudgetChars);
|
|
2923
|
+
const { taskPlan: taskPlanInline, slicePlan: slicePlanExcerpt, templates: contractedTemplates } =
|
|
2924
|
+
capExecuteTaskInlineBlocks(rawTaskPlanInline, rawSlicePlanExcerpt, rawContractedTemplates, inlineBudget);
|
|
2925
|
+
const inlineDropped = (rawTaskPlanInline.length + rawSlicePlanExcerpt.length + rawContractedTemplates.length)
|
|
2926
|
+
- (taskPlanInline.length + slicePlanExcerpt.length + contractedTemplates.length);
|
|
2927
|
+
trackPromptContext(
|
|
2928
|
+
contextTelemetry,
|
|
2929
|
+
"inline-cap",
|
|
2930
|
+
inlineDropped > 0 ? "excerpt" : "skipped",
|
|
2931
|
+
null,
|
|
2932
|
+
inlineDropped > 0 ? `dropped ${inlineDropped} chars (budget ${inlineBudget})` : "within budget",
|
|
2933
|
+
);
|
|
2849
2934
|
const onDemandResult = renderExecuteTaskOnDemandContext(base, mid, sid, contractedContext.onDemand);
|
|
2850
2935
|
const onDemandContext = onDemandResult.text;
|
|
2851
2936
|
trackPromptContext(
|
|
@@ -2941,12 +3026,14 @@ export async function buildCompleteSlicePrompt(
|
|
|
2941
3026
|
trackPromptContext(contextTelemetry, "prior-task-summaries", "skipped", null, "missing tasks dir");
|
|
2942
3027
|
return null;
|
|
2943
3028
|
}
|
|
2944
|
-
const summaryFiles = resolveTaskFiles(loc.dir, "SUMMARY")
|
|
3029
|
+
const summaryFiles = resolveTaskFiles(loc.dir, "SUMMARY")
|
|
3030
|
+
.filter((file) => summaryFileBelongsToSlice(file, base, mid, sid))
|
|
3031
|
+
.sort();
|
|
2945
3032
|
const blocks: string[] = [];
|
|
2946
3033
|
for (const file of summaryFiles) {
|
|
2947
3034
|
const absPath = join(loc.dir, file);
|
|
2948
3035
|
const relPath = `${loc.relPrefix}/${file}`;
|
|
2949
|
-
const taskId = file.replace(/-SUMMARY\.md$/i, "");
|
|
3036
|
+
const taskId = taskIdFromTaskFileName(file, "SUMMARY") ?? file.replace(/-SUMMARY\.md$/i, "");
|
|
2950
3037
|
blocks.push(await buildTaskSummaryExcerpt(absPath, relPath, taskId));
|
|
2951
3038
|
}
|
|
2952
3039
|
const body = blocks.length > 0 ? blocks.join("\n\n---\n\n") : null;
|
|
@@ -3559,7 +3646,9 @@ export async function buildReplanSlicePrompt(
|
|
|
3559
3646
|
let blockerTaskId = "";
|
|
3560
3647
|
const summaryLoc = resolveTaskSummariesLocation(base, mid, sid);
|
|
3561
3648
|
if (summaryLoc) {
|
|
3562
|
-
const summaryFiles = resolveTaskFiles(summaryLoc.dir, "SUMMARY")
|
|
3649
|
+
const summaryFiles = resolveTaskFiles(summaryLoc.dir, "SUMMARY")
|
|
3650
|
+
.filter((file) => summaryFileBelongsToSlice(file, base, mid, sid))
|
|
3651
|
+
.sort();
|
|
3563
3652
|
for (const file of summaryFiles) {
|
|
3564
3653
|
const absPath = join(summaryLoc.dir, file);
|
|
3565
3654
|
const content = await loadFile(absPath);
|
|
@@ -3567,7 +3656,7 @@ export async function buildReplanSlicePrompt(
|
|
|
3567
3656
|
const summary = parseSummary(content);
|
|
3568
3657
|
const relPath = `${summaryLoc.relPrefix}/${file}`;
|
|
3569
3658
|
if (summary.frontmatter.blocker_discovered) {
|
|
3570
|
-
blockerTaskId = summary.frontmatter.id || file.replace(/-SUMMARY\.md$/i, "");
|
|
3659
|
+
blockerTaskId = summary.frontmatter.id || taskIdFromTaskFileName(file, "SUMMARY") || file.replace(/-SUMMARY\.md$/i, "");
|
|
3571
3660
|
inlined.push(await buildTaskSummaryExcerpt(absPath, relPath, blockerTaskId, { blocker: true }));
|
|
3572
3661
|
}
|
|
3573
3662
|
}
|
|
@@ -39,8 +39,7 @@ import { isClosedStatus, isInactiveStatus } from "./status-guards.js";
|
|
|
39
39
|
import {
|
|
40
40
|
resolveSlicePath,
|
|
41
41
|
resolveSliceFile,
|
|
42
|
-
|
|
43
|
-
resolveTaskFiles,
|
|
42
|
+
resolveTaskFile,
|
|
44
43
|
relMilestoneFile,
|
|
45
44
|
relSliceFile,
|
|
46
45
|
buildSliceFileName,
|
|
@@ -324,13 +323,16 @@ export function writeReactiveExecuteBlocker(
|
|
|
324
323
|
const slicePath = resolveSlicePath(base, mid, sid);
|
|
325
324
|
if (!slicePath) return null;
|
|
326
325
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
)
|
|
326
|
+
// Resolve each batch task's SUMMARY with slice-qualified paths so flat-phase
|
|
327
|
+
// slices that reuse a task id (e.g. two T03s in one phase) don't collide: a
|
|
328
|
+
// sibling slice's summary must never make this slice's task look complete.
|
|
329
|
+
const hasSummary = (tid: string): boolean => {
|
|
330
|
+
const summaryPath = resolveTaskFile(base, mid, sid, tid, "SUMMARY");
|
|
331
|
+
return summaryPath !== null && existsSync(summaryPath);
|
|
332
|
+
};
|
|
331
333
|
|
|
332
|
-
const summaryPresent = batchIds.filter((tid) =>
|
|
333
|
-
const summaryMissing = batchIds.filter((tid) => !
|
|
334
|
+
const summaryPresent = batchIds.filter((tid) => hasSummary(tid));
|
|
335
|
+
const summaryMissing = batchIds.filter((tid) => !hasSummary(tid));
|
|
334
336
|
const completedTaskIds: string[] = [];
|
|
335
337
|
const skippedTaskIds: string[] = [];
|
|
336
338
|
const unchangedTaskIds: string[] = [];
|
|
@@ -10,6 +10,7 @@ import { existsSync, realpathSync, unlinkSync } from "node:fs";
|
|
|
10
10
|
import { isAbsolute, join, relative } from "node:path";
|
|
11
11
|
|
|
12
12
|
import { gsdRoot } from "./paths.js";
|
|
13
|
+
import { milestoneMetaPath } from "./git-service.js";
|
|
13
14
|
import { logWarning } from "./workflow-logger.js";
|
|
14
15
|
|
|
15
16
|
function isSamePath(a: string, b: string): boolean {
|
|
@@ -93,6 +94,9 @@ export function clearProjectRootStateFiles(
|
|
|
93
94
|
// worth removing on teardown.
|
|
94
95
|
const transientFiles = [
|
|
95
96
|
join(gsdDir, "STATE.md"),
|
|
97
|
+
// Integration-branch META now lives flat at .gsd/<MID>-META.json (ADR-045).
|
|
98
|
+
milestoneMetaPath(basePath, milestoneId),
|
|
99
|
+
// Legacy location — still cleaned for pre-migration trees.
|
|
96
100
|
join(gsdDir, "milestones", milestoneId, `${milestoneId}-META.json`),
|
|
97
101
|
];
|
|
98
102
|
|
|
@@ -65,7 +65,7 @@ export function teardownAutoWorktree(
|
|
|
65
65
|
|
|
66
66
|
// Mirror cleanup steps from mergeMilestoneToMain abort path:
|
|
67
67
|
|
|
68
|
-
// 1. Remove transient state files (STATE.md, auto.lock, {MID}-META.json).
|
|
68
|
+
// 1. Remove transient state files (STATE.md, auto.lock, .gsd/{MID}-META.json).
|
|
69
69
|
// Non-fatal — must not block teardown.
|
|
70
70
|
try {
|
|
71
71
|
clearProjectRootStateFiles(originalBasePath, milestoneId);
|
|
@@ -58,6 +58,7 @@ function registerAlias(pi: ExtensionAPI, toolDef: any, aliasName: string, canoni
|
|
|
58
58
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- toolDef shape matches ToolDefinition but varies by schema
|
|
59
59
|
function registerWorkflowTool(pi: ExtensionAPI, toolDef: any): void {
|
|
60
60
|
pi.registerTool(toolDef);
|
|
61
|
+
if (process.env.GSD_ADVERTISE_TOOL_ALIASES !== "1") return; // canonical-only model surface (see plan 035)
|
|
61
62
|
for (const alias of aliasesForWorkflowTool(toolDef.name)) {
|
|
62
63
|
registerAlias(pi, toolDef, alias, toolDef.name);
|
|
63
64
|
}
|
|
@@ -51,7 +51,9 @@ import {
|
|
|
51
51
|
confirmDestructiveCommand,
|
|
52
52
|
consumeDestructiveConfirmation,
|
|
53
53
|
isDestructiveConfirmGateId,
|
|
54
|
+
peekPendingDestructiveCommand,
|
|
54
55
|
requestDestructiveConfirmation,
|
|
56
|
+
resetDestructiveConfirmation,
|
|
55
57
|
} from "../safety/destructive-confirmation.js";
|
|
56
58
|
import { logWarning as safetyLogWarning, setStderrLoggingEnabled } from "../workflow-logger.js";
|
|
57
59
|
import { isUnitCloseoutTool, runInteractiveUnitCloseout } from "../unit-closeout.js";
|
|
@@ -59,6 +61,7 @@ import { installNotifyInterceptor } from "./notify-interceptor.js";
|
|
|
59
61
|
import { initNotificationStore } from "../notification-store.js";
|
|
60
62
|
import { initNotificationWidget } from "../notification-widget.js";
|
|
61
63
|
import { notifyPreferenceDiagnostics } from "../preferences-diagnostics.js";
|
|
64
|
+
import { resolveEffectivePlanningToolsPolicy } from "../planning-subagent-policy.js";
|
|
62
65
|
import { resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
63
66
|
import { extractSubagentAgentClasses } from "./subagent-input.js";
|
|
64
67
|
import {
|
|
@@ -187,6 +190,7 @@ function suppressWelcomeHeader(ctx: ExtensionContext): void {
|
|
|
187
190
|
* are bounded — cleared on activation, session boundaries, and verification.
|
|
188
191
|
*/
|
|
189
192
|
const deferredApprovalGates = new Map<string, string>();
|
|
193
|
+
const deferredDestructiveConfirmationPauses = new Set<string>();
|
|
190
194
|
|
|
191
195
|
export const MINIMAL_GSD_TOOL_NAMES = [
|
|
192
196
|
"gsd_exec",
|
|
@@ -618,6 +622,23 @@ function clearDeferredApprovalGate(basePath?: string): void {
|
|
|
618
622
|
}
|
|
619
623
|
}
|
|
620
624
|
|
|
625
|
+
function deferDestructiveConfirmationPause(basePath: string): void {
|
|
626
|
+
deferredDestructiveConfirmationPauses.add(basePath);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function clearDeferredDestructiveConfirmationPause(basePath?: string): void {
|
|
630
|
+
if (!basePath) {
|
|
631
|
+
deferredDestructiveConfirmationPauses.clear();
|
|
632
|
+
} else {
|
|
633
|
+
deferredDestructiveConfirmationPauses.delete(basePath);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function isDestructiveConfirmationBlocking(basePath: string): boolean {
|
|
638
|
+
return deferredDestructiveConfirmationPauses.has(basePath)
|
|
639
|
+
&& Boolean(peekPendingDestructiveCommand(basePath));
|
|
640
|
+
}
|
|
641
|
+
|
|
621
642
|
function deferApprovalGate(gateId: string, basePath: string): void {
|
|
622
643
|
// Verified-on-disk wins (same adapter policy as activation/re-arm): if the
|
|
623
644
|
// workflow MCP child already verified this gate, deferring would block
|
|
@@ -1015,6 +1036,7 @@ export function registerHooks(
|
|
|
1015
1036
|
await applyToolCallLoopGuardConfig(basePath);
|
|
1016
1037
|
approvalQuestionAbortInFlight = false;
|
|
1017
1038
|
clearDeferredApprovalGate();
|
|
1039
|
+
clearDeferredDestructiveConfirmationPause();
|
|
1018
1040
|
await resetAskUserQuestionsTurnCache();
|
|
1019
1041
|
await syncServiceTierStatus(ctx);
|
|
1020
1042
|
await applyDisabledModelProviderPolicy(ctx);
|
|
@@ -1106,6 +1128,7 @@ export function registerHooks(
|
|
|
1106
1128
|
resetToolCallLoopGuard();
|
|
1107
1129
|
await applyToolCallLoopGuardConfig(basePath);
|
|
1108
1130
|
clearDeferredApprovalGate();
|
|
1131
|
+
clearDeferredDestructiveConfirmationPause();
|
|
1109
1132
|
await resetAskUserQuestionsTurnCache();
|
|
1110
1133
|
clearDiscussionFlowState(basePath);
|
|
1111
1134
|
// /clear or /new destroys the conversation holding a discuss interview, so
|
|
@@ -1157,6 +1180,7 @@ export function registerHooks(
|
|
|
1157
1180
|
}
|
|
1158
1181
|
}
|
|
1159
1182
|
clearDeferredApprovalGate(beforeAgentBasePath);
|
|
1183
|
+
clearDeferredDestructiveConfirmationPause(beforeAgentBasePath);
|
|
1160
1184
|
|
|
1161
1185
|
// session_start can fire before the active provider has settled. By
|
|
1162
1186
|
// before_agent_start, Claude Code CLI sessions should get the same
|
|
@@ -1244,12 +1268,20 @@ export function registerHooks(
|
|
|
1244
1268
|
await handleAgentEnd(pi, event, ctx);
|
|
1245
1269
|
} finally {
|
|
1246
1270
|
activateDeferredApprovalGate(agentEndBasePath);
|
|
1271
|
+
const destructiveConfirmationBlocking = isDestructiveConfirmationBlocking(agentEndBasePath);
|
|
1272
|
+
clearDeferredDestructiveConfirmationPause(agentEndBasePath);
|
|
1247
1273
|
await maybePauseAutoForApprovalGate(
|
|
1248
1274
|
ctx,
|
|
1249
1275
|
pi,
|
|
1250
1276
|
isApprovalGateBlocking(agentEndBasePath),
|
|
1251
1277
|
"Depth confirmation is waiting for your answer — pausing auto-mode.",
|
|
1252
1278
|
);
|
|
1279
|
+
await maybePauseAutoForApprovalGate(
|
|
1280
|
+
ctx,
|
|
1281
|
+
pi,
|
|
1282
|
+
destructiveConfirmationBlocking,
|
|
1283
|
+
"Destructive-command confirmation is waiting for your answer — pausing auto-mode.",
|
|
1284
|
+
);
|
|
1253
1285
|
}
|
|
1254
1286
|
});
|
|
1255
1287
|
|
|
@@ -1559,6 +1591,7 @@ export function registerHooks(
|
|
|
1559
1591
|
const activeUnitType = dash.currentUnit?.type ?? guidedUnit?.unitType;
|
|
1560
1592
|
if (activeUnitType) {
|
|
1561
1593
|
const manifest = resolveManifest(activeUnitType);
|
|
1594
|
+
const planningBasePath = dash.basePath || guidedUnit?.basePath || discussionBasePath;
|
|
1562
1595
|
let planningInput = "";
|
|
1563
1596
|
let agentClasses: string[] | undefined;
|
|
1564
1597
|
if (isToolCallEventType("write", event)) {
|
|
@@ -1574,9 +1607,9 @@ export function registerHooks(
|
|
|
1574
1607
|
const planningGuard = shouldBlockPlanningUnit(
|
|
1575
1608
|
event.toolName,
|
|
1576
1609
|
planningInput,
|
|
1577
|
-
|
|
1610
|
+
planningBasePath,
|
|
1578
1611
|
activeUnitType,
|
|
1579
|
-
manifest?.tools,
|
|
1612
|
+
resolveEffectivePlanningToolsPolicy(activeUnitType, manifest?.tools, planningBasePath),
|
|
1580
1613
|
agentClasses,
|
|
1581
1614
|
(event as { input?: unknown }).input,
|
|
1582
1615
|
dash.currentUnit?.id,
|
|
@@ -1685,6 +1718,7 @@ export function registerHooks(
|
|
|
1685
1718
|
// Record the command as pending so an affirmative answer to a
|
|
1686
1719
|
// destructive_confirm gate (handled in tool_result) can confirm it.
|
|
1687
1720
|
requestDestructiveConfirmation(command, guardBasePath);
|
|
1721
|
+
deferDestructiveConfirmationPause(guardBasePath);
|
|
1688
1722
|
const reason = [
|
|
1689
1723
|
"HARD BLOCK: destructive Bash command requires explicit human confirmation.",
|
|
1690
1724
|
`Detected: ${classification.labels.join(", ")}`,
|
|
@@ -1695,14 +1729,6 @@ export function registerHooks(
|
|
|
1695
1729
|
safetyLogWarning("safety", `destructive command: ${classification.labels.join(", ")}`, {
|
|
1696
1730
|
command: String(command).slice(0, 200),
|
|
1697
1731
|
});
|
|
1698
|
-
if (ctx) {
|
|
1699
|
-
await maybePauseAutoForApprovalGate(
|
|
1700
|
-
ctx,
|
|
1701
|
-
pi,
|
|
1702
|
-
isAutoActive(),
|
|
1703
|
-
"Destructive-command confirmation is waiting for your answer — pausing auto-mode.",
|
|
1704
|
-
);
|
|
1705
|
-
}
|
|
1706
1732
|
return { block: true, reason };
|
|
1707
1733
|
}
|
|
1708
1734
|
}
|
|
@@ -1884,7 +1910,10 @@ export function registerHooks(
|
|
|
1884
1910
|
const answer = details.response?.answers?.[question.id];
|
|
1885
1911
|
if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
|
|
1886
1912
|
confirmDestructiveCommand(basePath);
|
|
1913
|
+
} else {
|
|
1914
|
+
resetDestructiveConfirmation(basePath);
|
|
1887
1915
|
}
|
|
1916
|
+
clearDeferredDestructiveConfirmationPause(basePath);
|
|
1888
1917
|
break;
|
|
1889
1918
|
}
|
|
1890
1919
|
}
|
|
@@ -21,6 +21,14 @@ import { formatOverridesSection, formatShortcut, loadActiveOverrides, loadFile,
|
|
|
21
21
|
import { toPosixPath } from "../../shared/mod.js";
|
|
22
22
|
import { autoEnableCmuxPreferences } from "../commands-cmux.js";
|
|
23
23
|
import { gsdHome } from "../gsd-home.js";
|
|
24
|
+
import { GSD_CONTEXT_MESSAGE_SENTINEL } from "../constants.js";
|
|
25
|
+
|
|
26
|
+
// Single source of truth lives in ../constants.js; re-exported here because
|
|
27
|
+
// buildContextMessage() stamps this marker on every context injection and the
|
|
28
|
+
// provider payload policy (filterSupersededContextInjections in
|
|
29
|
+
// context-masker.ts) matches it to dedupe. Adding a new context customType?
|
|
30
|
+
// Ensure buildContextMessage still prepends this sentinel.
|
|
31
|
+
export { GSD_CONTEXT_MESSAGE_SENTINEL };
|
|
24
32
|
|
|
25
33
|
const DEFAULT_CONTEXT_MESSAGE_MAX_CHARS = 4_000;
|
|
26
34
|
const DEFAULT_KNOWLEDGE_MAX_CHARS = 12_000;
|
|
@@ -448,14 +456,14 @@ export function buildContextMessage(opts: {
|
|
|
448
456
|
const memoryContent = markMemoryContextSupplied(opts.memoryBlock.trim());
|
|
449
457
|
if (opts.injection) {
|
|
450
458
|
const content = limitContextMessageContent(
|
|
451
|
-
memoryContent ? `${memoryContent}\n\n${opts.injection}` : opts.injection
|
|
459
|
+
`${GSD_CONTEXT_MESSAGE_SENTINEL}\n${memoryContent ? `${memoryContent}\n\n${opts.injection}` : opts.injection}`,
|
|
452
460
|
contextCharLimit,
|
|
453
461
|
);
|
|
454
462
|
return { customType: "gsd-guided-context", content, display: false as const };
|
|
455
463
|
}
|
|
456
464
|
if (opts.forensicsInjection) {
|
|
457
465
|
const content = limitContextMessageContent(
|
|
458
|
-
memoryContent ? `${memoryContent}\n\n${opts.forensicsInjection}` : opts.forensicsInjection
|
|
466
|
+
`${GSD_CONTEXT_MESSAGE_SENTINEL}\n${memoryContent ? `${memoryContent}\n\n${opts.forensicsInjection}` : opts.forensicsInjection}`,
|
|
459
467
|
contextCharLimit,
|
|
460
468
|
);
|
|
461
469
|
return { customType: "gsd-forensics", content, display: false as const };
|
|
@@ -463,7 +471,7 @@ export function buildContextMessage(opts: {
|
|
|
463
471
|
if (memoryContent) {
|
|
464
472
|
return {
|
|
465
473
|
customType: "gsd-memory",
|
|
466
|
-
content: limitContextMessageContent(memoryContent
|
|
474
|
+
content: limitContextMessageContent(`${GSD_CONTEXT_MESSAGE_SENTINEL}\n${memoryContent}`, contextCharLimit),
|
|
467
475
|
display: false as const,
|
|
468
476
|
};
|
|
469
477
|
}
|
|
@@ -9,6 +9,10 @@ import { canonicalToolName } from "../engine-hook-contract.js";
|
|
|
9
9
|
import { loadJsonFileOrNull } from "../json-persistence.js";
|
|
10
10
|
import { getIsolationMode } from "../preferences.js";
|
|
11
11
|
import { compileSubagentPermissionContract, type ToolsPolicy } from "../unit-context-manifest.js";
|
|
12
|
+
import {
|
|
13
|
+
allowedPlanningDispatchAgentsList,
|
|
14
|
+
isReadOnlyPlanningDispatchAgent,
|
|
15
|
+
} from "../planning-subagent-registry.js";
|
|
12
16
|
import { logWarning } from "../workflow-logger.js";
|
|
13
17
|
import { acquireSyncLock, releaseSyncLock } from "../sync-lock.js";
|
|
14
18
|
import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
@@ -1191,36 +1195,10 @@ export function shouldBlockQueueExecutionInSnapshot(
|
|
|
1191
1195
|
const PLANNING_WRITE_TOOLS = new Set(["write", "edit", "multi_edit", "notebook_edit"]);
|
|
1192
1196
|
const PLANNING_SUBAGENT_TOOLS = new Set(["subagent", "task"]);
|
|
1193
1197
|
|
|
1194
|
-
|
|
1195
|
-
* Canonical registry for agents that planning-dispatch may consider. Unit
|
|
1196
|
-
* manifests still declare per-unit subsets via ToolsPolicy.allowedSubagents.
|
|
1197
|
-
*/
|
|
1198
|
-
const PLANNING_DISPATCH_AGENT_REGISTRY = {
|
|
1199
|
-
mnemo: { readOnlySpecialist: true },
|
|
1200
|
-
scout: { readOnlySpecialist: true },
|
|
1201
|
-
planner: { readOnlySpecialist: true },
|
|
1202
|
-
reviewer: { readOnlySpecialist: true },
|
|
1203
|
-
security: { readOnlySpecialist: true },
|
|
1204
|
-
tester: { readOnlySpecialist: true },
|
|
1205
|
-
} as const satisfies Record<string, { readonly readOnlySpecialist: boolean }>;
|
|
1206
|
-
|
|
1207
|
-
export const ALLOWED_PLANNING_DISPATCH_AGENTS = new Set<string>(
|
|
1208
|
-
Object.entries(PLANNING_DISPATCH_AGENT_REGISTRY)
|
|
1209
|
-
.filter(([, metadata]) => metadata.readOnlySpecialist)
|
|
1210
|
-
.map(([agentId]) => agentId),
|
|
1211
|
-
);
|
|
1198
|
+
export { ALLOWED_PLANNING_DISPATCH_AGENTS } from "../planning-subagent-registry.js";
|
|
1212
1199
|
|
|
1213
1200
|
let warnedMissingControlledDispatchAgentClasses = false;
|
|
1214
1201
|
|
|
1215
|
-
function isReadOnlySpecialist(agentId: string): boolean {
|
|
1216
|
-
const metadata = PLANNING_DISPATCH_AGENT_REGISTRY[agentId as keyof typeof PLANNING_DISPATCH_AGENT_REGISTRY];
|
|
1217
|
-
return metadata?.readOnlySpecialist === true;
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
function allowedPlanningDispatchAgentsList(): string {
|
|
1221
|
-
return [...ALLOWED_PLANNING_DISPATCH_AGENTS].join(", ");
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
1202
|
function allowsControlledSubagentDispatch(
|
|
1225
1203
|
policy: ToolsPolicy,
|
|
1226
1204
|
): policy is ToolsPolicy & { readonly allowedSubagents: readonly string[] } {
|
|
@@ -1393,7 +1371,7 @@ export function shouldBlockPlanningUnit(
|
|
|
1393
1371
|
if (requested.length === 0) {
|
|
1394
1372
|
return { block: false };
|
|
1395
1373
|
}
|
|
1396
|
-
const globallyDisallowed = requested.find(a => !
|
|
1374
|
+
const globallyDisallowed = requested.find(a => !isReadOnlyPlanningDispatchAgent(a));
|
|
1397
1375
|
if (globallyDisallowed) {
|
|
1398
1376
|
return planningBlock(
|
|
1399
1377
|
unitType,
|
|
@@ -1406,7 +1384,7 @@ export function shouldBlockPlanningUnit(
|
|
|
1406
1384
|
return planningBlock(
|
|
1407
1385
|
unitType,
|
|
1408
1386
|
policy.mode,
|
|
1409
|
-
`subagent dispatch of "${disallowedByPolicy}" not permitted by ToolsPolicy.allowedSubagents; permitted agents for this unit: ${allowedSubagents.join(", ")}`,
|
|
1387
|
+
`subagent dispatch of "${disallowedByPolicy}" not permitted by unit allowlist (ToolsPolicy.allowedSubagents/planning_subagents); permitted agents for this unit: ${allowedSubagents.join(", ")}`,
|
|
1410
1388
|
);
|
|
1411
1389
|
}
|
|
1412
1390
|
return { block: false };
|
|
@@ -31,36 +31,37 @@ export const CACHE_MAX = 50;
|
|
|
31
31
|
* By scoping tools to this allowlist during discuss dispatches, the grammar
|
|
32
32
|
* sent to the provider stays well under provider limits.
|
|
33
33
|
*
|
|
34
|
-
* Included tools and why
|
|
34
|
+
* Included tools and why (canonical names only — aliases are no longer
|
|
35
|
+
* advertised to models by default, see plan 035):
|
|
35
36
|
* - gsd_summary_save: writes CONTEXT.md artifacts (all discuss prompts)
|
|
36
|
-
* - gsd_save_summary: alias for above
|
|
37
37
|
* - gsd_decision_save: records decisions (discuss.md output phase)
|
|
38
|
-
* - gsd_save_decision: alias for above
|
|
39
38
|
* - gsd_plan_milestone: writes roadmap (discuss.md single/multi milestone)
|
|
40
|
-
* - gsd_milestone_plan: alias for above
|
|
41
39
|
* - gsd_milestone_generate_id: generates milestone IDs (discuss.md multi-milestone)
|
|
42
|
-
* - gsd_generate_milestone_id: alias for above
|
|
43
40
|
* - gsd_requirement_save: creates requirements during discuss
|
|
44
|
-
* - gsd_save_requirement: alias for above
|
|
45
41
|
* - gsd_requirement_update: updates requirements during discuss
|
|
46
|
-
* - gsd_update_requirement: alias for above
|
|
47
42
|
*/
|
|
48
43
|
export const DISCUSS_TOOLS_ALLOWLIST: readonly string[] = [
|
|
49
44
|
// Context / summary writing
|
|
50
45
|
"gsd_summary_save",
|
|
51
|
-
"gsd_save_summary",
|
|
52
46
|
// Decision recording
|
|
53
47
|
"gsd_decision_save",
|
|
54
|
-
"gsd_save_decision",
|
|
55
48
|
// Milestone planning (needed for discuss.md output phase)
|
|
56
49
|
"gsd_plan_milestone",
|
|
57
|
-
"gsd_milestone_plan",
|
|
58
50
|
// Milestone ID generation (multi-milestone flow)
|
|
59
51
|
"gsd_milestone_generate_id",
|
|
60
|
-
"gsd_generate_milestone_id",
|
|
61
52
|
// Requirement updates
|
|
62
53
|
"gsd_requirement_save",
|
|
63
|
-
"gsd_save_requirement",
|
|
64
54
|
"gsd_requirement_update",
|
|
65
|
-
"gsd_update_requirement",
|
|
66
55
|
];
|
|
56
|
+
|
|
57
|
+
// ─── Context Injection ────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Leading marker stamped on every buildContextMessage() output
|
|
61
|
+
* (bootstrap/system-context.ts). Single source of truth for the producer and
|
|
62
|
+
* the consumer: the provider payload policy (filterSupersededContextInjections
|
|
63
|
+
* in context-masker.ts) matches this prefix to find and dedupe injected
|
|
64
|
+
* memory/guided/forensics messages after convertToLlm strips their customType.
|
|
65
|
+
* Both sides import it from here — do not inline the literal in either module.
|
|
66
|
+
*/
|
|
67
|
+
export const GSD_CONTEXT_MESSAGE_SENTINEL = "[GSD Context Injection]";
|