@gajae-code/coding-agent 0.14.0 → 0.14.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 (33) hide show
  1. package/CHANGELOG.md +21 -3
  2. package/dist/types/modes/components/tool-execution.d.ts +1 -1
  3. package/dist/types/sdk/broker/lifecycle.d.ts +3 -0
  4. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  5. package/dist/types/session/blob-store.d.ts +9 -0
  6. package/dist/types/session/session-manager.d.ts +2 -0
  7. package/dist/types/slash-commands/helpers/usage-report.d.ts +10 -0
  8. package/dist/types/tools/ask-contract.d.ts +7 -7
  9. package/dist/types/tools/descriptor-validation.d.ts +2 -2
  10. package/package.json +7 -7
  11. package/src/config/model-profiles.ts +68 -0
  12. package/src/config/model-registry.ts +20 -2
  13. package/src/defaults/gjc/skills/deep-interview/SKILL.md +14 -8
  14. package/src/internal-urls/docs-index.generated.ts +3 -3
  15. package/src/modes/components/tool-execution.ts +24 -4
  16. package/src/modes/controllers/command-controller.ts +115 -13
  17. package/src/modes/controllers/input-controller.ts +51 -18
  18. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +10 -1
  19. package/src/sdk/broker/lifecycle.ts +21 -0
  20. package/src/sdk/broker/session-index.ts +13 -0
  21. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  22. package/src/sdk/bus/telegram-daemon.ts +29 -6
  23. package/src/sdk/router/session-router.ts +11 -4
  24. package/src/session/account-inventory.ts +25 -21
  25. package/src/session/agent-session.ts +9 -0
  26. package/src/session/blob-store.ts +73 -7
  27. package/src/session/session-manager.ts +104 -15
  28. package/src/slash-commands/builtin-registry.ts +11 -2
  29. package/src/slash-commands/helpers/usage-report.ts +56 -2
  30. package/src/tools/ask-contract.ts +1 -1
  31. package/src/tools/computer.enforcement.test.ts +9 -1
  32. package/src/tools/computer.redteam.test.ts +893 -0
  33. package/src/utils/herdr-pane.ts +17 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.14.1] - 2026-08-18
