@gajae-code/coding-agent 0.12.6 → 0.12.8

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 (150) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/dist/types/async/job-manager.d.ts +36 -1
  3. package/dist/types/commands/notify.d.ts +4 -0
  4. package/dist/types/config/file-lock.d.ts +3 -0
  5. package/dist/types/config/model-discovery-manager.d.ts +6 -1
  6. package/dist/types/config/model-registry.d.ts +2 -0
  7. package/dist/types/config/resolve-config-value.d.ts +2 -1
  8. package/dist/types/config/settings-schema.d.ts +17 -1
  9. package/dist/types/eval/backend.d.ts +2 -2
  10. package/dist/types/exec/bash-executor.d.ts +8 -10
  11. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -36
  12. package/dist/types/internal-urls/registry-helpers.d.ts +0 -18
  13. package/dist/types/main.d.ts +1 -0
  14. package/dist/types/modes/components/hook-selector.d.ts +20 -0
  15. package/dist/types/modes/components/notifications-settings-editor.d.ts +53 -4
  16. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  17. package/dist/types/modes/controllers/selector-controller.d.ts +4 -2
  18. package/dist/types/sdk/broker/broker.d.ts +5 -1
  19. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
  20. package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -3
  21. package/dist/types/sdk/bus/config.d.ts +75 -113
  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 +31 -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 +9 -4
  31. package/dist/types/sdk/client/discovery.d.ts +2 -1
  32. package/dist/types/sdk/host/host.d.ts +2 -23
  33. package/dist/types/sdk/host/query/handlers.d.ts +2 -0
  34. package/dist/types/sdk/host/reverse-leases.d.ts +4 -7
  35. package/dist/types/sdk/index.d.ts +1 -0
  36. package/dist/types/sdk/providers.d.ts +22 -0
  37. package/dist/types/sdk/session-directory.d.ts +1 -1
  38. package/dist/types/session/artifacts.d.ts +5 -8
  39. package/dist/types/session/client-bridge.d.ts +0 -14
  40. package/dist/types/session/internal/managed-session-scope.d.ts +17 -1
  41. package/dist/types/session/internal/managed-session-storage.d.ts +11 -4
  42. package/dist/types/session/internal/native-publish-outcome.d.ts +1 -0
  43. package/dist/types/session/internal/session-open-errors.d.ts +5 -0
  44. package/dist/types/session/messages.d.ts +1 -4
  45. package/dist/types/session/session-manager.d.ts +7 -6
  46. package/dist/types/session/session-storage.d.ts +2 -0
  47. package/dist/types/session/streaming-output.d.ts +0 -32
  48. package/dist/types/task/receipt.d.ts +1 -2
  49. package/dist/types/task/types.d.ts +43 -178
  50. package/dist/types/tools/bash.d.ts +1 -1
  51. package/dist/types/tools/fetch.d.ts +0 -3
  52. package/dist/types/tools/output-meta.d.ts +5 -37
  53. package/dist/types/tools/subagent.d.ts +15 -0
  54. package/dist/types/tools/todo-write.d.ts +3 -0
  55. package/dist/types/tools/tool-result.d.ts +0 -1
  56. package/package.json +10 -8
  57. package/scripts/build-sdk-package-smoke.ts +51 -0
  58. package/scripts/capture-notifications-settings-showcase.ts +1 -1
  59. package/src/async/job-manager.ts +430 -46
  60. package/src/cli/notify-cli.ts +204 -94
  61. package/src/commands/notify.ts +19 -8
  62. package/src/config/file-lock.ts +6 -0
  63. package/src/config/model-discovery-manager.ts +50 -8
  64. package/src/config/model-registry.ts +862 -75
  65. package/src/config/resolve-config-value.ts +10 -8
  66. package/src/config/settings-schema.ts +25 -15
  67. package/src/config/settings.ts +15 -15
  68. package/src/cursor.ts +13 -20
  69. package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -3
  70. package/src/eval/backend.ts +2 -2
  71. package/src/exec/bash-executor.ts +151 -425
  72. package/src/gjc-runtime/session-state-sidecar.ts +12 -2
  73. package/src/gjc-runtime/ultragoal-runtime.ts +3 -280
  74. package/src/gjc-runtime/workflow-manifest.generated.json +1 -40
  75. package/src/gjc-runtime/workflow-manifest.ts +0 -12
  76. package/src/internal-urls/agent-protocol.ts +38 -98
  77. package/src/internal-urls/artifact-protocol.ts +17 -28
  78. package/src/internal-urls/docs-index.generated.ts +7 -7
  79. package/src/internal-urls/registry-helpers.ts +0 -62
  80. package/src/main.ts +43 -7
  81. package/src/modes/DESIGN.md +11 -11
  82. package/src/modes/acp/acp-agent.ts +0 -24
  83. package/src/modes/acp/acp-client-bridge.ts +11 -154
  84. package/src/modes/components/hook-selector.ts +155 -44
  85. package/src/modes/components/model-selector.ts +242 -49
  86. package/src/modes/components/notifications-settings-editor.ts +631 -85
  87. package/src/modes/controllers/extension-ui-controller.ts +114 -15
  88. package/src/modes/controllers/selector-controller.ts +464 -50
  89. package/src/prompts/tools/bash.md +2 -2
  90. package/src/sdk/acp/adapter.ts +9 -200
  91. package/src/sdk/broker/broker.ts +54 -10
  92. package/src/sdk/broker/lifecycle-ledger.ts +34 -1
  93. package/src/sdk/broker/lifecycle.ts +47 -50
  94. package/src/sdk/bus/chat-daemon-cli.ts +12 -5
  95. package/src/sdk/bus/chat-daemon-control.ts +35 -8
  96. package/src/sdk/bus/chat-daemon-runtime.ts +16 -2
  97. package/src/sdk/bus/config.ts +534 -286
  98. package/src/sdk/bus/discord-live-provider.ts +57 -2
  99. package/src/sdk/bus/discord-provider.ts +21 -0
  100. package/src/sdk/bus/index.ts +105 -359
  101. package/src/sdk/bus/notification-orchestration.ts +371 -68
  102. package/src/sdk/bus/notification-service.ts +431 -130
  103. package/src/sdk/bus/session-control.ts +119 -44
  104. package/src/sdk/bus/slack-live-provider.ts +78 -2
  105. package/src/sdk/bus/slack-provider.ts +24 -0
  106. package/src/sdk/bus/telegram-daemon-cli.ts +9 -2
  107. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  108. package/src/sdk/bus/telegram-daemon-control.ts +3 -3
  109. package/src/sdk/bus/telegram-daemon.ts +23 -15
  110. package/src/sdk/client/discovery.ts +10 -4
  111. package/src/sdk/host/host.ts +9 -187
  112. package/src/sdk/host/query/handlers.ts +20 -1
  113. package/src/sdk/host/reverse-leases.ts +42 -249
  114. package/src/sdk/index.ts +1 -0
  115. package/src/sdk/protocol/operation-inventory.generated.json +18 -0
  116. package/src/sdk/protocol/operation-registry.ts +5 -2
  117. package/src/sdk/providers.ts +51 -0
  118. package/src/sdk/session-directory.ts +1 -0
  119. package/src/sdk/session.ts +10 -7
  120. package/src/session/agent-session.ts +59 -202
  121. package/src/session/artifacts.ts +74 -211
  122. package/src/session/client-bridge.ts +0 -15
  123. package/src/session/internal/managed-session-scope.ts +981 -450
  124. package/src/session/internal/managed-session-storage.ts +362 -124
  125. package/src/session/internal/native-publish-outcome.ts +3 -1
  126. package/src/session/internal/session-open-errors.ts +11 -0
  127. package/src/session/messages.ts +7 -32
  128. package/src/session/session-manager.ts +48 -15
  129. package/src/session/session-storage.ts +72 -5
  130. package/src/session/streaming-output.ts +40 -198
  131. package/src/slash-commands/builtin-registry.ts +82 -4
  132. package/src/task/index.ts +237 -65
  133. package/src/task/receipt.ts +2 -4
  134. package/src/task/render.ts +0 -30
  135. package/src/task/types.ts +10 -34
  136. package/src/tools/ask.ts +1 -1
  137. package/src/tools/bash.ts +239 -818
  138. package/src/tools/eval.ts +4 -99
  139. package/src/tools/fetch.ts +15 -77
  140. package/src/tools/find.ts +3 -7
  141. package/src/tools/output-meta.ts +114 -696
  142. package/src/tools/search.ts +4 -9
  143. package/src/tools/subagent.ts +97 -75
  144. package/src/tools/telegram-send.ts +4 -2
  145. package/src/tools/todo-write.ts +47 -0
  146. package/src/tools/tool-result.ts +0 -1
  147. package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +0 -83
  148. package/dist/types/utils/artifact-id.d.ts +0 -2
  149. package/src/gjc-runtime/ultragoal-review-source.ts +0 -378
  150. package/src/utils/artifact-id.ts +0 -6
