@gajae-code/coding-agent 0.4.5 → 0.5.0

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 (87) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/types/commands/harness.d.ts +3 -0
  3. package/dist/types/config/model-profile-activation.d.ts +11 -2
  4. package/dist/types/config/model-profiles.d.ts +7 -0
  5. package/dist/types/config/model-registry.d.ts +3 -0
  6. package/dist/types/config/model-resolver.d.ts +2 -0
  7. package/dist/types/config/models-config-schema.d.ts +30 -0
  8. package/dist/types/config/settings-schema.d.ts +4 -3
  9. package/dist/types/gjc-runtime/team-runtime.d.ts +0 -1
  10. package/dist/types/gjc-runtime/tmux-common.d.ts +3 -0
  11. package/dist/types/harness-control-plane/owner.d.ts +1 -1
  12. package/dist/types/harness-control-plane/receipt-spool.d.ts +19 -0
  13. package/dist/types/harness-control-plane/state-machine.d.ts +6 -1
  14. package/dist/types/harness-control-plane/types.d.ts +4 -0
  15. package/dist/types/hindsight/mental-models.d.ts +5 -5
  16. package/dist/types/modes/components/model-selector.d.ts +1 -12
  17. package/dist/types/modes/rpc/rpc-client.d.ts +2 -2
  18. package/dist/types/modes/rpc/rpc-types.d.ts +4 -1
  19. package/dist/types/sdk.d.ts +5 -0
  20. package/dist/types/session/agent-session.d.ts +2 -0
  21. package/dist/types/session/blob-store.d.ts +20 -1
  22. package/dist/types/session/session-manager.d.ts +24 -6
  23. package/dist/types/session/streaming-output.d.ts +3 -2
  24. package/dist/types/session/tool-choice-queue.d.ts +6 -0
  25. package/dist/types/task/receipt.d.ts +1 -0
  26. package/dist/types/task/types.d.ts +7 -0
  27. package/dist/types/thinking-metadata.d.ts +16 -0
  28. package/dist/types/thinking.d.ts +3 -12
  29. package/dist/types/tools/index.d.ts +2 -0
  30. package/dist/types/tools/resolve.d.ts +0 -10
  31. package/dist/types/utils/tool-choice.d.ts +14 -1
  32. package/package.json +7 -7
  33. package/src/cli.ts +8 -4
  34. package/src/commands/harness.ts +36 -2
  35. package/src/commands/launch.ts +2 -2
  36. package/src/commands/session.ts +3 -1
  37. package/src/config/model-profile-activation.ts +15 -3
  38. package/src/config/model-profiles.ts +255 -56
  39. package/src/config/model-resolver.ts +9 -6
  40. package/src/config/models-config-schema.ts +1 -0
  41. package/src/config/settings-schema.ts +6 -3
  42. package/src/coordinator-mcp/server.ts +54 -23
  43. package/src/cursor.ts +16 -2
  44. package/src/defaults/gjc/skills/team/SKILL.md +3 -2
  45. package/src/defaults/gjc/skills/ultragoal/SKILL.md +8 -2
  46. package/src/export/html/index.ts +13 -9
  47. package/src/gjc-runtime/team-runtime.ts +33 -7
  48. package/src/gjc-runtime/tmux-common.ts +15 -0
  49. package/src/gjc-runtime/tmux-sessions.ts +19 -11
  50. package/src/gjc-runtime/ultragoal-runtime.ts +505 -41
  51. package/src/gjc-runtime/workflow-manifest.generated.json +27 -1
  52. package/src/gjc-runtime/workflow-manifest.ts +16 -1
  53. package/src/harness-control-plane/owner.ts +78 -27
  54. package/src/harness-control-plane/receipt-spool.ts +128 -0
  55. package/src/harness-control-plane/state-machine.ts +27 -6
  56. package/src/harness-control-plane/storage.ts +23 -0
  57. package/src/harness-control-plane/types.ts +4 -0
  58. package/src/hindsight/mental-models.ts +17 -16
  59. package/src/internal-urls/docs-index.generated.ts +2 -2
  60. package/src/modes/components/assistant-message.ts +26 -14
  61. package/src/modes/components/diff.ts +97 -0
  62. package/src/modes/components/model-selector.ts +353 -181
  63. package/src/modes/components/tool-execution.ts +30 -13
  64. package/src/modes/controllers/selector-controller.ts +33 -42
  65. package/src/modes/rpc/rpc-client.ts +3 -2
  66. package/src/modes/rpc/rpc-mode.ts +44 -14
  67. package/src/modes/rpc/rpc-types.ts +5 -2
  68. package/src/modes/shared/agent-wire/command-dispatch.ts +10 -5
  69. package/src/modes/shared/agent-wire/command-validation.ts +11 -0
  70. package/src/sdk.ts +29 -2
  71. package/src/secrets/obfuscator.ts +102 -27
  72. package/src/session/agent-session.ts +105 -20
  73. package/src/session/blob-store.ts +89 -3
  74. package/src/session/session-manager.ts +309 -58
  75. package/src/session/streaming-output.ts +185 -122
  76. package/src/session/tool-choice-queue.ts +23 -0
  77. package/src/task/executor.ts +69 -6
  78. package/src/task/receipt.ts +5 -0
  79. package/src/task/render.ts +21 -1
  80. package/src/task/types.ts +8 -0
  81. package/src/thinking-metadata.ts +51 -0
  82. package/src/thinking.ts +26 -46
  83. package/src/tools/bash.ts +1 -1
  84. package/src/tools/index.ts +2 -0
  85. package/src/tools/resolve.ts +93 -18
  86. package/src/utils/edit-mode.ts +1 -1
  87. package/src/utils/tool-choice.ts +45 -16
