@gajae-code/coding-agent 0.2.1 → 0.2.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 (101) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/dist/types/commands/contribution-prep.d.ts +18 -0
  3. package/dist/types/commands/session.d.ts +24 -0
  4. package/dist/types/config/model-registry.d.ts +2 -2
  5. package/dist/types/config/models-config-schema.d.ts +17 -9
  6. package/dist/types/config/settings-schema.d.ts +1 -24
  7. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +15 -0
  8. package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
  9. package/dist/types/gjc-runtime/launch-tmux.d.ts +12 -11
  10. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +25 -0
  11. package/dist/types/gjc-runtime/state-runtime.d.ts +13 -0
  12. package/dist/types/gjc-runtime/team-runtime.d.ts +37 -5
  13. package/dist/types/gjc-runtime/tmux-common.d.ts +41 -0
  14. package/dist/types/gjc-runtime/tmux-sessions.d.ts +17 -0
  15. package/dist/types/goals/runtime.d.ts +3 -9
  16. package/dist/types/goals/state.d.ts +3 -6
  17. package/dist/types/goals/tools/goal-tool.d.ts +1 -69
  18. package/dist/types/modes/components/status-line/types.d.ts +0 -3
  19. package/dist/types/modes/components/status-line.d.ts +0 -3
  20. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  21. package/dist/types/modes/interactive-mode.d.ts +1 -12
  22. package/dist/types/modes/theme/defaults/index.d.ts +0 -2
  23. package/dist/types/modes/theme/theme.d.ts +1 -2
  24. package/dist/types/modes/types.d.ts +1 -7
  25. package/dist/types/session/agent-session.d.ts +2 -0
  26. package/dist/types/session/contribution-prep.d.ts +47 -0
  27. package/dist/types/skill-state/active-state.d.ts +4 -0
  28. package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +6 -1
  29. package/dist/types/skill-state/workflow-hud.d.ts +9 -4
  30. package/dist/types/skill-state/workflow-state-contract.d.ts +34 -0
  31. package/package.json +7 -7
  32. package/src/cli/args.ts +3 -2
  33. package/src/cli.ts +6 -1
  34. package/src/commands/contribution-prep.ts +41 -0
  35. package/src/commands/deep-interview.ts +6 -22
  36. package/src/commands/launch.ts +10 -1
  37. package/src/commands/ralplan.ts +10 -22
  38. package/src/commands/session.ts +150 -0
  39. package/src/commands/state.ts +14 -4
  40. package/src/commands/team.ts +23 -3
  41. package/src/config/model-registry.ts +10 -2
  42. package/src/config/models-config-schema.ts +120 -102
  43. package/src/config/settings-schema.ts +1 -25
  44. package/src/config.ts +1 -1
  45. package/src/defaults/gjc/skills/deep-interview/SKILL.md +14 -13
  46. package/src/defaults/gjc/skills/ralplan/SKILL.md +14 -2
  47. package/src/defaults/gjc/skills/team/SKILL.md +29 -7
  48. package/src/defaults/gjc/skills/ultragoal/SKILL.md +23 -25
  49. package/src/eval/py/prelude.py +1 -1
  50. package/src/gjc-runtime/deep-interview-runtime.ts +279 -0
  51. package/src/gjc-runtime/goal-mode-request.ts +2 -19
  52. package/src/gjc-runtime/launch-tmux.ts +83 -43
  53. package/src/gjc-runtime/ralplan-runtime.ts +460 -0
  54. package/src/gjc-runtime/state-runtime.ts +562 -0
  55. package/src/gjc-runtime/team-runtime.ts +708 -52
  56. package/src/gjc-runtime/tmux-common.ts +119 -0
  57. package/src/gjc-runtime/tmux-sessions.ts +165 -0
  58. package/src/gjc-runtime/ultragoal-guard.ts +6 -3
  59. package/src/gjc-runtime/ultragoal-runtime.ts +5 -4
  60. package/src/goals/runtime.ts +38 -144
  61. package/src/goals/state.ts +36 -7
  62. package/src/goals/tools/goal-tool.ts +15 -172
  63. package/src/hooks/skill-state.ts +31 -12
  64. package/src/internal-urls/docs-index.generated.ts +4 -3
  65. package/src/modes/components/skill-hud/render.ts +4 -0
  66. package/src/modes/components/status-line/segments.ts +5 -16
  67. package/src/modes/components/status-line/types.ts +0 -3
  68. package/src/modes/components/status-line.ts +0 -6
  69. package/src/modes/controllers/command-controller.ts +25 -1
  70. package/src/modes/controllers/input-controller.ts +0 -15
  71. package/src/modes/interactive-mode.ts +18 -219
  72. package/src/modes/theme/defaults/dark-poimandres.json +0 -1
  73. package/src/modes/theme/defaults/light-poimandres.json +0 -1
  74. package/src/modes/theme/theme.ts +0 -6
  75. package/src/modes/types.ts +1 -7
  76. package/src/prompts/goals/goal-continuation.md +1 -4
  77. package/src/prompts/goals/goal-mode-active.md +3 -5
  78. package/src/prompts/system/system-prompt.md +5 -7
  79. package/src/prompts/tools/goal.md +4 -4
  80. package/src/sdk.ts +1 -1
  81. package/src/session/agent-session.ts +18 -0
  82. package/src/session/contribution-prep.ts +320 -0
  83. package/src/skill-state/active-state.ts +38 -0
  84. package/src/skill-state/deep-interview-mutation-guard.ts +88 -24
  85. package/src/skill-state/workflow-hud.ts +23 -5
  86. package/src/skill-state/workflow-state-contract.ts +121 -0
  87. package/src/slash-commands/builtin-registry.ts +24 -12
  88. package/src/task/commands.ts +1 -5
  89. package/src/tools/gh.ts +212 -2
  90. package/src/tools/index.ts +2 -5
  91. package/dist/types/commands/gjc-runtime-bridge.d.ts +0 -30
  92. package/dist/types/commands/question.d.ts +0 -7
  93. package/dist/types/modes/loop-limit.d.ts +0 -22
  94. package/src/commands/gjc-runtime-bridge.ts +0 -227
  95. package/src/commands/question.ts +0 -12
  96. package/src/modes/loop-limit.ts +0 -140
  97. package/src/prompts/commands/orchestrate.md +0 -49
  98. package/src/prompts/goals/goal-budget-limit.md +0 -16
  99. package/src/prompts/tools/create-goal.md +0 -3
  100. package/src/prompts/tools/get-goal.md +0 -3
  101. package/src/prompts/tools/update-goal.md +0 -3
