@gajae-code/coding-agent 0.5.2 → 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 (78) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/types/async/job-manager.d.ts +6 -0
  3. package/dist/types/dap/client.d.ts +2 -1
  4. package/dist/types/edit/read-file.d.ts +6 -0
  5. package/dist/types/eval/js/context-manager.d.ts +3 -0
  6. package/dist/types/eval/js/executor.d.ts +1 -0
  7. package/dist/types/exec/bash-executor.d.ts +2 -0
  8. package/dist/types/gjc-runtime/tmux-sessions.d.ts +7 -1
  9. package/dist/types/lsp/types.d.ts +2 -0
  10. package/dist/types/modes/bridge/bridge-mode.d.ts +1 -0
  11. package/dist/types/modes/components/model-selector.d.ts +2 -0
  12. package/dist/types/modes/components/oauth-selector.d.ts +1 -0
  13. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +1 -0
  14. package/dist/types/modes/components/tool-execution.d.ts +1 -0
  15. package/dist/types/runtime/process-lifecycle.d.ts +108 -0
  16. package/dist/types/runtime-mcp/transports/stdio.d.ts +1 -0
  17. package/dist/types/runtime-mcp/types.d.ts +2 -0
  18. package/dist/types/session/agent-session.d.ts +17 -1
  19. package/dist/types/session/artifacts.d.ts +4 -1
  20. package/dist/types/session/streaming-output.d.ts +5 -0
  21. package/dist/types/slash-commands/helpers/fast-status-report.d.ts +76 -0
  22. package/dist/types/tools/bash.d.ts +1 -0
  23. package/dist/types/tools/browser/tab-supervisor.d.ts +9 -0
  24. package/dist/types/tools/sqlite-reader.d.ts +2 -1
  25. package/package.json +7 -7
  26. package/src/async/job-manager.ts +153 -39
  27. package/src/config/file-lock.ts +9 -1
  28. package/src/dap/client.ts +105 -64
  29. package/src/dap/session.ts +44 -7
  30. package/src/edit/read-file.ts +19 -1
  31. package/src/eval/js/context-manager.ts +228 -65
  32. package/src/eval/js/executor.ts +2 -0
  33. package/src/eval/js/index.ts +1 -0
  34. package/src/eval/js/worker-core.ts +10 -6
  35. package/src/eval/py/executor.ts +68 -19
  36. package/src/eval/py/kernel.ts +46 -22
  37. package/src/eval/py/runner.py +68 -14
  38. package/src/exec/bash-executor.ts +49 -13
  39. package/src/gjc-runtime/tmux-gc.ts +86 -37
  40. package/src/gjc-runtime/tmux-sessions.ts +44 -6
  41. package/src/internal-urls/artifact-protocol.ts +10 -1
  42. package/src/internal-urls/docs-index.generated.ts +2 -2
  43. package/src/lsp/client.ts +64 -26
  44. package/src/lsp/index.ts +2 -1
  45. package/src/lsp/lspmux.ts +33 -9
  46. package/src/lsp/types.ts +2 -0
  47. package/src/modes/bridge/bridge-mode.ts +21 -0
  48. package/src/modes/components/assistant-message.ts +10 -2
  49. package/src/modes/components/bash-execution.ts +5 -1
  50. package/src/modes/components/eval-execution.ts +5 -1
  51. package/src/modes/components/model-selector.ts +34 -2
  52. package/src/modes/components/oauth-selector.ts +5 -0
  53. package/src/modes/components/runtime-mcp-add-wizard.ts +58 -7
  54. package/src/modes/components/skill-message.ts +24 -16
  55. package/src/modes/components/tool-execution.ts +6 -0
  56. package/src/modes/controllers/extension-ui-controller.ts +33 -6
  57. package/src/modes/controllers/input-controller.ts +5 -0
  58. package/src/modes/controllers/selector-controller.ts +6 -1
  59. package/src/modes/utils/ui-helpers.ts +5 -2
  60. package/src/runtime/process-lifecycle.ts +400 -0
  61. package/src/runtime-mcp/manager.ts +164 -50
  62. package/src/runtime-mcp/transports/http.ts +12 -11
  63. package/src/runtime-mcp/transports/stdio.ts +64 -38
  64. package/src/runtime-mcp/types.ts +3 -0
  65. package/src/sdk.ts +27 -0
  66. package/src/session/agent-session.ts +168 -22
  67. package/src/session/artifacts.ts +17 -2
  68. package/src/session/blob-store.ts +36 -2
  69. package/src/session/session-manager.ts +29 -13
  70. package/src/session/streaming-output.ts +54 -3
  71. package/src/slash-commands/builtin-registry.ts +30 -3
  72. package/src/slash-commands/helpers/fast-status-report.ts +111 -0
  73. package/src/tools/archive-reader.ts +10 -1
  74. package/src/tools/bash.ts +11 -4
  75. package/src/tools/browser/tab-supervisor.ts +22 -0
  76. package/src/tools/browser.ts +38 -4
  77. package/src/tools/read.ts +11 -12
  78. package/src/tools/sqlite-reader.ts +19 -5
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
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
+
5
19
  ## [0.5.2] - 2026-06-15