@@ -54,71 +54,270 @@ const profile = (
54
54
  });
55
55
 
56
56
  export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
57
- profile("opencode-go-eco", ["opencode-go"], {
58
- default: "opencode-go/deepseek-v4-flash",
59
- executor: "opencode-go/qwen3.5-plus",
60
- architect: "opencode-go/glm-5",
61
- planner: "opencode-go/minimax-m2.5",
62
- critic: "opencode-go/kimi-k2.5",
63
- }),
64
- profile("opencode-go-standard", ["opencode-go"], {
65
- default: "opencode-go/kimi-k2.6",
66
- executor: "opencode-go/qwen3.6-plus",
67
- architect: "opencode-go/glm-5.1",
68
- planner: "opencode-go/minimax-m2.7",
69
- critic: "opencode-go/deepseek-v4-pro",
70
- }),
71
- profile("opencode-go-pro", ["opencode-go"], {
72
- default: "opencode-go/qwen3.7-max",
73
- executor: "opencode-go/kimi-k2.6",
74
- architect: "opencode-go/deepseek-v4-pro:high",
75
- planner: "opencode-go/glm-5.1:high",
76
- critic: "opencode-go/minimax-m2.7:high",
77
- }),
78
57
  profile("codex-eco", ["openai-codex"], {
79
- default: "openai-codex/gpt-5.4-mini",
80
- executor: "openai-codex/gpt-5.4-nano",
81
- architect: "openai-codex/gpt-5.4-mini",
82
- planner: "openai-codex/gpt-5.4-mini",
83
- critic: "openai-codex/gpt-5.4-mini",
84
- }),
85
- profile("codex-standard", ["openai-codex"], {
86
- default: "openai-codex/gpt-5.4:medium",
87
- executor: "openai-codex/gpt-5.4:low",
88
- architect: "openai-codex/gpt-5.4:xhigh",
89
- planner: "openai-codex/gpt-5.4:medium",
90
- critic: "openai-codex/gpt-5.4:high",
58
+ default: "openai-codex/gpt-5.5:low",
59
+ executor: "openai-codex/gpt-5.5:minimal",
60
+ planner: "openai-codex/gpt-5.5:low",
61
+ critic: "openai-codex/gpt-5.5:medium",
62
+ architect: "openai-codex/gpt-5.5:high",
63
+ }),
64
+ profile("codex-medium", ["openai-codex"], {
65
+ default: "openai-codex/gpt-5.5:medium",
66
+ executor: "openai-codex/gpt-5.5:low",
67
+ planner: "openai-codex/gpt-5.5:medium",
68
+ critic: "openai-codex/gpt-5.5:high",
69
+ architect: "openai-codex/gpt-5.5:xhigh",
91
70
  }),
92
71
  profile("codex-pro", ["openai-codex"], {
93
- default: "openai-codex/gpt-5.5",
94
- executor: "openai-codex/gpt-5.2-codex",
95
- architect: "openai-codex/gpt-5.1-codex-max:high",
72
+ default: "openai-codex/gpt-5.5:xhigh",
73
+ executor: "openai-codex/gpt-5.5:medium",
96
74
  planner: "openai-codex/gpt-5.5:high",
97
- critic: "openai-codex/gpt-5.3-codex-spark:high",
75
+ critic: "openai-codex/gpt-5.5:xhigh",
76
+ architect: "openai-codex/gpt-5.5:xhigh",
98
77
  }),
