@pellux/goodvibes-tui 0.25.0 → 0.26.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 +5 -3
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +2419 -1040
- package/package.json +2 -2
- package/src/daemon/{calendar → handlers/calendar}/caldav-client.ts +28 -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 +210 -0
- package/src/daemon/{channels → handlers}/inbox/mapping.ts +8 -6
- package/src/daemon/{channels → handlers}/inbox/poller.ts +4 -4
- package/src/daemon/{channels → handlers}/inbox/provider-adapter.ts +14 -10
- package/src/daemon/{channels → handlers}/inbox/providers/discord.ts +8 -10
- package/src/daemon/{channels → handlers}/inbox/providers/imap-client.ts +1 -1
- package/src/daemon/{channels → handlers}/inbox/providers/route-util.ts +2 -2
- package/src/daemon/{channels → handlers}/inbox/providers/slack.ts +9 -11
- 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 +212 -0
- package/src/daemon/{triage → handlers/triage}/pipeline.ts +58 -70
- package/src/daemon/{triage → handlers/triage}/scorer.ts +21 -21
- package/src/daemon/handlers/triage/tagger/discord.ts +186 -0
- package/src/daemon/handlers/triage/tagger/imap.ts +383 -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/runtime/services.ts +48 -35
- package/src/version.ts +1 -1
- 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/{channels → handlers}/inbox/providers/email.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
|
+
}
|
package/src/runtime/services.ts
CHANGED
|
@@ -7,15 +7,6 @@ import { AutomationDeliveryManager, AutomationManager, AutomationRouteStore } fr
|
|
|
7
7
|
import { ChannelPluginRegistry, ChannelPolicyManager, RouteBindingManager, SurfaceRegistry } from '@pellux/goodvibes-sdk/platform/channels';
|
|
8
8
|
import { ChannelDeliveryRouter } from '@pellux/goodvibes-sdk/platform/channels';
|
|
9
9
|
import { ApprovalBroker, GatewayMethodCatalog, SharedSessionBroker } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
10
|
-
import {
|
|
11
|
-
registerDaemonOperatorSurfaces,
|
|
12
|
-
type DaemonOperatorSurfaces,
|
|
13
|
-
} from '../daemon/operator/surfaces.ts';
|
|
14
|
-
import type {
|
|
15
|
-
OperatorContext,
|
|
16
|
-
OperatorLogger,
|
|
17
|
-
} from '../daemon/operator/index.ts';
|
|
18
|
-
import { attachRemoteInvokeRoute } from '../daemon/remote/index.ts';
|
|
19
10
|
import { WatcherRegistry } from '@pellux/goodvibes-sdk/platform/watchers';
|
|
20
11
|
import { ArtifactStore } from '@pellux/goodvibes-sdk/platform/artifacts';
|
|
21
12
|
import {
|
|
@@ -96,6 +87,19 @@ import {
|
|
|
96
87
|
type WorkflowServices,
|
|
97
88
|
} from '@pellux/goodvibes-sdk/platform/tools';
|
|
98
89
|
import { WorkPlanStore } from '../work-plans/work-plan-store.ts';
|
|
90
|
+
import {
|
|
91
|
+
registerDaemonHandlers,
|
|
92
|
+
type DaemonHandlerSurfaces,
|
|
93
|
+
} from '../daemon/handlers/index.ts';
|
|
94
|
+
import type { HandlerContext, HandlerLogger } from '../daemon/handlers/context.ts';
|
|
95
|
+
import { createDaemonCredentialStore } from '../daemon/handlers/credentials.ts';
|
|
96
|
+
import { registerRouting } from '../daemon/handlers/routing/index.ts';
|
|
97
|
+
import { registerInboxMethods } from '../daemon/handlers/inbox/index.ts';
|
|
98
|
+
import { registerTriagedInbox } from '../daemon/handlers/triage/index.ts';
|
|
99
|
+
import { registerDraftMethods } from '../daemon/handlers/drafts/index.ts';
|
|
100
|
+
import { registerCalendar } from '../daemon/handlers/calendar/index.ts';
|
|
101
|
+
import { registerEmailMethods } from '../daemon/handlers/email/index.ts';
|
|
102
|
+
import { registerRemoteSurface } from '../daemon/handlers/remote/index.ts';
|
|
99
103
|
|
|
100
104
|
const REGULAR_KNOWLEDGE_DB_FILE = 'knowledge-wiki.sqlite';
|
|
101
105
|
const HOME_GRAPH_KNOWLEDGE_DB_FILE = 'knowledge-home-graph.sqlite';
|
|
@@ -178,7 +182,6 @@ export interface RuntimeServices {
|
|
|
178
182
|
readonly deliveryManager: AutomationDeliveryManager;
|
|
179
183
|
readonly automationManager: AutomationManager;
|
|
180
184
|
readonly gatewayMethods: GatewayMethodCatalog;
|
|
181
|
-
readonly operatorSurfaces: DaemonOperatorSurfaces;
|
|
182
185
|
readonly artifactStore: ArtifactStore;
|
|
183
186
|
readonly knowledgeService: KnowledgeService;
|
|
184
187
|
readonly agentKnowledgeService: KnowledgeService;
|
|
@@ -225,6 +228,7 @@ export interface RuntimeServices {
|
|
|
225
228
|
readonly providerRegistry: ProviderRegistry;
|
|
226
229
|
readonly toolLLM: ToolLLM;
|
|
227
230
|
readonly distributedRuntime: DistributedRuntimeManager;
|
|
231
|
+
readonly daemonHandlers: DaemonHandlerSurfaces;
|
|
228
232
|
readonly remoteRunnerRegistry: RemoteRunnerRegistry;
|
|
229
233
|
readonly remoteSupervisor: RemoteSupervisor;
|
|
230
234
|
readonly sessionMemoryStore: SessionMemoryStore;
|
|
@@ -281,30 +285,6 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
281
285
|
globalHome: homeDirectory,
|
|
282
286
|
configManager,
|
|
283
287
|
});
|
|
284
|
-
// Wire the daemon operator surfaces (routing / triaged inbox / drafts /
|
|
285
|
-
// calendar / email / remote) against the shared gateway-method catalog. Each
|
|
286
|
-
// surface initializes its stores lazily, so this stays synchronous.
|
|
287
|
-
const operatorLogger: OperatorLogger = {
|
|
288
|
-
info: (msg, meta) => console.info(`[operator] ${msg}`, meta ?? ''),
|
|
289
|
-
warn: (msg, meta) => console.warn(`[operator] ${msg}`, meta ?? ''),
|
|
290
|
-
error: (msg, meta) => console.error(`[operator] ${msg}`, meta ?? ''),
|
|
291
|
-
};
|
|
292
|
-
const operatorContext: OperatorContext = {
|
|
293
|
-
catalog: gatewayMethods,
|
|
294
|
-
secrets: secretsManager,
|
|
295
|
-
configManager,
|
|
296
|
-
workingDirectory,
|
|
297
|
-
homeDirectory,
|
|
298
|
-
logger: operatorLogger,
|
|
299
|
-
};
|
|
300
|
-
const operatorSurfaces = registerDaemonOperatorSurfaces(operatorContext);
|
|
301
|
-
// Wire the remote.peers.invoke route to the remote dispatch adapter, replacing
|
|
302
|
-
// the SDK builtin stub. This is the single faithful integration point: the
|
|
303
|
-
// DaemonHttpRouter resolves POST /api/remote/peers/{peerId}/invoke to
|
|
304
|
-
// catalog.invoke('remote.peers.invoke', ...), which now reaches the
|
|
305
|
-
// Docker/SSH/cloud-terminal/local-process backends via the dispatch adapter
|
|
306
|
-
// (which enforces confirm:true + explicitUserRequest before dispatch).
|
|
307
|
-
attachRemoteInvokeRoute(gatewayMethods, operatorSurfaces.remoteDispatch);
|
|
308
288
|
const subscriptionManager = new SubscriptionManager(
|
|
309
289
|
shellPaths.resolveUserPath('tui', 'subscriptions.json'),
|
|
310
290
|
);
|
|
@@ -551,6 +531,39 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
551
531
|
approvalBridge: approvalBroker,
|
|
552
532
|
automationBridge: automationManager,
|
|
553
533
|
});
|
|
534
|
+
|
|
535
|
+
// Daemon handler surfaces: attach HOST handlers to the SDK-auto-registered
|
|
536
|
+
// builtin gateway descriptors (channels.* / email.* / calendar.*). The SDK
|
|
537
|
+
// owns every id/descriptor/schema; this only fills the handler slot via
|
|
538
|
+
// catalog.register(descriptor, handler, { replace: true }). Routing returns
|
|
539
|
+
// the resolver the inbox surface consumes; triage decorates channels.inbox.list.
|
|
540
|
+
// The remote surface reuses the SAME DistributedRuntimeManager the SDK facade
|
|
541
|
+
// injects, so its peer/work methods share one persistent store; remote.peers.*
|
|
542
|
+
// stay SDK-published routes (not catalog methods).
|
|
543
|
+
const handlerLogger: HandlerLogger = {
|
|
544
|
+
info: (message, meta) => console.info(message, meta ?? ''),
|
|
545
|
+
warn: (message, meta) => console.warn(message, meta ?? ''),
|
|
546
|
+
error: (message, meta) => console.error(message, meta ?? ''),
|
|
547
|
+
};
|
|
548
|
+
const handlerContext: HandlerContext = {
|
|
549
|
+
catalog: gatewayMethods,
|
|
550
|
+
credentials: createDaemonCredentialStore(secretsManager),
|
|
551
|
+
configManager,
|
|
552
|
+
workingDirectory,
|
|
553
|
+
homeDirectory,
|
|
554
|
+
logger: handlerLogger,
|
|
555
|
+
};
|
|
556
|
+
const daemonHandlers = registerDaemonHandlers(handlerContext, {
|
|
557
|
+
registerRouting,
|
|
558
|
+
registerInbox: (ctx, routing) =>
|
|
559
|
+
registerTriagedInbox(ctx, (inboxCtx) => registerInboxMethods(inboxCtx, routing))
|
|
560
|
+
.unregister,
|
|
561
|
+
registerDrafts: (ctx) => registerDraftMethods(ctx),
|
|
562
|
+
registerCalendar,
|
|
563
|
+
registerEmail: (ctx) => registerEmailMethods(ctx),
|
|
564
|
+
registerRemote: (ctx) => registerRemoteSurface(ctx, { manager: distributedRuntime }),
|
|
565
|
+
});
|
|
566
|
+
|
|
554
567
|
const remoteRunnerRegistry = new RemoteRunnerRegistry(agentManager);
|
|
555
568
|
const remoteSupervisor = new RemoteSupervisor(remoteRunnerRegistry);
|
|
556
569
|
const sandboxSessionRegistry = new SandboxSessionRegistry(workingDirectory);
|
|
@@ -657,7 +670,6 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
657
670
|
deliveryManager,
|
|
658
671
|
automationManager,
|
|
659
672
|
gatewayMethods,
|
|
660
|
-
operatorSurfaces,
|
|
661
673
|
artifactStore,
|
|
662
674
|
knowledgeService,
|
|
663
675
|
agentKnowledgeService,
|
|
@@ -704,6 +716,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
704
716
|
providerRegistry,
|
|
705
717
|
toolLLM,
|
|
706
718
|
distributedRuntime,
|
|
719
|
+
daemonHandlers,
|
|
707
720
|
remoteRunnerRegistry,
|
|
708
721
|
remoteSupervisor,
|
|
709
722
|
sessionMemoryStore,
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '0.
|
|
9
|
+
let _version = '0.26.0';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
|
|
12
12
|
_version = pkg.version ?? _version;
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
// Public surface barrel for the CalDAV calendar connector.
|
|
2
|
-
//
|
|
3
|
-
// Integration registers the surface by calling registerCalendarMethods(ctx).
|
|
4
|
-
// All other exports are types / helpers for consumers and tests.
|
|
5
|
-
|
|
6
|
-
export { registerCalendarMethods, CALENDAR_METHOD_IDS } from './register.ts';
|
|
7
|
-
export type {
|
|
8
|
-
RegisterCalendarOptions,
|
|
9
|
-
CalDavClientFactory,
|
|
10
|
-
CalendarEventDetail,
|
|
11
|
-
CalendarEventSummaryResult,
|
|
12
|
-
} from './register.ts';
|
|
13
|
-
|
|
14
|
-
export {
|
|
15
|
-
createCalDavClient,
|
|
16
|
-
resolveCalDavConfig,
|
|
17
|
-
toRelativeHref,
|
|
18
|
-
parseMultiStatus,
|
|
19
|
-
} from './caldav-client.ts';
|
|
20
|
-
export type {
|
|
21
|
-
CalDavClient,
|
|
22
|
-
CalDavConfig,
|
|
23
|
-
CalDavEvent,
|
|
24
|
-
CreateCalDavClientOptions,
|
|
25
|
-
CreateEventInput,
|
|
26
|
-
CreatedEvent,
|
|
27
|
-
ImportResult,
|
|
28
|
-
ExportResult,
|
|
29
|
-
ListEventsOptions,
|
|
30
|
-
FetchLike,
|
|
31
|
-
} from './caldav-client.ts';
|
|
32
|
-
|
|
33
|
-
export {
|
|
34
|
-
parseICS,
|
|
35
|
-
generateICS,
|
|
36
|
-
generateCalendar,
|
|
37
|
-
foldLine,
|
|
38
|
-
unfoldLines,
|
|
39
|
-
escapeText,
|
|
40
|
-
unescapeText,
|
|
41
|
-
formatICalDate,
|
|
42
|
-
formatICalDateTime,
|
|
43
|
-
parseICalDate,
|
|
44
|
-
attendeeDisplayName,
|
|
45
|
-
eventToVEvent,
|
|
46
|
-
extractUid,
|
|
47
|
-
} from './ics.ts';
|
|
48
|
-
export type {
|
|
49
|
-
ParsedICalEvent,
|
|
50
|
-
ICalAttendee,
|
|
51
|
-
GenerateICalInput,
|
|
52
|
-
} from './ics.ts';
|