@gajae-code/coding-agent 0.12.0 → 0.12.2
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 +111 -0
- package/dist/types/async/job-manager.d.ts +17 -3
- package/dist/types/config/settings-schema.d.ts +56 -2
- package/dist/types/exa/mcp-client.d.ts +10 -1
- package/dist/types/exec/bash-executor.d.ts +45 -12
- package/dist/types/extensibility/extensions/types.d.ts +16 -2
- package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
- package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/ralplan-review-conflicts.d.ts +114 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +23 -3
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +25 -0
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +37 -80
- package/dist/types/modes/acp/acp-agent.d.ts +2 -19
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +16 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +18 -0
- package/dist/types/modes/components/settings-selector.d.ts +9 -0
- package/dist/types/modes/components/status-line/gh.d.ts +5 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +1 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +9 -0
- package/dist/types/modes/theme/theme.d.ts +3 -1
- package/dist/types/modes/types.d.ts +32 -0
- package/dist/types/reminders/star-reminder.d.ts +3 -13
- package/dist/types/runtime-mcp/manager.d.ts +1 -0
- package/dist/types/sdk/acp/adapter.d.ts +3 -1
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/config.d.ts +6 -2
- package/dist/types/sdk/bus/index.d.ts +29 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +8 -1
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +7 -3
- package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
- package/dist/types/sdk/bus/rich-render.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
- package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
- package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
- package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/prompt-status.d.ts +23 -3
- package/dist/types/sdk/session.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +40 -2
- package/dist/types/session/artifacts.d.ts +8 -0
- package/dist/types/session/blob-store.d.ts +57 -4
- package/dist/types/session/internal/managed-session-storage.d.ts +30 -4
- package/dist/types/session/session-manager.d.ts +23 -6
- package/dist/types/session/session-storage.d.ts +4 -0
- package/dist/types/session/streaming-output.d.ts +36 -7
- package/dist/types/setup/credential-import.d.ts +12 -0
- package/dist/types/skill-state/active-state.d.ts +12 -0
- package/dist/types/skill-state/workflow-hud.d.ts +6 -0
- package/dist/types/task/executor.d.ts +3 -2
- package/dist/types/task/index.d.ts +7 -1
- package/dist/types/task/output-manager.d.ts +1 -0
- package/dist/types/task/provider-retry-status.d.ts +8 -0
- package/dist/types/task/receipt.d.ts +9 -1
- package/dist/types/task/types.d.ts +23 -0
- package/dist/types/tools/ask-answer-registry.d.ts +2 -2
- package/dist/types/tools/bash-interactive.d.ts +4 -1
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/image-gen.d.ts +4 -0
- package/dist/types/tools/index.d.ts +6 -0
- package/dist/types/tools/output-meta.d.ts +23 -2
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/read.d.ts +0 -6
- package/dist/types/tools/review.d.ts +5 -7
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/utils/gh.d.ts +13 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/web/search/providers/kimi.d.ts +2 -0
- package/dist/types/web/search/providers/searxng.d.ts +7 -0
- package/dist/types/web/search/providers/xai.d.ts +2 -0
- package/package.json +10 -8
- package/scripts/acp-conformance-agent.ts +218 -0
- package/scripts/capture-gjc-bundle-settings.ts +306 -0
- package/scripts/dogfood-ralplan-review-conflicts.ts +272 -0
- package/scripts/generate-sdk-operation-inventory.ts +5 -0
- package/scripts/resident-memory-bench.ts +1341 -0
- package/scripts/run-acp-conformance.ts +290 -0
- package/src/async/job-manager.ts +99 -39
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/plugin-cli.ts +176 -18
- package/src/commands/team.ts +19 -1
- package/src/config/file-lock.ts +11 -2
- package/src/config/settings-schema.ts +53 -2
- package/src/coordinator-mcp/question-gate-codec.ts +1 -1
- package/src/coordinator-mcp/server.ts +5 -12
- package/src/debug/index.ts +42 -17
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
- package/src/defaults/gjc/skills/ralplan/SKILL.md +25 -20
- package/src/defaults/gjc/skills/team/SKILL.md +2 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +39 -26
- package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
- package/src/defaults/gjc-defaults.ts +4 -4
- package/src/exa/mcp-client.ts +12 -3
- package/src/exec/bash-executor.ts +144 -27
- package/src/extensibility/extensions/runner.ts +15 -0
- package/src/extensibility/extensions/types.ts +18 -1
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
- package/src/extensibility/gjc-plugins/index.ts +19 -2
- package/src/extensibility/gjc-plugins/installer.ts +189 -50
- package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
- package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
- package/src/extensibility/gjc-plugins/observability.ts +3 -3
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
- package/src/extensibility/gjc-plugins/schema.ts +84 -12
- package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
- package/src/extensibility/gjc-plugins/types.ts +141 -0
- package/src/extensibility/gjc-plugins/validation.ts +9 -0
- package/src/gjc-runtime/launch-tmux.ts +25 -6
- package/src/gjc-runtime/launch-worktree.ts +103 -1
- package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
- package/src/gjc-runtime/managed-owner-admission.ts +1 -1
- package/src/gjc-runtime/ralplan-review-conflicts.ts +472 -0
- package/src/gjc-runtime/ralplan-runtime.ts +491 -27
- package/src/gjc-runtime/team-launch.ts +8 -0
- package/src/gjc-runtime/team-runtime.ts +140 -32
- package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +10 -0
- package/src/gjc-runtime/tmux-sessions.ts +58 -13
- package/src/gjc-runtime/ultragoal-evidence.ts +6 -74
- package/src/gjc-runtime/ultragoal-runtime.ts +635 -612
- package/src/gjc-runtime/workflow-manifest.generated.json +37 -99
- package/src/gjc-runtime/workflow-manifest.ts +21 -70
- package/src/internal-urls/docs-index.generated.ts +16 -16
- package/src/modes/DESIGN.md +104 -0
- package/src/modes/acp/acp-agent.ts +298 -186
- package/src/modes/components/gjc-bundle-settings.ts +508 -0
- package/src/modes/components/irc-sidebar.ts +30 -1
- package/src/modes/components/notifications-settings-editor.ts +12 -1
- package/src/modes/components/oauth-selector.ts +68 -30
- package/src/modes/components/session-observer-overlay.ts +305 -86
- package/src/modes/components/settings-selector.ts +56 -5
- package/src/modes/components/status-line/gh.ts +16 -0
- package/src/modes/components/tool-status-header.ts +3 -15
- package/src/modes/components/transcript-viewer-overlay.ts +99 -24
- package/src/modes/controllers/command-controller.ts +42 -34
- package/src/modes/controllers/event-controller.ts +27 -16
- package/src/modes/controllers/extension-ui-controller.ts +193 -34
- package/src/modes/controllers/selector-controller.ts +160 -22
- package/src/modes/interactive-mode.ts +189 -62
- package/src/modes/jobs-observer.ts +1 -1
- package/src/modes/runtime-init.ts +3 -0
- package/src/modes/theme/theme.ts +11 -9
- package/src/modes/types.ts +88 -0
- package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
- package/src/prompts/system/eager-todo.md +8 -4
- package/src/prompts/system/system-prompt.md +11 -4
- package/src/prompts/tools/bash.md +5 -5
- package/src/prompts/tools/search-tool-bm25.md +5 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/reminders/star-reminder.ts +4 -42
- package/src/runtime-mcp/manager.ts +19 -4
- package/src/sdk/acp/adapter.ts +22 -6
- package/src/sdk/broker/lifecycle.ts +18 -13
- package/src/sdk/broker/transport.ts +35 -3
- package/src/sdk/bus/attachment-registry.ts +5 -1
- package/src/sdk/bus/chat-daemon-cli.ts +4 -39
- package/src/sdk/bus/chat-daemon-control.ts +7 -3
- package/src/sdk/bus/config.ts +18 -2
- package/src/sdk/bus/index.ts +661 -126
- package/src/sdk/bus/kind-aware-reconciliation.ts +148 -73
- package/src/sdk/bus/lifecycle-commands.ts +4 -1
- package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
- package/src/sdk/bus/reconciliation-store.ts +95 -16
- package/src/sdk/bus/rich-draft.ts +1 -1
- package/src/sdk/bus/rich-render.ts +2 -2
- package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
- package/src/sdk/bus/telegram-cli.ts +33 -8
- package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +12 -4
- package/src/sdk/bus/telegram-daemon.ts +916 -23
- package/src/sdk/bus/telegram-reference.ts +40 -3
- package/src/sdk/bus/threaded-render.ts +48 -3
- package/src/sdk/bus/topic-registry.ts +20 -0
- package/src/sdk/host/query/revision-store.ts +36 -2
- package/src/sdk/host/reverse-leases.ts +44 -7
- package/src/sdk/prompt-status.ts +16 -3
- package/src/sdk/protocol/operation-inventory.generated.json +33 -0
- package/src/sdk/session.ts +89 -9
- package/src/session/agent-session.ts +844 -173
- package/src/session/artifacts.ts +22 -0
- package/src/session/auth-broker-config.ts +10 -3
- package/src/session/blob-store.ts +915 -24
- package/src/session/internal/managed-session-scope.ts +200 -111
- package/src/session/internal/managed-session-storage.ts +492 -62
- package/src/session/session-manager.ts +1614 -435
- package/src/session/session-storage.ts +38 -45
- package/src/session/streaming-output.ts +238 -68
- package/src/setup/credential-import.ts +75 -13
- package/src/skill-state/active-state.ts +32 -0
- package/src/skill-state/workflow-hud.ts +17 -0
- package/src/skill-state/workflow-mutation-guard.ts +496 -2
- package/src/task/executor.ts +122 -39
- package/src/task/index.ts +280 -45
- package/src/task/output-manager.ts +24 -14
- package/src/task/provider-retry-status.ts +16 -0
- package/src/task/receipt.ts +34 -8
- package/src/task/render.ts +6 -2
- package/src/task/types.ts +108 -0
- package/src/tools/ask-answer-registry.ts +12 -6
- package/src/tools/ask.ts +86 -55
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash-interactive.ts +8 -3
- package/src/tools/bash.ts +503 -60
- package/src/tools/find.ts +1 -0
- package/src/tools/image-gen.ts +29 -5
- package/src/tools/index.ts +6 -0
- package/src/tools/output-meta.ts +108 -14
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +18 -0
- package/src/tools/review.ts +5 -7
- package/src/tools/search.ts +1 -0
- package/src/tools/subagent-render.ts +6 -2
- package/src/tools/subagent.ts +6 -0
- package/src/tools/telegram-send.ts +6 -3
- package/src/utils/gh.ts +39 -0
- package/src/utils/image-resize.ts +3 -0
- package/src/web/search/providers/anthropic.ts +4 -4
- package/src/web/search/providers/kimi.ts +15 -5
- package/src/web/search/providers/searxng.ts +47 -4
- package/src/web/search/providers/xai.ts +7 -2
- package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
- package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import { Container, type SelectItem, SelectList, Spacer, Text } from "@gajae-code/tui";
|
|
2
|
+
import {
|
|
3
|
+
applyGjcBundleUpdate,
|
|
4
|
+
type GjcLifecycleContext,
|
|
5
|
+
getGjcBundle,
|
|
6
|
+
listGjcBundles,
|
|
7
|
+
previewGjcBundleUpdate,
|
|
8
|
+
setGjcBundleEnabled,
|
|
9
|
+
setGjcBundleSurfaceEnabled,
|
|
10
|
+
} from "../../extensibility/gjc-plugins/lifecycle";
|
|
11
|
+
import { identityEquals, identityKey } from "../../extensibility/gjc-plugins/lifecycle-reconciliation";
|
|
12
|
+
import { findingsForBundle, type GjcRuntimeSnapshotProvider } from "../../extensibility/gjc-plugins/runtime-quarantine";
|
|
13
|
+
import type {
|
|
14
|
+
GjcBundleIdentity,
|
|
15
|
+
GjcBundleSummary,
|
|
16
|
+
GjcLifecycleResult,
|
|
17
|
+
GjcReviewedUpdateToken,
|
|
18
|
+
GjcToggleResult,
|
|
19
|
+
GjcUpdateApplyResult,
|
|
20
|
+
GjcUpdatePreview,
|
|
21
|
+
} from "../../extensibility/gjc-plugins/types";
|
|
22
|
+
import { getSelectListTheme, theme } from "../../modes/theme/theme";
|
|
23
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
24
|
+
|
|
25
|
+
/** Injectable lifecycle boundary; Settings never reads registries or executes bundle code. */
|
|
26
|
+
export interface GjcBundleLifecyclePort {
|
|
27
|
+
listGjcBundles(ctx: GjcLifecycleContext): Promise<GjcBundleSummary[]>;
|
|
28
|
+
getGjcBundle(ctx: GjcLifecycleContext, identity: GjcBundleIdentity): Promise<GjcLifecycleResult<GjcBundleSummary>>;
|
|
29
|
+
previewGjcBundleUpdate(
|
|
30
|
+
ctx: GjcLifecycleContext,
|
|
31
|
+
identity: GjcBundleIdentity,
|
|
32
|
+
): Promise<GjcLifecycleResult<GjcUpdatePreview>>;
|
|
33
|
+
applyGjcBundleUpdate(
|
|
34
|
+
ctx: GjcLifecycleContext,
|
|
35
|
+
token: GjcReviewedUpdateToken,
|
|
36
|
+
): Promise<GjcLifecycleResult<GjcUpdateApplyResult>>;
|
|
37
|
+
setGjcBundleEnabled(
|
|
38
|
+
ctx: GjcLifecycleContext,
|
|
39
|
+
identity: GjcBundleIdentity,
|
|
40
|
+
enabled: boolean,
|
|
41
|
+
): Promise<GjcLifecycleResult<GjcToggleResult>>;
|
|
42
|
+
setGjcBundleSurfaceEnabled(
|
|
43
|
+
ctx: GjcLifecycleContext,
|
|
44
|
+
identity: GjcBundleIdentity,
|
|
45
|
+
surfaceId: string,
|
|
46
|
+
enabled: boolean,
|
|
47
|
+
): Promise<GjcLifecycleResult<GjcToggleResult>>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface GjcBundleSettingsDependencies {
|
|
51
|
+
lifecycle?: GjcBundleLifecyclePort;
|
|
52
|
+
runtimeSnapshotProvider?: GjcRuntimeSnapshotProvider;
|
|
53
|
+
activationGeneration?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface GjcBundleSettingsCallbacks {
|
|
57
|
+
onClose: () => void;
|
|
58
|
+
onBundlesChanged?: () => void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type GjcBundleSettingsState =
|
|
62
|
+
| "loading"
|
|
63
|
+
| "error"
|
|
64
|
+
| "empty"
|
|
65
|
+
| "list"
|
|
66
|
+
| "detail"
|
|
67
|
+
| "unsupported-source"
|
|
68
|
+
| "update-review"
|
|
69
|
+
| "update-confirm"
|
|
70
|
+
| "update-running"
|
|
71
|
+
| "update-result"
|
|
72
|
+
| "stale-result"
|
|
73
|
+
| "quarantined-blocked"
|
|
74
|
+
| "mutation-in-flight-locked";
|
|
75
|
+
|
|
76
|
+
const PRODUCTION_LIFECYCLE: GjcBundleLifecyclePort = {
|
|
77
|
+
listGjcBundles,
|
|
78
|
+
getGjcBundle,
|
|
79
|
+
previewGjcBundleUpdate,
|
|
80
|
+
applyGjcBundleUpdate,
|
|
81
|
+
setGjcBundleEnabled,
|
|
82
|
+
setGjcBundleSurfaceEnabled,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** Scope-qualified, lifecycle-backed Settings UI for installed GJC bundles. */
|
|
86
|
+
export class GjcBundleSettingsComponent extends Container {
|
|
87
|
+
#input: SelectList | null = null;
|
|
88
|
+
#bundles: GjcBundleSummary[] = [];
|
|
89
|
+
#focused: GjcBundleIdentity | null = null;
|
|
90
|
+
#preview: GjcUpdatePreview | null = null;
|
|
91
|
+
#state: GjcBundleSettingsState = "loading";
|
|
92
|
+
#message: string | null = null;
|
|
93
|
+
#navigationLocked = false;
|
|
94
|
+
#disposed = false;
|
|
95
|
+
|
|
96
|
+
constructor(
|
|
97
|
+
cwd: string,
|
|
98
|
+
private readonly callbacks: GjcBundleSettingsCallbacks,
|
|
99
|
+
private readonly dependencies: GjcBundleSettingsDependencies = {},
|
|
100
|
+
) {
|
|
101
|
+
super();
|
|
102
|
+
this.#context = { cwd };
|
|
103
|
+
void this.#load();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
readonly #context: GjcLifecycleContext;
|
|
107
|
+
|
|
108
|
+
get stateId(): GjcBundleSettingsState {
|
|
109
|
+
return this.#state;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
get navigationLocked(): boolean {
|
|
113
|
+
return this.#navigationLocked;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get #lifecycle(): GjcBundleLifecyclePort {
|
|
117
|
+
return this.dependencies.lifecycle ?? PRODUCTION_LIFECYCLE;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async #load(): Promise<void> {
|
|
121
|
+
this.#state = "loading";
|
|
122
|
+
this.#message = null;
|
|
123
|
+
this.#render();
|
|
124
|
+
try {
|
|
125
|
+
this.#bundles = await this.#lifecycle.listGjcBundles(this.#context);
|
|
126
|
+
if (this.#disposed) return;
|
|
127
|
+
this.#state = this.#bundles.length === 0 ? "empty" : "list";
|
|
128
|
+
} catch {
|
|
129
|
+
if (this.#disposed) return;
|
|
130
|
+
this.#state = "error";
|
|
131
|
+
this.#message = "Unable to load GJC bundles.";
|
|
132
|
+
}
|
|
133
|
+
this.#render();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#render(): void {
|
|
137
|
+
if (this.#disposed) return;
|
|
138
|
+
this.clear();
|
|
139
|
+
this.#input = null;
|
|
140
|
+
this.addChild(new DynamicBorder());
|
|
141
|
+
this.addChild(new Text(theme.bold(theme.fg("accent", " GJC Bundles")), 0, 0));
|
|
142
|
+
this.addChild(new Spacer(1));
|
|
143
|
+
switch (this.#state) {
|
|
144
|
+
case "loading":
|
|
145
|
+
this.addChild(new Text(theme.fg("muted", " Loading installed bundles…"), 0, 0));
|
|
146
|
+
this.#renderActionList([]);
|
|
147
|
+
break;
|
|
148
|
+
case "error":
|
|
149
|
+
this.#renderActionList([
|
|
150
|
+
{ value: "retry", label: "Retry", description: this.#message ?? "Unable to load GJC bundles." },
|
|
151
|
+
]);
|
|
152
|
+
break;
|
|
153
|
+
case "empty":
|
|
154
|
+
this.addChild(new Text(theme.fg("muted", " No GJC bundles installed"), 0, 0));
|
|
155
|
+
this.#renderActionList([]);
|
|
156
|
+
break;
|
|
157
|
+
case "list":
|
|
158
|
+
this.#renderList();
|
|
159
|
+
break;
|
|
160
|
+
case "detail":
|
|
161
|
+
case "unsupported-source":
|
|
162
|
+
case "quarantined-blocked":
|
|
163
|
+
case "update-result":
|
|
164
|
+
case "stale-result":
|
|
165
|
+
this.#renderDetail();
|
|
166
|
+
break;
|
|
167
|
+
case "update-review":
|
|
168
|
+
case "update-confirm":
|
|
169
|
+
this.#renderUpdateReview();
|
|
170
|
+
break;
|
|
171
|
+
case "update-running":
|
|
172
|
+
case "mutation-in-flight-locked":
|
|
173
|
+
this.addChild(new Text(theme.fg("muted", " Applying non-cancellable bundle change…"), 0, 0));
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
this.addChild(new Spacer(1));
|
|
177
|
+
this.addChild(
|
|
178
|
+
new Text(
|
|
179
|
+
theme.fg(
|
|
180
|
+
"dim",
|
|
181
|
+
this.#navigationLocked
|
|
182
|
+
? " Navigation locked while mutation runs"
|
|
183
|
+
: " Enter to select · Esc to go back",
|
|
184
|
+
),
|
|
185
|
+
0,
|
|
186
|
+
0,
|
|
187
|
+
),
|
|
188
|
+
);
|
|
189
|
+
this.addChild(new DynamicBorder());
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#renderList(): void {
|
|
193
|
+
const items: SelectItem[] = this.#bundles.map(bundle => {
|
|
194
|
+
const runtime = findingsForBundle(
|
|
195
|
+
this.dependencies.runtimeSnapshotProvider,
|
|
196
|
+
bundle.identity,
|
|
197
|
+
this.dependencies.activationGeneration ?? 0,
|
|
198
|
+
);
|
|
199
|
+
const intent = bundle.enabled ? "enabled" : "disabled";
|
|
200
|
+
const quarantine = bundle.quarantined ? " · quarantined" : "";
|
|
201
|
+
const effective =
|
|
202
|
+
runtime.status === "unavailable"
|
|
203
|
+
? "runtime unavailable"
|
|
204
|
+
: runtime.findings.length > 0
|
|
205
|
+
? "runtime findings"
|
|
206
|
+
: "runtime current";
|
|
207
|
+
return {
|
|
208
|
+
value: identityKey(bundle.identity),
|
|
209
|
+
label: `${bundle.identity.name} (${bundle.identity.scope})`,
|
|
210
|
+
description: `v${bundle.version} · ${intent}${quarantine} · ${effective}`,
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
this.#renderActionList(items);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#renderDetail(): void {
|
|
217
|
+
const bundle = this.#bundleForFocus();
|
|
218
|
+
if (!bundle) {
|
|
219
|
+
this.#state = "list";
|
|
220
|
+
this.#render();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
this.addChild(
|
|
224
|
+
new Text(theme.fg("muted", ` ${bundle.identity.name} (${bundle.identity.scope}) · v${bundle.version}`), 0, 0),
|
|
225
|
+
);
|
|
226
|
+
if (bundle.description) this.addChild(new Text(theme.fg("muted", ` ${bundle.description}`), 0, 0));
|
|
227
|
+
this.addChild(new Text(theme.fg("dim", ` Source: ${bundle.source.display}`), 0, 0));
|
|
228
|
+
if (bundle.source.ref) this.addChild(new Text(theme.fg("dim", ` Ref: ${bundle.source.ref}`), 0, 0));
|
|
229
|
+
if (bundle.source.sha) this.addChild(new Text(theme.fg("dim", ` SHA: ${bundle.source.sha}`), 0, 0));
|
|
230
|
+
this.addChild(
|
|
231
|
+
new Text(theme.fg("dim", ` Installed: ${bundle.installedAt} · Updated: ${bundle.updatedAt}`), 0, 0),
|
|
232
|
+
);
|
|
233
|
+
if (!bundle.source.updatable) {
|
|
234
|
+
this.addChild(
|
|
235
|
+
new Text(
|
|
236
|
+
theme.fg(
|
|
237
|
+
"warning",
|
|
238
|
+
` Update unsupported: ${bundle.source.unsupportedReason ?? "This source cannot be refreshed."}`,
|
|
239
|
+
),
|
|
240
|
+
0,
|
|
241
|
+
0,
|
|
242
|
+
),
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
if (this.#message) this.addChild(new Text(theme.fg("warning", ` ${this.#message}`), 0, 0));
|
|
246
|
+
const actions: SelectItem[] = [];
|
|
247
|
+
if (bundle.enabled || !bundle.quarantined) {
|
|
248
|
+
actions.push({
|
|
249
|
+
value: `bundle:${bundle.enabled ? "disable" : "enable"}`,
|
|
250
|
+
label: bundle.enabled ? "Disable bundle" : "Enable bundle",
|
|
251
|
+
});
|
|
252
|
+
} else {
|
|
253
|
+
actions.push({
|
|
254
|
+
value: "bundle:blocked",
|
|
255
|
+
label: "Enable bundle (blocked)",
|
|
256
|
+
description: "Quarantined bundles cannot be enabled.",
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (bundle.source.updatable) actions.push({ value: "update", label: "Review update" });
|
|
260
|
+
for (const surface of bundle.surfaces) {
|
|
261
|
+
const action = surface.enabled ? "disable" : surface.quarantined ? "blocked" : "enable";
|
|
262
|
+
actions.push({
|
|
263
|
+
value: `surface:${surface.extensionId}:${action}`,
|
|
264
|
+
label: `${surface.name} (${surface.kind})`,
|
|
265
|
+
description: `${surface.enabled ? "enabled" : "disabled"}${surface.quarantined ? " · quarantined" : ""}`,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
this.#renderActionList(actions);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
#renderUpdateReview(): void {
|
|
272
|
+
const preview = this.#preview;
|
|
273
|
+
if (!preview) return;
|
|
274
|
+
this.addChild(new Text(theme.fg("muted", ` ${preview.identity.name} (${preview.identity.scope})`), 0, 0));
|
|
275
|
+
this.addChild(new Text(theme.fg("muted", ` v${preview.current.version} → v${preview.candidateVersion}`), 0, 0));
|
|
276
|
+
this.addChild(
|
|
277
|
+
new Text(
|
|
278
|
+
theme.fg(
|
|
279
|
+
"dim",
|
|
280
|
+
` Surfaces: +${preview.addedSurfaceIds.length} −${preview.removedSurfaceIds.length} =${preview.retainedSurfaceIds.length}`,
|
|
281
|
+
),
|
|
282
|
+
0,
|
|
283
|
+
0,
|
|
284
|
+
),
|
|
285
|
+
);
|
|
286
|
+
if (!preview.changed) this.addChild(new Text(theme.fg("muted", " No update changes were found."), 0, 0));
|
|
287
|
+
this.#renderActionList([
|
|
288
|
+
this.#state === "update-review"
|
|
289
|
+
? { value: "continue-update", label: "Continue to confirmation" }
|
|
290
|
+
: { value: "apply-update", label: "Apply update", description: "This operation cannot be cancelled." },
|
|
291
|
+
]);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
#renderActionList(items: SelectItem[]): void {
|
|
295
|
+
this.#input = new SelectList(items, Math.min(Math.max(items.length, 1), 12), getSelectListTheme());
|
|
296
|
+
this.#input.onSelect = item => this.#select(item.value);
|
|
297
|
+
this.#input.onCancel = () => this.#back();
|
|
298
|
+
this.addChild(this.#input);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
#select(value: string): void {
|
|
302
|
+
if (this.#navigationLocked) return;
|
|
303
|
+
if (value === "retry") {
|
|
304
|
+
void this.#load();
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (this.#state === "list") {
|
|
308
|
+
const bundle = this.#bundles.find(candidate => identityKey(candidate.identity) === value);
|
|
309
|
+
if (bundle) {
|
|
310
|
+
this.#focused = bundle.identity;
|
|
311
|
+
this.#state = bundle.source.updatable ? "detail" : "unsupported-source";
|
|
312
|
+
this.#render();
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (value === "update") {
|
|
317
|
+
void this.#reviewUpdate();
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (value === "continue-update") {
|
|
321
|
+
this.#state = "update-confirm";
|
|
322
|
+
this.#render();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (value === "apply-update") {
|
|
326
|
+
void this.#applyUpdate();
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (value === "bundle:blocked" || value.endsWith(":blocked")) {
|
|
330
|
+
this.#state = "quarantined-blocked";
|
|
331
|
+
this.#message = "quarantined: enable is blocked; disable remains available.";
|
|
332
|
+
this.#render();
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (value.startsWith("bundle:")) {
|
|
336
|
+
void this.#toggleBundle(value === "bundle:enable");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (value.startsWith("surface:")) {
|
|
340
|
+
const encoded = value.slice("surface:".length);
|
|
341
|
+
const separator = encoded.lastIndexOf(":");
|
|
342
|
+
const surfaceId = encoded.slice(0, separator);
|
|
343
|
+
const action = encoded.slice(separator + 1);
|
|
344
|
+
if (separator > 0 && surfaceId && action) void this.#toggleSurface(surfaceId, action === "enable");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async #reviewUpdate(): Promise<void> {
|
|
349
|
+
const bundle = this.#bundleForFocus();
|
|
350
|
+
if (!bundle) return;
|
|
351
|
+
this.#message = null;
|
|
352
|
+
try {
|
|
353
|
+
const result = await this.#lifecycle.previewGjcBundleUpdate(this.#context, bundle.identity);
|
|
354
|
+
if (this.#disposed || !this.#isFocused(bundle.identity)) return;
|
|
355
|
+
if (!result.ok) {
|
|
356
|
+
this.#state = result.error.code === "source_unsupported" ? "unsupported-source" : "detail";
|
|
357
|
+
this.#message = result.error.message;
|
|
358
|
+
} else {
|
|
359
|
+
this.#preview = result.value;
|
|
360
|
+
this.#state = "update-review";
|
|
361
|
+
}
|
|
362
|
+
} catch {
|
|
363
|
+
if (this.#disposed || !this.#isFocused(bundle.identity)) return;
|
|
364
|
+
this.#state = "detail";
|
|
365
|
+
this.#message = "Unable to review this update.";
|
|
366
|
+
}
|
|
367
|
+
this.#render();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async #applyUpdate(): Promise<void> {
|
|
371
|
+
const preview = this.#preview;
|
|
372
|
+
if (!preview) return;
|
|
373
|
+
await this.#mutate(preview.identity, "update-running", async () => {
|
|
374
|
+
const result = await this.#lifecycle.applyGjcBundleUpdate(this.#context, preview.token);
|
|
375
|
+
if (this.#disposed || !this.#isFocused(preview.identity)) return;
|
|
376
|
+
if (!result.ok) {
|
|
377
|
+
if (result.error.code.startsWith("stale_")) {
|
|
378
|
+
this.#state = "stale-result";
|
|
379
|
+
this.#message = `${result.error.message} Re-review the update.`;
|
|
380
|
+
} else {
|
|
381
|
+
this.#state = "detail";
|
|
382
|
+
this.#message = result.error.message;
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
this.#replaceSummary(result.value.summary);
|
|
387
|
+
this.#state = "update-result";
|
|
388
|
+
this.#message = result.value.status === "updated" ? "Update applied." : "Already up to date.";
|
|
389
|
+
this.callbacks.onBundlesChanged?.();
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async #toggleBundle(enabled: boolean): Promise<void> {
|
|
394
|
+
const bundle = this.#bundleForFocus();
|
|
395
|
+
if (!bundle) return;
|
|
396
|
+
if (enabled && bundle.quarantined) {
|
|
397
|
+
this.#state = "quarantined-blocked";
|
|
398
|
+
this.#message = "quarantined: enable is blocked; disable remains available.";
|
|
399
|
+
this.#render();
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
await this.#mutate(bundle.identity, "mutation-in-flight-locked", async () => {
|
|
403
|
+
const result = await this.#lifecycle.setGjcBundleEnabled(this.#context, bundle.identity, enabled);
|
|
404
|
+
if (this.#disposed || !this.#isFocused(bundle.identity)) return;
|
|
405
|
+
this.#applyToggleResult(result);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async #toggleSurface(surfaceId: string, enabled: boolean): Promise<void> {
|
|
410
|
+
const bundle = this.#bundleForFocus();
|
|
411
|
+
if (!bundle) return;
|
|
412
|
+
const surface = bundle.surfaces.find(candidate => candidate.extensionId === surfaceId);
|
|
413
|
+
if (enabled && surface?.quarantined) {
|
|
414
|
+
this.#state = "quarantined-blocked";
|
|
415
|
+
this.#message = "quarantined: enable is blocked; disable remains available.";
|
|
416
|
+
this.#render();
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
await this.#mutate(bundle.identity, "mutation-in-flight-locked", async () => {
|
|
420
|
+
const result = await this.#lifecycle.setGjcBundleSurfaceEnabled(
|
|
421
|
+
this.#context,
|
|
422
|
+
bundle.identity,
|
|
423
|
+
surfaceId,
|
|
424
|
+
enabled,
|
|
425
|
+
);
|
|
426
|
+
if (this.#disposed || !this.#isFocused(bundle.identity)) return;
|
|
427
|
+
this.#applyToggleResult(result);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
#applyToggleResult(result: GjcLifecycleResult<GjcToggleResult>): void {
|
|
432
|
+
if (!result.ok) {
|
|
433
|
+
this.#state = result.error.code === "quarantined" ? "quarantined-blocked" : "detail";
|
|
434
|
+
this.#message = result.error.message;
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
this.#replaceSummary(result.value.summary);
|
|
438
|
+
this.#state = "detail";
|
|
439
|
+
this.#message = null;
|
|
440
|
+
this.callbacks.onBundlesChanged?.();
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async #mutate(
|
|
444
|
+
identity: GjcBundleIdentity,
|
|
445
|
+
runningState: "update-running" | "mutation-in-flight-locked",
|
|
446
|
+
operation: () => Promise<void>,
|
|
447
|
+
): Promise<void> {
|
|
448
|
+
if (this.#navigationLocked) return;
|
|
449
|
+
this.#navigationLocked = true;
|
|
450
|
+
this.#state = runningState;
|
|
451
|
+
this.#render();
|
|
452
|
+
try {
|
|
453
|
+
await operation();
|
|
454
|
+
} catch {
|
|
455
|
+
if (!this.#disposed && this.#isFocused(identity)) {
|
|
456
|
+
this.#state = "detail";
|
|
457
|
+
this.#message = "Unable to apply this bundle change.";
|
|
458
|
+
}
|
|
459
|
+
} finally {
|
|
460
|
+
if (!this.#disposed) {
|
|
461
|
+
this.#navigationLocked = false;
|
|
462
|
+
if (this.#isFocused(identity)) this.#render();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
#replaceSummary(summary: GjcBundleSummary): void {
|
|
468
|
+
const index = this.#bundles.findIndex(bundle => identityEquals(bundle.identity, summary.identity));
|
|
469
|
+
if (index >= 0) this.#bundles[index] = summary;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
#bundleForFocus(): GjcBundleSummary | undefined {
|
|
473
|
+
return this.#focused ? this.#bundles.find(bundle => identityEquals(bundle.identity, this.#focused!)) : undefined;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
#isFocused(identity: GjcBundleIdentity): boolean {
|
|
477
|
+
return this.#focused !== null && identityEquals(this.#focused, identity);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
#back(): void {
|
|
481
|
+
if (this.#navigationLocked) return;
|
|
482
|
+
if (this.#state === "list" || this.#state === "empty" || this.#state === "error") {
|
|
483
|
+
this.callbacks.onClose();
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (this.#state === "update-review" || this.#state === "update-confirm") {
|
|
487
|
+
this.#state = "detail";
|
|
488
|
+
this.#preview = null;
|
|
489
|
+
} else {
|
|
490
|
+
this.#focused = null;
|
|
491
|
+
this.#preview = null;
|
|
492
|
+
this.#message = null;
|
|
493
|
+
this.#state = this.#bundles.length === 0 ? "empty" : "list";
|
|
494
|
+
}
|
|
495
|
+
this.#render();
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
override dispose(): void {
|
|
499
|
+
this.#disposed = true;
|
|
500
|
+
this.#input = null;
|
|
501
|
+
super.dispose();
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
handleInput(data: string): void {
|
|
505
|
+
if (this.#navigationLocked) return;
|
|
506
|
+
this.#input?.handleInput(data);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
@@ -82,6 +82,35 @@ export function computeIrcSplitWidths(width: number): {
|
|
|
82
82
|
rightWidth: sidebarWidth,
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolves the single shared work-lane geometry. The left lane is used by both
|
|
87
|
+
* transcript and todo content whenever the IRC lane is effective.
|
|
88
|
+
*/
|
|
89
|
+
export function computeIrcWorkLaneWidths(
|
|
90
|
+
width: number,
|
|
91
|
+
sidebarVisible: boolean,
|
|
92
|
+
): { leftWidth: number; separatorWidth: number; rightWidth: number } {
|
|
93
|
+
const fullWidth = Math.max(0, Math.floor(width));
|
|
94
|
+
return sidebarVisible
|
|
95
|
+
? computeIrcSplitWidths(fullWidth)
|
|
96
|
+
: { leftWidth: fullWidth, separatorWidth: 0, rightWidth: 0 };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class IrcLeftLaneComponent implements Component {
|
|
100
|
+
constructor(
|
|
101
|
+
private readonly content: Component,
|
|
102
|
+
private readonly isSidebarVisible: (width: number) => boolean,
|
|
103
|
+
) {}
|
|
104
|
+
|
|
105
|
+
render(width: number): string[] {
|
|
106
|
+
const layout = computeIrcWorkLaneWidths(width, this.isSidebarVisible(width));
|
|
107
|
+
return this.content.render(layout.leftWidth);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
invalidate(): void {
|
|
111
|
+
this.content.invalidate?.();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
85
114
|
|
|
86
115
|
type SemanticLine = Readonly<{
|
|
87
116
|
kind: "header" | "body" | "elision" | "blank";
|
|
@@ -288,7 +317,7 @@ export class IrcSplitViewComponent implements ViewportAnchorProvider {
|
|
|
288
317
|
renderWithViewportAnchors(width: number): ViewportAnchorRender {
|
|
289
318
|
if (!this.#visible) return renderComponentWithViewportAnchors(this.leftPane, width);
|
|
290
319
|
const componentTheme = typeof this.componentTheme === "function" ? this.componentTheme() : this.componentTheme;
|
|
291
|
-
const { leftWidth, separatorWidth, rightWidth } =
|
|
320
|
+
const { leftWidth, separatorWidth, rightWidth } = computeIrcWorkLaneWidths(width, this.#visible);
|
|
292
321
|
if (rightWidth === 0) return renderComponentWithViewportAnchors(this.leftPane, width);
|
|
293
322
|
const separator = separatorWidth > 0 ? componentTheme.fg("dim", ` ${componentTheme.boxSharp.vertical} `) : "";
|
|
294
323
|
const leftRender = withTerminalGraphicsFallback(
|
|
@@ -32,6 +32,7 @@ export interface NotificationsEditorPreferences {
|
|
|
32
32
|
richDraftEnabled: boolean;
|
|
33
33
|
toolActivityEnabled: boolean;
|
|
34
34
|
streamingEnabled: boolean;
|
|
35
|
+
sound: "all" | "important" | "none";
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
/** Secret-safe snapshot used to render the Notifications tab. */
|
|
@@ -160,7 +161,8 @@ type HomeActionId =
|
|
|
160
161
|
| "reconnect"
|
|
161
162
|
| "remove"
|
|
162
163
|
| "preferences"
|
|
163
|
-
| "tool-activity"
|
|
164
|
+
| "tool-activity"
|
|
165
|
+
| "sound";
|
|
164
166
|
|
|
165
167
|
interface Action {
|
|
166
168
|
id: HomeActionId | "telegram" | "external" | "save" | "save-inactive" | "cancel" | "confirm";
|
|
@@ -197,6 +199,7 @@ function emptyState(): NotificationsEditorState {
|
|
|
197
199
|
richDraftEnabled: false,
|
|
198
200
|
toolActivityEnabled: false,
|
|
199
201
|
streamingEnabled: true,
|
|
202
|
+
sound: "all",
|
|
200
203
|
},
|
|
201
204
|
};
|
|
202
205
|
}
|
|
@@ -942,6 +945,9 @@ export class NotificationsSettingsEditorComponent implements Component, Focusabl
|
|
|
942
945
|
case "test":
|
|
943
946
|
draft.streamingEnabled = !draft.streamingEnabled;
|
|
944
947
|
return;
|
|
948
|
+
case "sound":
|
|
949
|
+
draft.sound = draft.sound === "all" ? "important" : draft.sound === "important" ? "none" : "all";
|
|
950
|
+
return;
|
|
945
951
|
default:
|
|
946
952
|
return;
|
|
947
953
|
}
|
|
@@ -1092,6 +1098,11 @@ export class NotificationsSettingsEditorComponent implements Component, Focusabl
|
|
|
1092
1098
|
label: `Telegram streaming: ${draft.streamingEnabled ? "on" : "off"}`,
|
|
1093
1099
|
description: "Toggle the unsaved Telegram streaming preference.",
|
|
1094
1100
|
},
|
|
1101
|
+
{
|
|
1102
|
+
id: "sound",
|
|
1103
|
+
label: `Telegram notification sound: ${draft.sound}`,
|
|
1104
|
+
description: "Cycle between all, important, and none notification sounds.",
|
|
1105
|
+
},
|
|
1095
1106
|
{ id: "save", label: "Save preferences", description: "Atomically persist this preference draft." },
|
|
1096
1107
|
{ id: "cancel", label: "Cancel and discard draft", description: "Leave saved preferences unchanged." },
|
|
1097
1108
|
];
|