99
- profile("opencode-go-codex-eco", ["opencode-go", "openai-codex"], {
100
- default: "opencode-go/deepseek-v4-flash",
101
- executor: "opencode-go/qwen3.5-plus",
102
- architect: "openai-codex/gpt-5.4-mini",
103
- planner: "openai-codex/gpt-5.4-mini",
104
- critic: "openai-codex/gpt-5.4-mini",
105
- }),
106
- profile("opencode-go-codex-standard", ["opencode-go", "openai-codex"], {
78
+ profile("opencodego", ["opencode-go"], {
107
79
  default: "opencode-go/kimi-k2.6",
108
- executor: "opencode-go/qwen3.6-plus",
109
- architect: "openai-codex/gpt-5.4",
110
- planner: "openai-codex/gpt-5.4",
111
- critic: "openai-codex/gpt-5.4",
112
- }),
113
- profile("opencode-go-codex-pro", ["opencode-go", "openai-codex"], {
114
- default: "opencode-go/qwen3.7-max",
115
- executor: "opencode-go/kimi-k2.6",
116
- architect: "openai-codex/gpt-5.1-codex-max:high",
117
- planner: "openai-codex/gpt-5.5:high",
118
- critic: "openai-codex/gpt-5.3-codex-spark:high",
80
+ executor: "opencode-go/deepseek-v4-flash",
81
+ planner: "opencode-go/qwen3.7-max",
82
+ critic: "opencode-go/mimo-v2.5-pro",
83
+ architect: "opencode-go/deepseek-v4-pro",
84
+ }),
85
+ profile("claude-opus", ["anthropic"], {
86
+ default: "anthropic/claude-opus-4-8:xhigh",
87
+ executor: "anthropic/claude-sonnet-4-6",
88
+ planner: "anthropic/claude-opus-4-8:low",
89
+ critic: "anthropic/claude-opus-4-8:high",
90
+ architect: "anthropic/claude-opus-4-8:xhigh",
91
+ }),
92
+ profile("glm-eco", ["zai"], {
93
+ default: "zai/glm-5.1:low",
94
+ executor: "zai/glm-5.1:minimal",
95
+ planner: "zai/glm-5.1:low",
96
+ critic: "zai/glm-5.1:medium",
97
+ architect: "zai/glm-5.1:high",
98
+ }),
99
+ profile("glm-medium", ["zai"], {
100
+ default: "zai/glm-5.1:medium",
101
+ executor: "zai/glm-5.1:low",
102
+ planner: "zai/glm-5.1:medium",
103
+ critic: "zai/glm-5.1:high",
104
+ architect: "zai/glm-5.1:xhigh",
105
+ }),
106
+ profile("glm-pro", ["zai"], {
107
+ default: "zai/glm-5.1:xhigh",
108
+ executor: "zai/glm-5.1:medium",
109
+ planner: "zai/glm-5.1:high",
110
+ critic: "zai/glm-5.1:xhigh",
111
+ architect: "zai/glm-5.1:xhigh",
112
+ }),
113
+ profile("kimi-coding-plan-eco", ["kimi-code"], {
114
+ default: "kimi-code/kimi-k2.7-code:low",
115
+ executor: "kimi-code/kimi-k2.7-code:minimal",
116
+ planner: "kimi-code/kimi-k2.7-code:low",
117
+ critic: "kimi-code/kimi-k2.7-code:medium",
118
+ architect: "kimi-code/kimi-k2.7-code:high",
119
+ }),
120
+ profile("kimi-coding-plan-medium", ["kimi-code"], {
121
+ default: "kimi-code/kimi-k2.7-code:medium",
122
+ executor: "kimi-code/kimi-k2.7-code:low",
123
+ planner: "kimi-code/kimi-k2.7-code:medium",
124
+ critic: "kimi-code/kimi-k2.7-code:high",
125
+ architect: "kimi-code/kimi-k2.7-code:xhigh",
126
+ }),
127
+ profile("kimi-coding-plan-pro", ["kimi-code"], {
128
+ default: "kimi-code/kimi-k2.7-code:xhigh",
129
+ executor: "kimi-code/kimi-k2.7-code:medium",
130
+ planner: "kimi-code/kimi-k2.7-code:high",
131
+ critic: "kimi-code/kimi-k2.7-code:xhigh",
132
+ architect: "kimi-code/kimi-k2.7-code:xhigh",
133
+ }),
134
+ profile("mimo-eco", ["xiaomi"], {
135
+ default: "xiaomi/mimo-v2.5-pro:low",
136
+ executor: "xiaomi/mimo-v2.5-pro:minimal",
137
+ planner: "xiaomi/mimo-v2.5-pro:low",
138
+ critic: "xiaomi/mimo-v2.5-pro:medium",
139
+ architect: "xiaomi/mimo-v2.5-pro:high",
140
+ }),
141
+ profile("mimo-medium", ["xiaomi"], {
142
+ default: "xiaomi/mimo-v2.5-pro:medium",
143
+ executor: "xiaomi/mimo-v2.5-pro:low",
144
+ planner: "xiaomi/mimo-v2.5-pro:medium",
145
+ critic: "xiaomi/mimo-v2.5-pro:high",
146
+ architect: "xiaomi/mimo-v2.5-pro:xhigh",
147
+ }),
148
+ profile("mimo-pro", ["xiaomi"], {
149
+ default: "xiaomi/mimo-v2.5-pro:xhigh",
150
+ executor: "xiaomi/mimo-v2.5-pro:medium",
151
+ planner: "xiaomi/mimo-v2.5-pro:high",
152
+ critic: "xiaomi/mimo-v2.5-pro:xhigh",
153
+ architect: "xiaomi/mimo-v2.5-pro:xhigh",
154
+ }),
155
+ profile("grok-eco", ["xai"], {
156
+ default: "xai/grok-4.3:low",
157
+ executor: "xai/grok-4.3:minimal",
158
+ planner: "xai/grok-4.3:low",
159
+ critic: "xai/grok-4.3:medium",
160
+ architect: "xai/grok-4.3:high",
161
+ }),
162
+ profile("grok-medium", ["xai"], {
163
+ default: "xai/grok-4.3:medium",
164
+ executor: "xai/grok-4.3:low",
165
+ planner: "xai/grok-4.3:medium",
166
+ critic: "xai/grok-4.3:high",
167
+ architect: "xai/grok-4.3:xhigh",
168
+ }),
169
+ profile("grok-pro", ["xai"], {
170
+ default: "xai/grok-4.3:xhigh",
171
+ executor: "xai/grok-4.3:medium",
172
+ planner: "xai/grok-4.3:high",
173
+ critic: "xai/grok-4.3:xhigh",
174
+ architect: "xai/grok-4.3:xhigh",
175
+ }),
176
+ profile("cursor-eco", ["cursor"], {
177
+ default: "cursor/composer-1.5:low",
178
+ executor: "cursor/composer-1.5:minimal",
179
+ planner: "cursor/composer-1.5:low",
180
+ critic: "cursor/composer-1.5:medium",
181
+ architect: "cursor/composer-1.5:high",
182
+ }),
183
+ profile("cursor-medium", ["cursor"], {
184
+ default: "cursor/composer-1.5:medium",
185
+ executor: "cursor/composer-1.5:low",
186
+ planner: "cursor/composer-1.5:medium",
187
+ critic: "cursor/composer-1.5:high",
188
+ architect: "cursor/composer-1.5:xhigh",
119
189
  }),
190
+ profile("cursor-pro", ["cursor"], {
191
+ default: "cursor/composer-1.5:xhigh",
192
+ executor: "cursor/composer-1.5:medium",
193
+ planner: "cursor/composer-1.5:high",
194
+ critic: "cursor/composer-1.5:xhigh",
195
+ architect: "cursor/composer-1.5:xhigh",
196
+ }),
197
+ profile("minimax-eco", ["minimax-code"], {
198
+ default: "minimax-code/minimax-v3:low",
199
+ executor: "minimax-code/minimax-v3:minimal",
200
+ planner: "minimax-code/minimax-v3:low",
201
+ critic: "minimax-code/minimax-v3:medium",
202
+ architect: "minimax-code/minimax-v3:high",
203
+ }),
204
+ profile("minimax-medium", ["minimax-code"], {
205
+ default: "minimax-code/minimax-v3:medium",
206
+ executor: "minimax-code/minimax-v3:low",
207
+ planner: "minimax-code/minimax-v3:medium",
208
+ critic: "minimax-code/minimax-v3:high",
209
+ architect: "minimax-code/minimax-v3:xhigh",
210
+ }),
211
+ profile("minimax-pro", ["minimax-code"], {
212
+ default: "minimax-code/minimax-v3:xhigh",
213
+ executor: "minimax-code/minimax-v3:medium",
214
+ planner: "minimax-code/minimax-v3:high",
215
+ critic: "minimax-code/minimax-v3:xhigh",
216
+ architect: "minimax-code/minimax-v3:xhigh",
217
+ }),
218
+ profile("opus-codex", ["anthropic", "openai-codex"], {
219
+ default: "anthropic/claude-opus-4-8:xhigh",
220
+ executor: "openai-codex/gpt-5.5:low",
221
+ planner: "openai-codex/gpt-5.5:medium",
222
+ critic: "openai-codex/gpt-5.5:high",
223
+ architect: "openai-codex/gpt-5.5:xhigh",
224
+ }),
225
+ profile("codex-opencodego", ["openai-codex", "opencode-go"], {
226
+ default: "openai-codex/gpt-5.5:medium",
227
+ executor: "opencode-go/deepseek-v4-pro",
228
+ planner: "opencode-go/kimi-k2.6",
229
+ critic: "opencode-go/mimo-v2.5-pro",
230
+ architect: "openai-codex/gpt-5.5:xhigh",
231
+ }),
232
+ ];
233
+
234
+ export interface ModelProfilePresentation {
235
+ displayName: string;
236
+ providerGroup: string;
237
+ }
238
+
239
+ const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
240
+ "codex-eco": { displayName: "Codex Eco", providerGroup: "CODEX" },
241
+ "codex-medium": { displayName: "Codex Medium", providerGroup: "CODEX" },
242
+ "codex-pro": { displayName: "Codex Pro", providerGroup: "CODEX" },
243
+ opencodego: { displayName: "OpenCodeGo", providerGroup: "OPENCODEGO" },
244
+ "claude-opus": { displayName: "Claude Opus", providerGroup: "CLAUDE" },
245
+ "glm-eco": { displayName: "GLM Eco", providerGroup: "GLM" },
246
+ "glm-medium": { displayName: "GLM Medium", providerGroup: "GLM" },
247
+ "glm-pro": { displayName: "GLM Pro", providerGroup: "GLM" },
248
+ "kimi-coding-plan-eco": { displayName: "Kimi Coding Plan Eco", providerGroup: "KIMI CODING PLAN" },
249
+ "kimi-coding-plan-medium": { displayName: "Kimi Coding Plan Medium", providerGroup: "KIMI CODING PLAN" },
250
+ "kimi-coding-plan-pro": { displayName: "Kimi Coding Plan Pro", providerGroup: "KIMI CODING PLAN" },
251
+ "mimo-eco": { displayName: "Mimo Eco", providerGroup: "MIMO" },
252
+ "mimo-medium": { displayName: "Mimo Medium", providerGroup: "MIMO" },
253
+ "mimo-pro": { displayName: "Mimo Pro", providerGroup: "MIMO" },
254
+ "grok-eco": { displayName: "Grok Eco", providerGroup: "GROK" },
255
+ "grok-medium": { displayName: "Grok Medium", providerGroup: "GROK" },
256
+ "grok-pro": { displayName: "Grok Pro", providerGroup: "GROK" },
257
+ "cursor-eco": { displayName: "Cursor Eco", providerGroup: "CURSOR" },
258
+ "cursor-medium": { displayName: "Cursor Medium", providerGroup: "CURSOR" },
259
+ "cursor-pro": { displayName: "Cursor Pro", providerGroup: "CURSOR" },
260
+ "minimax-eco": { displayName: "MiniMax Eco", providerGroup: "MINIMAX" },
261
+ "minimax-medium": { displayName: "MiniMax Medium", providerGroup: "MINIMAX" },
262
+ "minimax-pro": { displayName: "MiniMax Pro", providerGroup: "MINIMAX" },
263
+ "opus-codex": { displayName: "Opus + Codex", providerGroup: "COMBOS" },
264
+ "codex-opencodego": { displayName: "Codex + OpenCodeGo", providerGroup: "COMBOS" },
265
+ };
266
+
267
+ const PROFILE_GROUP_ORDER = [
268
+ "CODEX",
269
+ "OPENCODEGO",
270
+ "CLAUDE",
271
+ "GLM",
272
+ "KIMI CODING PLAN",
273
+ "MIMO",
274
+ "GROK",
275
+ "CURSOR",
276
+ "MINIMAX",
277
+ "COMBOS",
120
278
  ];
