@gajae-code/coding-agent 0.7.7 → 0.7.9

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 (96) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/README.md +14 -0
  3. package/dist/types/cli/local-provider-smoke.d.ts +59 -0
  4. package/dist/types/cli/notify-cli.d.ts +1 -1
  5. package/dist/types/cli/update-cli.d.ts +24 -1
  6. package/dist/types/commands/local-provider.d.ts +34 -0
  7. package/dist/types/config/keybindings.d.ts +1 -1
  8. package/dist/types/config/model-profile-activation.d.ts +19 -2
  9. package/dist/types/config/model-profiles.d.ts +1 -0
  10. package/dist/types/config/model-registry.d.ts +9 -0
  11. package/dist/types/config/models-config-schema.d.ts +5 -0
  12. package/dist/types/config/settings-schema.d.ts +45 -0
  13. package/dist/types/config/settings.d.ts +7 -0
  14. package/dist/types/coordinator-mcp/server.d.ts +8 -0
  15. package/dist/types/gjc-runtime/launch-tmux.d.ts +14 -5
  16. package/dist/types/gjc-runtime/team-runtime.d.ts +1 -1
  17. package/dist/types/gjc-runtime/tmux-common.d.ts +12 -0
  18. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +3 -1
  19. package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
  20. package/dist/types/modes/components/hook-input.d.ts +1 -0
  21. package/dist/types/modes/components/model-selector.d.ts +11 -0
  22. package/dist/types/modes/components/settings-selector.d.ts +4 -1
  23. package/dist/types/modes/components/status-line/types.d.ts +5 -7
  24. package/dist/types/modes/components/status-line.d.ts +7 -3
  25. package/dist/types/modes/controllers/extension-ui-controller.d.ts +3 -1
  26. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  27. package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
  28. package/dist/types/modes/interactive-mode.d.ts +3 -1
  29. package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -0
  30. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +21 -1
  31. package/dist/types/modes/tmux-scroll.d.ts +18 -0
  32. package/dist/types/modes/types.d.ts +3 -1
  33. package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
  34. package/dist/types/notifications/daemon-paths.d.ts +9 -0
  35. package/dist/types/notifications/telegram-daemon-cli.d.ts +21 -4
  36. package/dist/types/notifications/telegram-daemon.d.ts +1 -10
  37. package/dist/types/tools/ask.d.ts +24 -0
  38. package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
  39. package/package.json +7 -7
  40. package/src/cli/local-provider-smoke.ts +632 -0
  41. package/src/cli/notify-cli.ts +5 -3
  42. package/src/cli/update-cli.ts +155 -13
  43. package/src/cli.ts +19 -0
  44. package/src/commands/launch.ts +46 -1
  45. package/src/commands/local-provider.ts +62 -0
  46. package/src/commands/notify.ts +1 -1
  47. package/src/config/keybindings.ts +1 -1
  48. package/src/config/model-profile-activation.ts +92 -11
  49. package/src/config/model-profiles.ts +15 -2
  50. package/src/config/model-registry.ts +136 -6
  51. package/src/config/models-config-schema.ts +9 -0
  52. package/src/config/settings-schema.ts +48 -0
  53. package/src/config/settings.ts +11 -0
  54. package/src/coordinator-mcp/server.ts +280 -36
  55. package/src/deep-interview/render-middleware.ts +1 -0
  56. package/src/defaults/gjc/skills/deep-interview/SKILL.md +14 -6
  57. package/src/defaults/gjc/skills/ralplan/SKILL.md +14 -7
  58. package/src/gjc-runtime/deep-interview-runtime.ts +1 -1
  59. package/src/gjc-runtime/launch-tmux.ts +321 -38
  60. package/src/gjc-runtime/launch-worktree.ts +16 -2
  61. package/src/gjc-runtime/team-runtime.ts +87 -7
  62. package/src/gjc-runtime/tmux-common.ts +16 -0
  63. package/src/gjc-runtime/tmux-sessions.ts +13 -9
  64. package/src/gjc-runtime/ultragoal-guard.ts +11 -5
  65. package/src/internal-urls/docs-index.generated.ts +10 -9
  66. package/src/modes/components/agent-dashboard.ts +2 -1
  67. package/src/modes/components/custom-model-preset-wizard.ts +31 -192
  68. package/src/modes/components/hook-editor.ts +7 -4
  69. package/src/modes/components/hook-input.ts +4 -0
  70. package/src/modes/components/model-selector.ts +260 -12
  71. package/src/modes/components/session-observer-overlay.ts +3 -3
  72. package/src/modes/components/settings-selector.ts +506 -5
  73. package/src/modes/components/status-line/segments.ts +16 -19
  74. package/src/modes/components/status-line/types.ts +6 -2
  75. package/src/modes/components/status-line.ts +70 -35
  76. package/src/modes/controllers/event-controller.ts +98 -4
  77. package/src/modes/controllers/extension-ui-controller.ts +5 -0
  78. package/src/modes/controllers/input-controller.ts +15 -0
  79. package/src/modes/controllers/selector-controller.ts +142 -11
  80. package/src/modes/interactive-mode.ts +7 -2
  81. package/src/modes/prompt-action-autocomplete.ts +8 -0
  82. package/src/modes/shared/agent-wire/deep-interview-gate.ts +11 -4
  83. package/src/modes/tmux-scroll.ts +92 -0
  84. package/src/modes/types.ts +6 -1
  85. package/src/modes/utils/terminal-bell.ts +48 -0
  86. package/src/notifications/daemon-paths.ts +22 -0
  87. package/src/notifications/telegram-daemon-cli.ts +125 -13
  88. package/src/notifications/telegram-daemon.ts +6 -21
  89. package/src/prompts/system/system-prompt.md +5 -0
  90. package/src/sdk.ts +10 -1
  91. package/src/session/agent-session.ts +84 -6
  92. package/src/slash-commands/builtin-registry.ts +106 -13
  93. package/src/tools/ask.ts +11 -1
  94. package/src/tools/eval.ts +2 -2
  95. package/src/tools/render-utils.ts +2 -0
  96. package/src/tools/ultragoal-ask-guard.ts +39 -5
