@gajae-code/coding-agent 0.5.1 → 0.5.2

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 (98) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +1 -1
  3. package/dist/types/cli/setup-cli.d.ts +8 -1
  4. package/dist/types/commands/setup.d.ts +7 -0
  5. package/dist/types/config/file-lock.d.ts +24 -2
  6. package/dist/types/config/model-registry.d.ts +4 -0
  7. package/dist/types/config/models-config-schema.d.ts +5 -0
  8. package/dist/types/config/settings-schema.d.ts +62 -0
  9. package/dist/types/gjc-runtime/state-writer.d.ts +64 -2
  10. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +10 -0
  11. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +29 -0
  12. package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
  13. package/dist/types/modes/interactive-mode.d.ts +1 -1
  14. package/dist/types/modes/rpc/rpc-mode.d.ts +56 -1
  15. package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
  16. package/dist/types/modes/theme/defaults/index.d.ts +302 -0
  17. package/dist/types/modes/theme/theme.d.ts +1 -0
  18. package/dist/types/modes/types.d.ts +1 -1
  19. package/dist/types/session/history-storage.d.ts +2 -2
  20. package/dist/types/session/session-manager.d.ts +10 -1
  21. package/dist/types/setup/credential-import.d.ts +79 -0
  22. package/dist/types/task/executor.d.ts +1 -0
  23. package/dist/types/task/render.d.ts +1 -1
  24. package/dist/types/tools/subagent-render.d.ts +7 -1
  25. package/dist/types/tools/subagent.d.ts +21 -0
  26. package/dist/types/tools/ultragoal-ask-guard.d.ts +5 -0
  27. package/dist/types/web/search/index.d.ts +4 -4
  28. package/dist/types/web/search/provider.d.ts +16 -20
  29. package/dist/types/web/search/providers/base.d.ts +2 -1
  30. package/dist/types/web/search/providers/openai-compatible.d.ts +9 -0
  31. package/dist/types/web/search/types.d.ts +14 -2
  32. package/package.json +7 -7
  33. package/scripts/build-binary.ts +7 -0
  34. package/src/cli/args.ts +2 -0
  35. package/src/cli/fast-help.ts +2 -0
  36. package/src/cli/setup-cli.ts +138 -3
  37. package/src/commands/setup.ts +5 -1
  38. package/src/commands/ultragoal.ts +3 -1
  39. package/src/config/file-lock-gc.ts +14 -2
  40. package/src/config/file-lock.ts +54 -12
  41. package/src/config/model-profile-activation.ts +15 -3
  42. package/src/config/model-profiles.ts +15 -15
  43. package/src/config/model-registry.ts +21 -1
  44. package/src/config/models-config-schema.ts +1 -0
  45. package/src/config/settings-schema.ts +62 -0
  46. package/src/defaults/gjc/skills/ultragoal/SKILL.md +30 -8
  47. package/src/gjc-runtime/deep-interview-recorder.ts +40 -0
  48. package/src/gjc-runtime/launch-tmux.ts +3 -4
  49. package/src/gjc-runtime/ralplan-runtime.ts +174 -12
  50. package/src/gjc-runtime/state-runtime.ts +2 -1
  51. package/src/gjc-runtime/state-writer.ts +254 -7
  52. package/src/gjc-runtime/tmux-gc.ts +2 -1
  53. package/src/gjc-runtime/ultragoal-guard.ts +155 -0
  54. package/src/gjc-runtime/ultragoal-runtime.ts +1227 -31
  55. package/src/gjc-runtime/workflow-manifest.generated.json +44 -0
  56. package/src/gjc-runtime/workflow-manifest.ts +12 -0
  57. package/src/harness-control-plane/owner.ts +3 -2
  58. package/src/harness-control-plane/rpc-adapter.ts +1 -1
  59. package/src/hooks/skill-state.ts +121 -2
  60. package/src/internal-urls/docs-index.generated.ts +13 -9
  61. package/src/lsp/defaults.json +1 -0
  62. package/src/main.ts +14 -4
  63. package/src/modes/acp/acp-agent.ts +4 -2
  64. package/src/modes/bridge/bridge-mode.ts +2 -1
  65. package/src/modes/components/history-search.ts +5 -2
  66. package/src/modes/components/model-selector.ts +26 -0
  67. package/src/modes/components/provider-onboarding-selector.ts +6 -1
  68. package/src/modes/controllers/selector-controller.ts +80 -1
  69. package/src/modes/interactive-mode.ts +11 -1
  70. package/src/modes/rpc/rpc-mode.ts +132 -18
  71. package/src/modes/shared/agent-wire/command-dispatch.ts +5 -2
  72. package/src/modes/shared/agent-wire/host-tool-bridge.ts +3 -0
  73. package/src/modes/shared/agent-wire/unattended-session.ts +16 -1
  74. package/src/modes/theme/defaults/claude-code.json +100 -0
  75. package/src/modes/theme/defaults/codex.json +100 -0
  76. package/src/modes/theme/defaults/index.ts +6 -0
  77. package/src/modes/theme/defaults/opencode.json +102 -0
  78. package/src/modes/theme/theme.ts +2 -2
  79. package/src/modes/types.ts +1 -1
  80. package/src/prompts/agents/executor.md +5 -2
  81. package/src/sdk.ts +12 -1
  82. package/src/session/agent-session.ts +22 -11
  83. package/src/session/history-storage.ts +32 -11
  84. package/src/session/session-manager.ts +70 -18
  85. package/src/setup/credential-import.ts +429 -0
  86. package/src/skill-state/deep-interview-mutation-guard.ts +2 -1
  87. package/src/task/executor.ts +7 -1
  88. package/src/task/render.ts +18 -7
  89. package/src/tools/ask.ts +4 -2
  90. package/src/tools/cron.ts +1 -1
  91. package/src/tools/subagent-render.ts +119 -29
  92. package/src/tools/subagent.ts +147 -7
  93. package/src/tools/ultragoal-ask-guard.ts +39 -0
  94. package/src/web/search/index.ts +25 -25
  95. package/src/web/search/provider.ts +178 -87
  96. package/src/web/search/providers/base.ts +2 -1
  97. package/src/web/search/providers/openai-compatible.ts +151 -0
  98. package/src/web/search/types.ts +47 -22