@@ -1,31 +1,19 @@
1
1
  import { Command } from "@gajae-code/utils/cli";
2
- import { syncSkillActiveState } from "../skill-state/active-state";
3
- import { runGjcRuntimeBridgeWithHudSidecar } from "./gjc-runtime-bridge";
2
+ import { runNativeRalplanCommand } from "../gjc-runtime/ralplan-runtime";
4
3
 
5
4
  export default class Ralplan extends Command {
6
- static description = "Run private GJC RALPLAN workflow commands";
5
+ static description = "Run native GJC RALPLAN consensus planning workflow";
7
6
  static strict = false;
8
- static examples = ["$ gjc ralplan --help"];
7
+ static examples = [
8
+ '$ gjc ralplan "<task description>"',
9
+ '$ gjc ralplan --interactive --deliberate "<task description>"',
10
+ '$ gjc ralplan --write --stage planner --stage_n 1 --artifact "<markdown or path>"',
11
+ ];
9
12
 
10
13
  async run(): Promise<void> {
11
- const cwd = process.cwd();
12
- const result = await runGjcRuntimeBridgeWithHudSidecar("ralplan", this.argv, {
13
- cwd,
14
- sidecarSkill: "ralplan",
15
- onHudPayload: payload =>
16
- syncSkillActiveState({
17
- cwd,
18
- skill: "ralplan",
19
- active: payload.active ?? true,
20
- phase: payload.phase,
21
- sessionId: payload.session_id,
22
- threadId: payload.thread_id,
23
- turnId: payload.turn_id,
24
- hud: payload.hud,
25
- source: "gjc-runtime-bridge",
26
- }),
27
- });
28
- if (result.error) process.stderr.write(`${result.error}\n`);
14
+ const result = await runNativeRalplanCommand(this.argv, process.cwd());
15
+ if (result.stdout) process.stdout.write(result.stdout);
16
+ if (result.stderr) process.stderr.write(result.stderr);
29
17
  process.exitCode = result.status;
30
18
  }
31
19
  }
