@gajae-code/coding-agent 0.10.0 → 0.10.1

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 (47) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/types/cli.d.ts +1 -0
  3. package/dist/types/config/settings.d.ts +2 -0
  4. package/dist/types/coordinator/contract.d.ts +1 -1
  5. package/dist/types/coordinator-mcp/policy.d.ts +3 -0
  6. package/dist/types/coordinator-mcp/server.d.ts +17 -5
  7. package/dist/types/coordinator-mcp/session-reaper.d.ts +53 -0
  8. package/dist/types/extensibility/extensions/types.d.ts +3 -1
  9. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  10. package/dist/types/modes/components/status-line.d.ts +0 -10
  11. package/dist/types/modes/print-mode.d.ts +3 -4
  12. package/dist/types/modes/rpc/rpc-client.d.ts +7 -1
  13. package/dist/types/modes/rpc/rpc-mode.d.ts +2 -1
  14. package/dist/types/modes/rpc/rpc-types.d.ts +17 -1
  15. package/dist/types/modes/utils/context-usage.d.ts +4 -19
  16. package/dist/types/session/agent-session.d.ts +14 -1
  17. package/dist/types/session/context-estimation.d.ts +31 -0
  18. package/package.json +7 -7
  19. package/src/cli.ts +1 -0
  20. package/src/config/settings.ts +96 -45
  21. package/src/coordinator/contract.ts +1 -0
  22. package/src/coordinator-mcp/policy.ts +30 -0
  23. package/src/coordinator-mcp/server.ts +294 -66
  24. package/src/coordinator-mcp/session-reaper.ts +129 -0
  25. package/src/extensibility/extensions/runner.ts +1 -1
  26. package/src/extensibility/extensions/types.ts +3 -1
  27. package/src/internal-urls/docs-index.generated.ts +4 -4
  28. package/src/main.ts +3 -5
  29. package/src/modes/acp/acp-agent.ts +2 -2
  30. package/src/modes/components/footer.ts +3 -3
  31. package/src/modes/components/status-line/segments.ts +13 -6
  32. package/src/modes/components/status-line/types.ts +1 -1
  33. package/src/modes/components/status-line.ts +4 -171
  34. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  35. package/src/modes/interactive-mode.ts +16 -0
  36. package/src/modes/print-mode.ts +228 -92
  37. package/src/modes/rpc/rpc-client.ts +58 -1
  38. package/src/modes/rpc/rpc-mode.ts +286 -85
  39. package/src/modes/rpc/rpc-types.ts +15 -1
  40. package/src/modes/shared/agent-wire/command-dispatch.ts +33 -1
  41. package/src/modes/shared/agent-wire/command-validation.ts +10 -0
  42. package/src/modes/shared/agent-wire/scopes.ts +1 -0
  43. package/src/modes/utils/context-usage.ts +56 -92
  44. package/src/session/agent-session.ts +287 -85
  45. package/src/session/context-estimation.ts +91 -0
  46. package/src/slash-commands/builtin-registry.ts +12 -2
  47. package/src/slash-commands/helpers/context-report.ts +26 -4
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.10.1] - 2026-07-13
6
+
7
+ ### Added
8
+
9
+ - Added an owner-proof idle session reaper and `gjc_coordinator_stop_session` for ephemeral (delegate-created) coordinator sessions. Termination goes exclusively through the owner-proof `forceCloseGjcTmuxSession` path (pid, native session id, owner generation, server key, and start time all verified before SIGTERM) — never a raw `process.kill`. The reaper binds each close to the persisted runtime-state file, re-validates ephemeral and no-active-turn at kill time under the same per-session mutation lock as delegate reuse, and purges state only on verified termination (#2080).
10
+ - RPC clients can now durably select the machine-global default model and effective thinking level for subsequent messages, while project policy and resumed session history retain precedence.
11
+ - Context-usage tokens/% now use provider-reported usage as the single source of truth on every surface. `AgentSession.getContextUsage()` returns a source-tagged snapshot (`source: "provider_anchor" | "heuristic" | "unknown"`), and the status-line `context_pct` segment, inline model percentage, and `/context` totals consume that snapshot instead of recomputing an independent heuristic sum — so footer, status line, `/context`, ACP, and RPC can no longer disagree about the same session state. Heuristic estimation now applies only when no provider anchor exists (session start, aborted/error-only turns), and that fallback now includes the fixed system-prompt/tool/skill context it previously omitted. The pre-prompt compaction estimate honors the latest-compaction boundary (never anchors on stale pre-compaction usage totals), unknown post-compaction usage stays unknown (status line renders `?`; ACP omits the `usage_update` instead of reporting `used: 0`), and `/context` labels its total by provenance with a reconciliation line when the estimated category composition diverges from the provider-reported total.
12
+ ### Fixed
13
+
14
+ - IRC deliveries now accept their exchange batch in the recipient's volatile current-session queue before recipient/main UI observations or sender success. Awaited deliveries generate the reply first, then accept the ordered incoming + auto-reply pair and commit the IRC roster claim before observation; provider failures and sender aborts before acceptance leave no ghost exchange, while observer failures after acceptance are isolated. This is not a durability guarantee: durable history injection remains a later flush and no fsync, recovery, persistent IDs, or deduplication was added.
15
+ - Print mode now records terminal text-mode errors as exit status 1 (or 78 for context overflow) without bypassing output quiescence or session disposal. It retains JSON event delivery through disposal and suppresses `EPIPE` from its owned stdout; `ERR_STREAM_DESTROYED` is suppressed only after that `EPIPE` has latched, while other output failures remain errors.
16
+ - Extension contexts now receive a defensive copy from `getSystemPrompt()` instead of the live mutable system-prompt array, so an in-place mutation by an in-process extension can no longer bypass context-revision tracking and serve stale display-only context-usage estimates.
17
+
5
18
  ## [0.10.0] - 2026-07-12
6
19
 
7
20
  ### Added
@@ -3,6 +3,7 @@
3
3
  * CLI entry point — registers all commands explicitly and delegates to the
4
4
  * lightweight CLI runner from pi-utils.
5
5
  */
6
+ import "@gajae-code/utils/postmortem";
6
7
  import { Command, type CommandEntry } from "@gajae-code/utils/cli";
7
8
  export declare const commands: CommandEntry[];
8
9
  export declare class RootHelpCommand extends Command {
@@ -115,6 +115,8 @@ export declare class Settings {
115
115
  * Set a model role (helper for modelRoles record).
116
116
  */
117
117
  setModelRole(role: ModelRole | string, modelId: string): void;
118
+ setGlobalModelRole(role: ModelRole | string, modelId: string | undefined): void;
119
+ setGlobalModelRoleAndFlush(role: ModelRole | string, modelId: string | undefined): Promise<void>;
118
120
  /**
119
121
  * Set an agent model override while keeping any live runtime override aligned.
120
122
  *
@@ -1,4 +1,4 @@
1
1
  export declare const COORDINATOR_MCP_PROTOCOL_VERSION = "2024-11-05";
2
2
  export declare const COORDINATOR_MCP_SERVER_NAME = "gjc-coordinator-mcp";
3
- export declare const COORDINATOR_MCP_TOOL_NAMES: readonly ["gjc_coordinator_list_sessions", "gjc_coordinator_read_status", "gjc_coordinator_read_tail", "gjc_coordinator_list_questions", "gjc_coordinator_list_artifacts", "gjc_coordinator_read_artifact", "gjc_coordinator_read_coordination_status", "gjc_coordinator_watch_events", "gjc_coordinator_register_session", "gjc_coordinator_start_session", "gjc_coordinator_send_prompt", "gjc_coordinator_submit_question_answer", "gjc_coordinator_read_turn", "gjc_coordinator_await_turn", "gjc_coordinator_report_status", "gjc_delegate_plan", "gjc_delegate_execute", "gjc_delegate_team"];
3
+ export declare const COORDINATOR_MCP_TOOL_NAMES: readonly ["gjc_coordinator_list_sessions", "gjc_coordinator_read_status", "gjc_coordinator_read_tail", "gjc_coordinator_list_questions", "gjc_coordinator_list_artifacts", "gjc_coordinator_read_artifact", "gjc_coordinator_read_coordination_status", "gjc_coordinator_watch_events", "gjc_coordinator_register_session", "gjc_coordinator_start_session", "gjc_coordinator_stop_session", "gjc_coordinator_send_prompt", "gjc_coordinator_submit_question_answer", "gjc_coordinator_read_turn", "gjc_coordinator_await_turn", "gjc_coordinator_report_status", "gjc_delegate_plan", "gjc_delegate_execute", "gjc_delegate_team"];
4
4
  export type CoordinatorToolName = (typeof COORDINATOR_MCP_TOOL_NAMES)[number];
@@ -10,6 +10,9 @@ export interface CoordinatorMcpConfig {
10
10
  namespace: CoordinatorNamespace;
11
11
  stateRoot: string;
12
12
  sessionCommand: string | null;
13
+ sessionIdleTtlMs: number;
14
+ sessionSweepIntervalMs: number;
15
+ forceStopEnabled: boolean;
13
16
  }
14
17
  export interface CoordinatorMutationRequest {
15
18
  allow_mutation?: boolean;
@@ -2,6 +2,7 @@ import { COORDINATOR_MCP_PROTOCOL_VERSION, COORDINATOR_MCP_SERVER_NAME, COORDINA
2
2
  import { type OwnerIsolationProbe } from "../gjc-runtime/tmux-owner-isolation";
3
3
  import { type CoordinatorModelProfileLoader } from "./model-preset";
4
4
  import { type CoordinatorMcpConfig } from "./policy";
5
+ import { type SessionReaper } from "./session-reaper";
5
6
  export type { CoordinatorToolName };
6
7
  export { COORDINATOR_MCP_PROTOCOL_VERSION, COORDINATOR_MCP_SERVER_NAME, COORDINATOR_MCP_TOOL_NAMES };
7
8
  interface JsonRpcRequest {
@@ -47,6 +48,7 @@ interface CoordinatorServices {
47
48
  listSessions?: () => unknown[] | Promise<unknown[]>;
48
49
  startSession?: (input: SessionStartInput) => unknown | Promise<unknown>;
49
50
  commandRunner?: CommandRunner;
51
+ forceCloseSession?: (sessionName: string, env: NodeJS.ProcessEnv, expectedSessionId?: string, expectedStateFile?: string) => Promise<unknown>;
50
52
  ownerIsolationProbe?: OwnerIsolationProbe;
51
53
  resolveModelProfiles?: CoordinatorModelProfileLoader;
52
54
  }
@@ -93,6 +95,17 @@ export declare function createCoordinatorMcpServer(options?: CoordinatorMcpServe
93
95
  callTool: (name: string, args?: Record<string, unknown>) => Promise<Record<string, unknown>>;
94
96
  handleJsonRpc: (request: JsonRpcRequest) => Promise<JsonRpcResponse>;
95
97
  handle: (request: JsonRpcRequest) => Promise<JsonRpcResponse>;
98
+ reapSession: (rawId: unknown, opts?: {
99
+ force?: boolean;
100
+ reason?: string;
101
+ }) => Promise<{
102
+ ok: boolean;
103
+ reason?: string;
104
+ killed: boolean;
105
+ active_turn_id?: string;
106
+ detail?: string;
107
+ }>;
108
+ sessionReaper: SessionReaper;
96
109
  };
97
110
  export declare function handleCoordinatorMcpRequest(request: JsonRpcRequest, options?: LegacyHandlerOptions): Promise<JsonRpcResponse>;
98
111
  export interface PumpCoordinatorOptions {
@@ -115,11 +128,10 @@ export interface PumpCoordinatorOptions {
115
128
  * answerable even while data handlers saturate.
116
129
  * - Data handlers are capped at `maxDataConcurrency`; excess is queued up to
117
130
  * `maxQueueDepth`, then rejected as `server_busy` (bounded memory / fanout).
118
- * - `writeLine` failures move the writer to a terminal closed state instead of
119
- * poisoning the serialized write chain or escaping as an unhandled rejection;
120
- * no writes happen after close.
121
- * - On EOF the pump drains in-flight handlers (bounded by `drainTimeoutMs`) and
122
- * flushes queued writes before returning, so shutdown never races live work.
131
+ * - A coded local `EPIPE` terminalizes writer and dispatch together; other
132
+ * writer faults reject the pump without poisoning the serialized write chain.
133
+ * - On EOF or a closed peer the pump drains already-running handlers (bounded
134
+ * by `drainTimeoutMs`) and never promotes queued work.
123
135
  * - Byte chunks are decoded with a streaming decoder so multibyte characters
124
136
  * split across chunks are not corrupted.
125
137
  */
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Idle reaper for coordinator-managed GJC worker sessions.
3
+ *
4
+ * Every `gjc_delegate_*` call that omits `session_id` starts a fresh tmux worker
5
+ * session. Nothing in the coordinator ever tore those down, so completed/crashed
6
+ * sessions accumulated (RAM + worktrees) until something killed them by hand.
7
+ *
8
+ * This is the automatic backstop (defense-in-depth): a periodic sweep force-closes
9
+ * sessions that are (a) ephemeral — coordinator delegate-created, never a user's
10
+ * registered resident session, (b) not mid-turn, and (c) idle past a TTL.
11
+ *
12
+ * The controller is pure + fully injectable (clock / list / reap side-effects) so
13
+ * it is unit-testable without tmux, the filesystem, or wall-clock waits. The
14
+ * scheduling mirrors the proven resource-gc controller: a recursive setTimeout
15
+ * with a generation guard so a stop()+start() can never leak a duplicate timer.
16
+ */
17
+ export declare const DEFAULT_SESSION_IDLE_TTL_MS: number;
18
+ export declare const DEFAULT_SESSION_SWEEP_INTERVAL_MS: number;
19
+ export declare const MIN_SESSION_IDLE_TTL_MS = 60000;
20
+ export declare const MIN_SESSION_SWEEP_INTERVAL_MS = 30000;
21
+ /** Minimal projection of a coordinator session the reaper needs to decide. */
22
+ export interface ReapableSession {
23
+ sessionId: string;
24
+ /** True only for coordinator delegate-created sessions. User-registered resident sessions are false and never reaped. */
25
+ ephemeral: boolean;
26
+ /** Epoch ms of last observed activity (turn update / session-state write / session creation). */
27
+ lastActivityMs: number;
28
+ /** True while a turn is active — never reap mid-turn. */
29
+ hasActiveTurn: boolean;
30
+ }
31
+ export interface SessionReaperPolicy {
32
+ idleTtlMs: number;
33
+ sweepIntervalMs: number;
34
+ }
35
+ /**
36
+ * Pure selection: which sessions are safe to reap at `now`.
37
+ * A session is reapable iff ephemeral AND not mid-turn AND idle ≥ (clamped) TTL.
38
+ */
39
+ export declare function selectReapableSessions(sessions: readonly ReapableSession[], now: number, idleTtlMs: number): ReapableSession[];
40
+ /** Injectable side-effects so the controller runs in tests without tmux/fs/real time. */
41
+ export interface SessionReaperDeps {
42
+ listSessions: () => Promise<ReapableSession[]>;
43
+ reapSession: (sessionId: string) => Promise<void>;
44
+ now: () => number;
45
+ }
46
+ export interface SessionReaper {
47
+ /** Run one sweep; returns the number of sessions successfully reaped. */
48
+ sweepOnce: () => Promise<number>;
49
+ start: () => void;
50
+ stop: () => void;
51
+ readonly running: boolean;
52
+ }
53
+ export declare function createSessionReaper(deps: SessionReaperDeps, policy: SessionReaperPolicy): SessionReaper;
@@ -176,11 +176,13 @@ export interface ExtensionUIContext {
176
176
  setToolsExpanded(expanded: boolean): void;
177
177
  }
178
178
  export interface ContextUsage {
179
- /** Estimated context tokens, or null if unknown (e.g. right after compaction, before next LLM response). */
179
+ /** Context tokens, or null if unknown (e.g. right after compaction, before next LLM response). */
180
180
  tokens: number | null;
181
181
  contextWindow: number;
182
182
  /** Context usage as percentage of context window, or null if tokens is unknown. */
183
183
  percent: number | null;
184
+ /** Provenance: "provider_anchor" = anchored on provider-reported usage (+ heuristic trailing delta); "heuristic" = no provider anchor available, full estimate; "unknown" = post-compaction, no post-compaction provider response yet. */
185
+ source: "provider_anchor" | "heuristic" | "unknown";
184
186
  }
185
187
  export interface CompactOptions {
186
188
  onComplete?: (result: CompactionResult) => void;
@@ -25,7 +25,7 @@ export interface SegmentContext {
25
25
  cost: number;
26
26
  tokensPerSecond: number | null;
27
27
  };
28
- contextPercent: number;
28
+ contextPercent: number | null;
29
29
  contextWindow: number;
30
30
  /**
31
31
  * True when a standalone `context_pct` segment is also part of the active
@@ -70,16 +70,6 @@ export declare class StatusLineComponent implements Component {
70
70
  * unit tests can verify the backoff invariant.
71
71
  */
72
72
  refreshUsageInBackground(): void;
73
- /**
74
- * Compute the (cached) used-tokens / context-window totals for the
75
- * status-line context% segment. Exposed (non-private) so unit tests can
76
- * verify the incremental-cache invariants; not part of any external
77
- * API.
78
- */
79
- getCachedContextBreakdown(): {
80
- usedTokens: number;
81
- contextWindow: number;
82
- };
83
73
  getTopBorder(width: number): {
84
74
  content: string;
85
75
  width: number;
@@ -20,9 +20,8 @@ export interface PrintModeOptions {
20
20
  /** Images to attach to the initial message */
21
21
  initialImages?: ImageContent[];
22
22
  /**
23
- * When true, an assistant error/abort does not call process.exit(); print mode
24
- * returns instead so the caller (e.g. RLM autonomous mode) can run its own
25
- * finalization/cleanup before the process exits.
23
+ * When true, a terminal assistant error/abort leaves process exit status
24
+ * untouched so the caller can run its own finalization and status handling.
26
25
  */
27
26
  suppressProcessExit?: boolean;
28
27
  }
@@ -37,7 +36,7 @@ export interface PrintModeOptions {
37
36
  * events from the subscription and does not run this terminal-error branch, so
38
37
  * it is intentionally NOT covered by this exit code.
39
38
  */
40
- export declare const CONTEXT_OVERFLOW_EXIT_CODE = 2;
39
+ export declare const CONTEXT_OVERFLOW_EXIT_CODE = 78;
41
40
  /**
42
41
  * Run in print (single-shot) mode.
43
42
  * Sends prompts to the agent and outputs the result.
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Spawns the agent in RPC mode and provides a typed API for all operations.
5
5
  */
6
- import type { AgentEvent, AgentMessage, AgentToolResult, ThinkingLevel } from "@gajae-code/agent-core";
6
+ import { type AgentEvent, type AgentMessage, type AgentToolResult, type ResolvedThinkingLevel, ThinkingLevel } from "@gajae-code/agent-core";
7
7
  import type { CompactionResult } from "@gajae-code/agent-core/compaction";
8
8
  import type { ImageContent, Model } from "@gajae-code/ai";
9
9
  import type { BashResult } from "../../exec/bash-executor";
@@ -36,6 +36,11 @@ export interface RpcClientOptions {
36
36
  customTools?: RpcClientCustomTool[];
37
37
  }
38
38
  export type ModelInfo = Pick<Model, "provider" | "id" | "contextWindow" | "reasoning" | "thinking">;
39
+ export type RpcDefaultModelSelection = {
40
+ readonly provider: string;
41
+ readonly modelId: string;
42
+ readonly thinkingLevel: ResolvedThinkingLevel;
43
+ };
39
44
  export type RpcEventListener = (event: AgentEvent) => void;
40
45
  export type RpcSessionEventListener = (event: AgentSessionEvent) => void;
41
46
  export interface RpcClientToolContext<TDetails = unknown> {
@@ -149,6 +154,7 @@ export declare class RpcClient {
149
154
  provider: string;
150
155
  id: string;
151
156
  }>;
157
+ setDefaultModelSelection(provider: string, modelId: string, thinkingLevel: ResolvedThinkingLevel): Promise<RpcDefaultModelSelection>;
152
158
  /**
153
159
  * Cycle to next model.
154
160
  */
@@ -18,7 +18,7 @@ export type PendingExtensionRequest = {
18
18
  resolve: (response: RpcExtensionUIResponse) => void;
19
19
  reject: (error: Error) => void;
20
20
  };
21
- type RpcOutput = (obj: RpcResponse | RpcExtensionUIRequest | RpcHostToolCallRequest | RpcHostToolCancelRequest | RpcHostUriRequest | RpcHostUriCancelRequest | object) => void;
21
+ type RpcOutput = (obj: RpcResponse | RpcExtensionUIRequest | RpcHostToolCallRequest | RpcHostToolCancelRequest | RpcHostUriRequest | RpcHostUriCancelRequest | object) => Promise<void>;
22
22
  export declare function shouldEmitRpcTitlesForTest(): boolean;
23
23
  /**
24
24
  * Cancellation commands bypass the ordered serial chain because they must
@@ -63,6 +63,7 @@ export declare function isFastLaneRpcCommand(type: RpcCommand["type"]): boolean;
63
63
  */
64
64
  export declare function createRpcCommandScheduler(run: (command: RpcCommand) => Promise<void>, track: (task: Promise<void>) => void): {
65
65
  dispatch: (command: RpcCommand) => void;
66
+ close: () => void;
66
67
  };
67
68
  export { RpcListenRefusedError } from "./rpc-socket-security";
68
69
  /**
@@ -4,7 +4,7 @@
4
4
  * Commands are sent as JSON lines on stdin.
5
5
  * Responses and events are emitted as JSON lines on stdout.
6
6
  */
7
- import type { AgentMessage, AgentToolResult, ThinkingLevel } from "@gajae-code/agent-core";
7
+ import type { AgentMessage, AgentToolResult, ResolvedThinkingLevel, ThinkingLevel } from "@gajae-code/agent-core";
8
8
  import type { CompactionResult } from "@gajae-code/agent-core/compaction";
9
9
  import type { Effort, ImageContent, Model } from "@gajae-code/ai";
10
10
  import type { BashResult } from "../../exec/bash-executor";
@@ -69,6 +69,12 @@ export type RpcCommand = {
69
69
  type: "set_model";
70
70
  provider: string;
71
71
  modelId: string;
72
+ } | {
73
+ id?: string;
74
+ type: "set_default_model_selection";
75
+ provider: string;
76
+ modelId: string;
77
+ thinkingLevel?: ResolvedThinkingLevel;
72
78
  } | {
73
79
  id?: string;
74
80
  type: "cycle_model";
@@ -278,6 +284,16 @@ export type RpcResponse = {
278
284
  command: "set_model";
279
285
  success: true;
280
286
  data: Model;
287
+ } | {
288
+ id?: string;
289
+ type: "response";
290
+ command: "set_default_model_selection";
291
+ success: true;
292
+ data: {
293
+ provider: string;
294
+ modelId: string;
295
+ thinkingLevel: ResolvedThinkingLevel;
296
+ };
281
297
  } | {
282
298
  id?: string;
283
299
  type: "response";
@@ -1,9 +1,8 @@
1
1
  import type { AgentMessage } from "@gajae-code/agent-core";
2
2
  import type { Model } from "@gajae-code/ai";
3
- import type { Skill } from "../../extensibility/skills";
4
3
  import type { AgentSession } from "../../session/agent-session";
5
- import type { Tool } from "../../tools";
6
4
  import type { theme as Theme } from "../theme/theme";
5
+ export { computeNonMessageTokens, estimateSkillsTokens, estimateToolSchemaTokens, } from "../../session/context-estimation";
7
6
  type CategoryId = "systemPrompt" | "systemContext" | "rules" | "tools" | "skills" | "messages" | "lastUserTurn";
8
7
  interface CategoryInfo {
9
8
  id: CategoryId;
@@ -17,25 +16,12 @@ export interface ContextBreakdown {
17
16
  contextWindow: number;
18
17
  categories: CategoryInfo[];
19
18
  lastUserTurnTokens: number;
20
- usedTokens: number;
19
+ estimatedCategoryTotal: number;
20
+ usedTokens: number | null;
21
+ source: "provider_anchor" | "heuristic" | "unknown";
21
22
  autoCompactBufferTokens: number;
22
23
  freeTokens: number;
23
24
  }
24
- export declare function estimateSkillsTokens(skills: readonly Skill[]): number;
25
- export declare function estimateToolSchemaTokens(tools: ReadonlyArray<Pick<Tool, "name" | "description" | "parameters">>): number;
26
- /**
27
- * Compute just the NON-MESSAGE token total: system prompt (with its skills
28
- * section subtracted, since skills are tokenized separately) + system context
29
- * (the rest of the system-prompt array) + tools + skills.
30
- *
31
- * Exposed so callers like `StatusLineComponent` can cache the non-message
32
- * total separately from the message total. Non-message inputs (skills,
33
- * tools, system prompt) change rarely; the message list grows on every
34
- * streaming turn. Splitting the two lets the caller refresh each on its own
35
- * cadence — non-message recomputed only when the inputs identity changes,
36
- * messages walked incrementally as new entries append.
37
- */
38
- export declare function computeNonMessageTokens(session: AgentSession): number;
39
25
  /**
40
26
  * Compute a breakdown of estimated context usage by category for the active
41
27
  * session and model.
@@ -48,4 +34,3 @@ export declare function computeContextBreakdown(session: AgentSession, options?:
48
34
  * lines pairing the grid (left) with the legend (right).
49
35
  */
50
36
  export declare function renderContextUsage(breakdown: ContextBreakdown, theme: typeof Theme): string;
51
- export {};
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * Modes use this class and add their own I/O layer on top.
14
14
  */
15
- import { type Agent, type AgentEvent, type AgentMessage, type AgentState, type AgentTool, type StablePrefixSnapshot, ThinkingLevel } from "@gajae-code/agent-core";
15
+ import { type Agent, type AgentEvent, type AgentMessage, type AgentState, type AgentTool, type ResolvedThinkingLevel, type StablePrefixSnapshot, ThinkingLevel } from "@gajae-code/agent-core";
16
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, ToolCall, ToolChoice, Usage, UsageReport } from "@gajae-code/ai";
18
18
  export interface ForkContextSeedMetadata {
@@ -309,6 +309,11 @@ export interface RoleModelCycleResult {
309
309
  thinkingLevel: ThinkingLevel | undefined;
310
310
  role: string;
311
311
  }
312
+ export interface DefaultModelSelectionResult {
313
+ readonly provider: string;
314
+ readonly modelId: string;
315
+ readonly thinkingLevel: ResolvedThinkingLevel;
316
+ }
312
317
  /** Session statistics for /session command */
313
318
  export interface SessionStats {
314
319
  sessionFile: string | undefined;
@@ -854,6 +859,7 @@ export declare class AgentSession {
854
859
  setModelTemporary(model: Model, thinkingLevel?: ThinkingLevel, options?: {
855
860
  persistAsSessionDefault?: boolean;
856
861
  }): Promise<void>;
862
+ setDefaultModelSelection(model: Model, thinkingLevel: ThinkingLevel | undefined): Promise<DefaultModelSelectionResult>;
857
863
  /**
858
864
  * Cycle to next/previous model.
859
865
  * Uses scoped models (from --models flag) if available, otherwise all available models.
@@ -1108,9 +1114,13 @@ export declare class AgentSession {
1108
1114
  promptText: string;
1109
1115
  onTextDelta?: (delta: string) => void;
1110
1116
  signal?: AbortSignal;
1117
+ /** Defer the successful roster-claim commit until the caller accepts its exchange. */
1118
+ deferRosterCommit?: boolean;
1111
1119
  }): Promise<{
1112
1120
  replyText: string;
1113
1121
  assistantMessage: AssistantMessage;
1122
+ commitRosterClaim?: () => void;
1123
+ releaseRosterClaim?: () => void;
1114
1124
  }>;
1115
1125
  /**
1116
1126
  * Reload the current session from disk.
@@ -1176,6 +1186,9 @@ export declare class AgentSession {
1176
1186
  * otherwise estimates tokens for all messages.
1177
1187
  */
1178
1188
  getContextUsage(): ContextUsage | undefined;
1189
+ getContextUsageObservabilityForTests(): {
1190
+ estimateCount: number;
1191
+ };
1179
1192
  fetchUsageReports(signal?: AbortSignal): Promise<UsageReport[] | null>;
1180
1193
  /**
1181
1194
  * Export session to HTML.
@@ -0,0 +1,31 @@
1
+ import type { Skill } from "../extensibility/skills";
2
+ import type { Tool } from "../tools";
3
+ import type { AgentSession } from "./agent-session";
4
+ export declare function estimateSkillsTokens(skills: readonly Skill[]): number;
5
+ export declare function estimateToolSchemaTokens(tools: ReadonlyArray<Pick<Tool, "name" | "description" | "parameters">>): number;
6
+ /**
7
+ * Compute just the NON-MESSAGE token total: system prompt (with its skills
8
+ * section subtracted, since skills are tokenized separately) + system context
9
+ * (the rest of the system-prompt array) + tools + skills.
10
+ *
11
+ * Exposed so callers like `StatusLineComponent` can cache the non-message
12
+ * total separately from the message total. Non-message inputs (skills,
13
+ * tools, system prompt) change rarely; the message list grows on every
14
+ * streaming turn. Splitting the two lets the caller refresh each on its own
15
+ * cadence — non-message recomputed only when the inputs identity changes,
16
+ * messages walked incrementally as new entries append.
17
+ */
18
+ export declare function computeNonMessageTokens(session: AgentSession): number;
19
+ /**
20
+ * Shared helper for the four non-message token totals. Single source of truth
21
+ * for `computeNonMessageTokens` (session estimates + status-line incremental
22
+ * cache) and `computeContextBreakdown` (/context panel). The split avoids
23
+ * drift between the surfaces — they MUST report the same numbers.
24
+ */
25
+ export declare function computeNonMessageBreakdown(session: AgentSession): {
26
+ rulesTokens: number;
27
+ skillsTokens: number;
28
+ toolsTokens: number;
29
+ systemContextTokens: number;
30
+ systemPromptTokens: number;
31
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.10.0",
4
+ "version": "0.10.1",
5
5
  "description": "Gajae Code CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo and Gajae Code Contributors",
@@ -49,12 +49,12 @@
49
49
  "@agentclientprotocol/sdk": "1.2.1",
50
50
  "@babel/parser": "^7.29.3",
51
51
  "@mozilla/readability": "^0.6.0",
52
- "@gajae-code/stats": "0.10.0",
53
- "@gajae-code/agent-core": "0.10.0",
54
- "@gajae-code/ai": "0.10.0",
55
- "@gajae-code/natives": "0.10.0",
56
- "@gajae-code/tui": "0.10.0",
57
- "@gajae-code/utils": "0.10.0",
52
+ "@gajae-code/stats": "0.10.1",
53
+ "@gajae-code/agent-core": "0.10.1",
54
+ "@gajae-code/ai": "0.10.1",
55
+ "@gajae-code/natives": "0.10.1",
56
+ "@gajae-code/tui": "0.10.1",
57
+ "@gajae-code/utils": "0.10.1",
58
58
  "@puppeteer/browsers": "^2.13.0",
59
59
  "@types/turndown": "5.0.6",
60
60
  "@xterm/headless": "^6.0.0",
package/src/cli.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * CLI entry point — registers all commands explicitly and delegates to the
5
5
  * lightweight CLI runner from pi-utils.
6
6
  */
7
+ import "@gajae-code/utils/postmortem";
7
8
  import { Args, type CliConfig, Command, type CommandEntry, Flags, run } from "@gajae-code/utils/cli";
8
9
  import { APP_NAME, formatBunRuntimeError, MIN_BUN_VERSION, VERSION } from "@gajae-code/utils/dirs";
9
10
  import { runFixtureReport } from "./cli/fixture-report";