@gajae-code/coding-agent 0.12.7 → 0.12.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/dist/types/cli/notify-cli.d.ts +1 -0
- package/dist/types/commands/notify.d.ts +4 -0
- package/dist/types/commands/sdk.d.ts +23 -0
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/config/model-discovery-manager.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +2 -0
- package/dist/types/config/resolve-config-value.d.ts +2 -1
- package/dist/types/config/settings-schema.d.ts +17 -1
- package/dist/types/discovery/agents-md.d.ts +11 -1
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/components/hook-selector.d.ts +20 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +53 -4
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +4 -2
- package/dist/types/modes/types.d.ts +6 -0
- package/dist/types/sdk/broker/broker.d.ts +5 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -1
- package/dist/types/sdk/bus/config.d.ts +75 -113
- package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
- package/dist/types/sdk/bus/discord-live-provider.d.ts +8 -2
- package/dist/types/sdk/bus/discord-provider.d.ts +19 -0
- package/dist/types/sdk/bus/index.d.ts +1 -1
- package/dist/types/sdk/bus/notification-orchestration.d.ts +127 -18
- package/dist/types/sdk/bus/notification-service.d.ts +54 -4
- package/dist/types/sdk/bus/session-control.d.ts +8 -3
- package/dist/types/sdk/bus/slack-live-provider.d.ts +9 -2
- package/dist/types/sdk/bus/slack-provider.d.ts +22 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +8 -1
- package/dist/types/sdk/client/discovery.d.ts +2 -1
- package/dist/types/sdk/host/query/handlers.d.ts +2 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/providers.d.ts +22 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/session/internal/managed-session-scope.d.ts +17 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +19 -1
- package/dist/types/session/internal/native-publish-outcome.d.ts +3 -2
- package/dist/types/session/internal/session-open-errors.d.ts +5 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/session/session-storage.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +12 -1
- package/dist/types/tools/composer-bash-policy.d.ts +9 -1
- package/package.json +10 -8
- package/scripts/build-sdk-package-smoke.ts +51 -0
- package/scripts/capture-notifications-settings-showcase.ts +1 -1
- package/scripts/verify-gjc-sdk-canonicalization.ts +14 -1
- package/src/cli/notify-cli.ts +215 -99
- package/src/commands/notify.ts +19 -8
- package/src/commands/sdk.ts +54 -0
- package/src/config/file-lock.ts +6 -0
- package/src/config/model-discovery-manager.ts +50 -8
- package/src/config/model-profiles.ts +8 -0
- package/src/config/model-registry.ts +866 -76
- package/src/config/resolve-config-value.ts +10 -8
- package/src/config/settings-schema.ts +25 -15
- package/src/config/settings.ts +15 -15
- package/src/discovery/agents-md.ts +76 -19
- package/src/gjc-runtime/psmux-detect.ts +12 -16
- package/src/gjc-runtime/session-state-sidecar.ts +12 -2
- package/src/gjc-runtime/tmux-sessions.ts +8 -1
- package/src/internal-urls/docs-index.generated.ts +10 -9
- package/src/main.ts +43 -7
- package/src/modes/DESIGN.md +11 -11
- package/src/modes/components/extensions/inspector-panel.ts +9 -5
- package/src/modes/components/hook-selector.ts +155 -44
- package/src/modes/components/model-selector.ts +242 -49
- package/src/modes/components/notifications-settings-editor.ts +631 -85
- package/src/modes/controllers/extension-ui-controller.ts +114 -15
- package/src/modes/controllers/selector-controller.ts +525 -93
- package/src/modes/print-mode.ts +3 -0
- package/src/modes/types.ts +6 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/sdk/broker/broker.ts +54 -10
- package/src/sdk/broker/lifecycle-ledger.ts +34 -1
- package/src/sdk/broker/lifecycle.ts +47 -50
- package/src/sdk/bus/chat-daemon-cli.ts +12 -5
- package/src/sdk/bus/chat-daemon-control.ts +35 -6
- package/src/sdk/bus/chat-daemon-runtime.ts +16 -2
- package/src/sdk/bus/config.ts +534 -286
- package/src/sdk/bus/daemon-paths.ts +2 -0
- package/src/sdk/bus/discord-live-provider.ts +57 -2
- package/src/sdk/bus/discord-provider.ts +21 -0
- package/src/sdk/bus/index.ts +97 -35
- package/src/sdk/bus/notification-orchestration.ts +371 -68
- package/src/sdk/bus/notification-service.ts +571 -137
- package/src/sdk/bus/session-control.ts +119 -44
- package/src/sdk/bus/slack-live-provider.ts +78 -2
- package/src/sdk/bus/slack-provider.ts +24 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +9 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +8 -1
- package/src/sdk/bus/telegram-daemon-control.ts +3 -3
- package/src/sdk/bus/telegram-daemon.ts +23 -15
- package/src/sdk/client/discovery.ts +10 -4
- package/src/sdk/host/query/handlers.ts +20 -1
- package/src/sdk/index.ts +1 -0
- package/src/sdk/protocol/operation-inventory.generated.json +18 -0
- package/src/sdk/protocol/operation-registry.ts +5 -2
- package/src/sdk/providers.ts +51 -0
- package/src/sdk/session-directory.ts +1 -0
- package/src/sdk/session.ts +34 -14
- package/src/session/agent-session.ts +109 -37
- package/src/session/internal/managed-session-scope.ts +981 -450
- package/src/session/internal/managed-session-storage.ts +418 -47
- package/src/session/internal/native-publish-outcome.ts +15 -3
- package/src/session/internal/session-open-errors.ts +11 -0
- package/src/session/session-manager.ts +27 -3
- package/src/session/session-storage.ts +72 -5
- package/src/slash-commands/builtin-registry.ts +117 -15
- package/src/system-prompt.ts +28 -15
- package/src/task/index.ts +9 -3
- package/src/tools/ask.ts +1 -1
- package/src/tools/bash.ts +4 -2
- package/src/tools/composer-bash-policy.ts +21 -5
- package/src/tools/telegram-send.ts +4 -2
- package/src/utils/command-args.ts +19 -30
- package/src/utils/lang-from-path.ts +2 -4
|
@@ -19,7 +19,7 @@ import { GJC_MODEL_ASSIGNMENT_TARGETS, type GjcModelAssignmentTargetId } from ".
|
|
|
19
19
|
import { formatModelSelectorValue } from "../../config/model-resolver";
|
|
20
20
|
import { selectorHead } from "../../config/model-selector-value";
|
|
21
21
|
import type { ModelProfileConfig } from "../../config/models-config-schema";
|
|
22
|
-
import { type Settings, settings } from "../../config/settings";
|
|
22
|
+
import { type Settings, type SettingsAtomicReceipt, settings } from "../../config/settings";
|
|
23
23
|
import { DebugSelectorComponent } from "../../debug";
|
|
24
24
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
25
25
|
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
@@ -50,16 +50,29 @@ import {
|
|
|
50
50
|
stopInteractiveActivityIndicator,
|
|
51
51
|
suspendInteractiveActivityIndicator,
|
|
52
52
|
} from "../../modes/types";
|
|
53
|
-
import {
|
|
53
|
+
import { ChatDaemonController } from "../../sdk/bus/chat-daemon-control";
|
|
54
|
+
import {
|
|
55
|
+
getCurrentTelegramActivationMarker,
|
|
56
|
+
getNotificationConfig,
|
|
57
|
+
isProviderEffectivelyEnabled,
|
|
58
|
+
isTelegramComplete,
|
|
59
|
+
maskToken,
|
|
60
|
+
type NotificationProvider,
|
|
61
|
+
} from "../../sdk/bus/config";
|
|
54
62
|
import {
|
|
55
63
|
clearTelegramActivationMarker,
|
|
56
64
|
createTelegramActivationMarker,
|
|
65
|
+
mutateNotificationProvider,
|
|
66
|
+
type NotificationProviderConfigurationMutation,
|
|
67
|
+
type NotificationProviderRuntimeAuthority,
|
|
57
68
|
observedTelegramActivationMarker,
|
|
58
69
|
persistTelegramActivationMarker,
|
|
59
70
|
proposedTelegramIdentity,
|
|
60
71
|
reconcileCommittedTelegramConfiguration,
|
|
72
|
+
removeNotificationProvider,
|
|
61
73
|
removeTelegramConfiguration,
|
|
62
74
|
saveTelegramInactive,
|
|
75
|
+
setGlobalNotificationsEnabled,
|
|
63
76
|
} from "../../sdk/bus/notification-orchestration";
|
|
64
77
|
import {
|
|
65
78
|
buildNotificationStatusReport,
|
|
@@ -141,6 +154,7 @@ import { JobsOverlayComponent } from "../components/jobs-overlay";
|
|
|
141
154
|
import { ModelSelectorComponent } from "../components/model-selector";
|
|
142
155
|
import type {
|
|
143
156
|
NotificationsEditorOperations,
|
|
157
|
+
PreparedNotificationProviderConfiguration,
|
|
144
158
|
PreparedTelegramConfiguration,
|
|
145
159
|
} from "../components/notifications-settings-editor";
|
|
146
160
|
import { OAuthSelectorComponent } from "../components/oauth-selector";
|
|
@@ -187,6 +201,11 @@ const CALLBACK_SERVER_PROVIDERS = new Set<string>([
|
|
|
187
201
|
|
|
188
202
|
const MANUAL_LOGIN_TIP = "Tip: You can complete pairing with /login <redirect URL>.";
|
|
189
203
|
|
|
204
|
+
/** Providers whose authorization server can display a code for `/login <provider> --manual`. */
|
|
205
|
+
const MANUAL_CODE_PROVIDERS = new Set<string>(["anthropic"]);
|
|
206
|
+
|
|
207
|
+
const MANUAL_CODE_LOGIN_TIP = "Tip: You can complete pairing with /login <code>.";
|
|
208
|
+
|
|
190
209
|
function isThemePreviewSuperseded(result: { success: boolean; error?: string }): boolean {
|
|
191
210
|
return !result.success && result.error?.includes("superseded by a newer request") === true;
|
|
192
211
|
}
|
|
@@ -234,6 +253,7 @@ type TelegramDaemonStopResult = {
|
|
|
234
253
|
|
|
235
254
|
export interface NotificationsEditorOperationDependencies {
|
|
236
255
|
getNotificationConfig: typeof getNotificationConfig;
|
|
256
|
+
getCurrentTelegramActivationMarker: typeof getCurrentTelegramActivationMarker;
|
|
237
257
|
maskToken: typeof maskToken;
|
|
238
258
|
buildNotificationStatusReport: typeof buildNotificationStatusReport;
|
|
239
259
|
checkNotificationHealth: typeof checkNotificationHealth;
|
|
@@ -258,10 +278,12 @@ export interface NotificationsEditorOperationDependencies {
|
|
|
258
278
|
reloadTelegramDaemon(settings: Settings): Promise<{ ok: boolean; message: string }>;
|
|
259
279
|
restartTelegramDaemon(settings: Settings): Promise<{ ok: boolean; message: string }>;
|
|
260
280
|
stopTelegramDaemon(settings: Settings): Promise<TelegramDaemonStopResult>;
|
|
281
|
+
providerRuntime?: NotificationProviderRuntimeAuthority;
|
|
261
282
|
}
|
|
262
283
|
|
|
263
284
|
const notificationEditorOperationDependencies: NotificationsEditorOperationDependencies = {
|
|
264
285
|
getNotificationConfig,
|
|
286
|
+
getCurrentTelegramActivationMarker,
|
|
265
287
|
maskToken,
|
|
266
288
|
buildNotificationStatusReport,
|
|
267
289
|
checkNotificationHealth,
|
|
@@ -298,9 +320,9 @@ function unavailableNotificationSessionStatus(): NotificationSessionStatus {
|
|
|
298
320
|
return {
|
|
299
321
|
eligible: false,
|
|
300
322
|
locallyEnabled: true,
|
|
301
|
-
|
|
323
|
+
genericSessionEnabled: false,
|
|
324
|
+
genericEligibilitySource: "none",
|
|
302
325
|
running: false,
|
|
303
|
-
environment: "off",
|
|
304
326
|
};
|
|
305
327
|
}
|
|
306
328
|
|
|
@@ -328,6 +350,10 @@ export function createNotificationsEditorOperations(
|
|
|
328
350
|
): NotificationsEditorOperations {
|
|
329
351
|
const services = { ...notificationEditorOperationDependencies, ...overrides };
|
|
330
352
|
const drafts = new WeakMap<PreparedTelegramConfiguration, string>();
|
|
353
|
+
const providerDrafts = new WeakMap<
|
|
354
|
+
PreparedNotificationProviderConfiguration,
|
|
355
|
+
{ botToken?: string; appToken?: string }
|
|
356
|
+
>();
|
|
331
357
|
const sessionContext = () => ({ sessionManager: ctx.sessionManager });
|
|
332
358
|
const notifyAfterDurableCommit = async (): Promise<void> => {
|
|
333
359
|
await ctx.notifyConfigChanged?.();
|
|
@@ -340,12 +366,52 @@ export function createNotificationsEditorOperations(
|
|
|
340
366
|
});
|
|
341
367
|
const telegramSetupPreflight = async (): Promise<TelegramSetupPreflight> =>
|
|
342
368
|
await services.resolveTelegramSetupPreflight(ctx.settings);
|
|
369
|
+
const providerRuntime: NotificationProviderRuntimeAuthority = services.providerRuntime ?? {
|
|
370
|
+
activate: async provider => {
|
|
371
|
+
if (provider === "telegram") {
|
|
372
|
+
const result = await reconnect();
|
|
373
|
+
if (result === "blocked_identity" || result === "disabled") {
|
|
374
|
+
throw new Error("Telegram activation failed after the durable save.");
|
|
375
|
+
}
|
|
376
|
+
await ctx.session.notificationSessionController?.reconcileCurrentSession(sessionContext());
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const result = await new ChatDaemonController(ctx.settings, provider).ensure();
|
|
380
|
+
if (result === "disabled") throw new Error(`${provider} activation failed after the durable save.`);
|
|
381
|
+
},
|
|
382
|
+
deactivate: async provider => {
|
|
383
|
+
if (provider === "telegram") {
|
|
384
|
+
const result = await services.stopTelegramDaemon(ctx.settings);
|
|
385
|
+
if (!result.ok) throw new Error(result.message);
|
|
386
|
+
await ctx.session.notificationSessionController?.reconcileCurrentSession(sessionContext());
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const result = await new ChatDaemonController(ctx.settings, provider).stop();
|
|
390
|
+
if (!result.ok) throw new Error(result.message);
|
|
391
|
+
},
|
|
392
|
+
};
|
|
343
393
|
|
|
344
394
|
return {
|
|
345
395
|
loadState: async () => {
|
|
346
396
|
const config = services.getNotificationConfig(ctx.settings);
|
|
397
|
+
const status = services.buildNotificationStatusReport(ctx.settings);
|
|
398
|
+
const runtime = async (provider: NotificationProvider) => {
|
|
399
|
+
const daemon =
|
|
400
|
+
provider === "telegram"
|
|
401
|
+
? await new TelegramDaemonController(ctx.settings).status()
|
|
402
|
+
: await new ChatDaemonController(ctx.settings, provider).status();
|
|
403
|
+
return daemon.health === "running" ? "ready" : daemon.health === "not_configured" ? "inactive" : "failed";
|
|
404
|
+
};
|
|
405
|
+
const [telegramRuntime, discordRuntime, slackRuntime] = await Promise.all([
|
|
406
|
+
runtime("telegram"),
|
|
407
|
+
runtime("discord"),
|
|
408
|
+
runtime("slack"),
|
|
409
|
+
]);
|
|
410
|
+
status.telegram.runtime = telegramRuntime;
|
|
411
|
+
status.discord.runtime = discordRuntime;
|
|
412
|
+
status.slack.runtime = slackRuntime;
|
|
347
413
|
return {
|
|
348
|
-
status
|
|
414
|
+
status,
|
|
349
415
|
session:
|
|
350
416
|
ctx.session.notificationSessionController?.query(sessionContext()) ??
|
|
351
417
|
unavailableNotificationSessionStatus(),
|
|
@@ -362,13 +428,14 @@ export function createNotificationsEditorOperations(
|
|
|
362
428
|
};
|
|
363
429
|
},
|
|
364
430
|
|
|
365
|
-
refreshHealth: async ({ probe, signal }) => {
|
|
431
|
+
refreshHealth: async ({ probe, provider, signal }) => {
|
|
366
432
|
if (signal?.aborted) throw new Error("Notification health refresh cancelled.");
|
|
367
433
|
try {
|
|
368
434
|
const input: Parameters<typeof checkNotificationHealth>[0] & { signal?: AbortSignal } = {
|
|
369
435
|
settings: ctx.settings,
|
|
370
436
|
stateRoot: path.join(ctx.sessionManager.getCwd(), ".gjc", "state"),
|
|
371
437
|
probe,
|
|
438
|
+
provider,
|
|
372
439
|
signal,
|
|
373
440
|
};
|
|
374
441
|
const report = await services.checkNotificationHealth(input);
|
|
@@ -390,9 +457,21 @@ export function createNotificationsEditorOperations(
|
|
|
390
457
|
}
|
|
391
458
|
},
|
|
392
459
|
|
|
393
|
-
sendTest: async
|
|
460
|
+
sendTest: async provider => {
|
|
394
461
|
try {
|
|
395
|
-
const result = await services.sendNotificationTest({
|
|
462
|
+
const result = await services.sendNotificationTest({
|
|
463
|
+
settings: ctx.settings,
|
|
464
|
+
provider,
|
|
465
|
+
deps: {
|
|
466
|
+
providerRuntimeStatus: async selected => {
|
|
467
|
+
const status =
|
|
468
|
+
selected === "telegram"
|
|
469
|
+
? await new TelegramDaemonController(ctx.settings).status()
|
|
470
|
+
: await new ChatDaemonController(ctx.settings, selected).status();
|
|
471
|
+
return status.health === "running" ? "ready" : "inactive";
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
});
|
|
396
475
|
return {
|
|
397
476
|
...result,
|
|
398
477
|
detail: services.sanitizeDiagnostic(
|
|
@@ -531,12 +610,14 @@ export function createNotificationsEditorOperations(
|
|
|
531
610
|
commitConfigure: async draft => {
|
|
532
611
|
const token = drafts.get(draft);
|
|
533
612
|
if (!token) throw new Error("The Telegram setup draft expired. Re-enter the masked bot token.");
|
|
613
|
+
let receipt: SettingsAtomicReceipt | undefined;
|
|
534
614
|
try {
|
|
535
615
|
const inactiveMarkerToClear = observedTelegramActivationMarker(ctx.settings, token, draft.chatId);
|
|
536
|
-
|
|
616
|
+
receipt = await ctx.settings.commitAtomicBatch([
|
|
537
617
|
{ path: "notifications.enabled", op: "set", value: true },
|
|
538
618
|
{ path: "notifications.telegram.botToken", op: "set", value: token },
|
|
539
619
|
{ path: "notifications.telegram.chatId", op: "set", value: draft.chatId },
|
|
620
|
+
{ path: "notifications.telegram.enabled", op: "set", value: true },
|
|
540
621
|
{ path: "notifications.telegram.rich.enabled", op: "set", value: draft.richEnabled },
|
|
541
622
|
{ path: "notifications.telegram.richDraft.enabled", op: "set", value: draft.richDraftEnabled },
|
|
542
623
|
{ path: "notifications.telegram.streaming.enabled", op: "set", value: draft.streamingEnabled },
|
|
@@ -571,12 +652,20 @@ export function createNotificationsEditorOperations(
|
|
|
571
652
|
marker: activationMarker,
|
|
572
653
|
},
|
|
573
654
|
});
|
|
574
|
-
|
|
655
|
+
let observerFailed = false;
|
|
656
|
+
try {
|
|
657
|
+
await notifyAfterDurableCommit();
|
|
658
|
+
} catch {
|
|
659
|
+
observerFailed = true;
|
|
660
|
+
}
|
|
575
661
|
if (activation.status === "blocked_identity") {
|
|
576
662
|
return {
|
|
577
663
|
status: "blocked_identity" as const,
|
|
578
664
|
receipt,
|
|
579
|
-
message: services.sanitizeDiagnostic(
|
|
665
|
+
message: services.sanitizeDiagnostic(
|
|
666
|
+
`${activation.message}${observerFailed ? " The settings observer also failed after the durable commit." : ""}`,
|
|
667
|
+
token,
|
|
668
|
+
),
|
|
580
669
|
restore: async () => {
|
|
581
670
|
const restored = await activation.restore();
|
|
582
671
|
if (restored.status === "restored" || restored.status === "still_blocked") {
|
|
@@ -587,12 +676,36 @@ export function createNotificationsEditorOperations(
|
|
|
587
676
|
retainCommitted: () => activation.retainCommitted(),
|
|
588
677
|
};
|
|
589
678
|
}
|
|
679
|
+
if (activation.status === "activation_failed") {
|
|
680
|
+
return {
|
|
681
|
+
status: "activation_failed" as const,
|
|
682
|
+
receipt,
|
|
683
|
+
message: services.sanitizeDiagnostic(
|
|
684
|
+
`${activation.message}${observerFailed ? " The settings observer also failed after the durable commit." : ""}`,
|
|
685
|
+
token,
|
|
686
|
+
),
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
if (observerFailed) {
|
|
690
|
+
return {
|
|
691
|
+
status: "observer_failed" as const,
|
|
692
|
+
receipt,
|
|
693
|
+
message: "Telegram configuration was saved and activated, but the settings observer failed.",
|
|
694
|
+
};
|
|
695
|
+
}
|
|
590
696
|
return {
|
|
591
697
|
status: "saved" as const,
|
|
592
698
|
receipt,
|
|
593
699
|
message: services.sanitizeDiagnostic("Telegram configuration saved and reconciled.", token),
|
|
594
700
|
};
|
|
595
701
|
} catch (error) {
|
|
702
|
+
if (receipt) {
|
|
703
|
+
return {
|
|
704
|
+
status: "activation_failed" as const,
|
|
705
|
+
receipt,
|
|
706
|
+
message: "Telegram configuration was saved, but post-commit activation failed.",
|
|
707
|
+
};
|
|
708
|
+
}
|
|
596
709
|
throw notificationOperationError(services, error, token);
|
|
597
710
|
}
|
|
598
711
|
},
|
|
@@ -607,7 +720,15 @@ export function createNotificationsEditorOperations(
|
|
|
607
720
|
chatId: draft.chatId,
|
|
608
721
|
});
|
|
609
722
|
drafts.delete(draft);
|
|
610
|
-
|
|
723
|
+
try {
|
|
724
|
+
await notifyAfterDurableCommit();
|
|
725
|
+
} catch {
|
|
726
|
+
return {
|
|
727
|
+
status: "observer_failed" as const,
|
|
728
|
+
receipt: result.receipt,
|
|
729
|
+
message: "Telegram configuration was saved inactive, but the settings observer failed.",
|
|
730
|
+
};
|
|
731
|
+
}
|
|
611
732
|
return {
|
|
612
733
|
status: "saved_inactive" as const,
|
|
613
734
|
receipt: result.receipt,
|
|
@@ -622,28 +743,289 @@ export function createNotificationsEditorOperations(
|
|
|
622
743
|
drafts.delete(draft);
|
|
623
744
|
},
|
|
624
745
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
746
|
+
prepareProviderConfiguration: async input => {
|
|
747
|
+
const cfg = services.getNotificationConfig(ctx.settings);
|
|
748
|
+
const consumeSecret = (action: typeof input.botToken): string | undefined => {
|
|
749
|
+
if (!action.value) return undefined;
|
|
750
|
+
const value = action.value.consume();
|
|
751
|
+
return action.action === "replace" ? value : undefined;
|
|
752
|
+
};
|
|
753
|
+
const botToken = consumeSecret(input.botToken);
|
|
754
|
+
const appToken = input.appToken ? consumeSecret(input.appToken) : undefined;
|
|
755
|
+
if (input.botToken.action === "replace" && !botToken?.trim()) {
|
|
756
|
+
throw new Error("A non-blank bot token replacement is required.");
|
|
634
757
|
}
|
|
758
|
+
if (input.provider === "slack" && input.appToken?.action === "replace" && !appToken?.trim()) {
|
|
759
|
+
throw new Error("A non-blank Slack app token replacement is required.");
|
|
760
|
+
}
|
|
761
|
+
if (input.provider === "discord") {
|
|
762
|
+
const applicationId = input.applicationId?.trim() || undefined;
|
|
763
|
+
const guildId = input.guildId?.trim() || undefined;
|
|
764
|
+
const parentChannelId = input.parentChannelId?.trim() || undefined;
|
|
765
|
+
const resolvedApplicationId = applicationId ?? cfg.discord.applicationId;
|
|
766
|
+
const resolvedGuildId = guildId ?? cfg.discord.guildId;
|
|
767
|
+
const resolvedParentChannelId = parentChannelId ?? cfg.discord.parentChannelId;
|
|
768
|
+
const removesSecret = input.botToken.action === "remove";
|
|
769
|
+
if (!removesSecret && (!resolvedApplicationId || !resolvedGuildId || !resolvedParentChannelId)) {
|
|
770
|
+
throw new Error("Discord application, guild, and parent channel IDs are required.");
|
|
771
|
+
}
|
|
772
|
+
if (!removesSecret && input.botToken.action === "keep" && !cfg.discord.botToken) {
|
|
773
|
+
throw new Error("Discord has no stored bot token to keep.");
|
|
774
|
+
}
|
|
775
|
+
const draft: PreparedNotificationProviderConfiguration = {
|
|
776
|
+
provider: "discord",
|
|
777
|
+
botTokenDisposition: input.botToken.action,
|
|
778
|
+
botTokenMask: maskToken(botToken ?? cfg.discord.botToken),
|
|
779
|
+
...(applicationId === undefined ? {} : { applicationId }),
|
|
780
|
+
...(guildId === undefined ? {} : { guildId }),
|
|
781
|
+
...(parentChannelId === undefined ? {} : { parentChannelId }),
|
|
782
|
+
applicationIdDisplay: resolvedApplicationId,
|
|
783
|
+
guildIdDisplay: resolvedGuildId,
|
|
784
|
+
parentChannelIdDisplay: resolvedParentChannelId,
|
|
785
|
+
};
|
|
786
|
+
providerDrafts.set(draft, { ...(botToken === undefined ? {} : { botToken }) });
|
|
787
|
+
return draft;
|
|
788
|
+
}
|
|
789
|
+
const workspaceId = input.workspaceId?.trim() || undefined;
|
|
790
|
+
const channelId = input.channelId?.trim() || undefined;
|
|
791
|
+
const authorizedUserId = input.authorizedUserId?.trim() || undefined;
|
|
792
|
+
const resolvedWorkspaceId = workspaceId ?? cfg.slack.workspaceId;
|
|
793
|
+
const resolvedChannelId = channelId ?? cfg.slack.channelId;
|
|
794
|
+
const resolvedAuthorizedUserId = authorizedUserId ?? cfg.slack.authorizedUserId;
|
|
795
|
+
const appDisposition = input.appToken?.action ?? "keep";
|
|
796
|
+
const removesSecret = input.botToken.action === "remove" || appDisposition === "remove";
|
|
797
|
+
if (!removesSecret && (!resolvedWorkspaceId || !resolvedChannelId)) {
|
|
798
|
+
throw new Error("Slack workspace and channel IDs are required.");
|
|
799
|
+
}
|
|
800
|
+
if (!removesSecret && input.botToken.action === "keep" && !cfg.slack.botToken) {
|
|
801
|
+
throw new Error("Slack has no stored bot token to keep.");
|
|
802
|
+
}
|
|
803
|
+
if (!removesSecret && appDisposition === "keep" && !cfg.slack.appToken) {
|
|
804
|
+
throw new Error("Slack has no stored app token to keep.");
|
|
805
|
+
}
|
|
806
|
+
const draft: PreparedNotificationProviderConfiguration = {
|
|
807
|
+
provider: "slack",
|
|
808
|
+
botTokenDisposition: input.botToken.action,
|
|
809
|
+
botTokenMask: maskToken(botToken ?? cfg.slack.botToken),
|
|
810
|
+
appTokenDisposition: appDisposition,
|
|
811
|
+
appTokenMask: maskToken(appToken ?? cfg.slack.appToken),
|
|
812
|
+
...(workspaceId === undefined ? {} : { workspaceId }),
|
|
813
|
+
...(channelId === undefined ? {} : { channelId }),
|
|
814
|
+
...(authorizedUserId === undefined ? {} : { authorizedUserId }),
|
|
815
|
+
workspaceIdDisplay: resolvedWorkspaceId,
|
|
816
|
+
channelIdDisplay: resolvedChannelId,
|
|
817
|
+
authorizedUserIdDisplay: resolvedAuthorizedUserId,
|
|
818
|
+
};
|
|
819
|
+
providerDrafts.set(draft, {
|
|
820
|
+
...(botToken === undefined ? {} : { botToken }),
|
|
821
|
+
...(appToken === undefined ? {} : { appToken }),
|
|
822
|
+
});
|
|
823
|
+
return draft;
|
|
635
824
|
},
|
|
636
825
|
|
|
637
|
-
|
|
826
|
+
commitProviderConfiguration: async draft => {
|
|
827
|
+
const secrets = providerDrafts.get(draft) ?? {};
|
|
638
828
|
try {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
829
|
+
let mutation: NotificationProviderConfigurationMutation;
|
|
830
|
+
if (draft.provider === "discord") {
|
|
831
|
+
mutation = {
|
|
832
|
+
provider: "discord",
|
|
833
|
+
botToken:
|
|
834
|
+
draft.botTokenDisposition === "replace"
|
|
835
|
+
? { action: "replace", value: secrets.botToken ?? "" }
|
|
836
|
+
: { action: draft.botTokenDisposition },
|
|
837
|
+
applicationId: draft.applicationId,
|
|
838
|
+
guildId: draft.guildId,
|
|
839
|
+
parentChannelId: draft.parentChannelId,
|
|
840
|
+
};
|
|
841
|
+
} else {
|
|
842
|
+
const appDisposition = draft.appTokenDisposition ?? "keep";
|
|
843
|
+
mutation = {
|
|
844
|
+
provider: "slack",
|
|
845
|
+
botToken:
|
|
846
|
+
draft.botTokenDisposition === "replace"
|
|
847
|
+
? { action: "replace", value: secrets.botToken ?? "" }
|
|
848
|
+
: { action: draft.botTokenDisposition },
|
|
849
|
+
appToken:
|
|
850
|
+
appDisposition === "replace"
|
|
851
|
+
? { action: "replace", value: secrets.appToken ?? "" }
|
|
852
|
+
: { action: appDisposition },
|
|
853
|
+
workspaceId: draft.workspaceId,
|
|
854
|
+
channelId: draft.channelId,
|
|
855
|
+
authorizedUserId: draft.authorizedUserId,
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
const removesSecret = draft.botTokenDisposition === "remove" || draft.appTokenDisposition === "remove";
|
|
859
|
+
const result = await mutateNotificationProvider({
|
|
860
|
+
settings: ctx.settings,
|
|
861
|
+
mutation,
|
|
862
|
+
configureAndActivate: !removesSecret,
|
|
863
|
+
...(removesSecret ? { desiredEnabled: false } : {}),
|
|
864
|
+
notifyConfigChanged: notifyAfterDurableCommit,
|
|
865
|
+
runtime: providerRuntime,
|
|
866
|
+
});
|
|
867
|
+
const outcome =
|
|
868
|
+
result.status === "commit_failed"
|
|
869
|
+
? "failed"
|
|
870
|
+
: result.status === "activation_failed" ||
|
|
871
|
+
result.status === "deactivation_failed" ||
|
|
872
|
+
result.observerFailed
|
|
873
|
+
? "degraded"
|
|
874
|
+
: "success";
|
|
875
|
+
const observerSuffix =
|
|
876
|
+
result.status !== "commit_failed" && result.observerFailed
|
|
877
|
+
? " The settings observer also failed after the durable commit."
|
|
878
|
+
: "";
|
|
879
|
+
return {
|
|
880
|
+
...(result.status === "commit_failed" ? {} : { receipt: result.receipt }),
|
|
881
|
+
outcome,
|
|
882
|
+
message:
|
|
883
|
+
(result.status === "activated"
|
|
884
|
+
? `${draft.provider} configuration saved and activated.`
|
|
885
|
+
: result.status === "activation_failed"
|
|
886
|
+
? `${draft.provider} configuration and desired intent were saved, but runtime activation failed.`
|
|
887
|
+
: result.status === "deactivation_failed"
|
|
888
|
+
? `${draft.provider} desired-off configuration was saved, but runtime deactivation failed.`
|
|
889
|
+
: result.status === "commit_failed"
|
|
890
|
+
? `${draft.provider} configuration was not saved because the CAS commit failed.`
|
|
891
|
+
: `${draft.provider} configuration saved.`) + observerSuffix,
|
|
892
|
+
};
|
|
893
|
+
} finally {
|
|
894
|
+
providerDrafts.delete(draft);
|
|
895
|
+
}
|
|
896
|
+
},
|
|
897
|
+
|
|
898
|
+
discardProviderConfiguration: draft => {
|
|
899
|
+
providerDrafts.delete(draft);
|
|
900
|
+
},
|
|
901
|
+
|
|
902
|
+
setProviderDesired: async (provider, enabled) => {
|
|
903
|
+
if (provider === "telegram" && enabled) {
|
|
904
|
+
const config = services.getNotificationConfig(ctx.settings);
|
|
905
|
+
if (services.getCurrentTelegramActivationMarker(config)) {
|
|
906
|
+
return {
|
|
907
|
+
outcome: "failed",
|
|
908
|
+
message:
|
|
909
|
+
"Telegram remains inactive because its exact activation marker must be restored or cleared after safe owner readiness.",
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
const mutation: NotificationProviderConfigurationMutation =
|
|
914
|
+
provider === "telegram"
|
|
915
|
+
? { provider, botToken: { action: "keep" } }
|
|
916
|
+
: provider === "discord"
|
|
917
|
+
? { provider, botToken: { action: "keep" } }
|
|
918
|
+
: { provider, botToken: { action: "keep" }, appToken: { action: "keep" } };
|
|
919
|
+
const result = await mutateNotificationProvider({
|
|
920
|
+
settings: ctx.settings,
|
|
921
|
+
mutation,
|
|
922
|
+
desiredEnabled: enabled,
|
|
923
|
+
notifyConfigChanged: notifyAfterDurableCommit,
|
|
924
|
+
runtime: providerRuntime,
|
|
925
|
+
});
|
|
926
|
+
const outcome =
|
|
927
|
+
result.status === "commit_failed"
|
|
928
|
+
? "failed"
|
|
929
|
+
: result.status === "activation_failed" ||
|
|
930
|
+
result.status === "deactivation_failed" ||
|
|
931
|
+
result.observerFailed
|
|
932
|
+
? "degraded"
|
|
933
|
+
: "success";
|
|
934
|
+
return {
|
|
935
|
+
...(result.status === "commit_failed" ? {} : { receipt: result.receipt }),
|
|
936
|
+
outcome,
|
|
937
|
+
message:
|
|
938
|
+
(result.status === "activation_failed"
|
|
939
|
+
? `${provider} desired intent was saved, but activation failed.`
|
|
940
|
+
: result.status === "deactivation_failed"
|
|
941
|
+
? `${provider} desired-off intent was saved, but deactivation failed.`
|
|
942
|
+
: result.status === "commit_failed"
|
|
943
|
+
? `${provider} desired intent was not saved because the CAS commit failed.`
|
|
944
|
+
: `${provider} desired intent ${enabled ? "enabled" : "disabled"}.`) +
|
|
945
|
+
(result.status !== "commit_failed" && result.observerFailed
|
|
946
|
+
? " The settings observer also failed after the durable commit."
|
|
947
|
+
: ""),
|
|
948
|
+
};
|
|
949
|
+
},
|
|
950
|
+
|
|
951
|
+
removeProvider: async provider => {
|
|
952
|
+
if (provider === "telegram") {
|
|
953
|
+
return { message: "Use the Telegram removal action to preserve root-registration fencing." };
|
|
646
954
|
}
|
|
955
|
+
const result = await removeNotificationProvider({
|
|
956
|
+
settings: ctx.settings,
|
|
957
|
+
provider,
|
|
958
|
+
runtime: providerRuntime,
|
|
959
|
+
notifyConfigChanged: notifyAfterDurableCommit,
|
|
960
|
+
});
|
|
961
|
+
const degraded =
|
|
962
|
+
result.status === "deactivation_failed" || ("observerFailed" in result && result.observerFailed);
|
|
963
|
+
return {
|
|
964
|
+
...("receipt" in result && result.receipt ? { receipt: result.receipt } : {}),
|
|
965
|
+
outcome:
|
|
966
|
+
result.status === "commit_failed" || result.status === "commit_failed_after_teardown"
|
|
967
|
+
? "failed"
|
|
968
|
+
: degraded
|
|
969
|
+
? "degraded"
|
|
970
|
+
: "success",
|
|
971
|
+
message:
|
|
972
|
+
(result.status === "removed"
|
|
973
|
+
? `${provider} configuration removed.`
|
|
974
|
+
: result.status === "deactivation_failed"
|
|
975
|
+
? `${provider} configuration was removed, but runtime deactivation failed.`
|
|
976
|
+
: `${provider} configuration removal failed.`) +
|
|
977
|
+
("observerFailed" in result && result.observerFailed
|
|
978
|
+
? " The settings observer also failed after the durable commit."
|
|
979
|
+
: ""),
|
|
980
|
+
};
|
|
981
|
+
},
|
|
982
|
+
|
|
983
|
+
enableGlobally: async () => {
|
|
984
|
+
const result = await setGlobalNotificationsEnabled({
|
|
985
|
+
settings: ctx.settings,
|
|
986
|
+
enabled: true,
|
|
987
|
+
notifyConfigChanged: notifyAfterDurableCommit,
|
|
988
|
+
runtime: providerRuntime,
|
|
989
|
+
});
|
|
990
|
+
const degraded =
|
|
991
|
+
result.status === "global_activation_partial" || ("observerFailed" in result && result.observerFailed);
|
|
992
|
+
return {
|
|
993
|
+
...(result.status === "commit_failed" ? {} : { receipt: result.receipt }),
|
|
994
|
+
outcome: result.status === "commit_failed" ? "failed" : degraded ? "degraded" : "success",
|
|
995
|
+
message:
|
|
996
|
+
(result.status === "global_activation_partial"
|
|
997
|
+
? `Global notifications were enabled, but activation failed for ${result.failed.join(", ")}.`
|
|
998
|
+
: result.status === "commit_failed"
|
|
999
|
+
? "Global notifications were not enabled because the CAS commit failed."
|
|
1000
|
+
: "Global notifications enabled using stored provider intent.") +
|
|
1001
|
+
(result.status !== "commit_failed" && result.observerFailed
|
|
1002
|
+
? " The settings observer also failed after the durable commit."
|
|
1003
|
+
: ""),
|
|
1004
|
+
};
|
|
1005
|
+
},
|
|
1006
|
+
|
|
1007
|
+
disableGlobally: async () => {
|
|
1008
|
+
const result = await setGlobalNotificationsEnabled({
|
|
1009
|
+
settings: ctx.settings,
|
|
1010
|
+
enabled: false,
|
|
1011
|
+
notifyConfigChanged: notifyAfterDurableCommit,
|
|
1012
|
+
runtime: providerRuntime,
|
|
1013
|
+
});
|
|
1014
|
+
const degraded =
|
|
1015
|
+
result.status === "global_deactivation_partial" || ("observerFailed" in result && result.observerFailed);
|
|
1016
|
+
return {
|
|
1017
|
+
...(result.status === "commit_failed" ? {} : { receipt: result.receipt }),
|
|
1018
|
+
outcome: result.status === "commit_failed" ? "failed" : degraded ? "degraded" : "success",
|
|
1019
|
+
message:
|
|
1020
|
+
(result.status === "global_deactivation_partial"
|
|
1021
|
+
? `Global notifications were disabled, but teardown failed for ${result.failed.join(", ")}.`
|
|
1022
|
+
: result.status === "commit_failed"
|
|
1023
|
+
? "Global notifications were not disabled because the CAS commit failed."
|
|
1024
|
+
: "Global notifications disabled; provider configuration and desired intent were preserved.") +
|
|
1025
|
+
(result.status !== "commit_failed" && result.observerFailed
|
|
1026
|
+
? " The settings observer also failed after the durable commit."
|
|
1027
|
+
: ""),
|
|
1028
|
+
};
|
|
647
1029
|
},
|
|
648
1030
|
|
|
649
1031
|
removeTelegram: async () => {
|
|
@@ -676,17 +1058,27 @@ export function createNotificationsEditorOperations(
|
|
|
676
1058
|
},
|
|
677
1059
|
},
|
|
678
1060
|
});
|
|
1061
|
+
const postCommitFailures: string[] = [];
|
|
679
1062
|
if (runtimePrepared && controller) {
|
|
680
|
-
|
|
681
|
-
|
|
1063
|
+
try {
|
|
1064
|
+
await controller.clearBlockedRuntime(sessionContext());
|
|
1065
|
+
await controller.reconcileCurrentSession(sessionContext());
|
|
1066
|
+
} catch {
|
|
1067
|
+
postCommitFailures.push("current-session reconciliation failed");
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
try {
|
|
1071
|
+
await notifyAfterDurableCommit();
|
|
1072
|
+
} catch {
|
|
1073
|
+
postCommitFailures.push("settings observer failed");
|
|
682
1074
|
}
|
|
683
|
-
await notifyAfterDurableCommit();
|
|
684
1075
|
return {
|
|
685
1076
|
receipt: result.receipt,
|
|
1077
|
+
outcome: postCommitFailures.length === 0 ? "success" : "degraded",
|
|
686
1078
|
globallyDisabled: result.globallyDisabled,
|
|
687
|
-
message:
|
|
688
|
-
? "
|
|
689
|
-
|
|
1079
|
+
message: `Telegram configuration removed without changing the global master.${
|
|
1080
|
+
postCommitFailures.length === 0 ? "" : ` Post-commit ${postCommitFailures.join(" and ")}.`
|
|
1081
|
+
}`,
|
|
690
1082
|
};
|
|
691
1083
|
} catch (error) {
|
|
692
1084
|
if (runtimePrepared) {
|
|
@@ -715,7 +1107,10 @@ export function createNotificationsEditorOperations(
|
|
|
715
1107
|
try {
|
|
716
1108
|
const before = services.getNotificationConfig(ctx.settings);
|
|
717
1109
|
const disablingToolActivity =
|
|
718
|
-
|
|
1110
|
+
isProviderEffectivelyEnabled(before, "telegram") &&
|
|
1111
|
+
isTelegramComplete(before) &&
|
|
1112
|
+
before.toolActivity.enabled &&
|
|
1113
|
+
!preferences.toolActivityEnabled;
|
|
719
1114
|
if (disablingToolActivity) {
|
|
720
1115
|
const stopped = await services.stopTelegramDaemon(ctx.settings);
|
|
721
1116
|
if (!stopped.ok)
|
|
@@ -725,7 +1120,7 @@ export function createNotificationsEditorOperations(
|
|
|
725
1120
|
daemonWasRunningForDisable = stopped.before?.health === "running";
|
|
726
1121
|
}
|
|
727
1122
|
|
|
728
|
-
let receipt:
|
|
1123
|
+
let receipt: SettingsAtomicReceipt;
|
|
729
1124
|
try {
|
|
730
1125
|
receipt = await ctx.settings.commitAtomicBatch([
|
|
731
1126
|
{ path: "notifications.redact", op: "set", value: preferences.redact },
|
|
@@ -758,16 +1153,31 @@ export function createNotificationsEditorOperations(
|
|
|
758
1153
|
throw error;
|
|
759
1154
|
}
|
|
760
1155
|
|
|
1156
|
+
const postCommitFailures: string[] = [];
|
|
761
1157
|
const config = services.getNotificationConfig(ctx.settings);
|
|
762
|
-
if (
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
1158
|
+
if (isProviderEffectivelyEnabled(config, "telegram") && isTelegramComplete(config)) {
|
|
1159
|
+
try {
|
|
1160
|
+
const reload = daemonWasRunningForDisable
|
|
1161
|
+
? await services.restartTelegramDaemon(ctx.settings)
|
|
1162
|
+
: await services.reloadTelegramDaemon(ctx.settings);
|
|
1163
|
+
if (!reload.ok) postCommitFailures.push(`daemon reload failed: ${reload.message}`);
|
|
1164
|
+
} catch {
|
|
1165
|
+
postCommitFailures.push("daemon reload failed");
|
|
1166
|
+
}
|
|
768
1167
|
}
|
|
769
|
-
|
|
770
|
-
|
|
1168
|
+
try {
|
|
1169
|
+
await notifyAfterDurableCommit();
|
|
1170
|
+
} catch {
|
|
1171
|
+
postCommitFailures.push("settings observer failed");
|
|
1172
|
+
}
|
|
1173
|
+
return {
|
|
1174
|
+
receipt,
|
|
1175
|
+
outcome: postCommitFailures.length === 0 ? "success" : "degraded",
|
|
1176
|
+
message:
|
|
1177
|
+
postCommitFailures.length === 0
|
|
1178
|
+
? "Notification preferences saved atomically."
|
|
1179
|
+
: `Notification preferences were saved, but post-commit ${postCommitFailures.join(" and ")}.`,
|
|
1180
|
+
};
|
|
771
1181
|
} catch (error) {
|
|
772
1182
|
throw notificationOperationError(services, error, services.getNotificationConfig(ctx.settings).botToken);
|
|
773
1183
|
}
|
|
@@ -2540,63 +2950,85 @@ export class SelectorController {
|
|
|
2540
2950
|
});
|
|
2541
2951
|
}
|
|
2542
2952
|
|
|
2543
|
-
async #handleOAuthLogin(providerId: string): Promise<void> {
|
|
2953
|
+
async #handleOAuthLogin(providerId: string, options?: OAuthSelectorOptions): Promise<void> {
|
|
2954
|
+
const manualCode = options?.manualCode === true;
|
|
2955
|
+
if (manualCode && !MANUAL_CODE_PROVIDERS.has(providerId)) {
|
|
2956
|
+
this.ctx.showError(
|
|
2957
|
+
`${providerId} has no paste-a-code login. Supported: ${[...MANUAL_CODE_PROVIDERS].join(", ")}.`,
|
|
2958
|
+
);
|
|
2959
|
+
return;
|
|
2960
|
+
}
|
|
2544
2961
|
this.ctx.showStatus(`Logging in to ${providerId}…`);
|
|
2545
2962
|
const manualInput = this.ctx.oauthManualInput;
|
|
2546
2963
|
const useManualInput = CALLBACK_SERVER_PROVIDERS.has(providerId as OAuthProvider);
|
|
2964
|
+
if (providerId === "opencodex") {
|
|
2965
|
+
this.ctx.showStatus("Checking the local OpenCodex proxy…");
|
|
2966
|
+
}
|
|
2547
2967
|
try {
|
|
2548
|
-
await this.ctx.session.modelRegistry.authStorage.login(
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
const hyperlink = `\x1b]8;;${info.url}\x07Click here to login\x1b]8;;\x07`;
|
|
2553
|
-
this.ctx.chatContainer.addChild(new Text(theme.fg("accent", hyperlink), 1, 0));
|
|
2554
|
-
if (info.instructions) {
|
|
2968
|
+
await this.ctx.session.modelRegistry.authStorage.login(
|
|
2969
|
+
providerId as OAuthProvider,
|
|
2970
|
+
{
|
|
2971
|
+
onAuth: (info: { url: string; instructions?: string }) => {
|
|
2555
2972
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
2556
|
-
this.ctx.chatContainer.addChild(new Text(theme.fg("
|
|
2557
|
-
|
|
2558
|
-
|
|
2973
|
+
this.ctx.chatContainer.addChild(new Text(theme.fg("dim", info.url), 1, 0));
|
|
2974
|
+
const hyperlink = `\x1b]8;;${info.url}\x07Click here to login\x1b]8;;\x07`;
|
|
2975
|
+
this.ctx.chatContainer.addChild(new Text(theme.fg("accent", hyperlink), 1, 0));
|
|
2976
|
+
if (info.instructions) {
|
|
2977
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
2978
|
+
this.ctx.chatContainer.addChild(new Text(theme.fg("warning", info.instructions), 1, 0));
|
|
2979
|
+
}
|
|
2980
|
+
if (useManualInput) {
|
|
2981
|
+
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
2982
|
+
this.ctx.chatContainer.addChild(
|
|
2983
|
+
new Text(theme.fg("dim", manualCode ? MANUAL_CODE_LOGIN_TIP : MANUAL_LOGIN_TIP), 1, 0),
|
|
2984
|
+
);
|
|
2985
|
+
}
|
|
2986
|
+
this.ctx.ui.requestRender();
|
|
2987
|
+
this.ctx.openInBrowser(info.url);
|
|
2988
|
+
},
|
|
2989
|
+
onPrompt: async (prompt: { message: string; placeholder?: string }) => {
|
|
2559
2990
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
2560
|
-
this.ctx.chatContainer.addChild(new Text(theme.fg("
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
codeInput.onSubmit = () => {
|
|
2575
|
-
const code = codeInput.getValue();
|
|
2991
|
+
this.ctx.chatContainer.addChild(new Text(theme.fg("warning", prompt.message), 1, 0));
|
|
2992
|
+
if (prompt.placeholder) {
|
|
2993
|
+
this.ctx.chatContainer.addChild(new Text(theme.fg("dim", prompt.placeholder), 1, 0));
|
|
2994
|
+
}
|
|
2995
|
+
this.ctx.ui.requestRender();
|
|
2996
|
+
const { promise, resolve } = Promise.withResolvers<string>();
|
|
2997
|
+
const codeInput = new Input();
|
|
2998
|
+
codeInput.onSubmit = () => {
|
|
2999
|
+
const code = codeInput.getValue();
|
|
3000
|
+
this.ctx.editorContainer.clear();
|
|
3001
|
+
this.ctx.editorContainer.addChild(this.ctx.editor);
|
|
3002
|
+
this.ctx.ui.setFocus(this.ctx.editor);
|
|
3003
|
+
resolve(code);
|
|
3004
|
+
};
|
|
2576
3005
|
this.ctx.editorContainer.clear();
|
|
2577
|
-
this.ctx.editorContainer.addChild(
|
|
2578
|
-
this.ctx.ui.setFocus(
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
onProgress: (message: string) => {
|
|
2588
|
-
this.ctx.chatContainer.addChild(new Text(theme.fg("dim", message), 1, 0));
|
|
2589
|
-
this.ctx.ui.requestRender();
|
|
3006
|
+
this.ctx.editorContainer.addChild(codeInput);
|
|
3007
|
+
this.ctx.ui.setFocus(codeInput);
|
|
3008
|
+
this.ctx.ui.requestRender();
|
|
3009
|
+
return promise;
|
|
3010
|
+
},
|
|
3011
|
+
onProgress: (message: string) => {
|
|
3012
|
+
this.ctx.chatContainer.addChild(new Text(theme.fg("dim", message), 1, 0));
|
|
3013
|
+
this.ctx.ui.requestRender();
|
|
3014
|
+
},
|
|
3015
|
+
onManualCodeInput: useManualInput ? () => manualInput.waitForInput(providerId) : undefined,
|
|
2590
3016
|
},
|
|
2591
|
-
|
|
2592
|
-
|
|
3017
|
+
{ manualCode },
|
|
3018
|
+
);
|
|
2593
3019
|
await this.ctx.session.modelRegistry.refresh();
|
|
2594
3020
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
3021
|
+
const successMessage =
|
|
3022
|
+
providerId === "opencodex"
|
|
3023
|
+
? `${theme.status.success} OpenCodex proxy status checked`
|
|
3024
|
+
: `${theme.status.success} Successfully logged in to ${providerId}`;
|
|
3025
|
+
this.ctx.chatContainer.addChild(new Text(theme.fg("success", successMessage), 1, 0));
|
|
3026
|
+
if (providerId !== "opencodex") {
|
|
3027
|
+
this.ctx.chatContainer.addChild(
|
|
3028
|
+
new Text(theme.fg("dim", `Credentials saved to ${getAgentDbPath()}`), 1, 0),
|
|
3029
|
+
);
|
|
3030
|
+
await this.#handlePostLoginModelProfileRecommendation(providerId);
|
|
3031
|
+
}
|
|
2600
3032
|
this.ctx.ui.requestRender();
|
|
2601
3033
|
} catch (error: unknown) {
|
|
2602
3034
|
this.ctx.showError(`Login failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -2636,7 +3068,7 @@ export class SelectorController {
|
|
|
2636
3068
|
return;
|
|
2637
3069
|
}
|
|
2638
3070
|
if (mode === "login") {
|
|
2639
|
-
await this.#handleOAuthLogin(providerId);
|
|
3071
|
+
await this.#handleOAuthLogin(providerId, options);
|
|
2640
3072
|
} else {
|
|
2641
3073
|
await this.#handleOAuthLogout(providerId);
|
|
2642
3074
|
}
|