@gajae-code/coding-agent 0.12.7 → 0.12.10

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 (117) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/types/cli/notify-cli.d.ts +1 -0
  3. package/dist/types/commands/notify.d.ts +4 -0
  4. package/dist/types/commands/sdk.d.ts +23 -0
  5. package/dist/types/config/file-lock.d.ts +3 -0
  6. package/dist/types/config/model-discovery-manager.d.ts +6 -1
  7. package/dist/types/config/model-registry.d.ts +2 -0
  8. package/dist/types/config/resolve-config-value.d.ts +2 -1
  9. package/dist/types/config/settings-schema.d.ts +17 -1
  10. package/dist/types/discovery/agents-md.d.ts +11 -1
  11. package/dist/types/main.d.ts +1 -0
  12. package/dist/types/modes/components/hook-selector.d.ts +20 -0
  13. package/dist/types/modes/components/notifications-settings-editor.d.ts +53 -4
  14. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  15. package/dist/types/modes/controllers/selector-controller.d.ts +4 -2
  16. package/dist/types/modes/types.d.ts +6 -0
  17. package/dist/types/sdk/broker/broker.d.ts +5 -1
  18. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
  19. package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -1
  20. package/dist/types/sdk/bus/config.d.ts +75 -113
  21. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  22. package/dist/types/sdk/bus/discord-live-provider.d.ts +8 -2
  23. package/dist/types/sdk/bus/discord-provider.d.ts +19 -0
  24. package/dist/types/sdk/bus/index.d.ts +1 -1
  25. package/dist/types/sdk/bus/notification-orchestration.d.ts +127 -18
  26. package/dist/types/sdk/bus/notification-service.d.ts +54 -4
  27. package/dist/types/sdk/bus/session-control.d.ts +8 -3
  28. package/dist/types/sdk/bus/slack-live-provider.d.ts +9 -2
  29. package/dist/types/sdk/bus/slack-provider.d.ts +22 -0
  30. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +8 -1
  31. package/dist/types/sdk/client/discovery.d.ts +2 -1
  32. package/dist/types/sdk/host/query/handlers.d.ts +2 -0
  33. package/dist/types/sdk/index.d.ts +1 -0
  34. package/dist/types/sdk/providers.d.ts +22 -0
  35. package/dist/types/sdk/session-directory.d.ts +1 -1
  36. package/dist/types/session/internal/managed-session-scope.d.ts +17 -1
  37. package/dist/types/session/internal/managed-session-storage.d.ts +19 -1
  38. package/dist/types/session/internal/native-publish-outcome.d.ts +3 -2
  39. package/dist/types/session/internal/session-open-errors.d.ts +5 -0
  40. package/dist/types/session/session-manager.d.ts +3 -1
  41. package/dist/types/session/session-storage.d.ts +2 -0
  42. package/dist/types/system-prompt.d.ts +12 -1
  43. package/dist/types/tools/composer-bash-policy.d.ts +9 -1
  44. package/package.json +10 -8
  45. package/scripts/build-sdk-package-smoke.ts +51 -0
  46. package/scripts/capture-notifications-settings-showcase.ts +1 -1
  47. package/scripts/verify-gjc-sdk-canonicalization.ts +14 -1
  48. package/src/cli/notify-cli.ts +215 -99
  49. package/src/commands/notify.ts +19 -8
  50. package/src/commands/sdk.ts +54 -0
  51. package/src/config/file-lock.ts +6 -0
  52. package/src/config/model-discovery-manager.ts +50 -8
  53. package/src/config/model-profiles.ts +8 -0
  54. package/src/config/model-registry.ts +866 -76
  55. package/src/config/resolve-config-value.ts +10 -8
  56. package/src/config/settings-schema.ts +25 -15
  57. package/src/config/settings.ts +15 -15
  58. package/src/discovery/agents-md.ts +76 -19
  59. package/src/gjc-runtime/psmux-detect.ts +12 -16
  60. package/src/gjc-runtime/session-state-sidecar.ts +12 -2
  61. package/src/gjc-runtime/tmux-sessions.ts +8 -1
  62. package/src/internal-urls/docs-index.generated.ts +10 -9
  63. package/src/main.ts +43 -7
  64. package/src/modes/DESIGN.md +11 -11
  65. package/src/modes/components/extensions/inspector-panel.ts +9 -5
  66. package/src/modes/components/hook-selector.ts +155 -44
  67. package/src/modes/components/model-selector.ts +242 -49
  68. package/src/modes/components/notifications-settings-editor.ts +631 -85
  69. package/src/modes/controllers/extension-ui-controller.ts +114 -15
  70. package/src/modes/controllers/selector-controller.ts +525 -93
  71. package/src/modes/print-mode.ts +3 -0
  72. package/src/modes/types.ts +6 -0
  73. package/src/prompts/system/system-prompt.md +1 -0
  74. package/src/sdk/broker/broker.ts +54 -10
  75. package/src/sdk/broker/lifecycle-ledger.ts +34 -1
  76. package/src/sdk/broker/lifecycle.ts +47 -50
  77. package/src/sdk/bus/chat-daemon-cli.ts +12 -5
  78. package/src/sdk/bus/chat-daemon-control.ts +35 -6
  79. package/src/sdk/bus/chat-daemon-runtime.ts +16 -2
  80. package/src/sdk/bus/config.ts +534 -286
  81. package/src/sdk/bus/daemon-paths.ts +2 -0
  82. package/src/sdk/bus/discord-live-provider.ts +57 -2
  83. package/src/sdk/bus/discord-provider.ts +21 -0
  84. package/src/sdk/bus/index.ts +97 -35
  85. package/src/sdk/bus/notification-orchestration.ts +371 -68
  86. package/src/sdk/bus/notification-service.ts +571 -137
  87. package/src/sdk/bus/session-control.ts +119 -44
  88. package/src/sdk/bus/slack-live-provider.ts +78 -2
  89. package/src/sdk/bus/slack-provider.ts +24 -0
  90. package/src/sdk/bus/telegram-daemon-cli.ts +9 -2
  91. package/src/sdk/bus/telegram-daemon-contract.ts +8 -1
  92. package/src/sdk/bus/telegram-daemon-control.ts +3 -3
  93. package/src/sdk/bus/telegram-daemon.ts +23 -15
  94. package/src/sdk/client/discovery.ts +10 -4
  95. package/src/sdk/host/query/handlers.ts +20 -1
  96. package/src/sdk/index.ts +1 -0
  97. package/src/sdk/protocol/operation-inventory.generated.json +18 -0
  98. package/src/sdk/protocol/operation-registry.ts +5 -2
  99. package/src/sdk/providers.ts +51 -0
  100. package/src/sdk/session-directory.ts +1 -0
  101. package/src/sdk/session.ts +34 -14
  102. package/src/session/agent-session.ts +109 -37
  103. package/src/session/internal/managed-session-scope.ts +981 -450
  104. package/src/session/internal/managed-session-storage.ts +418 -47
  105. package/src/session/internal/native-publish-outcome.ts +15 -3
  106. package/src/session/internal/session-open-errors.ts +11 -0
  107. package/src/session/session-manager.ts +27 -3
  108. package/src/session/session-storage.ts +72 -5
  109. package/src/slash-commands/builtin-registry.ts +117 -15
  110. package/src/system-prompt.ts +28 -15
  111. package/src/task/index.ts +9 -3
  112. package/src/tools/ask.ts +1 -1
  113. package/src/tools/bash.ts +4 -2
  114. package/src/tools/composer-bash-policy.ts +21 -5
  115. package/src/tools/telegram-send.ts +4 -2
  116. package/src/utils/command-args.ts +19 -30
  117. package/src/utils/lang-from-path.ts +2 -4
