@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.
- package/CHANGELOG.md +17 -0
- package/README.md +1 -1
- package/dist/types/cli/setup-cli.d.ts +8 -1
- package/dist/types/commands/setup.d.ts +7 -0
- package/dist/types/config/file-lock.d.ts +24 -2
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +62 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +64 -2
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +29 -0
- package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +56 -1
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
- package/dist/types/modes/theme/defaults/index.d.ts +302 -0
- package/dist/types/modes/theme/theme.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -1
- package/dist/types/session/history-storage.d.ts +2 -2
- package/dist/types/session/session-manager.d.ts +10 -1
- package/dist/types/setup/credential-import.d.ts +79 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/render.d.ts +1 -1
- package/dist/types/tools/subagent-render.d.ts +7 -1
- package/dist/types/tools/subagent.d.ts +21 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +5 -0
- package/dist/types/web/search/index.d.ts +4 -4
- package/dist/types/web/search/provider.d.ts +16 -20
- package/dist/types/web/search/providers/base.d.ts +2 -1
- package/dist/types/web/search/providers/openai-compatible.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +14 -2
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/cli/args.ts +2 -0
- package/src/cli/fast-help.ts +2 -0
- package/src/cli/setup-cli.ts +138 -3
- package/src/commands/setup.ts +5 -1
- package/src/commands/ultragoal.ts +3 -1
- package/src/config/file-lock-gc.ts +14 -2
- package/src/config/file-lock.ts +54 -12
- package/src/config/model-profile-activation.ts +15 -3
- package/src/config/model-profiles.ts +15 -15
- package/src/config/model-registry.ts +21 -1
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +62 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +30 -8
- package/src/gjc-runtime/deep-interview-recorder.ts +40 -0
- package/src/gjc-runtime/launch-tmux.ts +3 -4
- package/src/gjc-runtime/ralplan-runtime.ts +174 -12
- package/src/gjc-runtime/state-runtime.ts +2 -1
- package/src/gjc-runtime/state-writer.ts +254 -7
- package/src/gjc-runtime/tmux-gc.ts +2 -1
- package/src/gjc-runtime/ultragoal-guard.ts +155 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +1227 -31
- package/src/gjc-runtime/workflow-manifest.generated.json +44 -0
- package/src/gjc-runtime/workflow-manifest.ts +12 -0
- package/src/harness-control-plane/owner.ts +3 -2
- package/src/harness-control-plane/rpc-adapter.ts +1 -1
- package/src/hooks/skill-state.ts +121 -2
- package/src/internal-urls/docs-index.generated.ts +13 -9
- package/src/lsp/defaults.json +1 -0
- package/src/main.ts +14 -4
- package/src/modes/acp/acp-agent.ts +4 -2
- package/src/modes/bridge/bridge-mode.ts +2 -1
- package/src/modes/components/history-search.ts +5 -2
- package/src/modes/components/model-selector.ts +26 -0
- package/src/modes/components/provider-onboarding-selector.ts +6 -1
- package/src/modes/controllers/selector-controller.ts +80 -1
- package/src/modes/interactive-mode.ts +11 -1
- package/src/modes/rpc/rpc-mode.ts +132 -18
- package/src/modes/shared/agent-wire/command-dispatch.ts +5 -2
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +3 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +16 -1
- package/src/modes/theme/defaults/claude-code.json +100 -0
- package/src/modes/theme/defaults/codex.json +100 -0
- package/src/modes/theme/defaults/index.ts +6 -0
- package/src/modes/theme/defaults/opencode.json +102 -0
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/types.ts +1 -1
- package/src/prompts/agents/executor.md +5 -2
- package/src/sdk.ts +12 -1
- package/src/session/agent-session.ts +22 -11
- package/src/session/history-storage.ts +32 -11
- package/src/session/session-manager.ts +70 -18
- package/src/setup/credential-import.ts +429 -0
- package/src/skill-state/deep-interview-mutation-guard.ts +2 -1
- package/src/task/executor.ts +7 -1
- package/src/task/render.ts +18 -7
- package/src/tools/ask.ts +4 -2
- package/src/tools/cron.ts +1 -1
- package/src/tools/subagent-render.ts +119 -29
- package/src/tools/subagent.ts +147 -7
- package/src/tools/ultragoal-ask-guard.ts +39 -0
- package/src/web/search/index.ts +25 -25
- package/src/web/search/provider.ts +178 -87
- package/src/web/search/providers/base.ts +2 -1
- package/src/web/search/providers/openai-compatible.ts +151 -0
- 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
|
-
|
|
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
|
-
/**
|
|
12
|
-
export
|
|
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
|
|
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:
|
|
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?:
|
|
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. */
|