@@ -0,0 +1,150 @@
1
+ import { Args, Command, Flags } from "@gajae-code/utils/cli";
2
+ import {
3
+ attachGjcTmuxSession,
4
+ createGjcTmuxSession,
5
+ listGjcTmuxSessions,
6
+ removeGjcTmuxSession,
7
+ statusGjcTmuxSession,
8
+ } from "../gjc-runtime/tmux-sessions";
9
+
10
+ function writeJson(value: unknown): void {
11
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
12
+ }
13
+
14
+ function writeText(lines: string[]): void {
15
+ process.stdout.write(`${lines.join("\n")}\n`);
16
+ }
17
+
18
+ function writeJsonFailure(error: unknown): void {
19
+ const message = error instanceof Error ? error.message : String(error);
20
+ const [reason = "session_error"] = message.split(":");
21
+ writeJson({ ok: false, reason });
22
+ }
23
+
24
+ interface SessionJsonDto {
25
+ name: string;
26
+ attached: boolean;
27
+ windows: number;
28
+ panes: number;
29
+ bindings: string;
30
+ createdAt: string;
31
+ }
32
+
33
+ function sessionJson(session: SessionJsonDto): SessionJsonDto {
34
+ return {
35
+ name: session.name,
36
+ attached: session.attached,
37
+ windows: session.windows,
38
+ panes: session.panes,
39
+ bindings: session.bindings,
40
+ createdAt: session.createdAt,
41
+ };
42
+ }
43
+
44
+ export default class Session extends Command {
45
+ static description = "List, inspect, attach, and remove tagged GJC-managed tmux sessions";
46
+ static strict = false;
47
+
48
+ static args = {
49
+ action: Args.string({
50
+ description: "list (default), status, create, attach, or remove",
51
+ required: false,
52
+ }),
53
+ session: Args.string({
54
+ description: "Session name for status, attach, or remove",
55
+ required: false,
56
+ }),
57
+ };
58
+
59
+ static flags = {
60
+ json: Flags.boolean({ char: "j", description: "Emit machine-readable JSON", default: false }),
61
+ };
62
+
63
+ static examples = [
64
+ "gjc session list",
65
+ "gjc session create",
66
+ "gjc session status <session>",
67
+ "gjc session attach <session>",
68
+ "gjc session remove <session>",
69
+ ];
70
+
71
+ async run(): Promise<void> {
72
+ const { args, flags } = await this.parse(Session);
73
+ const action = args.action ?? "list";
74
+ const sessionName = args.session;
75
+ const json = flags.json ?? false;
76
+ try {
77
+ if (action === "list") {
78
+ const sessions = listGjcTmuxSessions();
79
+ if (json) {
80
+ writeJson({ ok: true, sessions: sessions.map(sessionJson) });
81
+ return;
82
+ }
83
+ writeText(
84
+ sessions.map(session =>
85
+ [
86
+ session.name,
87
+ `windows=${session.windows}`,
88
+ `attached=${session.attached}`,
89
+ `createdAt=${session.createdAt}`,
90
+ `panes=${session.panes}`,
91
+ `bindings=${session.bindings || "none"}`,
92
+ ].join("\t"),
93
+ ),
94
+ );
95
+ return;
96
+ }
97
+
98
+ if (action === "create") {
99
+ const session = createGjcTmuxSession();
100
+ if (json) {
101
+ writeJson({ ok: true, session: sessionJson(session) });
102
+ return;
103
+ }
104
+ writeText([`created: ${session.name}`]);
105
+ return;
106
+ }
107
+
108
+ if (!sessionName) throw new Error("missing_session_name");
109
+
110
+ if (action === "status") {
111
+ const session = statusGjcTmuxSession(sessionName);
112
+ if (json) {
113
+ writeJson({ ok: true, session: sessionJson(session) });
114
+ return;
115
+ }
116
+ writeText([
117
+ `session: ${session.name}`,
118
+ `windows: ${session.windows}`,
119
+ `attached: ${session.attached}`,
120
+ `createdAt: ${session.createdAt}`,
121
+ `panes: ${session.panes}`,
122
+ `bindings: ${session.bindings || "none"}`,
123
+ ]);
124
+ return;
125
+ }
126
+
127
+ if (action === "remove" || action === "rm" || action === "delete") {
128
+ const removed = removeGjcTmuxSession(sessionName);
129
+ if (json) {
130
+ writeJson({ ok: true, session: sessionJson(removed) });
131
+ return;
132
+ }
133
+ writeText([`removed: ${removed.name}`]);
134
+ return;
135
+ }
136
+
137
+ if (action === "attach") {
138
+ attachGjcTmuxSession(sessionName);
139
+ return;
140
+ }
141
+ throw new Error(`unknown_session_action:${action}`);
142
+ } catch (error) {
143
+ if (json) {
144
+ writeJsonFailure(error);
145
+ return;
146
+ }
147
+ throw error;
148
+ }
149
+ }
150
+ }
@@ -1,12 +1,22 @@
1
1
  import { Command } from "@gajae-code/utils/cli";