@@ -9,6 +9,7 @@ import { applyStartupModelProfiles, createSessionManager } from "../main";
9
9
  import { initializeExtensions } from "../modes/runtime-init";
10
10
  import { ACP_MCP_REQUEST_TIMEOUT_MS, ACP_MCP_STARTUP_HEADROOM_MS } from "../sdk/acp/mcp";
11
11
  import { Broker } from "../sdk/broker/broker";
12
+ import { readBrokerDiscovery } from "../sdk/broker/discovery";
12
13
  import { completeBrokerProcess } from "../sdk/broker/internal";
13
14
  import {
14
15
  type LifecycleTranscriptEvidence,
@@ -58,6 +59,54 @@ export async function lifecycleArgs(
58
59
  };
59
60
  }
60
61
 
62
+ /**
63
+ * How long a session host tolerates the complete absence of a live broker
64
+ * publication before treating itself as orphaned. Hosts intentionally survive
65
+ * broker restarts (a replacement broker republishes discovery within seconds),
66
+ * so this must comfortably exceed a restart window while still bounding the
67
+ * lifetime of hosts whose broker is gone for good — otherwise every crashed or
68
+ * torn-down broker leaks a detached multi-hundred-megabyte host forever.
69
+ */
70
+ export const SESSION_HOST_BROKER_ABSENCE_GRACE_MS = 10 * 60_000;
71
+ const SESSION_HOST_BROKER_POLL_MS = 15_000;
72
+
73
+ /**
74
+ * Resolves only once no live broker publication has been observable in
75
+ * `agentDir` for the full grace window. A reappearing broker (including a
76
+ * replacement with a different pid) resets the window; an unreadable
77
+ * publication is not proof of orphanhood but accrues against the same bound.
78
+ */
79
+ export async function watchSessionHostBrokerLiveness(deps: {
80
+ agentDir: string;
81
+ now?: () => number;
82
+ sleep?: (ms: number) => Promise<void>;
83
+ readDiscovery?: (agentDir: string) => Promise<unknown>;
84
+ graceMs?: number;
85
+ pollMs?: number;
86
+ }): Promise<void> {
87
+ const now = deps.now ?? Date.now;
88
+ const sleep = deps.sleep ?? (async ms => await Bun.sleep(ms));
89
+ const readDiscovery = deps.readDiscovery ?? readBrokerDiscovery;
90
+ const graceMs = deps.graceMs ?? SESSION_HOST_BROKER_ABSENCE_GRACE_MS;
91
+ const pollMs = deps.pollMs ?? SESSION_HOST_BROKER_POLL_MS;
92
+ let absentSince: number | null = null;
93
+ for (;;) {
94
+ let live: unknown = null;
95
+ try {
96
+ live = await readDiscovery(deps.agentDir);
97
+ } catch {
98
+ // Transient read failures are ambiguity, not proof of orphanhood.
99
+ }
100
+ if (live) {
101
+ absentSince = null;
102
+ } else {
103
+ absentSince ??= now();
104
+ if (now() - absentSince >= graceMs) return;
105
+ }
106
+ await sleep(pollMs);
107
+ }
108
+ }
109
+
61
110
  type LifecycleTranscriptSource = {
62
111
  cwd: string;
63
112
  path: string;
@@ -505,6 +554,11 @@ export async function runSessionHost(
505
554
  }
506
555
  process.once("SIGTERM", stop);
507
556
  process.once("SIGINT", stop);
557
+ // A detached host whose broker is gone for good would otherwise live (and
558
+ // hold its session's memory) forever; reap it through the same graceful
559
+ // teardown a SIGTERM would take once the bounded absence grace elapses.
560
+ await watchSessionHostBrokerLiveness({ agentDir });
561
+ stop();
508
562
  await new Promise<void>(() => {});
509
563
  }
