@gajae-code/coding-agent 0.3.1 → 0.3.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 (61) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/dist/types/cli/args.d.ts +2 -0
  4. package/dist/types/commands/launch.d.ts +6 -0
  5. package/dist/types/config/model-profile-activation.d.ts +30 -0
  6. package/dist/types/config/model-profiles.d.ts +19 -0
  7. package/dist/types/config/model-registry.d.ts +8 -0
  8. package/dist/types/config/model-resolver.d.ts +1 -1
  9. package/dist/types/config/models-config-schema.d.ts +47 -0
  10. package/dist/types/config/settings-schema.d.ts +10 -0
  11. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -1
  12. package/dist/types/main.d.ts +10 -1
  13. package/dist/types/modes/components/custom-provider-wizard.d.ts +10 -0
  14. package/dist/types/modes/components/model-selector.d.ts +6 -1
  15. package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
  16. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  17. package/dist/types/modes/types.d.ts +1 -0
  18. package/dist/types/sdk.d.ts +1 -1
  19. package/dist/types/task/executor.d.ts +1 -0
  20. package/dist/types/tools/hindsight-recall.d.ts +0 -2
  21. package/dist/types/tools/hindsight-reflect.d.ts +0 -2
  22. package/dist/types/tools/hindsight-retain.d.ts +0 -2
  23. package/dist/types/tools/index.d.ts +4 -4
  24. package/package.json +7 -7
  25. package/src/cli/args.ts +10 -0
  26. package/src/commands/launch.ts +8 -0
  27. package/src/config/model-profile-activation.ts +157 -0
  28. package/src/config/model-profiles.ts +155 -0
  29. package/src/config/model-registry.ts +19 -0
  30. package/src/config/model-resolver.ts +3 -2
  31. package/src/config/models-config-schema.ts +36 -0
  32. package/src/config/settings-schema.ts +10 -0
  33. package/src/defaults/gjc/skills/ultragoal/SKILL.md +11 -1
  34. package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +61 -0
  35. package/src/defaults/gjc-defaults.ts +7 -0
  36. package/src/gjc-runtime/ultragoal-runtime.ts +119 -14
  37. package/src/internal-urls/docs-index.generated.ts +6 -9
  38. package/src/main.ts +67 -1
  39. package/src/modes/components/custom-provider-wizard.ts +318 -0
  40. package/src/modes/components/model-selector.ts +108 -18
  41. package/src/modes/components/provider-onboarding-selector.ts +6 -1
  42. package/src/modes/controllers/selector-controller.ts +57 -1
  43. package/src/modes/types.ts +1 -0
  44. package/src/prompts/memories/consolidation.md +1 -1
  45. package/src/prompts/memories/read-path.md +6 -7
  46. package/src/prompts/memories/unavailable.md +2 -2
  47. package/src/prompts/tools/bash.md +1 -1
  48. package/src/prompts/tools/irc.md +1 -1
  49. package/src/prompts/tools/read.md +2 -2
  50. package/src/prompts/tools/recall.md +1 -0
  51. package/src/prompts/tools/reflect.md +1 -0
  52. package/src/prompts/tools/retain.md +1 -0
  53. package/src/sdk.ts +1 -1
  54. package/src/slash-commands/builtin-registry.ts +1 -1
  55. package/src/task/executor.ts +2 -0
  56. package/src/task/index.ts +2 -0
  57. package/src/tools/hindsight-recall.ts +0 -2
  58. package/src/tools/hindsight-reflect.ts +0 -2
  59. package/src/tools/hindsight-retain.ts +0 -2
  60. package/src/tools/index.ts +4 -18
  61. package/src/tools/read.ts +3 -3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.3.2] - 2026-06-05
6
+
7
+ ### Added
8
+
9
+ - Added model profiles with a `--mpreset <profile>` CLI flag and a `/model` selector "Profiles" section that activate a named profile's default model plus per-agent-role model overrides in one step, validating required-provider credentials before applying and surfacing a custom provider onboarding wizard for missing API-compatible providers.
10
+ - Integrated `ai-slop-cleaner` as an internal Ultragoal sub-skill fragment that runs as the mandatory completion-gate cleanup sweep over a story's changed files, reporting blocking and advisory findings without editing code or mutating `.gjc/` state.
11
+
12
+ ### Fixed
13
+
14
+ - Reconciled native Ultragoal commands with workflow mode-state and the HUD: `gjc ultragoal create-goals`, `complete-goals`, `checkpoint`, steering, review-blocker recording, and status now sync `.gjc/state/ultragoal-state.json` plus `skill-active-state.json` from the durable `.gjc/ultragoal` plan/ledger, clearing stale active HUD chips after all goals complete.
15
+ - Forwarded the parent session id when task subagents validate configured role-agent model overrides, preventing session-scoped OAuth providers from being misread as unauthenticated and falling back to the parent chat model.
16
+ - Removed unintended public memory-tool guidance and registration: Hindsight retain/recall/reflect helpers are now compatibility-only, local memory prompt injection no longer advertises `memory://` reads, and regression tests guard the public tool surface.
17
+ - Fixed `read` hashline anchors drifting on truncated reads so the `line+hash` anchors consumed by `edit`/`apply_patch` stay correct when a file is read past the truncation boundary.
18
+
5
19
  ## [0.3.1] - 2026-06-05
