@pellux/goodvibes-tui 0.25.0 → 0.27.0
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 +22 -3
- package/README.md +12 -7
- package/docs/foundation-artifacts/operator-contract.json +2422 -1040
- package/package.json +2 -2
- package/src/cli/bundle-command.ts +2 -1
- package/src/cli/service-posture.ts +2 -1
- package/src/core/conversation-rendering.ts +2 -2
- package/src/core/conversation.ts +49 -2
- package/src/core/session-recovery.ts +24 -18
- package/src/core/system-message-router.ts +42 -26
- package/src/core/turn-event-wiring.ts +12 -16
- package/src/daemon/{calendar → handlers/calendar}/caldav-client.ts +29 -24
- package/src/daemon/handlers/calendar/index.ts +316 -0
- package/src/daemon/handlers/context.ts +29 -0
- package/src/daemon/handlers/contracts.ts +77 -0
- package/src/daemon/{channels → handlers}/drafts/draft-store.ts +114 -50
- package/src/daemon/handlers/drafts/index.ts +17 -0
- package/src/daemon/handlers/drafts/register.ts +331 -0
- package/src/daemon/handlers/email/config.ts +164 -0
- package/src/daemon/handlers/email/index.ts +43 -0
- package/src/daemon/handlers/email/read-handlers.ts +80 -0
- package/src/daemon/handlers/email/runtime.ts +140 -0
- package/src/daemon/handlers/email/validation.ts +147 -0
- package/src/daemon/handlers/email/write-handlers.ts +133 -0
- package/src/daemon/handlers/errors.ts +18 -0
- package/src/daemon/{channels → handlers}/inbox/cursor-store.ts +58 -66
- package/src/daemon/handlers/inbox/index.ts +211 -0
- package/src/daemon/{channels → handlers}/inbox/mapping.ts +8 -6
- package/src/daemon/{channels → handlers}/inbox/poller.ts +6 -5
- package/src/daemon/{channels → handlers}/inbox/provider-adapter.ts +14 -10
- package/src/daemon/{channels → handlers}/inbox/providers/discord.ts +10 -11
- package/src/daemon/{channels → handlers}/inbox/providers/email.ts +2 -1
- package/src/daemon/{channels → handlers}/inbox/providers/imap-client.ts +1 -1
- package/src/daemon/{channels → handlers}/inbox/providers/route-util.ts +4 -3
- package/src/daemon/{channels → handlers}/inbox/providers/slack.ts +11 -12
- package/src/daemon/handlers/index.ts +107 -0
- package/src/daemon/handlers/register.ts +161 -0
- package/src/daemon/{remote → handlers/remote}/backends/cloud-terminal.ts +8 -3
- package/src/daemon/{remote → handlers/remote}/backends/docker.ts +2 -3
- package/src/daemon/handlers/remote/backends/index.ts +40 -0
- package/src/daemon/{remote → handlers/remote}/backends/process-runner.ts +16 -40
- package/src/daemon/{remote → handlers/remote}/backends/ssh.ts +8 -3
- package/src/daemon/{remote → handlers/remote}/backends/types.ts +29 -3
- package/src/daemon/{remote → handlers/remote}/dispatcher.ts +27 -6
- package/src/daemon/handlers/remote/index.ts +119 -0
- package/src/daemon/{remote → handlers/remote}/peer-registry.ts +47 -11
- package/src/daemon/handlers/remote/service.ts +191 -0
- package/src/daemon/{channels → handlers}/routing/inbox-bridge.ts +33 -20
- package/src/daemon/handlers/routing/index.ts +261 -0
- package/src/daemon/{channels → handlers}/routing/route-store.ts +57 -16
- package/src/daemon/{channels → handlers}/routing/routing-resolver.ts +1 -1
- package/src/daemon/{operator → handlers}/sqlite-store.ts +5 -5
- package/src/daemon/handlers/triage/index.ts +57 -0
- package/src/daemon/handlers/triage/integration.ts +213 -0
- package/src/daemon/{triage → handlers/triage}/pipeline.ts +60 -71
- package/src/daemon/{triage → handlers/triage}/scorer.ts +21 -21
- package/src/daemon/handlers/triage/tagger/discord.ts +187 -0
- package/src/daemon/handlers/triage/tagger/imap.ts +384 -0
- package/src/daemon/handlers/triage/tagger/index.ts +184 -0
- package/src/daemon/handlers/triage/tagger/shared.ts +70 -0
- package/src/daemon/handlers/triage/tagger/slack.ts +69 -0
- package/src/daemon/handlers/triage/types.ts +50 -0
- package/src/export/gist-uploader.ts +3 -1
- package/src/input/command-registry.ts +1 -0
- package/src/input/commands/cloudflare-runtime.ts +2 -1
- package/src/input/commands/guidance-runtime.ts +2 -4
- package/src/input/commands/health-runtime.ts +13 -7
- package/src/input/commands/knowledge.ts +2 -1
- package/src/input/commands/runtime-services.ts +40 -10
- package/src/input/handler-command-route.ts +1 -1
- package/src/input/handler-interactions.ts +2 -1
- package/src/input/handler-modal-routes.ts +2 -1
- package/src/input/handler-onboarding-cloudflare.ts +2 -1
- package/src/input/handler-onboarding.ts +8 -8
- package/src/input/handler-ui-state.ts +1 -1
- package/src/input/tts-settings-actions.ts +2 -1
- package/src/main.ts +52 -59
- package/src/panels/agent-inspector-panel.ts +72 -2
- package/src/panels/agent-logs-panel.ts +127 -19
- package/src/panels/base-panel.ts +2 -1
- package/src/panels/builtin/agent.ts +3 -1
- package/src/panels/builtin/development.ts +1 -0
- package/src/panels/builtin/session.ts +1 -1
- package/src/panels/context-visualizer-panel.ts +24 -14
- package/src/panels/cost-tracker-panel.ts +7 -13
- package/src/panels/debug-panel.ts +11 -22
- package/src/panels/docs-panel.ts +14 -24
- package/src/panels/file-explorer-panel.ts +2 -1
- package/src/panels/file-preview-panel.ts +2 -1
- package/src/panels/git-panel.ts +10 -17
- package/src/panels/marketplace-panel.ts +2 -1
- package/src/panels/memory-panel.ts +2 -1
- package/src/panels/project-planning-panel.ts +5 -4
- package/src/panels/provider-health-panel.ts +56 -67
- package/src/panels/schedule-panel.ts +4 -7
- package/src/panels/session-browser-panel.ts +13 -21
- package/src/panels/skills-panel.ts +2 -1
- package/src/panels/tasks-panel.ts +2 -7
- package/src/panels/thinking-panel.ts +6 -11
- package/src/panels/token-budget-panel.ts +31 -15
- package/src/panels/tool-inspector-panel.ts +10 -18
- package/src/panels/work-plan-panel.ts +2 -1
- package/src/panels/wrfc-panel.ts +37 -35
- package/src/renderer/agent-detail-modal.ts +2 -2
- package/src/renderer/modal-utils.ts +0 -10
- package/src/renderer/process-modal.ts +2 -2
- package/src/runtime/bootstrap-command-context.ts +3 -0
- package/src/runtime/bootstrap-command-parts.ts +3 -1
- package/src/runtime/bootstrap-core.ts +31 -31
- package/src/runtime/bootstrap-shell.ts +1 -0
- package/src/runtime/onboarding/apply.ts +4 -3
- package/src/runtime/onboarding/snapshot.ts +4 -3
- package/src/runtime/process-lifecycle.ts +195 -0
- package/src/runtime/services.ts +52 -36
- package/src/runtime/wrfc-persistence.ts +20 -5
- package/src/shell/ui-openers.ts +3 -2
- package/src/verification/live-verifier.ts +4 -3
- package/src/version.ts +1 -1
- package/src/core/context-auto-compact.ts +0 -110
- package/src/daemon/calendar/index.ts +0 -52
- package/src/daemon/calendar/register.ts +0 -527
- package/src/daemon/channels/drafts/index.ts +0 -22
- package/src/daemon/channels/drafts/register.ts +0 -449
- package/src/daemon/channels/inbox/index.ts +0 -58
- package/src/daemon/channels/inbox/register.ts +0 -247
- package/src/daemon/channels/routing/index.ts +0 -39
- package/src/daemon/channels/routing/register.ts +0 -296
- package/src/daemon/email/index.ts +0 -68
- package/src/daemon/email/register.ts +0 -715
- package/src/daemon/operator/index.ts +0 -43
- package/src/daemon/operator/register-helper.ts +0 -150
- package/src/daemon/operator/surfaces.ts +0 -137
- package/src/daemon/operator/types.ts +0 -207
- package/src/daemon/remote/backends/index.ts +0 -34
- package/src/daemon/remote/index.ts +0 -74
- package/src/daemon/remote/register.ts +0 -411
- package/src/daemon/triage/index.ts +0 -59
- package/src/daemon/triage/integration.ts +0 -179
- package/src/daemon/triage/register.ts +0 -231
- package/src/daemon/triage/tagger.ts +0 -777
- /package/src/daemon/{calendar → handlers/calendar}/ics.ts +0 -0
- /package/src/daemon/{operator/credential-store.ts → handlers/credentials.ts} +0 -0
- /package/src/daemon/{email → handlers/email}/imap-connector.ts +0 -0
- /package/src/daemon/{email → handlers/email}/imap-parsing.ts +0 -0
- /package/src/daemon/{email → handlers/email}/smtp-connector.ts +0 -0
- /package/src/daemon/{remote → handlers/remote}/backends/local-process.ts +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Daemon-internal triage TAGGER (composition).
|
|
3
|
+
//
|
|
4
|
+
// Applies user-defined triage labels back on the provider side:
|
|
5
|
+
// - IMAP : STORE a keyword flag on the message (IMAP4rev1 over TLS).
|
|
6
|
+
// - Slack: reactions.add emoji on the source message.
|
|
7
|
+
// - Discord: real forum thread tags (PATCH applied_tags, merge) when a
|
|
8
|
+
// forum-tag mapping is configured, else a unicode reaction analog.
|
|
9
|
+
//
|
|
10
|
+
// Hard rules honored here:
|
|
11
|
+
// - All provider credentials come ONLY from the daemon credential store.
|
|
12
|
+
// They are never returned in results and never logged.
|
|
13
|
+
// - The whole tagger is gated behind a config flag (surfaces.triage.autoTag);
|
|
14
|
+
// when disabled, applyTags() is a no-op that reports skipped:true.
|
|
15
|
+
// - Provider-side writes are EFFECTFUL; callers must pass an explicitly
|
|
16
|
+
// confirmed request (confirm === true && explicitUserRequest === true).
|
|
17
|
+
// Unconfirmed calls throw HandlerError(REQUIRE_CONFIRM).
|
|
18
|
+
//
|
|
19
|
+
// Contract-fidelity note: the triage surface (`inbox.triage.*`) is daemon-
|
|
20
|
+
// internal and NOT a published operator method, so there is no external
|
|
21
|
+
// request/response schema to certify these tag shapes against. What is
|
|
22
|
+
// guaranteed is the provider-side behavior: no silent data loss (Discord thread
|
|
23
|
+
// tags are merged, never blindly overwritten) and no command injection (IMAP
|
|
24
|
+
// quoting rejects control characters).
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
import type { HandlerContext } from '../../context.ts';
|
|
28
|
+
import type { DaemonCredentialStore } from '../../credentials.ts';
|
|
29
|
+
import { HandlerError, REQUIRE_CONFIRM } from '../../errors.ts';
|
|
30
|
+
import { labelToTag } from '../scorer.ts';
|
|
31
|
+
import { applyImap, imapStoreFlagOverTls, makeRetryingImapStoreFlag } from './imap.ts';
|
|
32
|
+
import type { ImapRetryOptions, ImapStoreFlag } from './imap.ts';
|
|
33
|
+
import { applySlack } from './slack.ts';
|
|
34
|
+
import { applyDiscord } from './discord.ts';
|
|
35
|
+
import type { ApplyTagsRequest, ApplyTagsResult, TaggerProviderConfig } from './shared.ts';
|
|
36
|
+
|
|
37
|
+
export type {
|
|
38
|
+
ApplyTagsRequest,
|
|
39
|
+
ApplyTagsResult,
|
|
40
|
+
TaggerProviderConfig,
|
|
41
|
+
} from './shared.ts';
|
|
42
|
+
export type { ImapRetryOptions, ImapStoreArgs, ImapStoreFlag } from './imap.ts';
|
|
43
|
+
|
|
44
|
+
export const TRIAGE_AUTOTAG_FLAG = 'surfaces.triage.autoTag';
|
|
45
|
+
|
|
46
|
+
export interface TriageTaggerOptions {
|
|
47
|
+
credentials?: DaemonCredentialStore;
|
|
48
|
+
/** Override the autotag flag lookup (used in tests). */
|
|
49
|
+
autoTagEnabled?: boolean;
|
|
50
|
+
/** Per-surface provider config; usually derived from configManager. */
|
|
51
|
+
providers?: TaggerProviderConfig;
|
|
52
|
+
/** Injectable fetch (Slack/Discord HTTP). Defaults to global fetch. */
|
|
53
|
+
fetchImpl?: typeof fetch;
|
|
54
|
+
/** Injectable IMAP flag-setter (used in tests to avoid a live socket). */
|
|
55
|
+
imapStoreFlag?: ImapStoreFlag;
|
|
56
|
+
/**
|
|
57
|
+
* Transient-failure retry policy for the default IMAP store implementation.
|
|
58
|
+
* Ignored when imapStoreFlag is injected and succeeds first-try.
|
|
59
|
+
*/
|
|
60
|
+
imapRetry?: ImapRetryOptions;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface TriageTagger {
|
|
64
|
+
/** Whether provider-side tagging is currently enabled. */
|
|
65
|
+
enabled(): boolean;
|
|
66
|
+
applyTags(request: ApplyTagsRequest): Promise<ApplyTagsResult>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function readBoolFlag(
|
|
70
|
+
configManager: HandlerContext['configManager'],
|
|
71
|
+
key: string,
|
|
72
|
+
): boolean {
|
|
73
|
+
try {
|
|
74
|
+
const value = configManager.get(key as never) as unknown;
|
|
75
|
+
return value === true || value === 'true' || value === 1;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function safeGet(configManager: HandlerContext['configManager'], key: string): unknown {
|
|
82
|
+
try {
|
|
83
|
+
return configManager.get(key as never) as unknown;
|
|
84
|
+
} catch {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function resolveProvidersFromConfig(
|
|
90
|
+
configManager: HandlerContext['configManager'],
|
|
91
|
+
): TaggerProviderConfig {
|
|
92
|
+
const out: TaggerProviderConfig = {};
|
|
93
|
+
const slackToken = safeGet(configManager, 'surfaces.slack.botToken');
|
|
94
|
+
if (typeof slackToken === 'string' && slackToken.length > 0) {
|
|
95
|
+
out.slack = { tokenConfigKey: 'surfaces.slack.botToken' };
|
|
96
|
+
}
|
|
97
|
+
const discordToken = safeGet(configManager, 'surfaces.discord.botToken');
|
|
98
|
+
if (typeof discordToken === 'string' && discordToken.length > 0) {
|
|
99
|
+
out.discord = { tokenConfigKey: 'surfaces.discord.botToken' };
|
|
100
|
+
}
|
|
101
|
+
const imapHost = safeGet(configManager, 'surfaces.email.imap.host');
|
|
102
|
+
const imapUser = safeGet(configManager, 'surfaces.email.imap.user');
|
|
103
|
+
if (typeof imapHost === 'string' && imapHost.length > 0 && typeof imapUser === 'string') {
|
|
104
|
+
const portRaw = safeGet(configManager, 'surfaces.email.imap.port');
|
|
105
|
+
const mailbox = safeGet(configManager, 'surfaces.email.imap.mailbox');
|
|
106
|
+
out.imap = {
|
|
107
|
+
host: imapHost,
|
|
108
|
+
port: typeof portRaw === 'number' ? portRaw : 993,
|
|
109
|
+
user: imapUser,
|
|
110
|
+
passwordConfigKey: 'surfaces.email.imap.password',
|
|
111
|
+
mailbox: typeof mailbox === 'string' && mailbox.length > 0 ? mailbox : 'INBOX',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function resolveTags(request: ApplyTagsRequest): string[] {
|
|
118
|
+
if (request.tags && request.tags.length > 0) {
|
|
119
|
+
return [...new Set(request.tags.map((t) => t.trim()).filter((t) => t.length > 0))];
|
|
120
|
+
}
|
|
121
|
+
if (request.label) return [labelToTag(request.label)];
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Create the triage tagger. Reads the autotag flag and provider config from the
|
|
127
|
+
* handler context; credentials are resolved lazily, per apply, from the daemon
|
|
128
|
+
* credential store.
|
|
129
|
+
*/
|
|
130
|
+
export function createTriageTagger(
|
|
131
|
+
ctx: HandlerContext,
|
|
132
|
+
options: TriageTaggerOptions = {},
|
|
133
|
+
): TriageTagger {
|
|
134
|
+
const credentials = options.credentials ?? ctx.credentials;
|
|
135
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
136
|
+
const providers = options.providers ?? resolveProvidersFromConfig(ctx.configManager);
|
|
137
|
+
// Retry wraps whichever store impl is in use (default TLS client OR an
|
|
138
|
+
// injected one), so transient failures are retried uniformly.
|
|
139
|
+
const imapStoreFlag = makeRetryingImapStoreFlag(
|
|
140
|
+
options.imapStoreFlag ?? imapStoreFlagOverTls,
|
|
141
|
+
options.imapRetry,
|
|
142
|
+
);
|
|
143
|
+
const enabled = (): boolean =>
|
|
144
|
+
options.autoTagEnabled ?? readBoolFlag(ctx.configManager, TRIAGE_AUTOTAG_FLAG);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
enabled,
|
|
148
|
+
async applyTags(request: ApplyTagsRequest): Promise<ApplyTagsResult> {
|
|
149
|
+
const { item } = request;
|
|
150
|
+
const tags = resolveTags(request);
|
|
151
|
+
const base: ApplyTagsResult = {
|
|
152
|
+
surface: item.surface,
|
|
153
|
+
itemId: item.id,
|
|
154
|
+
appliedTags: [],
|
|
155
|
+
skipped: true,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (!enabled()) {
|
|
159
|
+
return { ...base, reason: 'autotag-disabled' };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Provider-side mutation is effectful — require explicit confirmation.
|
|
163
|
+
if (request.confirm !== true || request.explicitUserRequest !== true) {
|
|
164
|
+
throw new HandlerError(
|
|
165
|
+
'Provider-side triage tagging requires explicit user confirmation.',
|
|
166
|
+
REQUIRE_CONFIRM,
|
|
167
|
+
403,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
switch (item.surface) {
|
|
172
|
+
case 'email':
|
|
173
|
+
case 'imap':
|
|
174
|
+
return applyImap(item, tags, providers, credentials, imapStoreFlag, base);
|
|
175
|
+
case 'slack':
|
|
176
|
+
return applySlack(item, tags, providers, credentials, fetchImpl, ctx, base);
|
|
177
|
+
case 'discord':
|
|
178
|
+
return applyDiscord(item, tags, providers, credentials, fetchImpl, ctx, base);
|
|
179
|
+
default:
|
|
180
|
+
return { ...base, reason: `unsupported-surface:${item.surface}` };
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Triage tagger — shared types and provider-agnostic helpers.
|
|
3
|
+
//
|
|
4
|
+
// Provider config shapes, the apply request/result contract, and the tag
|
|
5
|
+
// normalization helpers used by the IMAP/Slack/Discord modules. No I/O here.
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
import type { InboundChannelItem, TriageLabel } from '../types.ts';
|
|
9
|
+
|
|
10
|
+
export interface TaggerProviderConfig {
|
|
11
|
+
/** IMAP host:port (default port 993, TLS). Credentials resolved separately. */
|
|
12
|
+
imap?: { host: string; port?: number; user: string; passwordConfigKey: string; mailbox?: string };
|
|
13
|
+
/** Slack bot token config key (resolved from credential store). */
|
|
14
|
+
slack?: { tokenConfigKey: string };
|
|
15
|
+
/**
|
|
16
|
+
* Discord bot token config key (resolved from credential store), plus an
|
|
17
|
+
* optional forum-tag mapping. When `forumTagIds` maps a GoodVibes triage tag
|
|
18
|
+
* (e.g. 'GoodVibes/Spam') to a forum tag SNOWFLAKE id, items that target a
|
|
19
|
+
* forum/media-channel thread get that REAL thread tag applied (PATCH
|
|
20
|
+
* applied_tags) — exact fidelity to the contract's "Discord thread tags".
|
|
21
|
+
* Without a mapping (or for non-thread messages) tagging degrades to a
|
|
22
|
+
* unicode reaction analog.
|
|
23
|
+
*/
|
|
24
|
+
discord?: { tokenConfigKey: string; forumTagIds?: Record<string, string> };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ApplyTagsRequest {
|
|
28
|
+
item: InboundChannelItem;
|
|
29
|
+
/** Provider-side tags to apply. Defaults to [labelToTag(label)] when omitted. */
|
|
30
|
+
tags?: readonly string[];
|
|
31
|
+
label?: TriageLabel;
|
|
32
|
+
/** Must be true — provider-side mutation requires explicit confirmation. */
|
|
33
|
+
confirm?: boolean;
|
|
34
|
+
/** Mirror of the operator invocation context flag. */
|
|
35
|
+
explicitUserRequest?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ApplyTagsResult {
|
|
39
|
+
surface: string;
|
|
40
|
+
itemId: string;
|
|
41
|
+
appliedTags: string[];
|
|
42
|
+
/** True when the autotag flag is disabled or no provider matched. */
|
|
43
|
+
skipped: boolean;
|
|
44
|
+
reason?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** IMAP keywords cannot contain spaces or '/'; normalize the canonical tag. */
|
|
48
|
+
export function imapKeywordForTag(tag: string): string {
|
|
49
|
+
return tag.replace(/[^A-Za-z0-9_]+/g, '_');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function slackEmojiForTag(tag: string): string {
|
|
53
|
+
const lower = tag.toLowerCase();
|
|
54
|
+
if (lower.includes('spam')) return 'no_entry_sign';
|
|
55
|
+
if (lower.includes('priority')) return 'rotating_light';
|
|
56
|
+
return 'inbox_tray';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function discordEmojiForTag(tag: string): string {
|
|
60
|
+
const lower = tag.toLowerCase();
|
|
61
|
+
if (lower.includes('spam')) return '\u{1F6AB}';
|
|
62
|
+
if (lower.includes('priority')) return '\u{1F6A8}';
|
|
63
|
+
return '\u{1F4E5}';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Read a non-empty string from the item's opaque metadata bag. */
|
|
67
|
+
export function stringMeta(item: InboundChannelItem, key: string): string | undefined {
|
|
68
|
+
const value = item.metadata?.[key];
|
|
69
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
70
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Triage tagger — Slack provider.
|
|
3
|
+
//
|
|
4
|
+
// Applies a triage label as a Slack message reaction (reactions.add). The bot
|
|
5
|
+
// token is resolved per-apply from the daemon credential store and is never
|
|
6
|
+
// logged or returned. `already_reacted` is treated as idempotent success.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
import type { HandlerContext } from '../../context.ts';
|
|
10
|
+
import type { DaemonCredentialStore } from '../../credentials.ts';
|
|
11
|
+
import { HandlerError } from '../../errors.ts';
|
|
12
|
+
import type { InboundChannelItem } from '../types.ts';
|
|
13
|
+
import type { ApplyTagsResult, TaggerProviderConfig } from './shared.ts';
|
|
14
|
+
import { slackEmojiForTag, stringMeta } from './shared.ts';
|
|
15
|
+
|
|
16
|
+
export async function applySlack(
|
|
17
|
+
item: InboundChannelItem,
|
|
18
|
+
tags: string[],
|
|
19
|
+
providers: TaggerProviderConfig,
|
|
20
|
+
credentials: DaemonCredentialStore,
|
|
21
|
+
fetchImpl: typeof fetch,
|
|
22
|
+
ctx: HandlerContext,
|
|
23
|
+
base: ApplyTagsResult,
|
|
24
|
+
): Promise<ApplyTagsResult> {
|
|
25
|
+
const cfg = providers.slack;
|
|
26
|
+
if (!cfg) return { ...base, reason: 'slack-not-configured' };
|
|
27
|
+
const channel = stringMeta(item, 'channelId') ?? item.conversationId;
|
|
28
|
+
const ts = stringMeta(item, 'ts') ?? stringMeta(item, 'messageTs');
|
|
29
|
+
if (!channel || !ts) return { ...base, reason: 'slack-missing-target' };
|
|
30
|
+
if (tags.length === 0) return { ...base, reason: 'no-tags' };
|
|
31
|
+
|
|
32
|
+
const token = await credentials.resolveConfigSecret(cfg.tokenConfigKey);
|
|
33
|
+
if (!token) return { ...base, reason: 'slack-no-credentials' };
|
|
34
|
+
|
|
35
|
+
const applied: string[] = [];
|
|
36
|
+
for (const tag of tags) {
|
|
37
|
+
const emoji = slackEmojiForTag(tag);
|
|
38
|
+
const response = await fetchImpl('https://slack.com/api/reactions.add', {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: {
|
|
41
|
+
Authorization: `Bearer ${token}`,
|
|
42
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify({ channel, timestamp: ts, name: emoji }),
|
|
45
|
+
});
|
|
46
|
+
const payload = (await response.json().catch(() => ({}))) as {
|
|
47
|
+
ok?: boolean;
|
|
48
|
+
error?: string;
|
|
49
|
+
};
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
throw new HandlerError(
|
|
52
|
+
`Slack reactions.add HTTP ${response.status}`,
|
|
53
|
+
'TRIAGE_SLACK_TAG_FAILED',
|
|
54
|
+
502,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
// 'already_reacted' is an idempotent success for our purposes.
|
|
58
|
+
if (payload.ok !== true && payload.error !== 'already_reacted') {
|
|
59
|
+
ctx.logger.warn('triage: slack reaction rejected', { error: payload.error });
|
|
60
|
+
throw new HandlerError(
|
|
61
|
+
`Slack reactions.add rejected: ${payload.error ?? 'unknown'}`,
|
|
62
|
+
'TRIAGE_SLACK_TAG_FAILED',
|
|
63
|
+
502,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
applied.push(tag);
|
|
67
|
+
}
|
|
68
|
+
return { surface: item.surface, itemId: item.id, appliedTags: applied, skipped: false };
|
|
69
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Daemon-internal triage domain types.
|
|
3
|
+
//
|
|
4
|
+
// `InboundChannelItem` is the daemon-internal shape the inbox poller produces
|
|
5
|
+
// and the triage pipeline scores. It is NOT an SDK catalog contract: the
|
|
6
|
+
// published `channels.inbox.list` output schema (CHANNEL_INBOX_ITEM_SCHEMA) is
|
|
7
|
+
// owned by the SDK and never re-declared here. This is the internal poller
|
|
8
|
+
// item per the handoff doc — it carries `fromDigest` (never a raw sender id),
|
|
9
|
+
// `subjectPreview`/`bodyPreview` (PII-stripped, length-bounded) and an opaque
|
|
10
|
+
// `metadata` bag the tagger reads provider targeting from (imapUid, channelId,
|
|
11
|
+
// Slack ts, Discord messageId/threadId).
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
/** Triage label assigned by the scorer. */
|
|
15
|
+
export type TriageLabel = 'spam' | 'priority' | 'normal';
|
|
16
|
+
|
|
17
|
+
/** 1:1 vs group/channel/thread conversation hint (priority signal). */
|
|
18
|
+
export type ConversationKind = 'direct' | 'group' | 'channel' | 'thread' | 'service';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Internal inbound feed item. Mirrors the handoff `InboundChannelItem` shape
|
|
22
|
+
* plus the optional fields the scorer/tagger consult. `surface` is the provider
|
|
23
|
+
* family ('email' | 'imap' | 'slack' | 'discord' | ...) the tagger dispatches
|
|
24
|
+
* on; `provider` is the handoff-facing provider id. They are usually equal.
|
|
25
|
+
*/
|
|
26
|
+
export interface InboundChannelItem {
|
|
27
|
+
/** Stable, provider-scoped dedup key. */
|
|
28
|
+
readonly id: string;
|
|
29
|
+
/** Provider family the tagger dispatches on (email/imap/slack/discord/...). */
|
|
30
|
+
readonly surface: string;
|
|
31
|
+
/** Handoff-facing provider id ("slack" | "discord" | "email" | ...). */
|
|
32
|
+
readonly provider?: string;
|
|
33
|
+
readonly kind?: 'dm' | 'thread' | 'mention' | 'reaction';
|
|
34
|
+
/** SHA-256 first-N of sender external id — NEVER a raw identifier. */
|
|
35
|
+
readonly fromDigest?: string;
|
|
36
|
+
/** Conversation id (Slack/Discord channel, IMAP mailbox-scoped). */
|
|
37
|
+
readonly conversationId?: string;
|
|
38
|
+
readonly conversationKind?: ConversationKind;
|
|
39
|
+
/** Display subject (<= 200 chars). */
|
|
40
|
+
readonly subject?: string;
|
|
41
|
+
/** Display body preview (<= 500 chars, PII-stripped). Alias: bodyPreview. */
|
|
42
|
+
readonly snippet?: string;
|
|
43
|
+
/** Optional daemon route binding id. */
|
|
44
|
+
readonly routeId?: string;
|
|
45
|
+
/** Unix ms. */
|
|
46
|
+
readonly receivedAt?: number;
|
|
47
|
+
readonly unread?: boolean;
|
|
48
|
+
/** Opaque provider targeting bag (imapUid/uid, channelId, ts, messageId, threadId). */
|
|
49
|
+
readonly metadata?: Record<string, unknown>;
|
|
50
|
+
}
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
// the URL can view it).
|
|
17
17
|
// ---------------------------------------------------------------------------
|
|
18
18
|
|
|
19
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
20
|
+
|
|
19
21
|
export type UploadResult =
|
|
20
22
|
| { ok: true; url: string }
|
|
21
23
|
| { ok: false; error: string };
|
|
@@ -96,7 +98,7 @@ export class GistUploadTarget implements UploadTarget {
|
|
|
96
98
|
body,
|
|
97
99
|
});
|
|
98
100
|
} catch (fetchErr: unknown) {
|
|
99
|
-
const msg =
|
|
101
|
+
const msg = summarizeError(fetchErr);
|
|
100
102
|
return { ok: false, error: `Network error: ${msg}` };
|
|
101
103
|
}
|
|
102
104
|
|
|
@@ -139,6 +139,7 @@ export interface CommandSessionServices {
|
|
|
139
139
|
readonly sessionManager?: import('@pellux/goodvibes-sdk/platform/sessions').SessionManager;
|
|
140
140
|
readonly sessionMemoryStore?: import('@pellux/goodvibes-sdk/platform/core').SessionMemoryStore;
|
|
141
141
|
readonly sessionLineageTracker?: import('@pellux/goodvibes-sdk/platform/core').SessionLineageTracker;
|
|
142
|
+
readonly wrfcController?: import('@pellux/goodvibes-sdk/platform/agents').WrfcController;
|
|
142
143
|
readonly changeTracker?: import('@pellux/goodvibes-sdk/platform/sessions').SessionChangeTracker;
|
|
143
144
|
}
|
|
144
145
|
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from '../../runtime/cloudflare-control-plane.ts';
|
|
12
12
|
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
13
13
|
import { requireShellPaths } from './runtime-services.ts';
|
|
14
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
14
15
|
|
|
15
16
|
interface ParsedCloudflareArgs {
|
|
16
17
|
readonly positional: readonly string[];
|
|
@@ -366,5 +367,5 @@ function formatCloudflareError(error: unknown): string {
|
|
|
366
367
|
if (error instanceof CloudflareDaemonRouteError) {
|
|
367
368
|
return `${error.message} (HTTP ${error.status}, ${error.code})`;
|
|
368
369
|
}
|
|
369
|
-
return
|
|
370
|
+
return summarizeError(error);
|
|
370
371
|
}
|
|
@@ -2,7 +2,7 @@ import { estimateConversationTokens } from '@pellux/goodvibes-sdk/platform/core'
|
|
|
2
2
|
import { evaluateSessionMaintenance, formatSessionMaintenanceLines, getGuidanceMode } from '@/runtime/index.ts';
|
|
3
3
|
import { dismissGuidance, evaluateContextualGuidance, formatGuidanceItems, resetGuidance } from '@/runtime/index.ts';
|
|
4
4
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
5
|
-
import {
|
|
5
|
+
import { requireReadModels, requireSessionMemoryStore, requireShellPaths } from './runtime-services.ts';
|
|
6
6
|
|
|
7
7
|
export function registerGuidanceRuntimeCommands(registry: CommandRegistry): void {
|
|
8
8
|
registry.register({
|
|
@@ -68,8 +68,6 @@ export function registerGuidanceRuntimeCommands(registry: CommandRegistry): void
|
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
const providerApi = requireProviderApi(ctx);
|
|
72
|
-
const currentModel = await providerApi.getCurrentModel().catch(() => null); // best-effort: null handled as unknown context window
|
|
73
71
|
const llmMessages = ctx.session.conversationManager.getMessagesForLLM();
|
|
74
72
|
const readModels = requireReadModels(ctx);
|
|
75
73
|
const session = readModels.session.getSnapshot();
|
|
@@ -80,7 +78,7 @@ export function registerGuidanceRuntimeCommands(registry: CommandRegistry): void
|
|
|
80
78
|
const maintenance = evaluateSessionMaintenance({
|
|
81
79
|
configManager: ctx.platform.configManager,
|
|
82
80
|
currentTokens: estimateConversationTokens(llmMessages),
|
|
83
|
-
contextWindow:
|
|
81
|
+
contextWindow: ctx.provider.providerRegistry.getContextWindowForModel(ctx.provider.providerRegistry.getCurrentModel()),
|
|
84
82
|
messageCount: llmMessages.length,
|
|
85
83
|
sessionMemoryCount: requireSessionMemoryStore(ctx).list().length,
|
|
86
84
|
session: session.session,
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
openCommandPanel,
|
|
14
14
|
requireLocalUserAuthManager,
|
|
15
15
|
requireOperatorClient,
|
|
16
|
-
requireProviderApi,
|
|
17
16
|
requireReadModels,
|
|
18
17
|
requireSecretsManager,
|
|
19
18
|
requireServiceRegistry,
|
|
@@ -240,15 +239,19 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
240
239
|
|
|
241
240
|
if (sub === 'maintenance') {
|
|
242
241
|
const session = readModels.session.getSnapshot();
|
|
243
|
-
const
|
|
244
|
-
|
|
242
|
+
const providerRegistry = ctx.provider.providerRegistry;
|
|
243
|
+
// Resolve the context window the same way the Tokens panel does so the
|
|
244
|
+
// maintenance usage %/remaining agree across every diagnostics surface.
|
|
245
|
+
const contextWindow = providerRegistry.getContextWindowForModel(
|
|
246
|
+
providerRegistry.getCurrentModel(),
|
|
247
|
+
);
|
|
245
248
|
const llmMessages = typeof ctx.session.conversationManager.getMessagesForLLM === 'function'
|
|
246
249
|
? ctx.session.conversationManager.getMessagesForLLM()
|
|
247
250
|
: [];
|
|
248
251
|
const maintenance = evaluateSessionMaintenance({
|
|
249
252
|
configManager: ctx.platform.configManager,
|
|
250
253
|
currentTokens: estimateConversationTokens(llmMessages),
|
|
251
|
-
contextWindow
|
|
254
|
+
contextWindow,
|
|
252
255
|
messageCount: llmMessages.length,
|
|
253
256
|
sessionMemoryCount: requireSessionMemoryStore(ctx).list().length,
|
|
254
257
|
session: session.session,
|
|
@@ -386,12 +389,15 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
386
389
|
}
|
|
387
390
|
|
|
388
391
|
const session = readModels.session.getSnapshot();
|
|
389
|
-
const
|
|
390
|
-
const currentModel = await providerApi.getCurrentModel().catch(() => null); // best-effort: null handled as unknown context window
|
|
392
|
+
const providerRegistry = ctx.provider.providerRegistry;
|
|
391
393
|
const llmMessages = typeof ctx.session.conversationManager.getMessagesForLLM === 'function'
|
|
392
394
|
? ctx.session.conversationManager.getMessagesForLLM()
|
|
393
395
|
: [];
|
|
394
|
-
|
|
396
|
+
// Resolve the context window the same way the Tokens panel does so the
|
|
397
|
+
// maintenance usage %/remaining agree across every diagnostics surface.
|
|
398
|
+
const contextWindow = providerRegistry.getContextWindowForModel(
|
|
399
|
+
providerRegistry.getCurrentModel(),
|
|
400
|
+
);
|
|
395
401
|
const maintenance = evaluateSessionMaintenance({
|
|
396
402
|
configManager: ctx.platform.configManager,
|
|
397
403
|
currentTokens: estimateConversationTokens(llmMessages),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { KnowledgeService } from '@pellux/goodvibes-sdk/platform/knowledge';
|
|
2
2
|
import type { CommandContext, SlashCommand } from '../command-registry.ts';
|
|
3
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
3
4
|
|
|
4
5
|
const KNOWLEDGE_REVIEW_ACTIONS = ['accept', 'reject', 'resolve', 'reopen', 'edit', 'forget'] as const;
|
|
5
6
|
|
|
@@ -397,7 +398,7 @@ export const knowledgeCommand: SlashCommand = {
|
|
|
397
398
|
...(result.appliedFacts ? [` applied facts: ${Object.keys(result.appliedFacts).join(', ') || 'none'}`] : []),
|
|
398
399
|
].join('\n'));
|
|
399
400
|
} catch (error) {
|
|
400
|
-
context.print(`[knowledge] ${
|
|
401
|
+
context.print(`[knowledge] ${summarizeError(error)}`);
|
|
401
402
|
}
|
|
402
403
|
break;
|
|
403
404
|
}
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
CommandSessionServices,
|
|
8
8
|
CommandWorkspaceServices,
|
|
9
9
|
} from '../command-registry.ts';
|
|
10
|
-
import { getLastCompactionEvent } from '@pellux/goodvibes-sdk/platform/core';
|
|
10
|
+
import { compactSmallWindow, getLastCompactionEvent, SMALL_WINDOW_THRESHOLD } from '@pellux/goodvibes-sdk/platform/core';
|
|
11
11
|
import type { CompactionContext, CompactionEvent } from '@pellux/goodvibes-sdk/platform/core';
|
|
12
12
|
import type { UiReadModels } from '../../runtime/ui-read-models.ts';
|
|
13
13
|
import type { ShellPathService } from '@/runtime/index.ts';
|
|
@@ -244,23 +244,53 @@ export function requireProviderApi(context: CommandContext): ProviderApi {
|
|
|
244
244
|
* change).
|
|
245
245
|
*/
|
|
246
246
|
export async function compactConversation(context: CommandContext): Promise<CompactionEvent | null> {
|
|
247
|
+
const conversationManager = context.session.conversationManager;
|
|
248
|
+
const providerRegistry = context.provider.providerRegistry;
|
|
249
|
+
// Resolve the live context window for the current model so manual /compact
|
|
250
|
+
// matches the auto-compaction path (which scales behaviour by window size)
|
|
251
|
+
// instead of passing a meaningless 0.
|
|
252
|
+
const contextWindow = providerRegistry.getContextWindowForModel(
|
|
253
|
+
providerRegistry.getCurrentModel(),
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// Small-window models lack room for the structured (multi-LLM) extraction
|
|
257
|
+
// pipeline, so mirror the SDK auto-compaction path and degrade to the
|
|
258
|
+
// simplified "keep the most recent messages" compaction.
|
|
259
|
+
if (contextWindow > 0 && contextWindow < SMALL_WINDOW_THRESHOLD) {
|
|
260
|
+
const compacted = compactSmallWindow(conversationManager.getMessagesForLLM(), 10);
|
|
261
|
+
conversationManager.replaceMessagesForLLM(compacted);
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
247
265
|
const eventBefore = getLastCompactionEvent();
|
|
248
266
|
const sessionMemories = context.session.sessionMemoryStore?.list() ?? [];
|
|
267
|
+
const lineageTracker = context.session.sessionLineageTracker;
|
|
268
|
+
const agentManager = context.ops.agentManager;
|
|
269
|
+
const planManager = context.ops.planManager;
|
|
249
270
|
const compactionCtx: CompactionContext = {
|
|
250
|
-
messages:
|
|
271
|
+
messages: conversationManager.getMessagesForLLM(),
|
|
251
272
|
sessionMemories,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
273
|
+
// Mirror buildAutoCompactionContext: pass the running/pending agents, the
|
|
274
|
+
// active execution plan, and the session-lineage log so the handoff summary
|
|
275
|
+
// and lineage section are populated correctly (not "compaction #0").
|
|
276
|
+
agents:
|
|
277
|
+
agentManager
|
|
278
|
+
?.exportState()
|
|
279
|
+
.filter((agent) => agent.status === 'running' || agent.status === 'pending') ?? [],
|
|
280
|
+
// Mirror buildAutoCompactionContext: include the live WRFC chains (wired
|
|
281
|
+
// through context.session.wrfcController) so the handoff summary's
|
|
282
|
+
// orchestration section matches the SDK auto-compaction path.
|
|
283
|
+
wrfcChains: context.session.wrfcController?.listChains() ?? [],
|
|
284
|
+
activePlan: planManager?.getActive(context.session.runtime.sessionId) ?? null,
|
|
285
|
+
lineageEntries: lineageTracker?.getEntries() ?? [],
|
|
286
|
+
compactionCount: lineageTracker?.getCompactionCount() ?? 0,
|
|
287
|
+
contextWindow,
|
|
258
288
|
trigger: 'manual',
|
|
259
289
|
extractionModelId: context.session.runtime.model,
|
|
260
290
|
extractionProvider: context.session.runtime.provider,
|
|
261
291
|
};
|
|
262
|
-
await
|
|
263
|
-
|
|
292
|
+
await conversationManager.compact(
|
|
293
|
+
providerRegistry,
|
|
264
294
|
context.session.runtime.model,
|
|
265
295
|
'manual',
|
|
266
296
|
context.session.runtime.provider,
|
|
@@ -117,7 +117,7 @@ export function handleCommandModeToken(state: CommandModeRouteState, token: Inpu
|
|
|
117
117
|
return true;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
return token.logicalName !== 'left' && token.logicalName !== 'right';
|
|
120
|
+
return token.logicalName !== 'left' && token.logicalName !== 'right' && token.logicalName !== 'home' && token.logicalName !== 'end';
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
function withPanelFocusSync(context: CommandContext, state: CommandModeRouteState): CommandContext {
|
|
@@ -5,6 +5,7 @@ import { cleanupMarkerRegistry, expandPrompt, findMarkerAtPos, handleBlockCopy,
|
|
|
5
5
|
import { clearModalStack, handleEscape, modalOpened } from './handler-modal-stack.ts';
|
|
6
6
|
import { openOnboardingWizardState, type OpenOnboardingWizardOptions } from './handler-ui-state.ts';
|
|
7
7
|
import type { InputHandlerLike as InputHandler } from './handler-types.ts';
|
|
8
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
8
9
|
|
|
9
10
|
export function openOnboardingWizardForHandler(
|
|
10
11
|
handler: InputHandler,
|
|
@@ -49,7 +50,7 @@ export async function hydrateOnboardingWizardFromRuntimeForHandler(handler: Inpu
|
|
|
49
50
|
handler.requestRender();
|
|
50
51
|
} catch (error) {
|
|
51
52
|
if (!handler.onboardingWizard.active || hydrationSerial !== handler.onboardingHydrationSerial) return;
|
|
52
|
-
const message =
|
|
53
|
+
const message = summarizeError(error);
|
|
53
54
|
handler.onboardingWizard.failRuntimeHydration(message);
|
|
54
55
|
handler.commandContext?.print?.(`Onboarding runtime snapshot failed: ${message}`);
|
|
55
56
|
handler.requestRender();
|
|
@@ -3,6 +3,7 @@ import type { SelectionResult, SelectionAction } from './selection-modal.ts';
|
|
|
3
3
|
import type { CommandContext } from './command-registry.ts';
|
|
4
4
|
import { openTtsProviderPicker, openTtsVoicePicker } from './tts-settings-actions.ts';
|
|
5
5
|
import { isTextBackspace } from './delete-key-policy.ts';
|
|
6
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
6
7
|
|
|
7
8
|
type SelectionRouteState = {
|
|
8
9
|
selectionModal: {
|
|
@@ -285,7 +286,7 @@ function consumeSettingsPickerRequest(state: SettingsRouteState): void {
|
|
|
285
286
|
return;
|
|
286
287
|
}
|
|
287
288
|
void openTtsVoicePicker(state.commandContext).catch((error: unknown) => {
|
|
288
|
-
state.commandContext?.print(`Unable to list TTS voices: ${
|
|
289
|
+
state.commandContext?.print(`Unable to list TTS voices: ${summarizeError(error)}`);
|
|
289
290
|
state.requestRender();
|
|
290
291
|
});
|
|
291
292
|
return;
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
getCloudflareSetupSource,
|
|
23
23
|
shouldShowCloudflareStep,
|
|
24
24
|
} from './onboarding/onboarding-wizard-cloudflare.ts';
|
|
25
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
25
26
|
|
|
26
27
|
type CloudflareOnboardingAction = Extract<OnboardingWizardAction,
|
|
27
28
|
| 'cloudflare-token-requirements'
|
|
@@ -44,7 +45,7 @@ function normalizeCloudflareActionError(error: unknown): string {
|
|
|
44
45
|
if (error instanceof CloudflareDaemonRouteError) {
|
|
45
46
|
return `${error.message} (HTTP ${error.status}, ${error.code})`;
|
|
46
47
|
}
|
|
47
|
-
return
|
|
48
|
+
return summarizeError(error);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
function setCloudflareWizardStatusForHandler(
|