@gajae-code/coding-agent 0.5.1 → 0.5.3

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.
Files changed (165) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +1 -1
  3. package/dist/types/async/job-manager.d.ts +6 -0
  4. package/dist/types/cli/setup-cli.d.ts +8 -1
  5. package/dist/types/commands/setup.d.ts +7 -0
  6. package/dist/types/config/file-lock.d.ts +24 -2
  7. package/dist/types/config/model-registry.d.ts +4 -0
  8. package/dist/types/config/models-config-schema.d.ts +5 -0
  9. package/dist/types/config/settings-schema.d.ts +62 -0
  10. package/dist/types/dap/client.d.ts +2 -1
  11. package/dist/types/edit/read-file.d.ts +6 -0
  12. package/dist/types/eval/js/context-manager.d.ts +3 -0
  13. package/dist/types/eval/js/executor.d.ts +1 -0
  14. package/dist/types/exec/bash-executor.d.ts +2 -0
  15. package/dist/types/gjc-runtime/state-writer.d.ts +64 -2
  16. package/dist/types/gjc-runtime/tmux-sessions.d.ts +7 -1
  17. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +10 -0
  18. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +29 -0
  19. package/dist/types/lsp/types.d.ts +2 -0
  20. package/dist/types/modes/bridge/bridge-mode.d.ts +1 -0
  21. package/dist/types/modes/components/model-selector.d.ts +2 -0
  22. package/dist/types/modes/components/oauth-selector.d.ts +1 -0
  23. package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
  24. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +1 -0
  25. package/dist/types/modes/components/tool-execution.d.ts +1 -0
  26. package/dist/types/modes/interactive-mode.d.ts +1 -1
  27. package/dist/types/modes/rpc/rpc-mode.d.ts +56 -1
  28. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
  29. package/dist/types/modes/theme/defaults/index.d.ts +302 -0
  30. package/dist/types/modes/theme/theme.d.ts +1 -0
  31. package/dist/types/modes/types.d.ts +1 -1
  32. package/dist/types/runtime/process-lifecycle.d.ts +108 -0
  33. package/dist/types/runtime-mcp/transports/stdio.d.ts +1 -0
  34. package/dist/types/runtime-mcp/types.d.ts +2 -0
  35. package/dist/types/session/agent-session.d.ts +17 -1
  36. package/dist/types/session/artifacts.d.ts +4 -1
  37. package/dist/types/session/history-storage.d.ts +2 -2
  38. package/dist/types/session/session-manager.d.ts +10 -1
  39. package/dist/types/session/streaming-output.d.ts +5 -0
  40. package/dist/types/setup/credential-import.d.ts +79 -0
  41. package/dist/types/slash-commands/helpers/fast-status-report.d.ts +76 -0
  42. package/dist/types/task/executor.d.ts +1 -0
  43. package/dist/types/task/render.d.ts +1 -1
  44. package/dist/types/tools/bash.d.ts +1 -0
  45. package/dist/types/tools/browser/tab-supervisor.d.ts +9 -0
  46. package/dist/types/tools/sqlite-reader.d.ts +2 -1
  47. package/dist/types/tools/subagent-render.d.ts +7 -1
  48. package/dist/types/tools/subagent.d.ts +21 -0
  49. package/dist/types/tools/ultragoal-ask-guard.d.ts +5 -0
  50. package/dist/types/web/search/index.d.ts +4 -4
  51. package/dist/types/web/search/provider.d.ts +16 -20
  52. package/dist/types/web/search/providers/base.d.ts +2 -1
  53. package/dist/types/web/search/providers/openai-compatible.d.ts +9 -0
  54. package/dist/types/web/search/types.d.ts +14 -2
  55. package/package.json +7 -7
  56. package/scripts/build-binary.ts +7 -0
  57. package/src/async/job-manager.ts +153 -39
  58. package/src/cli/args.ts +2 -0
  59. package/src/cli/fast-help.ts +2 -0
  60. package/src/cli/setup-cli.ts +138 -3
  61. package/src/commands/setup.ts +5 -1
  62. package/src/commands/ultragoal.ts +3 -1
  63. package/src/config/file-lock-gc.ts +14 -2
  64. package/src/config/file-lock.ts +63 -13
  65. package/src/config/model-profile-activation.ts +15 -3
  66. package/src/config/model-profiles.ts +15 -15
  67. package/src/config/model-registry.ts +21 -1
  68. package/src/config/models-config-schema.ts +1 -0
  69. package/src/config/settings-schema.ts +62 -0
  70. package/src/dap/client.ts +105 -64
  71. package/src/dap/session.ts +44 -7
  72. package/src/defaults/gjc/skills/ultragoal/SKILL.md +30 -8
  73. package/src/edit/read-file.ts +19 -1
  74. package/src/eval/js/context-manager.ts +228 -65
  75. package/src/eval/js/executor.ts +2 -0
  76. package/src/eval/js/index.ts +1 -0
  77. package/src/eval/js/worker-core.ts +10 -6
  78. package/src/eval/py/executor.ts +68 -19
  79. package/src/eval/py/kernel.ts +46 -22
  80. package/src/eval/py/runner.py +68 -14
  81. package/src/exec/bash-executor.ts +49 -13
  82. package/src/gjc-runtime/deep-interview-recorder.ts +40 -0
  83. package/src/gjc-runtime/launch-tmux.ts +3 -4
  84. package/src/gjc-runtime/ralplan-runtime.ts +174 -12
  85. package/src/gjc-runtime/state-runtime.ts +2 -1
  86. package/src/gjc-runtime/state-writer.ts +254 -7
  87. package/src/gjc-runtime/tmux-gc.ts +88 -38
  88. package/src/gjc-runtime/tmux-sessions.ts +44 -6
  89. package/src/gjc-runtime/ultragoal-guard.ts +155 -0
  90. package/src/gjc-runtime/ultragoal-runtime.ts +1227 -31
  91. package/src/gjc-runtime/workflow-manifest.generated.json +44 -0
  92. package/src/gjc-runtime/workflow-manifest.ts +12 -0
  93. package/src/harness-control-plane/owner.ts +3 -2
  94. package/src/harness-control-plane/rpc-adapter.ts +1 -1
  95. package/src/hooks/skill-state.ts +121 -2
  96. package/src/internal-urls/artifact-protocol.ts +10 -1
  97. package/src/internal-urls/docs-index.generated.ts +14 -10
  98. package/src/lsp/client.ts +64 -26
  99. package/src/lsp/defaults.json +1 -0
  100. package/src/lsp/index.ts +2 -1
  101. package/src/lsp/lspmux.ts +33 -9
  102. package/src/lsp/types.ts +2 -0
  103. package/src/main.ts +14 -4
  104. package/src/modes/acp/acp-agent.ts +4 -2
  105. package/src/modes/bridge/bridge-mode.ts +23 -1
  106. package/src/modes/components/assistant-message.ts +10 -2
  107. package/src/modes/components/bash-execution.ts +5 -1
  108. package/src/modes/components/eval-execution.ts +5 -1
  109. package/src/modes/components/history-search.ts +5 -2
  110. package/src/modes/components/model-selector.ts +60 -2
  111. package/src/modes/components/oauth-selector.ts +5 -0
  112. package/src/modes/components/provider-onboarding-selector.ts +6 -1
  113. package/src/modes/components/runtime-mcp-add-wizard.ts +58 -7
  114. package/src/modes/components/skill-message.ts +24 -16
  115. package/src/modes/components/tool-execution.ts +6 -0
  116. package/src/modes/controllers/extension-ui-controller.ts +33 -6
  117. package/src/modes/controllers/input-controller.ts +5 -0
  118. package/src/modes/controllers/selector-controller.ts +86 -2
  119. package/src/modes/interactive-mode.ts +11 -1
  120. package/src/modes/rpc/rpc-mode.ts +132 -18
  121. package/src/modes/shared/agent-wire/command-dispatch.ts +5 -2
  122. package/src/modes/shared/agent-wire/host-tool-bridge.ts +3 -0
  123. package/src/modes/shared/agent-wire/unattended-session.ts +16 -1
  124. package/src/modes/theme/defaults/claude-code.json +100 -0
  125. package/src/modes/theme/defaults/codex.json +100 -0
  126. package/src/modes/theme/defaults/index.ts +6 -0
  127. package/src/modes/theme/defaults/opencode.json +102 -0
  128. package/src/modes/theme/theme.ts +2 -2
  129. package/src/modes/types.ts +1 -1
  130. package/src/modes/utils/ui-helpers.ts +5 -2
  131. package/src/prompts/agents/executor.md +5 -2
  132. package/src/runtime/process-lifecycle.ts +400 -0
  133. package/src/runtime-mcp/manager.ts +164 -50
  134. package/src/runtime-mcp/transports/http.ts +12 -11
  135. package/src/runtime-mcp/transports/stdio.ts +64 -38
  136. package/src/runtime-mcp/types.ts +3 -0
  137. package/src/sdk.ts +39 -1
  138. package/src/session/agent-session.ts +190 -33
  139. package/src/session/artifacts.ts +17 -2
  140. package/src/session/blob-store.ts +36 -2
  141. package/src/session/history-storage.ts +32 -11
  142. package/src/session/session-manager.ts +99 -31
  143. package/src/session/streaming-output.ts +54 -3
  144. package/src/setup/credential-import.ts +429 -0
  145. package/src/skill-state/deep-interview-mutation-guard.ts +2 -1
  146. package/src/slash-commands/builtin-registry.ts +30 -3
  147. package/src/slash-commands/helpers/fast-status-report.ts +111 -0
  148. package/src/task/executor.ts +7 -1
  149. package/src/task/render.ts +18 -7
  150. package/src/tools/archive-reader.ts +10 -1
  151. package/src/tools/ask.ts +4 -2
  152. package/src/tools/bash.ts +11 -4
  153. package/src/tools/browser/tab-supervisor.ts +22 -0
  154. package/src/tools/browser.ts +38 -4
  155. package/src/tools/cron.ts +1 -1
  156. package/src/tools/read.ts +11 -12
  157. package/src/tools/sqlite-reader.ts +19 -5
  158. package/src/tools/subagent-render.ts +119 -29
  159. package/src/tools/subagent.ts +147 -7
  160. package/src/tools/ultragoal-ask-guard.ts +39 -0
  161. package/src/web/search/index.ts +25 -25
  162. package/src/web/search/provider.ts +178 -87
  163. package/src/web/search/providers/base.ts +2 -1
  164. package/src/web/search/providers/openai-compatible.ts +151 -0
  165. package/src/web/search/types.ts +47 -22