6
20
  ### Added
7
21
 
package/README.md CHANGED
@@ -17,7 +17,7 @@ The agent supports three mutually-exclusive memory backends, selected via the `m
17
17
 
18
18
  - `off` (default) — no memory subsystem runs.
19
19
  - `local` — existing rollout-summarisation pipeline; writes `memory_summary.md` and consolidated artifacts under the agent dir.
20
- - `hindsight` — talks to a [Hindsight](https://hindsight.vectorize.io) server (Cloud or self-hosted Docker), retains transcripts every Nth user turn, recalls memories on the first turn of a session, and exposes `retain`, `recall`, and `reflect`.
20
+ - `hindsight` — talks to a [Hindsight](https://hindsight.vectorize.io) server (Cloud or self-hosted Docker). Hindsight uses private backend lifecycle hooks to retain transcripts and recall context; compatibility-only internals remain for legacy backend calls, but GJC does not expose public coding-harness memory tools such as `retain`, `recall`, or `reflect`.
21
21
 
22
22
  ### Hindsight quickstart
23
23
 
@@ -11,6 +11,8 @@ export interface Args {
11
11
  smol?: string;
12
12
  slow?: string;
13
13
  plan?: string;
14
+ mpreset?: string;
15
+ default?: boolean;
14
16
  apiKey?: string;
15
17
  systemPrompt?: string;
16
18
  appendSystemPrompt?: string;
@@ -25,6 +25,12 @@ export default class Index extends Command {
25
25
  plan: import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
26
26
  description: string;
27
27
  };
28
+ mpreset: import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
29
+ description: string;
30
+ };
31
+ default: import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
32
+ description: string;
33
+ };
28
34
  provider: import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
29
35
  description: string;
30
36
  };
@@ -0,0 +1,30 @@
1
+ import type { ThinkingLevel } from "@gajae-code/agent-core";
2
+ import type { Api, Model } from "@gajae-code/ai";
3
+ import type { AgentSession } from "../session/agent-session";
4
+ import { type ModelRegistry } from "./model-registry";
5
+ import type { Settings } from "./settings";
6
+ export interface PrepareModelProfileActivationOptions {
7
+ session: Pick<AgentSession, "model" | "thinkingLevel" | "sessionId">;
8
+ modelRegistry: Pick<ModelRegistry, "getModelProfile" | "getModelProfiles" | "getAvailableModelProfileNames" | "getApiKeyForProvider" | "getAll" | "resolveCanonicalModel" | "getCanonicalVariants" | "getCanonicalId">;
9
+ settings: Pick<Settings, "get">;
10
+ profileName: string;
11
+ }
12
+ export interface PreparedModelProfileActivation {
13
+ profileName: string;
14
+ session: Pick<AgentSession, "model" | "thinkingLevel" | "sessionId" | "setModelTemporary">;
15
+ settings: Pick<Settings, "get" | "override" | "set" | "flush">;
16
+ previousModel: Model<Api> | undefined;
17
+ previousThinkingLevel: ThinkingLevel | undefined;
18
+ previousAgentModelOverrides: Record<string, string>;
19
+ defaultModel: Model<Api> | undefined;
20
+ defaultThinkingLevel: ThinkingLevel | undefined;
21
+ agentModelOverrides: Record<string, string>;
22
+ }
23
+ export declare function formatModelProfileCredentialError(profileName: string, providers: readonly string[]): string;
24
+ export declare function prepareModelProfileActivation(options: PrepareModelProfileActivationOptions): Promise<PreparedModelProfileActivation>;
25
+ export declare function applyPreparedModelProfileActivation(prepared: PreparedModelProfileActivation, options?: {
26
+ persistDefault?: boolean;
27
+ }): Promise<void>;
28
+ export declare function activateModelProfile(options: PrepareModelProfileActivationOptions, applyOptions?: {
29
+ persistDefault?: boolean;
30
+ }): Promise<void>;
@@ -0,0 +1,19 @@
1
+ import type { GjcModelAssignmentTargetId } from "./model-registry";
2
+ import type { ModelsConfig } from "./models-config-schema";
3
+ export type ModelProfileRole = GjcModelAssignmentTargetId;
4
+ export interface ModelProfileDefinition {
5
+ name: string;
6
+ requiredProviders: string[];
7
+ modelMapping: Partial<Record<ModelProfileRole, string>>;
8
+ source: "builtin" | "user";
9
+ }
10
+ export interface ResolvedProfileBinding {
11
+ defaultSelector?: string;
12
+ agentModelOverrides: Partial<Record<Exclude<ModelProfileRole, "default">, string>>;
13
+ }
14
+ export declare function deriveModelProfileMappedProviders(definition: Pick<ModelProfileDefinition, "modelMapping">): string[];
15
+ export declare function aggregateModelProfileRequiredProviders(requiredProviders: readonly string[], definition: Pick<ModelProfileDefinition, "modelMapping">): string[];
16
+ export declare const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[];
17
+ export declare function mergeModelProfiles(userProfiles?: ModelsConfig["profiles"]): Map<string, ModelProfileDefinition>;
18
+ export declare function resolveProfileBindings(definition: ModelProfileDefinition): ResolvedProfileBinding;
19
+ export declare function formatAvailableProfileNames(profiles: ReadonlyMap<string, ModelProfileDefinition>): string;
@@ -4,6 +4,7 @@ import { type ThemeColor } from "../modes/theme/theme";
4
4
  import type { AuthStorage } from "../session/auth-storage";
5
5
  import { type ConfigError, ConfigFile } from "./config-file";
6
6
  import { type CanonicalModelIndex, type CanonicalModelRecord, type CanonicalModelVariant, type ModelEquivalenceConfig } from "./model-equivalence";
7
+ import { type ModelProfileDefinition } from "./model-profiles";
7
8
  import { type Settings } from "./settings";
8
9
  export type { CanonicalModelIndex, CanonicalModelRecord, CanonicalModelVariant, ModelEquivalenceConfig };
9
10
  export declare const kNoAuth = "N/A";
@@ -244,6 +245,10 @@ export declare const ModelsConfigFile: ConfigFile<{
244
245
  overrides?: Record<string, string> | undefined;
245
246
  exclude?: string[] | undefined;
246
247
  } | undefined;
248
+ profiles?: Record<string, {
249
+ required_providers: string[];
250
+ model_mapping: Partial<Record<"architect" | "critic" | "default" | "executor" | "planner", string>>;
251
+ }> | undefined;
247
252
  }>;
248
253
  /** Provider override config (baseUrl, headers, apiKey, compat, transport) without custom models */
249
254
  interface ProviderOverride {
@@ -306,6 +311,9 @@ export declare class ModelRegistry {
306
311
  * Get any error from loading models.json (undefined if no error).
307
312
  */
308
313
  getError(): ConfigError | undefined;
314
+ getModelProfiles(): Map<string, ModelProfileDefinition>;
315
+ getModelProfile(name: string): ModelProfileDefinition | undefined;
316
+ getAvailableModelProfileNames(): string[];
309
317
  applyConfiguredModelBindings(targetSettings: Settings): void;
310
318
  /**
311
319
  * Get all models (built-in + custom).
@@ -128,7 +128,7 @@ export declare function resolveModelOverride(modelPatterns: string[], modelRegis
128
128
  * primary resolution unchanged so the existing error path still surfaces
129
129
  * a meaningful failure downstream.
130
130
  */
131
- export declare function resolveModelOverrideWithAuthFallback(modelPatterns: string[], parentActiveModelPattern: string | undefined, modelRegistry: ModelLookupRegistry & Pick<ModelRegistry, "getApiKey">, settings?: Settings): Promise<{
131
+ export declare function resolveModelOverrideWithAuthFallback(modelPatterns: string[], parentActiveModelPattern: string | undefined, modelRegistry: ModelLookupRegistry & Pick<ModelRegistry, "getApiKey">, settings?: Settings, sessionId?: string): Promise<{
132
132
  model?: Model<Api>;
133
133
  thinkingLevel?: ThinkingLevel;
134
134
  explicitThinkingLevel: boolean;
@@ -53,6 +53,41 @@ export declare const OpenAICompatSchema: z.ZodObject<{
53
53
  none: "none";
54
54
  }>>;
55
55
  }, z.core.$strip>;
56
+ export declare const ProfileRoleSchema: z.ZodEnum<{
57
+ architect: "architect";
58
+ critic: "critic";
59
+ default: "default";
60
+ executor: "executor";
61
+ planner: "planner";
62
+ }>;
63
+ export declare const ProfileModelSelectorSchema: z.ZodString;
64
+ export declare const ProfileModelMappingSchema: z.ZodRecord<z.ZodEnum<{
65
+ architect: "architect";
66
+ critic: "critic";
67
+ default: "default";
68
+ executor: "executor";
69
+ planner: "planner";
70
+ }> & z.core.$partial, z.ZodString>;
71
+ export declare const ProfileDefinitionSchema: z.ZodObject<{
72
+ required_providers: z.ZodArray<z.ZodString>;
73
+ model_mapping: z.ZodRecord<z.ZodEnum<{
74
+ architect: "architect";
75
+ critic: "critic";
76
+ default: "default";
77
+ executor: "executor";
78
+ planner: "planner";
79
+ }> & z.core.$partial, z.ZodString>;
80
+ }, z.core.$strict>;
81
+ export declare const ProfilesSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
82
+ required_providers: z.ZodArray<z.ZodString>;
83
+ model_mapping: z.ZodRecord<z.ZodEnum<{
84
+ architect: "architect";
85
+ critic: "critic";
86
+ default: "default";
87
+ executor: "executor";
88
+ planner: "planner";
89
+ }> & z.core.$partial, z.ZodString>;
90
+ }, z.core.$strict>>;
56
91
  export declare const ModelOverrideSchema: z.ZodObject<{
57
92
  name: z.ZodOptional<z.ZodString>;
58
93
  reasoning: z.ZodOptional<z.ZodBoolean>;
@@ -547,5 +582,17 @@ export declare const ModelsConfigSchema: z.ZodObject<{
547
582
  overrides: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
548
583
  exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
549
584
  }, z.core.$strip>>;
585
+ profiles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
586
+ required_providers: z.ZodArray<z.ZodString>;
587
+ model_mapping: z.ZodRecord<z.ZodEnum<{
588
+ architect: "architect";
589
+ critic: "critic";
590
+ default: "default";
591
+ executor: "executor";
592
+ planner: "planner";
593
+ }> & z.core.$partial, z.ZodString>;
594
+ }, z.core.$strict>>>;
550
595
  }, z.core.$strict>;
551
596
  export type ModelsConfig = z.infer<typeof ModelsConfigSchema>;
597
+ export type ModelProfileConfig = z.infer<typeof ProfileDefinitionSchema>;
598
+ export type ModelProfilesConfig = z.infer<typeof ProfilesSchema>;
@@ -189,6 +189,16 @@ export declare const SETTINGS_SCHEMA: {
189
189
  readonly type: "record";
190
190
  readonly default: Record<string, string>;
191
191
  };
192
+ readonly "modelProfile.default": {
193
+ readonly type: "string";
194
+ readonly default: undefined;
195
+ readonly ui: {
196
+ readonly tab: "model";
197
+ readonly label: "Default Model Profile";
198
+ readonly description: "Model profile applied automatically at startup";
199
+ readonly options: "runtime";
200
+ };
201
+ };
192
202
  readonly modelTags: {
193
203
  readonly type: "record";
194
204
  readonly default: ModelTagsSettings;
@@ -1,4 +1,4 @@
1
- import type { WorkflowHudSummary } from "../skill-state/active-state";
1
+ import { type WorkflowHudSummary } from "../skill-state/active-state";
2
2
  export type UltragoalGjcGoalMode = "aggregate" | "per-story";
3
3
  export type UltragoalGoalStatus = "pending" | "active" | "complete" | "failed" | "blocked" | "review_blocked" | "superseded";
4
4
  export interface UltragoalGoal {
@@ -94,6 +94,7 @@ export declare function computeUltragoalPlanGeneration(input: {
94
94
  export declare function readUltragoalPlan(cwd: string): Promise<UltragoalPlan | null>;
95
95
  export declare function getUltragoalStatus(cwd: string): Promise<UltragoalStatusSummary>;
96
96
  export declare function buildUltragoalHudSummary(summary: UltragoalStatusSummary, latestLedger?: UltragoalLedgerEvent): WorkflowHudSummary;
97
+ export declare function syncUltragoalWorkflowState(cwd: string): Promise<void>;
97
98
  export declare function createUltragoalPlan(input: {
98
99
  cwd: string;
99
100
  brief: string;
@@ -24,10 +24,19 @@ export interface AcpSessionFactoryOptions {
24
24
  sessionDir?: string;
25
25
  authStorage: AuthStorage;
26
26
  modelRegistry: ModelRegistry;
27
- parsedArgs: Pick<Args, "apiKey">;
27
+ parsedArgs: Pick<Args, "apiKey" | "default" | "model" | "mpreset" | "thinking">;
28
28
  rawArgs: string[];
29
29
  createSession: (options: CreateAgentSessionOptions) => Promise<CreateAgentSessionResult>;
30
30
  }
31
+ export declare function applyStartupModelProfiles(args: {
32
+ session: AgentSession;
33
+ settings: Settings;
34
+ modelRegistry: ModelRegistry;
35
+ parsedArgs: Pick<Args, "default" | "model" | "mpreset" | "thinking">;
36
+ startupModel?: CreateAgentSessionOptions["model"];
37
+ startupThinkingLevel?: CreateAgentSessionOptions["thinkingLevel"];
38
+ }): Promise<void>;
39
+ export declare function applyStartupModelProfilesOrExit(args: Parameters<typeof applyStartupModelProfiles>[0]): Promise<void>;
31
40
  /**
32
41
  * Build the per-`session/new` factory used by ACP mode.
33
42
  *
@@ -0,0 +1,10 @@
1
+ import { Container } from "@gajae-code/tui";
2
+ import type { ProviderSetupInput } from "../../setup/provider-onboarding";
3
+ export type CustomProviderCredentialSource = "env" | "literal";
4
+ export type CustomProviderWizardSubmit = ProviderSetupInput;
5
+ export declare class CustomProviderWizardComponent extends Container {
6
+ #private;
7
+ constructor(onSubmit: (input: CustomProviderWizardSubmit) => void, onCancel: () => void, onRender?: () => void);
8
+ setSubmitError(error: string): void;
9
+ handleInput(keyData: string): void;
10
+ }
@@ -23,8 +23,12 @@ export type ModelSelectorSelection = {
23
23
  selector: string;
24
24
  preset: ModelAssignmentPreset;
25
25
  assignments: Record<GjcModelAssignmentTargetId, ThinkingLevel>;
26
+ } | {
27
+ kind: "profile";
28
+ profileName: string;
29
+ setDefault: boolean;
26
30
  };
27
- type RoleSelectCallback = (selection: ModelSelectorSelection) => void;
31
+ type RoleSelectCallback = (selection: ModelSelectorSelection) => void | Promise<void>;
28
32
  /**
29
33
  * Component that renders a canonical model selector with provider tabs.
30
34
  * - Tab/Arrow Left/Right: Switch between provider tabs
@@ -40,5 +44,6 @@ export declare class ModelSelectorComponent extends Container {
40
44
  });
41
45
  handleInput(keyData: string): void;
42
46
  getSearchInput(): Input;
47
+ __testSelectProfile(profileName: string, setDefault: boolean): Promise<void>;
43
48
  }
44
49
  export {};
@@ -1,5 +1,5 @@
1
1
  import { Container } from "@gajae-code/tui";
2
- export type ProviderOnboardingAction = "oauth-login" | "api-guide";
2
+ export type ProviderOnboardingAction = "custom-provider-wizard" | "oauth-login" | "api-guide";
3
3
  export declare class ProviderOnboardingSelectorComponent extends Container {
4
4
  #private;
5
5
  constructor(onSelect: (action: ProviderOnboardingAction) => void, onCancel: () => void);
@@ -15,6 +15,7 @@ export declare class SelectorController {
15
15
  focus: Component;
16
16
  }): void;
17
17
  showProviderOnboarding(): void;
18
+ showCustomProviderWizard(): void;
18
19
  showSettingsSelector(): void;
19
20
  showThemeSelector(): void;
20
21
  showHistorySearch(): void;
@@ -123,6 +123,7 @@ export interface InteractiveModeContext {
123
123
  }): void;
124
124
  showError(message: string): void;
125
125
  showWarning(message: string): void;
126
+ notifyConfigChanged?: () => Promise<void> | void;
126
127
  showNewVersionNotification(newVersion: string): void;
127
128
  clearEditor(): void;
128
129
  updatePendingMessagesDisplay(): void;
@@ -95,7 +95,7 @@ export interface CreateAgentSessionOptions {
95
95
  taskDepth?: number;
96
96
  /** Current role-agent type/name for nested task sessions. */
97
97
  currentAgentType?: string;
98
- /** Parent Hindsight state to alias for subagent memory tools. */
98
+ /** Parent Hindsight state to alias for subagent private memory backend compatibility. */
99
99
  parentHindsightSessionState?: HindsightSessionState;
100
100
  /** Pre-allocated agent identity for IRC routing. Default: "0-Main" for top-level, parentTaskPrefix-derived for sub. */
101
101
  agentId?: string;
@@ -37,6 +37,7 @@ export interface ExecutorOptions {
37
37
  * if the resolved subagent model has no working credentials. See #985.
38
38
  */
39
39
  parentActiveModelPattern?: string;
40
+ parentSessionId?: string;
40
41
  thinkingLevel?: ThinkingLevel;
41
42
  outputSchema?: unknown;
42
43
  /** Parent task recursion depth (0 = top-level, 1 = first child, etc.) */
@@ -14,8 +14,6 @@ export declare class HindsightRecallTool implements AgentTool<typeof hindsightRe
14
14
  query: z.ZodString;
15
15
  }, z.core.$strip>;
16
16
  readonly strict = true;
17
- readonly loadMode = "discoverable";
18
- readonly summary = "Search hindsight memory for relevant prior context";
19
17
  constructor(session: ToolSession);
20
18
  static createIf(session: ToolSession): HindsightRecallTool | null;
21
19
  execute(_id: string, params: HindsightRecallParams, signal?: AbortSignal): Promise<AgentToolResult>;
@@ -16,8 +16,6 @@ export declare class HindsightReflectTool implements AgentTool<typeof hindsightR
16
16
  context: z.ZodOptional<z.ZodString>;
17
17
  }, z.core.$strip>;
18
18
  readonly strict = true;
19
- readonly loadMode = "discoverable";
20
- readonly summary = "Reflect on recent work and write hindsight memory";
21
19
  constructor(session: ToolSession);
22
20
  static createIf(session: ToolSession): HindsightReflectTool | null;
23
21
  execute(_id: string, params: HindsightReflectParams, signal?: AbortSignal): Promise<AgentToolResult>;
@@ -20,8 +20,6 @@ export declare class HindsightRetainTool implements AgentTool<typeof hindsightRe
20
20
  }, z.core.$strip>>;
21
21
  }, z.core.$strip>;
22
22
  readonly strict = true;
23
- readonly loadMode = "discoverable";
24
- readonly summary = "Store important facts in hindsight memory";
25
23
  constructor(session: ToolSession);
26
24
  static createIf(session: ToolSession): HindsightRetainTool | null;
27
25
  execute(_id: string, params: HindsightRetainParams): Promise<AgentToolResult>;
@@ -37,9 +37,6 @@ export * from "./debug";
37
37
  export * from "./eval";
38
38
  export * from "./find";
39
39
  export * from "./gh";
40
- export * from "./hindsight-recall";
41
- export * from "./hindsight-reflect";
42
- export * from "./hindsight-retain";
43
40
  export * from "./image-gen";
44
41
  export * from "./inspect-image";
45
42
  export * from "./irc";
@@ -245,7 +242,10 @@ export declare const DEFAULT_ESSENTIAL_TOOL_NAMES: readonly string[];
245
242
  export declare function computeEssentialBuiltinNames(settings: Settings): string[];
246
243
  /**
247
244
  * Public callable factory map. External callers may invoke `BUILTIN_TOOLS.read(session)` or
248
- * `BUILTIN_TOOLS[name](session)` to construct a tool directly.
245
+ * `BUILTIN_TOOLS[name](session)` to construct a public coding-harness tool directly.
246
+ *
247
+ * Hindsight memory helpers are intentionally excluded: memory is a private backend
248
+ * integration, not a public gajae-code tool surface.
249
249
  */
250
250
  export declare const BUILTIN_TOOLS: Record<string, ToolFactory>;
251
251
  export declare const HIDDEN_TOOLS: Record<string, ToolFactory>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.3.1",
4
+ "version": "0.3.2",
5
5
  "description": "Gajae Code CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://gaebal-gajae.dev",
7
7
  "author": "Yeachan-Heo",
@@ -48,12 +48,12 @@
48
48
  "@agentclientprotocol/sdk": "0.21.0",
49
49
  "@babel/parser": "^7.29.3",
50
50
  "@mozilla/readability": "^0.6.0",
51
- "@gajae-code/stats": "0.3.1",
52
- "@gajae-code/agent-core": "0.3.1",
53
- "@gajae-code/ai": "0.3.1",
54
- "@gajae-code/natives": "0.3.1",
55
- "@gajae-code/tui": "0.3.1",
56
- "@gajae-code/utils": "0.3.1",
51
+ "@gajae-code/stats": "0.3.2",
52
+ "@gajae-code/agent-core": "0.3.2",
53
+ "@gajae-code/ai": "0.3.2",
54
+ "@gajae-code/natives": "0.3.2",
55
+ "@gajae-code/tui": "0.3.2",
56
+ "@gajae-code/utils": "0.3.2",
57
57
  "@puppeteer/browsers": "^2.13.0",
58
58
  "@types/turndown": "5.0.6",
59
59
  "@xterm/headless": "^6.0.0",
package/src/cli/args.ts CHANGED
@@ -17,6 +17,8 @@ export interface Args {
17
17
  smol?: string;
18
18
  slow?: string;
19
19
  plan?: string;
20
+ mpreset?: string;
21
+ default?: boolean;
20
22
  apiKey?: string;
21
23
  systemPrompt?: string;
22
24
  appendSystemPrompt?: string;
@@ -127,6 +129,10 @@ export function parseArgs(args: string[]): Args {
127
129
  result.slow = args[++i];
128
130
  } else if (arg === "--plan" && i + 1 < args.length) {
129
131
  result.plan = args[++i];
132
+ } else if (arg === "--mpreset" && i + 1 < args.length) {
133
+ result.mpreset = args[++i];
134
+ } else if (arg === "--default") {
135
+ result.default = true;
130
136
  } else if (arg === "--api-key" && i + 1 < args.length) {
131
137
  result.apiKey = args[++i];
132
138
  } else if (arg === "--system-prompt" && i + 1 < args.length) {
@@ -199,6 +205,10 @@ export function parseArgs(args: string[]): Args {
199
205
  }
200
206
  }
201
207
 
208
+ if (result.default && !result.mpreset) {
209
+ throw new Error("--default requires --mpreset <name>");
210
+ }
211
+
202
212
  return result;
203
213
  }
204
214
 
@@ -36,6 +36,12 @@ export default class Index extends Command {
36
36
  plan: Flags.string({
37
37
  description: "Plan model for architectural planning (or GJC_PLAN_MODEL env)",
38
38
  }),
39
+ mpreset: Flags.string({
40
+ description: "Model profile preset to activate for this session",
41
+ }),
42
+ default: Flags.boolean({
43
+ description: "Persist --mpreset as the default model profile",
44
+ }),
39
45
  provider: Flags.string({
40
46
  description: "Provider to use (legacy; prefer --model)",
41
47
  }),
@@ -136,6 +142,8 @@ export default class Index extends Command {
136
142
  `# Launch in a sibling git worktree\n ${APP_NAME} --worktree`,