510
564
 
@@ -17,6 +17,10 @@ const DEFAULT_OPTIONS: Required<FileLockOptions> = {
17
17
 
18
18
  type LockInfo = FileLockOwnerToken;
19
19
 
20
+ export const FileLockTestHooks: {
21
+ afterParentMkdir?: (lockPath: string) => void | Promise<void>;
22
+ } = {};
23
+
20
24
  /**
21
25
  * Returns the OS-provided process start timestamp for PID-reuse detection.
22
26
  * `ps` is available on the supported Unix hosts (macOS and Linux), unlike
@@ -237,6 +241,8 @@ async function removeStaleLockForAcquire(lockPath: string, snapshot: LockStaleSn
237
241
 
238
242
  async function tryAcquireLock(lockPath: string): Promise<LockInfo | null> {
239
243
  await fs.mkdir(path.dirname(lockPath), { recursive: true });
244
+ const afterParentMkdir = FileLockTestHooks.afterParentMkdir;
245
+ if (afterParentMkdir) await afterParentMkdir(lockPath);
240
246
  try {
241
247
  await fs.mkdir(lockPath);
242
248
  return await writeLockInfo(lockPath);
@@ -37,6 +37,8 @@ export interface DiscoveryMergeInput {
37
37
  models: readonly Model<Api>[];
38
38
  state: ProviderDiscoveryState;
39
39
  warning?: string;
40
+ authGeneration?: string;
41
+ fetched?: boolean;
40
42
  }
41
43
 
42
44
  export interface ProviderDiscoveryCallbacks<TProvider extends DiscoveryProvider> {
@@ -44,7 +46,9 @@ export interface ProviderDiscoveryCallbacks<TProvider extends DiscoveryProvider>
44
46
  requiresAuth: (provider: TProvider) => boolean;
45
47
  peekApiKey: (provider: TProvider) => Promise<string | undefined>;
46
48
  isAuthenticated: (apiKey: string | undefined) => boolean;
47
- fetchModels: (provider: TProvider) => Promise<Model<Api>[]>;
49
+ fetchModels: (provider: TProvider, apiKey: string | undefined) => Promise<Model<Api>[]>;
50
+ getEvidenceGeneration?: (provider: TProvider) => string;
51
+ canPublishCache?: (provider: TProvider) => boolean;
48
52
  }
49
53
 
50
54
  /** Owns configured discovery inputs, status, cache lifecycle, and refresh generations. */
@@ -89,6 +93,11 @@ export class ModelDiscoveryManager<TProvider extends DiscoveryProvider> {
89
93
  const state = this.#states.get(provider);
90
94
  return state === undefined ? undefined : this.#snapshot(state);
91
95
  }
96
+ invalidate(provider: string): void {
97
+ this.#invalidate(provider);
98
+ this.#states.delete(provider);
99
+ this.#lastWarnings.delete(provider);
100
+ }
92
101
 
93
102
  loadCached(provider: TProvider, cacheDbPath?: string): readonly Model<Api>[] {
94
103
  const cache = readModelCache<Api>(provider.provider, 24 * 60 * 60 * 1000, Date.now, cacheDbPath);
@@ -133,9 +142,21 @@ export class ModelDiscoveryManager<TProvider extends DiscoveryProvider> {
133
142
  models: models.map(model => model.id),
134
143
  });
135
144
 
145
+ let authGeneration = callbacks.getEvidenceGeneration?.(provider);
146
+ let apiKey: string | undefined;
136
147
  if (callbacks.requiresAuth(provider)) {
137
- const apiKey = await callbacks.peekApiKey(provider);
148
+ apiKey = await callbacks.peekApiKey(provider);
149
+ const resolvedGeneration = callbacks.getEvidenceGeneration?.(provider);
138
150
  if (!this.isCurrent(token)) return this.#stale(token);
151
+ if (authGeneration !== resolvedGeneration) {
152
+ authGeneration = resolvedGeneration;
153
+ // Resolving a command-backed key can update the evidence generation. Keep
154
+ // the key resolved for this refresh so round-robin selection cannot switch
155
+ // credentials between the request and its published evidence.
156
+ if (!this.isCurrent(token) || authGeneration !== callbacks.getEvidenceGeneration?.(provider)) {
157
+ return this.#stale(token);
158
+ }
159
+ }
139
160
  if (!callbacks.isAuthenticated(apiKey)) return unauthenticated(cachedModels);
140
161
  }
141
162
 
@@ -145,10 +166,14 @@ export class ModelDiscoveryManager<TProvider extends DiscoveryProvider> {
145
166
  staticModels: [],
146
167
  cacheDbPath: callbacks.cacheDbPath,
147
168
  cacheTtlMs: 24 * 60 * 60 * 1000,
148
- canPublishCache: () => this.isCurrent(token),
169
+ canPublishCache: () =>
170
+ this.isCurrent(token) &&
171
+ (callbacks.getEvidenceGeneration === undefined ||
172
+ callbacks.getEvidenceGeneration(provider) === authGeneration) &&
173
+ (callbacks.canPublishCache?.(provider) ?? true),
149
174
  fetchDynamicModels: async () => {
150
175
  try {
151
- return await callbacks.fetchModels(provider);
176
+ return await callbacks.fetchModels(provider, apiKey);
152
177
  } catch (cause) {
153
178
  error = cause instanceof Error ? cause.message : String(cause);
154
179
  return null;
@@ -156,7 +181,11 @@ export class ModelDiscoveryManager<TProvider extends DiscoveryProvider> {
156
181
  },
157
182
  });
158
183
  const result = await manager.refresh(strategy);
159
- if (!this.isCurrent(token)) return this.#stale(token);
184
+ if (
185
+ !this.isCurrent(token) ||
186
+ (callbacks.getEvidenceGeneration !== undefined && callbacks.getEvidenceGeneration(provider) !== authGeneration)
187
+ )
188
+ return this.#stale(token);
160
189
  const status: ProviderDiscoveryStatus = error
161
190
  ? result.models.length > 0
162
191
  ? "cached"
@@ -166,7 +195,9 @@ export class ModelDiscoveryManager<TProvider extends DiscoveryProvider> {
166
195
  ? "cached"
167
196
  : "idle"
168
197
  : result.models.length > 0
169
- ? "ok"
198
+ ? result.stale
199
+ ? "cached"
200
+ : "ok"
170
201
  : "empty";
171
202
  const state: ProviderDiscoveryState = {
172
203
  provider: provider.provider,
@@ -177,7 +208,7 @@ export class ModelDiscoveryManager<TProvider extends DiscoveryProvider> {
177
208
  models: result.models.map(model => model.id),
178
209
  error,
179
210
  };
180
- return this.#complete(token, result.models, state, error);
211
+ return this.#complete(token, result.models, state, error, authGeneration, result.fetched);
181
212
  }
182
213
 
183
214
  #complete(
@@ -185,6 +216,8 @@ export class ModelDiscoveryManager<TProvider extends DiscoveryProvider> {
185
216
  models: readonly Model<Api>[],
186
217
  state: ProviderDiscoveryState,
187
218
  error?: string,
219
+ authGeneration?: string,
220
+ fetched?: boolean,
188
221
  ): DiscoveryMergeInput {
189
222
  const current = this.isCurrent(token);
190
223
  if (current) this.#states.set(token.provider, this.#snapshot(state));
@@ -193,7 +226,16 @@ export class ModelDiscoveryManager<TProvider extends DiscoveryProvider> {
193
226
  if (error) this.#lastWarnings.set(token.provider, error);
194
227
  else this.#lastWarnings.delete(token.provider);
195
228
  }
196
- return this.#snapshot({ provider: token.provider, token, current, models, state, warning });
229
+ return this.#snapshot({
230
+ provider: token.provider,
231
+ token,
232
+ current,
233
+ models,
234
+ state,
235
+ warning,
236
+ authGeneration,
237
+ fetched,
238
+ });
197
239
  }
198
240
 
199
241
  #stale(token: DiscoveryRefreshToken): DiscoveryMergeInput {
@@ -291,6 +291,13 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
291
291
  architect: "alibaba-token-plan/qwen3.8-max-preview:xhigh",
292
292
  critic: "alibaba-token-plan/glm-5.2:high",
293
293
  }),
