@gajae-code/coding-agent 0.11.8 → 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 (101) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/dist/types/cli.d.ts +1 -0
  3. package/dist/types/config/model-profiles.d.ts +2 -3
  4. package/dist/types/config/model-registry.d.ts +2 -0
  5. package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +12 -5
  6. package/dist/types/gjc-runtime/deep-interview-draft.d.ts +8 -0
  7. package/dist/types/gjc-runtime/deep-interview-payload.d.ts +41 -0
  8. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +32 -17
  9. package/dist/types/gjc-runtime/deep-interview-repair.d.ts +17 -0
  10. package/dist/types/gjc-runtime/deep-interview-state.d.ts +135 -4
  11. package/dist/types/gjc-runtime/state-writer.d.ts +45 -0
  12. package/dist/types/gjc-runtime/workflow-manifest.d.ts +5 -3
  13. package/dist/types/modes/components/welcome.d.ts +2 -0
  14. package/dist/types/modes/controllers/selector-controller.d.ts +16 -8
  15. package/dist/types/runtime-mcp/redaction.d.ts +2 -0
  16. package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -1
  17. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  18. package/dist/types/sdk/bus/index.d.ts +8 -1
  19. package/dist/types/sdk/bus/operator-runtime.d.ts +1 -0
  20. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +14 -7
  21. package/dist/types/sdk/bus/telegram-daemon.d.ts +74 -5
  22. package/dist/types/sdk/session.d.ts +7 -0
  23. package/dist/types/session/agent-session.d.ts +2 -0
  24. package/dist/types/session/internal/managed-session-scope.d.ts +13 -3
  25. package/dist/types/session/internal/managed-session-storage.d.ts +8 -3
  26. package/dist/types/session/session-manager.d.ts +5 -1
  27. package/dist/types/session/session-storage.d.ts +13 -0
  28. package/dist/types/skill-state/active-state.d.ts +4 -0
  29. package/dist/types/task/provider-retry-status.d.ts +13 -0
  30. package/dist/types/task/types.d.ts +3 -0
  31. package/dist/types/tools/index.d.ts +3 -1
  32. package/package.json +8 -8
  33. package/scripts/generate-sdk-operation-inventory.ts +1 -0
  34. package/src/cli/mcp-cli.ts +4 -21
  35. package/src/cli/notify-cli.ts +1 -0
  36. package/src/cli.ts +32 -0
  37. package/src/commands/deep-interview.ts +9 -4
  38. package/src/commit/agentic/index.ts +3 -3
  39. package/src/commit/map-reduce/index.ts +2 -2
  40. package/src/config/model-profiles.ts +9 -10
  41. package/src/config/model-registry.ts +4 -2
  42. package/src/defaults/gjc/skills/deep-interview/SKILL.md +78 -163
  43. package/src/eval/js/tool-bridge.ts +1 -1
  44. package/src/gjc-runtime/deep-interview-ambiguity.ts +68 -47
  45. package/src/gjc-runtime/deep-interview-draft.ts +843 -0
  46. package/src/gjc-runtime/deep-interview-payload.ts +262 -0
  47. package/src/gjc-runtime/deep-interview-recorder.ts +310 -227
  48. package/src/gjc-runtime/deep-interview-repair.ts +1674 -0
  49. package/src/gjc-runtime/deep-interview-runtime.ts +264 -103
  50. package/src/gjc-runtime/deep-interview-state.ts +916 -4
  51. package/src/gjc-runtime/ralplan-runtime.ts +3 -1
  52. package/src/gjc-runtime/state-writer.ts +166 -2
  53. package/src/gjc-runtime/workflow-manifest.generated.json +386 -1
  54. package/src/gjc-runtime/workflow-manifest.ts +191 -5
  55. package/src/hooks/skill-state.ts +23 -1
  56. package/src/internal-urls/docs-index.generated.ts +6 -5
  57. package/src/modes/components/welcome.ts +10 -2
  58. package/src/modes/controllers/selector-controller.ts +38 -8
  59. package/src/modes/interactive-mode.ts +29 -9
  60. package/src/prompts/agent-fragments/restricted-bash.md +1 -1
  61. package/src/prompts/agents/architect.md +8 -1
  62. package/src/prompts/agents/critic.md +8 -1
  63. package/src/prompts/agents/planner.md +8 -1
  64. package/src/prompts/system/system-prompt.md +1 -0
  65. package/src/runtime-mcp/json-rpc.ts +7 -4
  66. package/src/runtime-mcp/redaction.ts +30 -0
  67. package/src/sdk/broker/identity.ts +22 -12
  68. package/src/sdk/broker/session-index.ts +11 -1
  69. package/src/sdk/bus/chat-daemon-control.ts +5 -3
  70. package/src/sdk/bus/daemon-paths.ts +2 -0
  71. package/src/sdk/bus/index.ts +60 -5
  72. package/src/sdk/bus/notification-service.ts +19 -5
  73. package/src/sdk/bus/operator-runtime.ts +31 -5
  74. package/src/sdk/bus/telegram-cli.ts +6 -13
  75. package/src/sdk/bus/telegram-daemon-cli.ts +8 -3
  76. package/src/sdk/bus/telegram-daemon-contract.ts +15 -7
  77. package/src/sdk/bus/telegram-daemon-control.ts +14 -5
  78. package/src/sdk/bus/telegram-daemon.ts +685 -120
  79. package/src/sdk/protocol/operation-inventory.generated.json +11 -0
  80. package/src/sdk/session.ts +12 -0
  81. package/src/session/agent-session.ts +140 -10
  82. package/src/session/internal/managed-session-scope.ts +339 -228
  83. package/src/session/internal/managed-session-storage.ts +17 -4
  84. package/src/session/session-manager.ts +96 -33
  85. package/src/session/session-storage.ts +23 -0
  86. package/src/skill-state/active-state.ts +10 -0
  87. package/src/skill-state/workflow-mutation-guard.ts +41 -7
  88. package/src/task/executor.ts +154 -44
  89. package/src/task/provider-retry-status.ts +88 -0
  90. package/src/task/render.ts +45 -26
  91. package/src/task/types.ts +3 -0
  92. package/src/tools/ask.ts +1 -0
  93. package/src/tools/bash-allowed-prefixes.ts +45 -1
  94. package/src/tools/index.ts +3 -1
  95. package/src/tools/subagent-render.ts +74 -16
  96. package/src/tools/subagent.ts +2 -0
  97. package/vendor/insane-search/MANIFEST.json +4 -1
  98. package/vendor/insane-search/engine/bias_check.py +5 -0
  99. package/vendor/insane-search/engine/fetch_chain.py +67 -1
  100. package/vendor/insane-search/engine/transport.py +13 -2
  101. package/vendor/insane-search/engine/validators.py +4 -1
