Commit graph

170 commits

Author SHA1 Message Date
Tibsfox
42d2e25e0b fix(cli): handle pnpm global installs by merging both node_modules roots
pnpm's virtual-store layout doesn't hoist @gsd/* workspace scopes to
the parent node_modules, so the simple symlink-to-hoisted approach from
the original fix (#3529) left workspace packages unresolvable.

Detect when workspace scopes are missing from the hoisted root and
create a real node_modules directory with symlinks from both the hoisted
root (external deps) and internal root (workspace packages). A .gsd-merged
marker file skips rebuild on subsequent startups.

Restores behavioral tests deleted in the original PR and adds unit tests
for the pnpm merge path and scope detection logic.

Reported-by: @moekify
Fixes: #3564 (comment)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:40:32 -07:00
Tibsfox
a3f2de828e test(cli): add node_modules symlink regression test 2026-04-05 11:56:58 -07:00
Jeremy
9fe13da3f2 fix(pi-coding-agent): resolve model fallback race that ignores configured provider (#3534)
Extension-provided models (e.g. claude-code/*) were unavailable during
findInitialModel() because pendingProviderRegistrations had not been
flushed yet, causing the fallback chain to select Google Gemini even
when the user explicitly configured claude-code as their default.

Three compounding issues fixed:

(A) Flush pendingProviderRegistrations in createAgentSession() before
    findInitialModel() runs, so extension models are in the registry
    when initial model selection happens.

(B) Re-apply the validated model to the session after
    validateConfiguredModel() in both print and interactive CLI paths.
    Previously, validation updated settingsManager but never called
    session.setModel(), leaving the session on the wrong model.

(C) Update defaultModelPerProvider.anthropic from "claude-opus-4-6[1m]"
    to "claude-opus-4-6" — the [1m] variant was removed from the model
    registry when the base model was upgraded to 1M context, causing the
    Anthropic fallback to silently fail and skip to Google.

Closes #3534
2026-04-05 07:14:24 -05:00
Tom Boucher
4cb6252b9b fix(perf): share jiti module cache across extension loads (#3308)
* fix(perf): share jiti module cache across extension loads (#2108)

Each extension was creating a new jiti instance with moduleCache: false,
causing shared dependencies to be recompiled for every extension. Use a
shared singleton with moduleCache: true so shared modules are compiled once.

Export resetExtensionLoaderCache() for test teardown and explicit reload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: correct loader path in extension-load-perf test (4 → 3 levels up)

The test file is at src/tests/ (2 levels deep from repo root), so
fileURLToPath(import.meta.url) + 3x'..' reaches the repo root.
Using 4 levels exits the repo into the GitHub Actions workspace parent,
causing ERR_MODULE_NOT_FOUND for loader.js in dist/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use process.cwd() for loader path in perf test (source/compiled portability)

import.meta.url resolves to different depths in source (src/tests/) vs compiled
(dist-test/src/tests/), so relative '../' navigation produces the wrong path in
the build phase. process.cwd() is always the repo root in CI regardless of
where the test file is compiled to.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: trek-e <trek-e@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 07:59:17 -04:00
Tom Boucher
e9dabdc649 fix(resource-sync): prune removed bundled subdirectory extensions on upgrade (#1972)
* fix(resource-sync): prune removed bundled subdirectory extensions on upgrade

The managed-resources manifest and pruning system only tracked root-level
files, not subdirectory extensions. When a bundled subdirectory extension
like mcporter/ was removed from the bundle in a newer GSD version, the
previously-synced copy in ~/.gsd/agent/extensions/ persisted indefinitely,
causing tool name conflicts with its replacement (mcp-client/).

- Add installedExtensionDirs to the manifest alongside installedExtensionRootFiles,
  recording directory names present in the bundled extensions dir at sync time.
- In pruneRemovedBundledExtensions, diff previous installedExtensionDirs against
  current bundled dirs and rmSync({ recursive: true }) any that were removed.
- Add mcporter to the hardcoded stale-entry list for pre-manifest upgrades.
- Fix extension conflict error prefix: also match "conflicts with" (not just
  "supersedes") so extension-vs-extension conflicts are classified as warnings
  rather than hard errors.

Fixes #1955

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(resource-loader): repair mangled lines from conflict resolution

The Python regex used to resolve cherry-pick conflicts stripped trailing
newlines, causing declarations and comments to merge onto the same line.
Replace the file with the upstream/main version which contains all the
installedExtensionDirs logic correctly formatted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(resource-loader): sweep all installed extension dirs not in current bundle

The manifest-based pruner only removed dirs it had previously recorded.
Extensions installed by pre-manifest versions (or manually) were never
tracked, so they survived upgrades. Add a sweep of the actual installed
extensions directory that removes any subdirectory absent from the current
bundle, regardless of manifest history.

Fixes the mcporter stale-dir regression test (#1972).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: check external-state DB path before symlink-resolved handler (#2952)

The external-state handler added in c609d813 was placed after the generic
symlink-resolved handler, which matches the same /.gsd/projects/<hash>/worktrees/
pattern and short-circuits to the wrong result. Move the external-state check
(which uses the more specific hex-hash regex) first so it takes precedence.

Fixes shared-wal test: external-state worktree path resolves to project state DB.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: update db-path-worktree-symlink expectations for external-state (#2952)

/.gsd/projects/<hash>/worktrees/ paths now resolve to <hash>/gsd.db
after the external-state handler from #2952 was placed before the
symlink-resolved handler. On POSIX, getcwd() returns canonical paths so
<proj>/.gsd/projects/<hash>/worktrees/ would in practice appear as
~/.gsd/projects/<hash>/worktrees/ after OS symlink resolution — both
correctly handled by the external-state behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: trek-e <trek-e@users.noreply.github.com>
2026-04-05 07:44:51 -04:00
Tom Boucher
29517f177d refactor(web): consolidate subprocess boilerplate into shared runner (#1899)
* refactor(web): consolidate subprocess boilerplate into shared runner

Extract subprocess-runner.ts with runSubprocess<T>() and resolveModulePaths()
to replace identical execFile+Promise+JSON.parse callback blocks duplicated
across 12 web service files. Adds 30s default timeout to all subprocess calls.

Fixes #1888

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: check external-state DB path before symlink-resolved handler (#2952)

The external-state handler added in c609d813 was placed after the generic
symlink-resolved handler, which matches the same /.gsd/projects/<hash>/worktrees/
pattern and short-circuits to the wrong result. Move the external-state check
(which uses the more specific hex-hash regex) first so it takes precedence.

Fixes shared-wal test: external-state worktree path resolves to project state DB.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: update db-path-worktree-symlink expectations for external-state (#2952)

/.gsd/projects/<hash>/worktrees/ paths now resolve to <hash>/gsd.db
after the external-state handler from #2952 was placed before the
symlink-resolved handler. On POSIX, getcwd() returns canonical paths so
<proj>/.gsd/projects/<hash>/worktrees/ would in practice appear as
~/.gsd/projects/<hash>/worktrees/ after OS symlink resolution — both
correctly handled by the external-state behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: trek-e <trek-e@users.noreply.github.com>
2026-04-05 07:44:32 -04:00
Tom Boucher
ba71db3b28 fix(web): use safePackageRootFromImportUrl for cross-platform package root (#1881) (#1893)
Replace raw fileURLToPath in getDefaultPackageRoot with safePackageRootFromImportUrl
which returns null instead of throwing when the URL is not a valid local file URL.
This prevents the standalone bundle from crashing on Windows when import.meta.url
is baked in at build time with a Linux file:// path.

Co-authored-by: trek-e <trek-e@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 07:43:46 -04:00
Tom Boucher
2a40f3f35d feat(mcp-client): add OAuth auth provider for HTTP transport (#3295)
* feat(mcp-client): add OAuth auth provider for HTTP transport

MCP HTTP transport was created without any auth options, causing 401
errors when connecting to remote servers (Sentry, Linear, etc.) that
require OAuth or bearer token authentication.

Add a new auth module that builds StreamableHTTPClientTransport options
from two config fields:
- `headers`: static headers with ${VAR} env resolution (for bearer tokens)
- `oauth`: full OAuthClientProvider for servers implementing MCP OAuth

The config is parsed from .mcp.json / .gsd/mcp.json and passed through
to the SDK transport constructor.

Fixes #2160

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: retrigger CI

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: trek-e <trek-e@users.noreply.github.com>
2026-04-05 01:05:10 -04:00
Tom Boucher
34e73b01d1 fix(extensions): use bundledExtensionKeys for conflict detection instead of broken path heuristic (#3305)
Fixes #2075

The isBuiltIn check in detectExtensionConflicts used a path-based
heuristic that excluded paths containing /.gsd/agent/extensions/.
Since initResources() syncs all bundled extensions into that same
directory, the heuristic could never return true for bundled extensions,
preventing the "supersedes" hint from being appended. This caused
downstream cli.ts to display "Extension load error" instead of the
intended "Extension conflict" for benign precedence collisions.

Replace the path heuristic with an explicit bundledExtensionKeys set
(already computed by buildResourceLoader) threaded through to
detectExtensionConflicts. The set is matched against the extension
directory name extracted from the owner path.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 01:04:50 -04:00
Tom Boucher
5c2d8988bb fix: track remote-questions in managed-resources manifest (#3312)
* fix: track remote-questions extension in managed-resources manifest

writeManagedResourceManifest only checked for index.js/index.ts when
deciding if a subdirectory is an extension. remote-questions uses mod.ts
as its entry point and was missed, causing it to be pruned on upgrades.

Also check for extension-manifest.json which is the canonical marker for
bundled extensions.

Fixes #2367

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: retrigger CI

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: trek-e <trek-e@users.noreply.github.com>
2026-04-05 01:04:44 -04:00
Tom Boucher
9b6ff01471 docs: add provider setup guide for third-party LLM providers (#3294)
* docs: add provider setup guide and improve onboarding hints

Fixes #2161

Add docs/providers.md with step-by-step setup instructions for every
supported LLM provider: OpenRouter, Ollama, LM Studio, vLLM, SGLang,
and all built-in providers. Includes env var names, example configs,
common pitfalls, and verification steps.

Improve onboarding wizard:
- Add URL hints to provider selection list
- Show common local endpoints when choosing Custom (OpenAI-compatible)
- Add post-setup guidance for OpenRouter and custom endpoints
- Reference docs/providers.md for compat troubleshooting

Update cross-references in getting-started.md, troubleshooting.md,
docs/README.md, and help-text.ts to link to the new guide.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: verify config help mentions OpenRouter, Ollama, and docs/providers.md

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: trek-e <trek-e@users.noreply.github.com>
2026-04-05 00:48:19 -04:00
Justin Wyer
95875c41c5 refactor(test): consolidate regression and override tests into #666 test files
Move regression tests and override tests from standalone files into
the existing test files introduced by PR #666:

- resolve-config-value.test.ts: add REGRESSION #666 describe block
  and setAllowedCommandPrefixes override tests
- url-utils.test.ts: add REGRESSION #666 describe block and
  setFetchAllowedUrls override tests
- Delete: regression-666.test.ts, resolve-config-value-override.test.ts,
  url-utils-override.test.ts

Same 59 tests, fewer files, tests live next to the code they test.
2026-04-02 14:06:19 +02:00
Justin Wyer
d5f581fe6b test: add regression tests for #666 (fails on main, passes on fix)
Two regression tests that prove the bug introduced by PR #666:

1. Non-default credential tool (sops) is silently blocked by the
   hardcoded SAFE_COMMAND_PREFIXES with no way to override.
2. Private IP URL is silently blocked by isBlockedUrl() with no
   way to allowlist.

Both tests use dynamic import to check for the override functions,
so they run cleanly on both main (where they fail) and this branch
(where they pass). Verified in a git worktree of main.
2026-04-02 14:03:34 +02:00
Justin Wyer
71caa18552 fix(security): add configurable overrides for command allowlist and SSRF blocklist
PR #666 introduced hardcoded SAFE_COMMAND_PREFIXES and SSRF URL
blocklists with no override mechanism. Users with non-standard
credential tools (sops, doppler, age, infisical) or needing to fetch
from internal URLs (self-hosted docs, VPN services) were silently
blocked with no recourse.

Add two global-only settings (ignored in project-level settings.json
to preserve the security property against malicious repos):

- allowedCommandPrefixes: replaces the built-in command allowlist
- fetchAllowedUrls: exempts hostnames from SSRF blocking

Both also support env var overrides (GSD_ALLOWED_COMMAND_PREFIXES,
GSD_FETCH_ALLOWED_URLS) for CI/container environments. Env vars
take precedence over settings.json.

Security model: global-only keys are stripped from project settings
at load time via stripGlobalOnlyKeys(), applied at all three
assignment points for this.projectSettings. The merge function
stays untouched — no future caller can accidentally skip stripping.

15 new tests covering override behavior, cache invalidation,
allowlist exemptions, and global-only enforcement.
2026-04-02 13:45:05 +02:00
Jeremy McSpadden
46d5fa56af Merge pull request #2312 from jeremymcs/fix/tui-review
fix(tui): comprehensive TUI review — layout, flow, rendering, and state fixes
2026-04-01 16:38:31 -05:00
Jeremy
a3a2f2e3b3 test(tui): update provider-manager tests for confirmation-based removal
Tests now match the new hasAuth guard and double-press r confirmation
flow introduced in the TUI review PR.
2026-04-01 16:24:14 -05:00
Jeremy McSpadden
d0555857c2 Merge pull request #2976 from jeremymcs/splash-header-updates-clean
feat(splash): add remote channel indicator to tools row
2026-04-01 16:14:23 -05:00
Tom Boucher
de9ba8aeb7 fix: add windowsHide to all web-mode subprocess spawns (#2628) (#3046)
On Windows, child_process.spawn() and execFile() open a visible console
window by default. The web server spawn, RPC bridge, browser opener, and
all 15 web service subprocess calls were missing windowsHide: true,
causing constant console window flashing when running gsd --web.

Closes #2628

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:50:13 -06:00
Tom Boucher
893c525578 fix(read-tool): clamp offset to file bounds instead of throwing (#3007) (#3042)
When an agent requests read(file, offset: 30) on a 13-line file, the
read tool threw "Offset 30 is beyond end of file" which propagated as
invalid JSON downstream during milestone completion. Now clamps the
offset to the last line and prepends a notice, allowing the agent to
continue with valid content.

Fixes both read.ts and hashline-read.ts variants.

Closes #3007

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:48:01 -06:00
Tom Boucher
dfb4fbecef fix: preserve interactive terminal across tab switches and project changes (#3055)
Two root causes destroyed terminal state during normal navigation:

1. The pagehide handler fired a shutdown beacon unconditionally, but on
   mobile/Safari tab switches pagehide fires with event.persisted=true
   (bfcache entry). This killed the server and all PTY sessions when the
   user merely switched browser tabs. Fix: check event.persisted and skip
   the beacon when the page is being cached, not unloaded.

2. ShellTerminal used project-agnostic session IDs ("default"), so
   switching projects and switching back either collided with the old
   session or spawned a new one, losing terminal state. Fix: scope session
   IDs by project path (e.g. "default:/path/to/project") so the server's
   getOrCreateSession returns the existing live PTY on reconnect.

Closes #2701

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:46:09 -06:00
Tom Boucher
05b7cb95cb fix: route gsd auto to headless runner to prevent hang on piped stdin/stdout (#3057)
`gsd auto` was not handled as a subcommand — it fell through to the
interactive TUI, which hangs indefinitely when stdin/stdout are piped
(non-TTY). Add `auto` as a recognized subcommand that rewrites argv
and delegates to `runHeadless(parseHeadlessArgs(...))`, matching the
existing `gsd headless auto` behavior.

Also adds `gsd auto` to TTY error hints and help text.

Closes #2732

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:44:04 -06:00
Tom Boucher
cb26d71483 fix: preserve active tab when switching projects (#3071)
Closes #2711

Two changes fix the tab-reset-to-dashboard bug:

1. Remove the forced `gsd:navigate-view` dispatch to "dashboard" in
   ProjectsPanel.handleSelectProject — this was unconditionally resetting
   the view on every project switch.

2. Add a useEffect in WorkspaceChrome that resets `viewRestored` when
   `projectPath` changes, so the per-project sessionStorage view restore
   fires for the newly-selected project.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:41:05 -06:00
Tom Boucher
6e22a20580 fix: defer model validation until after extensions register (#3089)
* fix: defer model validation until after extensions register (#2626)

Extension-provided models (e.g. claude-code/claude-sonnet-4-6) were
silently overwritten on every startup because the model validation ran
before createAgentSession(), which is where extensions register their
models in the ModelRegistry. At validation time, extension models did
not exist in the registry, so the user's valid choice was replaced
with a built-in fallback.

Extract validation into validateConfiguredModel() and call it after
createAgentSession() in both print-mode and interactive-mode paths.

Closes #2626

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: align MinimalSettingsManager interface with SettingsManager

The MinimalSettingsManager interface used `string` for thinking level
types, but SettingsManager uses a specific union type and returns
`undefined`. This caused TS2345 at cli.ts lines 448 and 587.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:38:10 -06:00
Tom Boucher
3e78270cad fix: chat mode misrepresents terminal output, looks stuck, omits user messages (#3092)
Three root causes addressed:

1. PtyChatParser: user input echoed after a bare prompt line (e.g. "❯ \n"
   followed by "hello\n") was misclassified as assistant content. Added
   _awaitingInput flag that flips true on prompt boundary and classifies the
   next content line as role=user.

2. Chat mode "looks stuck": when the session is idle (connected, not
   streaming, has timeline content), no visual cue indicated GSD was waiting
   for input. Added a "Ready for your input" indicator with a pulsing dot.

3. Transcript overflow misalignment: chatUserMessages was not trimmed when
   liveTranscript/completedTurnSegments overflowed MAX_TRANSCRIPT_BLOCKS,
   causing index-based interleaving to pair user messages with wrong
   assistant responses.

Also exposed isAwaitingInput() on PtyChatParser so chat UIs can query
whether the session is waiting for user input, and widened the > and $
prompt marker regexes to match bare prompts after trimEnd strips trailing
whitespace.

Closes #2707

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 14:36:21 -06:00
Tom Boucher
873a79f484 fix: skip staleness rebuild in npm tarball installs (#2877) (#3250)
The ensure-workspace-builds.cjs postinstall script falsely detected
workspace packages as stale in npm tarball installs. npm sets all
tarball entries to a canonical timestamp (Oct 26 1985), but extraction
ordering causes src/ files to appear 1-2 seconds newer than dist/
files. This triggered a rebuild attempt that either failed silently
(no tsc available) or — when tsc was globally installed — could
produce broken dist/ output, corrupting the known-good pre-built
files and causing the DefaultResourceLoader export error on startup.

The fix gates the src-vs-dist staleness check behind a .git directory
check: only development clones (with .git/) perform the timestamp
comparison. npm tarball installs (no .git/) only check for missing
dist/index.js, which is the safe and correct behavior.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: TÂCHES <afromanguy@me.com>
2026-03-30 14:17:02 -06:00
Tom Boucher
50fbd0a837 fix(docs): use ~/.pi/agent/extensions/ for community extension install path (#3131) (#3259)
Community extensions must be placed in ~/.pi/agent/extensions/, not
~/.gsd/agent/extensions/ which is reserved for bundled extensions synced
from the gsd-pi package. Extensions placed in the wrong path are silently
ignored by the loader.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:50:25 -06:00
Tom Boucher
3d896eee8a fix: skip TUI render loop on non-TTY stdout to prevent CPU burn (#3095) (#3263)
When gsd is spawned as an RPC bridge child process, stdout is a pipe
(process.stdout.isTTY === undefined). The TUI render loop would run at
~4,600 renders/sec writing ANSI escape codes to the pipe, consuming
500%+ CPU per process while idle.

Add isTTY guard to Terminal interface, ProcessTerminal.start(), TUI.start(),
and requestRender() so the entire render pipeline is skipped on non-TTY stdout.
RemoteTerminal (browser-backed) correctly reports isTTY=true.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:49:55 -06:00
Tom Boucher
155df22e9e fix: invalidate workspace state on turn_end so milestones list stays current (#2706) (#3266)
The milestones list only refreshed on agent_end events, causing stale
milestone state during multi-turn agent execution. Add turn_end as a
workspace cache invalidation trigger so the UI reflects milestone
changes after each turn boundary.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:48:40 -06:00
Tom Boucher
03a479858d fix(web): reconcile auto-mode state with on-disk lock in dashboard (#2705) (#3265)
The subprocess spawned by collectAuthoritativeAutoDashboardData always
starts with fresh module state (s.active === false), so the web UI
always showed "Start auto" even while auto mode was running. After
obtaining the subprocess result, reconcile active/paused state with
the on-disk session lock (.gsd/auto.lock) and paused-session metadata
(.gsd/runtime/paused-session.json).

Closes #2705

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:48:21 -06:00
Tom Boucher
3ec96fd992 fix: redirect auto-mode to headless when stdout is piped (#2732) (#3269)
When `gsd auto` is run with piped stdout (e.g. `gsd auto | cat` or
`gsd auto > file`), the TUI cannot render on a non-terminal output
stream, causing the process to hang indefinitely.

This fix:
- Detects piped stdout before entering interactive mode and redirects
  `gsd auto` to headless mode automatically
- Extends the interactive mode TTY gate to also check process.stdout.isTTY
  (previously only checked stdin), with a descriptive error message
- Adds `gsd headless` to the non-interactive alternatives hint

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:48:07 -06:00
Tom Boucher
7b72e1132c fix: correct OAuth fallback request shape for google_search (#2963) (#3272)
The searchWithOAuth() function sent a request body that the Cloud Code
Assist API rejected with 400 INVALID_ARGUMENT. Two issues:

1. URL was missing ?alt=sse query parameter (endpoint returns SSE format)
2. Request body was missing the required userAgent field

Also adds regression tests that capture the fetch call and assert the
request URL and body match the Cloud Code Assist wire contract.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:46:38 -06:00
Jeremy
b0bb5390fb feat(splash): add remote channel indicator to welcome screen tools row
Add configured remote channel (Discord/Slack/Telegram) as a checkmark
in the tools row alongside Brave/Answers/Jina. Remove verbose remote
status lines and duplicate display from header-renderer and register-hooks.
2026-03-28 12:38:33 -05:00
TÂCHES
0a2c9b64c6 feat: stream full text and thinking output in headless verbose mode (#2934)
Previously, headless --verbose mode accumulated text_delta events into a
buffer and displayed a single truncated 120-char [thinking] line before
tool calls. The model's actual text responses between tool calls were
effectively invisible.

Changes:
- Stream text_delta and thinking_delta events directly to stderr in
  verbose mode with [text] and [thinking] block markers
- No truncation — full model output is visible
- Fix non-verbose fallback: read from ame.delta (correct field) instead
  of ame.text (always undefined for text_delta events)
- Track inTextBlock/inThinkingBlock state to properly close streaming
  blocks before tool calls
- Expand summarizeToolArgs with support for async_bash, await_job,
  cancel_job, find, ls, lsp, hashline_edit, subagent, browser_navigate,
  and gsd_* tools
- Add streaming formatter functions: formatTextStart, formatTextEnd,
  formatThinkingStart, formatThinkingEnd
- Update tests for new tool arg summarization and path field handling
2026-03-27 21:57:11 -06:00
Lex Christopherson
1f130952a7 fix(headless): skip execution_complete for multi-turn commands (auto/next)
Multi-turn commands (auto, next) have their own completion signals via
isTerminalNotification ("Auto-mode stopped..."/"Step-mode stopped...").
The execution_complete event fires after command setup before any real
work begins, causing these commands to exit immediately with zero work done.

Closes #2917

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 17:38:18 -06:00
TÂCHES
ac85f28822 Merge pull request #2915 from gsd-build/fix/headless-completed-status
fix(headless): match "completed" status from RPC v2
2026-03-27 17:16:00 -06:00
Lex Christopherson
8870d84012 fix(headless): match "completed" status from RPC v2 in exit code mapper
mapStatusToExitCode only handled "complete" but RPC v2 emits "completed",
causing all headless sessions to falsely timeout and restart.

Also emits milestone-ready notification in checkAutoStartAfterDiscuss so
headless parent can detect and chain into auto-mode.

Closes #2914

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 17:04:31 -06:00
Lex Christopherson
d1c948086e merge: Resolve conflicts with origin/main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 15:56:48 -06:00
Lex Christopherson
2d41de9b32 fix: Accept flags after positional command in headless arg parser
`gsd headless new-milestone --auto --verbose` now works — flags are
parsed regardless of position relative to the command word.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 15:49:16 -06:00
Iouri Goussev
b6e105b058 perf(test): compile unit tests with esbuild, reclassify integration tests, fix node_modules symlink (#2809)
* fix(test): wire src/resources/extensions/shared/tests/ into test:unit runner

The test:unit glob excluded src/resources/extensions/shared/tests/ entirely,
leaving format-utils.test.ts (and any future tests there) silently unfired.

- Add shared/tests/*.test.ts to the test:unit glob in package.json
- Export newestSrcMtime from ensure-workspace-builds.cjs (require.main guard
  prevents side-effects on require) so the staleness logic can be tested
- Add src/tests/ensure-workspace-builds.test.ts covering newestSrcMtime:
  non-existent dir, no .ts files, single file, max of multiple, recursion,
  node_modules skip

Closes #2808

* perf(test): compile unit tests with esbuild and fix dist-test/node_modules

Replace per-file --experimental-strip-types with a single esbuild compilation
step (scripts/compile-tests.mjs) that compiles all src/ TypeScript to dist-test/
in ~3s, then runs the pre-compiled JS. Eliminates ~1.7s Node startup overhead
per test file.

- scripts/compile-tests.mjs: esbuild compilation, asset copy, .ts→.js rewrite,
  stale file cleanup; creates dist-test/node_modules symlink so resource-loader.ts
  resolves gsdNodeModules to a real path (fixes node-modules-symlink test failure)
- scripts/dist-test-resolve.mjs: ESM loader hook for @gsd/* bare specifiers and
  .ts→.js fallback rewriting at runtime
- .gitignore: exclude dist-test/ from version control
- package.json: add test:compile script; update test:unit to compile-then-run;
  update test:integration globs to cover new integration/ subdirectories
- worker-registry.ts: unref() cleanup timer so it does not keep the Node process
  alive after tests complete

Closes #2858

* fix(test): update relative imports in tests/integration/ after directory move

When tests were moved from tests/ to tests/integration/ in the previous
commit, relative imports weren't updated. ../foo now resolves one level
too shallow.

Fix all 117 import paths across 43 test files:
- ../foo → ../../foo (source files at gsd/ level)
- ../../get-secrets-from-user.ts → ../../../ (at extensions/ level)
- ../../subagent/worker-registry.ts → ../../../ (at extensions/ level)
- ./marketplace-test-fixtures.js → ../marketplace-test-fixtures.ts
- ./test-helpers.ts → ../test-helpers.ts

typecheck:extensions now passes with zero errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(integration): set 10-minute timeout for integration test runner

build job takes ~7min on main. Without a global timeout, hanging tests
block the suite indefinitely. --test-timeout=600000 caps each test at 10min.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Revert "test(integration): set 10-minute timeout for integration test runner"

This reverts commit be77ead77d369ad8569292ae6b69ba56435f5433.

* fix(test): correct formatDuration(0) edge case and docker test root path

- formatDuration(0) now returns '0s' instead of '0ms' by guarding the
  sub-second branch with ms > 0
- docker-template.test.ts root path goes ../../.. from dist-test/src/tests/
  to reach project root instead of landing in dist-test/
- replace require() calls in skill-health.ts and visualizer-overlay.ts
  with proper ES module imports

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(test): correct relative import paths in integration tests

All affected tests were one directory level off — importing from ../web/
and ../resources/ when the correct paths are ../../web/ and ../../resources/.
Tests live at src/tests/integration/, not src/tests/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(test): add esbuild to root devDeps and wire dist-test-resolve hook

P1: esbuild was only in web/package.json — compile-tests.mjs requires it
at the root node_modules path, so CI failed on clean installs.

P2: dist-test-resolve.mjs existed but was never loaded; @gsd/* imports in
compiled tests resolved to installed workspace packages instead of freshly
compiled dist-test output. Add --import to test:unit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(deps): align esbuild version with lock file (0.25.12)

^0.27.4 didn't satisfy the existing lock file entry. Use the version
already present so npm ci passes without regenerating the lock file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(test): correct all relative import depths in src/tests/integration/

Tests in src/tests/integration/ need 3 levels up (../../..) to reach
project-root dirs (web/, packages/) and 2 levels up (../..) to reach
src-level dirs (src/web/, src/cli-web-branch.ts).

Fixes:
- ../../web/lib/ → ../../../web/lib/   (Next.js app, not src/web/)
- ../../web/app/ → ../../../web/app/
- ../../packages/ → ../../../packages/
- ../cli-web-branch.ts → ../../cli-web-branch.ts
- ../web-mode.ts → ../../web-mode.ts
- ../resources/extensions/ → ../../resources/extensions/
- ci_monitor ROOT path: 2 levels up → 3 levels up
- web-responsive WEB_ROOT: 2 levels up → 3 levels up

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(test): use dot reporter for test:unit to reduce noise

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(test): switch test:unit reporter to tap

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(test): compact test reporter — silent on pass, failures + summary only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(test): include shared/tests in test:coverage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(test): correct path depths in tests moved to integration/

Tests moved from tests/ to tests/integration/ need one extra ../
to reach the same source files. Also fix web component paths — those
files live at web/ not src/web/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(test): fix web component paths in web-session-parity-contract

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(test): use process.cwd() for project root in docker-template test

Resolving relative to __dirname breaks under test:coverage which runs
source files directly from src/tests/ — needs ../.. not ../../..
(the extra level only exists in the compiled dist-test/ output).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: retrigger CI

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 14:51:49 -06:00
mastertyko
cedf6a558d fix(web): improve light theme terminal contrast (#2819)
Unify the Power Mode xterm light palette behind a shared helper and replace low-contrast ANSI white/yellow entries with contrast-safe values.

Add a regression test that guards both the readable light-theme palette and the shared helper wiring so the duplicated terminal palettes do not drift again.

Closes #2810
2026-03-27 14:47:44 -06:00
mastertyko
24c4e393a7 fix(cli): let gsd update bypass version mismatch gate (#2845)
* test(integration): suppress npm pack buffer overflows

* fix(cli): let gsd update bypass version mismatch gate
2026-03-27 14:30:13 -06:00
TÂCHES
666731f56d feat: colorized headless verbose output with thinking, phases, cost, and durations (#2886)
* feat: colorized headless verbose output with thinking, phases, cost, and durations

Overhaul --verbose text output to make agent activity observable:

1. ANSI color for all output categories (tool, agent, gsd, phase, cost,
   thinking, error) with NO_COLOR and non-TTY support
2. LLM thinking deltas condensed to ~120 char previews between tool calls
3. Phase tracking from setStatus events with statusKey parsing (suppresses
   empty [status] lines)
4. Cumulative cost shown on agent_end from tracked cost_update events
5. Tool call durations (start/end timestamp tracking)
6. Tool arg summarizer (file paths, bash commands, grep patterns) for
   context-rich [tool] lines

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: update headless progress tests for new ProgressContext API and color features

- Update formatProgress tests for ProgressContext signature (verbose, toolDuration, lastCost, isError)
- Add summarizeToolArgs tests (file paths, bash commands, grep patterns, truncation)
- Add formatThinkingLine tests (truncation, whitespace collapsing)
- Add formatCostLine tests
- Add phase tracking tests (setStatus with statusKey)
- Add agent_end cost display tests
- 28 tests, all passing

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:29:20 -06:00
TÂCHES
1d5590c19a feat: headless text mode observability + skip UAT pause (#2867)
* feat: headless text mode shows tool calls + skip UAT pause in headless

Text mode observability:
- Tool calls always visible with summarized args (path, command, pattern)
- Tool errors surfaced even in non-verbose mode
- Cost updates shown periodically
- Empty [status] lines suppressed (setStatus/setWidget are TUI-only)
- Empty notify messages suppressed

UAT pause skip:
- Set GSD_HEADLESS=1 env var when spawning RPC child process
- auto-dispatch checks GSD_HEADLESS and skips pauseAfterDispatch for UAT
- Headless runs no longer stall waiting for human UAT verification

* test: add formatProgress unit tests for headless text mode

16 tests covering tool call display, arg summarization, cost formatting,
empty status suppression, and notify filtering.

* ci: retrigger
2026-03-27 12:13:17 -06:00
Jean-Dominique Stepek
67f78a7314 fix: detect monorepo roots in project discovery to prevent workspace fragmentation (#2849)
When devRoot pointed at a monorepo, discoverProjects scanned one level
deep and listed each workspace/package as a separate project. Now it
checks for monorepo markers (pnpm-workspace.yaml, lerna.json, turbo.json,
nx.json, rush.json, package.json workspaces) before scanning children.
If the root is a monorepo, it returns it as a single project entry.

- Add detectMonorepo() to bridge-service with support for 6 monorepo formats
- Add isMonorepo signal to ProjectDetectionSignals
- Update discoverProjects to short-circuit when root is a monorepo
- Show 'Monorepo' tag in project list UI
- Add 24 tests covering all monorepo detection scenarios
2026-03-27 09:55:00 -06:00
mastertyko
2bc92afa6b fix(bg-shell): recover from deleted cwd in timers (#2850)
* test(integration): suppress npm pack buffer overflows

* fix(bg-shell): recover from deleted cwd in timers
2026-03-27 09:54:31 -06:00
mastertyko
c5907c3677 fix(interactive): fully remove providers from /providers (#2852)
* test(integration): suppress npm pack buffer overflows

* fix(interactive): fully remove providers from /providers
2026-03-27 09:53:35 -06:00
TÂCHES
a91b8bec34 feat: Headless Integration Hardening & Release (M002) (#2811)
* feat: Migrated headless orchestrator to use execution_complete events,…

- "src/headless.ts"
- "src/headless-ui.ts"
- "src/tests/headless-v2-migration.test.ts"

GSD-Task: S06/T02

* test: Wired pi-coding-agent to re-export JSONL utils from @gsd/rpc-clie…

- "packages/pi-coding-agent/src/modes/rpc/jsonl.ts"
- "packages/pi-coding-agent/package.json"
- "packages/rpc-client/src/index.ts"
- "packages/rpc-client/src/jsonl.ts"
- "packages/rpc-client/src/rpc-client.ts"
- "packages/rpc-client/src/rpc-types.ts"
- "packages/rpc-client/src/rpc-client.test.ts"
- "packages/rpc-client/package.json"

GSD-Task: S06/T03

* feat: Wire --resume flag to resolve session IDs via prefix matching and…

- "src/headless.ts"
- "dist/headless.js"

GSD-Task: S01/T01

* test: Added 5 e2e integration tests proving headless JSON batch, SIGINT…

- "src/tests/integration/e2e-headless.test.ts"

GSD-Task: S01/T02

* test: Updated @gsd/rpc-client and @gsd/mcp-server to 2.52.0 with publis…

- "packages/rpc-client/package.json"
- "packages/mcp-server/package.json"
- "packages/rpc-client/.npmignore"
- "packages/mcp-server/.npmignore"

GSD-Task: S02/T01

* chore: auto-commit after complete-milestone

GSD-Unit: M002-gzq23a

* fix: revert jsonl.ts to inline implementation — @gsd-build/rpc-client not available at source-level test time in CI

The re-export from @gsd-build/rpc-client fails in CI because tests run against
TypeScript source (--experimental-strip-types) before any build step. The npm
dependency resolves to node_modules/ which requires dist/ to exist. Reverting
to the original inline implementation eliminates the cross-package dependency
for source-level imports.
2026-03-26 23:33:22 -06:00
Lex Christopherson
b2f02a23ab test: Added 5 e2e integration tests proving headless JSON batch, SIGINT…
- "src/tests/integration/e2e-headless.test.ts"

GSD-Task: S01/T02
2026-03-26 21:25:16 -06:00
Lex Christopherson
3be38e3794 feat: Migrated headless orchestrator to use execution_complete events,…
- "src/headless.ts"
- "src/headless-ui.ts"
- "src/tests/headless-v2-migration.test.ts"

GSD-Task: S06/T02
2026-03-26 20:17:05 -06:00
TÂCHES
d5b318a222 fix: use localStorage for auth token to enable multi-tab usage (#2785)
* fix: use localStorage for auth token to enable multi-tab usage

sessionStorage is tab-scoped, so manually opened second tabs cannot
access the auth token delivered via URL fragment to the first tab.
localStorage is shared across all tabs on the same origin, and since
each GSD instance binds to a unique random port the origin already
scopes the token to that instance.

Also adds a `storage` event listener so already-open tabs pick up
token changes immediately.

Closes #2714

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: update web-auth-token test for localStorage migration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:06:50 -06:00