2
- import { runBridgedRuntimeEndpoint } from "./gjc-runtime-bridge";
2
+ import { runNativeStateCommand } from "../gjc-runtime/state-runtime";
3
3
 
4
4
  export default class State extends Command {
5
- static description = "Read or update private GJC workflow state";
5
+ static description = "Read or update GJC workflow state receipts under .gjc/state";
6
6
  static strict = false;
7
- static examples = ['$ gjc state read --input \'{"mode":"team"}\' --json'];
7
+ static examples = [
8
+ '$ gjc state read --input \'{"mode":"deep-interview"}\' --json',
9
+ '$ gjc state write --input \'{"state":{"interview_id":"abc"}}\' --mode deep-interview --json',
10
+ "$ gjc state clear --mode deep-interview",
11
+ "$ gjc state deep-interview read --json",
12
+ '$ gjc state ralplan write --input \'{"phase":"approval","active":true}\' --json',
13
+ "$ gjc state team contract",
14
+ ];
8
15
 
9
16
  async run(): Promise<void> {
10
- await runBridgedRuntimeEndpoint("state", this.argv);
17
+ const result = await runNativeStateCommand(this.argv);
18
+ if (result.stdout) process.stdout.write(result.stdout);
19
+ if (result.stderr) process.stderr.write(result.stderr);
20
+ process.exitCode = result.status;
11
21
  }
12
22
  }
@@ -42,6 +42,18 @@ function formatTaskCounts(counts: Record<string, number>): string {
42
42
  .join(" ");
43
43
  }
44
44
 
