@gajae-code/coding-agent 0.11.9 → 0.11.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 (59) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/dist/types/config/model-registry.d.ts +2 -0
  3. package/dist/types/gjc-runtime/state-writer.d.ts +1 -0
  4. package/dist/types/modes/controllers/selector-controller.d.ts +16 -8
  5. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  6. package/dist/types/sdk/bus/index.d.ts +8 -1
  7. package/dist/types/sdk/bus/operator-runtime.d.ts +1 -0
  8. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -7
  9. package/dist/types/sdk/bus/telegram-daemon.d.ts +74 -5
  10. package/dist/types/sdk/session.d.ts +7 -0
  11. package/dist/types/session/agent-session.d.ts +2 -0
  12. package/dist/types/session/internal/managed-session-scope.d.ts +1 -1
  13. package/dist/types/task/provider-retry-status.d.ts +13 -0
  14. package/dist/types/task/types.d.ts +3 -0
  15. package/dist/types/tools/index.d.ts +3 -1
  16. package/package.json +8 -8
  17. package/scripts/generate-sdk-operation-inventory.ts +1 -0
  18. package/src/cli/notify-cli.ts +1 -0
  19. package/src/config/model-profiles.ts +7 -7
  20. package/src/config/model-registry.ts +4 -2
  21. package/src/eval/js/tool-bridge.ts +1 -1
  22. package/src/gjc-runtime/deep-interview-draft.ts +2 -1
  23. package/src/gjc-runtime/deep-interview-recorder.ts +16 -1
  24. package/src/gjc-runtime/state-writer.ts +1 -1
  25. package/src/hooks/skill-state.ts +23 -1
  26. package/src/internal-urls/docs-index.generated.ts +3 -3
  27. package/src/modes/controllers/selector-controller.ts +38 -8
  28. package/src/prompts/agent-fragments/restricted-bash.md +1 -1
  29. package/src/prompts/agents/architect.md +8 -1
  30. package/src/prompts/agents/critic.md +8 -1
  31. package/src/prompts/agents/planner.md +8 -1
  32. package/src/sdk/broker/identity.ts +22 -12
  33. package/src/sdk/bus/daemon-paths.ts +2 -0
  34. package/src/sdk/bus/index.ts +60 -5
  35. package/src/sdk/bus/notification-service.ts +19 -5
  36. package/src/sdk/bus/operator-runtime.ts +31 -5
  37. package/src/sdk/bus/telegram-cli.ts +6 -13
  38. package/src/sdk/bus/telegram-daemon-cli.ts +8 -3
  39. package/src/sdk/bus/telegram-daemon-contract.ts +13 -7
  40. package/src/sdk/bus/telegram-daemon-control.ts +14 -5
  41. package/src/sdk/bus/telegram-daemon.ts +667 -118
  42. package/src/sdk/protocol/operation-inventory.generated.json +11 -0
  43. package/src/sdk/session.ts +12 -0
  44. package/src/session/agent-session.ts +54 -10
  45. package/src/session/internal/managed-session-scope.ts +133 -117
  46. package/src/skill-state/workflow-mutation-guard.ts +41 -7
  47. package/src/task/executor.ts +141 -39
  48. package/src/task/provider-retry-status.ts +88 -0
  49. package/src/task/render.ts +45 -26
  50. package/src/task/types.ts +3 -0
  51. package/src/tools/bash-allowed-prefixes.ts +45 -1
  52. package/src/tools/index.ts +3 -1
  53. package/src/tools/subagent-render.ts +74 -16
  54. package/src/tools/subagent.ts +2 -0
  55. package/vendor/insane-search/MANIFEST.json +4 -1
  56. package/vendor/insane-search/engine/bias_check.py +5 -0
  57. package/vendor/insane-search/engine/fetch_chain.py +67 -1
  58. package/vendor/insane-search/engine/transport.py +13 -2
  59. package/vendor/insane-search/engine/validators.py +4 -1
package/CHANGELOG.md CHANGED
@@ -2,21 +2,38 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.11.10] - 2026-07-25
6
+ ### Changed
7
+
8
+ - The built-in `claude-opus`, `opus-codex`, and `fable-opus-codex` presets now use `anthropic/claude-opus-5` instead of `anthropic/claude-opus-4-8`, with effort suffixes preserved; `packages/ai/src/models.json` was regenerated so `anthropic/claude-opus-5` resolves; non-opus roles (`anthropic/claude-sonnet-5` executor/planner overrides, codex and fable roles) are unchanged.
9
+
5
10
  ## [0.11.9] - 2026-07-24
6
11
 
7
12
  ### Fixed
8
13
 