@@ -13,7 +13,9 @@ export type ChatDaemonAction = "stop" | "reload";
13
13
  * authority declaration contract with bounded frame-delivery acknowledgement.
14
14
  * Generation 11 accepts typed retained exact-unlink cleanup authority — a
15
15
  * concrete detached quarantine plus a proven-absent canonical lock pathname —
16
- * when deleting an observed owner-lock lease.
16
+ * when deleting an observed owner-lock lease. Generation 12 refreshes retained
17
+ * native path and process authority semantics. Generation 13 rejects special
18
+ * files before retained native authority opens.
17
19
  */
18
20
  export declare const CHAT_DAEMON_GENERATIONS: Readonly<Record<ChatDaemonKind, number>>;
19
21
  export declare function chatDaemonGeneration(kind: ChatDaemonKind): number;
@@ -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
@@ -36,5 +33,15 @@ export declare const NOTIFICATION_PROTOCOL_VERSION = 3;
36
33
  * Generation 25 adds startup dead-root prune + leak-artifact self-heal
37
34
  * on TelegramNotificationDaemon.run (#2958). Generation 26 adds bounded reload
38
35
  * cooldown and lazy Telegram topic lifecycle safeguards (#2956, #2960, #2984).
36
+ * Generation 27 refreshes retained native path and process authority semantics.
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).
39
40
  */
40
- export declare const DAEMON_GENERATION = 26;
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
@@ -1,6 +1,7 @@
1
1
  import * as native from "@gajae-code/natives";
2
2
  import type { ResumeSessionIdentity } from "../session-manager";
3
- import { type ManagedDirectoryRoot, type ManagedSessionSecurityPolicy } from "./managed-session-storage";
3
+ import { type NativeDirectoryTreeSnapshot } from "../session-storage";
4
+ import { type ManagedDirectoryRoot, type ManagedSessionSecurityPolicy, type ManagedStorageLock } from "./managed-session-storage";
4
5
  export declare const MANAGED_SESSION_LAYOUT_VERSION: 2;
5
6
  export declare const MANAGED_SESSION_IDENTITY_VERSION: 1;
6
7
  export declare const MANAGED_SESSION_BINDING_FILE = ".gjc-managed-session-scope.v2.json";
@@ -67,7 +68,7 @@ export type ManagedCandidateListing = {
67
68
  code: "scan_failed" | "unsafe_root" | "invalid_candidate";
68
69
  message: string;
69
70
  };
70
- export type ManagedOpenFailure = "migration_busy" | "binding_conflict" | "binding_invalid" | "destination_conflict" | "source_changed" | "unsafe_artifacts" | "durability_failed" | "atomic_unavailable" | "invalid_request" | "durability_not_provable" | "migration_retired" | "legacy_migration_disabled" | "managed_storage_unsupported";
71
+ export type ManagedOpenFailure = "migration_busy" | "binding_conflict" | "binding_invalid" | "destination_conflict" | "source_changed" | "unsafe_artifacts" | "artifact_capacity_exceeded" | "durability_failed" | "atomic_unavailable" | "invalid_request" | "durability_not_provable" | "migration_retired" | "legacy_migration_disabled" | "managed_storage_unsupported";
71
72
  export type ManagedOpenCandidateResult = {
72
73
  kind: "opened";
73
74
  path: string;
@@ -114,12 +115,21 @@ interface ManagedScopeInput {
114
115
  export declare function resolveManagedScope(input: ManagedScopeInput): ManagedScopeResolution;
115
116
  /** Resolve a scope for a synchronous write without mutating an existing ACL mismatch. */
116
117
  export declare function resolveManagedScopeForWrite(input: ManagedScopeInput): ManagedScopeResolution;
118
+ export declare function canonicalBindingOpenFlags(platform?: NodeJS.Platform): number;
119
+ export declare function fsyncCanonicalBinding(bindingPath: string, expected: string): void;
117
120
  export declare function ensureManagedScope(scope: ManagedScope, policy?: ManagedSessionSecurityPolicy): Promise<ManagedScopeResolution>;
118
121
  /** Synchronously create and validate the v2 binding before a default session writer exists. */
119
122
  export declare function prepareManagedSessionScopeForWriteSync(scope: ManagedScope, policy?: ManagedSessionSecurityPolicy, authority?: ManagedCandidateWriteAuthority): ManagedScopeResolution;
120
123
  export declare function listManagedCandidates(scope: ManagedScope): ManagedCandidateListing;
124
+ export declare function matchesMigrationArtifactRoot(pathname: string, identity: {
125
+ dev: bigint;
126
+ ino: bigint;
127
+ size: bigint;
128
+ mtimeNs: bigint;
129
+ }, expectedTree: NativeDirectoryTreeSnapshot, platform?: NodeJS.Platform): boolean;
130
+ export declare function restorePreparedArtifactRoot(scope: ManagedScope, source: ManagedCandidate, lock?: ManagedStorageLock): void;
121
131
  /** Resume tombstoned cleanup under its original operation lease without restoring retired candidates. */
122
- export declare function reconcileManagedTombstones(scope: ManagedScope): Promise<void>;
132
+ export declare function reconcileManagedTombstones(scope: ManagedScope, expectedCandidate?: ManagedCandidate): Promise<void>;
123
133
  /** Create the v2 binding and private write protocol directories before managed writes. */
124
134
  export declare function prepareManagedSessionScopeForWrite(scope: ManagedScope, policy?: ManagedSessionSecurityPolicy, authority?: ManagedCandidateWriteAuthority, expectedCandidate?: ManagedCandidate, expectedIdentity?: ResumeSessionIdentity): Promise<ManagedScopeResolution>;
125
135
  /**
@@ -1,9 +1,10 @@
1
1
  import { type NativeDirectoryTreeSnapshot, type NativeOwnerOnlySecurityResult, type RecoveryFsRoot } from "@gajae-code/natives";
2
2
  import { type NativePublishOutcome } from "./native-publish-outcome";
3
3
  export declare const MANAGED_ARTIFACT_MAX_DEPTH = 32;
4
- export declare const MANAGED_ARTIFACT_MAX_FILES = 10000;
4
+ export declare const MANAGED_ARTIFACT_MAX_FILES = 50000;
5
5
  export declare const MANAGED_ARTIFACT_MAX_FILE_BYTES: number;
6
6
  export declare const MANAGED_ARTIFACT_MAX_TOTAL_BYTES: number;
7
+ export declare const MANAGED_ARTIFACT_COPY_BATCH_SIZE = 256;
7
8
  export declare class ManagedPublishError extends Error {
8
9
  readonly classification: "destination_conflict" | "atomic_unavailable" | "invalid_request" | "durability_not_provable" | "identity_mismatch" | "io_error" | "durability_failed";
9
10
  readonly diagnostic: string;
@@ -12,7 +13,7 @@ export declare class ManagedPublishError extends Error {
12
13
  /** Cleanup is safe only when the native move reports that it did not commit. */
13
14
  export declare function mayCleanManagedTreeStaging(error: unknown): boolean;
14
15
  export type ManagedSessionSecurityPolicy = "default" | "windows-existing-verify-first";
15
- export type ManagedStorageFailure = "migration_busy" | "binding_conflict" | "binding_invalid" | "destination_conflict" | "source_changed" | "unsafe_artifacts" | "durability_failed" | "atomic_unavailable" | "durability_not_provable" | "migration_retired" | "managed_storage_unsupported";
16
+ export type ManagedStorageFailure = "migration_busy" | "binding_conflict" | "binding_invalid" | "destination_conflict" | "source_changed" | "unsafe_artifacts" | "artifact_capacity_exceeded" | "durability_failed" | "atomic_unavailable" | "durability_not_provable" | "migration_retired" | "managed_storage_unsupported";
16
17
  export interface ManagedStorageLock {
17
18
  path: string;
18
19
  attemptId: string;
@@ -104,8 +105,12 @@ export declare function replaceManagedFile(destination: string, bytes: Uint8Arra
104
105
  export declare function copyManagedFileNoReplace(source: string, destination: string, snapshot?: ManagedFileSnapshot, root?: ManagedDirectoryRoot, policy?: ManagedSessionSecurityPolicy): Promise<void>;
105
106
  /** Acquire a lease lock with bounded wait, heartbeats, conservative stale reclaim, and fencing. */
106
107
  export declare function acquireManagedLock(locksDirectory: string, name: string, root?: ManagedDirectoryRoot, policy?: ManagedSessionSecurityPolicy): Promise<ManagedStorageLock>;
108
+ export interface ManagedArtifactTreeLimits {
109
+ maxFiles?: number;
110
+ maxTotalBytes?: number;
111
+ }
107
112
  /** Bounds a no-follow artifact tree before it can be copied or deleted. */
108
- export declare function validateManagedArtifactTree(root: string): void;
113
+ export declare function validateManagedArtifactTree(root: string, limits?: ManagedArtifactTreeLimits): void;
109
114
  /** Flush a copied managed artifact tree, including empty directories, before publishing its receipt. */
110
115
  export declare function fsyncManagedArtifactTree(root: string): NativeDirectoryTreeSnapshot;
111
116
  export declare function publishManagedTombstone(destination: string, record: Record<string, unknown>, assertOwned?: () => void): Promise<void>;
@@ -327,6 +327,9 @@ export declare class SessionMigrationPolicyError extends Error {
327
327
  readonly code = "legacy_migration_disabled";
328
328
  constructor();
329
329
  }
330
+ export declare class SessionArtifactCapacityError extends Error {
331
+ constructor(message: string);
332
+ }
330
333
  export type ResumeTailInspection = ResumeTailResumable | ResumeTailTerminal | ResumeTailError;
331
334
  export interface StrictSessionOpenSuccess {
332
335
  kind: "opened";
@@ -334,7 +337,8 @@ export interface StrictSessionOpenSuccess {
334
337
  }
335
338
  export interface StrictSessionOpenFailure {
336
339
  kind: "error";
337
- reason: ResumeTailError["reason"] | "identity-mismatch" | "migration-required";
340
+ reason: ResumeTailError["reason"] | "identity-mismatch" | "migration-required" | "artifact_capacity_exceeded";
341
+ message?: string;
338
342
  }
339
343
  /** Exact transcript bytes and authority captured from one descriptor-bound read. */
340
344
  export interface CapturedSessionTranscriptSnapshot {
@@ -112,6 +112,11 @@ export interface SessionStorage {
112
112
  readSnapshotSync?(path: string): SessionStorageSnapshot;
113
113
  statSync(path: string): SessionStorageStat;
114
114
  listFilesSync(dir: string, pattern: string): string[];
115
+ /** List matching files with mtimes without issuing one JavaScript stat call per path. */
116
+ listFilesByMtime?(dir: string, pattern: string): Promise<Array<{
117
+ path: string;
118
+ mtimeMs: number;
119
+ }>>;
115
120
  /**
116
121
  * Strict directory scan that never suppresses scan/root errors. Used by strict
117
122
  * authorization inventory; the forgiving {@link listFilesSync} stays display-only.
@@ -267,6 +272,10 @@ export declare class FileSessionStorage implements SessionStorage {
267
272
  readSnapshotSync(fpath: string): SessionStorageSnapshot;
268
273
  statSync(path: string): SessionStorageStat;
269
274
  listFilesSync(dir: string, pattern: string): string[];
275
+ listFilesByMtime(dir: string, pattern: string): Promise<Array<{
276
+ path: string;
277
+ mtimeMs: number;
278
+ }>>;
270
279
  listFilesStrictSync(dir: string, pattern: string): string[];
271
280
  exists(path: string): Promise<boolean>;
272
281
  readText(path: string): Promise<string>;
@@ -299,6 +308,10 @@ export declare class MemorySessionStorage implements SessionStorage {
299
308
  readSnapshotSync(path: string): SessionStorageSnapshot;
300
309
  statSync(path: string): SessionStorageStat;
301
310
  listFilesSync(dir: string, pattern: string): string[];
311
+ listFilesByMtime(dir: string, pattern: string): Promise<Array<{
312
+ path: string;
313
+ mtimeMs: number;
314
+ }>>;
302
315
  listFilesStrictSync(dir: string, pattern: string): string[];
303
316
  exists(path: string): Promise<boolean>;
304
317
  readText(path: string): Promise<string>;
@@ -45,6 +45,8 @@ export interface SkillActiveEntry {
45
45
  handoff_at?: string;
46
46
  active_subskills?: ActiveSubskillEntry[];
47
47
  source_state_revision?: number;
48
+ /** Durable ordering token from the source workflow mode-state, not this cache file's revision. */
49
+ committed_mode_state_revision?: number;
48
50
  }
49
51
  export interface SkillActiveState {
50
52
  version?: number;
@@ -85,6 +87,8 @@ export interface SyncSkillActiveStateOptions {
85
87
  handoff_at?: string;
86
88
  active_subskills?: ActiveSubskillEntry[];
87
89
  sourceRevision?: number;
90
+ /** Durable ordering token from the source workflow mode-state, not this cache file's revision. */
91
+ committedModeRevision?: number;
88
92
  }
89
93
  export declare function normalizeWorkflowHudSummary(raw: unknown): WorkflowHudSummary | undefined;
90
94
  export declare function isCanonicalGjcWorkflowSkill(skill: string): skill is CanonicalGjcWorkflowSkill;
@@ -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.8",
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.8",
57
- "@gajae-code/agent-core": "0.11.8",
58
- "@gajae-code/ai": "0.11.8",
59
- "@gajae-code/bridge-client": "0.11.8",
60
- "@gajae-code/natives": "0.11.8",
61
- "@gajae-code/tui": "0.11.8",
62
- "@gajae-code/utils": "0.11.8",
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":
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { getMCPConfigPath, getProjectDir } from "@gajae-code/utils";
8
8
  import { getMCPServer, readMCPConfigFile, removeMCPServer, upsertMCPServer } from "../runtime-mcp/config-writer";
9
+ import { redactMCPEndpoint } from "../runtime-mcp/redaction";
9
10
  import type { MCPConfigFile, MCPServerConfig } from "../runtime-mcp/types";
10
11
 
11
12
  export type MCPAction = "add" | "list" | "remove";
@@ -141,24 +142,6 @@ function redactRecord(
141
142
  );
142
143
  }
143
144
 
144
- function redactUrl(value: string | undefined): string | undefined {
145
- if (!value) return value;
146
- try {
147
- const url = new URL(value);
148
- if (url.username) url.username = REDACTED;
149
- if (url.password) url.password = REDACTED;
150
- if (url.search) {
151
- for (const key of Array.from(url.searchParams.keys())) {
152
- url.searchParams.set(key, REDACTED);
153
- }
154
- }
155
- url.hash = "";
156
- return url.toString();
157
- } catch {
158
- return SENSITIVE_KEY_PATTERN.test(value) ? REDACTED : value;
159
- }
160
- }
161
-
162
145
  function redactArgs(args: string[] | undefined): string[] | undefined {
163
146
  if (!args) return undefined;
164
147
  const redacted: string[] = [];
@@ -196,7 +179,7 @@ export function redactMCPServerConfig(config: MCPServerConfig): MCPServerConfig
196
179
  if (headers) redacted.headers = headers;
197
180
  }
198
181
  if ("url" in redacted) {
199
- const url = redactUrl(redacted.url);
182
+ const url = redactMCPEndpoint(redacted.url);
200
183
  if (url) redacted.url = url;
201
184
  }
202
185
  if ("args" in redacted) {
@@ -207,7 +190,7 @@ export function redactMCPServerConfig(config: MCPServerConfig): MCPServerConfig
207
190
  redacted.auth = {
208
191
  type: redacted.auth.type,
209
192
  credentialId: redacted.auth.credentialId ? REDACTED : undefined,
210
- tokenUrl: redactUrl(redacted.auth.tokenUrl),
193
+ tokenUrl: redactMCPEndpoint(redacted.auth.tokenUrl),
211
194
  clientId: redacted.auth.clientId ? REDACTED : undefined,
212
195
  clientSecret: redacted.auth.clientSecret ? REDACTED : undefined,
213
196
  };
@@ -216,7 +199,7 @@ export function redactMCPServerConfig(config: MCPServerConfig): MCPServerConfig
216
199
  redacted.oauth = {
217
200
  clientId: redacted.oauth.clientId ? REDACTED : undefined,
218
201
  clientSecret: redacted.oauth.clientSecret ? REDACTED : undefined,
219
- redirectUri: redactUrl(redacted.oauth.redirectUri),
202
+ redirectUri: redactMCPEndpoint(redacted.oauth.redirectUri),
220
203
  callbackPort: redacted.oauth.callbackPort,
221
204
  callbackPath: redacted.oauth.callbackPath,
222
205
  };
@@ -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),
package/src/cli.ts CHANGED
@@ -118,6 +118,36 @@ Options:
118
118
  `);
119
119
  }
120
120
 
121
+ export function interactiveBootstrapText(
122
+ argv: readonly string[],
123
+ stdinIsTTY = process.stdin.isTTY,
124
+ stdoutIsTTY = process.stdout.isTTY,
125
+ ): string | undefined {
126
+ if (!stdinIsTTY || !stdoutIsTTY || argv[0] !== "launch") return undefined;
127
+ for (let index = 1; index < argv.length; index++) {
128
+ const arg = argv[index];
129
+ if (
130
+ arg === "--print" ||
131
+ arg?.startsWith("--print=") ||
132
+ arg === "-p" ||
133
+ arg === "--export" ||
134
+ arg?.startsWith("--export=") ||
135
+ arg === "--list-models" ||
136
+ arg?.startsWith("--list-models=") ||
137
+ arg === "--mode" ||
138
+ arg?.startsWith("--mode=") ||
139
+ arg === "--help" ||
140
+ arg?.startsWith("--help=") ||
141
+ arg === "-h" ||
142
+ arg === "--version" ||
143
+ arg?.startsWith("--version=") ||
144
+ arg === "-v"
145
+ )
146
+ return undefined;
147
+ }
148
+ return "\u001b[?25h\u001b[38;5;45mGJC\u001b[0m warming workspace\r\n\r\n> ";
149
+ }
150
+
121
151
  function isNotifyDaemonInternalFastPath(argv: string[]): boolean {
122
152
  return argv[0] === "notify" && argv[1] === "daemon-internal";
123
153
  }
@@ -412,6 +442,8 @@ export async function runCli(argv: string[]): Promise<void> {
412
442
  showStatsFastHelp();
413
443
  return;
414
444
  }
445
+ const bootstrap = interactiveBootstrapText(runArgv);
446
+ if (bootstrap) process.stdout.write(bootstrap);
415
447
  await installRuntimeGlobals();
416
448
  return run({ bin: APP_NAME, version: VERSION, argv: runArgv, commands, help: showHelp });
417
449
  }