@gajae-code/coding-agent 0.16.0 → 0.16.3
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 +52 -1
- package/dist/types/async/job-manager.d.ts +32 -1
- package/dist/types/browser-backend.d.ts +12 -0
- package/dist/types/cli/update-cli.d.ts +1 -0
- package/dist/types/commands/sdk.d.ts +2 -0
- package/dist/types/config/billing-path.d.ts +25 -0
- package/dist/types/config/config-schema-version.d.ts +13 -1
- package/dist/types/config/model-registry.d.ts +16 -0
- package/dist/types/config/settings-schema.d.ts +17 -7
- package/dist/types/extensibility/extensions/types.d.ts +8 -3
- package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +21 -21
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +16 -2
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -1
- package/dist/types/sdk/broker/ensure.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +14 -0
- package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
- package/dist/types/sdk/broker/state-version.d.ts +17 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/index.d.ts +3 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/rows.d.ts +15 -2
- package/dist/types/sdk/cli/session-cli.d.ts +2 -1
- package/dist/types/sdk/host/events.d.ts +2 -0
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/query/cursor.d.ts +4 -2
- package/dist/types/sdk/host/query/handlers.d.ts +1 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +18 -17
- package/dist/types/sdk/lifecycle-session.d.ts +1 -1
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
- package/dist/types/sdk/receipt-state.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +4 -0
- package/dist/types/sdk/turn-result.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +47 -7
- package/dist/types/session/fold-coordinator.d.ts +29 -2
- package/dist/types/session/session-manager.d.ts +14 -2
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +12 -0
- package/dist/types/tools/browser/launch.d.ts +10 -0
- package/dist/types/tools/browser/registry.d.ts +8 -0
- package/dist/types/tools/index.d.ts +4 -10
- package/dist/types/tools/job.d.ts +3 -1
- package/dist/types/utils/external-editor.d.ts +12 -0
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/pirate.ts +7 -4
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +7 -1
- package/src/async/job-manager.ts +107 -9
- package/src/browser-backend.ts +29 -0
- package/src/cli/plugin-cli.ts +29 -0
- package/src/cli/update-cli.ts +63 -12
- package/src/commands/sdk.ts +22 -7
- package/src/config/billing-path.ts +42 -0
- package/src/config/config-schema-version.ts +18 -1
- package/src/config/model-preset-registry.ts +177 -18
- package/src/config/model-registry.ts +268 -42
- package/src/config/settings-schema.ts +19 -7
- package/src/config/settings.ts +46 -15
- package/src/coordinator-mcp/projection-scan.ts +6 -6
- package/src/coordinator-mcp/server.ts +21 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/extensibility/extensions/runner.ts +27 -0
- package/src/extensibility/extensions/types.ts +8 -3
- package/src/extensibility/gjc-plugins/installer.ts +51 -11
- package/src/extensibility/runtime-skill-discovery.ts +110 -3
- package/src/extensibility/skills.ts +22 -12
- package/src/gjc-runtime/launch-worktree.ts +285 -4
- package/src/gjc-runtime/session-state-lock.ts +156 -32
- package/src/gjc-runtime/session-state-sidecar.ts +10 -17
- package/src/gjc-runtime/tmux-owner-isolation.ts +16 -1
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/main.ts +4 -1
- package/src/modes/acp/acp-agent.ts +34 -19
- package/src/modes/components/model-selector.ts +76 -8
- package/src/modes/components/session-selector.ts +23 -15
- package/src/modes/components/sessions-dashboard.ts +8 -5
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +30 -4
- package/src/modes/controllers/extension-ui-controller.ts +13 -13
- package/src/modes/controllers/input-controller.ts +2 -2
- package/src/modes/controllers/selector-controller.ts +10 -3
- package/src/modes/interactive-mode.ts +36 -12
- package/src/modes/prompt-action-autocomplete.ts +30 -4
- package/src/modes/runtime-init.ts +12 -9
- package/src/modes/theme/theme.ts +5 -0
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +22 -3
- package/src/prompts/agent-fragments/browser-aside.md +22 -0
- package/src/prompts/tools/ask.md +1 -1
- package/src/prompts/tools/bash.md +1 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +6 -1
- package/src/sdk/broker/broker.ts +10 -1
- package/src/sdk/broker/ensure.ts +76 -3
- package/src/sdk/broker/lifecycle-ledger.ts +6 -0
- package/src/sdk/broker/lifecycle.ts +254 -34
- package/src/sdk/broker/spawn-substrate.ts +45 -2
- package/src/sdk/broker/startup-budget.ts +144 -2
- package/src/sdk/broker/state-version.ts +17 -0
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +118 -24
- package/src/sdk/bus/discord-daemon.ts +50 -10
- package/src/sdk/bus/index.ts +33 -3
- package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
- package/src/sdk/bus/lifecycle-commands.ts +4 -0
- package/src/sdk/bus/notification-service.ts +22 -5
- package/src/sdk/bus/prompt-reconciliation.ts +7 -2
- package/src/sdk/bus/reconciliation-store.ts +55 -31
- package/src/sdk/bus/slack-conversation.ts +1 -1
- package/src/sdk/bus/slack-daemon.ts +72 -38
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/rows.ts +75 -7
- package/src/sdk/cli/session-cli.ts +68 -35
- package/src/sdk/host/control/dispatch.ts +2 -2
- package/src/sdk/host/events.ts +26 -2
- package/src/sdk/host/host.ts +6 -1
- package/src/sdk/host/query/cursor.ts +52 -7
- package/src/sdk/host/query/handlers.ts +33 -12
- package/src/sdk/host/query/revision-store.ts +12 -0
- package/src/sdk/host/session-runtime.test.ts +497 -31
- package/src/sdk/host/session-runtime.ts +387 -54
- package/src/sdk/host/surface-policy.ts +3 -3
- package/src/sdk/lifecycle-session.ts +7 -2
- package/src/sdk/prompt-deadline-manager.ts +2 -1
- package/src/sdk/protocol/operation-inventory.generated.json +54 -3
- package/src/sdk/protocol/operation-registry.ts +1 -1
- package/src/sdk/receipt-state.ts +10 -0
- package/src/sdk/router/session-router.ts +210 -94
- package/src/sdk/session.ts +59 -41
- package/src/sdk/turn-result.ts +22 -0
- package/src/session/agent-session.ts +1033 -260
- package/src/session/fold-coordinator.ts +40 -3
- package/src/session/session-manager.ts +517 -77
- package/src/slash-commands/builtin-registry.ts +41 -0
- package/src/task/executor.ts +9 -4
- package/src/tools/ask-contract.test.ts +64 -0
- package/src/tools/ask-contract.ts +36 -1
- package/src/tools/ask.ts +10 -8
- package/src/tools/bash-interactive.ts +11 -2
- package/src/tools/bash.ts +158 -33
- package/src/tools/browser/launch.ts +37 -0
- package/src/tools/browser/registry.ts +99 -9
- package/src/tools/cron.ts +6 -4
- package/src/tools/descriptors.test.ts +15 -0
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/index.ts +4 -6
- package/src/tools/job.ts +4 -1
- package/src/tools/monitor.ts +12 -9
- package/src/tools/resolve.ts +30 -10
- package/src/tools/tool-catalog.generated.ts +2 -2
- package/src/utils/external-editor.ts +86 -1
package/src/main.ts
CHANGED
|
@@ -453,6 +453,7 @@ type StartupModelProfileArgs = {
|
|
|
453
453
|
startupModel?: CreateAgentSessionOptions["model"];
|
|
454
454
|
startupThinkingLevel?: CreateAgentSessionOptions["thinkingLevel"];
|
|
455
455
|
preferCachedModels?: boolean;
|
|
456
|
+
preferCachedDefaultProfile?: boolean;
|
|
456
457
|
};
|
|
457
458
|
|
|
458
459
|
async function applyStartupModelProfilesWithPolicy(
|
|
@@ -484,7 +485,9 @@ async function applyStartupModelProfilesWithPolicy(
|
|
|
484
485
|
// deferred `--model <pattern>` path resolved inside createAgentSession.
|
|
485
486
|
const explicitModel = args.parsedArgs.model ? (args.startupModel ?? args.session.model) : undefined;
|
|
486
487
|
const defaultProfile = args.settings.get("modelProfile.default");
|
|
487
|
-
const preferCachedProfiles =
|
|
488
|
+
const preferCachedProfiles =
|
|
489
|
+
(args.preferCachedModels === true && args.parsedArgs.mpreset !== undefined) ||
|
|
490
|
+
(args.preferCachedDefaultProfile === true && defaultProfile !== undefined);
|
|
488
491
|
const applyConfiguredProfiles = async (): Promise<boolean> => {
|
|
489
492
|
let applied = true;
|
|
490
493
|
if (defaultProfile) {
|
|
@@ -715,11 +715,11 @@ const ACP_CONFIG_OPTIONS = [
|
|
|
715
715
|
],
|
|
716
716
|
},
|
|
717
717
|
{
|
|
718
|
-
id: "
|
|
719
|
-
name: "
|
|
718
|
+
id: "toolInterruptPolicy",
|
|
719
|
+
name: "Tool interrupt policy",
|
|
720
720
|
options: [
|
|
721
|
-
{ value: "
|
|
722
|
-
{ value: "
|
|
721
|
+
{ value: "abort_tools", name: "Abort running tools" },
|
|
722
|
+
{ value: "finish_tools", name: "Finish running tools" },
|
|
723
723
|
],
|
|
724
724
|
},
|
|
725
725
|
] as const;
|
|
@@ -727,7 +727,7 @@ const ACP_CONFIG_OPTIONS = [
|
|
|
727
727
|
const ACP_CONFIG_CONTROL_OPERATIONS: Record<string, string> = {
|
|
728
728
|
steeringMode: "queue.steering_mode.set",
|
|
729
729
|
followUpMode: "queue.follow_up_mode.set",
|
|
730
|
-
|
|
730
|
+
toolInterruptPolicy: "queue.tool_interrupt_policy.set",
|
|
731
731
|
};
|
|
732
732
|
|
|
733
733
|
function configValues(query: unknown): Map<string, string> {
|
|
@@ -1699,9 +1699,13 @@ export class AcpAgent implements Agent {
|
|
|
1699
1699
|
try {
|
|
1700
1700
|
await record.adapter.ensureProviders();
|
|
1701
1701
|
} catch (error) {
|
|
1702
|
-
if (waiter.settled || record.
|
|
1702
|
+
if (waiter.settled || record.activePrompt !== waiter) {
|
|
1703
1703
|
this.#retiredPromptAcknowledgements.delete(params.sessionId);
|
|
1704
|
-
|
|
1704
|
+
return await response;
|
|
1705
|
+
}
|
|
1706
|
+
if (record.cancelRequested && waiter.cancelAcknowledged) {
|
|
1707
|
+
this.#retiredPromptAcknowledgements.delete(params.sessionId);
|
|
1708
|
+
await this.#settleCancelledPrompt(params.sessionId, record, waiter);
|
|
1705
1709
|
return await response;
|
|
1706
1710
|
}
|
|
1707
1711
|
if (record.activePrompt === waiter) {
|
|
@@ -1712,9 +1716,13 @@ export class AcpAgent implements Agent {
|
|
|
1712
1716
|
}
|
|
1713
1717
|
throw error;
|
|
1714
1718
|
}
|
|
1715
|
-
if (waiter.settled || record.
|
|
1719
|
+
if (waiter.settled || record.activePrompt !== waiter) {
|
|
1716
1720
|
this.#retiredPromptAcknowledgements.delete(params.sessionId);
|
|
1717
|
-
|
|
1721
|
+
return await response;
|
|
1722
|
+
}
|
|
1723
|
+
if (record.cancelRequested && waiter.cancelAcknowledged) {
|
|
1724
|
+
this.#retiredPromptAcknowledgements.delete(params.sessionId);
|
|
1725
|
+
await this.#settleCancelledPrompt(params.sessionId, record, waiter);
|
|
1718
1726
|
return await response;
|
|
1719
1727
|
}
|
|
1720
1728
|
|
|
@@ -1739,8 +1747,11 @@ export class AcpAgent implements Agent {
|
|
|
1739
1747
|
);
|
|
1740
1748
|
}
|
|
1741
1749
|
waiter.dispatched = true;
|
|
1742
|
-
if (waiter.settled || record.
|
|
1743
|
-
|
|
1750
|
+
if (waiter.settled || record.activePrompt !== waiter) {
|
|
1751
|
+
return await response;
|
|
1752
|
+
}
|
|
1753
|
+
if (record.cancelRequested && waiter.cancelAcknowledged) {
|
|
1754
|
+
await this.#settleCancelledPrompt(params.sessionId, record, waiter);
|
|
1744
1755
|
return await response;
|
|
1745
1756
|
}
|
|
1746
1757
|
|
|
@@ -1922,7 +1933,7 @@ export class AcpAgent implements Agent {
|
|
|
1922
1933
|
// return the semantically meaningful `cancelled` stop reason, so that Clients
|
|
1923
1934
|
// can reliably confirm the cancellation." Surfacing the transport's `busy`
|
|
1924
1935
|
// rejection instead would show the user a spurious error for their own cancel.
|
|
1925
|
-
if (record.cancelRequested) {
|
|
1936
|
+
if (record.cancelRequested && waiter.cancelAcknowledged) {
|
|
1926
1937
|
record.cancelRequested = false;
|
|
1927
1938
|
// The client's turn is settled by the return; the advisory idle publication
|
|
1928
1939
|
// must not gate it and must still be attempted so the running phase is
|
|
@@ -2006,11 +2017,6 @@ export class AcpAgent implements Agent {
|
|
|
2006
2017
|
}
|
|
2007
2018
|
waiter?.cancelAttemptResolve?.(true);
|
|
2008
2019
|
} catch (error) {
|
|
2009
|
-
if (waiter && !waiter.dispatched) {
|
|
2010
|
-
await this.#settleCancelledPrompt(params.sessionId, record, waiter);
|
|
2011
|
-
waiter.cancelAttemptResolve?.(true);
|
|
2012
|
-
return;
|
|
2013
|
-
}
|
|
2014
2020
|
if (!waiter) record.cancelRequested = false;
|
|
2015
2021
|
// Only the LAST in-flight attempt resolves the shared promise false;
|
|
2016
2022
|
// an earlier attempt may still acknowledge (review thread P2). After
|
|
@@ -2058,7 +2064,8 @@ export class AcpAgent implements Agent {
|
|
|
2058
2064
|
|
|
2059
2065
|
async #settleCancelledPrompt(id: string, record: SessionRecord, waiter: PromptWaiter): Promise<void> {
|
|
2060
2066
|
// The authoritative terminal wins whenever it arrives in time; this only runs
|
|
2061
|
-
// when nothing settled the prompt the
|
|
2067
|
+
// when nothing settled the prompt after the SDK acknowledged the cancellation.
|
|
2068
|
+
if (!waiter.cancelAcknowledged) return;
|
|
2062
2069
|
if (this.#sessions.get(id) !== record || record.activePrompt !== waiter || waiter.settled) return;
|
|
2063
2070
|
if (waiter.terminalReserved) return;
|
|
2064
2071
|
record.activePrompt = undefined;
|
|
@@ -2896,6 +2903,14 @@ export class AcpAgent implements Agent {
|
|
|
2896
2903
|
if (reconnected) {
|
|
2897
2904
|
const waiter = record.activePrompt;
|
|
2898
2905
|
if (waiter && !waiter.settled && !waiter.terminal) {
|
|
2906
|
+
// A successful abort acknowledgement is authoritative for this prompt even
|
|
2907
|
+
// when the SDK transport changes identity during the cancellation grace.
|
|
2908
|
+
// Treating the reconnect as owner loss would replace the acknowledged
|
|
2909
|
+
// cancellation with connection_closed and leave cancellation nondeterministic.
|
|
2910
|
+
if (record.cancelRequested && waiter.cancelAcknowledged) {
|
|
2911
|
+
await this.#settleCancelledPrompt(id, record, waiter);
|
|
2912
|
+
return;
|
|
2913
|
+
}
|
|
2899
2914
|
record.activePrompt = undefined;
|
|
2900
2915
|
record.busy = record.backgroundBusy;
|
|
2901
2916
|
clearPromptWatchdog(waiter);
|
|
@@ -3113,7 +3128,7 @@ export class AcpAgent implements Agent {
|
|
|
3113
3128
|
id: string,
|
|
3114
3129
|
notification: SessionNotification,
|
|
3115
3130
|
adapter: AcpSdkAdapter,
|
|
3116
|
-
|
|
3131
|
+
_publicationGeneration: number,
|
|
3117
3132
|
): void {
|
|
3118
3133
|
const prior = this.#failureDiagnosticTails.get(id) ?? Promise.resolve();
|
|
3119
3134
|
const task = prior.then(async () => {
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
normalizeTierMap,
|
|
23
23
|
validateAutoroutingSetup,
|
|
24
24
|
} from "../../config/autorouting-contract";
|
|
25
|
+
import type { BillingPath } from "../../config/billing-path";
|
|
25
26
|
import {
|
|
26
27
|
getProxyRoutableProviders,
|
|
27
28
|
inspectProxyProviderId,
|
|
@@ -30,7 +31,6 @@ import {
|
|
|
30
31
|
rewriteSelectorForProxy,
|
|
31
32
|
tryResolveProxyProviderId,
|
|
32
33
|
} from "../../config/model-profile-activation";
|
|
33
|
-
|
|
34
34
|
import { isModelProfileProviderAvailable } from "../../config/model-profile-contract";
|
|
35
35
|
import {
|
|
36
36
|
deriveModelProfileMappedProviders,
|
|
@@ -429,6 +429,9 @@ export class ModelSelectorComponent extends Container {
|
|
|
429
429
|
#imageRoleFilter: boolean = false;
|
|
430
430
|
#smartRoutingPanel?: SmartRoutingPanelComponent;
|
|
431
431
|
#smartRoutingPreviewBuilder?: (draft: AutoroutingSetup) => SmartRoutingPreview;
|
|
432
|
+
#isBillingDismissed?: (key: string) => boolean;
|
|
433
|
+
#markBillingDismissed?: (key: string) => void;
|
|
434
|
+
#pendingBillingNoticeKey?: string;
|
|
432
435
|
|
|
433
436
|
// Tab state
|
|
434
437
|
#providers: ProviderTabState[] = STATIC_PROVIDER_TABS;
|
|
@@ -455,6 +458,10 @@ export class ModelSelectorComponent extends Container {
|
|
|
455
458
|
smartRoutingPreview?: (draft: AutoroutingSetup) => SmartRoutingPreview;
|
|
456
459
|
/** Open the smart-routing panel directly instead of the preset landing. */
|
|
457
460
|
smartRoutingOnly?: boolean;
|
|
461
|
+
/** Test seam: synchronous persisted-dismissal check to avoid async render churn. */
|
|
462
|
+
isBillingDismissed?: (key: string) => boolean;
|
|
463
|
+
/** Test seam: record that a billing notice was shown/dismissed. */
|
|
464
|
+
markBillingDismissed?: (key: string) => void;
|
|
458
465
|
},
|
|
459
466
|
) {
|
|
460
467
|
super();
|
|
@@ -468,6 +475,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
468
475
|
this.#temporaryOnly = options?.temporaryOnly ?? false;
|
|
469
476
|
this.#authSessionId = options?.sessionId;
|
|
470
477
|
this.#smartRoutingPreviewBuilder = options?.smartRoutingPreview;
|
|
478
|
+
this.#isBillingDismissed = (options as any)?.isBillingDismissed;
|
|
479
|
+
this.#markBillingDismissed = (options as any)?.markBillingDismissed;
|
|
471
480
|
this.#currentModel = _currentModel;
|
|
472
481
|
this.#currentThinkingLevel = options?.currentThinkingLevel;
|
|
473
482
|
this.#activeModelProfile = options?.activeModelProfile;
|
|
@@ -1497,6 +1506,25 @@ export class ModelSelectorComponent extends Container {
|
|
|
1497
1506
|
const availableModels = this.#getProfileAvailableModels();
|
|
1498
1507
|
const resolutionRegistry = this.#createProfileResolutionRegistry(availableModels);
|
|
1499
1508
|
const providers = new Set<string>();
|
|
1509
|
+
// Preset groups share selectors heavily (role assignments repeat across
|
|
1510
|
+
// profiles), and each `resolveModelRoleValue` call scans the candidate set
|
|
1511
|
+
// while validating lookup contexts and resolving aliases. Resolve identical
|
|
1512
|
+
// selectors once per refresh; the inputs are shared and only the provider is
|
|
1513
|
+
// needed here. This map is deliberately local to the synchronous, pre-await
|
|
1514
|
+
// snapshot below: catalog/config changes and credential-generation changes
|
|
1515
|
+
// start a new refresh with a new map.
|
|
1516
|
+
const resolvedProviderBySelector = new Map<string, string | undefined>();
|
|
1517
|
+
const resolveSelectorProvider = (selector: string): string | undefined => {
|
|
1518
|
+
if (resolvedProviderBySelector.has(selector)) return resolvedProviderBySelector.get(selector);
|
|
1519
|
+
const provider = resolveModelRoleValue(selector, availableModels, {
|
|
1520
|
+
settings: this.#settings,
|
|
1521
|
+
modelRegistry: this.#modelRegistry,
|
|
1522
|
+
aliasIntent: "preset-equivalent",
|
|
1523
|
+
credentialSessionId: this.#authSessionId,
|
|
1524
|
+
}).model?.provider;
|
|
1525
|
+
resolvedProviderBySelector.set(selector, provider);
|
|
1526
|
+
return provider;
|
|
1527
|
+
};
|
|
1500
1528
|
for (const profiles of this.#getPresetGroups().values()) {
|
|
1501
1529
|
for (const profile of profiles) {
|
|
1502
1530
|
for (const provider of profileRequiredProviders(profile)) providers.add(provider);
|
|
@@ -1516,13 +1544,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
1516
1544
|
];
|
|
1517
1545
|
for (const value of values) {
|
|
1518
1546
|
for (const selector of normalizeModelSelectorValue(value)) {
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1521
|
-
modelRegistry: this.#modelRegistry,
|
|
1522
|
-
aliasIntent: "preset-equivalent",
|
|
1523
|
-
credentialSessionId: this.#authSessionId,
|
|
1524
|
-
}).model;
|
|
1525
|
-
if (resolved) providers.add(resolved.provider);
|
|
1547
|
+
const resolvedProvider = resolveSelectorProvider(selector);
|
|
1548
|
+
if (resolvedProvider !== undefined) providers.add(resolvedProvider);
|
|
1526
1549
|
}
|
|
1527
1550
|
}
|
|
1528
1551
|
}
|
|
@@ -1705,12 +1728,24 @@ export class ModelSelectorComponent extends Container {
|
|
|
1705
1728
|
return lines;
|
|
1706
1729
|
}
|
|
1707
1730
|
|
|
1731
|
+
#billingPathForModel(model: Model): BillingPath | undefined {
|
|
1732
|
+
try {
|
|
1733
|
+
const fn = (
|
|
1734
|
+
this.#modelRegistry as unknown as { getBillingPath?: (m: Model, s?: string) => BillingPath | undefined }
|
|
1735
|
+
).getBillingPath;
|
|
1736
|
+
if (typeof fn === "function") return fn.call(this.#modelRegistry, model, this.#authSessionId);
|
|
1737
|
+
} catch {}
|
|
1738
|
+
return undefined;
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1708
1741
|
#formatAssignedModelLabel(model: Model, thinkingLevel: ThinkingLevel | undefined): string {
|
|
1709
1742
|
const modelLabel = sanitizeText(`${model.provider}/${model.id}`).replace(/\s+/g, " ").trim();
|
|
1710
1743
|
let label = modelLabel;
|
|
1711
1744
|
if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) {
|
|
1712
1745
|
label += ` (${getThinkingLevelMetadata(thinkingLevel).label})`;
|
|
1713
1746
|
}
|
|
1747
|
+
const billing = this.#billingPathForModel(model)?.label;
|
|
1748
|
+
if (billing) label += ` [${billing}]`;
|
|
1714
1749
|
return truncateToWidth(label, ROLE_BINDING_MAX_WIDTH);
|
|
1715
1750
|
}
|
|
1716
1751
|
|
|
@@ -1914,6 +1949,27 @@ export class ModelSelectorComponent extends Container {
|
|
|
1914
1949
|
this.#listContainer.addChild(new Spacer(1));
|
|
1915
1950
|
this.#listContainer.addChild(new Text(theme.fg("warning", ` ${this.#presetLoginHint}`), 0, 0));
|
|
1916
1951
|
}
|
|
1952
|
+
// One-time metered billing notice for the currently selected model (first bind per credential source)
|
|
1953
|
+
try {
|
|
1954
|
+
const sel = this.#getSelectedItem?.() as any;
|
|
1955
|
+
const m = sel?.model as any;
|
|
1956
|
+
if (m && this.#viewMode === "models") {
|
|
1957
|
+
const bp = this.#billingPathForModel(m);
|
|
1958
|
+
if (bp && !(this.#isBillingDismissed?.(bp.dismissKey) ?? false)) {
|
|
1959
|
+
this.#listContainer.addChild(new Spacer(1));
|
|
1960
|
+
this.#listContainer.addChild(
|
|
1961
|
+
new Text(
|
|
1962
|
+
theme.fg("warning", ` Billing: ${bp.label} — charges may apply. Press d to dismiss.`),
|
|
1963
|
+
0,
|
|
1964
|
+
0,
|
|
1965
|
+
),
|
|
1966
|
+
);
|
|
1967
|
+
this.#pendingBillingNoticeKey = bp.dismissKey;
|
|
1968
|
+
} else {
|
|
1969
|
+
this.#pendingBillingNoticeKey = undefined;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
} catch {}
|
|
1917
1973
|
const previewProfile = this.#getProfileByName(this.#previewProfileName);
|
|
1918
1974
|
if (previewProfile) this.#renderPresetPreview(previewProfile);
|
|
1919
1975
|
}
|
|
@@ -2075,6 +2131,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
2075
2131
|
) {
|
|
2076
2132
|
roleBadgeTokens.push(theme.icon.fast);
|
|
2077
2133
|
}
|
|
2134
|
+
const billingLabel = this.#billingPathForModel(item.model)?.label;
|
|
2135
|
+
if (billingLabel) roleBadgeTokens.push(theme.fg("warning", `[${billingLabel}]`));
|
|
2078
2136
|
const badgeText = roleBadgeTokens.length > 0 ? ` ${roleBadgeTokens.join(" ")}` : "";
|
|
2079
2137
|
|
|
2080
2138
|
let line = "";
|
|
@@ -2307,6 +2365,16 @@ export class ModelSelectorComponent extends Container {
|
|
|
2307
2365
|
return;
|
|
2308
2366
|
}
|
|
2309
2367
|
|
|
2368
|
+
if (keyData === "d" || keyData === "D") {
|
|
2369
|
+
if (this.#pendingBillingNoticeKey) {
|
|
2370
|
+
try {
|
|
2371
|
+
this.#markBillingDismissed?.(this.#pendingBillingNoticeKey);
|
|
2372
|
+
} catch {}
|
|
2373
|
+
this.#pendingBillingNoticeKey = undefined;
|
|
2374
|
+
this.#updateList();
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2310
2378
|
// Pass everything else to search input
|
|
2311
2379
|
this.#searchInput.handleInput(keyData);
|
|
2312
2380
|
this.#filterModels(this.#searchInput.getValue());
|
|
@@ -14,7 +14,12 @@ import {
|
|
|
14
14
|
import { formatBytes } from "@gajae-code/utils";
|
|
15
15
|
import { theme } from "../../modes/theme/theme";
|
|
16
16
|
import { matchesAppInterrupt } from "../../modes/utils/keybinding-matchers";
|
|
17
|
-
import
|
|
17
|
+
import {
|
|
18
|
+
prioritizeStarredSessions,
|
|
19
|
+
type ResumeSessionIdentity,
|
|
20
|
+
type ResumeTailInspection,
|
|
21
|
+
type SessionInfo,
|
|
22
|
+
} from "../../session/session-manager";
|
|
18
23
|
import { DynamicBorder } from "./dynamic-border";
|
|
19
24
|
import { HookSelectorComponent } from "./hook-selector";
|
|
20
25
|
|
|
@@ -71,17 +76,19 @@ class SessionList implements Component {
|
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
#filterSessions(query: string): void {
|
|
74
|
-
this.#filteredSessions =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
this.#filteredSessions = prioritizeStarredSessions(
|
|
80
|
+
fuzzyFilter(this.allSessions, query, session =>
|
|
81
|
+
[
|
|
82
|
+
session.id,
|
|
83
|
+
session.title ?? "",
|
|
84
|
+
session.cwd ?? "",
|
|
85
|
+
session.firstMessage ?? "",
|
|
86
|
+
session.allMessagesText,
|
|
87
|
+
session.path,
|
|
88
|
+
]
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
.join(" "),
|
|
91
|
+
),
|
|
85
92
|
);
|
|
86
93
|
this.#clampSelectedIndex();
|
|
87
94
|
}
|
|
@@ -131,8 +138,9 @@ class SessionList implements Component {
|
|
|
131
138
|
const cursorWidth = visibleWidth(cursor);
|
|
132
139
|
const prefix = selected ? theme.fg("accent", cursor) : padding(cursorWidth);
|
|
133
140
|
const message = session.firstMessage.replace(/\n/g, " ").trim();
|
|
134
|
-
const
|
|
135
|
-
|
|
141
|
+
const star = session.starred ? theme.fg("warning", `${theme.icon.star} `) : "";
|
|
142
|
+
const title = truncateToWidth(session.title ?? message, width - cursorWidth - visibleWidth(star));
|
|
143
|
+
lines.push(prefix + star + (selected ? theme.bold(title) : title));
|
|
136
144
|
if (session.title) lines.push(` ${theme.fg("dim", truncateToWidth(message, width - cursorWidth))}`);
|
|
137
145
|
lines.push(
|
|
138
146
|
theme.fg(
|
|
@@ -211,7 +219,7 @@ export class SessionSelectorComponent extends Container {
|
|
|
211
219
|
this.addChild(new DynamicBorder());
|
|
212
220
|
this.addChild(new Spacer(1));
|
|
213
221
|
this.addChild(this.#messageContainer);
|
|
214
|
-
this.#sessionList = new SessionList(sessions);
|
|
222
|
+
this.#sessionList = new SessionList(prioritizeStarredSessions(sessions));
|
|
215
223
|
this.#sessionList.onSelect = session => {
|
|
216
224
|
if (this.inspector) void this.#inspect(session);
|
|
217
225
|
else this.#dispatchSelect(session.path);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import { type Component, Container, matchesKey, truncateToWidth } from "@gajae-code/tui";
|
|
3
|
-
import type
|
|
2
|
+
import { type Component, Container, matchesKey, truncateToWidth, visibleWidth } from "@gajae-code/tui";
|
|
3
|
+
import { prioritizeStarredSessions, type SessionInfo } from "../../session/session-manager";
|
|
4
4
|
import { theme } from "../theme/theme";
|
|
5
5
|
|
|
6
6
|
const PRESENCE_MAX_BYTES = 4096;
|
|
@@ -41,6 +41,7 @@ export interface DashboardSession {
|
|
|
41
41
|
id: string;
|
|
42
42
|
cwd: string;
|
|
43
43
|
title: string;
|
|
44
|
+
starred: boolean;
|
|
44
45
|
modified: Date;
|
|
45
46
|
messageCount: number;
|
|
46
47
|
messageCountIsEstimate?: boolean;
|
|
@@ -78,10 +79,11 @@ export function dashboardSessions(
|
|
|
78
79
|
sessions: readonly SessionInfo[],
|
|
79
80
|
options: { readFile?: (filePath: string) => string; now?: number } = {},
|
|
80
81
|
): DashboardSession[] {
|
|
81
|
-
return sessions.map(session => ({
|
|
82
|
+
return prioritizeStarredSessions(sessions).map(session => ({
|
|
82
83
|
id: session.id,
|
|
83
84
|
cwd: session.cwd,
|
|
84
85
|
title: session.title ?? session.firstMessage,
|
|
86
|
+
starred: session.starred,
|
|
85
87
|
modified: session.modified,
|
|
86
88
|
messageCountIsEstimate: session.messageCountIsEstimate,
|
|
87
89
|
messageCount: session.messageCount,
|
|
@@ -169,10 +171,11 @@ class SessionDashboardRow implements Component {
|
|
|
169
171
|
const fullMetadata = `· ${count} · ${modified}`;
|
|
170
172
|
const compactMetadata = `· ${this.session.messageCountIsEstimate ? `~${this.session.messageCount}` : this.session.messageCount} msgs`;
|
|
171
173
|
const metadata = width >= 48 ? fullMetadata : width >= 24 ? compactMetadata : "";
|
|
172
|
-
const
|
|
174
|
+
const star = this.session.starred ? theme.fg("warning", `${theme.icon.star} `) : "";
|
|
175
|
+
const titleWidth = Math.max(0, width - 2 - visibleWidth(star) - metadata.length - (metadata ? 1 : 0));
|
|
173
176
|
return [
|
|
174
177
|
fit(
|
|
175
|
-
` ${status} ${truncateToWidth(title, titleWidth)}${metadata ? ` ${theme.fg("dim", metadata)}` : ""}`,
|
|
178
|
+
` ${status} ${star}${truncateToWidth(title, titleWidth)}${metadata ? ` ${theme.fg("dim", metadata)}` : ""}`,
|
|
176
179
|
width,
|
|
177
180
|
),
|
|
178
181
|
fit(` ${theme.fg("dim", cwd)}`, width),
|
|
@@ -1161,7 +1161,7 @@ export class CommandController {
|
|
|
1161
1161
|
}
|
|
1162
1162
|
|
|
1163
1163
|
try {
|
|
1164
|
-
await this.ctx.sessionManager.
|
|
1164
|
+
await this.ctx.sessionManager.runExclusiveCwdMoveTransition(async () => {
|
|
1165
1165
|
await this.ctx.sessionManager.flush();
|
|
1166
1166
|
await this.ctx.sessionManager.moveTo(resolvedPath);
|
|
1167
1167
|
setProjectDir(resolvedPath);
|
|
@@ -57,6 +57,17 @@ export const __eventControllerPerfCounters = {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
const COMPLETION_NOTIFY_COMMAND_TIMEOUT_MS = 10_000;
|
|
60
|
+
const TEXTLESS_LENGTH_WARNING =
|
|
61
|
+
'Response ended with stopReason "length" before producing visible text or a tool call. Check the model\'s output and context token limits (maxTokens in models.yml) or lower the reasoning level (/reasoning), then retry.';
|
|
62
|
+
|
|
63
|
+
function isTextlessLengthStop(message: AssistantMessage | undefined): boolean {
|
|
64
|
+
return (
|
|
65
|
+
message?.stopReason === "length" &&
|
|
66
|
+
!message.content.some(
|
|
67
|
+
block => (block.type === "text" && block.text.trim().length > 0) || block.type === "toolCall",
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
60
71
|
|
|
61
72
|
interface CompletionNotifyPayload {
|
|
62
73
|
type: "agent-turn-complete";
|
|
@@ -904,7 +915,7 @@ export class EventController {
|
|
|
904
915
|
}
|
|
905
916
|
}
|
|
906
917
|
|
|
907
|
-
async #handleAgentEnd(
|
|
918
|
+
async #handleAgentEnd(event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
|
|
908
919
|
this.ctx.setWorkingMessage(undefined);
|
|
909
920
|
stopInteractiveActivityIndicator(this.ctx, { foregroundSettled: true });
|
|
910
921
|
if (this.ctx.streamingComponent) {
|
|
@@ -929,6 +940,14 @@ export class EventController {
|
|
|
929
940
|
this.ctx.ui.requestRender();
|
|
930
941
|
this.#scheduleIdleCompaction();
|
|
931
942
|
emitHostStatus("finished");
|
|
943
|
+
const lastAssistant = this.ctx.session.getLastAssistantMessage();
|
|
944
|
+
if (
|
|
945
|
+
event.stopReason !== "cancelled" &&
|
|
946
|
+
event.stopReason !== "maintenance" &&
|
|
947
|
+
isTextlessLengthStop(lastAssistant)
|
|
948
|
+
) {
|
|
949
|
+
this.ctx.showWarning(TEXTLESS_LENGTH_WARNING);
|
|
950
|
+
}
|
|
932
951
|
this.sendCompletionNotification();
|
|
933
952
|
this.ctx.promptSuggestion?.onAgentEnd();
|
|
934
953
|
}
|
|
@@ -1204,13 +1223,20 @@ export class EventController {
|
|
|
1204
1223
|
// errored — those are not "Task complete" events. Mirrors the gate
|
|
1205
1224
|
// already used by #currentContextTokens, #handleMessageEnd, and the
|
|
1206
1225
|
// retry / TTSR / compaction skip paths across agent-session.ts.
|
|
1207
|
-
const last = this.ctx.session.getLastAssistantMessage
|
|
1226
|
+
const last = this.ctx.session.getLastAssistantMessage();
|
|
1208
1227
|
if (last?.stopReason === "aborted" || last?.stopReason === "error") return;
|
|
1209
1228
|
|
|
1210
1229
|
const sessionName = this.ctx.sessionManager.getSessionName();
|
|
1211
|
-
const
|
|
1230
|
+
const textlessLengthStop = isTextlessLengthStop(last);
|
|
1231
|
+
const title = textlessLengthStop
|
|
1232
|
+
? sessionName
|
|
1233
|
+
? `${sessionName}: Output limit reached`
|
|
1234
|
+
: "Output limit reached"
|
|
1235
|
+
: sessionName
|
|
1236
|
+
? `${sessionName}: Complete`
|
|
1237
|
+
: "Complete";
|
|
1212
1238
|
const summary = summaryFromMessage(last, 1000);
|
|
1213
|
-
const body = summary ?? "Complete";
|
|
1239
|
+
const body = textlessLengthStop ? TEXTLESS_LENGTH_WARNING : (summary ?? "Complete");
|
|
1214
1240
|
const sessionManager = this.ctx.sessionManager as { getCwd?: () => string; getSessionId?: () => string };
|
|
1215
1241
|
const payload: CompletionNotifyPayload = {
|
|
1216
1242
|
type: "agent-turn-complete",
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
SYNTHETIC_PROVIDER_ID,
|
|
40
40
|
syntheticNamespaceCollision,
|
|
41
41
|
} from "../../sdk/model-profile-model";
|
|
42
|
+
import { bashBackgroundControlError } from "../../session/fold-coordinator";
|
|
42
43
|
import { createReadonlySessionManager } from "../../session/session-manager";
|
|
43
44
|
import { parseThinkingLevel } from "../../thinking";
|
|
44
45
|
import type { TodoPhase } from "../../tools/todo-write";
|
|
@@ -311,12 +312,11 @@ export class ExtensionUiController {
|
|
|
311
312
|
throw Object.assign(new Error("No retry backoff is pending."), { code: "retry_not_pending" });
|
|
312
313
|
session.retryNow();
|
|
313
314
|
return { retried: true, immediate: true };
|
|
314
|
-
case "bash.background":
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
return { backgrounded: true };
|
|
315
|
+
case "bash.background": {
|
|
316
|
+
const outcome = await session.requestForegroundBashBackgroundOutcome("sdk_control");
|
|
317
|
+
if (outcome.status !== "folded") throw bashBackgroundControlError(outcome);
|
|
318
|
+
return { backgrounded: true, jobId: outcome.jobId };
|
|
319
|
+
}
|
|
320
320
|
case "compaction.auto.set":
|
|
321
321
|
session.setAutoCompactionEnabled(input.on === true);
|
|
322
322
|
return { changed: true };
|
|
@@ -591,8 +591,8 @@ export class ExtensionUiController {
|
|
|
591
591
|
this.ctx.session.setFollowUpMode(mode);
|
|
592
592
|
return true;
|
|
593
593
|
}
|
|
594
|
-
if (kind === "
|
|
595
|
-
this.ctx.session.
|
|
594
|
+
if (kind === "tool_interrupt" && (mode === "abort_tools" || mode === "finish_tools")) {
|
|
595
|
+
this.ctx.session.setToolInterruptPolicy(mode);
|
|
596
596
|
return true;
|
|
597
597
|
}
|
|
598
598
|
return false;
|
|
@@ -821,8 +821,8 @@ export class ExtensionUiController {
|
|
|
821
821
|
this.ctx.session.setFollowUpMode(mode);
|
|
822
822
|
return true;
|
|
823
823
|
}
|
|
824
|
-
if (kind === "
|
|
825
|
-
this.ctx.session.
|
|
824
|
+
if (kind === "tool_interrupt" && (mode === "abort_tools" || mode === "finish_tools")) {
|
|
825
|
+
this.ctx.session.setToolInterruptPolicy(mode);
|
|
826
826
|
return true;
|
|
827
827
|
}
|
|
828
828
|
return false;
|
|
@@ -962,8 +962,8 @@ export class ExtensionUiController {
|
|
|
962
962
|
this.ctx.session.setFollowUpMode(mode);
|
|
963
963
|
return true;
|
|
964
964
|
}
|
|
965
|
-
if (kind === "
|
|
966
|
-
this.ctx.session.
|
|
965
|
+
if (kind === "tool_interrupt" && (mode === "abort_tools" || mode === "finish_tools")) {
|
|
966
|
+
this.ctx.session.setToolInterruptPolicy(mode);
|
|
967
967
|
return true;
|
|
968
968
|
}
|
|
969
969
|
return false;
|
|
@@ -973,7 +973,7 @@ export class ExtensionUiController {
|
|
|
973
973
|
getConfigItems: () => ({
|
|
974
974
|
steeringMode: this.ctx.session.steeringMode,
|
|
975
975
|
followUpMode: this.ctx.session.followUpMode,
|
|
976
|
-
|
|
976
|
+
toolInterruptPolicy: this.ctx.session.toolInterruptPolicy,
|
|
977
977
|
}),
|
|
978
978
|
getBranchCandidates: () => this.ctx.sessionManager.getTree(),
|
|
979
979
|
getExtensions: () => this.ctx.session.extensionRunner?.getExtensionPaths() ?? [],
|
|
@@ -1170,7 +1170,7 @@ export class InputController {
|
|
|
1170
1170
|
const promptOptions =
|
|
1171
1171
|
streamingBehavior === "followUp"
|
|
1172
1172
|
? { streamingBehavior, images, followUpQueuePolicy: "sequential" as const }
|
|
1173
|
-
: { streamingBehavior, images };
|
|
1173
|
+
: { streamingBehavior, images, steerQueuePolicy: "sequential" as const };
|
|
1174
1174
|
await this.ctx.withLocalSubmission(text, () => this.ctx.session.prompt(text, promptOptions), {
|
|
1175
1175
|
imageCount: images?.length ?? 0,
|
|
1176
1176
|
});
|
|
@@ -1683,7 +1683,7 @@ export class InputController {
|
|
|
1683
1683
|
},
|
|
1684
1684
|
streamingBehavior === "followUp"
|
|
1685
1685
|
? { streamingBehavior, followUpQueuePolicy: "sequential" }
|
|
1686
|
-
: { streamingBehavior },
|
|
1686
|
+
: { streamingBehavior, steerQueuePolicy: "sequential" },
|
|
1687
1687
|
);
|
|
1688
1688
|
}
|
|
1689
1689
|
if (this.ctx.session.isStreaming) {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
isSqliteCorruptionError,
|
|
8
8
|
isSqliteError,
|
|
9
9
|
type Model,
|
|
10
|
+
OAuthCredentialSelectorError,
|
|
10
11
|
resolveOAuthStorageProvider,
|
|
11
12
|
} from "@gajae-code/ai/core";
|
|
12
13
|
import { getOAuthProviders } from "@gajae-code/ai/utils/oauth";
|
|
@@ -2243,8 +2244,8 @@ export class SelectorController {
|
|
|
2243
2244
|
case "followUpMode":
|
|
2244
2245
|
this.ctx.session.setFollowUpMode(value as "all" | "one-at-a-time");
|
|
2245
2246
|
break;
|
|
2246
|
-
case "
|
|
2247
|
-
this.ctx.session.
|
|
2247
|
+
case "toolInterruptPolicy":
|
|
2248
|
+
this.ctx.session.setToolInterruptPolicy(value as "abort_tools" | "finish_tools");
|
|
2248
2249
|
break;
|
|
2249
2250
|
case "thinkingLevel":
|
|
2250
2251
|
case "defaultThinkingLevel":
|
|
@@ -3706,7 +3707,13 @@ export class SelectorController {
|
|
|
3706
3707
|
{
|
|
3707
3708
|
accountProviderId: selectedProviderId,
|
|
3708
3709
|
onAccountSelect: async (selectorValue: AuthCredentialSelector) => {
|
|
3709
|
-
|
|
3710
|
+
try {
|
|
3711
|
+
await this.ctx.session.setCredentialPin(selectedProviderId, selectorValue);
|
|
3712
|
+
} catch (error: unknown) {
|
|
3713
|
+
if (!(error instanceof OAuthCredentialSelectorError)) throw error;
|
|
3714
|
+
this.ctx.showError(error.message);
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3710
3717
|
selector.stopValidation();
|
|
3711
3718
|
done();
|
|
3712
3719
|
this.ctx.showStatus(`Pinned OAuth account for ${selectedProviderId} to this session.`);
|