294
+ profile("alibaba-token-plan-pro", ["alibaba-token-plan"], {
295
+ default: "alibaba-token-plan/qwen3.8-max-preview:medium",
296
+ executor: "alibaba-token-plan/deepseek-v4-flash-0731:max",
297
+ planner: "alibaba-token-plan/glm-5.2:high",
298
+ architect: "alibaba-token-plan/qwen3.8-max-preview:xhigh",
299
+ critic: "alibaba-token-plan/glm-5.2:xhigh",
300
+ }),
294
301
  profile("alibaba-token-plan-qwenmaxxing", ["alibaba-token-plan"], {
295
302
  default: "alibaba-token-plan/qwen3.8-max-preview:medium",
296
303
  executor: "alibaba-token-plan/qwen3.8-max-preview:low",
@@ -361,6 +368,7 @@ const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
361
368
  "minimax-medium": { displayName: "MiniMax Medium", providerGroup: "MINIMAX" },
362
369
  "minimax-pro": { displayName: "MiniMax Pro", providerGroup: "MINIMAX" },
363
370
  "alibaba-token-plan-balanced": { displayName: "Balanced", providerGroup: "ALIBABA TOKEN PLAN" },
371
+ "alibaba-token-plan-pro": { displayName: "Pro", providerGroup: "ALIBABA TOKEN PLAN" },
364
372
  "alibaba-token-plan-qwenmaxxing": { displayName: "QwenMaxxing", providerGroup: "ALIBABA TOKEN PLAN" },
365
373
  "opus-codex": { displayName: "Opus + Codex", providerGroup: "COMBOS" },
366
374
  "codex-opencodego": { displayName: "Codex + OpenCodeGo", providerGroup: "COMBOS" },