6
20
 
7
21
  ### Fixed
@@ -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,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
@@ -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 */
@@ -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
  }
@@ -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;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Shared runtime lifecycle foundation.
3
+ *
4
+ * Two minimal, deliberately small primitives that subsystem runtimes
5
+ * (DAP/LSP/MCP stdio, eval workers, etc.) adopt so spawned children and
6
+ * non-process resources cannot outlive their owner:
7
+ *
8
+ * F1(a) `spawnOwnedProcess` — wraps `ptree.spawn` with explicit
9
+ * process-group ownership, escalating (SIGTERM -> grace -> SIGKILL)
10
+ * tree termination, bounded `awaitExit`, abort-listener cleanup on
11
+ * settle, idempotent `dispose`, and a single postmortem hook that
12
+ * reaps every still-live owned process group on fatal/normal shutdown.
13
+ *
14
+ * F1(b) `registerResourceOwner` — a generic, idempotent postmortem adapter
15
+ * for non-process resources (Bun Workers, VM contexts, timers,
16
+ * sockets) built on the existing `postmortem.register` facility.
17
+ *
18
+ * Ownership is keyed to the *process group*, not the root process. A root that
19
+ * exits after backgrounding descendants (`sh -c "worker & exit 0"`) keeps the
20
+ * owner registered until the group is actually gone, so the descendant tree is
21
+ * still reaped by `dispose()`/postmortem.
22
+ *
23
+ * This module intentionally owns only these primitives. It does not migrate
24
+ * existing call sites; subsystem PRs adopt it incrementally.
25
+ *
26
+ * Note: `ptree.spawn` always pipes stdout/stderr. Adopters that expect output
27
+ * (DAP/LSP/MCP protocol servers) must consume `owner.child.stdout`; F1 does not
28
+ * drain it, so a chatty child whose stdout is never read can still block on a
29
+ * full pipe. That draining is the adopter's responsibility.
30
+ */
31
+ import { ptree } from "@gajae-code/utils";
32
+ /** Options for {@link spawnOwnedProcess}. */
33
+ export interface SpawnOwnedOptions {
34
+ cwd?: string;
35
+ env?: Record<string, string | undefined>;
36
+ /** stdin mode passed through to the child. Defaults to `"ignore"`. */
37
+ stdin?: "pipe" | "ignore";
38
+ /** When aborted, the owned process tree is disposed (escalating kill). */
39
+ signal?: AbortSignal;
40
+ /** Grace period (ms) between SIGTERM and SIGKILL on dispose. Default 2000. */
41
+ gracefulMs?: number;
42
+ /**
43
+ * Spawn the child as its own process-group leader so the whole descendant
44
+ * tree can be signalled on dispose. Defaults to `true` on POSIX. Has no
45
+ * effect on Windows, where teardown falls back to single-process kill.
46
+ */
47
+ processGroup?: boolean;
48
+ /** Label used in diagnostics. */
49
+ name?: string;
50
+ }
51
+ /** Result of a bounded {@link OwnedProcess.awaitExit}. */
52
+ export interface AwaitExitResult {
53
+ /** `true` when the process has exited; `false` when the timeout fired first. */
54
+ exited: boolean;
55
+ /** Exit code if known, else `null`. */
56
+ code: number | null;
57
+ }
58
+ /** A spawned child process owned by the runtime with guaranteed teardown. */
59
+ export interface OwnedProcess {
60
+ readonly child: ptree.ChildProcess;
61
+ readonly pid: number | undefined;
62
+ /** Resolves/rejects when the root child exits (mirrors ptree's `exited`). */
63
+ readonly exited: Promise<number>;
64
+ /** `true` once `dispose()` has started. */
65
+ readonly disposed: boolean;
66
+ /**
67
+ * Wait for the root child to exit, optionally bounded by `timeoutMs`. With no
68
+ * timeout it resolves only when the child exits. Never rejects.
69
+ */
70
+ awaitExit(opts?: {
71
+ timeoutMs?: number;
72
+ }): Promise<AwaitExitResult>;
73
+ /**
74
+ * Idempotently terminate the owned process *group*: SIGTERM the group, wait
75
+ * `gracefulMs`, then SIGKILL, polling group liveness throughout. Removes the
76
+ * abort listener and deregisters from the live-owner set only after teardown
77
+ * has completed. Repeated/concurrent calls return the same in-flight promise.
78
+ */
79
+ dispose(): Promise<void>;
80
+ }
81
+ /**
82
+ * Spawn a child process owned by the runtime. The returned {@link OwnedProcess}
83
+ * is registered for postmortem cleanup and tears down its whole process group
84
+ * on dispose/abort.
85
+ */
86
+ export declare function spawnOwnedProcess(cmd: string[], opts?: SpawnOwnedOptions): OwnedProcess;
87
+ /** Number of currently live owned processes. Exposed for leak assertions/tests. */
88
+ export declare function liveOwnedProcessCount(): number;
89
+ /** Dispose every live owned process. For owner-scoped teardown and tests. */
90
+ export declare function disposeAllOwnedProcesses(): Promise<void>;
91
+ type ResourceDisposer = () => void | Promise<void>;
92
+ /**
93
+ * Register a non-process resource for postmortem/fatal-exit cleanup.
94
+ *
95
+ * Idempotent by `name`: re-registering the same name replaces the prior
96
+ * disposer (last wins). Returns an unregister function that removes the owner
97
+ * only while it is still the active registration for that name.
98
+ */
99
+ export declare function registerResourceOwner(name: string, disposer: ResourceDisposer): () => void;
100
+ /** Number of registered resource owners. Exposed for leak assertions/tests. */
101
+ export declare function resourceOwnerCount(): number;
102
+ /**
103
+ * Run and clear every registered resource disposer. Attempts all disposers even
104
+ * if some throw, then surfaces the failures as an `AggregateError` so callers
105
+ * can distinguish "all closed" from "a resource may still be alive".
106
+ */
107
+ export declare function disposeAllResourceOwners(): Promise<void>;
108
+ export {};
@@ -14,6 +14,7 @@ export declare class StdioTransport implements MCPTransport {
14
14
  onRequest?: (method: string, params: unknown) => Promise<unknown>;
15
15
  constructor(config: MCPStdioServerConfig);
16
16
  get connected(): boolean;
17
+ get closeBeforeReconnect(): true;
17
18
  /**
18
19
  * Start the subprocess and begin reading.
19
20
  */
@@ -185,6 +185,8 @@ export interface MCPTransport {
185
185
  notify(method: string, params?: Record<string, unknown>): Promise<void>;
186
186
  /** Close the transport */
187
187
  close(): Promise<void>;
188
+ /** Whether close must finish before reconnect can safely spawn a replacement. */
189
+ readonly closeBeforeReconnect?: boolean;
188
190
  /** Whether the transport is connected */
189
191
  readonly connected: boolean;
190
192
  /** Event handlers */
@@ -13,7 +13,7 @@
13
13
  * Modes use this class and add their own I/O layer on top.
14
14
  */
15
15
  import { type Agent, type AgentEvent, type AgentMessage, type AgentState, type AgentTool, type StablePrefixSnapshot, ThinkingLevel } from "@gajae-code/agent-core";
16
- import { type CompactionResult } from "@gajae-code/agent-core/compaction";
16
+ import { type CompactionResult, type EmergencyCompactionSample } from "@gajae-code/agent-core/compaction";
17
17
  import type { AssistantMessage, Effort, ImageContent, Message, MessageAttribution, Model, ProviderSessionState, ServiceTier, SimpleStreamOptions, TextContent, ToolChoice, UsageReport } from "@gajae-code/ai";
18
18
  export interface ForkContextSeedMetadata {
19
19
  sourceSessionId: string;
@@ -726,6 +726,20 @@ export declare class AgentSession {
726
726
  * {@link isFastModeActive} instead — that one respects the model's provider.
727
727
  */
728
728
  isFastModeEnabled(): boolean;
729
+ /**
730
+ * True when the configured `serviceTier` resolves to `"priority"` for the
731
+ * given model `provider`. Returns false for scoped tiers that don't match
732
+ * (e.g. `"openai-only"` on an anthropic provider) and when `provider` is
733
+ * undefined. This is the canonical provider-aware fast-mode predicate.
734
+ */
735
+ isFastForProvider(provider?: string): boolean;
736
+ /**
737
+ * Provider-aware fast-mode predicate for task-tool subagent roles, evaluated
738
+ * against the effective subagent tier (`task.serviceTier`) rather than the
739
+ * main session tier. Use this for `task.agentModelOverrides` role rows so the
740
+ * ⚡ glyph reflects the tier the subagent actually runs under.
741
+ */
742
+ isFastForSubagentProvider(provider?: string): boolean;
729
743
  /**
730
744
  * True when the configured `serviceTier` resolves to `"priority"` for the
731
745
  * *currently selected model's provider*. Returns false for scoped tiers
@@ -789,6 +803,8 @@ export declare class AgentSession {
789
803
  */
790
804
  handoff(customInstructions?: string, options?: SessionHandoffOptions): Promise<HandoffResult | undefined>;
791
805
  prepareContributionPrep(options?: ContributionPrepOptions): Promise<ContributionPrepResult>;
806
+ /** Test seam: override the emergency-compaction resource sampler so tests never read real RSS. */
807
+ setResourceSampler(sampler: () => EmergencyCompactionSample): void;
792
808
  /**
793
809
  * Toggle auto-compaction setting.
794
810
  */
@@ -1,3 +1,6 @@
1
+ export interface ArtifactSaveOptions {
2
+ maxBytes?: number;
3
+ }
1
4
  /**
2
5
  * Manages artifact storage for a session.
3
6
  *
@@ -40,7 +43,7 @@ export declare class ArtifactManager {
40
43
  * @param toolType Tool name for file extension (e.g., "bash", "read")
41
44
  * @returns Artifact ID (numeric string)
42
45
  */
43
- save(content: string, toolType: string): Promise<string>;
46
+ save(content: string, toolType: string, options?: ArtifactSaveOptions): Promise<string>;
44
47
  /**
45
48
  * Check if an artifact exists.
46
49
  * @param id Artifact ID (numeric string)
@@ -2,6 +2,7 @@ import type { AgentToolUpdateCallback } from "@gajae-code/agent-core";
2
2
  export declare const DEFAULT_MAX_LINES = 3000;
3
3
  export declare const DEFAULT_MAX_BYTES: number;
4
4
  export declare const DEFAULT_MAX_COLUMN = 1024;
5
+ export declare const DEFAULT_ARTIFACT_MAX_BYTES: number;
5
6
  export interface OutputSummary {
6
7
  output: string;
7
8
  truncated: boolean;
@@ -19,6 +20,8 @@ export interface OutputSummary {
19
20
  columnTruncatedLines?: number;
20
21
  /** Artifact ID for internal URL access (artifact://<id>) when truncated */
21
22
  artifactId?: string;
23
+ /** Bytes omitted from artifact storage after the artifact hard cap was reached. */
24
+ artifactTruncatedBytes?: number;
22
25
  }
23
26
  export interface OutputSinkOptions {
24
27
  artifactPath?: string;
@@ -38,6 +41,8 @@ export interface OutputSinkOptions {
38
41
  * writes still respect the budget. Default 0 = no per-line cap.
39
42
  */
40
43
  maxColumns?: number;
44
+ /** Hard cap for artifact writes/pending replay. Default DEFAULT_ARTIFACT_MAX_BYTES. */
45
+ artifactMaxBytes?: number;
41
46
  onChunk?: (chunk: string) => void;
42
47
  /** Minimum ms between onChunk calls. 0 = every chunk (default). */
43
48
  chunkThrottleMs?: number;
@@ -0,0 +1,76 @@
1
+ import type { Model } from "@gajae-code/ai";
2
+ /**
3
+ * A single line in the `/fast status` report: a labelled model and whether fast
4
+ * mode is effective for it. The `fast` flag is resolved by the caller
5
+ * (`buildFastStatusReport`) so each row can use the correct service tier — the
6
+ * main session tier for the current model / `modelRoles` roles, or the subagent
7
+ * tier (`task.serviceTier`) for `task.agentModelOverrides` roles.
8
+ */
9
+ export interface FastStatusRow {
10
+ /** Display label, e.g. "현재 모델", "DEFAULT", "EXECUTOR". */
11
+ label: string;
12
+ /** Resolved model for this row, if any. */
13
+ model?: Model;
14
+ /** Whether fast mode is effective for this row's model. */
15
+ fast: boolean;
16
+ }
17
+ export interface FormatFastStatusReportArgs {
18
+ rows: FastStatusRow[];
19
+ /** The active theme's fast icon token (`theme.icon.fast`). */
20
+ iconFast: string;
21
+ /** Optional decorator for inactive ("off") text, e.g. theme dim in the TUI. */
22
+ formatInactive?: (text: string) => string;
23
+ }
24
+ /** Title line of the `/fast status` report. */
25
+ export declare const FAST_STATUS_TITLE = "Fast \uBAA8\uB4DC \uC0C1\uD0DC";
26
+ /** The inactive marker shown for rows where fast mode does not apply. */
27
+ export declare const FAST_STATUS_OFF = "off";
28
+ /**
29
+ * Format a multiline `/fast status` report. Pure and shared by the CLI
30
+ * (`handle`) and TUI (`handleTui`) command branches so the two never drift.
31
+ * Each row's fast/off state is decided by the caller (see
32
+ * {@link buildFastStatusReport}) so per-row service-tier differences are honored.
33
+ */
34
+ export declare function formatFastStatusReport(args: FormatFastStatusReportArgs): string;
35
+ /** Minimal session surface needed to build the `/fast status` report. */
36
+ export interface FastStatusSessionLike {
37
+ readonly model?: Model;
38
+ /** Fast predicate against the main session tier (current model + `modelRoles`). */
39
+ isFastForProvider(provider?: string): boolean;
40
+ /** Fast predicate against the effective subagent tier (`task.agentModelOverrides` roles). */
41
+ isFastForSubagentProvider(provider?: string): boolean;
42
+ resolveRoleModelWithThinking(role: string): {
43
+ model?: Model;
44
+ };
45
+ }
46
+ /** A role to enumerate in the report, with the tier source its subagent runs under. */
47
+ export interface FastStatusRoleTarget {
48
+ id: string;
49
+ label: string;
50
+ /**
51
+ * True for `task.agentModelOverrides` roles (executor/architect/planner/critic)
52
+ * that run under `task.serviceTier`; false for `modelRoles` roles (default)
53
+ * that run under the main session tier.
54
+ */
55
+ isSubagentRole: boolean;
56
+ }
57
+ export interface BuildFastStatusReportArgs {
58
+ session: FastStatusSessionLike;
59
+ /** Role targets to enumerate, in display order. */
60
+ roleTargets: ReadonlyArray<FastStatusRoleTarget>;
61
+ /** The active theme's fast icon token (`theme.icon.fast`). */
62
+ iconFast: string;
63
+ /** Optional decorator for inactive ("off") text, e.g. theme dim in the TUI. */
64
+ formatInactive?: (text: string) => string;
65
+ }
66
+ /**
67
+ * Build the `/fast status` report from a live session: the active/current model
68
+ * followed by each assigned role (subagent) model. Unassigned roles are skipped
69
+ * so the report mirrors the `/model` selector, which only badges assigned roles.
70
+ *
71
+ * Subagent roles (`task.agentModelOverrides`) are evaluated against the
72
+ * effective subagent tier (`task.serviceTier`), while the current model and
73
+ * `modelRoles` roles use the main session tier — matching where each model
74
+ * actually runs.
75
+ */
76
+ export declare function buildFastStatusReport(args: BuildFastStatusReportArgs): string;
@@ -6,6 +6,7 @@ import { type Theme } from "../modes/theme/theme";
6
6
  import type { ToolSession } from ".";
7
7
  import { type OutputMeta } from "./output-meta";
8
8
  export declare const BASH_DEFAULT_PREVIEW_LINES = 10;
9
+ export declare function saveBashOriginalArtifactForTests(session: ToolSession, originalText: string): Promise<string | undefined>;
9
10
  declare const bashSchemaBase: z.ZodObject<{
10
11
  command: z.ZodString;
11
12
  env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
@@ -26,6 +26,8 @@ export interface TabSession {
26
26
  pending: Map<string, PendingRun>;
27
27
  dialogPolicy?: DialogPolicy;
28
28
  kindTag: BrowserKindTag;
29
+ /** Session that acquired this tab; used for session-scoped teardown (F13). */
30
+ ownerId?: string;
29
31
  }
30
32
  export interface AcquireTabOptions {
31
33
  url?: string;
@@ -39,6 +41,8 @@ export interface AcquireTabOptions {
39
41
  signal?: AbortSignal;
40
42
  timeoutMs: number;
41
43
  dialogs?: DialogPolicy;
44
+ /** Owning session id so dispose can release only this session's tabs (F13). */
45
+ ownerId?: string;
42
46
  }
43
47
  export interface AcquireTabResult {
44
48
  tab: TabSession;
@@ -58,6 +62,11 @@ export declare function acquireTab(name: string, browser: BrowserHandle, opts: A
58
62
  export declare function runInTab(name: string, opts: RunInTabOptions): Promise<RunResultOk>;
59
63
  export declare function releaseTab(name: string, opts?: ReleaseTabOptions): Promise<boolean>;
60
64
  export declare function releaseAllTabs(opts?: ReleaseTabOptions): Promise<number>;
65
+ /**
66
+ * Release only the tabs owned by `ownerId` (F13 session-scoped teardown). Tabs acquired
67
+ * by other sessions (or with no owner) are left untouched. No-op for a null/empty owner.
68
+ */
69
+ export declare function releaseTabsForOwner(ownerId: string | null | undefined, opts?: ReleaseTabOptions): Promise<number>;
61
70
  export declare function dropHeadlessTabs(): Promise<void>;
62
71
  export declare function initializeTabWorkerForTest(worker: WorkerHandle, payload: WorkerInitPayload, timeoutMs: number): Promise<ReadyInfo>;
63
72
  export {};
@@ -60,9 +60,10 @@ export declare function getRowByKey(db: Database, table: string, pk: {
60
60
  type?: string;
61
61
  }, key: string): Record<string, unknown> | null;
62
62
  export declare function getRowByRowId(db: Database, table: string, key: string): Record<string, unknown> | null;
63
- export declare function executeReadQuery(db: Database, sql: string): {
63
+ export declare function executeReadQuery(db: Database, sql: string, maxRows?: number): {
64
64
  columns: string[];
65
65
  rows: Record<string, unknown>[];
66
+ truncated: boolean;
66
67
  };
67
68
  export declare function insertRow(db: Database, table: string, data: Record<string, unknown>): void;
68
69
  export declare function updateRowByKey(db: Database, table: string, pk: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.5.2",
4
+ "version": "0.5.3",
5
5
  "description": "Gajae Code CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://gaebal-gajae.dev",
7
7
  "author": "Yeachan-Heo",
@@ -51,12 +51,12 @@
51
51
  "@agentclientprotocol/sdk": "0.21.0",
52
52
  "@babel/parser": "^7.29.3",
53
53
  "@mozilla/readability": "^0.6.0",
54
- "@gajae-code/stats": "0.5.2",
55
- "@gajae-code/agent-core": "0.5.2",
56
- "@gajae-code/ai": "0.5.2",
57
- "@gajae-code/natives": "0.5.2",
58
- "@gajae-code/tui": "0.5.2",
59
- "@gajae-code/utils": "0.5.2",
54
+ "@gajae-code/stats": "0.5.3",
55
+ "@gajae-code/agent-core": "0.5.3",
56
+ "@gajae-code/ai": "0.5.3",
57
+ "@gajae-code/natives": "0.5.3",
58
+ "@gajae-code/tui": "0.5.3",
59
+ "@gajae-code/utils": "0.5.3",
60
60
  "@puppeteer/browsers": "^2.13.0",
61
61
  "@types/turndown": "5.0.6",
62
62
  "@xterm/headless": "^6.0.0",