package/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.5.2] - 2026-06-15
6
+
7
+ ### Fixed
8
+
9
+ - Prevented `gjc --tmux` partial-launch diagnostics from throwing when stderr is already closed during shutdown.
10
+ - Fixed v0.5.1-style macOS/Linux standalone binaries crashing before the first model request with `Cannot find module '@gajae-code/natives' from '/$bunfs/root/gjc-*'` when pre-prompt context maintenance invokes the native tokenizer.
11
+ - Mapped the retired `codex-standard` model profile name to `codex-medium` during profile activation, **as a fallback only** so a user-defined profile literally named `codex-standard` is never shadowed, letting stale `modelProfile.default: codex-standard` configs reach activation instead of blocking startup after the rebuilt profile catalog.
12
+ - Fixed interactive goal-mode auto-continuation looping `Error: Agent is already processing…` (`AgentBusyError`) while the session is busy. A wedged/orphaned subagent turn — or an in-progress compaction — can leave the session non-idle while the interactive loop is back at `getUserInput()`; the 800 ms continuation timer then fired `prompt()`, threw `AgentBusyError`, surfaced it via `showError`, and re-armed — spamming the error roughly every 800 ms. The continuation now skips and re-arms while `isStreaming`/`isCompacting`, firing only once the session returns to idle.
13
+ - Fixed the built-in `minimax-eco`/`minimax-medium`/`minimax-pro` model profiles 400ing on activation because every role pinned the non-existent `minimax-code/minimax-v3`. All three profiles now pin `minimax-code/minimax-m3`, the canonical `minimax-code` default already present in the bundled models catalog (#656).
14
+ - Fixed the native Stop hook letting a deep-interview run terminalize through the ordinary stop path without crystallizing its distilled interview state. A deep-interview mode-state that would release the Stop block (e.g. `active:true` with a `complete`/`completed`/`inactive` phase) is now held until it has actually persisted a final spec — a `spec_path` that still resolves to a real `.gjc/specs/` artifact — and the public-safe diagnostic points the agent at `gjc deep-interview --write --stage final` (optionally `--handoff ralplan`). The guard is scoped to deep-interview only: explicit abort/cancel phases (`failed`/`cancelled`/`canceled`) and the `active:false` demotion/clear outcome remain legitimate terminals, and no other workflow's stop behavior changes (#674).
15
+
16
+ ### Added
17
+
18
+ - Added three bundled dark TUI migration themes — `claude-code`, `codex`, and `opencode` — whose palettes mirror the Claude Code, OpenAI Codex CLI, and opencode TUIs for easy eye-migration. They join the crustacean defaults (`red-claw` dark, `blue-crab` light) as selectable built-ins via Settings or `/theme`; defaults are unchanged and the new themes keep GJC's default symbol identity. A built-in inventory test now validates every bundled theme against the required `THEME_COLOR_KEYS` token set, name/key equality, var resolution, dark classification, and brand-vs-semantic token separation.
19
+ - Documented and regression-guarded the `gjc --tmux` scroll/mouse profile so WSL/Linux launches are not left guessing about mouse-wheel scrolling. The GJC-managed tmux session already applies `mouse on` (plus `set-clipboard on` and a readable copy-mode `mode-style`) scoped to the GJC session only, on macOS/Linux/WSL alike (only native `win32` skips the tmux launch); a new launch-path test asserts a WSL/Linux `--tmux` launch issues session-scoped `set-option ... mouse on` (never global `set -g`) and that `GJC_MOUSE=off` opts out without dropping the ownership tags. `docs/environment-variables.md` now documents the `--tmux` startup env vars (`GJC_LAUNCH_POLICY`, `GJC_TMUX_SESSION`, `GJC_TMUX_COMMAND`, `GJC_TMUX_PROFILE`, `GJC_MOUSE`) and the WSL/Windows Terminal scroll behavior (tmux copy-mode wheel scroll vs. native scrollback, copy-mode keyboard fallback, and that GJC never modifies tmux sessions you started yourself), and `gjc --help` surfaces `GJC_TMUX_PROFILE`/`GJC_MOUSE` (#650).
20
+ - Added a subagent-scoped `task.serviceTier` setting (default `"inherit"`) so the service tier / fast mode applied to task-tool subagents can be controlled independently of the main session. `"inherit"` keeps the current behavior (the main session tier is copied into each subagent's isolated settings snapshot), while any explicit value (`none`, `priority`, `openai-only`, `claude-only`, …) overrides only the subagent sessions, which already read `serviceTier` from their own settings. Implemented in `createSubagentSettings` with a focused test covering inherit and explicit-override behavior (#664).
21
+
5
22
  ## [0.5.1] - 2026-06-14
6
23
 
7
24
  ### Added
package/README.md CHANGED
@@ -34,4 +34,4 @@ Switching backends mid-session is honoured on the next system-prompt rebuild and
34
34
 
35
35
  ## Red-claw TUI theme
36
36
 
37
- The interactive TUI defaults to the bundled `red-claw` crustacean theme for dark terminals and the bundled `blue-crab` theme for light-appearance terminals, with matching welcome/icon assets. Explicit user theme settings still win; set `theme.dark: red-claw` and `theme.light: blue-crab` in `~/.gjc/agent/config.yml` to pin them.
37
+ The interactive TUI defaults to the bundled `red-claw` crustacean theme for dark terminals and the bundled `blue-crab` theme for light-appearance terminals, with matching welcome/icon assets. Three additional bundled migration themes — `claude-code`, `codex`, and `opencode` — mirror the look of those tools for easy eye-migration and are selectable from Settings or `/theme`. Explicit user theme settings still win; set `theme.dark: red-claw` and `theme.light: blue-crab` in `~/.gjc/agent/config.yml` to pin them.
@@ -1,4 +1,9 @@
1
- export type SetupComponent = "defaults" | "hermes" | "hooks" | "provider" | "python" | "stt";
1
+ /**
2
+ * Setup CLI command handler.
3
+ *
4
+ * Handles `gjc setup [component]` to install the normal defaults or optional feature dependencies.
5
+ */
6
+ export type SetupComponent = "credentials" | "defaults" | "hermes" | "hooks" | "provider" | "python" | "stt";
2
7
  export interface SetupCommandArgs {
3
8
  component: SetupComponent;
4
9
  flags: {
@@ -27,6 +32,8 @@ export interface SetupCommandArgs {
27
32
  gjcCommand?: string;
28
33
  target?: string;
29
34
  profileDir?: string;
35
+ yes?: boolean;
36
+ dryRun?: boolean;
30
37
  };
31
38
  }
32
39
  /**
@@ -93,6 +93,13 @@ export default class Setup extends Command {
93
93
  "models-path": import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
94
94
  description: string;
95
95
  };
96
+ yes: import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
97
+ char: string;
98
+ description: string;
99
+ };
100
+ "dry-run": import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
101
+ description: string;
102
+ };
96
103
  };
97
104
  run(): Promise<void>;
98
105
  }
@@ -8,6 +8,28 @@ export declare function readFileLockInfoForGc(lockDir: string): Promise<{
8
8
  pid: number;
9
9
  timestamp: number;
10
10
  } | null>;
11
- /** @internal */
12
- export declare function removeFileLockDirForGc(lockDir: string): Promise<void>;
11
+ /** Owner identity stamped into a `<file>.lock/info` record. */
12
+ export interface FileLockOwnerToken {
13
+ pid: number;
14
+ timestamp: number;
15
+ }
16
+ /** Outcome of a guarded GC removal attempt (`removeFileLockDirForGc`). */
17
+ export type FileLockGcRemoval = "removed" | "owner_changed" | "missing";
18
+ /**
19
+ * @internal
20
+ * Fail-closed removal of a dead lock dir for GC. Re-reads the on-disk owner
21
+ * token as close to the unlink as possible and only deletes the dir when it
22
+ * STILL holds the exact `{pid, timestamp}` identity the caller observed dead.
23
+ *
24
+ * Closes the prune-time TOCTOU window (#606): between GC's dead re-read/probe
25
+ * and the unlink, a live process can reclaim a stale lock at the same path
26
+ * (`acquireLock` rms the stale dir, then re-`mkdir`s and rewrites `info` with a
27
+ * fresh pid+timestamp). Deleting by path alone would reap that LIVE lock. Any
28
+ * mismatch (`owner_changed`) or absent/unreadable info (`missing` — e.g. a
29
+ * fresh acquirer between `mkdir` and `writeLockInfo`) refuses the delete and
30
+ * leaves the dir intact. POSIX has no atomic compare-and-delete for a
31
+ * directory, so the residual read->unlink window cannot be fully eliminated,
32
+ * but the reclaim-after-stale scenario the issue describes is now guarded.
33
+ */
34
+ export declare function removeFileLockDirForGc(lockDir: string, expected: FileLockOwnerToken): Promise<FileLockGcRemoval>;
13
35
  export declare function withFileLock<T>(filePath: string, fn: () => Promise<T>, options?: FileLockOptions): Promise<T>;
@@ -2,6 +2,7 @@ import { type Api, type AssistantMessageEventStream, type CacheRetention, type C
2
2
  import type { OAuthCredentials, OAuthLoginCallbacks } from "@gajae-code/ai/utils/oauth/types";
3
3
  import { type ThemeColor } from "../modes/theme/theme";
4
4
  import type { AuthStorage } from "../session/auth-storage";
5
+ import type { ActiveSearchModelContext, WebSearchMode } from "../web/search/types";
5
6
  import { type ConfigError, ConfigFile } from "./config-file";
6
7
  import { type CanonicalModelIndex, type CanonicalModelRecord, type CanonicalModelVariant, type ModelEquivalenceConfig } from "./model-equivalence";
7
8
  import { type ModelProfileDefinition } from "./model-profiles";
@@ -87,6 +88,7 @@ export declare const ModelsConfigFile: ConfigFile<{
87
88
  supportsStrictMode?: boolean | undefined;
88
89
  toolStrictMode?: "all_strict" | "none" | undefined;
89
90
  } | undefined;
91
+ webSearch?: "auto" | "off" | "on" | undefined;
90
92
  authHeader?: boolean | undefined;
91
93
  auth?: "apiKey" | "none" | "oauth" | undefined;
92
94
  discovery?: {
@@ -364,6 +366,8 @@ export declare class ModelRegistry {
364
366
  * Get the base URL associated with a provider, if any model defines one.
365
367
  */
366
368
  getProviderBaseUrl(provider: string): string | undefined;
369
+ getProviderWebSearchMode(provider: string): WebSearchMode | undefined;
370
+ getActiveSearchModelContext(model: Model<Api>): ActiveSearchModelContext;
367
371
  /**
368
372
  * Get API key for a model.
369
373
  */
@@ -333,6 +333,11 @@ export declare const ModelsConfigSchema: z.ZodObject<{
333
333
  none: "none";
334
334
  }>>;
335
335
  }, z.core.$strip>>;
336
+ webSearch: z.ZodOptional<z.ZodEnum<{
337
+ auto: "auto";
338
+ off: "off";
339
+ on: "on";
340
+ }>>;
336
341
  authHeader: z.ZodOptional<z.ZodBoolean>;
337
342
  auth: z.ZodOptional<z.ZodEnum<{
338
343
  apiKey: "apiKey";
@@ -86,6 +86,9 @@ interface EnumDef<T extends readonly string[]> {
86
86
  interface ArrayDef<T> {
87
87
  type: "array";
88
88
  default: T[];
89
+ items?: {
90
+ enum: readonly string[];
91
+ };
89
92
  ui?: UiBase;
90
93
  }
91
94
  interface RecordDef<T> {
@@ -956,6 +959,53 @@ export declare const SETTINGS_SCHEMA: {
956
959
  }];
957
960
  };
958
961
  };
962
+ readonly "task.serviceTier": {
963
+ readonly type: "enum";
964
+ readonly values: readonly ["inherit", "none", "auto", "default", "flex", "scale", "priority", "openai-only", "claude-only"];
965
+ readonly default: "inherit";
966
+ readonly ui: {
967
+ readonly tab: "tasks";
968
+ readonly label: "Subagent Service Tier";
969
+ readonly description: 'Service tier applied to task-tool subagents only. "inherit" copies the main session tier; any explicit value overrides it for subagents without touching the main session.';
970
+ readonly options: readonly [{
971
+ readonly value: "inherit";
972
+ readonly label: "Inherit";
973
+ readonly description: "Use the main session's service tier (default)";
974
+ }, {
975
+ readonly value: "none";
976
+ readonly label: "None";
977
+ readonly description: "Omit service_tier for subagents";
978
+ }, {
979
+ readonly value: "auto";
980
+ readonly label: "Auto";
981
+ readonly description: "Use provider default tier selection (OpenAI)";
982
+ }, {
983
+ readonly value: "default";
984
+ readonly label: "Default";
985
+ readonly description: "Standard priority processing (OpenAI)";
986
+ }, {
987
+ readonly value: "flex";
988
+ readonly label: "Flex";
989
+ readonly description: "Flexible capacity tier when available (OpenAI)";
990
+ }, {
991
+ readonly value: "scale";
992
+ readonly label: "Scale";
993
+ readonly description: "Scale Tier credits when available (OpenAI)";
994
+ }, {
995
+ readonly value: "priority";
996
+ readonly label: "Priority";
997
+ readonly description: "Priority on every supported provider (OpenAI `service_tier`, Anthropic fast mode)";
998
+ }, {
999
+ readonly value: "openai-only";
1000
+ readonly label: "Priority (OpenAI only)";
1001
+ readonly description: "Priority on OpenAI/OpenAI-Codex requests; ignored elsewhere";
1002
+ }, {
1003
+ readonly value: "claude-only";
1004
+ readonly label: "Priority (Claude only)";
1005
+ readonly description: "Anthropic fast mode on direct Claude requests; ignored elsewhere (incl. Bedrock/Vertex)";
1006
+ }];
1007
+ };
1008
+ };
959
1009
  readonly "retry.enabled": {
960
1010
  readonly type: "boolean";
961
1011
  readonly default: true;
@@ -2411,6 +2461,18 @@ export declare const SETTINGS_SCHEMA: {
2411
2461
  readonly description: "Enable the web_search tool for web searching";
2412
2462
  };
2413
2463
  };
2464
+ readonly "web_search.fallback": {
2465
+ readonly type: "array";
2466
+ readonly default: string[];
2467
+ readonly items: {
2468
+ readonly enum: readonly ["duckduckgo", "exa", "brave", "jina", "kimi", "zai", "anthropic", "perplexity", "gemini", "codex", "tavily", "parallel", "kagi", "synthetic", "searxng"];
2469
+ };
2470
+ readonly ui: {
2471
+ readonly tab: "tools";
2472
+ readonly label: "Web Search Fallback";
2473
+ readonly description: "Ordered fallback web search providers after the active model native provider";
2474
+ };
2475
+ };
2414
2476
  readonly "browser.enabled": {
2415
2477
  readonly type: "boolean";
2416
2478
  readonly default: true;
@@ -1,4 +1,5 @@
1
- import * as fs from "node:fs/promises";
1
+ import type { Stats } from "node:fs";
2
+ import { type FileLockOptions } from "../config/file-lock";
2
3
  import type { SkillActiveEntry } from "../skill-state/active-state";
3
4
  import { type AuditEntry, type CanonicalGjcWorkflowSkill, type WorkflowStateMutationOwner } from "../skill-state/workflow-state-contract";
4
5
  /**
@@ -55,6 +56,12 @@ export interface StateWriterOptions {
55
56
  cwd?: string;
56
57
  receipt?: StateWriterReceiptContext;
57
58
  audit?: StateWriterAuditContext;
59
+ /**
60
+ * Cross-process lock tuning for read-modify-write paths that route through
61
+ * `withWorkflowStateLock` / `updateJsonAtomic`. Omit for the hardened
62
+ * `withFileLock` defaults.
63
+ */
64
+ lock?: FileLockOptions;
58
65
  }
59
66
  export interface DeleteIfOwnedOptions extends StateWriterOptions {
60
67
  predicate?: (current: unknown) => boolean | Promise<boolean>;
@@ -81,7 +88,7 @@ export interface GenericHardPruneTarget {
81
88
  export interface GenericHardPruneSelectorContext {
82
89
  path: string;
83
90
  category: WriterCategory | string;
84
- stat: Awaited<ReturnType<typeof fs.stat>>;
91
+ stat: Stats;
85
92
  readJson: () => Promise<unknown>;
86
93
  }
87
94
  export type GenericHardPruneSelector = (context: GenericHardPruneSelectorContext) => boolean | Promise<boolean>;
@@ -109,8 +116,63 @@ export declare function detectWorkflowEnvelopeIntegrityMismatch(filePath: string
109
116
  export declare function writeJsonAtomic(targetPath: string, value: unknown, options?: StateWriterOptions): Promise<string>;
110
117
  export declare function writeWorkflowEnvelopeAtomic(targetPath: string, value: unknown, options?: StateWriterOptions): Promise<string>;
111
118
  export declare function writeTextAtomic(targetPath: string, text: string, options?: StateWriterOptions): Promise<string>;
119
+ /**
120
+ * Serialize a read-modify-write (or any multi-step mutation) against concurrent
121
+ * writers of the same `.gjc/**` target. Uses the cross-process directory lock
122
+ * from `withFileLock`, keyed on the resolved file path, so separate CLI/agent
123
+ * processes (e.g. team-mode workers) cannot interleave one writer's read with
124
+ * another writer's write and silently drop the first mutation (issue #646).
125
+ *
126
+ * The lock is advisory: it only protects callers that route through it, so every
127
+ * read-modify-write of a given file MUST acquire this lock for the same resolved
128
+ * path. `atomicWrite`'s temp-file + rename crash-atomicity is preserved; this
129
+ * layers concurrency-atomicity on top without weakening it.
130
+ */
131
+ export declare function withWorkflowStateLock<T>(targetPath: string, fn: () => Promise<T>, options?: StateWriterOptions): Promise<T>;
112
132
  export declare function updateJsonAtomic<T = unknown>(targetPath: string, mutator: (current: T | undefined) => T | Promise<T>, options?: StateWriterOptions): Promise<string>;
113
133
  export declare function appendJsonl(targetPath: string, entry: unknown, options?: StateWriterOptions): Promise<string>;
134
+ export interface AppendJsonlIdempotentOptions extends StateWriterOptions {
135
+ /**
136
+ * Identity key for an entry. Two entries that produce the same non-`undefined`
137
+ * key are duplicates, so only the first is appended. Return `undefined` to opt a
138
+ * candidate out of dedup (it is always appended). Use `key` for the common case
139
+ * where identity reduces to a single string.
140
+ */
141
+ key?: (entry: unknown) => string | undefined;
142
+ /**
143
+ * Equivalence predicate: return `true` when `existing` already represents
144
+ * `candidate`, suppressing the append. Use when identity cannot be reduced to a
145
+ * single string key. When both `key` and `equals` are supplied, `equals` wins.
146
+ */
147
+ equals?: (candidate: unknown, existing: unknown) => boolean;
148
+ }
149
+ export interface AppendJsonlIdempotentResult {
150
+ path: string;
151
+ /** `true` when the entry was written; `false` when an equivalent entry already existed. */
152
+ appended: boolean;
153
+ /** The pre-existing entry that suppressed the append, when `appended` is `false`. */
154
+ duplicate?: unknown;
155
+ }
156
+ /**
157
+ * Append `entry` to a JSONL file only when no equivalent entry already exists —
158
+ * the shared idempotent append primitive (issue #660).
159
+ *
160
+ * `appendJsonl` is a pure append with no dedup, so every recurring "duplicate
161
+ * ledger row" bug (#638, #643, #645) had to be patched with bespoke per-call-site
162
+ * guards. This primitive centralizes the read-check-append cycle: a caller
163
+ * declares identity once via `key` or `equals` instead of re-deriving the lookup
164
+ * at each site.
165
+ *
166
+ * The read-then-append is serialized through the same cross-process workflow lock
167
+ * as `updateJsonAtomic`, so two concurrent idempotent appends cannot both observe
168
+ * "no duplicate" and both write (the #646 TOCTOU that a plain `appendJsonl`
169
+ * preceded by a manual existence check is still exposed to).
170
+ *
171
+ * Scope note: this dedups the *append* only. Call sites whose idempotency must
172
+ * also skip a coupled mutation — e.g. the plan/state rewrite in #643/#645 — still
173
+ * need a whole-operation guard; this primitive is the ledger-level half of that.
174
+ */
175
+ export declare function appendJsonlIdempotent(targetPath: string, entry: unknown, options: AppendJsonlIdempotentOptions): Promise<AppendJsonlIdempotentResult>;
114
176
  export declare function appendText(targetPath: string, text: string, options?: StateWriterOptions): Promise<string>;
115
177
  export declare function createJsonNoClobber(targetPath: string, value: unknown, options?: StateWriterOptions): Promise<string>;
116
178
  export declare function deleteIfOwned(targetPath: string, predicateOrOptions?: ((current: unknown) => boolean | Promise<boolean>) | DeleteIfOwnedOptions): Promise<DeleteResult>;
@@ -5,6 +5,15 @@ export interface UltragoalGuardDiagnostic {
5
5
  message: string;
6
6
  goalId?: string;
7
7
  }
8
+ export interface UltragoalAskBlockDiagnostic {
9
+ active: boolean;
10
+ reason: string;
11
+ source: "absent" | "durable_state" | "durable_state_unreadable" | "ledger" | "goals_json";
12
+ goalsPath?: string;
13
+ ledgerPath?: string;
14
+ goalIds?: string[];
15
+ message: string;
16
+ }
8
17
  export interface CurrentGoalLike {
9
18
  objective: string;
10
19
  status?: string;
@@ -19,6 +28,7 @@ export declare function readUltragoalVerificationState(input: {
19
28
  cwd: string;
20
29
  currentGoal?: CurrentGoalLike | null;
21
30
  }): Promise<UltragoalGuardDiagnostic>;
31
+ export declare function isUltragoalAskBlocked(cwd: string): Promise<UltragoalAskBlockDiagnostic>;
22
32
  export declare function assertCanCompleteCurrentGoal(input: {
23
33
  cwd: string;
24
34
  currentGoal?: CurrentGoalLike | null;
@@ -68,6 +68,8 @@ export interface UltragoalStatusSummary {
68
68
  goals: UltragoalGoal[];
69
69
  }
70
70
  export interface UltragoalCommandResult {
71
+ reviewBlockerGoalIds?: string[];
72
+ createdReviewPlan?: boolean;
71
73
  status: number;
72
74
  stdout?: string;
73
75
  stderr?: string;
@@ -118,6 +120,17 @@ export declare function startNextUltragoalGoal(input: {
118
120
  goal?: UltragoalGoal;
119
121
  allComplete: boolean;
120
122
  }>;
123
+ type SurfaceFamily = "web" | "cli" | "native" | "api-package" | "algorithm-math" | "unknown";
124
+ export declare function normalizeSurfaceToken(value: string): string;
125
+ export declare function surfaceFamily(value: string): SurfaceFamily;
126
+ export interface ReplayProcessHandle {
127
+ readonly exited: Promise<number>;
128
+ kill(signal?: number | NodeJS.Signals): void;
129
+ }
130
+ export declare function waitForReplayProcessWithTimeout(process: ReplayProcessHandle, timeoutMs: number, graceMs?: number): Promise<number>;
131
+ export declare function validateExecutorQaRedTeamEvidenceForReview(cwd: string, executorQa: Record<string, unknown>, options?: {
132
+ mode?: "review";
133
+ }): Promise<void>;
121
134
  export declare function checkpointUltragoalGoal(input: {
122
135
  cwd: string;
123
136
  goalId: string;
@@ -159,5 +172,21 @@ export declare function recordUltragoalReviewBlockers(input: {
159
172
  evidence: string;
160
173
  gjcGoalJson?: string;
161
174
  }): Promise<UltragoalPlan>;
175
+ type UltragoalReviewContractStrength = "strong" | "thin-derived";
176
+ interface UltragoalReviewFinding extends JsonObject {
177
+ severity: "blocker";
178
+ message: string;
179
+ }
180
+ interface UltragoalReviewResult extends JsonObject {
181
+ verdict: "pass" | "fail" | "inconclusive: weak-contract";
182
+ contractStrength: UltragoalReviewContractStrength;
183
+ cleanPassEligible: boolean;
184
+ source: JsonObject;
185
+ findings: UltragoalReviewFinding[];
186
+ artifactValidationSummary: JsonObject;
187
+ weakContractCapApplied: boolean;
188
+ blockerGoalIds?: string[];
189
+ }
190
+ export declare function runUltragoalReview(cwd: string, args: readonly string[]): Promise<UltragoalReviewResult>;
162
191
  export declare function runNativeUltragoalCommand(args: string[], cwd?: string): Promise<UltragoalCommandResult>;
163
192
  export {};
@@ -1,5 +1,5 @@
1
1
  import { Container } from "@gajae-code/tui";
2
- export type ProviderOnboardingAction = "custom-provider-wizard" | "oauth-login" | "api-guide";
2
+ export type ProviderOnboardingAction = "custom-provider-wizard" | "oauth-login" | "import-credentials" | "api-guide";
3
3
  export declare class ProviderOnboardingSelectorComponent extends Container {
4
4
  #private;
5
5
  constructor(onSelect: (action: ProviderOnboardingAction) => void, onCancel: () => void);
@@ -86,7 +86,7 @@ export declare class InteractiveMode implements InteractiveModeContext {
86
86
  retryLoader: Loader | undefined;
87
87
  autoCompactionEscapeHandler?: () => void;
88
88
  retryEscapeHandler?: () => void;
89
- retryCountdownTimer?: ReturnType<typeof setInterval>;
89
+ retryCountdownTimer?: NodeJS.Timeout;
90
90
  unsubscribe?: () => void;
91
91
  onInputCallback?: (input: SubmittedUserInput) => void;
92
92
  optimisticUserMessageSignature: string | undefined;
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import type { ExtensionUIContext, ExtensionUIDialogOptions } from "../../extensibility/extensions";
14
14
  import type { AgentSession } from "../../session/agent-session";
15
- import type { RpcExtensionUIRequest, RpcExtensionUIResponse, RpcHostToolCallRequest, RpcHostToolCancelRequest, RpcHostUriCancelRequest, RpcHostUriRequest, RpcResponse } from "./rpc-types";
15
+ import type { RpcCommand, RpcExtensionUIRequest, RpcExtensionUIResponse, RpcHostToolCallRequest, RpcHostToolCancelRequest, RpcHostUriCancelRequest, RpcHostUriRequest, RpcResponse } from "./rpc-types";
16
16
  export type * from "./rpc-types";
17
17
  export type PendingExtensionRequest = {
18
18
  resolve: (response: RpcExtensionUIResponse) => void;
@@ -20,6 +20,61 @@ export type PendingExtensionRequest = {
20
20
  };
21
21
  type RpcOutput = (obj: RpcResponse | RpcExtensionUIRequest | RpcHostToolCallRequest | RpcHostToolCancelRequest | RpcHostUriRequest | RpcHostUriCancelRequest | object) => void;
22
22
  export declare function shouldEmitRpcTitlesForTest(): boolean;
23
+ /**
24
+ * Cancellation commands bypass the ordered serial chain because they must
25
+ * interrupt in-flight work — they cannot wait behind the very command they are
26
+ * meant to abort.
27
+ */
28
+ export declare const RPC_CANCELLATION_COMMANDS: ReadonlySet<RpcCommand["type"]>;
29
+ /**
30
+ * Safe read-only commands that bypass the ordered serial chain so they never
31
+ * head-of-line-block behind a long-running ordered command like
32
+ * `bash`/`compact`/`handoff`/`login` (#606, issue 13 — the partial fix only
33
+ * fast-laned cancellation).
34
+ *
35
+ * Every command listed here has a dispatch handler that is **fully synchronous
36
+ * and side-effect-free**: on the single-threaded event loop it runs to
37
+ * completion between the await points of any in-flight ordered command, reading
38
+ * live state without mutating it. Because such a read performs no causal write,
39
+ * jumping ahead of an earlier *queued* ordered command is observably harmless —
40
+ * there is no state change to reorder. Read payloads are additionally
41
+ * snapshotted inside the handler (e.g. `get_messages` returns a shallow copy of
42
+ * `session.messages`) so a fast-lane read can never serialize a half-mutated
43
+ * array that an ordered turn/compaction is rewriting in place.
44
+ *
45
+ * Deliberately excluded (kept ordered): every async/long command and every
46
+ * mutating command. In particular the control-flag setters (`set_thinking_level`,
47
+ * `cycle_thinking_level`, `set_steering_mode`, `set_follow_up_mode`,
48
+ * `set_interrupt_mode`, `set_auto_compaction`, `set_auto_retry`) stay ordered.
49
+ * Their handlers are synchronous, so fast-laning one ahead of an already-queued
50
+ * `prompt`/`bash` would apply the new mode *before* that earlier command runs —
51
+ * the earlier command would then observe the later setter's value, a
52
+ * causal-order (arrival-order) regression. Mutations therefore stay on the
53
+ * chain, and new command types default to ordered (fail-safe).
54
+ */
55
+ export declare const RPC_SAFE_READ_CONTROL_COMMANDS: ReadonlySet<RpcCommand["type"]>;
56
+ /** True when a command may bypass the ordered serial chain and run immediately. */
57
+ export declare function isFastLaneRpcCommand(type: RpcCommand["type"]): boolean;
58
+ /**
59
+ * Schedules inbound RPC commands: fast-lane commands run immediately while
60
+ * everything else runs through a serial chain so causal order is preserved. The
61
+ * read loop never blocks, which is what lets a fast-lane command reach a
62
+ * long-running ordered command instead of being head-of-line-blocked behind it.
63
+ */
64
+ export declare function createRpcCommandScheduler(run: (command: RpcCommand) => Promise<void>, track: (task: Promise<void>) => void): {
65
+ dispatch: (command: RpcCommand) => void;
66
+ };
67
+ export declare class RpcListenRefusedError extends Error {
68
+ constructor(socketPath: string);
69
+ }
70
+ /**
71
+ * Probe whether a unix-domain socket path has a live server accepting
72
+ * connections. Returns `true` when a connection succeeds (a previous owner is
73
+ * still alive), and returns `false` only for known missing/stale endpoints
74
+ * (ENOENT / ECONNREFUSED). Unexpected probe failures fail closed as "alive" so
75
+ * `--listen` startup refuses to unlink a path it could not safely classify.
76
+ */
77
+ export declare function isUnixSocketAlive(socketPath: string): Promise<boolean>;
23
78
  export declare function requestRpcEditor(pendingRequests: Map<string, PendingExtensionRequest>, output: RpcOutput, title: string, prefill?: string, dialogOptions?: ExtensionUIDialogOptions, editorOptions?: {
24
79
  promptStyle?: boolean;
25
80
  }): Promise<string | undefined>;
@@ -14,10 +14,20 @@
14
14
  * Also implements the dispatch-facing {@link RpcUnattendedControlPlane} so the
15
15
  * RPC server can route `negotiate_unattended` + `workflow_gate_response` here.
16
16
  */
17
+ import type { Model } from "@gajae-code/ai";
17
18
  import type { RpcCommand, RpcUnattendedAccepted, RpcUnattendedDeclaration, RpcWorkflowGate, RpcWorkflowGateResolution, RpcWorkflowGateResponse } from "../../rpc/rpc-types";
18
19
  import type { RpcUnattendedControlPlane } from "./command-dispatch";
19
20
  import { type UnattendedAbortHooks, type UnattendedAuditEvent, UnattendedRunController } from "./unattended-run-controller";
20
21
  import { type GateStore, type OpenGateInput } from "./workflow-gate-broker";
22
+ /**
23
+ * Derive an explicit `providerSupportsTokenCostMetrics` capability from the
24
+ * active model so unattended negotiation fails closed when token/cost usage
25
+ * cannot be accounted for (#606). Callers that omit a model — or whose model is
26
+ * configured to suppress streaming usage (`compat.supportsUsageInStreaming:
27
+ * false`) — get `false`, which the controller refuses with
28
+ * `unsupported_budget_metric`.
29
+ */
30
+ export declare function modelSupportsTokenCostMetrics(model: Model | undefined): boolean;
21
31
  /** Minimal surface a skill runtime / ask tool uses to emit a gate and await its answer. */
22
32
  export interface WorkflowGateEmitter {
23
33
  /** True only when unattended mode has been negotiated. */