14
+ - Restricted role-agent `bash` now accepts literal mid-word tildes, so git revision syntax such as `git diff HEAD~1` no longer has to be quoted. Bash performs tilde expansion only at the start of a word, so word-initial forms (`~`, `~/path`, `~user`) remain blocked.
15
+ - Restricted role-agent `bash` now rejects unquoted tildes at every bash expansion position inside assignment words, including the compound `name+=value` form, so `A=~`, `A+=~`, `foo=~root/bar`, `A=x:~`, `A+=x:~`, and repeated colon segments such as `a=x:~:y:~` fail closed. Tildes bash does not expand — mid-word git revisions (`HEAD~1`), non-assignment words (`--opt=~`, `1abc=~`, `a++=~`, `a+b=~`), and quoted forms — remain allowed (#3117).
16
+ - Read-only role agents (`architect`, `planner`, `critic`) now receive the `irc` coordination tool and a read-only git prefix set (`status`, `log`, `show`, `diff`, `blame`, `rev-parse`, `ls-files`) in restricted bash; mutating git and arbitrary shell stay blocked. `irc` also stays in the initial active tool set for subagents whenever the parent runtime reports IRC availability, instead of costing a discovery round-trip (#3109).
17
+ - The restricted-bash workflow guard now allows `/dev/null` redirects (so `cmd 2>/dev/null` is no longer treated as a repository write during planning phases) while keeping `/dev/stdout`, `/dev/stderr`, and `/dev/fd/<n>` blocked, failing closed on `exec` redirections, and recognizing `>|`, `>&path`, `<>`, path-qualified writers, and every `dd of=` operand. Hook-seeded deep-interview state is now gated on `isNativeDeepInterviewV1` and seeded as a native v1 envelope, so typed operations no longer fail with `DI_STATE_SCHEMA_INVALID` and run the interview manually (#3127).
18
+ - The vendored `insane-search` engine no longer treats a `429` as terminal: rate-limited probe and grid candidates back off (linear escalation honoring `Retry-After`, hard-capped at 30s) and continue through grid diversity and browser fallback. The backoff base from `INSANE_RATE_LIMIT_BACKOFF_S` is validated and clamped, so non-numeric, `NaN`, infinite, negative, or huge values can no longer raise, hang, or defeat a per-attempt deadline, and sleeps stay short enough to honor cancellation (#3131).
19
+ - ACP sessions now apply execution permission decisions to eval calls and to tools invoked from JavaScript or Python eval contexts, while non-ACP session behavior remains unchanged.
20
+ - Interactive prompt cancellation now reaches API-key preflight through `ModelRegistry`, allowing aborted submissions to clear immediately even while a shared credential-usage request continues in the background.
9
21
  - Alibaba Token Plan canonical first-event timeouts now surface without session retry/fallback replay and are not internally retried by auto-compaction, preventing repeated provider usage (#3026).
22
+ - Delegated-task and subagent status surfaces now distinguish provider recovery from normal running, identify first-event versus idle-stream stalls, show retry budget and provider-progress age, and aggregate concurrent degradation by provider (#3071).
23
+ - Telegram notification daemon ownership hardening (#3048): Bot API outcomes now share one honest classifier so both the initiating `429` response and cooldown-suppressed calls settle retryably instead of being lost or falsely rejected, including selected acknowledgements; exclusive operator work is registered before its callback can throw; notification health degrades corrupt daemon-state JSON to a warning; root-registration ownership tokens propagate through injected and built-in ensure, rollback, reconciliation, teardown, and abandoned-startup cleanup seams, with token-bearing rows refusing tokenless cleanup while genuinely legacy rows retain root-match behavior; and initial daemon readiness is published only after the matching heartbeat sidecar rename is durable, so no waiter can attach during the proof window.
24
+ - `/new`, `fork()`, handoff, `/resume`, and branch/tree-jump transitions now complete verified managed `local://` legacy-root migration for the successor session identity *before* that identity is published to the agent, the workflow-gate emitter, or extension hooks, so no observer can resolve `local://` against an ungated root across the gate's `await` boundary. Matches cold-start `createAgentSession()` (#2797) and extends `/resume` (#2925). Sending a prompt right after `/new` no longer fails with "local:// legacy migration must complete before path resolution".
10
25
 
11
26
  - Telegram notification topics now fence malformed successful `createForumTopic` responses per session endpoint, preventing repeated ambiguous topic creation while keeping explicit Bot API failures retryable.
12
27
  - Windows managed-session resume no longer reports `durability_failed` when Bun rejects `fsync` on the read-only descriptor used to revalidate an existing canonical binding; Windows now uses an owner-writable descriptor for that durability fence while retaining no-follow and pre/post identity/content checks.
13
- - SDK daemon CLI end-to-end tests now drain spawned child stdout and stderr concurrently with process exit, preventing CI pipe teardown races from replacing the product exit contract with SIGPIPE status 141 (#3024).
28
+ - SDK daemon CLI end-to-end tests now capture spawned child stdout and stderr through temporary files instead of pipes, removing the CI pipe teardown race that replaced the product exit contract with SIGPIPE status 141 (#3024).
14
29
  - Interactive launch bootstrap is now suppressed for parser-accepted `--print=`, `--help=`, and `--version=` equals forms, keeping non-interactive output free of the warming-workspace preamble on TTYs.
15
30
  - Managed legacy-session artifact migration now accepts up to 50,000 files, processes copy work in bounded batches, and reports capacity exhaustion separately from unsafe artifact topology (#2935).
16
31
  - Kimi Code first-event timeouts now surface after the provider's continuous first-event wait instead of replaying the full request from zero.
17
32
 
18
33
  - Rejected subagent schema payloads now retain their complete structured data in canonical output artifacts; inline results remain bounded while `agent://` output stays lossless (#2894).
19
34
  - Managed legacy-session artifact migration now validates Windows directory roots from the native tree snapshot, tolerates only lazy metadata on plain Windows directories, and replays both clean and cleanup-pending detaches while retaining fail-closed receipt validation. Canonical binding durability sync uses a writable no-follow handle on Windows NTFS stacks that reject `FlushFileBuffers` on read-only handles (#3015, #2913).
35
+ - A single managed session tombstone that fails to reconcile (e.g. an artifact directory identity mismatch) no longer blocks resume or delete of every other session in the same managed scope; the failure is isolated to that tombstone and logged, unless it belongs to the session currently being opened, which still fails closed.
36
+
20
37
  ## [0.11.8] - 2026-07-23
21
38
  ### Added
22
39
 
@@ -387,12 +387,14 @@ export declare class ModelRegistry {
387
387
  */
388
388
  getApiKey(model: Model<Api>, sessionId?: string, options?: {
389
389
  credentialSelector?: AuthCredentialSelector;
390
+ signal?: AbortSignal;
390
391
  }): Promise<string | undefined>;
391
392
  /**
392
393
  * Get API key for a provider (e.g., "openai").
393
394
  */
394
395
  getApiKeyForProvider(provider: string, sessionId?: string, baseUrl?: string, options?: {
395
396
  credentialSelector?: AuthCredentialSelector;
397
+ signal?: AbortSignal;
396
398
  }): Promise<string | undefined>;
397
399
  /**
398
400
  * Check if a model is using OAuth credentials (subscription).
@@ -183,6 +183,7 @@ export type StrictMutationReadResult = {
183
183
  };
184
184
  export declare function readExistingStateForMutation(filePath: string): Promise<StrictMutationReadResult>;
185
185
  export declare function workflowEnvelopeContentSha256(value: unknown): string;
186
+ export declare function isNativeDeepInterviewV1(value: Record<string, unknown>): boolean;
186
187
  export declare function verifyWorkflowEnvelopeReceiptValue(value: unknown, filePath: string): WorkflowReceiptVerification;
187
188
  export declare function stampWorkflowEnvelopeChecksum<T>(value: T, filePath: string, computedAt?: string): T;
188
189
  export declare function detectWorkflowEnvelopeIntegrityMismatch(filePath: string): Promise<WorkflowEnvelopeIntegrityMismatch | undefined>;
@@ -5,7 +5,7 @@ import type { InteractiveModeContext, OAuthSelectorOptions } from "../../modes/t
5
5
  import { getNotificationConfig, maskToken } from "../../sdk/bus/config";
6
6
  import { proposedTelegramIdentity, reconcileCommittedTelegramConfiguration, removeTelegramConfiguration, saveTelegramInactive } from "../../sdk/bus/notification-orchestration";
7
7
  import { buildNotificationStatusReport, checkNotificationHealth, recoverNotifications, sanitizeDiagnostic, sendNotificationTest } from "../../sdk/bus/notification-service";
8
- import { ensureTelegramDaemonRunningDetailed, resolveTelegramSetupPreflight, unregisterNotificationRoot } from "../../sdk/bus/telegram-daemon";
8
+ import { ensureTelegramDaemonRunningDetailed, readNotificationRootRegistration, resolveTelegramSetupPreflight, unregisterNotificationRoot } from "../../sdk/bus/telegram-daemon";
9
9
  import { runTelegramSetup } from "../../sdk/bus/telegram-setup";
10
10
  import { type CredentialAutoImportStateStore } from "../../setup/credential-auto-import";
11
11
  import { type CommandPaletteAction } from "../components/command-palette";
@@ -28,6 +28,13 @@ export interface NotificationsEditorAdapterContext {
28
28
  sessionManager: Pick<InteractiveModeContext["sessionManager"], "getCwd" | "getSessionId">;
29
29
  notifyConfigChanged?: () => Promise<void> | void;
30
30
  }
31
+ type TelegramDaemonStopResult = {
32
+ ok: boolean;
33
+ message: string;
34
+ before?: {
35
+ health?: string;
36
+ };
37
+ };
31
38
  export interface NotificationsEditorOperationDependencies {
32
39
  getNotificationConfig: typeof getNotificationConfig;
33
40
  maskToken: typeof maskToken;
@@ -43,7 +50,13 @@ export interface NotificationsEditorOperationDependencies {
43
50
  reconcileCommittedTelegramConfiguration: typeof reconcileCommittedTelegramConfiguration;
44
51
  saveTelegramInactive: typeof saveTelegramInactive;
45
52
  removeTelegramConfiguration: typeof removeTelegramConfiguration;
53
+ readNotificationRootRegistration: typeof readNotificationRootRegistration;
46
54
  unregisterNotificationRoot: typeof unregisterNotificationRoot;
55
+ stopTelegramDaemonIfRootRegistryFenceMatches(input: {
56
+ settings: Settings;
57
+ registryFingerprint: string;
58
+ stop: () => Promise<TelegramDaemonStopResult>;
59
+ }): Promise<boolean>;
47
60
  reloadTelegramDaemon(settings: Settings): Promise<{
48
61
  ok: boolean;
49
62
  message: string;
@@ -52,13 +65,7 @@ export interface NotificationsEditorOperationDependencies {
52
65
  ok: boolean;
53
66
  message: string;
54
67
  }>;
55
- stopTelegramDaemon(settings: Settings): Promise<{
56
- ok: boolean;
57
- message: string;
58
- before?: {
59
- health?: string;
60
- };
61
- }>;
68
+ stopTelegramDaemon(settings: Settings): Promise<TelegramDaemonStopResult>;
62
69
  }
63
70
  /**
64
71
  * Concrete service adapter for the direct Notifications settings tab. Secrets remain in this closure's
@@ -128,3 +135,4 @@ export declare class SelectorController {
128
135
  showJobsOverlay(observer: JobsObserver): void;
129
136
  showTasksPane(aggregator: TasksAggregator): void;
130
137
  }
138
+ export {};
@@ -9,6 +9,7 @@ export interface DaemonPaths {
9
9
  dir: string;
10
10
  lock: string;
11
11
  state: string;
12
+ heartbeat: string;
12
13
  ownership: string;
13
14
  roots: string;
14
15
  steal: string;
@@ -29,7 +29,7 @@ import { CursorRegistry, RevisionStore } from "../host/query";
29
29
  import { type SdkStartupFailure } from "../startup-capability";
30
30
  import { type NotificationConfig } from "./config";
31
31
  import { NotificationSessionController } from "./session-control";
32
- import { type EnsureDaemonResult } from "./telegram-daemon";
32
+ import { type EnsureDaemonResult, type RegisterNotificationRootResult } from "./telegram-daemon";
33
33
  /** Where a `session_create` should run. Discriminated by `kind`. */
34
34
  export type SessionCreateTarget = {
35
35
  kind: "existing_path";
@@ -324,6 +324,12 @@ interface SessionRuntime {
324
324
  hostStopped: boolean;
325
325
  serverStopped: boolean;
326
326
  brokerRegistrationReleased: boolean;
327
+ /** Managed Telegram root registration released during terminal teardown. */
328
+ notificationRootRegistration?: {
329
+ settings: Settings;
330
+ cwd: string;
331
+ registrationToken?: string;
332
+ };
327
333
  verbosity: "lean" | "verbose";
328
334
  sessionTag: string;
329
335
  /** Whether the agent loop is currently running (drives the typing indicator). */
@@ -511,6 +517,7 @@ export declare function createNotificationsExtension(api: ExtensionAPI, options?
511
517
  settings: Settings;
512
518
  cwd: string;
513
519
  sessionId: string;
520
+ onRegistered?: (registration: RegisterNotificationRootResult) => void;
514
521
  }) => Promise<EnsureDaemonResult>;
515
522
  ensureProviderDaemon?: (provider: "discord" | "slack", settings: Settings) => Promise<unknown>;
516
523
  /** Suppress auto-delivery for a GJC-spawned child under `sessionScope=primary`. */
@@ -47,6 +47,7 @@ export declare class NotificationOperatorRuntime {
47
47
  stopInterval(name: string): void;
48
48
  stopAllIntervals(): void;
49
49
  runExclusive(name: string, fn: () => Promise<void>): Promise<void>;
50
+ joinExclusive(name: string, timeoutMs: number): Promise<boolean>;
50
51
  sleep(ms: number, signal?: AbortSignal): Promise<void>;
51
52
  now(): number;
52
53
  }
@@ -5,12 +5,9 @@
5
5
  /** Protocol version the daemon advertises in its ClientHello. */
6
6
  export declare const NOTIFICATION_PROTOCOL_VERSION = 3;
7
7
  /**
8
- * Operational generation the current daemon build speaks. Decoupled from
9
- * {@link NOTIFICATION_PROTOCOL_VERSION} (#2304): additive `tool_activity` /
10
- * `reasoning_summary` frames do not bump the wire protocol version, but a
11
- * freshly-upgraded host must still recognize an older, still-live daemon that
12
- * predates capability-gated frame enforcement and trigger a reload. Bump this
13
- * on every daemon-behavior change independent of the wire version.
8
+ * Guarded behavior-inventory version for the current daemon build. Bump this
9
+ * on every guarded daemon-behavior change independent of the wire version; it
10
+ * does not force a live daemon reload by itself.
14
11
  * The current development baseline already includes #2299's generation 4,
15
12
  * incarnation fencing in generation 5, owner-lock authority in generation 6,
16
13
  * identity-atomic transition markers in generation 7, stable signaling plus
@@ -38,5 +35,13 @@ export declare const NOTIFICATION_PROTOCOL_VERSION = 3;
38
35
  * cooldown and lazy Telegram topic lifecycle safeguards (#2956, #2960, #2984).
39
36
  * Generation 27 refreshes retained native path and process authority semantics.
40
37
  * Generation 28 rejects special files before retained native authority opens.
38
+ * Generation 29 adds serving-epoch compatibility, sidecar heartbeat, root GC,
39
+ * and Bot API cooldown structural fixes (#2956, #2960, #3048).
41
40
  */
42
- export declare const DAEMON_GENERATION = 28;
41
+ export declare const DAEMON_GENERATION = 29;
42
+ /**
43
+ * Serving-compatibility boundary for daemon lifecycle requests. Epoch 1 covers
44
+ * all builds published before this field existed; bump this to force serving
45
+ * convergence and reload of compatible live predecessors.
46
+ */
47
+ export declare const SERVING_EPOCH = 2;
@@ -5,10 +5,10 @@ import { HEARTBEAT_TTL_MS } from "./daemon-paths";
5
5
  import { type DaemonTransitionLock, type NotificationEndpointFile, type NotificationEndpointFileIdentity, type NotificationExactUnlinkResult } from "./notification-service";
6
6
  import { type DaemonProcessReference } from "./telegram-daemon-control";
7
7
  import { type TelegramSetupPreflight } from "./telegram-setup";
8
- export { DAEMON_GENERATION, NOTIFICATION_PROTOCOL_VERSION } from "./telegram-daemon-contract";
8
+ export { DAEMON_GENERATION, NOTIFICATION_PROTOCOL_VERSION, SERVING_EPOCH } from "./telegram-daemon-contract";
9
9
  import { type ControlServerLike, type LifecycleControlServerFactory } from "./lifecycle-control-runtime";
10
10
  import type { OrchestratorDeps } from "./lifecycle-orchestrator";
11
- import { NotificationOperatorRuntime, OperatorBackoffPolicy } from "./operator-runtime";
11
+ import { NotificationOperatorRuntime } from "./operator-runtime";
12
12
  import { type AliasTable, type CallbackRoute } from "./telegram-reference";
13
13
  import { type TopicEndpointBinding } from "./topic-registry";
14
14
  export type EnsureDaemonResult = "owner_spawned" | "attached" | "disabled" | "blocked";
@@ -41,6 +41,8 @@ export interface DaemonState {
41
41
  * notification wire protocol version. Absent on pre-generation state.
42
42
  */
43
43
  generation?: number;
44
+ /** Lifecycle-serving compatibility epoch; absent pre-epoch records are epoch 1. */
45
+ servingEpoch?: number;
44
46
  stoppedAt?: number;
45
47
  }
46
48
  export interface TelegramDaemonFs {
@@ -201,15 +203,59 @@ export declare function liveOwnershipLockDecision(input: {
201
203
  attached: false;
202
204
  provisional: true;
203
205
  } | undefined;
206
+ type OwnerHeartbeatSidecar = {
207
+ pid: number;
208
+ incarnation: string;
209
+ ownerId: string;
210
+ acquisitionId: string;
211
+ heartbeatAt: number;
212
+ };
213
+ export interface OwnerFreshnessSnapshot {
214
+ ownerTag: Pick<OwnerHeartbeatSidecar, "pid" | "incarnation" | "ownerId" | "acquisitionId"> | null;
215
+ effectiveHeartbeatAt: number | undefined;
216
+ legacyEmbedded: boolean;
217
+ state: DaemonState | undefined;
218
+ }
219
+ export declare function readOwnerFreshnessSnapshot(input: {
220
+ settings: Settings;
221
+ fs?: TelegramDaemonFs;
222
+ }): Promise<OwnerFreshnessSnapshot>;
223
+ /** Marker-free steady heartbeat renewal. The final lock reread fences a stale writer. */
224
+ export declare function renewOwnerHeartbeatSidecar(input: {
225
+ settings: Settings;
226
+ ownerId: string;
227
+ acquisitionId?: string;
228
+ fs?: TelegramDaemonFs;
229
+ now?: () => number;
230
+ pid?: number;
231
+ pidIncarnation?: (pid: number) => string | undefined;
232
+ }): Promise<boolean>;
233
+ export interface NotificationRootRegistration {
234
+ root?: string;
235
+ managed?: boolean;
236
+ token?: string;
237
+ }
238
+ export declare function readNotificationRootRegistration(input: {
239
+ settings: Settings;
240
+ sessionId: string;
241
+ fs?: TelegramDaemonFs;
242
+ }): Promise<NotificationRootRegistration>;
243
+ export interface RegisterNotificationRootResult {
244
+ root: string;
245
+ /** Per-registration ownership token fencing a stale same-session cleanup from deleting a live replacement registration. */
246
+ token: string;
247
+ }
204
248
  export declare function registerNotificationRoot(input: {
205
249
  settings: Settings;
206
250
  cwd: string;
207
251
  sessionId: string;
208
252
  fs?: TelegramDaemonFs;
209
- }): Promise<string>;
253
+ }): Promise<RegisterNotificationRootResult>;
210
254
  export interface UnregisterNotificationRootResult {
211
255
  root: string;
212
256
  remainingRoots: number;
257
+ /** Opaque fingerprint of the exact registry written by this successful unregister. */
258
+ registryFingerprint?: string;
213
259
  }
214
260
  /** Remove one session's Telegram scan root without disturbing other live session roots. */
215
261
  export declare function unregisterNotificationRoot(input: {
@@ -217,7 +263,16 @@ export declare function unregisterNotificationRoot(input: {
217
263
  cwd: string;
218
264
  sessionId: string;
219
265
  fs?: TelegramDaemonFs;
266
+ /** Ownership token minted by this runtime's own registration; a stale cleanup presenting an older token never deletes a replacement registration. */
267
+ registrationToken?: string;
220
268
  }): Promise<UnregisterNotificationRootResult>;
269
+ /** Run an action while the roots registry lock excludes any registration or unregister. */
270
+ export declare function withNotificationRootRegistryFence(input: {
271
+ settings: Settings;
272
+ registryFingerprint: string;
273
+ action: () => Promise<void>;
274
+ fs?: TelegramDaemonFs;
275
+ }): Promise<boolean>;
221
276
  /**
222
277
  * Leak artifact prefixes left when native exact-unlink cleanup is retained
223
278
  * (#2956). Nothing previously reaped these; they accumulate in the notifications
@@ -319,6 +374,7 @@ export declare function isFreshLiveOwner(input: {
319
374
  chatId: string;
320
375
  pidAlive: (pid: number) => boolean;
321
376
  pidIncarnation?: (pid: number) => string | undefined;
377
+ effectiveHeartbeatAt?: number;
322
378
  }): boolean;
323
379
  /** True only when a physically live matching owner can serve this build's daemon lifecycle contract. */
324
380
  export declare function isCurrentCompatibleOwner(input: {
@@ -328,6 +384,7 @@ export declare function isCurrentCompatibleOwner(input: {
328
384
  chatId: string;
329
385
  pidAlive: (pid: number) => boolean;
330
386
  pidIncarnation?: (pid: number) => string | undefined;
387
+ effectiveHeartbeatAt?: number;
331
388
  }): boolean;
332
389
  export declare function acquireDaemonOwnership(input: {
333
390
  settings: Settings;
@@ -399,7 +456,7 @@ export declare function retireProvisionalDaemonOwnership(input: {
399
456
  /** Only no-child confirmation may retire a ready-like launcher publication. */
400
457
  allowReadyWithoutChildPid?: boolean;
401
458
  }): Promise<boolean>;
402
- /** Wait for a matching current-generation daemon to publish a ready state. */
459
+ /** Wait for a matching compatible owner to publish a ready state. Readiness is serving-epoch gated (same-epoch cross-generation owners attach, per isCurrentCompatibleOwner); generation is inventory-only here. Mutation paths still require exact current-generation equality. */
403
460
  export declare function waitForTelegramDaemonReady(input: {
404
461
  settings: Settings;
405
462
  ownerId?: string;
@@ -548,6 +605,9 @@ export declare function ensureTelegramDaemonRunningDetailed(input: {
548
605
  settings: Settings;
549
606
  cwd: string;
550
607
  sessionId: string;
608
+ registerRoot?: boolean;
609
+ /** Receives each registration this ensure operation minted so the caller can fence its own later cleanup. */
610
+ onRegistered?: (registration: RegisterNotificationRootResult) => void;
551
611
  }, deps?: TelegramDaemonDeps): Promise<EnsureTelegramDaemonDetailedResult>;
552
612
  /**
553
613
  * Legacy compatibility mapping for callers that only distinguish ownership from
@@ -600,7 +660,10 @@ export type TelegramPollResult = {
600
660
  export interface TelegramUpdatePollerOptions {
601
661
  botApi: BotApi;
602
662
  runtime: NotificationOperatorRuntime;
603
- backoff: OperatorBackoffPolicy;
663
+ backoff: {
664
+ next(): number;
665
+ reset(): void;
666
+ };
604
667
  processUpdate: (update: unknown) => Promise<TelegramUpdateOutcome>;
605
668
  health?: TelegramPollHealth;
606
669
  }
@@ -785,6 +848,9 @@ export declare class TelegramNotificationDaemon {
785
848
  private readonly pool;
786
849
  private readonly poller;
787
850
  private readonly dispatchState;
851
+ /** Bot-wide flood-control window; inbound polling remains eligible during it. */
852
+ private botCooldownUntil;
853
+ private warnedBotCooldownUntil;
788
854
  /** Original markdown of rich messages we sent (chat+message_id), for restoring reply context on inbound replies. */
789
855
  private readonly replyStore;
790
856
  /** Per-session debounce + monotonic draft-id state for opt-in draft streaming. */
@@ -871,6 +937,9 @@ export declare class TelegramNotificationDaemon {
871
937
  private refreshBotIdentity;
872
938
  /** Map a lifecycle response/error to a user-facing message (G010 surfacing). */
873
939
  private formatLifecycleResponse;
940
+ private callBotApi;
941
+ private readonly callBotApiClassified;
942
+ private botApiWithOutcomeCollector;
874
943
  constructor(opts: TelegramDaemonOptions);
875
944
  private createSessionRouter;
876
945
  loadAliases(): Promise<void>;
@@ -149,6 +149,13 @@ export interface CreateAgentSessionOptions {
149
149
  goalToolAllowedOps?: readonly ("create" | "get" | "complete" | "resume" | "drop" | "pause")[];
150
150
  /** Optional per-session allowlist for tools exposed through search_tool_bm25. */
151
151
  discoverableToolAllowedNames?: readonly string[];
152
+ /**
153
+ * Discoverable built-in tools that must stay in the initial active set even when
154
+ * `tools.discoveryMode === "all"` would otherwise hide them behind `search_tool_bm25`.
155
+ * Used for coordination tools (e.g. `irc`) that a subagent must be able to use
156
+ * immediately without first spending a discovery round-trip to find them.
157
+ */
158
+ alwaysActiveToolNames?: readonly string[];
152
159
  /** Optional shared agent registry for IRC routing. Default: AgentRegistry.global(). */
153
160
  agentRegistry?: AgentRegistry;
154
161
  /** Parent task ID prefix for nested artifact naming (e.g., "6-Extensions") */
@@ -677,6 +677,8 @@ export declare class AgentSession {
677
677
  * Get a tool by name from the registry.
678
678
  */
679
679
  getToolByName(name: string): AgentTool | undefined;
680
+ /** Get a registered tool with the same guards used for model-facing execution. */
681
+ getToolForExecution(name: string): AgentTool | undefined;
680
682
  /**
681
683
  * Register a UI/control-plane request handler for a currently foregrounded
682
684
  * managed bash execution. This is intentionally narrower than generic
@@ -129,7 +129,7 @@ export declare function matchesMigrationArtifactRoot(pathname: string, identity:
129
129
  }, expectedTree: NativeDirectoryTreeSnapshot, platform?: NodeJS.Platform): boolean;
130
130
  export declare function restorePreparedArtifactRoot(scope: ManagedScope, source: ManagedCandidate, lock?: ManagedStorageLock): void;
131
131
  /** Resume tombstoned cleanup under its original operation lease without restoring retired candidates. */
132
- export declare function reconcileManagedTombstones(scope: ManagedScope): Promise<void>;
132
+ export declare function reconcileManagedTombstones(scope: ManagedScope, expectedCandidate?: ManagedCandidate): Promise<void>;
133
133
  /** Create the v2 binding and private write protocol directories before managed writes. */
134
134
  export declare function prepareManagedSessionScopeForWrite(scope: ManagedScope, policy?: ManagedSessionSecurityPolicy, authority?: ManagedCandidateWriteAuthority, expectedCandidate?: ManagedCandidate, expectedIdentity?: ResumeSessionIdentity): Promise<ManagedScopeResolution>;
135
135
  /**
@@ -0,0 +1,13 @@
1
+ import type { AgentProgress, TaskToolDetails } from "./types";
2
+ export type ProviderRetryKind = NonNullable<AgentProgress["retryState"]>["kind"];
3
+ export declare function classifyProviderRetry(errorMessage: string): ProviderRetryKind;
4
+ export declare function providerNameFromModel(model: string | undefined): string | undefined;
5
+ export declare function providerRetryPhaseLabel(kind: ProviderRetryKind): string;
6
+ export declare function providerProgressAgeLabel(retryState: NonNullable<AgentProgress["retryState"]>, nowMs: number): string;
7
+ export declare function hasActiveProviderRetryInTaskDetails(details: TaskToolDetails, seen?: WeakSet<object>): boolean;
8
+ export declare function hasActiveProviderRetryInProgress(progress: AgentProgress, seen?: WeakSet<object>): boolean;
9
+ export interface ProviderDegradationGroup {
10
+ provider: string;
11
+ count: number;
12
+ }
13
+ export declare function collectProviderDegradationGroups(progress: readonly AgentProgress[]): ProviderDegradationGroup[];
@@ -416,6 +416,9 @@ export interface AgentProgress {
416
416
  attempt: number;
417
417
  maxAttempts: number;
418
418
  unbounded?: boolean;
419
+ kind: "first_event_timeout" | "idle_stream_stall" | "provider_error";
420
+ provider?: string;
421
+ lastProviderProgressAtMs?: number;
419
422
  delayMs: number;
420
423
  errorMessage: string;
421
424
  startedAtMs: number;
@@ -198,9 +198,11 @@ export interface ToolSession {
198
198
  /** Agent identity used for IRC routing. Returns the registry id (e.g. "0-Main", "0-AuthLoader"). */
199
199
  getAgentId?: () => string | null;
200
200
  /** Look up a registered tool by name (used by the eval js backend's tool bridge). */
201
+ getToolByName?: (name: string) => AgentTool | undefined;
202
+ /** Look up a registered tool with the session's execution guards applied. */
203
+ getToolForExecution?: (name: string) => AgentTool | undefined;
201
204
  /** Purge undelivered queued custom messages matching the predicate. Returns counts. */
202
205
  purgeQueuedCustomMessages?: (predicate: (message: CustomMessage) => boolean) => PurgeQueuedCustomMessagesResult;
203
- getToolByName?: (name: string) => AgentTool | undefined;
204
206
  /** Agent registry for IRC routing across live sessions. */
205
207
  agentRegistry?: AgentRegistry;
206
208
  /** Optional restricted bash command prefixes for read-only role agents and constrained modes. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.11.9",
4
+ "version": "0.11.10",
5
5
  "description": "Gajae Code CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo and Gajae Code Contributors",
@@ -53,13 +53,13 @@
53
53
  "@agentclientprotocol/sdk": "1.2.1",
54
54
  "@babel/parser": "^7.29.3",
55
55
  "@mozilla/readability": "^0.6.0",
56
- "@gajae-code/stats": "0.11.9",
57
- "@gajae-code/agent-core": "0.11.9",
58
- "@gajae-code/ai": "0.11.9",
59
- "@gajae-code/bridge-client": "0.11.9",
60
- "@gajae-code/natives": "0.11.9",
61
- "@gajae-code/tui": "0.11.9",
62
- "@gajae-code/utils": "0.11.9",
56
+ "@gajae-code/stats": "0.11.10",
57
+ "@gajae-code/agent-core": "0.11.10",
58
+ "@gajae-code/ai": "0.11.10",
59
+ "@gajae-code/bridge-client": "0.11.10",
60
+ "@gajae-code/natives": "0.11.10",
61
+ "@gajae-code/tui": "0.11.10",
62
+ "@gajae-code/utils": "0.11.10",
63
63
  "@puppeteer/browsers": "^2.13.0",
64
64
  "@types/turndown": "5.0.6",
65
65
  "@xterm/headless": "^6.0.0",
@@ -70,6 +70,7 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
70
70
  "agent_session:getAsyncDeliveryStateForAcp":
71
71
  "internal ACP lifecycle quiescence plumbing, not a user-facing control seam",
72
72
  "agent_session:getToolByName": "internal accessor/plumbing, not a user-facing control seam",
73
+ "agent_session:getToolForExecution": "internal accessor/plumbing, not a user-facing control seam",
73
74
  "agent_session:registerForegroundBashBackgroundRequestHandler":
74
75
  "internal accessor/plumbing, not a user-facing control seam",
75
76
  "agent_session:hasForegroundBashBackgroundRequestHandler":
@@ -413,6 +413,7 @@ async function runTelegramSetup(cmd: NotifyCommandArgs, deps: NotifyCommandDeps)
413
413
  settings,
414
414
  cwd: process.cwd(),
415
415
  sessionId: `notify-cli-${process.pid}`,
416
+ registerRoot: false,
416
417
  }),
417
418
  persistInactive: async marker => await persistTelegramActivationMarker(settings, marker),
418
419
  clearInactive: async marker => await clearTelegramActivationMarker(settings, marker),
@@ -100,11 +100,11 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
100
100
  architect: "opencode-go/deepseek-v4-pro",
101
101
  }),
102
102
  profile("claude-opus", ["anthropic"], {
103
- default: "anthropic/claude-opus-4-8:xhigh",
103
+ default: "anthropic/claude-opus-5:xhigh",
104
104
  executor: "anthropic/claude-sonnet-5",
105
- planner: "anthropic/claude-opus-4-8:low",
106
- critic: "anthropic/claude-opus-4-8:high",
107
- architect: "anthropic/claude-opus-4-8:xhigh",
105
+ planner: "anthropic/claude-opus-5:low",
106
+ critic: "anthropic/claude-opus-5:high",
107
+ architect: "anthropic/claude-opus-5:xhigh",
108
108
  }),
109
109
  profile("claude-fable", ["anthropic"], {
110
110
  default: "anthropic/claude-fable-5:xhigh",
@@ -271,7 +271,7 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
271
271
  critic: "alibaba-token-plan/qwen3.8-max-preview:xhigh",
272
272
  }),
273
273
  profile("opus-codex", ["anthropic", "openai-codex"], {
274
- default: "anthropic/claude-opus-4-8:xhigh",
274
+ default: "anthropic/claude-opus-5:xhigh",
275
275
  executor: "openai-codex/gpt-5.6-terra:low",
276
276
  planner: "anthropic/claude-sonnet-5",
277
277
  critic: "openai-codex/gpt-5.6-sol:xhigh",
@@ -287,8 +287,8 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
287
287
  profile("fable-opus-codex", ["anthropic", "openai-codex"], {
288
288
  default: "anthropic/claude-fable-5:high",
289
289
  executor: "openai-codex/gpt-5.6-terra:medium",
290
- planner: "anthropic/claude-opus-4-8:medium",
291
- critic: "anthropic/claude-opus-4-8:high",
290
+ planner: "anthropic/claude-opus-5:medium",
291
+ critic: "anthropic/claude-opus-5:high",
292
292
  architect: "openai-codex/gpt-5.6-sol:xhigh",
293
293
  }),
294
294
  ];
@@ -2564,13 +2564,14 @@ export class ModelRegistry {
2564
2564
  async getApiKey(
2565
2565
  model: Model<Api>,
2566
2566
  sessionId?: string,
2567
- options: { credentialSelector?: AuthCredentialSelector } = {},
2567
+ options: { credentialSelector?: AuthCredentialSelector; signal?: AbortSignal } = {},
2568
2568
  ): Promise<string | undefined> {
2569
2569
  return this.#getApiKeyOrNoAuth(model.provider, () =>
2570
2570
  this.authStorage.getApiKey(model.provider, sessionId, {
2571
2571
  baseUrl: model.baseUrl,
2572
2572
  modelId: model.id,
2573
2573
  credentialSelector: options.credentialSelector,
2574
+ signal: options.signal,
2574
2575
  }),
2575
2576
  );
2576
2577
  }
@@ -2582,12 +2583,13 @@ export class ModelRegistry {
2582
2583
  provider: string,
2583
2584
  sessionId?: string,
2584
2585
  baseUrl?: string,
2585
- options: { credentialSelector?: AuthCredentialSelector } = {},
2586
+ options: { credentialSelector?: AuthCredentialSelector; signal?: AbortSignal } = {},
2586
2587
  ): Promise<string | undefined> {
2587
2588
  return this.#getApiKeyOrNoAuth(provider, () =>
2588
2589
  this.authStorage.getApiKey(provider, sessionId, {
2589
2590
  baseUrl,
2590
2591
  credentialSelector: options.credentialSelector,
2592
+ signal: options.signal,
2591
2593
  }),
2592
2594
  );
2593
2595
  }
@@ -30,7 +30,7 @@ function toolResultHasError(result: AgentToolResult): boolean {
30
30
  }
31
31
 
32
32
  function getTool(session: ToolSession, name: string): AgentTool {
33
- const tool = session.getToolByName?.(name);
33
+ const tool = session.getToolForExecution ? session.getToolForExecution(name) : session.getToolByName?.(name);
34
34
  if (!tool) {
35
35
  throw new ToolError(`Unknown tool from js runtime: ${name}`);
36
36
  }
@@ -17,6 +17,7 @@ import { type DeepInterviewRepairResult, runDeepInterviewRepairCommand } from ".
17
17
  import { canonicalDeepInterviewJson, validateDeepInterviewV1Envelope } from "./deep-interview-state";
18
18
  import { modeStatePath } from "./session-layout";
19
19
  import {
20
+ isNativeDeepInterviewV1,
20
21
  readExistingStateForMutation,
21
22
  transformGuardedWorkflowEnvelopeAtomic,
22
23
  verifyWorkflowEnvelopeReceiptValue,
@@ -295,7 +296,7 @@ async function revision(cwd: string, session: string): Promise<{ revision: numbe
295
296
  if (receipt === "receipt-missing") throw new Error("DI_RECEIPT_MISSING");
296
297
  if (receipt === "checksum-mismatch") throw new Error("DI_RECEIPT_CHECKSUM_MISMATCH");
297
298
  try {
298
- if (receipt === "native-valid") validateDeepInterviewV1Envelope(observed.value);
299
+ if (isNativeDeepInterviewV1(observed.value)) validateDeepInterviewV1Envelope(observed.value);
299
300
  } catch {
300
301
  throw new Error("DI_STATE_SCHEMA_INVALID");
301
302
  }
@@ -101,6 +101,21 @@ export interface TransitionValidationResult {
101
101
  // Pure helpers: records
102
102
  // =============================================================================
103
103
 
104
+ /**
105
+ * `deepInterview.dimension` is agent-supplied free text on post-topology asks, but the persisted
106
+ * envelope only accepts canonical dimension ids. Fold obvious case/label variants down, and drop
107
+ * anything unrecognized rather than persisting a value the validator rejects.
108
+ */
109
+ function canonicalDimension(value: string | undefined): string | undefined {
110
+ if (value === undefined) return undefined;
111
+ const normalized = value.trim().toLowerCase();
112
+ if (normalized === "topology") return "topology";
113
+ for (const dimension of ["goal", "constraints", "criteria", "context"]) {
114
+ if (normalized === dimension || normalized.startsWith(`${dimension} `)) return dimension;
115
+ }
116
+ return undefined;
117
+ }
118
+
104
119
  export function buildAnswerShell(
105
120
  input: DeepInterviewAnswerInput,
106
121
  now: string = new Date().toISOString(),
@@ -116,7 +131,7 @@ export function buildAnswerShell(
116
131
  selected_options: input.selectedOptions,
117
132
  custom_input: input.customInput,
118
133
  component: input.component,
119
- dimension: input.dimension,
134
+ dimension: canonicalDimension(input.dimension),
120
135
  ambiguity_at_ask: input.ambiguity,
121
136
  lifecycle: "answered",
122
137
  answered_at: now,
@@ -298,7 +298,7 @@ export function workflowEnvelopeContentSha256(value: unknown): string {
298
298
  .update(JSON.stringify(canonicalizeJson(withoutReceiptChecksum(value))))
299
299
  .digest("hex");
300
300
  }
301
- function isNativeDeepInterviewV1(value: Record<string, unknown>): boolean {
301
+ export function isNativeDeepInterviewV1(value: Record<string, unknown>): boolean {
302
302
  if (value.skill !== "deep-interview") return false;
303
303
  return value.schema_version === 1 || (isPlainObject(value.state) && value.state.schema_version === 1);
304
304
  }