45
+ function formatNotificationSummary(snapshot: GjcTeamSnapshot): string {
46
+ const summary = snapshot.notification_summary;
47
+ return `notifications: total=${summary.total} replay_eligible=${summary.replay_eligible} pending=${summary.by_state.pending} queued=${summary.by_state.queued} deferred=${summary.by_state.deferred} failed=${summary.by_state.failed}`;
48
+ }
49
+
50
+ function formatAwaitingIntegrationNextStep(snapshot: GjcTeamSnapshot): string[] {
51
+ if (snapshot.phase !== "awaiting_integration") return [];
52
+ return [
53
+ "next: worker tasks are completed, but integration still needs leader attention before the team is complete",
54
+ ];
55
+ }
56
+
45
57
  function formatIntegrationSummary(snapshot: {
46
58
  integration_by_worker?: Record<string, { status?: string; conflict_files?: string[] }>;
47
59
  }): string[] {
@@ -64,7 +76,7 @@ function parseInputFlag(argv: string[]): Record<string, unknown> {
64
76
  }
65
77
 
66
78
  export default class Team extends Command {
67
- static description = "Run native GJC tmux team orchestration commands";
79
+ static description = "Run native GJC tmux team orchestration; --dry-run writes ephemeral .gjc/state/team state only";
68
80
  static strict = false;
69
81
 
70
82
  static args = {
@@ -76,13 +88,18 @@ export default class Team extends Command {
76
88
 
77
89
  static flags = {
78
90
  json: Flags.boolean({ char: "j", description: "Emit machine-readable JSON", default: false }),
79
- "dry-run": Flags.boolean({ description: "Create team state without starting tmux panes", default: false }),
91
+ "dry-run": Flags.boolean({
92
+ description:
93
+ "Create ephemeral .gjc/state/team state without starting tmux panes; do not commit generated state",
94
+ default: false,
95
+ }),
80
96
  };
81
97
 
82
98
  static examples = [
83
99
  'gjc team 3:executor "Implement the approved plan"',
84
100
  "gjc team status <team-name> --json",
85
101
  'gjc team api claim-task --input \'{"team_name":"demo","worker_id":"worker-1"}\' --json',
102
+ 'gjc team 2:executor --dry-run --json "Preview state only"',
86
103
  "gjc team shutdown <team-name>",
87
104
  ];
88
105
 
@@ -118,6 +135,8 @@ export default class Team extends Command {
118
135
  `state: ${snapshot.state_dir}`,
119
136
  `tasks: ${snapshot.task_total} (${formatTaskCounts(snapshot.task_counts)})`,
120
137
  `workers: ${snapshot.workers.map(worker => `${worker.id}:${worker.status}`).join(" ")}`,
138
+ formatNotificationSummary(snapshot),
139
+ ...formatAwaitingIntegrationNextStep(snapshot),
121
140
  ...formatIntegrationSummary(snapshot),
122
141
  ]);
123
142
  return;
@@ -141,7 +160,7 @@ export default class Team extends Command {
141
160
  if (!operation || operation === "--help" || operation === "help") {
142
161
  writeText([
143
162
  "Supported operations:",
144
- "send-message broadcast mailbox-list mailbox-mark-delivered mailbox-mark-notified",
163
+ "send-message broadcast mailbox-list mailbox-mark-delivered mailbox-mark-notified notification-list notification-read notification-replay notification-mark-pane-attempt worker-startup-ack",
145
164
  "create-task read-task list-tasks update-task claim-task transition-task-status release-task-claim",
146
165
  "read-config read-manifest read-worker-status read-worker-heartbeat update-worker-heartbeat write-worker-inbox write-worker-identity",
147
166
  "append-event read-events await-event write-shutdown-request read-shutdown-ack read-monitor-snapshot write-monitor-snapshot read-task-approval write-task-approval",
@@ -176,6 +195,7 @@ export default class Team extends Command {
176
195
  `tmux: ${snapshot.tmux_session}`,
177
196
  `state: ${snapshot.state_dir}`,
178
197
  `workers: ${snapshot.workers.length}`,
198
+ ...(dryRun ? ["dry-run: wrote ephemeral .gjc/state/team state only; do not commit generated .gjc state"] : []),
179
199
  ]);
180
200
  }
181
201
  }
@@ -243,6 +243,10 @@ interface ProviderValidationConfig {
243
243
  models: ProviderValidationModel[];
244
244
  }
245
245
 
246
+ function usesAwsCredentialChain(api: Api | undefined): boolean {
247
+ return api === "bedrock-converse-stream";
248
+ }
249
+
246
250
  function validateProviderConfiguration(
247
251
  providerName: string,
248
252
  config: ProviderValidationConfig,
@@ -274,10 +278,14 @@ function validateProviderConfiguration(
274
278
  if (!config.baseUrl) {
275
279
  throw new Error(`Provider ${providerName}: "baseUrl" is required when defining custom models.`);
276
280
  }
281
+ const usesProviderCredentialChain = usesAwsCredentialChain(config.api);
277
282
  const requiresAuth =
278
283
  mode === "runtime-register"
279
- ? !config.apiKey && !config.oauthConfigured
280
- : !config.apiKey && !config.apiKeyEnv && (config.auth ?? "apiKey") !== "none";
284
+ ? !usesProviderCredentialChain && !config.apiKey && !config.oauthConfigured
285
+ : !usesProviderCredentialChain &&
286
+ !config.apiKey &&
287
+ !config.apiKeyEnv &&
288
+ (config.auth ?? "apiKey") !== "none";
281
289
  if (requiresAuth) {
282
290
  throw new Error(
283
291
  mode === "runtime-register"
@@ -63,76 +63,86 @@ const ModelThinkingSchema = z.object({
63
63
  levels: z.array(EffortSchema).optional(),
64
64
  });
65
65
 
66
- const RequestTransformSchema = z.object({
67
- profile: z.enum(["openai-proxy"]).optional(),
68
- stripHeaders: z.array(z.string().min(1)).optional(),
69
- setHeaders: z.record(z.string(), z.string().nullable()).optional(),
70
- extraBody: z.record(z.string(), z.unknown()).optional(),
71
- });
66
+ const RequestTransformSchema = z
67
+ .object({
68
+ profile: z.enum(["openai-proxy"]).optional(),
69
+ stripHeaders: z.array(z.string().min(1)).optional(),
70
+ setHeaders: z.record(z.string(), z.string().nullable()).optional(),
71
+ extraBody: z.record(z.string(), z.unknown()).optional(),
72
+ })
73
+ .strict();
72
74
 
73
75
  const ModelBindingsSchema = z.object({
74
76
  modelRoles: z.record(z.string(), z.string().min(1)).optional(),
75
77
  agentModelOverrides: z.record(z.string(), z.string().min(1)).optional(),
76
78
  });
77
79
 
78
- const ModelDefinitionSchema = z.object({
79
- id: z.string().min(1),
80
- name: z.string().min(1).optional(),
81
- api: z
82
- .enum([
83
- "openai-completions",
84
- "openai-responses",
85
- "openai-codex-responses",
86
- "azure-openai-responses",
87
- "anthropic-messages",
88
- "google-generative-ai",
89
- "google-vertex",
90
- ])
91
- .optional(),
92
- baseUrl: z.string().min(1).optional(),
93
- reasoning: z.boolean().optional(),
94
- thinking: ModelThinkingSchema.optional(),
95
- input: z.array(z.enum(["text", "image"])).optional(),
96
- cost: z
97
- .object({
98
- input: z.number(),
99
- output: z.number(),
100
- cacheRead: z.number(),
101
- cacheWrite: z.number(),
102
- })
103
- .optional(),
104
- premiumMultiplier: z.number().optional(),
105
- contextWindow: z.number().optional(),
106
- maxTokens: z.number().optional(),
107
- headers: z.record(z.string(), z.string()).optional(),
108
- compat: OpenAICompatSchema.optional(),
109
- contextPromotionTarget: z.string().min(1).optional(),
110
- wireModelId: z.string().min(1).optional(),
111
- requestTransform: RequestTransformSchema.optional(),
112
- });
113
-
114
- export const ModelOverrideSchema = z.object({
115
- name: z.string().min(1).optional(),
116
- reasoning: z.boolean().optional(),
117
- thinking: ModelThinkingSchema.optional(),
118
- input: z.array(z.enum(["text", "image"])).optional(),
119
- cost: z
120
- .object({
121
- input: z.number().optional(),
122
- output: z.number().optional(),
123
- cacheRead: z.number().optional(),
124
- cacheWrite: z.number().optional(),
125
- })
126
- .optional(),
127
- premiumMultiplier: z.number().optional(),
128
- contextWindow: z.number().optional(),
129
- maxTokens: z.number().optional(),
130
- headers: z.record(z.string(), z.string()).optional(),
131
- compat: OpenAICompatSchema.optional(),
132
- contextPromotionTarget: z.string().min(1).optional(),
133
- wireModelId: z.string().min(1).optional(),
134
- requestTransform: RequestTransformSchema.optional(),
135
- });
80
+ const ModelDefinitionSchema = z
81
+ .object({
82
+ id: z.string().min(1),
83
+ name: z.string().min(1).optional(),
84
+ api: z
85
+ .enum([
86
+ "openai-completions",
87
+ "openai-responses",
88
+ "openai-codex-responses",
89
+ "azure-openai-responses",
90
+ "anthropic-messages",
91
+ "bedrock-converse-stream",
92
+ "google-generative-ai",
93
+ "google-vertex",
94
+ "google-gemini-cli",
95
+ "ollama-chat",
96
+ "cursor-agent",
97
+ ])
98
+ .optional(),
99
+ baseUrl: z.string().min(1).optional(),
100
+ reasoning: z.boolean().optional(),
101
+ thinking: ModelThinkingSchema.optional(),
102
+ input: z.array(z.enum(["text", "image"])).optional(),
103
+ cost: z
104
+ .object({
105
+ input: z.number(),
106
+ output: z.number(),
107
+ cacheRead: z.number(),
108
+ cacheWrite: z.number(),
109
+ })
110
+ .optional(),
111
+ premiumMultiplier: z.number().optional(),
112
+ contextWindow: z.number().optional(),
113
+ maxTokens: z.number().optional(),
114
+ headers: z.record(z.string(), z.string()).optional(),
115
+ compat: OpenAICompatSchema.optional(),
116
+ contextPromotionTarget: z.string().min(1).optional(),
117
+ wireModelId: z.string().min(1).optional(),
118
+ requestTransform: RequestTransformSchema.optional(),
119
+ })
120
+ .strict();
121
+
122
+ export const ModelOverrideSchema = z
123
+ .object({
124
+ name: z.string().min(1).optional(),
125
+ reasoning: z.boolean().optional(),
126
+ thinking: ModelThinkingSchema.optional(),
127
+ input: z.array(z.enum(["text", "image"])).optional(),
128
+ cost: z
129
+ .object({
130
+ input: z.number().optional(),
131
+ output: z.number().optional(),
132
+ cacheRead: z.number().optional(),
133
+ cacheWrite: z.number().optional(),
134
+ })
135
+ .optional(),
136
+ premiumMultiplier: z.number().optional(),
137
+ contextWindow: z.number().optional(),
138
+ maxTokens: z.number().optional(),
139
+ headers: z.record(z.string(), z.string()).optional(),
140
+ compat: OpenAICompatSchema.optional(),
141
+ contextPromotionTarget: z.string().min(1).optional(),
142
+ wireModelId: z.string().min(1).optional(),
143
+ requestTransform: RequestTransformSchema.optional(),
144
+ })
145
+ .strict();
136
146
 
137
147
  export type ModelOverride = z.infer<typeof ModelOverrideSchema>;
138
148
 
@@ -145,49 +155,57 @@ export const ProviderAuthSchema = z.enum(["apiKey", "none", "oauth"]);
145
155
  export type ProviderAuthMode = z.infer<typeof ProviderAuthSchema>;
146
156
  export type ProviderDiscovery = z.infer<typeof ProviderDiscoverySchema>;
147
157
 
148
- const ProviderConfigSchema = z.object({
149
- baseUrl: z.string().min(1).optional(),
150
- apiKey: z.string().min(1).optional(),
151
- apiKeyEnv: z.string().min(1).optional(),
152
- api: z
153
- .enum([
154
- "openai-completions",
155
- "openai-responses",
156
- "openai-codex-responses",
157
- "azure-openai-responses",
158
- "anthropic-messages",
159
- "google-generative-ai",
160
- "google-vertex",
161
- ])
162
- .optional(),
163
- headers: z.record(z.string(), z.string()).optional(),
164
- compat: OpenAICompatSchema.optional(),
165
- authHeader: z.boolean().optional(),
166
- auth: ProviderAuthSchema.optional(),
167
- discovery: ProviderDiscoverySchema.optional(),
168
- requestTransform: RequestTransformSchema.optional(),
169
- models: z.array(ModelDefinitionSchema).optional(),
170
- modelOverrides: z.record(z.string(), ModelOverrideSchema).optional(),
171
- disableStrictTools: z.boolean().optional(),
172
- /**
173
- * Streaming transport override. When set to `"pi-native"`, gjc dispatches
174
- * every model under this provider via the auth-gateway's
175
- * `POST /v1/pi/stream` endpoint instead of the per-provider SDK. The
176
- * provider's `baseUrl` must point at a compatible `gjc auth-gateway`
177
- * and `apiKey` must carry the gateway bearer.
178
- */
179
- transport: z.literal("pi-native").optional(),
180
- });
158
+ const ProviderConfigSchema = z
159
+ .object({
160
+ baseUrl: z.string().min(1).optional(),
161
+ apiKey: z.string().min(1).optional(),
162
+ apiKeyEnv: z.string().min(1).optional(),
163
+ api: z
164
+ .enum([
165
+ "openai-completions",
166
+ "openai-responses",
167
+ "openai-codex-responses",
168
+ "azure-openai-responses",
169
+ "anthropic-messages",
170
+ "bedrock-converse-stream",
171
+ "google-generative-ai",
172
+ "google-vertex",
173
+ "google-gemini-cli",
174
+ "ollama-chat",
175
+ "cursor-agent",
176
+ ])
177
+ .optional(),
178
+ headers: z.record(z.string(), z.string()).optional(),
179
+ compat: OpenAICompatSchema.optional(),
180
+ authHeader: z.boolean().optional(),
181
+ auth: ProviderAuthSchema.optional(),
182
+ discovery: ProviderDiscoverySchema.optional(),
183
+ requestTransform: RequestTransformSchema.optional(),
184
+ models: z.array(ModelDefinitionSchema).optional(),
185
+ modelOverrides: z.record(z.string(), ModelOverrideSchema).optional(),
186
+ disableStrictTools: z.boolean().optional(),
187
+ /**
188
+ * Streaming transport override. When set to `"pi-native"`, gjc dispatches
189
+ * every model under this provider via the auth-gateway's
190
+ * `POST /v1/pi/stream` endpoint instead of the per-provider SDK. The
191
+ * provider's `baseUrl` must point at a compatible `gjc auth-gateway`
192
+ * and `apiKey` must carry the gateway bearer.
193
+ */
194
+ transport: z.literal("pi-native").optional(),
195
+ })
196
+ .strict();
181
197
 
182
198
  const EquivalenceConfigSchema = z.object({
183
199
  overrides: z.record(z.string(), z.string().min(1)).optional(),
184
200
  exclude: z.array(z.string().min(1)).optional(),
185
201
  });
186
202
 
187
- export const ModelsConfigSchema = z.object({
188
- providers: z.record(z.string(), ProviderConfigSchema).optional(),
189
- modelBindings: ModelBindingsSchema.optional(),
190
- equivalence: EquivalenceConfigSchema.optional(),
191
- });
203
+ export const ModelsConfigSchema = z
204
+ .object({
205
+ providers: z.record(z.string(), ProviderConfigSchema).optional(),
206
+ modelBindings: ModelBindingsSchema.optional(),
207
+ equivalence: EquivalenceConfigSchema.optional(),
208
+ })
209
+ .strict();
192
210
 
193
211
  export type ModelsConfig = z.infer<typeof ModelsConfigSchema>;
@@ -901,30 +901,6 @@ export const SETTINGS_SCHEMA = {
901
901
  },
902
902
  },
903
903
 
904
- "loop.mode": {
905
- type: "enum",
906
- values: ["prompt", "compact", "reset"] as const,
907
- default: "prompt",
908
- ui: {
909
- tab: "interaction",
910
- label: "Loop Mode",
911
- description: "What happens between /loop iterations before re-submitting the prompt",
912
- options: [
913
- {
914
- value: "prompt",
915
- label: "Prompt",
916
- description: "Re-submit the prompt as a follow-up message (current behavior)",
917
- },
918
- {
919
- value: "compact",
920
- label: "Compact",
921
- description: "Compact the session context, then re-submit the prompt",
922
- },
923
- { value: "reset", label: "Reset", description: "Start a new session, then re-submit the prompt" },
924
- ],
925
- },
926
- },
927
-
928
904
  // Input and startup
929
905
  doubleEscapeAction: {
930
906
  type: "enum",
@@ -2207,7 +2183,7 @@ export const SETTINGS_SCHEMA = {
2207
2183
  ui: {
2208
2184
  tab: "tasks",
2209
2185
  label: "Goal Status In Footer",
2210
- description: "Show token budget alongside the goal indicator in the status line",
2186
+ description: "Show goal usage alongside the goal indicator in the status line",
2211
2187
  },
2212
2188
  },
2213
2189
 
package/src/config.ts CHANGED
@@ -20,7 +20,7 @@ const PROJECT_CONFIG_PRIORITY = [{ dir: CONFIG_DIR_NAME }, { dir: ".gemini" }];
20
20
  */
21
21
  export function getPackageDir(): string {
22
22
  // Allow override via environment variable (useful for Nix/Guix where store paths tokenize poorly)
23
- const envDir = process.env.PI_PACKAGE_DIR;
23
+ const envDir = process.env.GJC_PACKAGE_DIR ?? process.env.PI_PACKAGE_DIR;
24
24
  if (envDir) {
25
25
  return expandTilde(envDir);
26
26
  }