gsd-pi 2.72.0-dev.3118184 → 2.72.0-dev.4f3264a
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/async-jobs/await-tool.js +4 -7
- package/dist/resources/extensions/async-jobs/job-manager.js +3 -28
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +26 -27
- package/dist/resources/extensions/gsd/auto/loop.js +1 -84
- package/dist/resources/extensions/gsd/auto-post-unit.js +0 -6
- package/dist/resources/extensions/gsd/auto.js +19 -25
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -9
- package/dist/resources/extensions/gsd/commands-handlers.js +1 -4
- package/dist/resources/extensions/gsd/context-injector.js +1 -1
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +7 -3
- package/dist/resources/extensions/gsd/gsd-db.js +5 -47
- package/dist/resources/extensions/gsd/key-manager.js +0 -2
- package/dist/resources/extensions/gsd/preferences-skills.js +34 -2
- package/dist/resources/extensions/gsd/preferences-types.js +0 -15
- package/dist/resources/extensions/gsd/preferences.js +3 -16
- package/dist/resources/extensions/gsd/prompt-loader.js +1 -4
- package/dist/resources/extensions/gsd/state.js +1 -21
- package/dist/resources/extensions/gsd/write-intercept.js +1 -10
- package/dist/resources/extensions/ollama/index.js +5 -4
- package/dist/resources/extensions/ollama/ollama-client.js +6 -35
- package/dist/resources/extensions/ollama/ollama-discovery.js +6 -32
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- 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/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 +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/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 +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +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/resize/route.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/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.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/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.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/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.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/session/browser/route.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/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/manage/route.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/shutdown/route.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/steer/route.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/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +3 -3
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +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/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 +12 -12
- package/dist/web/standalone/.next/server/chunks/2331.js +16 -16
- package/dist/web/standalone/.next/server/chunks/4741.js +12 -12
- package/dist/web/standalone/.next/server/chunks/5822.js +2 -2
- package/dist/web/standalone/.next/server/chunks/63.js +8 -8
- package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
- package/dist/web/standalone/.next/server/functions-config-manifest.json +9 -0
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +2 -29
- package/dist/web/standalone/.next/server/middleware.js +12 -4
- 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/.next/server/webpack-runtime.js +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/env-api-keys.js +0 -1
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.custom.d.ts +0 -105
- package/packages/pi-ai/dist/models.custom.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.custom.js +0 -97
- package/packages/pi-ai/dist/models.custom.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +140 -648
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +364 -861
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.test.js +0 -105
- package/packages/pi-ai/dist/models.test.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +1 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/env-api-keys.ts +0 -1
- package/packages/pi-ai/src/models.custom.ts +0 -98
- package/packages/pi-ai/src/models.generated.ts +364 -861
- package/packages/pi-ai/src/models.test.ts +0 -135
- package/packages/pi-ai/src/types.ts +0 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +0 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/src/core/model-resolver.ts +0 -1
- package/src/resources/extensions/async-jobs/await-tool.test.ts +7 -40
- package/src/resources/extensions/async-jobs/await-tool.ts +4 -7
- package/src/resources/extensions/async-jobs/job-manager.ts +3 -33
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +26 -27
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +2 -20
- package/src/resources/extensions/gsd/auto/loop.ts +1 -89
- package/src/resources/extensions/gsd/auto-post-unit.ts +0 -7
- package/src/resources/extensions/gsd/auto.ts +20 -25
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +10 -8
- package/src/resources/extensions/gsd/commands-handlers.ts +1 -5
- package/src/resources/extensions/gsd/context-injector.ts +1 -1
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +8 -4
- package/src/resources/extensions/gsd/gsd-db.ts +5 -52
- package/src/resources/extensions/gsd/key-manager.ts +0 -2
- package/src/resources/extensions/gsd/preferences-skills.ts +36 -2
- package/src/resources/extensions/gsd/preferences-types.ts +0 -16
- package/src/resources/extensions/gsd/preferences.ts +6 -19
- package/src/resources/extensions/gsd/prompt-loader.ts +1 -6
- package/src/resources/extensions/gsd/state.ts +0 -20
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +0 -74
- package/src/resources/extensions/gsd/tests/key-manager.test.ts +0 -63
- package/src/resources/extensions/gsd/tests/preferences.test.ts +0 -53
- package/src/resources/extensions/gsd/write-intercept.ts +1 -10
- package/src/resources/extensions/ollama/index.ts +5 -4
- package/src/resources/extensions/ollama/ollama-client.ts +6 -35
- package/src/resources/extensions/ollama/ollama-discovery.ts +6 -37
- package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -54
- package/dist/resources/extensions/gsd/definition-io.js +0 -15
- package/dist/web/standalone/.next/server/edge-runtime-webpack.js +0 -2
- package/packages/pi-ai/dist/models.generated.test.d.ts +0 -2
- package/packages/pi-ai/dist/models.generated.test.d.ts.map +0 -1
- package/packages/pi-ai/dist/models.generated.test.js +0 -334
- package/packages/pi-ai/dist/models.generated.test.js.map +0 -1
- package/packages/pi-ai/src/models.generated.test.ts +0 -373
- package/src/resources/extensions/gsd/definition-io.ts +0 -18
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +0 -27
- package/src/resources/extensions/gsd/tests/block-db-writes.test.ts +0 -63
- package/src/resources/extensions/gsd/tests/definition-io.test.ts +0 -57
- package/src/resources/extensions/gsd/tests/doctor-heal-fixable-warnings.test.ts +0 -14
- package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +0 -104
- package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +0 -54
- package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +0 -34
- package/src/resources/extensions/gsd/tests/preferences-formatting.test.ts +0 -87
- package/src/resources/extensions/gsd/tests/prompt-loader-working-directory.test.ts +0 -19
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +0 -97
- package/src/resources/extensions/gsd/tests/stale-slice-rows.test.ts +0 -41
- /package/dist/web/standalone/.next/static/{NzO79SOz9jHX-VY5-0t2O → vr6Pbde48w4rMUplqDdh_}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{NzO79SOz9jHX-VY5-0t2O → vr6Pbde48w4rMUplqDdh_}/_ssgManifest.js +0 -0
|
@@ -54,14 +54,11 @@ export function createAwaitTool(getManager) {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
// the cross-turn case (job already completed before await_job was called).
|
|
61
|
-
// Previously this only set j.awaited = true, which missed the cross-turn
|
|
62
|
-
// case because the queueMicrotask had already fired (#3787).
|
|
57
|
+
// Mark all watched jobs as awaited upfront so the onJobComplete
|
|
58
|
+
// callback (which fires synchronously in the promise .then()) knows
|
|
59
|
+
// to suppress the follow-up message.
|
|
63
60
|
for (const j of watched)
|
|
64
|
-
|
|
61
|
+
j.awaited = true;
|
|
65
62
|
// If all watched jobs are already done, return immediately
|
|
66
63
|
const running = watched.filter((j) => j.status === "running");
|
|
67
64
|
if (running.length === 0) {
|
|
@@ -118,38 +118,13 @@ export class AsyncJobManager {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
// ── Private ────────────────────────────────────────────────────────────
|
|
121
|
-
/**
|
|
122
|
-
* Suppress follow-up notification for a job — cancels any pending delivery
|
|
123
|
-
* timer and marks the job as awaited. Safe to call at any time, including
|
|
124
|
-
* before or after the job completes (#3787).
|
|
125
|
-
*/
|
|
126
|
-
suppressFollowUp(id) {
|
|
127
|
-
const job = this.jobs.get(id);
|
|
128
|
-
if (!job)
|
|
129
|
-
return;
|
|
130
|
-
job.awaited = true;
|
|
131
|
-
if (job.deliveryTimer !== undefined) {
|
|
132
|
-
clearTimeout(job.deliveryTimer);
|
|
133
|
-
job.deliveryTimer = undefined;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
121
|
deliverResult(job) {
|
|
137
122
|
if (!this.onJobComplete)
|
|
138
123
|
return;
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
// a later LLM turn (after the job already completed). queueMicrotask ran
|
|
142
|
-
// immediately and could not be cancelled (#2762, #3787).
|
|
124
|
+
// Defer delivery by one microtask so await_job's .then() chain runs first
|
|
125
|
+
// and can set job.awaited = true before onJobComplete checks it (#2762).
|
|
143
126
|
const cb = this.onJobComplete;
|
|
144
|
-
|
|
145
|
-
job.deliveryTimer = undefined;
|
|
146
|
-
if (!job.awaited)
|
|
147
|
-
cb(job);
|
|
148
|
-
}, 0);
|
|
149
|
-
// Allow process to exit even if timer is pending
|
|
150
|
-
if (typeof job.deliveryTimer === "object" && "unref" in job.deliveryTimer) {
|
|
151
|
-
job.deliveryTimer.unref();
|
|
152
|
-
}
|
|
127
|
+
queueMicrotask(() => cb(job));
|
|
153
128
|
}
|
|
154
129
|
scheduleEviction(id) {
|
|
155
130
|
const existing = this.evictionTimers.get(id);
|
|
@@ -405,25 +405,32 @@ export function makeAbortedMessage(model, lastTextContent) {
|
|
|
405
405
|
/**
|
|
406
406
|
* Resolve the Claude Code permission mode for the current run.
|
|
407
407
|
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
* already enforce. `GSD_CLAUDE_CODE_PERMISSION_MODE` lets security-conscious
|
|
414
|
-
* users opt into a stricter mode (`acceptEdits`, `default`, `plan`).
|
|
408
|
+
* - Auto-mode / headless runs bypass permissions so tool calls don't block
|
|
409
|
+
* on prompts the user isn't watching.
|
|
410
|
+
* - Interactive runs default to `acceptEdits` so file/bash writes still
|
|
411
|
+
* land quickly but the SDK retains a permission gate.
|
|
412
|
+
* - `GSD_CLAUDE_CODE_PERMISSION_MODE` forces a specific mode when set.
|
|
415
413
|
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
* (#4099) is continuous approval fatigue that blocks real work.
|
|
414
|
+
* Cross-extension coupling is kept minimal by dynamically importing
|
|
415
|
+
* `isAutoActive` and falling back to the bypass default if the import
|
|
416
|
+
* fails (e.g. in unit tests that load stream-adapter in isolation).
|
|
420
417
|
*/
|
|
421
418
|
export async function resolveClaudePermissionMode(env = process.env) {
|
|
422
419
|
const override = env.GSD_CLAUDE_CODE_PERMISSION_MODE?.trim();
|
|
423
420
|
if (override === "bypassPermissions" || override === "acceptEdits" || override === "default" || override === "plan") {
|
|
424
421
|
return override;
|
|
425
422
|
}
|
|
426
|
-
|
|
423
|
+
try {
|
|
424
|
+
const autoMod = (await import("../gsd/auto.js"));
|
|
425
|
+
if (typeof autoMod.isAutoActive === "function" && autoMod.isAutoActive()) {
|
|
426
|
+
return "bypassPermissions";
|
|
427
|
+
}
|
|
428
|
+
return "acceptEdits";
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
// auto.ts unavailable (tests, non-GSD contexts) — stay permissive.
|
|
432
|
+
return "bypassPermissions";
|
|
433
|
+
}
|
|
427
434
|
}
|
|
428
435
|
/**
|
|
429
436
|
* Build the options object passed to the Claude Agent SDK's `query()` call.
|
|
@@ -440,21 +447,13 @@ export function buildSdkOptions(modelId, prompt, overrides, extraOptions = {}) {
|
|
|
440
447
|
const mcpServers = buildWorkflowMcpServers();
|
|
441
448
|
const permissionMode = overrides?.permissionMode ?? "bypassPermissions";
|
|
442
449
|
const disallowedTools = ["AskUserQuestion"];
|
|
443
|
-
// Pre-authorize
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
"Write",
|
|
451
|
-
"Edit",
|
|
452
|
-
"Glob",
|
|
453
|
-
"Grep",
|
|
454
|
-
"Bash(ls:*)",
|
|
455
|
-
"Bash(pwd)",
|
|
456
|
-
...(mcpServers ? Object.keys(mcpServers).map((serverName) => `mcp__${serverName}__*`) : []),
|
|
457
|
-
];
|
|
450
|
+
// Pre-authorize every registered workflow MCP server's tools. Without this,
|
|
451
|
+
// `acceptEdits` mode (the interactive default) auto-approves built-in
|
|
452
|
+
// Edit/Write/Bash but still gates MCP calls like `mcp__gsd-workflow__*`,
|
|
453
|
+
// surfacing "This command requires approval" on every GSD action (#4099).
|
|
454
|
+
const allowedTools = mcpServers
|
|
455
|
+
? Object.keys(mcpServers).map((serverName) => `mcp__${serverName}__*`)
|
|
456
|
+
: [];
|
|
458
457
|
return {
|
|
459
458
|
pathToClaudeCodeExecutable: getClaudePath(),
|
|
460
459
|
model: modelId,
|
|
@@ -13,69 +13,6 @@ import { runPreDispatch, runDispatch, runGuards, runUnitPhase, runFinalize, } fr
|
|
|
13
13
|
import { debugLog } from "../debug-logger.js";
|
|
14
14
|
import { isInfrastructureError, isTransientCooldownError, getCooldownRetryAfterMs, COOLDOWN_FALLBACK_WAIT_MS, MAX_COOLDOWN_RETRIES } from "./infra-errors.js";
|
|
15
15
|
import { resolveEngine } from "../engine-resolver.js";
|
|
16
|
-
import { logWarning } from "../workflow-logger.js";
|
|
17
|
-
import { gsdRoot } from "../paths.js";
|
|
18
|
-
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
19
|
-
import { join } from "node:path";
|
|
20
|
-
// ── Stuck detection persistence (#3704) ──────────────────────────────────
|
|
21
|
-
// Persist stuck detection state to disk so it survives session restarts.
|
|
22
|
-
// Without this, restarting auto-mode resets all counters, allowing the
|
|
23
|
-
// same blocked unit to burn a full retry budget each session.
|
|
24
|
-
function stuckStatePath(basePath) {
|
|
25
|
-
return join(gsdRoot(basePath), "runtime", "stuck-state.json");
|
|
26
|
-
}
|
|
27
|
-
function loadStuckState(basePath) {
|
|
28
|
-
try {
|
|
29
|
-
const data = JSON.parse(readFileSync(stuckStatePath(basePath), "utf-8"));
|
|
30
|
-
return {
|
|
31
|
-
recentUnits: Array.isArray(data.recentUnits) ? data.recentUnits : [],
|
|
32
|
-
stuckRecoveryAttempts: typeof data.stuckRecoveryAttempts === "number" ? data.stuckRecoveryAttempts : 0,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
catch (err) {
|
|
36
|
-
debugLog("autoLoop", { phase: "load-stuck-state-failed", error: err instanceof Error ? err.message : String(err) });
|
|
37
|
-
return { recentUnits: [], stuckRecoveryAttempts: 0 };
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
function saveStuckState(basePath, state) {
|
|
41
|
-
try {
|
|
42
|
-
const filePath = stuckStatePath(basePath);
|
|
43
|
-
mkdirSync(join(gsdRoot(basePath), "runtime"), { recursive: true });
|
|
44
|
-
writeFileSync(filePath, JSON.stringify({
|
|
45
|
-
recentUnits: state.recentUnits.slice(-20), // keep last 20 entries
|
|
46
|
-
stuckRecoveryAttempts: state.stuckRecoveryAttempts,
|
|
47
|
-
updatedAt: new Date().toISOString(),
|
|
48
|
-
}) + "\n");
|
|
49
|
-
}
|
|
50
|
-
catch (err) {
|
|
51
|
-
debugLog("autoLoop", { phase: "save-stuck-state-failed", error: err instanceof Error ? err.message : String(err) });
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
// ── Memory pressure monitoring (#3331) ──────────────────────────────────
|
|
55
|
-
// Check heap usage every N iterations and trigger graceful shutdown before
|
|
56
|
-
// the OS OOM killer sends SIGKILL. The threshold is 90% of the V8 heap
|
|
57
|
-
// limit (--max-old-space-size or default ~1.5-4GB depending on platform).
|
|
58
|
-
const MEMORY_CHECK_INTERVAL = 5; // check every 5 iterations
|
|
59
|
-
const MEMORY_PRESSURE_THRESHOLD = 0.85; // 85% of heap limit
|
|
60
|
-
function checkMemoryPressure() {
|
|
61
|
-
const mem = process.memoryUsage();
|
|
62
|
-
// v8.getHeapStatistics() gives heap_size_limit but requires import
|
|
63
|
-
// Use a conservative estimate: RSS > 3GB is danger zone on most systems
|
|
64
|
-
const heapMB = Math.round(mem.heapUsed / 1024 / 1024);
|
|
65
|
-
const rssMB = Math.round(mem.rss / 1024 / 1024);
|
|
66
|
-
// Try to get the actual V8 heap limit
|
|
67
|
-
let limitMB = 4096; // conservative default
|
|
68
|
-
try {
|
|
69
|
-
const v8 = require("node:v8");
|
|
70
|
-
const stats = v8.getHeapStatistics();
|
|
71
|
-
limitMB = Math.round(stats.heap_size_limit / 1024 / 1024);
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
limitMB = 4096; /* v8 stats unavailable — use conservative default */
|
|
75
|
-
}
|
|
76
|
-
const pct = heapMB / limitMB;
|
|
77
|
-
return { pressured: pct > MEMORY_PRESSURE_THRESHOLD, heapMB, limitMB, pct };
|
|
78
|
-
}
|
|
79
16
|
/**
|
|
80
17
|
* Main auto-mode execution loop. Iterates: derive → dispatch → guards →
|
|
81
18
|
* runUnit → finalize → repeat. Exits when s.active becomes false or a
|
|
@@ -87,13 +24,7 @@ function checkMemoryPressure() {
|
|
|
87
24
|
export async function autoLoop(ctx, pi, s, deps) {
|
|
88
25
|
debugLog("autoLoop", { phase: "enter" });
|
|
89
26
|
let iteration = 0;
|
|
90
|
-
|
|
91
|
-
const persisted = loadStuckState(s.basePath);
|
|
92
|
-
const loopState = {
|
|
93
|
-
recentUnits: persisted.recentUnits,
|
|
94
|
-
stuckRecoveryAttempts: persisted.stuckRecoveryAttempts,
|
|
95
|
-
consecutiveFinalizeTimeouts: 0,
|
|
96
|
-
};
|
|
27
|
+
const loopState = { recentUnits: [], stuckRecoveryAttempts: 0, consecutiveFinalizeTimeouts: 0 };
|
|
97
28
|
let consecutiveErrors = 0;
|
|
98
29
|
let consecutiveCooldowns = 0;
|
|
99
30
|
const recentErrorMessages = [];
|
|
@@ -113,19 +44,6 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
113
44
|
await deps.stopAuto(ctx, pi, `Safety: loop exceeded ${MAX_LOOP_ITERATIONS} iterations — possible runaway`);
|
|
114
45
|
break;
|
|
115
46
|
}
|
|
116
|
-
// ── Memory pressure check (#3331) ──
|
|
117
|
-
// Graceful shutdown before OOM killer sends SIGKILL.
|
|
118
|
-
if (iteration % MEMORY_CHECK_INTERVAL === 0) {
|
|
119
|
-
const mem = checkMemoryPressure();
|
|
120
|
-
debugLog("autoLoop", { phase: "memory-check", ...mem });
|
|
121
|
-
if (mem.pressured) {
|
|
122
|
-
logWarning("dispatch", `Memory pressure: ${mem.heapMB}MB / ${mem.limitMB}MB (${Math.round(mem.pct * 100)}%) — stopping auto-mode to prevent OOM kill`);
|
|
123
|
-
await deps.stopAuto(ctx, pi, `Memory pressure: heap at ${mem.heapMB}MB / ${mem.limitMB}MB (${Math.round(mem.pct * 100)}%). ` +
|
|
124
|
-
`Stopping gracefully to prevent OOM kill after ${iteration} iterations. ` +
|
|
125
|
-
`Resume with /gsd auto to continue from where you left off.`);
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
47
|
if (!s.cmdCtx) {
|
|
130
48
|
debugLog("autoLoop", { phase: "exit", reason: "no-cmdCtx" });
|
|
131
49
|
break;
|
|
@@ -244,7 +162,6 @@ export async function autoLoop(ctx, pi, s, deps) {
|
|
|
244
162
|
consecutiveCooldowns = 0;
|
|
245
163
|
recentErrorMessages.length = 0;
|
|
246
164
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
|
|
247
|
-
saveStuckState(s.basePath, loopState); // persist across session restarts (#3704)
|
|
248
165
|
debugLog("autoLoop", { phase: "iteration-complete", iteration });
|
|
249
166
|
if (reconcileResult.outcome === "milestone-complete") {
|
|
250
167
|
await deps.stopAuto(ctx, pi, "Workflow complete");
|
|
@@ -16,7 +16,6 @@ import { loadFile, parseSummary, resolveAllOverrides } from "./files.js";
|
|
|
16
16
|
import { loadPrompt } from "./prompt-loader.js";
|
|
17
17
|
import { resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveMilestoneFile, resolveTasksDir, buildTaskFileName, } from "./paths.js";
|
|
18
18
|
import { invalidateAllCaches } from "./cache.js";
|
|
19
|
-
import { rebuildState } from "./doctor.js";
|
|
20
19
|
import { parseUnitId } from "./unit-id.js";
|
|
21
20
|
import { closeoutUnit } from "./auto-unit-closeout.js";
|
|
22
21
|
import { autoCommitCurrentBranch, } from "./worktree.js";
|
|
@@ -289,11 +288,6 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
289
288
|
debugLog("postUnit", { phase: "browser-teardown", status: "closed" });
|
|
290
289
|
}
|
|
291
290
|
});
|
|
292
|
-
// Keep the on-disk STATE.md aligned with the live derived state after
|
|
293
|
-
// ordinary unit completion, before any worktree state is synced back.
|
|
294
|
-
await runSafely("postUnit", "state-rebuild", async () => {
|
|
295
|
-
await rebuildState(s.basePath);
|
|
296
|
-
});
|
|
297
291
|
// Sync worktree state back to project root (skipped for lightweight sidecars)
|
|
298
292
|
if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath !== s.basePath) {
|
|
299
293
|
await runSafely("postUnit", "worktree-sync", () => {
|
|
@@ -425,13 +425,9 @@ function cleanupAfterLoopExit(ctx) {
|
|
|
425
425
|
/* best-effort — mirror stopAuto cleanup */
|
|
426
426
|
logWarning("session", `lock cleanup failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
427
427
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
ctx.ui.setStatus("gsd-auto", undefined);
|
|
432
|
-
ctx.ui.setWidget("gsd-progress", undefined);
|
|
433
|
-
ctx.ui.setFooter(undefined);
|
|
434
|
-
}
|
|
428
|
+
ctx.ui.setStatus("gsd-auto", undefined);
|
|
429
|
+
ctx.ui.setWidget("gsd-progress", undefined);
|
|
430
|
+
ctx.ui.setFooter(undefined);
|
|
435
431
|
// Restore CWD out of worktree back to original project root
|
|
436
432
|
if (s.originalBasePath) {
|
|
437
433
|
s.basePath = s.originalBasePath;
|
|
@@ -533,22 +529,7 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
533
529
|
catch (e) {
|
|
534
530
|
debugLog("stop-cleanup-worktree", { error: e instanceof Error ? e.message : String(e) });
|
|
535
531
|
}
|
|
536
|
-
// ── Step 5:
|
|
537
|
-
// rebuildState() calls deriveState() which needs the DB for authoritative
|
|
538
|
-
// state. Previously this ran after closeDatabase(), forcing a filesystem
|
|
539
|
-
// fallback that could disagree with the DB-backed dispatch decisions —
|
|
540
|
-
// a split-brain where dispatch says "blocked" but STATE.md shows work.
|
|
541
|
-
if (s.basePath) {
|
|
542
|
-
try {
|
|
543
|
-
await rebuildState(s.basePath);
|
|
544
|
-
}
|
|
545
|
-
catch (e) {
|
|
546
|
-
debugLog("stop-rebuild-state-failed", {
|
|
547
|
-
error: e instanceof Error ? e.message : String(e),
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
// ── Step 6: DB cleanup ──
|
|
532
|
+
// ── Step 5: DB cleanup ──
|
|
552
533
|
if (isDbAvailable()) {
|
|
553
534
|
try {
|
|
554
535
|
const { closeDatabase } = await import("./gsd-db.js");
|
|
@@ -560,7 +541,7 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
560
541
|
});
|
|
561
542
|
}
|
|
562
543
|
}
|
|
563
|
-
// ── Step
|
|
544
|
+
// ── Step 6: Restore basePath and chdir ──
|
|
564
545
|
try {
|
|
565
546
|
if (s.originalBasePath) {
|
|
566
547
|
s.basePath = s.originalBasePath;
|
|
@@ -576,7 +557,7 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
576
557
|
catch (e) {
|
|
577
558
|
debugLog("stop-cleanup-basepath", { error: e instanceof Error ? e.message : String(e) });
|
|
578
559
|
}
|
|
579
|
-
// ── Step
|
|
560
|
+
// ── Step 7: Ledger notification ──
|
|
580
561
|
try {
|
|
581
562
|
const ledger = getLedger();
|
|
582
563
|
if (ledger && ledger.units.length > 0) {
|
|
@@ -590,6 +571,17 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
590
571
|
catch (e) {
|
|
591
572
|
debugLog("stop-cleanup-ledger", { error: e instanceof Error ? e.message : String(e) });
|
|
592
573
|
}
|
|
574
|
+
// ── Step 8: Rebuild state ──
|
|
575
|
+
if (s.basePath) {
|
|
576
|
+
try {
|
|
577
|
+
await rebuildState(s.basePath);
|
|
578
|
+
}
|
|
579
|
+
catch (e) {
|
|
580
|
+
debugLog("stop-rebuild-state-failed", {
|
|
581
|
+
error: e instanceof Error ? e.message : String(e),
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
}
|
|
593
585
|
// ── Step 9: Cmux sidebar / event log ──
|
|
594
586
|
try {
|
|
595
587
|
clearCmuxSidebar(loadedPreferences);
|
|
@@ -1302,6 +1294,8 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
|
|
|
1302
1294
|
pi.sendMessage({ customType: "gsd-auto", content: hookPrompt, display: true }, { triggerTurn: true });
|
|
1303
1295
|
return true;
|
|
1304
1296
|
}
|
|
1297
|
+
// Direct phase dispatch → auto-direct-dispatch.ts
|
|
1298
|
+
export { dispatchDirectPhase } from "./auto-direct-dispatch.js";
|
|
1305
1299
|
// Re-export recovery functions for external consumers
|
|
1306
1300
|
export { buildLoopRemediationSteps, } from "./auto-recovery.js";
|
|
1307
1301
|
export { resolveExpectedArtifactPath } from "./auto-artifact-paths.js";
|
|
@@ -172,10 +172,14 @@ export function registerHooks(pi) {
|
|
|
172
172
|
// Only gate-shaped ask_user_questions calls should block execution.
|
|
173
173
|
// The gate stays pending until the user selects the approval option.
|
|
174
174
|
if (event.toolName === "ask_user_questions") {
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
if (
|
|
178
|
-
|
|
175
|
+
const milestoneId = getDiscussionMilestoneId(discussionBasePath);
|
|
176
|
+
const inDiscussion = milestoneId !== null || isQueuePhaseActive();
|
|
177
|
+
if (inDiscussion) {
|
|
178
|
+
const questions = event.input?.questions ?? [];
|
|
179
|
+
const questionId = questions.find((question) => typeof question?.id === "string" && isGateQuestionId(question.id))?.id;
|
|
180
|
+
if (typeof questionId === "string") {
|
|
181
|
+
setPendingGate(questionId);
|
|
182
|
+
}
|
|
179
183
|
}
|
|
180
184
|
}
|
|
181
185
|
// ── Discussion gate enforcement: block tool calls while gate is pending ──
|
|
@@ -257,6 +261,8 @@ export function registerHooks(pi) {
|
|
|
257
261
|
return;
|
|
258
262
|
const milestoneId = getDiscussionMilestoneId(process.cwd());
|
|
259
263
|
const queueActive = isQueuePhaseActive();
|
|
264
|
+
if (!milestoneId && !queueActive)
|
|
265
|
+
return;
|
|
260
266
|
const details = event.details;
|
|
261
267
|
// ── Discussion gate enforcement: handle gate question responses ──
|
|
262
268
|
// If the result is cancelled or has no response, the pending gate stays active
|
|
@@ -287,16 +293,12 @@ export function registerHooks(pi) {
|
|
|
287
293
|
// Only unlock the gate if the user selected the first option (confirmation).
|
|
288
294
|
// Cross-references against the question's defined options to reject free-form "Other" text.
|
|
289
295
|
const answer = details.response?.answers?.[question.id];
|
|
290
|
-
const inferredMilestoneId = extractDepthVerificationMilestoneId(question.id) ?? milestoneId;
|
|
291
296
|
if (isDepthConfirmationAnswer(answer?.selected, question.options)) {
|
|
292
|
-
markDepthVerified(
|
|
293
|
-
clearPendingGate();
|
|
297
|
+
markDepthVerified(extractDepthVerificationMilestoneId(question.id) ?? milestoneId);
|
|
294
298
|
}
|
|
295
299
|
break;
|
|
296
300
|
}
|
|
297
301
|
}
|
|
298
|
-
if (!milestoneId && !queueActive)
|
|
299
|
-
return;
|
|
300
302
|
if (!milestoneId)
|
|
301
303
|
return;
|
|
302
304
|
const basePath = process.cwd();
|
|
@@ -61,9 +61,6 @@ export function parseDoctorArgs(args) {
|
|
|
61
61
|
const requestedScope = mode === "doctor" ? parts[0] : parts[1];
|
|
62
62
|
return { jsonMode, dryRun, fixFlag, includeBuild, includeTests, mode, requestedScope };
|
|
63
63
|
}
|
|
64
|
-
export function isDoctorHealActionable(issue) {
|
|
65
|
-
return issue.fixable && issue.severity !== "info";
|
|
66
|
-
}
|
|
67
64
|
export async function handleDoctor(args, ctx, pi) {
|
|
68
65
|
const { jsonMode, dryRun, fixFlag, includeBuild, includeTests, mode, requestedScope } = parseDoctorArgs(args);
|
|
69
66
|
const scope = await selectDoctorScope(projectRoot(), requestedScope);
|
|
@@ -91,7 +88,7 @@ export async function handleDoctor(args, ctx, pi) {
|
|
|
91
88
|
scope: effectiveScope,
|
|
92
89
|
includeWarnings: true,
|
|
93
90
|
});
|
|
94
|
-
const actionable = unresolved.filter(
|
|
91
|
+
const actionable = unresolved.filter(issue => issue.severity === "error");
|
|
95
92
|
if (actionable.length === 0) {
|
|
96
93
|
ctx.ui.notify("Doctor heal found nothing actionable to hand off to the LLM.", "info");
|
|
97
94
|
return;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { readFileSync, existsSync } from "node:fs";
|
|
16
16
|
import { resolve, sep } from "node:path";
|
|
17
|
-
import { readFrozenDefinition } from "./
|
|
17
|
+
import { readFrozenDefinition } from "./custom-workflow-engine.js";
|
|
18
18
|
/** Maximum characters per artifact to prevent context window blowout. */
|
|
19
19
|
const MAX_CONTEXT_CHARS = 10_000;
|
|
20
20
|
/**
|
|
@@ -13,13 +13,17 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { readFileSync } from "node:fs";
|
|
15
15
|
import { join } from "node:path";
|
|
16
|
+
import { parse } from "yaml";
|
|
16
17
|
import { readGraph, writeGraph, getNextPendingStep, markStepComplete, expandIteration, } from "./graph.js";
|
|
17
18
|
import { injectContext } from "./context-injector.js";
|
|
18
|
-
import { readFrozenDefinition } from "./definition-io.js";
|
|
19
19
|
import { parseUnitId } from "./unit-id.js";
|
|
20
20
|
import { withFileLock } from "./file-lock.js";
|
|
21
|
-
|
|
22
|
-
export
|
|
21
|
+
/** Read and parse the frozen DEFINITION.yaml from a run directory. */
|
|
22
|
+
export function readFrozenDefinition(runDir) {
|
|
23
|
+
const defPath = join(runDir, "DEFINITION.yaml");
|
|
24
|
+
const raw = readFileSync(defPath, "utf-8");
|
|
25
|
+
return parse(raw, { schema: "core" });
|
|
26
|
+
}
|
|
23
27
|
export class CustomWorkflowEngine {
|
|
24
28
|
engineId = "custom";
|
|
25
29
|
runDir;
|
|
@@ -121,25 +121,6 @@ function openRawDb(path) {
|
|
|
121
121
|
return new Database(path);
|
|
122
122
|
}
|
|
123
123
|
const SCHEMA_VERSION = 14;
|
|
124
|
-
function indexExists(db, name) {
|
|
125
|
-
return !!db.prepare("SELECT 1 as present FROM sqlite_master WHERE type = 'index' AND name = ?").get(name);
|
|
126
|
-
}
|
|
127
|
-
function dedupeVerificationEvidenceRows(db) {
|
|
128
|
-
db.exec(`
|
|
129
|
-
DELETE FROM verification_evidence
|
|
130
|
-
WHERE rowid NOT IN (
|
|
131
|
-
SELECT MIN(rowid)
|
|
132
|
-
FROM verification_evidence
|
|
133
|
-
GROUP BY task_id, slice_id, milestone_id, command, verdict
|
|
134
|
-
)
|
|
135
|
-
`);
|
|
136
|
-
}
|
|
137
|
-
function ensureVerificationEvidenceDedupIndex(db) {
|
|
138
|
-
if (indexExists(db, "idx_verification_evidence_dedup"))
|
|
139
|
-
return;
|
|
140
|
-
dedupeVerificationEvidenceRows(db);
|
|
141
|
-
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_verification_evidence_dedup ON verification_evidence(task_id, slice_id, milestone_id, command, verdict)");
|
|
142
|
-
}
|
|
143
124
|
function initSchema(db, fileBacked) {
|
|
144
125
|
if (fileBacked)
|
|
145
126
|
db.exec("PRAGMA journal_mode=WAL");
|
|
@@ -377,7 +358,7 @@ function initSchema(db, fileBacked) {
|
|
|
377
358
|
db.exec("CREATE INDEX IF NOT EXISTS idx_milestones_status ON milestones(status)");
|
|
378
359
|
db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
|
|
379
360
|
db.exec("CREATE INDEX IF NOT EXISTS idx_verification_evidence_task ON verification_evidence(milestone_id, slice_id, task_id)");
|
|
380
|
-
|
|
361
|
+
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_verification_evidence_dedup ON verification_evidence(task_id, slice_id, milestone_id, command, verdict)");
|
|
381
362
|
// v14 index — slice dependency lookups
|
|
382
363
|
db.exec("CREATE INDEX IF NOT EXISTS idx_slice_deps_target ON slice_dependencies(milestone_id, depends_on_slice_id)");
|
|
383
364
|
db.exec(`CREATE VIEW IF NOT EXISTS active_decisions AS SELECT * FROM decisions WHERE superseded_by IS NULL`);
|
|
@@ -687,7 +668,7 @@ function migrateSchema(db) {
|
|
|
687
668
|
db.exec("CREATE INDEX IF NOT EXISTS idx_milestones_status ON milestones(status)");
|
|
688
669
|
db.exec("CREATE INDEX IF NOT EXISTS idx_quality_gates_pending ON quality_gates(milestone_id, slice_id, status)");
|
|
689
670
|
db.exec("CREATE INDEX IF NOT EXISTS idx_verification_evidence_task ON verification_evidence(milestone_id, slice_id, task_id)");
|
|
690
|
-
|
|
671
|
+
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_verification_evidence_dedup ON verification_evidence(task_id, slice_id, milestone_id, command, verdict)");
|
|
691
672
|
db.prepare("INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)").run({
|
|
692
673
|
":version": 13,
|
|
693
674
|
":applied_at": new Date().toISOString(),
|
|
@@ -1353,29 +1334,6 @@ export function setSliceSummaryMd(milestoneId, sliceId, summaryMd, uatMd) {
|
|
|
1353
1334
|
currentDb.prepare(`UPDATE slices SET full_summary_md = :summary_md, full_uat_md = :uat_md WHERE milestone_id = :mid AND id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId, ":summary_md": summaryMd, ":uat_md": uatMd });
|
|
1354
1335
|
}
|
|
1355
1336
|
function rowToTask(row) {
|
|
1356
|
-
const parseTaskArray = (value) => {
|
|
1357
|
-
if (Array.isArray(value)) {
|
|
1358
|
-
return value.filter((entry) => typeof entry === "string");
|
|
1359
|
-
}
|
|
1360
|
-
if (typeof value !== "string")
|
|
1361
|
-
return [];
|
|
1362
|
-
const trimmed = value.trim();
|
|
1363
|
-
if (!trimmed)
|
|
1364
|
-
return [];
|
|
1365
|
-
try {
|
|
1366
|
-
const parsed = JSON.parse(trimmed);
|
|
1367
|
-
if (Array.isArray(parsed)) {
|
|
1368
|
-
return parsed.filter((entry) => typeof entry === "string");
|
|
1369
|
-
}
|
|
1370
|
-
if (typeof parsed === "string" && parsed.trim()) {
|
|
1371
|
-
return [parsed.trim()];
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
catch {
|
|
1375
|
-
// Older/corrupt DB rows may contain raw comma-separated paths instead of JSON arrays.
|
|
1376
|
-
}
|
|
1377
|
-
return trimmed.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
1378
|
-
};
|
|
1379
1337
|
return {
|
|
1380
1338
|
milestone_id: row["milestone_id"],
|
|
1381
1339
|
slice_id: row["slice_id"],
|
|
@@ -1395,10 +1353,10 @@ function rowToTask(row) {
|
|
|
1395
1353
|
full_summary_md: row["full_summary_md"],
|
|
1396
1354
|
description: row["description"] ?? "",
|
|
1397
1355
|
estimate: row["estimate"] ?? "",
|
|
1398
|
-
files:
|
|
1356
|
+
files: JSON.parse(row["files"] || "[]"),
|
|
1399
1357
|
verify: row["verify"] ?? "",
|
|
1400
|
-
inputs:
|
|
1401
|
-
expected_output:
|
|
1358
|
+
inputs: JSON.parse(row["inputs"] || "[]"),
|
|
1359
|
+
expected_output: JSON.parse(row["expected_output"] || "[]"),
|
|
1402
1360
|
observability_impact: row["observability_impact"] ?? "",
|
|
1403
1361
|
full_plan_md: row["full_plan_md"] ?? "",
|
|
1404
1362
|
sequence: row["sequence"] ?? 0,
|
|
@@ -26,8 +26,6 @@ export const PROVIDER_REGISTRY = [
|
|
|
26
26
|
{ id: "custom-openai", label: "Custom (OpenAI-compat)", category: "llm", envVar: "CUSTOM_OPENAI_API_KEY" },
|
|
27
27
|
{ id: "cerebras", label: "Cerebras", category: "llm", envVar: "CEREBRAS_API_KEY" },
|
|
28
28
|
{ id: "azure-openai-responses", label: "Azure OpenAI", category: "llm", envVar: "AZURE_OPENAI_API_KEY" },
|
|
29
|
-
{ id: "alibaba-coding-plan", label: "Alibaba Coding Plan", category: "llm", envVar: "ALIBABA_API_KEY", dashboardUrl: "bailian.console.aliyun.com" },
|
|
30
|
-
{ id: "alibaba-dashscope", label: "Alibaba DashScope", category: "llm", envVar: "DASHSCOPE_API_KEY", dashboardUrl: "dashscope.console.aliyun.com" },
|
|
31
29
|
// Tool Keys
|
|
32
30
|
{ id: "context7", label: "Context7 Docs", category: "tool", envVar: "CONTEXT7_API_KEY", dashboardUrl: "context7.com/dashboard" },
|
|
33
31
|
{ id: "jina", label: "Jina Page Extract", category: "tool", envVar: "JINA_API_KEY", dashboardUrl: "jina.ai/api" },
|
|
@@ -9,6 +9,7 @@ import { homedir } from "node:os";
|
|
|
9
9
|
import { isAbsolute, join } from "node:path";
|
|
10
10
|
import { statSync } from "node:fs";
|
|
11
11
|
import { validatePreferences } from "./preferences-validation.js";
|
|
12
|
+
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
12
13
|
/**
|
|
13
14
|
* Known skill directories, in priority order.
|
|
14
15
|
* Searches both the skills.sh ecosystem directory (~/.agents/skills/) and
|
|
@@ -129,5 +130,36 @@ export function resolveAllSkillReferences(preferences, cwd) {
|
|
|
129
130
|
}
|
|
130
131
|
return { resolutions, warnings };
|
|
131
132
|
}
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Format a skill reference for the system prompt.
|
|
135
|
+
* If resolved, shows the path so the agent knows exactly where to read.
|
|
136
|
+
* If unresolved, marks it clearly.
|
|
137
|
+
*/
|
|
138
|
+
export function formatSkillRef(ref, resolutions) {
|
|
139
|
+
const resolution = resolutions.get(ref);
|
|
140
|
+
if (!resolution || resolution.method === "unresolved") {
|
|
141
|
+
return `${ref} (⚠ not found — check skill name or path)`;
|
|
142
|
+
}
|
|
143
|
+
// For absolute paths where SKILL.md is just appended, don't clutter the output
|
|
144
|
+
if (resolution.method === "absolute-path" || resolution.method === "absolute-dir") {
|
|
145
|
+
return ref;
|
|
146
|
+
}
|
|
147
|
+
// For bare names resolved from skill directories, show the resolved path
|
|
148
|
+
return `${ref} → \`${resolution.resolvedPath}\``;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Resolve the skill discovery mode from effective preferences.
|
|
152
|
+
* Defaults to "suggest" -- skills are identified during research but not installed automatically.
|
|
153
|
+
*/
|
|
154
|
+
export function resolveSkillDiscoveryMode() {
|
|
155
|
+
const prefs = loadEffectiveGSDPreferences();
|
|
156
|
+
return prefs?.preferences.skill_discovery ?? "suggest";
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Resolve the skill staleness threshold in days.
|
|
160
|
+
* Returns 0 if disabled, default 60 if not configured.
|
|
161
|
+
*/
|
|
162
|
+
export function resolveSkillStalenessDays() {
|
|
163
|
+
const prefs = loadEffectiveGSDPreferences();
|
|
164
|
+
return prefs?.preferences.skill_staleness_days ?? 60;
|
|
165
|
+
}
|
|
@@ -92,18 +92,3 @@ export const KNOWN_UNIT_TYPES = [
|
|
|
92
92
|
"discuss-milestone", "discuss-slice", "worktree-merge",
|
|
93
93
|
];
|
|
94
94
|
export const SKILL_ACTIONS = new Set(["use", "prefer", "avoid"]);
|
|
95
|
-
/**
|
|
96
|
-
* Format a skill reference for the system prompt.
|
|
97
|
-
* If resolved, shows the path so the agent knows exactly where to read.
|
|
98
|
-
* If unresolved, marks it clearly.
|
|
99
|
-
*/
|
|
100
|
-
export function formatSkillRef(ref, resolutions) {
|
|
101
|
-
const resolution = resolutions.get(ref);
|
|
102
|
-
if (!resolution || resolution.method === "unresolved") {
|
|
103
|
-
return `${ref} (⚠ not found — check skill name or path)`;
|
|
104
|
-
}
|
|
105
|
-
if (resolution.method === "absolute-path" || resolution.method === "absolute-dir") {
|
|
106
|
-
return ref;
|
|
107
|
-
}
|
|
108
|
-
return `${ref} → \`${resolution.resolvedPath}\``;
|
|
109
|
-
}
|