package/CHANGELOG.md CHANGED
@@ -2,6 +2,37 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.5.3] - 2026-06-16
6
+
7
+ ### Added
8
+
9
+ - Added `GJC_CREDENTIAL_RANKING_MODE` env var (`balanced` (default) | `earliest-reset`), wired through `discoverAuthStorage` into `AuthStorage.credentialRankingMode`. `earliest-reset` selects multi-account OAuth credentials earliest-expiry-first so soon-to-reset tumbling-window quota (e.g. Claude 5h/7d) is drained before it is lost at reset; unset/unknown leaves the default `balanced` behavior unchanged.
10
+ - The `/model` selector and `/fast` status now show a per-model fast-mode indicator (`⚡`) resolved with the provider-aware predicate, including subagent (role) models evaluated against the effective `task.serviceTier` (falling back to the session tier when `inherit`), so it is visible at a glance which models will run with Anthropic `speed:"fast"` / OpenAI `service_tier=priority`. Display-only: no `serviceTier`/`modelRoles`/`agentModelOverrides` writes (#691).
11
+ - Added an opt-in `GJC_BRIDGE_ENDPOINTS` env var (`all`, or a comma list of matrix keys: `events`, `commands`, `control`, `uiResponses`, `hostToolResults`, `hostUriResults`) to enable bridge-mode session-control endpoints, which were previously permanently fail-closed (`403 endpoint_disabled`) with no supported way to turn them on. Unset stays fail-closed and backward compatible (#663).
12
+
13
+ ### Fixed
14
+
15
+ - Auto-compaction no longer silently requires OpenAI when the active route is a custom Anthropic-capable provider. The compaction model-candidate selection already prefers the active session model, but its last-resort "largest-context model" fallback scanned the entire bundled catalog across all providers, so a stray OpenAI credential (e.g. an out-of-credit key left in the environment) could be picked when the active provider's compaction credential was unusable — turning OpenAI into an implicit hard dependency. The implicit fallback is now scoped to the active model's provider; cross-provider compaction still works but only when explicitly configured via `modelRoles`. When the active provider cannot compact and no role is configured, compaction now fails with the existing clear, provider-specific credential error instead of reaching for OpenAI (#697).
16
+ - Long-running-session freeze/leak remediation across the TUI, agent, and tool runtime: the TUI render loop, component-dispose lifecycle, and markdown highlighting are hardened against huge frames and reuse leaks (#716); agent context append, an emergency-compaction floor that cannot be disabled, token accounting, and session resource teardown (own-session browser tabs, LSP clients, Cursor conversation cache) are bounded (#717); oversized tool inputs/outputs are capped (8 MiB edit/read guard ahead of the notebook fast-path, 1000-row SQLite raw-query cap, 16 MiB artifact / 256 MiB archive read caps, budget-bounded browser return serialization) (#721); native synchronous entrypoints add defense-in-depth caps for tokenization, highlighting, and fuzzy edit matching (#744); and the session blob store is LRU-bounded (64 MiB / 4096 entries) with bounded-concurrency blob resume (limit 8) (#719).
17
+ - Process & resource lifecycle hardening so child processes and external resources are reliably reaped on disconnect, abort, and shutdown, built on a new owned-process foundation — process-group ownership with escalating SIGTERM→SIGKILL tree termination, idempotent dispose, and a postmortem reap hook (F1). Owned-process handles are terminalized on clean drain so a retained handle can never signal a recycled PID/process group (B1); the native blocking-task boundary, PTY lifecycle, and pi-shell timeout/abort reaping are hardened (U1–U3); the Python eval kernel (U4) and JS eval worker/VM (U5) coalesce concurrent first cells, settle queued/pending runs on teardown, and return worker/kernel counts to baseline; bash shell sessions are owner-scoped with one-shot async/monitor jobs and a hard artifact byte cap (U6); DAP adapters and LSP servers are spawned as owned processes and killed on terminate/timeout/reload (U7); MCP stdio/HTTP/SSE transports and the manager close idempotently with stale-publication identity guards (U8); the async job-manager bounds dispose, the delivery queue/retry (with dead-lettering), and terminal purge (U9); and tmux GC never prunes live/attached sessions and reaps only durably-owned orphans (U10).
18
+
19
+ ## [0.5.2] - 2026-06-15
20
+
21
+ ### Fixed
22
+
23
+ - Prevented `gjc --tmux` partial-launch diagnostics from throwing when stderr is already closed during shutdown.
24
+ - Fixed v0.5.1-style macOS/Linux standalone binaries crashing before the first model request with `Cannot find module '@gajae-code/natives' from '/$bunfs/root/gjc-*'` when pre-prompt context maintenance invokes the native tokenizer.
25
+ - Mapped the retired `codex-standard` model profile name to `codex-medium` during profile activation, **as a fallback only** so a user-defined profile literally named `codex-standard` is never shadowed, letting stale `modelProfile.default: codex-standard` configs reach activation instead of blocking startup after the rebuilt profile catalog.
26
+ - Fixed interactive goal-mode auto-continuation looping `Error: Agent is already processing…` (`AgentBusyError`) while the session is busy. A wedged/orphaned subagent turn — or an in-progress compaction — can leave the session non-idle while the interactive loop is back at `getUserInput()`; the 800 ms continuation timer then fired `prompt()`, threw `AgentBusyError`, surfaced it via `showError`, and re-armed — spamming the error roughly every 800 ms. The continuation now skips and re-arms while `isStreaming`/`isCompacting`, firing only once the session returns to idle.
27
+ - Fixed the built-in `minimax-eco`/`minimax-medium`/`minimax-pro` model profiles 400ing on activation because every role pinned the non-existent `minimax-code/minimax-v3`. All three profiles now pin `minimax-code/minimax-m3`, the canonical `minimax-code` default already present in the bundled models catalog (#656).
28
+ - Fixed the native Stop hook letting a deep-interview run terminalize through the ordinary stop path without crystallizing its distilled interview state. A deep-interview mode-state that would release the Stop block (e.g. `active:true` with a `complete`/`completed`/`inactive` phase) is now held until it has actually persisted a final spec — a `spec_path` that still resolves to a real `.gjc/specs/` artifact — and the public-safe diagnostic points the agent at `gjc deep-interview --write --stage final` (optionally `--handoff ralplan`). The guard is scoped to deep-interview only: explicit abort/cancel phases (`failed`/`cancelled`/`canceled`) and the `active:false` demotion/clear outcome remain legitimate terminals, and no other workflow's stop behavior changes (#674).
29
+
30
+ ### Added
31
+
32
+ - Added three bundled dark TUI migration themes — `claude-code`, `codex`, and `opencode` — whose palettes mirror the Claude Code, OpenAI Codex CLI, and opencode TUIs for easy eye-migration. They join the crustacean defaults (`red-claw` dark, `blue-crab` light) as selectable built-ins via Settings or `/theme`; defaults are unchanged and the new themes keep GJC's default symbol identity. A built-in inventory test now validates every bundled theme against the required `THEME_COLOR_KEYS` token set, name/key equality, var resolution, dark classification, and brand-vs-semantic token separation.
33
+ - Documented and regression-guarded the `gjc --tmux` scroll/mouse profile so WSL/Linux launches are not left guessing about mouse-wheel scrolling. The GJC-managed tmux session already applies `mouse on` (plus `set-clipboard on` and a readable copy-mode `mode-style`) scoped to the GJC session only, on macOS/Linux/WSL alike (only native `win32` skips the tmux launch); a new launch-path test asserts a WSL/Linux `--tmux` launch issues session-scoped `set-option ... mouse on` (never global `set -g`) and that `GJC_MOUSE=off` opts out without dropping the ownership tags. `docs/environment-variables.md` now documents the `--tmux` startup env vars (`GJC_LAUNCH_POLICY`, `GJC_TMUX_SESSION`, `GJC_TMUX_COMMAND`, `GJC_TMUX_PROFILE`, `GJC_MOUSE`) and the WSL/Windows Terminal scroll behavior (tmux copy-mode wheel scroll vs. native scrollback, copy-mode keyboard fallback, and that GJC never modifies tmux sessions you started yourself), and `gjc --help` surfaces `GJC_TMUX_PROFILE`/`GJC_MOUSE` (#650).
34
+ - Added a subagent-scoped `task.serviceTier` setting (default `"inherit"`) so the service tier / fast mode applied to task-tool subagents can be controlled independently of the main session. `"inherit"` keeps the current behavior (the main session tier is copied into each subagent's isolated settings snapshot), while any explicit value (`none`, `priority`, `openai-only`, `claude-only`, …) overrides only the subagent sessions, which already read `serviceTier` from their own settings. Implemented in `createSubagentSettings` with a focused test covering inherit and explicit-override behavior (#664).
35
+
5
36
  ## [0.5.1] - 2026-06-14
6
37
 
7
38
  ### Added
package/README.md CHANGED
@@ -34,4 +34,4 @@ Switching backends mid-session is honoured on the next system-prompt rebuild and
34
34
 
35
35
  ## Red-claw TUI theme
36
36
 
37
- The interactive TUI defaults to the bundled `red-claw` crustacean theme for dark terminals and the bundled `blue-crab` theme for light-appearance terminals, with matching welcome/icon assets. Explicit user theme settings still win; set `theme.dark: red-claw` and `theme.light: blue-crab` in `~/.gjc/agent/config.yml` to pin them.
37
+ The interactive TUI defaults to the bundled `red-claw` crustacean theme for dark terminals and the bundled `blue-crab` theme for light-appearance terminals, with matching welcome/icon assets. Three additional bundled migration themes — `claude-code`, `codex`, and `opencode` — mirror the look of those tools for easy eye-migration and are selectable from Settings or `/theme`. Explicit user theme settings still win; set `theme.dark: red-claw` and `theme.light: blue-crab` in `~/.gjc/agent/config.yml` to pin them.
@@ -109,11 +109,16 @@ export interface AsyncJobManagerOptions {
109
109
  maxRunningJobs?: number;
110
110
  retentionMs?: number;
111
111
  }
112
+ export interface AsyncJobDisposeDiagnostics {
113
+ stuckJobIds: string[];
114
+ deliveriesDrained: boolean;
115
+ }
112
116
  export interface AsyncJobDeliveryState {
113
117
  queued: number;
114
118
  delivering: boolean;
115
119
  nextRetryAt?: number;
116
120
  pendingJobIds: string[];
121
+ deadLettered: number;
117
122
  }
118
123
  export interface AsyncJobLifecycleCleanup {
119
124
  onCancel?: (job: AsyncJob) => void;
@@ -308,6 +313,7 @@ export declare class AsyncJobManager {
308
313
  * (used by `dispose()` to nuke the manager's state).
309
314
  */
310
315
  cancelAll(filter?: AsyncJobFilter): void;
316
+ getLastDisposeDiagnostics(): AsyncJobDisposeDiagnostics;
311
317
  waitForAll(): Promise<void>;
312
318
  drainDeliveries(options?: {
313
319
  timeoutMs?: number;
@@ -1,4 +1,9 @@
1
- export type SetupComponent = "defaults" | "hermes" | "hooks" | "provider" | "python" | "stt";
1
+ /**
2
+ * Setup CLI command handler.
3
+ *
4
+ * Handles `gjc setup [component]` to install the normal defaults or optional feature dependencies.
5
+ */
6
+ export type SetupComponent = "credentials" | "defaults" | "hermes" | "hooks" | "provider" | "python" | "stt";
2
7
  export interface SetupCommandArgs {
3
8
  component: SetupComponent;
4
9
  flags: {
@@ -27,6 +32,8 @@ export interface SetupCommandArgs {
27
32
  gjcCommand?: string;
28
33
  target?: string;
29
34
  profileDir?: string;
35
+ yes?: boolean;
36
+ dryRun?: boolean;
30
37
  };
31
38
  }
32
39
  /**
@@ -93,6 +93,13 @@ export default class Setup extends Command {
93
93
  "models-path": import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
94
94
  description: string;
95
95
  };
96
+ yes: import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
97
+ char: string;
98
+ description: string;
99
+ };
100
+ "dry-run": import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
101
+ description: string;
102
+ };
96
103
  };
97
104
  run(): Promise<void>;
98
105
  }
@@ -8,6 +8,28 @@ export declare function readFileLockInfoForGc(lockDir: string): Promise<{
8
8
  pid: number;
9
9
  timestamp: number;
10
10
  } | null>;
11
- /** @internal */
12
- export declare function removeFileLockDirForGc(lockDir: string): Promise<void>;
11
+ /** Owner identity stamped into a `<file>.lock/info` record. */
12
+ export interface FileLockOwnerToken {
13
+ pid: number;
14
+ timestamp: number;
15
+ }
16
+ /** Outcome of a guarded GC removal attempt (`removeFileLockDirForGc`). */
17
+ export type FileLockGcRemoval = "removed" | "owner_changed" | "missing";
18
+ /**
19
+ * @internal
20
+ * Fail-closed removal of a dead lock dir for GC. Re-reads the on-disk owner
21
+ * token as close to the unlink as possible and only deletes the dir when it
22
+ * STILL holds the exact `{pid, timestamp}` identity the caller observed dead.
23
+ *
24
+ * Closes the prune-time TOCTOU window (#606): between GC's dead re-read/probe
25
+ * and the unlink, a live process can reclaim a stale lock at the same path
26
+ * (`acquireLock` rms the stale dir, then re-`mkdir`s and rewrites `info` with a
27
+ * fresh pid+timestamp). Deleting by path alone would reap that LIVE lock. Any
28
+ * mismatch (`owner_changed`) or absent/unreadable info (`missing` — e.g. a
29
+ * fresh acquirer between `mkdir` and `writeLockInfo`) refuses the delete and
30
+ * leaves the dir intact. POSIX has no atomic compare-and-delete for a
31
+ * directory, so the residual read->unlink window cannot be fully eliminated,
32
+ * but the reclaim-after-stale scenario the issue describes is now guarded.
33
+ */
34
+ export declare function removeFileLockDirForGc(lockDir: string, expected: FileLockOwnerToken): Promise<FileLockGcRemoval>;
13
35
  export declare function withFileLock<T>(filePath: string, fn: () => Promise<T>, options?: FileLockOptions): Promise<T>;
@@ -2,6 +2,7 @@ import { type Api, type AssistantMessageEventStream, type CacheRetention, type C
2
2
  import type { OAuthCredentials, OAuthLoginCallbacks } from "@gajae-code/ai/utils/oauth/types";
3
3
  import { type ThemeColor } from "../modes/theme/theme";
4
4
  import type { AuthStorage } from "../session/auth-storage";
5
+ import type { ActiveSearchModelContext, WebSearchMode } from "../web/search/types";
5
6
  import { type ConfigError, ConfigFile } from "./config-file";
6
7
  import { type CanonicalModelIndex, type CanonicalModelRecord, type CanonicalModelVariant, type ModelEquivalenceConfig } from "./model-equivalence";
7
8
  import { type ModelProfileDefinition } from "./model-profiles";
@@ -87,6 +88,7 @@ export declare const ModelsConfigFile: ConfigFile<{
87
88
  supportsStrictMode?: boolean | undefined;
88
89
  toolStrictMode?: "all_strict" | "none" | undefined;
89
90
  } | undefined;
91
+ webSearch?: "auto" | "off" | "on" | undefined;
90
92
  authHeader?: boolean | undefined;
91
93
  auth?: "apiKey" | "none" | "oauth" | undefined;
92
94
  discovery?: {
@@ -364,6 +366,8 @@ export declare class ModelRegistry {
364
366
  * Get the base URL associated with a provider, if any model defines one.
365
367
  */
366
368
  getProviderBaseUrl(provider: string): string | undefined;
369
+ getProviderWebSearchMode(provider: string): WebSearchMode | undefined;
370
+ getActiveSearchModelContext(model: Model<Api>): ActiveSearchModelContext;
367
371
  /**
368
372
  * Get API key for a model.
369
373
  */
@@ -333,6 +333,11 @@ export declare const ModelsConfigSchema: z.ZodObject<{
333
333
  none: "none";
334
334
  }>>;
335
335
  }, z.core.$strip>>;
336
+ webSearch: z.ZodOptional<z.ZodEnum<{
337
+ auto: "auto";
338
+ off: "off";
339
+ on: "on";
340
+ }>>;
336
341
  authHeader: z.ZodOptional<z.ZodBoolean>;
337
342
  auth: z.ZodOptional<z.ZodEnum<{
338
343
  apiKey: "apiKey";
@@ -86,6 +86,9 @@ interface EnumDef<T extends readonly string[]> {
86
86
  interface ArrayDef<T> {
87
87
  type: "array";
88
88
  default: T[];
89
+ items?: {
90
+ enum: readonly string[];
91
+ };
89
92
  ui?: UiBase;
90
93
  }
91
94
  interface RecordDef<T> {
@@ -956,6 +959,53 @@ export declare const SETTINGS_SCHEMA: {
956
959
  }];
957
960
  };
958
961
  };
962
+ readonly "task.serviceTier": {
963
+ readonly type: "enum";
964
+ readonly values: readonly ["inherit", "none", "auto", "default", "flex", "scale", "priority", "openai-only", "claude-only"];
965
+ readonly default: "inherit";
966
+ readonly ui: {
967
+ readonly tab: "tasks";
968
+ readonly label: "Subagent Service Tier";
969
+ readonly description: 'Service tier applied to task-tool subagents only. "inherit" copies the main session tier; any explicit value overrides it for subagents without touching the main session.';
970
+ readonly options: readonly [{
971
+ readonly value: "inherit";
972
+ readonly label: "Inherit";
973
+ readonly description: "Use the main session's service tier (default)";
974
+ }, {
975
+ readonly value: "none";
976
+ readonly label: "None";
977
+ readonly description: "Omit service_tier for subagents";
978
+ }, {
979
+ readonly value: "auto";
980
+ readonly label: "Auto";
981
+ readonly description: "Use provider default tier selection (OpenAI)";
982
+ }, {
983
+ readonly value: "default";
984
+ readonly label: "Default";
985
+ readonly description: "Standard priority processing (OpenAI)";
986
+ }, {
987
+ readonly value: "flex";
988
+ readonly label: "Flex";
989
+ readonly description: "Flexible capacity tier when available (OpenAI)";
990
+ }, {
991
+ readonly value: "scale";
992
+ readonly label: "Scale";
993
+ readonly description: "Scale Tier credits when available (OpenAI)";
994
+ }, {
995
+ readonly value: "priority";
996
+ readonly label: "Priority";
997
+ readonly description: "Priority on every supported provider (OpenAI `service_tier`, Anthropic fast mode)";
998
+ }, {
999
+ readonly value: "openai-only";
1000
+ readonly label: "Priority (OpenAI only)";
1001
+ readonly description: "Priority on OpenAI/OpenAI-Codex requests; ignored elsewhere";
1002
+ }, {
1003
+ readonly value: "claude-only";
1004
+ readonly label: "Priority (Claude only)";
1005
+ readonly description: "Anthropic fast mode on direct Claude requests; ignored elsewhere (incl. Bedrock/Vertex)";
1006
+ }];
1007
+ };
1008
+ };
959
1009
  readonly "retry.enabled": {
960
1010
  readonly type: "boolean";
961
1011
  readonly default: true;
@@ -2411,6 +2461,18 @@ export declare const SETTINGS_SCHEMA: {
2411
2461
  readonly description: "Enable the web_search tool for web searching";
2412
2462
  };
2413
2463
  };
2464
+ readonly "web_search.fallback": {
2465
+ readonly type: "array";
2466
+ readonly default: string[];
2467
+ readonly items: {
2468
+ readonly enum: readonly ["duckduckgo", "exa", "brave", "jina", "kimi", "zai", "anthropic", "perplexity", "gemini", "codex", "tavily", "parallel", "kagi", "synthetic", "searxng"];
2469
+ };
2470
+ readonly ui: {
2471
+ readonly tab: "tools";
2472
+ readonly label: "Web Search Fallback";
2473
+ readonly description: "Ordered fallback web search providers after the active model native provider";
2474
+ };
2475
+ };
2414
2476
  readonly "browser.enabled": {
2415
2477
  readonly type: "boolean";
2416
2478
  readonly default: true;
@@ -1,3 +1,4 @@
1
+ import { type OwnedProcess } from "../runtime/process-lifecycle";
1
2
  import type { DapCapabilities, DapClientState, DapEventMessage, DapInitializeArguments, DapRequestMessage, DapResolvedAdapter } from "./types";
2
3
  interface DapSpawnOptions {
3
4
  adapter: DapResolvedAdapter;
@@ -15,7 +16,7 @@ export declare class DapClient {
15
16
  readonly adapter: DapResolvedAdapter;
16
17
  readonly cwd: string;
17
18
  readonly proc: DapClientState["proc"];
18
- constructor(adapter: DapResolvedAdapter, cwd: string, proc: DapClientState["proc"], options?: {
19
+ constructor(adapter: DapResolvedAdapter, cwd: string, owner: OwnedProcess, options?: {
19
20
  readable?: ReadableStream<Uint8Array>;
20
21
  writeSink?: DapWriteSink;
21
22
  socket?: {
@@ -1,2 +1,8 @@
1
+ /**
2
+ * Max byte size of a file the edit modes will load whole. Editing loads + normalizes +
3
+ * fuzzy-matches + diffs the entire file on the main thread, so a multi-MB/generated file
4
+ * would block the event loop (F19). Above this, fail fast with an actionable error.
5
+ */
6
+ export declare const MAX_EDIT_FILE_BYTES: number;
1
7
  export declare function readEditFileText(absolutePath: string, path: string): Promise<string>;
2
8
  export declare function serializeEditFileText(absolutePath: string, path: string, content: string): Promise<string>;
@@ -12,6 +12,7 @@ export declare function executeInVmContext(options: {
12
12
  sessionId: string;
13
13
  cwd: string;
14
14
  session: ToolSession;
15
+ ownerId?: string;
15
16
  reset?: boolean;
16
17
  code: string;
17
18
  filename: string;
@@ -21,4 +22,6 @@ export declare function executeInVmContext(options: {
21
22
  value: unknown;
22
23
  }>;
23
24
  export declare function resetVmContext(sessionKey: string): Promise<void>;
25
+ export declare function disposeVmContextsByOwner(ownerId: string): Promise<void>;
24
26
  export declare function disposeAllVmContexts(): Promise<void>;
27
+ export declare function liveVmContextCount(): number;
@@ -7,6 +7,7 @@ export interface JsExecutorOptions {
7
7
  onChunk?: (chunk: string) => Promise<void> | void;
8
8
  signal?: AbortSignal;
9
9
  sessionId: string;
10
+ ownerId?: string;
10
11
  reset?: boolean;
11
12
  sessionFile?: string;
12
13
  artifactPath?: string;
@@ -19,6 +19,8 @@ export interface BashExecutorOptions {
19
19
  /** Artifact path/id for full output storage */
20
20
  artifactPath?: string;
21
21
  artifactId?: string;
22
+ /** Execute without retaining a native Shell in the persistent session registry. */
23
+ oneShot?: boolean;
22
24
  /**
23
25
  * Invoked when the native minimizer rewrote the command's output, giving
24
26
  * the caller a chance to persist the lossless original capture (typically
@@ -1,4 +1,5 @@
1
- import * as fs from "node:fs/promises";
1
+ import type { Stats } from "node:fs";
2
+ import { type FileLockOptions } from "../config/file-lock";
2
3
  import type { SkillActiveEntry } from "../skill-state/active-state";
3
4
  import { type AuditEntry, type CanonicalGjcWorkflowSkill, type WorkflowStateMutationOwner } from "../skill-state/workflow-state-contract";
4
5
  /**
@@ -55,6 +56,12 @@ export interface StateWriterOptions {
55
56
  cwd?: string;
56
57
  receipt?: StateWriterReceiptContext;
57
58
  audit?: StateWriterAuditContext;
59
+ /**
60
+ * Cross-process lock tuning for read-modify-write paths that route through
61
+ * `withWorkflowStateLock` / `updateJsonAtomic`. Omit for the hardened
62
+ * `withFileLock` defaults.
63
+ */
64
+ lock?: FileLockOptions;
58
65
  }
59
66
  export interface DeleteIfOwnedOptions extends StateWriterOptions {
60
67
  predicate?: (current: unknown) => boolean | Promise<boolean>;
@@ -81,7 +88,7 @@ export interface GenericHardPruneTarget {
81
88
  export interface GenericHardPruneSelectorContext {
82
89
  path: string;
83
90
  category: WriterCategory | string;
84
- stat: Awaited<ReturnType<typeof fs.stat>>;
91
+ stat: Stats;
85
92
  readJson: () => Promise<unknown>;
86
93
  }
87
94
  export type GenericHardPruneSelector = (context: GenericHardPruneSelectorContext) => boolean | Promise<boolean>;
@@ -109,8 +116,63 @@ export declare function detectWorkflowEnvelopeIntegrityMismatch(filePath: string
109
116
  export declare function writeJsonAtomic(targetPath: string, value: unknown, options?: StateWriterOptions): Promise<string>;
110
117
  export declare function writeWorkflowEnvelopeAtomic(targetPath: string, value: unknown, options?: StateWriterOptions): Promise<string>;
111
118
  export declare function writeTextAtomic(targetPath: string, text: string, options?: StateWriterOptions): Promise<string>;
119
+ /**
120
+ * Serialize a read-modify-write (or any multi-step mutation) against concurrent
121
+ * writers of the same `.gjc/**` target. Uses the cross-process directory lock
122
+ * from `withFileLock`, keyed on the resolved file path, so separate CLI/agent
123
+ * processes (e.g. team-mode workers) cannot interleave one writer's read with
124
+ * another writer's write and silently drop the first mutation (issue #646).
125
+ *
126
+ * The lock is advisory: it only protects callers that route through it, so every
127
+ * read-modify-write of a given file MUST acquire this lock for the same resolved
128
+ * path. `atomicWrite`'s temp-file + rename crash-atomicity is preserved; this
129
+ * layers concurrency-atomicity on top without weakening it.
130
+ */
131
+ export declare function withWorkflowStateLock<T>(targetPath: string, fn: () => Promise<T>, options?: StateWriterOptions): Promise<T>;
112
132
  export declare function updateJsonAtomic<T = unknown>(targetPath: string, mutator: (current: T | undefined) => T | Promise<T>, options?: StateWriterOptions): Promise<string>;
113
133
  export declare function appendJsonl(targetPath: string, entry: unknown, options?: StateWriterOptions): Promise<string>;
134
+ export interface AppendJsonlIdempotentOptions extends StateWriterOptions {
135
+ /**
136
+ * Identity key for an entry. Two entries that produce the same non-`undefined`
137
+ * key are duplicates, so only the first is appended. Return `undefined` to opt a
138
+ * candidate out of dedup (it is always appended). Use `key` for the common case
139
+ * where identity reduces to a single string.
140
+ */
141
+ key?: (entry: unknown) => string | undefined;
142
+ /**
143
+ * Equivalence predicate: return `true` when `existing` already represents
144
+ * `candidate`, suppressing the append. Use when identity cannot be reduced to a
145
+ * single string key. When both `key` and `equals` are supplied, `equals` wins.
146
+ */
147
+ equals?: (candidate: unknown, existing: unknown) => boolean;
148
+ }
149
+ export interface AppendJsonlIdempotentResult {
150
+ path: string;
151
+ /** `true` when the entry was written; `false` when an equivalent entry already existed. */
152
+ appended: boolean;
153
+ /** The pre-existing entry that suppressed the append, when `appended` is `false`. */
154
+ duplicate?: unknown;
155
+ }
156
+ /**
157
+ * Append `entry` to a JSONL file only when no equivalent entry already exists —
158
+ * the shared idempotent append primitive (issue #660).
159
+ *
160
+ * `appendJsonl` is a pure append with no dedup, so every recurring "duplicate
161
+ * ledger row" bug (#638, #643, #645) had to be patched with bespoke per-call-site
162
+ * guards. This primitive centralizes the read-check-append cycle: a caller
163
+ * declares identity once via `key` or `equals` instead of re-deriving the lookup
164
+ * at each site.
165
+ *
166
+ * The read-then-append is serialized through the same cross-process workflow lock
167
+ * as `updateJsonAtomic`, so two concurrent idempotent appends cannot both observe
168
+ * "no duplicate" and both write (the #646 TOCTOU that a plain `appendJsonl`
169
+ * preceded by a manual existence check is still exposed to).
170
+ *
171
+ * Scope note: this dedups the *append* only. Call sites whose idempotency must
172
+ * also skip a coupled mutation — e.g. the plan/state rewrite in #643/#645 — still
173
+ * need a whole-operation guard; this primitive is the ledger-level half of that.
174
+ */
175
+ export declare function appendJsonlIdempotent(targetPath: string, entry: unknown, options: AppendJsonlIdempotentOptions): Promise<AppendJsonlIdempotentResult>;
114
176
  export declare function appendText(targetPath: string, text: string, options?: StateWriterOptions): Promise<string>;
115
177
  export declare function createJsonNoClobber(targetPath: string, value: unknown, options?: StateWriterOptions): Promise<string>;
116
178
  export declare function deleteIfOwned(targetPath: string, predicateOrOptions?: ((current: unknown) => boolean | Promise<boolean>) | DeleteIfOwnedOptions): Promise<DeleteResult>;
@@ -8,15 +8,21 @@ export interface GjcTmuxSessionStatus {
8
8
  branch?: string;
9
9
  branchSlug?: string;
10
10
  project?: string;
11
+ panePids: number[];
12
+ profile?: string;
11
13
  }
12
14
  export interface GjcTmuxSessionTagsForGc {
13
15
  profile?: string;
14
16
  project?: string;
15
17
  branch?: string;
18
+ branchSlug?: string;
19
+ createdAt?: string;
20
+ attached?: boolean;
21
+ panePids?: number[];
16
22
  }
17
23
  export interface GjcTmuxSessionsForGc {
18
24
  tagged: GjcTmuxSessionStatus[];
19
- untagged: string[];
25
+ untagged: GjcTmuxSessionStatus[];
20
26
  }
21
27
  export declare function listGjcTmuxSessions(env?: NodeJS.ProcessEnv): GjcTmuxSessionStatus[];
22
28
  /** @internal */
@@ -5,6 +5,15 @@ export interface UltragoalGuardDiagnostic {
5
5
  message: string;
6
6
  goalId?: string;
7
7
  }
8
+ export interface UltragoalAskBlockDiagnostic {
9
+ active: boolean;
10
+ reason: string;
11
+ source: "absent" | "durable_state" | "durable_state_unreadable" | "ledger" | "goals_json";
12
+ goalsPath?: string;
13
+ ledgerPath?: string;
14
+ goalIds?: string[];
15
+ message: string;
16
+ }
8
17
  export interface CurrentGoalLike {
9
18
  objective: string;
10
19
  status?: string;
@@ -19,6 +28,7 @@ export declare function readUltragoalVerificationState(input: {
19
28
  cwd: string;
20
29
  currentGoal?: CurrentGoalLike | null;
21
30
  }): Promise<UltragoalGuardDiagnostic>;
31
+ export declare function isUltragoalAskBlocked(cwd: string): Promise<UltragoalAskBlockDiagnostic>;
22
32
  export declare function assertCanCompleteCurrentGoal(input: {
23
33
  cwd: string;
24
34
  currentGoal?: CurrentGoalLike | null;
@@ -68,6 +68,8 @@ export interface UltragoalStatusSummary {
68
68
  goals: UltragoalGoal[];
69
69
  }
70
70
  export interface UltragoalCommandResult {
71
+ reviewBlockerGoalIds?: string[];
72
+ createdReviewPlan?: boolean;
71
73
  status: number;
72
74
  stdout?: string;
73
75
  stderr?: string;
@@ -118,6 +120,17 @@ export declare function startNextUltragoalGoal(input: {
118
120
  goal?: UltragoalGoal;
119
121
  allComplete: boolean;
120
122
  }>;
123
+ type SurfaceFamily = "web" | "cli" | "native" | "api-package" | "algorithm-math" | "unknown";
124
+ export declare function normalizeSurfaceToken(value: string): string;
125
+ export declare function surfaceFamily(value: string): SurfaceFamily;
126
+ export interface ReplayProcessHandle {
127
+ readonly exited: Promise<number>;
128
+ kill(signal?: number | NodeJS.Signals): void;
129
+ }
130
+ export declare function waitForReplayProcessWithTimeout(process: ReplayProcessHandle, timeoutMs: number, graceMs?: number): Promise<number>;
131
+ export declare function validateExecutorQaRedTeamEvidenceForReview(cwd: string, executorQa: Record<string, unknown>, options?: {
132
+ mode?: "review";
133
+ }): Promise<void>;
121
134
  export declare function checkpointUltragoalGoal(input: {
122
135
  cwd: string;
123
136
  goalId: string;
@@ -159,5 +172,21 @@ export declare function recordUltragoalReviewBlockers(input: {
159
172
  evidence: string;
160
173
  gjcGoalJson?: string;
161
174
  }): Promise<UltragoalPlan>;
175
+ type UltragoalReviewContractStrength = "strong" | "thin-derived";
176
+ interface UltragoalReviewFinding extends JsonObject {
177
+ severity: "blocker";
178
+ message: string;
179
+ }
180
+ interface UltragoalReviewResult extends JsonObject {
181
+ verdict: "pass" | "fail" | "inconclusive: weak-contract";
182
+ contractStrength: UltragoalReviewContractStrength;
183
+ cleanPassEligible: boolean;
184
+ source: JsonObject;
185
+ findings: UltragoalReviewFinding[];
186
+ artifactValidationSummary: JsonObject;
187
+ weakContractCapApplied: boolean;
188
+ blockerGoalIds?: string[];
189
+ }
190
+ export declare function runUltragoalReview(cwd: string, args: readonly string[]): Promise<UltragoalReviewResult>;
162
191
  export declare function runNativeUltragoalCommand(args: string[], cwd?: string): Promise<UltragoalCommandResult>;
163
192
  export {};
@@ -1,5 +1,6 @@
1
1
  import type { ptree } from "@gajae-code/utils";
2
2
  import * as z from "zod/v4";
3
+ import type { OwnedProcess } from "../runtime/process-lifecycle";
3
4
  export declare const lspSchema: z.ZodObject<{
4
5
  action: z.ZodEnum<{
5
6
  capabilities: "capabilities";
@@ -261,6 +262,7 @@ export interface LspClient {
261
262
  cwd: string;
262
263
  config: ServerConfig;
263
264
  proc: ptree.ChildProcess<"pipe">;
265
+ owner?: OwnedProcess;
264
266
  requestId: number;
265
267
  diagnostics: Map<string, PublishedDiagnostics>;
266
268
  diagnosticsVersion: number;
@@ -41,6 +41,7 @@ interface BridgeIdempotencyRecord {
41
41
  pending?: boolean;
42
42
  }
43
43
  type BridgeIdempotencyCache = Map<string, BridgeIdempotencyRecord>;
44
+ export declare function parseBridgeEndpoints(value: string | undefined): Partial<BridgeEndpointMatrix> | undefined;
44
45
  export declare function createBridgeFetchHandler(options: BridgeFetchHandlerOptions): (request: Request) => Promise<Response>;
45
46
  export declare function runBridgeMode(session: AgentSession, setToolUIContext?: (uiContext: ExtensionUIContext, hasUI: boolean) => void): Promise<never>;
46
47
  export {};
@@ -30,6 +30,8 @@ export declare class ModelSelectorComponent extends Container {
30
30
  temporaryOnly?: boolean;
31
31
  initialSearchInput?: string;
32
32
  sessionId?: string;
33
+ isFastForProvider?: (provider?: string) => boolean;
34
+ isFastForSubagentProvider?: (provider?: string) => boolean;
33
35
  });
34
36
  handleInput(keyData: string): void;
35
37
  getSearchInput(): Input;
@@ -10,5 +10,6 @@ export declare class OAuthSelectorComponent extends Container {
10
10
  requestRender?: () => void;
11
11
  });
12
12
  stopValidation(): void;
13
+ dispose(): void;
13
14
  handleInput(keyData: string): void;
14
15
  }
@@ -1,5 +1,5 @@
1
1
  import { Container } from "@gajae-code/tui";
2
- export type ProviderOnboardingAction = "custom-provider-wizard" | "oauth-login" | "api-guide";
2
+ export type ProviderOnboardingAction = "custom-provider-wizard" | "oauth-login" | "import-credentials" | "api-guide";
3
3
  export declare class ProviderOnboardingSelectorComponent extends Container {
4
4
  #private;
5
5
  constructor(onSelect: (action: ProviderOnboardingAction) => void, onCancel: () => void);
@@ -20,6 +20,7 @@ export interface MCPAddWizardOAuthResult {
20
20
  export declare class MCPAddWizard extends Container {
21
21
  #private;
22
22
  constructor(onComplete: (name: string, config: MCPServerConfig, scope: Scope) => void, onCancel: () => void, onOAuth?: (authUrl: string, tokenUrl: string, clientId: string, clientSecret: string, scopes: string) => Promise<MCPAddWizardOAuthResult>, onTestConnection?: (config: MCPServerConfig) => Promise<void>, onRender?: () => void, initialName?: string);
23
+ dispose(): void;
23
24
  handleInput(keyData: string): void;
24
25
  }
25
26
  export {};
@@ -47,6 +47,7 @@ export declare class ToolExecutionComponent extends Container {
47
47
  * Stop spinner animation and cleanup resources.
48
48
  */
49
49
  stopAnimation(): void;
50
+ dispose(): void;
50
51
  setExpanded(expanded: boolean): void;
51
52
  setShowImages(show: boolean): void;
52
53
  invalidate(): void;
@@ -86,7 +86,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
86
86
  retryLoader: Loader | undefined;
87
87
  autoCompactionEscapeHandler?: () => void;
88
88
  retryEscapeHandler?: () => void;
89
- retryCountdownTimer?: ReturnType<typeof setInterval>;
89
+ retryCountdownTimer?: NodeJS.Timeout;
90
90
  unsubscribe?: () => void;
91
91
  onInputCallback?: (input: SubmittedUserInput) => void;
92
92
  optimisticUserMessageSignature: string | undefined;