When all credentials for a provider are exhausted, the system now
automatically falls back to the next available provider in a
user-configured fallback chain. Higher-priority providers are
restored automatically when their backoff expires.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
deriveState() was called ~7 times per dispatch cycle, each call re-reading
the entire .gsd/milestones/ tree from disk (~50-60 file reads per call,
~350-420 redundant reads per cycle). Add a 100ms TTL cache keyed by
basePath so repeated calls within the same dispatch cycle return the
cached result. Expose invalidateStateCache() and call it at every
mutation boundary in auto.ts: handleAgentEnd start, post-merge
re-derivations, and resume-from-pause.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests that write files and immediately call deriveState() got stale results
because the path resolution cache (dirEntryCache/dirListCache) returned
cached directory listings that didn't include newly written files.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a "Custom (OpenAI-compatible)" provider option to the API key
flow in the onboarding wizard. When selected, prompts for base URL,
API key, and model ID, then writes the config to models.json.
Add explicit provider targeting for model preferences when the same
model ID exists across multiple providers (e.g., claude-sonnet-4-6
on both Anthropic and Bedrock).
Two formats supported:
- String: "bedrock/claude-sonnet-4-6"
- Object: { model: claude-sonnet-4-6, provider: bedrock }
The provider/model string format already worked in the resolution
code but was undocumented. This adds the provider field to the
object format and documents both approaches.
OpenRouter models use slash-separated IDs (e.g. "moonshotai/kimi-k2.5") where the
full string is the model ID on the "openrouter" provider. The auto-mode model
switcher incorrectly split on the first slash and treated the prefix as a provider
name, causing all OpenRouter preference models to fail resolution and fall back to
the default model for every phase.
Now the resolver first checks whether the slash-prefix is a known provider, and if
not (or if no match is found), falls back to matching the full string as a model ID
— consistent with model-resolver.ts.
Also improves the progress widget and notifications to show [PHASE] and
provider/model so users can confirm the correct model is active.
Closes#402
Systems with a buggy git-svn Perl module (notably Arch Linux) emit
"Duplicate specification" warnings on every git invocation. Filter
these from error messages and suppress git-svn loading via GIT_SVN_ID.
Also update repository URLs from stale glittercowboy/gsd-pi to
gsd-build/gsd-2.
loader.ts previously maintained a hardcoded list of bundled extension paths
for GSD_BUNDLED_EXTENSION_PATHS. This required manual updates whenever
extensions were added or removed, and created a consistency gap with
buildResourceLoader() which already discovers extensions dynamically.
Replace with runtime directory scanning that mirrors the discovery rules
in resource-loader.ts:
- Top-level .ts/.js files → extension entry point
- Directories with index.ts or index.js → extension entry point
- Directories without either (shared/, remote-questions/) → skipped
Benefits:
- Adding a new extension no longer requires editing loader.ts
- GSD_BUNDLED_EXTENSION_PATHS stays in sync with what buildResourceLoader()
loads in the main process — subagents now receive the same extensions
- Fixes: 5 extensions (google-search, mcporter, ttsr, universal-config,
voice) were loaded in the main process but missing from
GSD_BUNDLED_EXTENSION_PATHS, meaning subagents did not receive them
- Eliminates a common source of merge conflicts for contributors and forks
that add custom extensions
* fix: include export-html templates in pkg/ shim for --export support (#370)
The --export command fails with ENOENT because getExportTemplateDir()
resolves to pkg/dist/core/export-html/ which doesn't exist. The build
script copies themes into pkg/dist/ but had no equivalent step for
export-html templates.
- Add copy-export-html build script mirroring copy-themes pattern
- Chain copy-export-html into the build script
- Fix .gitignore: re-negate pkg/dist/ after the catch-all dist/ rule
- Add vendor/ exception for pkg/dist/core/export-html/vendor/
- Commit template.html, template.css, template.js, and vendor/ files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: tighten .gitignore negation to specific pkg/dist subdirs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevent duplicate slice/dependency summaries from being inlined into
prompts when the same ID appears more than once. Uses a Set to track
already-included IDs in inlineDependencySummaries and
buildCompleteMilestonePrompt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
parseRoadmap, parsePlan, parseSummary, and parseContinue are pure
functions that get called repeatedly with the same content during
deriveState dispatch cycles. A module-scoped Map keyed by a fast
composite key (length + first 100 chars + last 100 chars) avoids
redundant parsing. Cache caps at 50 entries and clears when full.
Exports clearParseCache() for explicit invalidation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
resolveDir(), resolveFile(), and resolveTaskFiles() call readdirSync()
on every invocation with no caching. These are called dozens of times
per dispatch cycle through resolveMilestonePath, resolveSliceFile,
relTaskFile, etc. This adds a module-level Map cache for directory
listings with an exported clearPathCache() function for invalidation
at dispatch cycle boundaries and milestone transitions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The dispatch gap watchdog is a safety net for when the dispatch chain
silently breaks. 30s is unnecessarily long and makes failures feel
sluggish. 5s is plenty of time to detect a stall without the wait.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The LoginDialogComponent's showPrompt/showManualInput methods returned
Promises that could hang forever if: (a) a new prompt superseded a
pending one without rejecting it, (b) the abort signal fired without
cleaning up promises, or (c) the dialog was removed without disposing
pending state.
- Add rejectPending() to safely reject outstanding promises before
creating new ones
- Wire AbortSignal listener to auto-reject on external cancellation
- Add dispose() method called by restoreEditor() as a safety net
- Clean up manualCodePromise on error path
- Filter internal error messages (Superseded/disposed) from user display
GSD's internal git commits (auto-commits, slice merges, runtime file cleanup)
trigger pre-commit hooks like lint-staged. When lint-staged runs formatters
(e.g. prettier) on already-formatted files, it detects no changes from its
tasks and blocks the commit with "Prevented an empty git commit!" — even
though there are legitimate staged changes.
This is a known incompatibility between lint-staged's backup/restore mechanism
and squash-merge commits. The code being committed already passed hooks when
originally committed on the slice branch, so re-running hooks on the merge
commit is redundant.
Adds --no-verify to all 7 internal commit calls in git-service.ts:
- writeIntegrationBranch() — records integration branch metadata
- smartStage() cleanup — untracks runtime files from git index
- commit() — general commit method used by GSD
- autoCommit() — auto-commits dirty working tree between units
- mergeSliceToMain() — pre-merge runtime file untracking (2 calls)
- mergeSliceToMain() — squash-merge commit and --no-ff amend
* fix(auto): prevent hang when dispatch chain breaks after slice tasks complete (#381)
After the last task in a slice completes, dispatchNextUnit() can throw
(e.g. template mismatch, branch error, or any unprotected operation).
The error propagates to the pi event emitter which silently swallows
async rejections, leaving auto-mode active but permanently stalled —
no dispatch, no stop, no recovery.
Three defensive layers added:
1. Try-catch around dispatchNextUnit in handleAgentEnd — catches errors,
shows them to the user, and schedules a retry via the gap watchdog.
2. Dispatch gap watchdog (30s timer) — fires when auto-mode is active
but no unit was dispatched after a unit completion. Re-derives state
and retries. If retry fails, stops auto-mode with diagnostics.
3. Error boundary in the agent_end event handler — last-resort catch
that pauses auto-mode if handleAgentEnd itself throws.
Closes#381
* fix: improve Cloud Code Assist 404 error with actionable model guidance (#368)
When a model like gemini-2.0-flash isn't available via Cloud Code Assist,
the 404 error now names the model and suggests using the google provider
with GOOGLE_API_KEY or switching to a supported model.
* fix: read resources from dist/ to prevent branch-drift in npm-link setups
initResources() reads extensions, prompts, skills, and agents from
src/resources/ — which points into the live working tree when gsd is
installed via npm link. Switching branches in the gsd repo changes
src/resources/ for ALL projects using gsd, causing stale or broken
extensions to be synced to ~/.gsd/agent/ on next launch.
Fix: the build step now copies src/resources/ to dist/resources/.
At runtime, resource-loader.ts and loader.ts prefer dist/resources/
(stable, set at build time) over src/resources/ (live working tree).
Fallback to src/resources/ is preserved for setups without a build.
Also adds npm run dev watch-resources watcher that syncs src/resources/
to dist/resources/ on file changes, running alongside tsc --watch.
* fix: cache prompt templates per session to prevent cross-session invalidation
When two gsd sessions run concurrently, the second session's
initResources() overwrites ~/.gsd/agent/ templates on disk. The first
session then reads a newer template that expects variables its in-memory
code doesn't know about, causing 'template declares {{X}} but no value
was provided' crashes that hang auto-mode indefinitely.
Fix: cache each template on first read. A running session uses the
template versions from when it first loaded them, immune to later
disk overwrites by other sessions.
Three bugs prevented native web_search from being used with Anthropic:
1. model_select never fires on session restore (SDK's modelsAreEqual
guard suppresses it when the default model matches the restored one),
so isAnthropicProvider stayed false and native search was never
injected. Fix: fall back to detecting "claude-" in the payload model
name when model_select hasn't fired.
2. Custom search tools (search-the-web, search_and_read) were only
removed from the payload when BRAVE_API_KEY was missing. With the
key present, the model saw both native and Brave tools and often
picked the Brave ones, which failed with network errors. Fix: always
remove custom search tools from Anthropic requests.
3. google_search (Gemini-based) was not included in the disable list,
so the model could call it even without GEMINI_API_KEY set. Fix: new
CUSTOM_SEARCH_TOOL_NAMES list covers all three custom search tools.
npx runs with --ignore-scripts, skipping postinstall. The loader now
creates node_modules/@gsd/* symlinks pointing to packages/* before
importing cli.js, so @gsd/* packages resolve without postinstall.
Bumps to v2.10.12.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>