121
279
 
280
+ const PROFILE_RECOMMENDATIONS: Record<string, string> = {
281
+ "openai-codex": "codex-medium",
282
+ anthropic: "claude-opus",
283
+ "opencode-go": "opencodego",
284
+ zai: "glm-medium",
285
+ "kimi-code": "kimi-coding-plan-medium",
286
+ xiaomi: "mimo-medium",
287
+ xai: "grok-medium",
288
+ cursor: "cursor-medium",
289
+ "minimax-code": "minimax-medium",
290
+ };
291
+
292
+ export function getModelProfilePresentation(name: string): ModelProfilePresentation {
293
+ return PROFILE_PRESENTATION[name] ?? { displayName: name, providerGroup: "COMBOS" };
294
+ }
295
+
296
+ export function groupModelProfilesForPresetLanding(
297
+ profiles: ReadonlyMap<string, ModelProfileDefinition>,
298
+ ): Map<string, ModelProfileDefinition[]> {
299
+ const groups = new Map<string, ModelProfileDefinition[]>();
300
+ for (const group of PROFILE_GROUP_ORDER) groups.set(group, []);
301
+ for (const profile of profiles.values()) {
302
+ const group = getModelProfilePresentation(profile.name).providerGroup;
303
+ if (!groups.has(group)) groups.set(group, []);
304
+ groups.get(group)?.push(profile);
305
+ }
306
+ for (const [group, entries] of groups) {
307
+ if (entries.length === 0) groups.delete(group);
308
+ else entries.sort((a, b) => a.name.localeCompare(b.name));
309
+ }
310
+ return groups;
311
+ }
312
+
313
+ export function recommendModelProfileForProvider(
314
+ providerId: string,
315
+ profiles: ReadonlyMap<string, ModelProfileDefinition>,
316
+ ): ModelProfileDefinition | undefined {
317
+ const recommended = PROFILE_RECOMMENDATIONS[providerId];
318
+ return recommended ? profiles.get(recommended) : undefined;
319
+ }
320
+
122
321
  export function mergeModelProfiles(userProfiles?: ModelsConfig["profiles"]): Map<string, ModelProfileDefinition> {
123
322
  const profiles = new Map<string, ModelProfileDefinition>();
124
323
  for (const definition of BUILTIN_MODEL_PROFILES) {
@@ -776,30 +776,33 @@ export async function resolveModelOverrideWithAuthFallback(
776
776
  thinkingLevel?: ThinkingLevel;
777
777
  explicitThinkingLevel: boolean;
778
778
  authFallbackUsed: boolean;
779
+ requestedModel?: Model<Api>;
780
+ fallbackReason?: "auth_unavailable";
779
781
  }> {
780
782
  const primary = resolveModelOverride(modelPatterns, modelRegistry, settings);
783
+ const unchanged = { ...primary, requestedModel: primary.model, authFallbackUsed: false };
781
784
  if (!primary.model || !parentActiveModelPattern) {
782
- return { ...primary, authFallbackUsed: false };
785
+ return unchanged;
783
786
  }
784
787
 
785
788
  const primaryKey = await modelRegistry.getApiKey(primary.model, sessionId);
786
789
  if (primaryKey === kNoAuth || isAuthenticated(primaryKey)) {
787
- return { ...primary, authFallbackUsed: false };
790
+ return unchanged;
788
791
  }
789
792
 
790
793
  const fallback = resolveModelOverride([parentActiveModelPattern], modelRegistry, settings);
791
794
  if (!fallback.model) {
792
- return { ...primary, authFallbackUsed: false };
795
+ return unchanged;
793
796
  }
794
797
  if (modelsAreEqual(fallback.model, primary.model)) {
795
- return { ...primary, authFallbackUsed: false };
798
+ return unchanged;
796
799
  }
797
800
  const fallbackKey = await modelRegistry.getApiKey(fallback.model, sessionId);
798
801
  if (!isAuthenticated(fallbackKey)) {
799
- return { ...primary, authFallbackUsed: false };
802
+ return unchanged;
800
803
  }
801
804
 
802
- return { ...fallback, authFallbackUsed: true };
805
+ return { ...fallback, requestedModel: primary.model, authFallbackUsed: true, fallbackReason: "auth_unavailable" };
803
806
  }
804
807
 
805
808
  /**
@@ -37,6 +37,7 @@ export const OpenAICompatSchema = z.object({
37
37
  requiresAssistantContentForToolCalls: z.boolean().optional(),
38
38
  supportsToolChoice: z.boolean().optional(),
39
39
  supportsForcedToolChoice: z.boolean().optional(),
40
+ toolChoiceSupport: z.enum(["none", "auto", "required", "named"]).optional(),
40
41
  disableReasoningOnForcedToolChoice: z.boolean().optional(),
41
42
  disableReasoningOnToolChoice: z.boolean().optional(),
42
43
  thinkingFormat: z.enum(["openai", "openrouter", "zai", "qwen", "qwen-chat-template"]).optional(),
@@ -1,7 +1,10 @@
1
- import { THINKING_EFFORTS } from "@gajae-code/ai/model-thinking";
1
+ import type { Effort } from "@gajae-code/ai/model-thinking";
2
2
  import { TASK_SIMPLE_MODES } from "../task/simple-mode";
3
- import { getThinkingLevelMetadata } from "../thinking";
3
+ import { getThinkingLevelMetadata } from "../thinking-metadata";
4
4
  import { EDIT_MODES } from "../utils/edit-mode";
5
+
6
+ const THINKING_EFFORTS = ["minimal", "low", "medium", "high", "xhigh", "max"] as readonly Effort[];
7
+
5
8
  import {
6
9
  DEFAULT_DISABLED_EXTENSIONS,
7
10
  DEFAULT_SKILL_DISCOVERY_SETTINGS,
@@ -665,7 +668,7 @@ export const SETTINGS_SCHEMA = {
665
668
  defaultThinkingLevel: {
666
669
  type: "enum",
667
670
  values: THINKING_EFFORTS,
668
- default: "high",
671
+ default: THINKING_EFFORTS[3],
669
672
  ui: {
670
673
  tab: "model",
671
674
  label: "Thinking Level",
@@ -742,6 +742,7 @@ async function startTmuxSession(
742
742
  config: CoordinatorMcpConfig,
743
743
  input: SessionStartInput,
744
744
  namespaceDir: string,
745
+ runner: CommandRunner = runCommand,
745
746
  ): Promise<Record<string, unknown>> {
746
747
  if (!config.sessionCommand) throw new Error("coordinator_session_command_required");
747
748
  const sessionName = `gjc-coordinator-${randomUUID().slice(0, 8)}`;
@@ -752,7 +753,7 @@ async function startTmuxSession(
752
753
  `${GJC_COORDINATOR_SESSION_ID_ENV}=${shellQuote(sessionName)}`,
753
754
  config.sessionCommand,
754
755
  ].join(" ");
755
- const started = await runCommand([
756
+ const started = await runner([
756
757
  "tmux",
757
758
  "new-session",
758
759
  "-d",
@@ -767,9 +768,6 @@ async function startTmuxSession(
767
768
  ]);
768
769
  if (started.exitCode !== 0) throw new Error(`coordinator_tmux_start_failed:${started.stderr || started.stdout}`);
769
770
  const [tmuxTarget, paneId] = started.stdout.trim().split(/\s+/, 2);
770
- const initialPromptTmuxKeysSent = input.prompt
771
- ? await sendTmuxPromptKeys(tmuxTarget || sessionName, input.prompt)
772
- : false;
773
771
  return {
774
772
  sessionId: sessionName,
775
773
  tmuxSession: sessionName,
@@ -779,7 +777,6 @@ async function startTmuxSession(
779
777
  createdAt: new Date().toISOString(),
780
778
  sessionCommand: config.sessionCommand,
781
779
  runtimeStateFile,
782
- initialPromptTmuxKeysSent,
783
780
  };
784
781
  }
785
782
 
@@ -964,27 +961,26 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
964
961
  }
965
962
 
966
963
  async function activateTurn(session: Record<string, unknown>, turn: TurnRecord): Promise<TurnRecord> {
967
- const tmuxKeysSent = await sendTmuxPrompt(session, turn.prompt.text, commandRunner);
968
964
  const timestamp = new Date().toISOString();
969
965
  const target = typeof session.tmux_target === "string" ? session.tmux_target : session.tmuxTarget;
970
966
  const live = hasTmuxIdentity(session) ? await hasTmuxSession(session, commandRunner) : null;
971
- const activeTurn: TurnRecord = {
967
+ const pendingTurn: TurnRecord = {
972
968
  ...turn,
973
969
  status: "active",
974
970
  delivery: {
975
971
  delivered: false,
976
- queued: !tmuxKeysSent,
972
+ queued: true,
977
973
  target: typeof target === "string" ? target : null,
978
- tmux_keys_sent: tmuxKeysSent,
974
+ tmux_keys_sent: false,
979
975
  prompt_acknowledged: false,
980
- state: tmuxKeysSent ? "tmux_keys_sent" : "unavailable",
976
+ state: "queued",
981
977
  attempts: [
982
978
  {
983
979
  delivered: false,
984
- tmux_keys_sent: tmuxKeysSent,
980
+ tmux_keys_sent: false,
985
981
  channel: "tmux_keys",
986
982
  created_at: timestamp,
987
- reason: tmuxKeysSent ? "awaiting_runtime_ack" : "tmux_delivery_unavailable",
983
+ reason: "awaiting_tmux_delivery",
988
984
  },
989
985
  ],
990
986
  },
@@ -992,13 +988,50 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
992
988
  started_at: turn.started_at ?? timestamp,
993
989
  updated_at: timestamp,
994
990
  };
995
- await writeActiveTurn(namespaceDir, activeTurn);
996
- await writeSessionState(namespaceDir, activeTurn.session_id, tmuxKeysSent ? "running" : "stale", {
997
- currentTurnId: activeTurn.turn_id,
991
+ await writeTurnRecord(namespaceDir, pendingTurn);
992
+ await writeActiveTurn(namespaceDir, pendingTurn);
993
+ await writeSessionState(namespaceDir, pendingTurn.session_id, "running", {
994
+ currentTurnId: pendingTurn.turn_id,
998
995
  live,
999
- reason: tmuxKeysSent ? null : "tmux_delivery_unavailable",
996
+ reason: null,
1000
997
  });
998
+
999
+ const tmuxKeysSent = await sendTmuxPrompt(session, turn.prompt.text, commandRunner);
1000
+ const deliveredAt = new Date().toISOString();
1001
+ const activeTurn: TurnRecord = {
1002
+ ...pendingTurn,
1003
+ delivery: {
1004
+ delivered: false,
1005
+ queued: !tmuxKeysSent,
1006
+ target: typeof target === "string" ? target : null,
1007
+ tmux_keys_sent: tmuxKeysSent,
1008
+ prompt_acknowledged: false,
1009
+ state: tmuxKeysSent ? "tmux_keys_sent" : "unavailable",
1010
+ attempts: [
1011
+ {
1012
+ delivered: false,
1013
+ tmux_keys_sent: tmuxKeysSent,
1014
+ channel: "tmux_keys",
1015
+ created_at: deliveredAt,
1016
+ reason: tmuxKeysSent ? "awaiting_runtime_ack" : "tmux_delivery_unavailable",
1017
+ },
1018
+ ],
1019
+ },
1020
+ updated_at: deliveredAt,
1021
+ };
1001
1022
  await writeTurnRecord(namespaceDir, activeTurn);
1023
+ await writeActiveTurn(namespaceDir, activeTurn);
1024
+ const sessionState = await readSessionState(namespaceDir, activeTurn.session_id);
1025
+ const runtimeStateAlreadySettled =
1026
+ sessionState?.current_turn_id === activeTurn.turn_id &&
1027
+ (sessionState.state === "completed" || sessionState.state === "errored");
1028
+ if (!runtimeStateAlreadySettled) {
1029
+ await writeSessionState(namespaceDir, activeTurn.session_id, tmuxKeysSent ? "running" : "stale", {
1030
+ currentTurnId: activeTurn.turn_id,
1031
+ live,
1032
+ reason: tmuxKeysSent ? null : "tmux_delivery_unavailable",
1033
+ });
1034
+ }
1002
1035
  return activeTurn;
1003
1036
  }
1004
1037
 
@@ -1149,18 +1182,16 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1149
1182
  };
1150
1183
  const started = services.startSession
1151
1184
  ? await services.startSession(input)
1152
- : await startTmuxSession(config, input, namespaceDir);
1185
+ : await startTmuxSession(config, input, namespaceDir, commandRunner);
1153
1186
  const startedRecord = asRecord(started);
1154
1187
  if (!startedRecord) throw new Error("coordinator_session_command_required");
1155
1188
  const session = normalizeSession(startedRecord);
1156
1189
  await writeJsonFile(sessionFile(session.session_id), session);
1157
1190
  const live = hasTmuxIdentity(session) ? await hasTmuxSession(session, commandRunner) : null;
1158
- let sessionState = await writeSessionState(
1159
- namespaceDir,
1160
- String(session.session_id),
1161
- input.prompt ? "running" : "ready_for_input",
1162
- { live, reason: null },
1163
- );
1191
+ let sessionState = await writeSessionState(namespaceDir, String(session.session_id), "ready_for_input", {
1192
+ live,
1193
+ reason: null,
1194
+ });
1164
1195
  if (typeof args.prompt === "string" && args.prompt.length > 0) {
1165
1196
  const turn = await activateTurn(
1166
1197
  session,
package/src/cursor.ts CHANGED
@@ -160,6 +160,20 @@ function formatMcpToolErrorMessage(toolName: string, availableTools: string[]):
160
160
  return `MCP tool "${toolName}" not found. Available tools: ${list}`;
161
161
  }
162
162
 
163
+ /**
164
+ * Cursor's wire protocol carries shell timeouts in milliseconds — the
165
+ * model-facing parameter is `block_until_ms`, and `ShellArgs.hard_timeout` is
166
+ * likewise documented in ms — while the bash tool's `timeout` is seconds.
167
+ * Passing the raw value through made a requested 30 s wait (30000 ms) arrive
168
+ * as 30000 s and clamp to the 3600 s ceiling, i.e. an accidental 1-hour
169
+ * timeout on a blocking command. Convert, rounding sub-second values up to 1 s
170
+ * so a tiny requested wait does not collapse to "no timeout".
171
+ */
172
+ function shellTimeoutSeconds(timeout: number | undefined): number | undefined {
173
+ if (!timeout || timeout <= 0) return undefined;
174
+ return Math.max(1, Math.ceil(timeout / 1000));
175
+ }
176
+
163
177
  export class CursorExecHandlers implements ICursorExecHandlers {
164
178
  constructor(private options: CursorExecBridgeOptions) {
165
179
  // Bind every native handler so methods stay instance-safe when invoked
@@ -240,7 +254,7 @@ export class CursorExecHandlers implements ICursorExecHandlers {
240
254
 
241
255
  async shell(args: Parameters<NonNullable<ICursorExecHandlers["shell"]>>[0]) {
242
256
  const toolCallId = decodeToolCallId(args.toolCallId);
243
- const timeoutSeconds = args.timeout && args.timeout > 0 ? args.timeout : undefined;
257
+ const timeoutSeconds = shellTimeoutSeconds(args.timeout);
244
258
  const toolResultMessage = await executeTool(this.#optionsForCall(), "bash", toolCallId, {
245
259
  command: args.command,
246
260
  cwd: args.workingDirectory || undefined,
@@ -262,7 +276,7 @@ export class CursorExecHandlers implements ICursorExecHandlers {
262
276
  return createToolResultMessage(toolCallId, toolName, result, true);
263
277
  }
264
278
 
265
- const timeoutSeconds = args.timeout && args.timeout > 0 ? args.timeout : undefined;
279
+ const timeoutSeconds = shellTimeoutSeconds(args.timeout);
266
280
  const toolArgs: Record<string, unknown> = {
267
281
  command: args.command,
268
282
  cwd: args.workingDirectory || undefined,
@@ -306,8 +306,9 @@ Worker protocol:
306
306
 
307
307
  Useful runtime env vars:
308
308
 
309
- - `GJC_TEAM_TMUX_COMMAND`
310
- - tmux binary/command override (default `tmux`)
309
+ - `GJC_TMUX_COMMAND` / `GJC_TEAM_TMUX_COMMAND`
310
+ - tmux binary/command override (default `tmux`). `GJC_TMUX_COMMAND` applies to every GJC tmux flow; `GJC_TEAM_TMUX_COMMAND` is honored as an alias by the team path. Both resolve through the same resolver, so the team leader and `gjc session ...` always target the same multiplexer.
311
+ - Multiplexer support boundary: GJC-managed sessions and the team leader are detected via tmux user options (`@gjc-profile`, written with `set-option` and read back with `show-options` / `list-sessions -F`). A provider must round-trip those user options to be supported. Real tmux works. Alternative multiplexers such as psmux on Windows do not reliably persist tmux user options yet, so `gjc session status` reports `gjc_tmux_session_untagged` (the session exists in the multiplexer but is not GJC-tagged) and team startup rejects the leader as `unmanaged_tmux_session`. The Windows-native psmux path is therefore not fully supported; use real tmux for GJC-managed session and team flows.
311
312
  - `GJC_TEAM_WORKER_COMMAND`
312
313
  - worker command override (default resolves to active GJC entrypoint or `gjc`)
313
314
  - `GJC_TEAM_STATE_ROOT`
@@ -120,9 +120,15 @@ Examples:
120
120
 
121
121
  ```sh
122
122
  gjc ultragoal steer --kind add_subgoal --title "Investigate blocker" --objective "Validate the blocker and report evidence." --evidence "log/test output" --rationale "The blocker changes the safe execution order." --json
123
- gjc ultragoal steer --directive-json ./steering.json --json
123
+ gjc ultragoal steer --kind split_subgoal --goal-id G002 --replacements-json '[{"title":"Fix parser","objective":"Resolve parser blocker."},{"title":"Verify parser","objective":"Run focused parser verification."}]' --evidence "Implementation split found two separable risks" --rationale "Splitting keeps each sub-goal independently verifiable." --json
124
+ gjc ultragoal steer --kind reorder_pending --order-json '["G003","G002"]' --evidence "Dependency order changed after investigation" --rationale "G003 must land before G002 can proceed safely." --json
125
+ gjc ultragoal steer --kind revise_pending_wording --goal-id G002 --title "Clarify blocker story" --evidence "The current title hides the actual blocker" --rationale "Clear wording keeps the ledger auditable." --json
126
+ gjc ultragoal steer --kind annotate_ledger --evidence "User changed release ordering at runtime" --rationale "The aggregate objective is unchanged, but the execution history needs an audit note." --json
127
+ gjc ultragoal steer --kind mark_blocked_superseded --goal-id G004 --evidence "The blocked work is no longer required because replacement evidence covers it" --rationale "No replacement sub-goal is needed; superseding only the blocked sub-goal unblocks final completion without changing the aggregate objective." --json
124
128
  ```
125
129
 
130
+ `--directive-json` and UserPromptSubmit structured steering are planned/deferred routing surfaces, not part of the native typed `--kind` CLI path described above.
131
+
126
132
  Steering invariants:
127
133
 
128
134
  - Do not edit the aggregate goal objective, original brief constraints, quality gates, or completion status. The aggregate objective is a stable pointer to `.gjc/ultragoal/goals.json` and `.gjc/ultragoal/ledger.jsonl`, not an enumeration of initial goal ids.
@@ -131,7 +137,7 @@ Steering invariants:
131
137
  - Superseded goals remain in `goals.json` with steering metadata and are skipped for scheduling.
132
138
  - Blocked goals without replacements are skipped for scheduling but still block final completion until later explicit steering replaces or supersedes them.
133
139
 
134
- UserPromptSubmit uses the same steering API only for structured directives such as `GJC_ULTRAGOAL_STEER: { ... }`, `gjc.ultragoal.steer: { ... }`, or `gjc ultragoal steer: { ... }`. Normal prose does not mutate state, and repeated prompt-submit directives dedupe by prompt signature or idempotency key.
140
+ UserPromptSubmit structured steering directives are a planned/deferred routing surface. Normal prose does not mutate state.
135
141
 
136
142
  ## Role-agent delegation guidance
137
143
 
@@ -150,15 +150,19 @@ export async function exportFromFile(inputPath: string, options?: ExportOptions
150
150
  throw err;
151
151
  }
152
152
 
153
- const sessionData: SessionData = {
154
- header: sm.getHeader(),
155
- entries: sm.getEntries(),
156
- leafId: sm.getLeafId(),
157
- };
153
+ try {
154
+ const sessionData: SessionData = {
155
+ header: sm.getHeader(),
156
+ entries: sm.getEntries(),
157
+ leafId: sm.getLeafId(),
158
+ };
158
159
 
159
- const html = await generateHtml(sessionData, opts.themeName);
160
- const outputPath = opts.outputPath || `${APP_NAME}-session-${path.basename(inputPath, ".jsonl")}.html`;
160
+ const html = await generateHtml(sessionData, opts.themeName);
161
+ const outputPath = opts.outputPath || `${APP_NAME}-session-${path.basename(inputPath, ".jsonl")}.html`;
161
162
 
162
- await Bun.write(outputPath, html);
163
- return outputPath;
163
+ await Bun.write(outputPath, html);
164
+ return outputPath;
165
+ } finally {
166
+ await sm.close();
167
+ }
164
168
  }