@@ -12,7 +12,12 @@ import {
12
12
  GJC_MODEL_ASSIGNMENT_TARGETS,
13
13
  type GjcModelAssignmentTargetId,
14
14
  } from "../config/model-registry";
15
- import { extractExplicitThinkingSelector, formatModelSelectorValue, parseModelPattern } from "../config/model-resolver";
15
+ import {
16
+ extractExplicitThinkingSelector,
17
+ formatModelSelectorValue,
18
+ parseModelPattern,
19
+ parseModelString,
20
+ } from "../config/model-resolver";
16
21
  import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../discovery/helpers.js";
17
22
  import { resolveMemoryBackend } from "../memory-backend";
18
23
  import { DynamicBorder } from "../modes/components/dynamic-border";
@@ -169,11 +174,32 @@ function splitExplicitThinkingSelector(selector: string): { baseSelector: string
169
174
  return thinkingLevel ? { baseSelector: trimmed.slice(0, colonIndex), thinkingLevel } : { baseSelector: trimmed };
170
175
  }
171
176
 
172
- function resolveModelCommandSelection(
177
+ interface ModelCommandSelection {
178
+ model: Model;
179
+ selector: string;
180
+ thinkingLevel?: ThinkingLevel;
181
+ }
182
+
183
+ interface ModelCommandResolutionFailure {
184
+ message: string;
185
+ }
186
+
187
+ type ModelCommandResolution =
188
+ | { ok: true; selection: ModelCommandSelection }
189
+ | { ok: false; failure: ModelCommandResolutionFailure };
190
+
191
+ function parseProviderQualifiedSelector(selector: string): { provider: string; modelId: string } | undefined {
192
+ const splitSelector = splitExplicitThinkingSelector(selector);
193
+ const parsed = parseModelString(splitSelector.baseSelector);
194
+ if (!parsed) return undefined;
195
+ return { provider: parsed.provider, modelId: parsed.id };
196
+ }
197
+
198
+ function resolveModelCommandSelectionFromAvailable(
173
199
  runtime: SlashCommandRuntime,
174
200
  selector: string,
175
- ): { model: Model; selector: string; thinkingLevel?: ThinkingLevel } | undefined {
176
- const availableModels = runtime.session.getAvailableModels?.() ?? [];
201
+ availableModels: Model[],
202
+ ): ModelCommandSelection | undefined {
177
203
  const matchPreferences = { usageOrder: runtime.settings.getStorage()?.getModelUsageOrder() };
178
204
  const resolved = parseModelPattern(selector, availableModels, matchPreferences, {
179
205
  modelRegistry: runtime.session.modelRegistry,
@@ -198,6 +224,78 @@ function resolveModelCommandSelection(
198
224
  };
199
225
  }
200
226
 
227
+ function formatDiscoverableProviderFailure(
228
+ selector: string,
229
+ provider: string,
230
+ modelId: string,
231
+ runtime: SlashCommandRuntime,
232
+ ): string {
233
+ const state = runtime.session.modelRegistry.getProviderDiscoveryState?.(provider);
234
+ const discovered = state?.models ?? [];
235
+ const base = `Unknown model: ${selector}.`;
236
+ if (!modelId.trim()) {
237
+ return `${base} Local provider model selectors must use provider/model-id syntax with a non-empty model id.`;
238
+ }
239
+ if (!state) {
240
+ return `${base} Provider ${provider} is configured for discovery but has not reported models yet.`;
241
+ }
242
+ if (state.status === "unavailable") {
243
+ const details = state.error ? ` (${state.error})` : "";
244
+ return `${base} Provider ${provider} discovery is unavailable${details}. Check the local endpoint and run /model again.`;
245
+ }
246
+ if (state.status === "unauthenticated") {
247
+ return `${base} Provider ${provider} requires authentication before model discovery.`;
248
+ }
249
+ if (state.status === "empty") {
250
+ return `${base} Provider ${provider} discovery succeeded but returned no models.`;
251
+ }
252
+ if (discovered.length > 0) {
253
+ const preview = discovered.slice(0, 8).join(", ");
254
+ const suffix = discovered.length > 8 ? ", …" : "";
255
+ return `${base} Provider ${provider} did not report model ${modelId}. Available local models: ${preview}${suffix}.`;
256
+ }
257
+ return `${base} Provider ${provider} did not report model ${modelId}.`;
258
+ }
259
+
260
+ async function resolveModelCommandSelection(
261
+ runtime: SlashCommandRuntime,
262
+ selector: string,
263
+ ): Promise<ModelCommandResolution> {
264
+ let availableModels = runtime.session.getAvailableModels?.() ?? [];
265
+ const initialSelection = resolveModelCommandSelectionFromAvailable(runtime, selector, availableModels as Model[]);
266
+ if (initialSelection) {
267
+ return { ok: true, selection: initialSelection };
268
+ }
269
+
270
+ const providerRef = parseProviderQualifiedSelector(selector);
271
+ const discoverableProviders = runtime.session.modelRegistry?.getDiscoverableProviders?.() ?? [];
272
+ if (providerRef && discoverableProviders.includes(providerRef.provider)) {
273
+ await runtime.session.modelRegistry.refreshProvider?.(providerRef.provider, "online");
274
+ availableModels = runtime.session.getAvailableModels?.() ?? [];
275
+ const refreshedSelection = resolveModelCommandSelectionFromAvailable(
276
+ runtime,
277
+ selector,
278
+ availableModels as Model[],
279
+ );
280
+ if (refreshedSelection) {
281
+ return { ok: true, selection: refreshedSelection };
282
+ }
283
+ return {
284
+ ok: false,
285
+ failure: {
286
+ message: formatDiscoverableProviderFailure(selector, providerRef.provider, providerRef.modelId, runtime),
287
+ },
288
+ };
289
+ }
290
+
291
+ return {
292
+ ok: false,
293
+ failure: {
294
+ message: `Unknown model: ${selector}. Configure or login to a provider first, then list/select models with /model.`,
295
+ },
296
+ };
297
+ }
298
+
201
299
  function modelSelectionUsage(runtime: SlashCommandRuntime, currentModelLine?: string): string {
202
300
  return [
203
301
  currentModelLine,
@@ -282,16 +380,11 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
282
380
  runtime,
283
381
  );
284
382
  }
285
- const selection = resolveModelCommandSelection(runtime, modelId);
286
- if (!selection) {
287
- return usage(
288
- modelSelectionUsage(
289
- runtime,
290
- `Unknown model: ${modelId}. Configure or login to a provider first, then list/select models with /model.`,
291
- ),
292
- runtime,
293
- );
383
+ const resolution = await resolveModelCommandSelection(runtime, modelId);
384
+ if (!resolution.ok) {
385
+ return usage(modelSelectionUsage(runtime, resolution.failure.message), runtime);
294
386
  }
387
+ const { selection } = resolution;
295
388
  try {
296
389
  const persistedSelector = formatModelSelectorValue(selection.selector, selection.thinkingLevel);
297
390
  if (parsedArgs.targetId === "default") {
package/src/tools/ask.ts CHANGED
@@ -62,6 +62,11 @@ const DeepInterviewMeta = z.object({
62
62
  ambiguity: z.number().min(0).max(1).describe("ambiguity at ask time (0..1)"),
63
63
  });
64
64
 
65
+ const WorkflowGateMeta = z.object({
66
+ stage: z.enum(["deep-interview", "ralplan", "ultragoal"]).describe("workflow gate stage"),
67
+ kind: z.enum(["question", "approval", "execution"]).describe("workflow gate kind"),
68
+ });
69
+
65
70
  const QuestionItem = z.object({
66
71
  id: z.string().describe("question id"),
67
72
  question: z.string().describe("question text"),
@@ -69,6 +74,7 @@ const QuestionItem = z.object({
69
74
  multi: z.boolean().describe("allow multiple selections").optional(),
70
75
  recommended: z.number().describe("recommended option index").optional(),
71
76
  deepInterview: DeepInterviewMeta.describe("optional deep-interview round metadata").optional(),
77
+ workflowGate: WorkflowGateMeta.describe("optional workflow gate stage/kind override").optional(),
72
78
  });
73
79
 
74
80
  export const askSchema = z.object({
@@ -551,7 +557,10 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
551
557
  _onUpdate?: AgentToolUpdateCallback<AskToolDetails>,
552
558
  context?: AgentToolContext,
553
559
  ): Promise<AgentToolResult<AskToolDetails>> {
554
- await assertUltragoalAskAllowed(this.session.cwd);
560
+ await assertUltragoalAskAllowed(this.session.cwd, {
561
+ activeSkillState: this.session.getActiveSkillState?.(),
562
+ sessionId: this.session.getSessionId?.() ?? null,
563
+ });
555
564
  const gateEmitter = this.session.getWorkflowGateEmitter?.();
556
565
  const canUseWorkflowGate = gateEmitter?.isUnattended() === true;
557
566
 
@@ -665,6 +674,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
665
674
  multi: q.multi,
666
675
  recommended: q.recommended,
667
676
  deepInterview: q.deepInterview,
677
+ workflowGate: q.workflowGate,
668
678
  };
669
679
  const answer = await gateEmitter.emitGate(questionToGate(gateQuestion));
670
680
  const decoded = gateAnswerToResult(gateQuestion, answer);
package/src/tools/eval.ts CHANGED
@@ -754,8 +754,8 @@ function formatStatusEventExpanded(event: EvalStatusEvent, theme: Theme): string
754
754
  case "git_log":
755
755
  if (data.entries) {
756
756
  addItems(data.entries as unknown[], e => {
757
- const entry = e as { sha: string; subject: string };
758
- return `${entry.sha} ${truncateToWidth(entry.subject, 50)}`;
757
+ const entry = e as { sha?: string; subject?: string };
758
+ return `${String(entry.sha ?? "")} ${truncateToWidth(String(entry.subject ?? ""), 50)}`;
759
759
  });
760
760
  }
761
761
  break;
@@ -86,6 +86,7 @@ export const EXPAND_HINT = "(Ctrl+O for more)";
86
86
  * Get first N lines of text as preview, with each line truncated.
87
87
  */
88
88
  export function getPreviewLines(text: string, maxLines: number, maxLineLen: number, ellipsis?: Ellipsis): string[] {
89
+ if (typeof text !== "string") return [];
89
90
  const lines = text.split("\n").filter(l => l.trim());
90
91
  return lines.slice(0, maxLines).map(l => truncateToWidth(l.trim(), maxLineLen, ellipsis));
91
92
  }
@@ -568,6 +569,7 @@ export function truncateDiffByHunk(
568
569
  // =============================================================================
569
570
 
570
571
  export function shortenPath(filePath: string, homeDir?: string): string {
572
+ if (typeof filePath !== "string") return "";
571
573
  const home = homeDir ?? os.homedir();
572
574
  if (home && filePath.startsWith(home)) {
573
575
  return `~${filePath.slice(home.length)}`;
@@ -10,6 +10,29 @@ const ULTRAGOAL_ASK_GUARD = Symbol.for("gajae-code.ultragoalAskGuard");
10
10
 
11
11
  type GuardedTool = AgentTool & { [ULTRAGOAL_ASK_GUARD]?: true };
12
12
 
13
+ export interface UltragoalAskGuardContext {
14
+ activeSkillState?: { skill?: string; session_id?: string } | null;
15
+ sessionId?: string | null;
16
+ }
17
+
18
+ const UPSTREAM_PLANNING_ASK_SKILLS = new Set(["deep-interview", "ralplan"]);
19
+
20
+ function normalizedActiveSkill(context?: UltragoalAskGuardContext): string | undefined {
21
+ const skill = context?.activeSkillState?.skill?.trim();
22
+ return skill || undefined;
23
+ }
24
+
25
+ function sessionScopedAskGuardId(
26
+ context: UltragoalAskGuardContext,
27
+ activeSkill: string | undefined,
28
+ ): string | undefined {
29
+ if (activeSkill !== "ultragoal" && !UPSTREAM_PLANNING_ASK_SKILLS.has(activeSkill ?? "")) return undefined;
30
+ const activeSessionId = context.activeSkillState?.session_id?.trim();
31
+ if (activeSessionId) return activeSessionId;
32
+ const sessionId = context.sessionId?.trim();
33
+ return sessionId || undefined;
34
+ }
35
+
13
36
  export function formatUltragoalAskBlockMessage(diagnostic: UltragoalAskBlockDiagnostic): string {
14
37
  return [
15
38
  diagnostic.message,
@@ -18,15 +41,26 @@ export function formatUltragoalAskBlockMessage(diagnostic: UltragoalAskBlockDiag
18
41
  ].join("\n");
19
42
  }
20
43
 
21
- export async function assertUltragoalAskAllowed(cwd: string): Promise<void> {
22
- const diagnostic = await isUltragoalAskBlocked(cwd);
44
+ export async function assertUltragoalAskAllowed(cwd: string, context: UltragoalAskGuardContext = {}): Promise<void> {
45
+ const activeSkill = normalizedActiveSkill(context);
46
+ // Deep-interview and ralplan are upstream planning workflows whose core gates
47
+ // are `ask` calls. Scope their Ultragoal check to the current session so stale
48
+ // or ambiguous Ultragoal durable state from another session cannot hijack those
49
+ // prompts; same-session active Ultragoal state still falls through to the
50
+ // blocker/nudge checks below.
51
+ const sessionId = sessionScopedAskGuardId(context, activeSkill);
52
+ const diagnostic = await isUltragoalAskBlocked(cwd, { sessionId });
23
53
  if (!diagnostic.active) return;
24
- const nudge = await consumeUltragoalAskNudge(cwd);
54
+ const nudge = await consumeUltragoalAskNudge(cwd, sessionId);
25
55
  if (nudge.nudged) throw new ToolError(nudge.message);
26
56
  throw new ToolError(formatUltragoalAskBlockMessage(diagnostic));
27
57
  }
28
58
 
29
- export function guardToolForUltragoalAsk<T extends AgentTool>(tool: T, getCwd: () => string): T {
59
+ export function guardToolForUltragoalAsk<T extends AgentTool>(
60
+ tool: T,
61
+ getCwd: () => string,
62
+ getContext: () => UltragoalAskGuardContext = () => ({}),
63
+ ): T {
30
64
  if (tool.name !== "ask") return tool;
31
65
  const candidate = tool as GuardedTool;
32
66
  if (candidate[ULTRAGOAL_ASK_GUARD]) return tool;
@@ -35,7 +69,7 @@ export function guardToolForUltragoalAsk<T extends AgentTool>(tool: T, getCwd: (
35
69
  if (prop === ULTRAGOAL_ASK_GUARD) return true;
36
70
  if (prop !== "execute") return Reflect.get(target, prop, receiver);
37
71
  return async (...args: unknown[]): Promise<unknown> => {
38
- await assertUltragoalAskAllowed(getCwd());
72
+ await assertUltragoalAskAllowed(getCwd(), getContext());
39
73
  return Reflect.apply(target.execute, target, args);
40
74
  };
41
75
  },