@@ -21,9 +21,16 @@ export type ChatDaemonAction = "stop" | "reload";
21
21
  * daemon transports. Generation 16 applies Telegram sound-policy configuration
22
22
  * through shared notification parsing. Generation 17 bound managed-session
23
23
  * replacement to exact native filesystem authority; generation 18 retired that
24
- * binding, generation 19 bound exact cleanup to parent/link-count authority,
25
- * generation 20 refreshed retained cleanup semantics, and generation 21 hardens
26
- * exact Bash process-tree ownership shared by chat daemon cleanup.
24
+ * binding, and generation 19 binds exact cleanup to parent/link-count authority
25
+ * while also adding durable provider-intent admission without changing lifecycle
26
+ * behavior. Generation 20 discovers isolated chat-only session endpoints when
27
+ * Telegram identity ownership is blocked. Discord generation 21 applies rustfmt
28
+ * and clippy-equivalent cleanup to the pi-shell process-tree authority (#3682).
29
+ * Discord generation 22 / slack generation 21 refreshes retained cleanup
30
+ * semantics; discord generation 23 / slack generation 22 hardens exact Bash
31
+ * process-tree ownership shared by chat daemon cleanup.
32
+ * Discord generation 24 / slack generation 23 apply provider-completeness and
33
+ * effective-enable admission to chat daemon lifecycle controls.
27
34
  */
28
35
  export declare const CHAT_DAEMON_GENERATIONS: Readonly<Record<ChatDaemonKind, number>>;
29
36
  export declare function chatDaemonGeneration(kind: ChatDaemonKind): number;
@@ -104,6 +111,7 @@ export declare class ChatDaemonController implements BuiltInDaemonController {
104
111
  readonly kind: ChatDaemonKind;
105
112
  constructor(settings: Settings, kind: ChatDaemonKind, deps?: ChatDaemonControlDeps);
106
113
  private identity;
114
+ private effectivelyEnabled;
107
115
  private alive;
108
116
  status(): Promise<DaemonStatus>;
109
117
  stop(opts?: DaemonOperationOptions): Promise<DaemonOperationResult>;
@@ -22,9 +22,29 @@ export interface TelegramActivationMarker {
22
22
  export type TelegramActivationMarkers = Record<string, TelegramActivationMarker>;
23
23
  /** Validate and clone activation markers crossing the settings boundary. */
24
24
  export declare function readTelegramActivationMarkers(value?: unknown): TelegramActivationMarkers;
25
+ export type NotificationProvider = "telegram" | "discord" | "slack";
26
+ export type NotificationRuntime = "inactive" | "starting" | "ready" | "attached" | "blocked" | "failed";
27
+ export type ProviderSecretDisposition = "keep" | "replace" | "remove";
28
+ export type ProviderResolutionIssueCode = "missing" | "blank" | "wrong_type" | "invalid_container" | "contradictory";
29
+ export interface ProviderResolutionIssue {
30
+ path: string;
31
+ code: ProviderResolutionIssueCode;
32
+ }
33
+ export type NotificationProviderIssueCode = ProviderResolutionIssueCode;
34
+ export type NotificationProviderIssue = ProviderResolutionIssue;
35
+ export interface ProviderResolution {
36
+ provider: NotificationProvider;
37
+ configured: boolean;
38
+ quarantined: boolean;
39
+ desiredEnabled: boolean;
40
+ desiredSource: "explicit" | "legacy";
41
+ effectiveEnabled: boolean;
42
+ issues: readonly ProviderResolutionIssue[];
43
+ }
25
44
  export interface NotificationSettingsSnapshot {
26
45
  enabled: boolean;
27
46
  telegram: {
47
+ enabled?: boolean;
28
48
  botToken?: string;
29
49
  chatId?: string;
30
50
  activation?: Record<string, unknown>;
@@ -49,12 +69,14 @@ export interface NotificationSettingsSnapshot {
49
69
  };
50
70
  };
51
71
  discord: {
72
+ enabled?: boolean;
52
73
  botToken?: string;
53
74
  applicationId?: string;
54
75
  guildId?: string;
55
76
  parentChannelId?: string;
56
77
  };
57
78
  slack: {
79
+ enabled?: boolean;
58
80
  botToken?: string;
59
81
  appToken?: string;
60
82
  workspaceId?: string;
@@ -65,33 +87,38 @@ export interface NotificationSettingsSnapshot {
65
87
  verbosity: "lean" | "verbose";
66
88
  sessionScope: "all" | "primary";
67
89
  idleTimeoutMs: number;
90
+ /** Safe provider-local validation issues. Raw values never cross this boundary. */
91
+ providerIssues?: Partial<Record<NotificationProvider, readonly ProviderResolutionIssue[]>>;
68
92
  }
69
93
  /**
70
94
  * Narrow settings boundary for remote notification identity and behavior.
71
- * Implementations must return only user-global values, with schema defaults
72
- * applied; project settings and runtime overrides are intentionally excluded.
95
+ * Implementations return only user-global values; project settings and runtime
96
+ * overrides are intentionally excluded.
73
97
  */
74
98
  export interface NotificationSettingsReader {
75
99
  getNotificationSettingsSnapshot(): NotificationSettingsSnapshot;
76
100
  getAgentDir(): string;
77
101
  }
78
- /**
79
- * Validate and snapshot the raw global notification configuration used by both
80
- * the interactive host and the lightweight daemon process.
81
- */
102
+ /** Validate and snapshot raw global notification configuration without leaking invalid provider values. */
82
103
  export declare function parseNotificationSettingsSnapshot(rawConfig?: unknown): NotificationSettingsSnapshot;
83
104
  export interface NotificationConfig {
84
105
  enabled: boolean;
85
106
  botToken?: string;
86
107
  activation?: TelegramActivationMarkers;
87
108
  chatId?: string;
109
+ /** Optional nested Telegram intent metadata; absent means legacy configuration. */
110
+ telegram?: {
111
+ enabled?: boolean;
112
+ };
88
113
  discord: {
114
+ enabled?: boolean;
89
115
  botToken?: string;
90
116
  applicationId?: string;
91
117
  guildId?: string;
92
118
  parentChannelId?: string;
93
119
  };
94
120
  slack: {
121
+ enabled?: boolean;
95
122
  botToken?: string;
96
123
  appToken?: string;
97
124
  workspaceId?: string;
@@ -100,11 +127,6 @@ export interface NotificationConfig {
100
127
  };
101
128
  redact: boolean;
102
129
  verbosity: "lean" | "verbose";
103
- /**
104
- * Which sessions may register a notification endpoint. `all` (default)
105
- * preserves historical behavior; `primary` suppresses GJC-spawned children
106
- * (those carrying {@link SPAWN_PROVENANCE_ENV}) unless they explicitly opt in.
107
- */
108
130
  sessionScope: "all" | "primary";
109
131
  sound: "all" | "important" | "none";
110
132
  idleTimeoutMs: number;
@@ -124,62 +146,24 @@ export interface NotificationConfig {
124
146
  enabled: boolean;
125
147
  };
126
148
  topics: {
127
- /**
128
- * Optional Telegram forum-topic name template with `{repo}`, `{branch}`,
129
- * and `{title}` placeholders. Unset preserves the built-in
130
- * `{repo}/{branch} - {title}` composition (with its title/repo/branch
131
- * fallbacks).
132
- */
133
149
  nameTemplate?: string;
134
150
  };
151
+ providerIssues?: Partial<Record<NotificationProvider, readonly ProviderResolutionIssue[]>>;
135
152
  }
136
153
  /** Read typed global-only notification config from a narrow settings reader. */
137
154
  export declare function getNotificationConfig(settings: NotificationSettingsReader): NotificationConfig;
138
- declare const notificationConfigSchema: z.ZodObject<{
139
- notifications: z.ZodOptional<z.ZodObject<{
140
- enabled: z.ZodOptional<z.ZodBoolean>;
141
- discord: z.ZodOptional<z.ZodObject<{
142
- botToken: z.ZodOptional<z.ZodString>;
143
- applicationId: z.ZodOptional<z.ZodString>;
144
- guildId: z.ZodOptional<z.ZodString>;
145
- parentChannelId: z.ZodOptional<z.ZodString>;
146
- }, z.core.$loose>>;
147
- slack: z.ZodOptional<z.ZodObject<{
148
- botToken: z.ZodOptional<z.ZodString>;
149
- appToken: z.ZodOptional<z.ZodString>;
150
- workspaceId: z.ZodOptional<z.ZodString>;
151
- channelId: z.ZodOptional<z.ZodString>;
152
- authorizedUserId: z.ZodOptional<z.ZodString>;
153
- }, z.core.$loose>>;
154
- redact: z.ZodOptional<z.ZodBoolean>;
155
- verbosity: z.ZodOptional<z.ZodEnum<{
156
- lean: "lean";
157
- verbose: "verbose";
158
- }>>;
159
- }, z.core.$loose>>;
160
- }, z.core.$loose>;
155
+ declare const notificationConfigSchema: z.ZodObject<{}, z.core.$loose>;
161
156
  type NotificationConfigFile = z.infer<typeof notificationConfigSchema>;
162
157
  /** Read daemon settings through the canonical validated config-file boundary. */
163
158
  export declare function loadNotificationConfigFile(agentDir: string): LoadResult<NotificationConfigFile>;
164
- export declare function notificationConfigFromFile(value: NotificationConfigFile): Pick<NotificationConfig, "enabled" | "discord" | "slack" | "redact" | "verbosity">;
159
+ export declare function notificationConfigFromFile(value: NotificationConfigFile): NotificationConfig;
165
160
  export declare function hasNonBlankValue(value: string | undefined): boolean;
166
- /**
167
- * Resolve live assistant streaming independently of generic notification
168
- * lifecycle enablement. Explicit environment values override the durable
169
- * Telegram preference; otherwise streaming is available only to an active
170
- * configured Telegram identity.
171
- */
172
- export declare function isNotificationStreamingEnabled(input: {
173
- cfg: NotificationConfig;
174
- env: NodeJS.ProcessEnv;
175
- }): boolean;
176
- /** Is Telegram configured with usable non-blank boundary credentials? */
177
- export declare function isTelegramConfigured(cfg: NotificationConfig): cfg is NotificationConfig & {
161
+ export declare function isProviderComplete(cfg: NotificationConfig, provider: NotificationProvider): boolean;
162
+ export declare function isTelegramComplete(cfg: NotificationConfig): cfg is NotificationConfig & {
178
163
  botToken: string;
179
164
  chatId: string;
180
165
  };
181
- /** Is Discord configured with all credentials and routing identifiers required by its daemon. */
182
- export declare function isDiscordConfigured(cfg: Pick<NotificationConfig, "enabled" | "discord">): cfg is Pick<NotificationConfig, "enabled"> & {
166
+ export declare function isDiscordComplete(cfg: NotificationConfig): cfg is NotificationConfig & {
183
167
  discord: {
184
168
  botToken: string;
185
169
  applicationId: string;
@@ -187,8 +171,7 @@ export declare function isDiscordConfigured(cfg: Pick<NotificationConfig, "enabl
187
171
  parentChannelId: string;
188
172
  };
189
173
  };
190
- /** Is Slack configured with both SDK tokens and its workspace/channel routing identifiers. */
191
- export declare function isSlackConfigured(cfg: Pick<NotificationConfig, "enabled" | "slack">): cfg is Pick<NotificationConfig, "enabled"> & {
174
+ export declare function isSlackComplete(cfg: NotificationConfig): cfg is NotificationConfig & {
192
175
  slack: {
193
176
  botToken: string;
194
177
  appToken: string;
@@ -196,80 +179,59 @@ export declare function isSlackConfigured(cfg: Pick<NotificationConfig, "enabled
196
179
  channelId: string;
197
180
  };
198
181
  };
199
- /** Is global config sufficient for auto-on (enabled + at least one configured adapter)? */
200
- export declare function isGloballyConfigured(cfg: NotificationConfig): boolean;
182
+ export declare function resolveNotificationProvider(cfg: NotificationConfig, provider: NotificationProvider): ProviderResolution;
183
+ export declare function isProviderEffectivelyEnabled(cfg: NotificationConfig, provider: NotificationProvider): boolean;
184
+ export declare function hasAnyCompleteProvider(cfg: NotificationConfig): boolean;
185
+ export declare function hasAnyEffectivelyEnabledProvider(cfg: NotificationConfig): boolean;
201
186
  /**
202
- * Per-run opt-out for completion notifications, honored before settings lookups.
203
- *
204
- * `GJC_NOTIFY=off` (also `0` / `false`, case-insensitive) suppresses the
205
- * completion notification surface for this process only. `config.yml` is
206
- * untouched and child processes inherit the env var, which lets non-interactive
207
- * fleet runs (`gjc -p --no-session`) stay silent even when a user-level/global
208
- * completion notification configuration is enabled.
187
+ * Resolve generic live-stream policy. This policy only governs automatic
188
+ * current-session frames; it never changes durable provider eligibility.
209
189
  */
190
+ export type GenericNotificationSessionSource = "hard_opt_out" | "session_local_off" | "explicit_env" | "token_env" | "configured_provider" | "session_scope" | "none";
191
+ export interface GenericNotificationSessionEligibility {
192
+ enabled: boolean;
193
+ source: GenericNotificationSessionSource;
194
+ }
195
+ export type GenericNotificationStreamSource = "session_not_admitted" | "env_on" | "env_off" | "durable_telegram" | "none";
196
+ export interface GenericNotificationStreamPolicy {
197
+ enabled: boolean;
198
+ source: GenericNotificationStreamSource;
199
+ }
200
+ export interface GenericNotificationSessionEligibilityInput {
201
+ cfg: NotificationConfig;
202
+ env: NodeJS.ProcessEnv;
203
+ sessionDisabled: boolean;
204
+ spawnedByGjc?: boolean;
205
+ }
206
+ export declare function resolveGenericNotificationSessionEligibility(input: GenericNotificationSessionEligibilityInput): GenericNotificationSessionEligibility;
207
+ export declare function resolveGenericNotificationStreamPolicy(input: {
208
+ cfg: NotificationConfig;
209
+ env: NodeJS.ProcessEnv;
210
+ genericSessionEnabled: boolean;
211
+ }): GenericNotificationStreamPolicy;
210
212
  export declare function completionNotifyDisabledByEnv(env: NodeJS.ProcessEnv): boolean;
211
- /** Canonical host eligibility for the dormant notification session surface. */
212
213
  export interface NotificationHostEligibilityInput {
213
214
  env: NodeJS.ProcessEnv;
214
- /** False for host modes that cannot own a notification session endpoint. */
215
215
  hostModeSupported?: boolean;
216
- /** Task recursion depth; helper/subagent sessions must not own remote surfaces. */
217
216
  taskDepth?: number;
218
- /** Parent subagent id/prefix; present for helper/subagent sessions even when depth is omitted. */
219
217
  parentTaskPrefix?: string;
220
- /** Role-agent type/name; present for task sessions even if depth metadata is lost. */
221
218
  currentAgentType?: string;
222
- /** Canonical global session scope; absent preserves the default `all` behavior. */
223
219
  sessionScope?: NotificationConfig["sessionScope"];
224
- /** Whether this process was spawned by one of GJC's marked child spawn sites. */
225
220
  spawnedByGjc?: boolean;
226
221
  }
227
- /**
228
- * Resolve whether this host may receive the dormant notification controller.
229
- * This intentionally says nothing about whether an adapter is configured: an
230
- * eligible unconfigured host still gets a zero-side-effect control surface.
231
- */
232
- export declare function isNotificationHostEligible(input: NotificationHostEligibilityInput): boolean;
233
- /**
234
- * Legacy compatibility helper for callers that require both host eligibility
235
- * and a currently configured or explicit notification runtime.
236
- */
237
- export declare function shouldRegisterNotificationsExtension(input: {
222
+ /** Generic host eligibility for the dormant automatic notification surface. */
223
+ export declare function isGenericNotificationHostEligible(input: NotificationHostEligibilityInput): boolean;
224
+ export interface GenericNotificationRegistrationInput {
238
225
  env: NodeJS.ProcessEnv;
239
226
  cfg?: NotificationConfig;
240
- /** Task recursion depth; helper/subagent sessions must not spawn remote surfaces. */
241
227
  taskDepth?: number;
242
- /** Parent subagent id/prefix; present for helper/subagent sessions even when depth is omitted. */
243
228
  parentTaskPrefix?: string;
244
- /** Role-agent type/name; present for task sessions even if depth metadata is lost. */
245
229
  currentAgentType?: string;
246
- /**
247
- * True when this session was launched by one of GJC's own programmatic
248
- * separate-process child spawn sites (marked via {@link SPAWN_PROVENANCE_ENV}).
249
- * Under `notifications.sessionScope = "primary"` such children are suppressed
250
- * unless they explicitly opt in, so an interactive parent that fans out work
251
- * does not flood the paired chat with topics for children the user never
252
- * asked for. User-opened sessions (CLI/tmux/headless) never carry the marker.
253
- */
254
- spawnedByGjc?: boolean;
255
- }): boolean;
256
- /**
257
- * Resolve whether THIS session should run notifications.
258
- * Precedence (highest first):
259
- * 1) env.GJC_NOTIFICATIONS === "0" -> false (hard opt-out)
260
- * 2) sessionDisabled === true -> false (local /notify off)
261
- * 3) env.GJC_NOTIFICATIONS === "1" || env.GJC_NOTIFICATIONS_TOKEN present -> true (legacy explicit)
262
- * 4) spawned GJC child with `sessionScope=primary` -> false
263
- * 5) isGloballyConfigured(cfg) -> true (global auto-on)
264
- * 6) otherwise false
265
- */
266
- export declare function isSessionNotificationsEnabled(input: {
267
- cfg: NotificationConfig;
268
- env: NodeJS.ProcessEnv;
269
- sessionDisabled: boolean;
270
- /** This process was programmatically spawned by GJC (consumed at SDK startup). */
271
230
  spawnedByGjc?: boolean;
272
- }): boolean;
231
+ }
232
+ /** Generic registration admission; direct provider actions do not call this helper. */
233
+ export declare function shouldRegisterGenericNotificationsExtension(input: GenericNotificationRegistrationInput): boolean;
234
+ export declare function isGenericNotificationSessionEnabled(input: GenericNotificationSessionEligibilityInput): boolean;
273
235
  /** Mask a bot token for display: first 4 chars + "…" + "(len N)"; "(unset)" when undefined/empty. Never reveal full token. */
274
236
  export declare function maskToken(token: string | undefined): string;
275
237
  /** Stable non-reversible fingerprint of a token: sha256 hex, first 12 chars. */
@@ -1,4 +1,4 @@
1
- import type { DiscordInboundEvent, DiscordMessageComponent, DiscordProvider, DiscordThread } from "./discord-provider";
1
+ import type { DiscordConfigurationProbeResult, DiscordDiagnosticProvider, DiscordInboundEvent, DiscordMessageComponent, DiscordOneShotTestResult, DiscordProvider, DiscordThread } from "./discord-provider";
2
2
  export interface DiscordGatewaySocket {
3
3
  readonly readyState: number;
4
4
  send(data: string): void;
@@ -21,7 +21,7 @@ export interface DiscordLiveProviderOptions {
21
21
  apiBaseUrl?: string;
22
22
  }
23
23
  /** Discord REST/Gateway implementation. The only credential is held privately and is never emitted. */
24
- export declare class DiscordLiveProvider implements DiscordProvider {
24
+ export declare class DiscordLiveProvider implements DiscordProvider, DiscordDiagnosticProvider {
25
25
  #private;
26
26
  readonly applicationId: string;
27
27
  constructor(options: DiscordLiveProviderOptions);
@@ -64,6 +64,12 @@ export declare class DiscordLiveProvider implements DiscordProvider {
64
64
  }): Promise<{
65
65
  id: string;
66
66
  } | null>;
67
+ probeConfiguration(signal?: AbortSignal): Promise<DiscordConfigurationProbeResult>;
68
+ sendOneShotTest(input: {
69
+ channelId: string;
70
+ message: string;
71
+ signal?: AbortSignal;
72
+ }): Promise<DiscordOneShotTestResult>;
67
73
  start(onEvent: (event: DiscordInboundEvent) => Promise<void>): Promise<void>;
68
74
  stop(): Promise<void>;
69
75
  }
@@ -36,6 +36,25 @@ export interface DiscordMessageComponent {
36
36
  }>;
37
37
  }>;
38
38
  }
39
+ export interface DiscordConfigurationProbeResult {
40
+ ok: boolean;
41
+ detail: string;
42
+ botUserId?: string;
43
+ }
44
+ export interface DiscordOneShotTestResult {
45
+ ok: boolean;
46
+ detail: string;
47
+ messageId?: string;
48
+ uncertain?: boolean;
49
+ }
50
+ export interface DiscordDiagnosticProvider {
51
+ probeConfiguration(signal?: AbortSignal): Promise<DiscordConfigurationProbeResult>;
52
+ sendOneShotTest(input: {
53
+ channelId: string;
54
+ message: string;
55
+ signal?: AbortSignal;
56
+ }): Promise<DiscordOneShotTestResult>;
57
+ }
39
58
  export interface DiscordProvider {
40
59
  readonly applicationId: string;
41
60
  readonly botUserId: string;
@@ -337,7 +337,7 @@ interface SessionRuntime {
337
337
  notificationRootRegistration?: {
338
338
  settings: Settings;
339
339
  cwd: string;
340
- registrationToken?: string;
340
+ registrationToken: string;
341
341
  };
342
342
  verbosity: "lean" | "verbose";
343
343
  sessionTag: string;
@@ -1,5 +1,5 @@
1
1
  import type { RawSettings, SettingsAtomicPatch, SettingsAtomicReceipt } from "../../config/settings";
2
- import { type NotificationSettingsReader, type TelegramActivationMarker, type TelegramActivationMarkers, type TelegramActivationReason, type TelegramActivationState, telegramActivationIdentity } from "./config";
2
+ import { type NotificationProvider, type NotificationSettingsReader, type ProviderSecretDisposition, type TelegramActivationMarker, type TelegramActivationMarkers, type TelegramActivationReason, type TelegramActivationState, telegramActivationIdentity } from "./config";
3
3
  import { type EnsureTelegramDaemonDetailedResult, type TelegramDaemonFs } from "./telegram-daemon";
4
4
  export type { TelegramActivationMarker, TelegramActivationMarkers, TelegramActivationReason, TelegramActivationState };
5
5
  export { telegramActivationIdentity };
@@ -41,7 +41,7 @@ export interface ProposedTelegramIdentityPreflightInput {
41
41
  export declare function proposedTelegramIdentity(input: ProposedTelegramIdentityPreflightInput): Promise<ProposedTelegramIdentity>;
42
42
  export interface NotificationConfigurationWriter extends NotificationSettingsReader {
43
43
  commitAtomicBatch(patches: readonly SettingsAtomicPatch[]): Promise<SettingsAtomicReceipt>;
44
- commitAtomicBatchWithCurrent?(buildPatches: (current: Readonly<RawSettings>) => Promise<readonly SettingsAtomicPatch[]> | readonly SettingsAtomicPatch[]): Promise<SettingsAtomicReceipt>;
44
+ commitAtomicBatchWithCurrent(buildPatches: (current: Readonly<RawSettings>) => Promise<readonly SettingsAtomicPatch[]> | readonly SettingsAtomicPatch[]): Promise<SettingsAtomicReceipt>;
45
45
  }
46
46
  export declare function createTelegramActivationMarker(input: {
47
47
  botToken: string;
@@ -55,23 +55,16 @@ export declare function observedTelegramActivationMarker(settings: NotificationS
55
55
  export declare function persistTelegramActivationMarker(settings: NotificationConfigurationWriter, marker: TelegramActivationMarker): Promise<SettingsAtomicReceipt>;
56
56
  /** Clear only the exact marker that was safely reconciled. */
57
57
  export declare function clearTelegramActivationMarker(settings: NotificationConfigurationWriter, marker: TelegramActivationMarker): Promise<void>;
58
- export type CompleteNonTelegramAdapter = "discord" | "slack";
59
58
  export type SaveTelegramInactiveAvailability = {
60
59
  available: true;
61
60
  };
62
- /**
63
- * Telegram-specific activation markers let Save inactive preserve globally
64
- * enabled Discord and Slack adapters, so the action is always available.
65
- */
61
+ /** Telegram activation markers make Save inactive independent of sibling providers. */
66
62
  export declare function getSaveTelegramInactiveAvailability(_settings: NotificationSettingsReader): SaveTelegramInactiveAvailability;
67
63
  export type SaveTelegramInactiveResult = {
68
64
  status: "saved_inactive";
69
65
  receipt: SettingsAtomicReceipt;
70
66
  };
71
- /**
72
- * Atomically persist Telegram credentials with a non-secret inactive marker.
73
- * Global notifications are disabled only when Telegram is the sole complete adapter.
74
- */
67
+ /** Atomically persist Telegram credentials, desired-off intent, and its inactive marker. */
75
68
  export declare function saveTelegramInactive(input: {
76
69
  settings: NotificationConfigurationWriter;
77
70
  botToken: string;
@@ -117,9 +110,16 @@ export interface TelegramPostCommitActivation {
117
110
  }
118
111
  export type PostCommitTelegramActivationResult = {
119
112
  status: "activated";
120
- reconnect: Exclude<TelegramDaemonReconnectOutcome, "blocked_identity">;
113
+ receipt: SettingsAtomicReceipt;
114
+ reconnect: Exclude<TelegramDaemonReconnectOutcome, "blocked_identity" | "disabled">;
115
+ } | {
116
+ status: "activation_failed";
117
+ receipt: SettingsAtomicReceipt;
118
+ reconnect?: TelegramDaemonReconnectOutcome;
119
+ message: string;
121
120
  } | {
122
121
  status: "blocked_identity";
122
+ receipt: SettingsAtomicReceipt;
123
123
  message: string;
124
124
  restore(): Promise<BlockedTelegramRestoreResult>;
125
125
  retainCommitted(): void;
@@ -135,12 +135,6 @@ export type BlockedTelegramRestoreResult = {
135
135
  } | {
136
136
  status: "still_blocked";
137
137
  };
138
- /**
139
- * Complete a committed Telegram update in the required order: identity
140
- * reconnect first, normal session reconciliation second. A post-commit foreign
141
- * owner race stops the endpoint, durably marks only that Telegram identity
142
- * inactive, then exposes an ordered CAS restore/retain choice.
143
- */
144
138
  export declare function reconcileCommittedTelegramConfiguration(input: {
145
139
  receipt: SettingsAtomicReceipt;
146
140
  activation: TelegramPostCommitActivation;
@@ -169,3 +163,118 @@ export declare function saveTelegramConfiguration(input: {
169
163
  preflight?: (input: Omit<ProposedTelegramIdentityPreflightInput, "settings">) => Promise<ProposedTelegramIdentity>;
170
164
  activation?: TelegramPostCommitActivation;
171
165
  }): Promise<SaveTelegramConfigurationResult>;
166
+ export type ProviderSecretMutation = {
167
+ action: "keep";
168
+ } | {
169
+ action: "replace";
170
+ value: string;
171
+ } | {
172
+ action: "remove";
173
+ };
174
+ export interface TelegramProviderConfigurationMutation {
175
+ provider: "telegram";
176
+ botToken: ProviderSecretMutation;
177
+ chatId?: string;
178
+ richEnabled?: boolean;
179
+ richDraftEnabled?: boolean;
180
+ streamingEnabled?: boolean;
181
+ }
182
+ export interface DiscordProviderConfigurationMutation {
183
+ provider: "discord";
184
+ botToken: ProviderSecretMutation;
185
+ applicationId?: string;
186
+ guildId?: string;
187
+ parentChannelId?: string;
188
+ }
189
+ export interface SlackProviderConfigurationMutation {
190
+ provider: "slack";
191
+ botToken: ProviderSecretMutation;
192
+ appToken: ProviderSecretMutation;
193
+ workspaceId?: string;
194
+ channelId?: string;
195
+ authorizedUserId?: string | null;
196
+ }
197
+ export type NotificationProviderConfigurationMutation = TelegramProviderConfigurationMutation | DiscordProviderConfigurationMutation | SlackProviderConfigurationMutation;
198
+ export interface NotificationProviderRuntimeAuthority {
199
+ activate(provider: NotificationProvider): Promise<void>;
200
+ deactivate(provider: NotificationProvider): Promise<void>;
201
+ }
202
+ export type NotificationProviderMutationResult = {
203
+ status: "saved";
204
+ receipt: SettingsAtomicReceipt;
205
+ observerFailed: boolean;
206
+ } | {
207
+ status: "activated";
208
+ receipt: SettingsAtomicReceipt;
209
+ observerFailed: boolean;
210
+ } | {
211
+ status: "activation_failed";
212
+ receipt: SettingsAtomicReceipt;
213
+ observerFailed: boolean;
214
+ } | {
215
+ status: "deactivation_failed";
216
+ receipt: SettingsAtomicReceipt;
217
+ observerFailed: boolean;
218
+ } | {
219
+ status: "commit_failed";
220
+ };
221
+ /**
222
+ * Sole CAS-backed provider configuration authority. It commits one selected-provider
223
+ * batch before observer/runtime effects and never reports rollback after a durable save.
224
+ */
225
+ export declare function mutateNotificationProvider(input: {
226
+ settings: NotificationConfigurationWriter;
227
+ mutation: NotificationProviderConfigurationMutation;
228
+ desiredEnabled?: boolean;
229
+ configureAndActivate?: boolean;
230
+ notifyConfigChanged?: () => Promise<void> | void;
231
+ redact?: boolean;
232
+ runtime?: NotificationProviderRuntimeAuthority;
233
+ signal?: AbortSignal;
234
+ }): Promise<NotificationProviderMutationResult>;
235
+ export type NotificationProviderRemovalResult = {
236
+ status: "removed";
237
+ receipt: SettingsAtomicReceipt;
238
+ observerFailed: boolean;
239
+ } | {
240
+ status: "deactivation_failed";
241
+ receipt?: SettingsAtomicReceipt;
242
+ observerFailed?: boolean;
243
+ } | {
244
+ status: "commit_failed_after_teardown";
245
+ } | {
246
+ status: "commit_failed";
247
+ };
248
+ /** Remove only one provider. Telegram keeps teardown-before-delete; chat providers deactivate after commit. */
249
+ export declare function removeNotificationProvider(input: {
250
+ settings: NotificationConfigurationWriter;
251
+ provider: NotificationProvider;
252
+ runtime: NotificationProviderRuntimeAuthority;
253
+ notifyConfigChanged?: () => Promise<void> | void;
254
+ }): Promise<NotificationProviderRemovalResult>;
255
+ export type GlobalNotificationMutationResult = {
256
+ status: "saved";
257
+ receipt: SettingsAtomicReceipt;
258
+ observerFailed: boolean;
259
+ } | {
260
+ status: "global_deactivation_partial";
261
+ receipt: SettingsAtomicReceipt;
262
+ failed: readonly NotificationProvider[];
263
+ observerFailed: boolean;
264
+ } | {
265
+ status: "global_activation_partial";
266
+ receipt: SettingsAtomicReceipt;
267
+ failed: readonly NotificationProvider[];
268
+ observerFailed: boolean;
269
+ } | {
270
+ status: "commit_failed";
271
+ };
272
+ /** Toggle only the global master. Provider configuration and desired intent are preserved. */
273
+ export declare function setGlobalNotificationsEnabled(input: {
274
+ settings: NotificationConfigurationWriter;
275
+ enabled: boolean;
276
+ runtime?: NotificationProviderRuntimeAuthority;
277
+ notifyConfigChanged?: () => Promise<void> | void;
278
+ }): Promise<GlobalNotificationMutationResult>;
279
+ /** Safe helper for UI contracts that must retain only the disposition, never replacement material. */
280
+ export declare function providerSecretDisposition(mutation: ProviderSecretMutation): ProviderSecretDisposition;
@@ -1,5 +1,8 @@
1
1
  import type { WriteFileOptions } from "node:fs";
2
2
  import type { Settings } from "../../config/settings";
3
+ import { type NotificationProvider, type NotificationRuntime, type ProviderResolution } from "./config";
4
+ import type { DiscordDiagnosticProvider } from "./discord-provider";
5
+ import type { SlackDiagnosticProvider } from "./slack-provider";
3
6
  /**
4
7
  * Strip secrets from a human-facing diagnostic string. Redacts the configured
5
8
  * bot token (exact match) and any token-shaped substring so health/test details
@@ -53,17 +56,34 @@ export interface NotificationServiceDeps {
53
56
  pidAlive?: (pid: number) => boolean;
54
57
  fetchImpl?: typeof fetch;
55
58
  apiBase?: string;
59
+ createDiscordDiagnostic?: (config: {
60
+ applicationId: string;
61
+ botToken: string;
62
+ }) => DiscordDiagnosticProvider;
63
+ createSlackDiagnostic?: (config: {
64
+ appToken: string;
65
+ botToken: string;
66
+ }) => SlackDiagnosticProvider;
67
+ providerRuntimeStatus?: (provider: NotificationProvider) => Promise<NotificationRuntime> | NotificationRuntime;
56
68
  }
57
69
  export interface AdapterConfigView {
58
70
  botTokenMasked: string;
59
71
  channel: string | undefined;
60
72
  configured: boolean;
73
+ quarantined: boolean;
74
+ desiredEnabled: boolean;
75
+ desiredSource: ProviderResolution["desiredSource"];
76
+ effectiveEnabled: boolean;
77
+ issues: ProviderResolution["issues"];
78
+ runtime?: NotificationRuntime;
61
79
  }
62
80
  export interface NotificationStatusReport {
63
81
  enabled: boolean;
64
82
  redact: boolean;
65
83
  verbosity: "lean" | "verbose";
66
84
  globallyConfigured: boolean;
85
+ anyProviderComplete: boolean;
86
+ anyProviderEffective: boolean;
67
87
  telegram: AdapterConfigView & {
68
88
  tokenFingerprint: string | undefined;
69
89
  };
@@ -139,6 +159,8 @@ export interface EndpointHealth {
139
159
  export interface NotificationHealthReport {
140
160
  overall: HealthLevel;
141
161
  configured: boolean;
162
+ provider?: NotificationProvider;
163
+ resolution?: ProviderResolution;
142
164
  checks: HealthCheck[];
143
165
  daemon: DaemonHealth;
144
166
  endpoints: EndpointHealth;
@@ -152,8 +174,10 @@ export interface HealthOptions {
152
174
  settings: Settings;
153
175
  stateRoot?: string;
154
176
  deps?: NotificationServiceDeps;
155
- /** When true and Telegram is configured, probe the Bot API (getMe) for reachability. */
177
+ provider?: NotificationProvider;
178
+ /** When true, use the selected provider's REST-only diagnostic path. */
156
179
  probe?: boolean;
180
+ signal?: AbortSignal;
157
181
  }
158
182
  /** Structural (offline-by-default) health of the notification subsystem. */
159
183
  export declare function checkNotificationHealth(opts: HealthOptions): Promise<NotificationHealthReport>;
@@ -161,16 +185,19 @@ export declare function checkNotificationHealth(opts: HealthOptions): Promise<No
161
185
  export declare function formatNotificationHealthReport(report: NotificationHealthReport): string;
162
186
  export interface NotificationTestResult {
163
187
  ok: boolean;
164
- adapter: "telegram";
165
- chatId: string | undefined;
188
+ adapter?: NotificationProvider;
189
+ destination?: string;
166
190
  detail: string;
191
+ uncertain?: boolean;
167
192
  }
168
193
  export interface TestOptions {
169
194
  settings: Settings;
170
195
  deps?: NotificationServiceDeps;
196
+ provider?: NotificationProvider;
171
197
  text?: string;
198
+ signal?: AbortSignal;
172
199
  }
173
- /** Send a one-off test notification through the configured Telegram adapter. */
200
+ /** Send a one-off test through exactly one durable, effective provider. */
174
201
  export declare function sendNotificationTest(opts: TestOptions): Promise<NotificationTestResult>;
175
202
  /** Render a test result as a single human-readable line (no secrets). */
176
203
  export declare function formatNotificationTestResult(result: NotificationTestResult): string;