137
143
  `# Use different model (fuzzy matching)\n ${APP_NAME} --model opus "Help me refactor this code"`,
138
144
  `# Limit model cycling to specific models\n ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o`,
145
+ `# Activate a model profile for this session\n ${APP_NAME} --mpreset codex-standard`,
146
+ `# Persist a model profile as the default\n ${APP_NAME} --mpreset opencode-go-pro --default`,
139
147
  `# Export a session file to HTML\n ${APP_NAME} --export ~/.gjc/agent/sessions/--path--/session.jsonl`,
140
148
  ];
141
149
 
@@ -0,0 +1,157 @@
1
+ import type { ThinkingLevel } from "@gajae-code/agent-core";
2
+ import type { Api, Model } from "@gajae-code/ai";
3
+ import type { AgentSession } from "../session/agent-session";
4
+ import {
5
+ aggregateModelProfileRequiredProviders,
6
+ formatAvailableProfileNames,
7
+ resolveProfileBindings,
8
+ } from "./model-profiles";
9
+ import { type GjcModelAssignmentTargetId, isAuthenticated, type ModelRegistry } from "./model-registry";
10
+ import { resolveModelRoleValue } from "./model-resolver";
11
+ import type { Settings } from "./settings";
12
+
13
+ export interface PrepareModelProfileActivationOptions {
14
+ session: Pick<AgentSession, "model" | "thinkingLevel" | "sessionId">;
15
+ modelRegistry: Pick<
16
+ ModelRegistry,
17
+ | "getModelProfile"
18
+ | "getModelProfiles"
19
+ | "getAvailableModelProfileNames"
20
+ | "getApiKeyForProvider"
21
+ | "getAll"
22
+ | "resolveCanonicalModel"
23
+ | "getCanonicalVariants"
24
+ | "getCanonicalId"
25
+ >;
26
+ settings: Pick<Settings, "get">;
27
+ profileName: string;
28
+ }
29
+
30
+ export interface PreparedModelProfileActivation {
31
+ profileName: string;
32
+ session: Pick<AgentSession, "model" | "thinkingLevel" | "sessionId" | "setModelTemporary">;
33
+ settings: Pick<Settings, "get" | "override" | "set" | "flush">;
34
+ previousModel: Model<Api> | undefined;
35
+ previousThinkingLevel: ThinkingLevel | undefined;
36
+ previousAgentModelOverrides: Record<string, string>;
37
+ defaultModel: Model<Api> | undefined;
38
+ defaultThinkingLevel: ThinkingLevel | undefined;
39
+ agentModelOverrides: Record<string, string>;
40
+ }
41
+
42
+ export function formatModelProfileCredentialError(profileName: string, providers: readonly string[]): string {
43
+ return `Model profile "${profileName}" requires credentials for: ${providers.join(", ")}. Run /login and configure the missing provider(s), then retry.`;
44
+ }
45
+
46
+ export async function prepareModelProfileActivation(
47
+ options: PrepareModelProfileActivationOptions,
48
+ ): Promise<PreparedModelProfileActivation> {
49
+ const profile = options.modelRegistry.getModelProfile(options.profileName);
50
+ if (!profile) {
51
+ const available = formatAvailableProfileNames(options.modelRegistry.getModelProfiles());
52
+ throw new Error(`Unknown model profile "${options.profileName}". Available profiles: ${available}`);
53
+ }
54
+
55
+ const missingProviders: string[] = [];
56
+ for (const provider of aggregateModelProfileRequiredProviders(profile.requiredProviders, profile)) {
57
+ const apiKey = await options.modelRegistry.getApiKeyForProvider(provider, options.session.sessionId);
58
+ if (!isAuthenticated(apiKey)) {
59
+ missingProviders.push(provider);
60
+ }
61
+ }
62
+ if (missingProviders.length > 0) {
63
+ throw new Error(formatModelProfileCredentialError(options.profileName, missingProviders));
64
+ }
65
+
66
+ const availableModels = options.modelRegistry.getAll();
67
+ const bindings = resolveProfileBindings(profile);
68
+ const resolvedDefault = bindings.defaultSelector
69
+ ? resolveModelRoleValue(bindings.defaultSelector, availableModels, {
70
+ settings: options.settings as Settings,
71
+ modelRegistry: options.modelRegistry,
72
+ })
73
+ : undefined;
74
+ if (bindings.defaultSelector && !resolvedDefault?.model) {
75
+ throw new Error(
76
+ `Model profile "${options.profileName}" default selector did not resolve: ${bindings.defaultSelector}`,
77
+ );
78
+ }
79
+
80
+ const agentModelOverrides: Record<string, string> = {};
81
+ for (const [role, selector] of Object.entries(bindings.agentModelOverrides) as [
82
+ GjcModelAssignmentTargetId,
83
+ string,
84
+ ][]) {
85
+ const resolved = resolveModelRoleValue(selector, availableModels, {
86
+ settings: options.settings as Settings,
87
+ modelRegistry: options.modelRegistry,
88
+ });
89
+ if (!resolved.model) {
90
+ throw new Error(`Model profile "${options.profileName}" ${role} selector did not resolve: ${selector}`);
91
+ }
92
+ agentModelOverrides[role] = selector;
93
+ }
94
+
95
+ return {
96
+ profileName: options.profileName,
97
+ session: options.session as PreparedModelProfileActivation["session"],
98
+ settings: options.settings as PreparedModelProfileActivation["settings"],
99
+ previousModel: options.session.model,
100
+ previousThinkingLevel: options.session.thinkingLevel,
101
+ previousAgentModelOverrides: { ...options.settings.get("task.agentModelOverrides") },
102
+ defaultModel: resolvedDefault?.model,
103
+ defaultThinkingLevel: resolvedDefault?.thinkingLevel,
104
+ agentModelOverrides,
105
+ };
106
+ }
107
+
108
+ export async function applyPreparedModelProfileActivation(
109
+ prepared: PreparedModelProfileActivation,
110
+ options: { persistDefault?: boolean } = {},
111
+ ): Promise<void> {
112
+ const previousModel = prepared.previousModel;
113
+ const previousThinkingLevel = prepared.previousThinkingLevel;
114
+ const previousAgentModelOverrides = prepared.previousAgentModelOverrides;
115
+ const previousPersistedDefault = prepared.settings.get("modelProfile.default");
116
+ let modelChanged = false;
117
+ let overridesChanged = false;
118
+ let defaultChanged = false;
119
+
120
+ try {
121
+ if (prepared.defaultModel) {
122
+ await prepared.session.setModelTemporary(prepared.defaultModel, prepared.defaultThinkingLevel);
123
+ modelChanged = true;
124
+ }
125
+ if (Object.keys(prepared.agentModelOverrides).length > 0) {
126
+ prepared.settings.override("task.agentModelOverrides", {
127
+ ...prepared.settings.get("task.agentModelOverrides"),
128
+ ...prepared.agentModelOverrides,
129
+ });
130
+ overridesChanged = true;
131
+ }
132
+ if (options.persistDefault) {
133
+ prepared.settings.set("modelProfile.default", prepared.profileName);
134
+ defaultChanged = true;
135
+ await prepared.settings.flush();
136
+ }
137
+ } catch (error) {
138
+ if (defaultChanged) {
139
+ prepared.settings.set("modelProfile.default", previousPersistedDefault);
140
+ }
141
+ if (overridesChanged) {
142
+ prepared.settings.override("task.agentModelOverrides", previousAgentModelOverrides);
143
+ }
144
+ if (modelChanged && previousModel) {
145
+ await prepared.session.setModelTemporary(previousModel, previousThinkingLevel);
146
+ }
147
+ throw error;
148
+ }
149
+ }
150
+
151
+ export async function activateModelProfile(
152
+ options: PrepareModelProfileActivationOptions,
153
+ applyOptions: { persistDefault?: boolean } = {},
154
+ ): Promise<void> {
155
+ const prepared = await prepareModelProfileActivation(options);
156
+ await applyPreparedModelProfileActivation(prepared, applyOptions);
157
+ }