6
+ - Fixed a resident-cache demotion aborting the turn it was supposed to rescue. The demotion is the salvage that runs *because* the cache already failed, so it materializes a store that is by definition missing blobs — yet it was the one materialize call site that never passed `missingPolicy`, inheriting the fail-closed default. It therefore threw `ResidentBlobMissingError` on the very entry it existed to rescue, the transition never committed, the store stayed broken, and every later turn repeated the same throw out of the `message_end` handler. That handler only `logger.warn`s, so the operator saw a bare `Operation aborted` with every pending tool marked `Tool execution was aborted.` and nothing naming a cache, a blob, or an errno — permanently, until the session was restarted. The salvage now substitutes the same placeholder the persistence path already uses, which never emits a `blob:sha256:` ref, so the invariant the fail-closed policy protects is preserved while the session stays usable. Unrecoverable substitutions are counted in `residentBlobPlaceholderCount` and logged once each with their kind, hash, and the demotion's errno, so silent content loss is visible rather than inferred.
7
+ - Fixed the resident-cache GC reaping the cache of a *running* session, which made long-lived sessions abort every turn with an unexplained `Operation aborted`. The owner lease stores a process start time purely to detect PID reuse, and derived it from `ps -o lstart=` — a zoneless local wall clock rendered from `/etc/localtime` by `ps`, then bound to a zone by `Date.parse` in the reading runtime. Those are two independent zone resolutions: pinning `LC_ALL`/`LANG` stabilized the *format* but not the *instant*, so a reader whose runtime resolved UTC while `ps` rendered UTC+9 computed a start time nine hours off for the same live PID, declared the owner reused, and deleted its instance directory. Observed in the wild: a test process reaped the live instance directories of two running sessions in the same sweep (captured at 32,400,000 ms of skew, exactly the host offset). Each victim then lost the externalized bodies of its large `edit` snapshots, and every later turn died materializing them. The probe now pins `TZ=UTC` on the `ps` child and parses the render explicitly as GMT, so the value is absolute in any reader; leases carry `startTimeBasis: "utc"`, and a lease written before the pin leaves reuse unproven instead of stale, so upgrading cannot reap a live owner once on the way through.
8
+ - A resident text cache demotion now names the OS failure behind it. `ResidentCacheTrustError` lifts the wrapped errno into `causeCode` and a bounded, single-line `causeSummary`; `SessionManager` logs both on `Resident cache trust rejection` and reports `residentCacheDegradedCauseCode` in observability stats; the managed-sidecar disposal warning gains the path-free `causeCode` only, so the cache path it deliberately withholds stays withheld. Previously the record carried `reason` alone, which collapses distinct failures: `blob_create_failed` reads identically whether the instance directory vanished under a live store (`ENOENT`), the process exhausted descriptors (`EMFILE`), or the tree turned read-only (`EACCES`). That mattered in practice — long-running sessions that had externalized a large edit snapshot began aborting every turn once their cache went missing, and the demotion record could not distinguish that from a hostile-path rejection without attaching a debugger to a process that was already failing.
9
+ - Fixed `/usage` and `/usage check` omitting provider limits for stored OAuth accounts. Cache-only snapshots now use the provider's resolved base URL when reading usage, and explicit checks render the successful probe report directly instead of depending on a cache-key-identical readback (#4634).
10
+ - `/usage` shows quota resets again. Canonicalizing multi-account management (`364f14022`) rewired the interactive `/usage` handler from the graphical panel to the account-inventory text view, and that view rendered only `label: N% used (M% left)` — no bars, no reset countdown — leaving the command unable to answer when a quota comes back and stranding `handleUsageCommand`/`renderUsageReports` as unreachable code. Plain `/usage` in the TUI renders the panel again, sourced from the same cache-only inventory snapshot the text view reads, so the cache-only contract is preserved and no fetch or probe is reintroduced; `/usage check` keeps the text path, where the per-credential health verdict is the point. Account rows on every surface (TUI, ACP, Telegram) now carry `resets in <countdown> (<absolute time>)`, and the panel itself gained multi-account reset lines, hour-precision countdowns past 48h (`6d 14h`, previously rounded to a bare `7d` at anywhere from 6.6 to 7.4 days), and set-aware account-label truncation so pooled credentials sharing a domain no longer collapse into identical columns.
11
+ - Fixed resume listing scaling its read-syscall count with total transcript bytes. The trailing `header_patch` scan walks back to BOF whenever `cwd`/`title` stay unresolved (#3633), which is the common case because only `/rename` and workspace moves ever emit a patch; because the scan borrowed the caller's 4 KiB prefix buffer, that walk cost one `read` per 4 KiB of every candidate transcript on each `--resume`, `--continue`, and picker open. The scan now owns a 64 KiB buffer, so the same bytes are covered in ~16x fewer syscalls. Measured on a real 31-session workspace holding 105 MB of transcripts (largest 41 MB): 25,715 reads / 61.9 s before, 1,652 reads / 0.5 s after, with all 24 recovered titles unchanged. Buried-title recovery, the bytes examined, the `header_patch` marker prefilter, and listing results are unchanged.
12
+
13
+ - Fixed Telegram forum topics freezing after the identity header: an attached, trusted session whose topic-host lease expired (20 s `HEARTBEAT_TTL_MS`) could never renew it, because `renewActiveTopicLeases` only renewed sessions that already passed the trusted-lease gate, so every later `turn_stream`/`context_update`/tool frame was rejected pre-send with "trusted attachment lease is stale" and the topic never updated again (#4647). A live attachment that still owns its exact logical session and holds an authorized recovery lease may now re-arm its own expired host lease — from the ownership heartbeat and once more before the publication gate — mirroring `acquireLease` admission (expired-but-owned active lease, or a same-owner resume inside the disconnect-grace window, which also covers the incident's persisted `disconnect_grace` record). Dropped sessions, closed endpoints, foreign lease owners, archive-fenced/inactive topics, malformed bindings, and cross-session ownership checks all still fail closed. Daemon generation bumped 169→170.
14
+ - A tool call the agent loop refuses now reports why it was refused. The loop attaches its own failure envelope (`{ failureKind }`) in place of the tool's details, and the TUI still dispatched that envelope at the tool's renderer, which owns only its own detail shape: `search_tool_bm25` threw on `details.tools`, `task` printed `Task result details unavailable`, `resolve` printed `Failed: pending action`, and `write` painted its success card — in every case the rejection text (for example the `\uXXXX`-escaped-arguments rejection that ends a Korean `task` call) never reached the screen. Such a result now renders the same error card a tool without a renderer already produces: the failed status line plus the reason. Results a tool produced itself keep their renderer, including `todo_write`'s own `failureKind`.
15
+ - Discovered oMLX models now keep thinking metadata (`reasoning: true`, `supportsReasoningEffort`, `thinkingFormat: qwen-chat-template`) so `macos-omlx-*` role suffixes (`:low`/`:medium`/`:high`) survive clamp and reach oMLX as `chat_template_kwargs.reasoning_effort`.
16
+ - Added built-in `MACOS LOCAL (OMLX)` model profiles (`macos-omlx-fast`, `macos-omlx-balanced`, `macos-omlx-quality`, `macos-omlx-abliterated-fast`, `macos-omlx-abliterated-balanced`) for oMLX local inference on Apple Silicon Macs with native full context support and single-LLM thinking effort role mappings to eliminate model swap latency.
17
+ - Fixed an HTTP 400 that killed every deep-interview session on the `google-antigravity` provider before the first assistant turn. The Round-0 topology `ask` schema pinned `round` with `z.literal(0)`, which zod serializes as `const: 0` and the Cloud Code Assist normalizer rewrites to a numeric `enum: [0]` — a shape CCA rejects (`TYPE_STRING`). `round` is now pinned with an integer range `[0, 0]` instead, so the wire schema carries `type: integer` with the bounds spilled into the description (the same treatment `ambiguity` already gets) and no numeric enum remains. Runtime contract unchanged: only `0` validates (#4606).
18
+ - The terminal-app integration docs now cite the upstream work that backs each support rating: Gajae Code is proposed for Paseo's in-app ACP provider catalog ([getpaseo/paseo#3471](https://github.com/getpaseo/paseo/pull/3471)) and for Orca's built-in agent registry ([stablyai/orca#15025](https://github.com/stablyai/orca/pull/15025)), while T3 Code has no GJC harness and the integration shape is under discussion upstream ([pingdotgg/t3code#7290](https://github.com/pingdotgg/t3code/discussions/7290)).
19
+ - The README controller section is now generic and SDK-first: it is titled for OpenClaw / Hermes / Grokbot / your own bot, and the copy-paste bootstrap prompt drives GJC through the broker-bound `gjc sdk session` CLI and the bundled [`sdk-skills/`](https://github.com/Yeachan-Heo/gajae-code/tree/main/sdk-skills) procedures instead of the Coordinator MCP setup. The prompt now names the real surface: `list`/`inspect`/`raw query` discovery, `session.create|fork|resume|close` lifecycle with an idempotency key, `send --op-ref` plus `status` reconciliation (one fresh op-ref per logical prompt; `unknown` is uncertainty, not proof of non-execution), `tail --until-idle`, and the `ask.answer` / `workflow.gate_answer` control allowlist. Coordinator MCP is kept as the pointer for event-driven fan-out across worktrees.
20
+ - Every session start under a non-writable cwd (e.g. a Windows console defaulting to `C:\Windows\System32`) no longer dies with an uncaught `EPERM` before any output: `FileGateStore.beginRuntimeInstance` no longer flushes at construction when the store holds no gates and no counters (the runtime instance id rides along with the first real mutation, preserving the documented lazy first-write contract), and `flushState` now runs `mkdirSync` inside the typed write boundary so a genuinely unwritable directory surfaces as `GateStoreWriteError` instead of a raw `ErrnoException` escaping the store abstraction (#4568).
21
+ - Fixed ACP `session/new` failing with `lost exact Router authority` for every symlinked workspace cwd. The broker's session index stores the lifecycle caller's lexical cwd in `locator.repo` (`reconcileReadyScope` re-scopes only that field) while `locator.stateRoot` stays the host process's physical path, because the host derives it from `process.cwd()`, which resolves symlinks; `SessionRouter#readEndpoint` derived the expected state root from `repo` and compared spellings with a plain `path.resolve` equality, so a symlinked cwd (`/home/jun/desk -> /data/Lina-Desk`, macOS `/var -> /private/var`) made the scope test fail on every reconcile, the adopted attachment was retired, and the ACP agent tore down a healthy host after its publication poll expired. The scope test now compares path identity through `resolveEquivalentPath` — the same symlink-equivalent comparison `sameResumeLocator` and the index fence-row predicate already use — so equivalent spellings resolve to `default`/`chat` and the attachment publishes.
22
+ - A cold-spilled tool call whose payload can no longer be recovered no longer breaks the whole session. `rehydrateColdSpillRef` reports an unreadable, hash-mismatched, or unparseable blob by returning the human-readable `[Cold-spill blob unavailable: …]` sentence, and that string landed directly on `toolCall.arguments`. Providers forward it verbatim — Anthropic serializes it into `tool_use.input` and rejects the entire request with `tool_use.input: Input should be a valid dictionary` — so one missing blob made a compacted session permanently unresumable, with an error naming a message index rather than the lost payload. Rehydration now enforces the object invariant at the session boundary and degrades just that call to the existing `incompleteArguments` / `"malformed"` contract, preserving the recovered text under `recoveryNotice`, so the agent loop refuses that one call with retryable guidance while the rest of the transcript still loads.
23
+ - `/copy` no longer throws when the most recent assistant turn carries a persisted `toolCall.arguments` of `null` (written by an earlier cold-spill eviction path). The last-bash-command lookup read `arguments.command` unguarded, the same shape that made compaction pruning fatal.
24
+
5
25
  ## [0.14.0] - 2026-08-17
6
26
  - Documented how to run GJC inside external agent shells. `README.md` gains a support-rated integration table for [Paseo](https://paseo.sh) (★★★★★ — `gjc setup paseo` writes a conformance-tested ACP provider), [Orca](https://onorca.dev) (★★★★ — GJC runs as a custom CLI agent per worktree), and [T3 Code](https://t3.codes) (★★★ experimental — no GJC harness exists upstream yet), and [`docs/terminal-app-integrations.md`](../../docs/terminal-app-integrations.md) carries the per-host setup, verification, cancel-semantics, and troubleshooting detail. The T3 Code row is deliberately marked unsupported rather than advertising an install command for a bridge that does not exist.
7
27
  - Added the `commandcode-goat` model profile for the Command Code GOAT provider, assigning GLM-5.3 to the default role, DeepSeek V4 Flash to execution, Kimi K3 to planning, GLM-5.2 to criticism, and DeepSeek V4 Pro to architecture.
@@ -37,6 +57,7 @@
37
57
  - `browser` Chrome profile mode (`app.browser: "chrome"`) now defaults an omitted `path` to installed Chrome/Chromium and an omitted `profile_directory` to `"Default"`. `user_data_dir` remains explicit and must be non-default because Chrome 136+ disables remote debugging for default Chrome data directories; Stable/Beta/Dev/Canary/Chromium roots (including trusted Linux environment, Flatpak, and Snap defaults), symlink aliases to them, and non-Chrome browser executables are rejected with remediation instead of timing out or risking cross-brand profile access. Profile and Windows executable discovery use trusted environment sources that ignore repository `.env`, platform path semantics are preserved, executable/profile-root canonicalization races stalled filesystem work against cancellation, the canonical Snap Chromium launcher is admitted only when it resolves to `/usr/bin/snap`, and wrapper-launched Linux Chrome processes are reused through an abort-aware asynchronous `/proc` scan only after kernel executable identity plus exact profile/loopback-CDP guards pass. Cancellation propagates before, during, and after CDP probing, and omitted profile names render as `Default` in the TUI.
38
58
  - Defense-in-depth: when an Anthropic-origin assistant transcript message carrying directly adjacent `thinking`/`redacted_thinking` blocks is persisted, a single bounded warn is emitted per session manager instance — but only in development/test builds, never in production. The diagnostic names only the envelope shape (block count, adjacency presence, provider), never raw thinking text, signatures, redacted payloads, or transcript-path metadata. Storage is never mutated — the send-boundary collapse remains the wire source of truth; this is a read-only observation that helps surface upstream producers of the rejected shape (#4443).
39
59
  ### Fixed
60
+ - The Herdr integration no longer disappears from Herdr's sidebar after a session restarts in the same pane. Herdr keeps the highest report sequence it has accepted per source on the pane's terminal, and that watermark outlives the gjc process that set it, so a counter restarting at zero made every report of the next session look stale: the pane showed no agent at all until the new session happened to out-count its predecessor, which for an idle resumed session could be never. Lifecycle-state and pane-title sequences now start from wall-clock milliseconds (with room for 1000 reports per millisecond), matching what Herdr's own bundled integrations do.
40
61
  - Ouroboros now keeps its own authored base, idle, work-transition, work, and burst frames across Sixel, Kitty, and iTerm2 rendering. Marked iTerm pet drag paths are also discarded in Bash and Python composer modes instead of being inserted literally.
41
62
  - iTerm2 pet animation now stays silent while the TUI is temporarily stopped for an external editor or todo edit, preventing raster writes from corrupting the child terminal session. Its unavailable-capability deadline now begins after iTerm probing starts rather than during initialization work.
42
63
  - Forced Kitty Gajae Pet graphics remain unavailable inside tmux, screen, and zellij; forced Sixel and verified iTerm2 transport keep their existing behavior.
@@ -318,7 +339,6 @@
318
339
  - `--clipboard-transport <auto|native|osc52|ssh>` and `--clipboard-ssh-host <alias>` CLI flags, plus persisted `clipboard.transport` / `clipboard.sshHost` settings (CLI overrides config, config overrides the `auto` default). `ssh` mode routes text copy/paste through `ssh -o BatchMode=yes -o ConnectTimeout=3 -- <host> pbcopy/pbpaste` via argv spawn (never a shell string) with a 5-second hard timeout covering the whole operation lifecycle, a 1 MiB payload cap enforced during streaming (never fully buffered before the check), fatal UTF-8 decoding on the inbound side (invalid bytes are rejected, not silently normalized to U+FFFD), and outbound NUL-byte/unpaired-surrogate rejection. Explicit `ssh` failures (nonzero exit, timeout, invalid host, oversize/invalid payload) surface a sanitized error and never silently fall back to native clipboard or OSC 52 — clipboard payloads are never written to logs or artifacts. `auto`/`native`/`osc52` behave exactly as before. New `app.clipboard.pasteText` action (no default key; command palette only, so it never collides with the existing image-paste binding) reads the configured `ssh` clipboard and inserts it at the cursor.
319
340
  - `gjc sdk session` is the new broker-bound CLI for operating live SDK sessions: `list`, `inspect`, `send`, `status`, and `tail` plus the explicit `raw control|query|global` hatch. Every semantic verb resolves sessions through the SDK broker (validated endpoint identity, credentials used and never printed), output is a versioned credential-free row DTO, `send --wait` polls `turn.prompt_status` to a terminal state without ever cancelling, `tail` replays retained history from the durable checkpoint with `--strict` failing closed on `retention_gap`, and `--until-idle`/`--all-events`/`--cursor` bound and widen the follow. The removed `gjc daemon session` route is not aliased; the new surface is documented in `docs/sdk-session-cli.md` and referenced from `docs/sdk.md`.
320
341
 
321
-
322
342
  ### Changed
323
343
 
324
344
  - Edit cards now keep live calls concise when collapsed and show per-file results, diff statistics, diagnostics availability, and full details consistently when expanded.
@@ -958,7 +978,6 @@
958
978
  ### Changed
959
979
  - The `read` tool is now receipt-by-default: bare and unparseable reads return a bounded receipt (≤50 lines / 10 KiB) with a re-read-with-selector footer only when truncated, `:raw` stays pure verbatim up to a max(2 MiB, spill threshold) ceiling, structural summaries cap unit-granularly at 20 KiB while preserving elision and source-recovery footers, and directories are byte/line capped and never spill. Only an explicit full-content selector (`:raw` or an explicit range) with real content is spill-eligible. Subagent previews now enforce a real byte/code-point cap via per-shape render budgets plus a shape-aware artifact-eligibility tag enforced centrally in output-meta.
960
980
 
961
-
962
981
  ### Fixed
963
982
  - Ultragoal objective ownership no longer treats arbitrary strings that merely mention `goals.json`/`ledger.jsonl` as Ultragoal-owned; only the exact default aggregate objective qualifies for the known-objective path.
964
983
 
@@ -1090,7 +1109,6 @@
1090
1109
 
1091
1110
  - Accepted or declined initial external credential-import decisions now persist across normal restarts and upgrades, suppressing automatic startup and bare `/login` discovery; same-version legacy markers remain compatible and explicit `/provider` import remains available ([#2117](https://github.com/Yeachan-Heo/gajae-code/issues/2117)).
1092
1111
 
1093
-
1094
1112
  ## [0.10.1] - 2026-07-13
1095
1113
 
1096
1114
  ### Added
@@ -1,4 +1,4 @@
1
- import type { AgentTool } from "@gajae-code/agent-core";
1
+ import { type AgentTool } from "@gajae-code/agent-core";
2
2
  import { Container, type TUI } from "@gajae-code/tui";
3
3
  export interface ToolExecutionOptions {
4
4
  showImages?: boolean;
@@ -96,6 +96,9 @@ export declare function sdkSessionCommandOverrideForTest(): {
96
96
  } | undefined;
97
97
  type BrokerIndex = Pick<Broker, "index">;
98
98
  export declare function setProcessIncarnationForTest(broker: Broker, value: ((pid: number) => string | undefined) | undefined): void;
99
+ type ProcessObservation = "alive" | "exited" | "uncertain";
100
+ /** Test seam for the lifecycle-owned process observation boundary. */
101
+ export declare function observeProcessForTest(pid: number, expectedIncarnation: string | undefined, readIncarnation?: (pid: number) => string | undefined): ProcessObservation;
99
102
  /** The child writes this only after its endpoint and semantic ready event are both live. */
100
103
  export declare function writeSessionLifecycleReady(root: string, id: string, effectMarker: string): Promise<void>;
101
104
  export interface LifecycleTranscriptEvidence {
@@ -269,7 +269,7 @@ export declare const SDK_LIFECYCLE_ROUTER_PROTOCOL_VERSION = 1;
269
269
  * Generation 169 delivers every ring-positioned session event live through the
270
270
  * bounded, capability-gated directed subscriber leg used by replay.
271
271
  */
272
- export declare const DAEMON_GENERATION = 169;
272
+ export declare const DAEMON_GENERATION = 170;
273
273
  /**
274
274
  * Serving-compatibility boundary for daemon lifecycle requests. Epoch 7
275
275
  * requires durable publication receipts and acknowledged attachment dispatch,
@@ -25,6 +25,15 @@ export declare class BlobCorruptError extends Error {
25
25
  export declare class ResidentCacheTrustError extends Error {
26
26
  readonly reason: string;
27
27
  readonly path: string;
28
+ /**
29
+ * errno of the OS failure this rejection wrapped (`ENOENT`, `EMFILE`, `EACCES`, …),
30
+ * or `undefined` when the rejection was a pure policy decision with no cause.
31
+ * `reason` alone names the *step* that failed; only this names *why*, so a
32
+ * demotion is diagnosable from a log line instead of requiring a debugger.
33
+ */
34
+ readonly causeCode: string | undefined;
35
+ /** Bounded one-line rendering of the wrapped cause for logs that must not serialize an arbitrary thrown value. */
36
+ readonly causeSummary: string | undefined;
28
37
  constructor(reason: string, path: string, options?: ErrorOptions);
29
38
  }
30
39
  export declare function disposeVerifiedResidentCacheInstanceDir(instanceDir: string, parentDescriptor?: number): void;
@@ -78,6 +78,8 @@ export interface SessionManagerObservabilityStats {
78
78
  residentCacheTrustRejectCount: number;
79
79
  residentCacheWin32FallbackCount: number;
80
80
  residentCacheDegradedReason?: string;
81
+ residentCacheDegradedCauseCode?: string;
82
+ residentBlobPlaceholderCount: number;
81
83
  publicMaterializerCallCount: number;
82
84
  getEntryMaterializerCallCount: number;
83
85
  getBranchMaterializerCallCount: number;
@@ -1,4 +1,14 @@
1
+ import type { UsageLimit, UsageReport } from "@gajae-code/ai/core";
1
2
  import type { SlashCommandRuntime } from "../types";
3
+ /** One limit line: how much is left, and when it comes back. */
4
+ export declare function formatLimitDetail(limit: UsageLimit, nowMs: number): string;
5
+ /**
6
+ * Cache-only usage reports for the interactive panel. Mirrors the plain
7
+ * `/usage` contract exactly — reads the account inventory snapshot, never
8
+ * fetches or probes — so the graphical view can be restored without
9
+ * reintroducing the network call that motivated replacing it.
10
+ */
11
+ export declare function collectCachedUsageReports(runtime: SlashCommandRuntime): UsageReport[];
2
12
  export interface UsageReportOptions {
3
13
  check?: boolean;
4
14
  }
@@ -42,7 +42,7 @@ declare const DeepInterviewMeta: z.ZodUnion<readonly [z.ZodObject<{
42
42
  url: z.ZodOptional<z.ZodString>;
43
43
  excerpt: z.ZodOptional<z.ZodString>;
44
44
  }, z.core.$strict>>>;
45
- round: z.ZodLiteral<0>;
45
+ round: z.ZodNumber;
46
46
  component: z.ZodLiteral<"review-topology">;
47
47
  dimension: z.ZodLiteral<"topology">;
48
48
  intent_contract: z.ZodObject<{
@@ -128,7 +128,7 @@ export declare const askSchema: z.ZodObject<{
128
128
  url?: string | undefined;
129
129
  excerpt?: string | undefined;
130
130
  }[] | undefined;
131
- round: 0;
131
+ round: number;
132
132
  component: "review-topology";
133
133
  dimension: "topology";
134
134
  intent_contract: {
@@ -191,7 +191,7 @@ export declare const askSchema: z.ZodObject<{
191
191
  url?: string | undefined;
192
192
  excerpt?: string | undefined;
193
193
  }[] | undefined;
194
- round: 0;
194
+ round: number;
195
195
  component: "review-topology";
196
196
  dimension: "topology";
197
197
  intent_contract: {
@@ -278,7 +278,7 @@ export declare const topologyAskSchema: z.ZodObject<{
278
278
  url?: string | undefined;
279
279
  excerpt?: string | undefined;
280
280
  }[] | undefined;
281
- round: 0;
281
+ round: number;
282
282
  component: "review-topology";
283
283
  dimension: "topology";
284
284
  intent_contract: {
@@ -341,7 +341,7 @@ export declare const topologyAskSchema: z.ZodObject<{
341
341
  url?: string | undefined;
342
342
  excerpt?: string | undefined;
343
343
  }[] | undefined;
344
- round: 0;
344
+ round: number;
345
345
  component: "review-topology";
346
346
  dimension: "topology";
347
347
  intent_contract: {
@@ -428,7 +428,7 @@ export declare const postTopologyAskSchema: z.ZodObject<{
428
428
  url?: string | undefined;
429
429
  excerpt?: string | undefined;
430
430
  }[] | undefined;
431
- round: 0;
431
+ round: number;
432
432
  component: "review-topology";
433
433
  dimension: "topology";
434
434
  intent_contract: {
@@ -491,7 +491,7 @@ export declare const postTopologyAskSchema: z.ZodObject<{
491
491
  url?: string | undefined;
492
492
  excerpt?: string | undefined;
493
493
  }[] | undefined;
494
- round: 0;
494
+ round: number;
495
495
  component: "review-topology";
496
496
  dimension: "topology";
497
497
  intent_contract: {
@@ -22,7 +22,7 @@ export declare const deferredAskParameters: import("zod").ZodObject<{
22
22
  url?: string | undefined;
23
23
  excerpt?: string | undefined;
24
24
  }[] | undefined;
25
- round: 0;
25
+ round: number;
26
26
  component: "review-topology";
27
27
  dimension: "topology";
28
28
  intent_contract: {
@@ -85,7 +85,7 @@ export declare const deferredAskParameters: import("zod").ZodObject<{
85
85
  url?: string | undefined;
86
86
  excerpt?: string | undefined;
87
87
  }[] | undefined;
88
- round: 0;
88
+ round: number;
89
89
  component: "review-topology";
90
90
  dimension: "topology";
91
91
  intent_contract: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.14.0",
4
+ "version": "0.14.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",
@@ -55,12 +55,12 @@
55
55
  "@agentclientprotocol/sdk": "1.3.0",
56
56
  "@babel/parser": "^7.29.3",
57
57
  "@mozilla/readability": "^0.6.0",
58
- "@gajae-code/stats": "0.14.0",
59
- "@gajae-code/agent-core": "0.14.0",
60
- "@gajae-code/ai": "0.14.0",
61
- "@gajae-code/natives": "0.14.0",
62
- "@gajae-code/tui": "0.14.0",
63
- "@gajae-code/utils": "0.14.0",
58
+ "@gajae-code/stats": "0.14.1",
59
+ "@gajae-code/agent-core": "0.14.1",
60
+ "@gajae-code/ai": "0.14.1",
61
+ "@gajae-code/natives": "0.14.1",
62
+ "@gajae-code/tui": "0.14.1",
63
+ "@gajae-code/utils": "0.14.1",
64
64
  "@puppeteer/browsers": "^2.13.0",
65
65
  "@types/turndown": "5.0.6",
66
66
  "@xterm/headless": "^6.0.0",
@@ -99,6 +99,41 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
99
99
  critic: "openai-codex/gpt-5.6-luna:max",
100
100
  architect: "openai-codex/gpt-5.6-luna:max",
101
101
  }),
102
+ profile("macos-omlx-fast", ["omlx"], {
103
+ default: "omlx/Qwen3.6-35B-A3B-4bit:low",
104
+ executor: "omlx/Qwen3.6-35B-A3B-4bit:low",
105
+ architect: "omlx/Qwen3.6-35B-A3B-4bit:medium",
106
+ planner: "omlx/Qwen3.6-35B-A3B-4bit:medium",
107
+ critic: "omlx/Qwen3.6-35B-A3B-4bit:low",
108
+ }),
109
+ profile("macos-omlx-balanced", ["omlx"], {
110
+ default: "omlx/Qwen3.6-35B-A3B-8bit:medium",
111
+ executor: "omlx/Qwen3.6-35B-A3B-8bit:low",
112
+ architect: "omlx/Qwen3.6-35B-A3B-8bit:high",
113
+ planner: "omlx/Qwen3.6-35B-A3B-8bit:high",
114
+ critic: "omlx/Qwen3.6-35B-A3B-8bit:medium",
115
+ }),
116
+ profile("macos-omlx-quality", ["omlx"], {
117
+ default: "omlx/Qwen3.6-35B-A3B-bf16:high",
118
+ executor: "omlx/Qwen3.6-35B-A3B-bf16:medium",
119
+ architect: "omlx/Qwen3.6-35B-A3B-bf16:high",
120
+ planner: "omlx/Qwen3.6-35B-A3B-bf16:high",
121
+ critic: "omlx/Qwen3.6-35B-A3B-bf16:high",
122
+ }),
123
+ profile("macos-omlx-abliterated-fast", ["omlx"], {
124
+ default: "omlx/Qwen3.8-27B-Abliterated-MLX-4bit:medium",
125
+ executor: "omlx/Qwen3.8-27B-Abliterated-MLX-4bit:low",
126
+ architect: "omlx/Qwen3.8-27B-Abliterated-MLX-4bit:high",
127
+ planner: "omlx/Qwen3.8-27B-Abliterated-MLX-4bit:high",
128
+ critic: "omlx/Qwen3.8-27B-Abliterated-MLX-4bit:medium",
129
+ }),
130
+ profile("macos-omlx-abliterated-balanced", ["omlx"], {
131
+ default: "omlx/Qwen3.8-27B-Abliterated-MLX-6bit:medium",
132
+ executor: "omlx/Qwen3.8-27B-Abliterated-MLX-6bit:low",
133
+ architect: "omlx/Qwen3.8-27B-Abliterated-MLX-6bit:high",
134
+ planner: "omlx/Qwen3.8-27B-Abliterated-MLX-6bit:high",
135
+ critic: "omlx/Qwen3.8-27B-Abliterated-MLX-6bit:medium",
136
+ }),
102
137
  profile("opencodego", ["opencode-go"], {
103
138
  default: "opencode-go/kimi-k3",
104
139
  executor: "opencode-go/deepseek-v4-flash",
@@ -558,12 +593,30 @@ const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
558
593
  "opus-codex": { displayName: "Opus + Codex", providerGroup: "COMBOS" },
559
594
  "codex-opencodego": { displayName: "Codex + OpenCodeGo", providerGroup: "COMBOS" },
560
595
  "fable-opus-codex": { displayName: "Fable + Opus + Codex", providerGroup: "COMBOS" },
596
+ "macos-omlx-fast": { displayName: "4-bit Fast (24GB~36GB+ / Default Ctx)", providerGroup: "MACOS LOCAL (OMLX)" },
597
+ "macos-omlx-balanced": {
598
+ displayName: "8-bit Balanced (64GB~128GB / Default Ctx)",
599
+ providerGroup: "MACOS LOCAL (OMLX)",
600
+ },
601
+ "macos-omlx-quality": {
602
+ displayName: "BF16 Max Quality (M5 Max 128GB / Default Ctx)",
603
+ providerGroup: "MACOS LOCAL (OMLX)",
604
+ },
605
+ "macos-omlx-abliterated-fast": {
606
+ displayName: "4-bit Abliterated Fast (Qwen3.8 27B / Default Ctx)",
607
+ providerGroup: "MACOS LOCAL (OMLX)",
608
+ },
609
+ "macos-omlx-abliterated-balanced": {
610
+ displayName: "6-bit Abliterated Balanced (Qwen3.8 27B / Default Ctx)",
611
+ providerGroup: "MACOS LOCAL (OMLX)",
612
+ },
561
613
  };
562
614
 
563
615
  const PROFILE_GROUP_ORDER = [
564
616
  "CODEX",
565
617
  "OPENCODEGO",
566
618
  "COMMAND CODE GOAT",
619
+ "MACOS LOCAL (OMLX)",
567
620
  "OPEN WEIGHT MODELS (PROVIDER AGNOSTIC)",
568
621
  "CLAUDE",
569
622
  "GLM",
@@ -591,6 +644,14 @@ const OPEN_WEIGHT_PROFILE_ORDER = [
591
644
  "open-weights-all",
592
645
  ] as const;
593
646
  const OPEN_WEIGHT_PROFILE_RANK = new Map<string, number>(OPEN_WEIGHT_PROFILE_ORDER.map((name, index) => [name, index]));
647
+ const MACOS_OMLX_PROFILE_ORDER = [
648
+ "macos-omlx-fast",
649
+ "macos-omlx-balanced",
650
+ "macos-omlx-quality",
651
+ "macos-omlx-abliterated-fast",
652
+ "macos-omlx-abliterated-balanced",
653
+ ] as const;
654
+ const MACOS_OMLX_PROFILE_RANK = new Map<string, number>(MACOS_OMLX_PROFILE_ORDER.map((name, index) => [name, index]));
594
655
 
595
656
  const PROFILE_RECOMMENDATIONS: Record<string, string> = {
596
657
  "openai-codex": "codex-medium",
@@ -606,6 +667,7 @@ const PROFILE_RECOMMENDATIONS: Record<string, string> = {
606
667
  xai: "grok-46-medium",
607
668
  "grok-build": "grok-build-pro",
608
669
  cursor: "cursor-medium",
670
+ omlx: "macos-omlx-balanced",
609
671
  "minimax-code": "minimax-medium",
610
672
  "alibaba-token-plan": "alibaba-token-plan-balanced",
611
673
  };
@@ -649,6 +711,12 @@ export function groupModelProfilesForPresetLanding(
649
711
  (OPEN_WEIGHT_PROFILE_RANK.get(a.name) ?? Number.MAX_SAFE_INTEGER) -
650
712
  (OPEN_WEIGHT_PROFILE_RANK.get(b.name) ?? Number.MAX_SAFE_INTEGER),
651
713
  );
714
+ } else if (group === "MACOS LOCAL (OMLX)") {
715
+ entries.sort(
716
+ (a, b) =>
717
+ (MACOS_OMLX_PROFILE_RANK.get(a.name) ?? Number.MAX_SAFE_INTEGER) -
718
+ (MACOS_OMLX_PROFILE_RANK.get(b.name) ?? Number.MAX_SAFE_INTEGER),
719
+ );
652
720
  } else {
653
721
  entries.sort((a, b) => a.name.localeCompare(b.name));
654
722
  }
@@ -11,6 +11,7 @@ import {
11
11
  type Context,
12
12
  codexContextOverrideKey,
13
13
  createModelManager,
14
+ Effort,
14
15
  enrichModelThinking,
15
16
  getBundledModels,
16
17
  getBundledProviders,
@@ -3042,7 +3043,7 @@ export class ModelRegistry {
3042
3043
  api,
3043
3044
  provider: providerConfig.provider,
3044
3045
  baseUrl: requestBaseUrl,
3045
- reasoning: referenceModel?.reasoning ?? false,
3046
+ reasoning: providerConfig.provider === "omlx" ? true : (referenceModel?.reasoning ?? false),
3046
3047
  thinking: referenceModel?.thinking,
3047
3048
  input: referenceModel?.input ?? ["text"],
3048
3049
  output: referenceModel?.output,
@@ -3069,8 +3070,25 @@ export class ModelRegistry {
3069
3070
  ...referenceModel?.compat,
3070
3071
  supportsStore: false,
3071
3072
  supportsDeveloperRole: false,
3072
- supportsReasoningEffort: false,
3073
+ supportsReasoningEffort: providerConfig.provider === "omlx",
3074
+ ...(providerConfig.provider === "omlx"
3075
+ ? {
3076
+ thinkingFormat: "qwen-chat-template" as const,
3077
+ reasoningContentField: "reasoning_content" as const,
3078
+ }
3079
+ : {}),
3073
3080
  },
3081
+ ...(providerConfig.provider === "omlx"
3082
+ ? {
3083
+ reasoning: true,
3084
+ thinking: {
3085
+ mode: "effort" as const,
3086
+ minLevel: Effort.Low,
3087
+ maxLevel: Effort.High,
3088
+ defaultLevel: Effort.Medium,
3089
+ },
3090
+ }
3091
+ : {}),
3074
3092
  }),
3075
3093
  );
3076
3094
  }
@@ -601,15 +601,20 @@ To dispute: send `{ "id": "<fact-id>", "disputed": true }`. To supersede: send `
601
601
 
602
602
  **`write --input`** — same `{"state":{…}}` shape and same merge semantics as a staged `merge-state` apply, committed in one step. `write --reset --input` replaces the whole `state` with the payload (the locked `intent_contract` is re-attached automatically); use it only for deliberate re-initialization.
603
603
 
604
- ### Step 2f: Check Tiered Confirmation Cadence
604
+ ### Step 2f: Check Continuation Contract (issue #4589)
605
605
 
606
- Confirmation cadence is tiered by round, adopted from ouroboros's ooo interview, while the hard safety cap is retained:
606
+ An ordinary answered round NEVER asks for generic continuation approval. After scoring, persisting the round, and reporting progress, continue directly to the next weakest-dimension question until a legitimate terminal condition occurs. Generic "continue?"-style or continue/cancel/clear choices are NOT interview-round prompts; surfacing a generic continuation ask after an ordinary answered round converts the ambiguity gate into per-answer consent friction and is a contract violation.
607
607
 
608
- - **Rounds 1-3 (auto-continue)**: minimum context gathering — proceed to the next question without a "continue?" prompt.
609
- - **Rounds 4-15 (ask to continue)**: after each round, ask "Continue, or proceed with current clarity ({score}%)?" so the user controls depth.
610
- - **Rounds 16+ (diminishing-returns warning)**: keep asking "Continue?" but prefix a diminishing-returns warning: "We're at {n} rounds (ambiguity: {score}%); each further round yields less. Continue or proceed?"
611
- - **Round 3+ early exit**: still allow immediate exit if the user says "enough", "let's go", "build it".
612
- - **Round 100 (hard cap)**: "Maximum interview rounds reached. Proceeding with current clarity level ({score}%)." The tiered cadence never removes this hard safety cap.
608
+ Legitimate terminal conditions — the ONLY places the interview may stop or ask about stopping:
609
+
610
+ 1. **Threshold + closure gates**: ambiguity ≤ the resolved threshold AND the Phase 4 closure audit and one-sentence Restate gate have passed. Then crystallize the spec and present the Phase 5 execution options.
611
+ 2. **Explicit user exit**: preserve the two exit-intent classes in any session language:
612
+ - **Hard cancellation**: "stop", "cancel", "abort", or equivalent stops immediately at any round and saves state for resume. Never turn a hard cancellation into a clarifying question.
613
+ - **Early proceed**: "enough", "let's go", "build it", or equivalent stops with the early-exit warning from round 3+ when ambiguity > threshold. Before round 3, ask one targeted clarifying question about what the user wants changed instead; do not treat that early-proceed intent as a hard cancellation.
614
+ 3. **Invocation/resume suitability ambiguity only**: the Phase 0.5 continue/cancel/clear choice exists solely at the invocation boundary when existing state already contains rounds, topology, spec, or handoff metadata. It is never re-asked inside an active interview.
615
+ 4. **Bounded continuation safety recovery**: the Round 100 hard cap ("Maximum interview rounds reached. Proceeding with current clarity level ({score}%).") or the runtime's bounded continuation budget being exhausted. These are safety stops, not consent prompts.
616
+
617
+ The user always keeps passive exit control: any answer, option, or free-text reply can carry an exit intent. Hard cancellations are honored immediately; early-proceed intents follow their round-3 safety rule above. Depth control comes from answering the questions themselves or exiting explicitly — not from per-round continue? interruptions.
613
618
 
614
619
  ## Phase 3: Lateral Review Panel (milestone-triggered)
615
620
 
@@ -957,7 +962,7 @@ Why bad: 45% ambiguity means nearly half the requirements are unclear. The mathe
957
962
 
958
963
  <Escalation_And_Stop_Conditions>
959
964
  - **Hard cap at 100 rounds**: Proceed with whatever clarity exists, noting the risk
960
- - **Tiered confirmation cadence**: rounds 1-3 auto-continue, rounds 4-15 ask to continue, rounds 16+ ask with a diminishing-returns warning
965
+ - **Continuation contract**: ordinary answered rounds auto-continue to the next weakest-dimension question with no generic continue/cancel/clear ask; stopping is reserved for threshold + closure gates, explicit user exit, invocation/resume suitability, or bounded safety recovery
961
966
  - **Early exit (round 3+)**: Allow with warning if ambiguity > threshold
962
967
  - **User says "stop", "cancel", "abort"**: Stop immediately, save state for resume
963
968
  - **Ambiguity stalls** (same score +-0.05 for 3 rounds): Activate Ontologist mode to reframe
@@ -976,6 +981,7 @@ Why bad: 45% ambiguity means nearly half the requirements are unclear. The mathe
976
981
  - [ ] Free-text answers passed the Refine gate; dialectic rhythm guard forced a user question after 3 agent-resolved answers; any auto-answer threshold crossing explicitly confirmed
977
982
  - [ ] Closure / Acceptance Guard and the one-sentence Restate gate both passed before crystallization
978
983
  - [ ] Interview reached ambiguity ≤ threshold OR an explicit early exit with warning
984
+ - [ ] Ordinary answered rounds auto-continued to the next weakest-dimension question with no generic continue/cancel/clear ask; any stop matched a legitimate terminal condition (threshold + closure gates, explicit user exit, invocation/resume suitability, or bounded safety recovery)
979
985
  - [ ] Spec persisted to `.gjc/_session-{sessionid}/specs/deep-interview-{slug}.md` exactly via the GJC CLI (no direct `.gjc/` edits without force override), covering every active topology component plus goal/constraints/acceptance criteria/clarity/ontology/transcript
980
986
  - [ ] Spec metadata includes the auto/lateral counters (`auto_researched_rounds`, `auto_answered_rounds`, `lateral_reviews`, `refined_rounds`, `architect_failures`, `lateral_panel_failures`)
981
987
  - [ ] Execution bridge presented via `ask`; execution invoked only after explicit approval through a public workflow entrypoint (never direct implementation); state cleaned up after handoff