@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.
Files changed (146) hide show
  1. package/CHANGELOG.md +22 -3
  2. package/README.md +12 -7
  3. package/docs/foundation-artifacts/operator-contract.json +2422 -1040
  4. package/package.json +2 -2
  5. package/src/cli/bundle-command.ts +2 -1
  6. package/src/cli/service-posture.ts +2 -1
  7. package/src/core/conversation-rendering.ts +2 -2
  8. package/src/core/conversation.ts +49 -2
  9. package/src/core/session-recovery.ts +24 -18
  10. package/src/core/system-message-router.ts +42 -26
  11. package/src/core/turn-event-wiring.ts +12 -16
  12. package/src/daemon/{calendar → handlers/calendar}/caldav-client.ts +29 -24
  13. package/src/daemon/handlers/calendar/index.ts +316 -0
  14. package/src/daemon/handlers/context.ts +29 -0
  15. package/src/daemon/handlers/contracts.ts +77 -0
  16. package/src/daemon/{channels → handlers}/drafts/draft-store.ts +114 -50
  17. package/src/daemon/handlers/drafts/index.ts +17 -0
  18. package/src/daemon/handlers/drafts/register.ts +331 -0
  19. package/src/daemon/handlers/email/config.ts +164 -0
  20. package/src/daemon/handlers/email/index.ts +43 -0
  21. package/src/daemon/handlers/email/read-handlers.ts +80 -0
  22. package/src/daemon/handlers/email/runtime.ts +140 -0
  23. package/src/daemon/handlers/email/validation.ts +147 -0
  24. package/src/daemon/handlers/email/write-handlers.ts +133 -0
  25. package/src/daemon/handlers/errors.ts +18 -0
  26. package/src/daemon/{channels → handlers}/inbox/cursor-store.ts +58 -66
  27. package/src/daemon/handlers/inbox/index.ts +211 -0
  28. package/src/daemon/{channels → handlers}/inbox/mapping.ts +8 -6
  29. package/src/daemon/{channels → handlers}/inbox/poller.ts +6 -5
  30. package/src/daemon/{channels → handlers}/inbox/provider-adapter.ts +14 -10
  31. package/src/daemon/{channels → handlers}/inbox/providers/discord.ts +10 -11
  32. package/src/daemon/{channels → handlers}/inbox/providers/email.ts +2 -1
  33. package/src/daemon/{channels → handlers}/inbox/providers/imap-client.ts +1 -1
  34. package/src/daemon/{channels → handlers}/inbox/providers/route-util.ts +4 -3
  35. package/src/daemon/{channels → handlers}/inbox/providers/slack.ts +11 -12
  36. package/src/daemon/handlers/index.ts +107 -0
  37. package/src/daemon/handlers/register.ts +161 -0
  38. package/src/daemon/{remote → handlers/remote}/backends/cloud-terminal.ts +8 -3
  39. package/src/daemon/{remote → handlers/remote}/backends/docker.ts +2 -3
  40. package/src/daemon/handlers/remote/backends/index.ts +40 -0
  41. package/src/daemon/{remote → handlers/remote}/backends/process-runner.ts +16 -40
  42. package/src/daemon/{remote → handlers/remote}/backends/ssh.ts +8 -3
  43. package/src/daemon/{remote → handlers/remote}/backends/types.ts +29 -3
  44. package/src/daemon/{remote → handlers/remote}/dispatcher.ts +27 -6
  45. package/src/daemon/handlers/remote/index.ts +119 -0
  46. package/src/daemon/{remote → handlers/remote}/peer-registry.ts +47 -11
  47. package/src/daemon/handlers/remote/service.ts +191 -0
  48. package/src/daemon/{channels → handlers}/routing/inbox-bridge.ts +33 -20
  49. package/src/daemon/handlers/routing/index.ts +261 -0
  50. package/src/daemon/{channels → handlers}/routing/route-store.ts +57 -16
  51. package/src/daemon/{channels → handlers}/routing/routing-resolver.ts +1 -1
  52. package/src/daemon/{operator → handlers}/sqlite-store.ts +5 -5
  53. package/src/daemon/handlers/triage/index.ts +57 -0
  54. package/src/daemon/handlers/triage/integration.ts +213 -0
  55. package/src/daemon/{triage → handlers/triage}/pipeline.ts +60 -71
  56. package/src/daemon/{triage → handlers/triage}/scorer.ts +21 -21
  57. package/src/daemon/handlers/triage/tagger/discord.ts +187 -0
  58. package/src/daemon/handlers/triage/tagger/imap.ts +384 -0
  59. package/src/daemon/handlers/triage/tagger/index.ts +184 -0
  60. package/src/daemon/handlers/triage/tagger/shared.ts +70 -0
  61. package/src/daemon/handlers/triage/tagger/slack.ts +69 -0
  62. package/src/daemon/handlers/triage/types.ts +50 -0
  63. package/src/export/gist-uploader.ts +3 -1
  64. package/src/input/command-registry.ts +1 -0
  65. package/src/input/commands/cloudflare-runtime.ts +2 -1
  66. package/src/input/commands/guidance-runtime.ts +2 -4
  67. package/src/input/commands/health-runtime.ts +13 -7
  68. package/src/input/commands/knowledge.ts +2 -1
  69. package/src/input/commands/runtime-services.ts +40 -10
  70. package/src/input/handler-command-route.ts +1 -1
  71. package/src/input/handler-interactions.ts +2 -1
  72. package/src/input/handler-modal-routes.ts +2 -1
  73. package/src/input/handler-onboarding-cloudflare.ts +2 -1
  74. package/src/input/handler-onboarding.ts +8 -8
  75. package/src/input/handler-ui-state.ts +1 -1
  76. package/src/input/tts-settings-actions.ts +2 -1
  77. package/src/main.ts +52 -59
  78. package/src/panels/agent-inspector-panel.ts +72 -2
  79. package/src/panels/agent-logs-panel.ts +127 -19
  80. package/src/panels/base-panel.ts +2 -1
  81. package/src/panels/builtin/agent.ts +3 -1
  82. package/src/panels/builtin/development.ts +1 -0
  83. package/src/panels/builtin/session.ts +1 -1
  84. package/src/panels/context-visualizer-panel.ts +24 -14
  85. package/src/panels/cost-tracker-panel.ts +7 -13
  86. package/src/panels/debug-panel.ts +11 -22
  87. package/src/panels/docs-panel.ts +14 -24
  88. package/src/panels/file-explorer-panel.ts +2 -1
  89. package/src/panels/file-preview-panel.ts +2 -1
  90. package/src/panels/git-panel.ts +10 -17
  91. package/src/panels/marketplace-panel.ts +2 -1
  92. package/src/panels/memory-panel.ts +2 -1
  93. package/src/panels/project-planning-panel.ts +5 -4
  94. package/src/panels/provider-health-panel.ts +56 -67
  95. package/src/panels/schedule-panel.ts +4 -7
  96. package/src/panels/session-browser-panel.ts +13 -21
  97. package/src/panels/skills-panel.ts +2 -1
  98. package/src/panels/tasks-panel.ts +2 -7
  99. package/src/panels/thinking-panel.ts +6 -11
  100. package/src/panels/token-budget-panel.ts +31 -15
  101. package/src/panels/tool-inspector-panel.ts +10 -18
  102. package/src/panels/work-plan-panel.ts +2 -1
  103. package/src/panels/wrfc-panel.ts +37 -35
  104. package/src/renderer/agent-detail-modal.ts +2 -2
  105. package/src/renderer/modal-utils.ts +0 -10
  106. package/src/renderer/process-modal.ts +2 -2
  107. package/src/runtime/bootstrap-command-context.ts +3 -0
  108. package/src/runtime/bootstrap-command-parts.ts +3 -1
  109. package/src/runtime/bootstrap-core.ts +31 -31
  110. package/src/runtime/bootstrap-shell.ts +1 -0
  111. package/src/runtime/onboarding/apply.ts +4 -3
  112. package/src/runtime/onboarding/snapshot.ts +4 -3
  113. package/src/runtime/process-lifecycle.ts +195 -0
  114. package/src/runtime/services.ts +52 -36
  115. package/src/runtime/wrfc-persistence.ts +20 -5
  116. package/src/shell/ui-openers.ts +3 -2
  117. package/src/verification/live-verifier.ts +4 -3
  118. package/src/version.ts +1 -1
  119. package/src/core/context-auto-compact.ts +0 -110
  120. package/src/daemon/calendar/index.ts +0 -52
  121. package/src/daemon/calendar/register.ts +0 -527
  122. package/src/daemon/channels/drafts/index.ts +0 -22
  123. package/src/daemon/channels/drafts/register.ts +0 -449
  124. package/src/daemon/channels/inbox/index.ts +0 -58
  125. package/src/daemon/channels/inbox/register.ts +0 -247
  126. package/src/daemon/channels/routing/index.ts +0 -39
  127. package/src/daemon/channels/routing/register.ts +0 -296
  128. package/src/daemon/email/index.ts +0 -68
  129. package/src/daemon/email/register.ts +0 -715
  130. package/src/daemon/operator/index.ts +0 -43
  131. package/src/daemon/operator/register-helper.ts +0 -150
  132. package/src/daemon/operator/surfaces.ts +0 -137
  133. package/src/daemon/operator/types.ts +0 -207
  134. package/src/daemon/remote/backends/index.ts +0 -34
  135. package/src/daemon/remote/index.ts +0 -74
  136. package/src/daemon/remote/register.ts +0 -411
  137. package/src/daemon/triage/index.ts +0 -59
  138. package/src/daemon/triage/integration.ts +0 -179
  139. package/src/daemon/triage/register.ts +0 -231
  140. package/src/daemon/triage/tagger.ts +0 -777
  141. /package/src/daemon/{calendar → handlers/calendar}/ics.ts +0 -0
  142. /package/src/daemon/{operator/credential-store.ts → handlers/credentials.ts} +0 -0
  143. /package/src/daemon/{email → handlers/email}/imap-connector.ts +0 -0
  144. /package/src/daemon/{email → handlers/email}/imap-parsing.ts +0 -0
  145. /package/src/daemon/{email → handlers/email}/smtp-connector.ts +0 -0
  146. /package/src/daemon/{remote → handlers/remote}/backends/local-process.ts +0 -0
@@ -1,449 +0,0 @@
1
- import {
2
- OperatorError,
3
- createAtRestCipher,
4
- createDaemonCredentialStore,
5
- declareOperatorMethods,
6
- } from '../../operator/index.ts';
7
- import type {
8
- OperatorContext,
9
- OperatorHandler,
10
- OperatorMethodDescriptor,
11
- Unregister,
12
- } from '../../operator/index.ts';
13
- import {
14
- ALL_DRAFT_STATUSES,
15
- DEFAULT_DRAFT_LIST_LIMIT,
16
- DraftSyncStore,
17
- MAX_DRAFT_LIST_LIMIT,
18
- WRITABLE_DRAFT_STATUSES,
19
- } from './draft-store.ts';
20
- import type {
21
- DraftListQuery,
22
- DraftRecord,
23
- DraftSaveInput,
24
- DraftSaveResult,
25
- DraftStatus,
26
- } from './draft-store.ts';
27
-
28
- // ---------------------------------------------------------------------------
29
- // channels.drafts.* operator methods (Draft Sync Backend).
30
- //
31
- // channels.drafts.list read-only scopes ['channels:drafts:read']
32
- // channels.drafts.get read-only scopes ['channels:drafts:read']
33
- // channels.drafts.save local mutation scopes ['channels:drafts:write']
34
- // channels.drafts.delete local mutation scopes ['channels:drafts:write']
35
- //
36
- // save/delete are LOCAL state mutations (no external provider effect), so per
37
- // the handoff ("Confirmation / Effect Semantics") they do NOT require
38
- // confirm:true. The daemon still enforces encrypt-at-rest for the body and
39
- // redaction of the webhook in every response (handled by DraftSyncStore).
40
- // ---------------------------------------------------------------------------
41
-
42
- const CATEGORY = 'channels';
43
- const READ_SCOPES = ['channels:drafts:read'];
44
- const WRITE_SCOPES = ['channels:drafts:write'];
45
- const BAD_INPUT = 'OPERATOR_INVALID_INPUT';
46
-
47
- const writableStatusSet = new Set<string>(WRITABLE_DRAFT_STATUSES);
48
- const allStatusSet = new Set<string>(ALL_DRAFT_STATUSES);
49
-
50
- // --- Input validation helpers ----------------------------------------------
51
-
52
- function asRecord(body: unknown): Record<string, unknown> {
53
- if (typeof body !== 'object' || body === null || Array.isArray(body)) {
54
- throw new OperatorError('Request body must be an object.', BAD_INPUT, 400);
55
- }
56
- return body as Record<string, unknown>;
57
- }
58
-
59
- function optionalString(
60
- value: unknown,
61
- field: string,
62
- ): string | undefined {
63
- if (value === undefined || value === null) return undefined;
64
- if (typeof value !== 'string') {
65
- throw new OperatorError(`Field '${field}' must be a string.`, BAD_INPUT, 400);
66
- }
67
- return value;
68
- }
69
-
70
- function requiredString(value: unknown, field: string): string {
71
- const str = optionalString(value, field);
72
- if (str === undefined || str.length === 0) {
73
- throw new OperatorError(`Field '${field}' is required.`, BAD_INPUT, 400);
74
- }
75
- return str;
76
- }
77
-
78
- function optionalTags(value: unknown): string[] | undefined {
79
- if (value === undefined || value === null) return undefined;
80
- if (!Array.isArray(value)) {
81
- throw new OperatorError("Field 'tags' must be an array of strings.", BAD_INPUT, 400);
82
- }
83
- const tags: string[] = [];
84
- for (const entry of value) {
85
- if (typeof entry !== 'string') {
86
- throw new OperatorError("Field 'tags' must contain only strings.", BAD_INPUT, 400);
87
- }
88
- tags.push(entry);
89
- }
90
- return tags;
91
- }
92
-
93
- function optionalLimit(value: unknown): number | undefined {
94
- if (value === undefined || value === null) return undefined;
95
- // Mirror the declared schema exactly: { type:'integer', minimum:1,
96
- // maximum:MAX_DRAFT_LIST_LIMIT }. A float (e.g. 1.5) or out-of-range value
97
- // must be rejected with OPERATOR_INVALID_INPUT rather than silently
98
- // floored/clamped downstream by clampLimit — closing the contract-fidelity
99
- // gap between the integer schema and the validator.
100
- if (typeof value !== 'number' || !Number.isInteger(value)) {
101
- throw new OperatorError("Field 'limit' must be an integer.", BAD_INPUT, 400);
102
- }
103
- if (value < 1 || value > MAX_DRAFT_LIST_LIMIT) {
104
- throw new OperatorError(
105
- `Field 'limit' must be between 1 and ${MAX_DRAFT_LIST_LIMIT}.`,
106
- BAD_INPUT,
107
- 400,
108
- );
109
- }
110
- return value;
111
- }
112
-
113
- // Strict ISO-8601 date-time: 'YYYY-MM-DDTHH:mm:ss' with optional fractional
114
- // seconds and a 'Z' or '+/-HH:mm' offset. Anchored so loosely-formatted but
115
- // Date.parse-able values (e.g. '2020', 'Jan 1 2020', '2020-1-1') are rejected.
116
- const ISO_8601_DATE_TIME =
117
- /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
118
-
119
- function optionalIso8601(value: unknown, field: string): string | undefined {
120
- const str = optionalString(value, field);
121
- if (str === undefined) return undefined;
122
- // Lets an integrator push the agent's authoritative updatedAt so the sync
123
- // contract's 'most recent updatedAt wins' conflict model is expressible
124
- // end-to-end. The stored value is surfaced as DraftRecord.updatedAt, which
125
- // the contract documents as ISO-8601 (format:'date-time'), so the input must
126
- // be a strict ISO-8601 date-time — not merely Date.parse-able. We then
127
- // normalize via Date#toISOString() to guarantee the persisted/returned value
128
- // is always canonical ISO-8601, honoring the declared date-time format.
129
- const time = ISO_8601_DATE_TIME.test(str) ? Date.parse(str) : Number.NaN;
130
- if (Number.isNaN(time)) {
131
- throw new OperatorError(
132
- `Field '${field}' must be an ISO-8601 timestamp.`,
133
- BAD_INPUT,
134
- 400,
135
- );
136
- }
137
- return new Date(time).toISOString();
138
- }
139
-
140
- function parseListStatus(value: unknown): DraftStatus | undefined {
141
- const str = optionalString(value, 'status');
142
- if (str === undefined) return undefined;
143
- if (!allStatusSet.has(str)) {
144
- throw new OperatorError(
145
- `Field 'status' must be one of: ${ALL_DRAFT_STATUSES.join(', ')}.`,
146
- BAD_INPUT,
147
- 400,
148
- );
149
- }
150
- return str as DraftStatus;
151
- }
152
-
153
- function parseSaveStatus(value: unknown): DraftStatus | undefined {
154
- const str = optionalString(value, 'status');
155
- if (str === undefined) return undefined;
156
- if (!writableStatusSet.has(str)) {
157
- throw new OperatorError(
158
- `Field 'status' must be one of: ${WRITABLE_DRAFT_STATUSES.join(', ')}.`,
159
- BAD_INPUT,
160
- 400,
161
- );
162
- }
163
- return str as DraftStatus;
164
- }
165
-
166
- function parseListQuery(body: unknown): DraftListQuery {
167
- const record = asRecord(body ?? {});
168
- const query: DraftListQuery = {};
169
- const status = parseListStatus(record.status);
170
- if (status !== undefined) query.status = status;
171
- const limit = optionalLimit(record.limit);
172
- if (limit !== undefined) query.limit = limit;
173
- return query;
174
- }
175
-
176
- function parseSaveInput(body: unknown): DraftSaveInput {
177
- const record = asRecord(body);
178
- const input: DraftSaveInput = {
179
- message: requiredString(record.message, 'message'),
180
- };
181
- const id = optionalString(record.id, 'id');
182
- if (id !== undefined) input.id = id;
183
- const title = optionalString(record.title, 'title');
184
- if (title !== undefined) input.title = title;
185
- const channel = optionalString(record.channel, 'channel');
186
- if (channel !== undefined) input.channel = channel;
187
- const route = optionalString(record.route, 'route');
188
- if (route !== undefined) input.route = route;
189
- const webhook = optionalString(record.webhook, 'webhook');
190
- if (webhook !== undefined) input.webhook = webhook;
191
- const link = optionalString(record.link, 'link');
192
- if (link !== undefined) input.link = link;
193
- const tags = optionalTags(record.tags);
194
- if (tags !== undefined) input.tags = tags;
195
- const status = parseSaveStatus(record.status);
196
- if (status !== undefined) input.status = status;
197
- const updatedAt = optionalIso8601(record.updatedAt, 'updatedAt');
198
- if (updatedAt !== undefined) input.updatedAt = updatedAt;
199
- return input;
200
- }
201
-
202
- function parseIdInput(body: unknown): string {
203
- const record = asRecord(body);
204
- return requiredString(record.id, 'id');
205
- }
206
-
207
- // --- JSON Schemas (catalog metadata) ---------------------------------------
208
-
209
- const draftRecordSchema: Record<string, unknown> = {
210
- type: 'object',
211
- required: ['id', 'createdAt', 'updatedAt', 'status', 'messageDigest'],
212
- properties: {
213
- id: { type: 'string' },
214
- createdAt: { type: 'string' },
215
- updatedAt: { type: 'string' },
216
- status: { type: 'string', enum: [...ALL_DRAFT_STATUSES] },
217
- title: { type: 'string' },
218
- messageDigest: { type: 'string' },
219
- channel: { type: 'string' },
220
- route: { type: 'string' },
221
- webhook: { type: 'string', description: "Always '[redacted]' when present." },
222
- link: { type: 'string' },
223
- tags: { type: 'array', items: { type: 'string' } },
224
- sentResponseId: { type: 'string' },
225
- sendError: { type: 'string' },
226
- },
227
- };
228
-
229
- const listInputSchema: Record<string, unknown> = {
230
- type: 'object',
231
- properties: {
232
- status: { type: 'string', enum: [...ALL_DRAFT_STATUSES] },
233
- limit: {
234
- type: 'integer',
235
- minimum: 1,
236
- maximum: MAX_DRAFT_LIST_LIMIT,
237
- default: DEFAULT_DRAFT_LIST_LIMIT,
238
- },
239
- },
240
- };
241
-
242
- const listOutputSchema: Record<string, unknown> = {
243
- type: 'object',
244
- required: ['drafts'],
245
- properties: { drafts: { type: 'array', items: draftRecordSchema } },
246
- };
247
-
248
- const getInputSchema: Record<string, unknown> = {
249
- type: 'object',
250
- required: ['id'],
251
- properties: { id: { type: 'string' } },
252
- };
253
-
254
- const getOutputSchema: Record<string, unknown> = {
255
- oneOf: [
256
- draftRecordSchema,
257
- {
258
- type: 'object',
259
- required: ['notFound', 'id'],
260
- properties: { notFound: { const: true }, id: { type: 'string' } },
261
- },
262
- ],
263
- };
264
-
265
- const saveInputSchema: Record<string, unknown> = {
266
- type: 'object',
267
- required: ['message'],
268
- properties: {
269
- id: { type: 'string' },
270
- title: { type: 'string' },
271
- message: { type: 'string' },
272
- channel: { type: 'string' },
273
- route: { type: 'string' },
274
- webhook: { type: 'string' },
275
- link: { type: 'string' },
276
- tags: { type: 'array', items: { type: 'string' } },
277
- status: { type: 'string', enum: [...WRITABLE_DRAFT_STATUSES] },
278
- updatedAt: {
279
- type: 'string',
280
- format: 'date-time',
281
- description:
282
- "Optional caller-supplied last-modified timestamp (ISO-8601). When "
283
- + "provided it is persisted verbatim, enabling the 'most recent updatedAt "
284
- + "wins' conflict model; when omitted the daemon stamps server now().",
285
- },
286
- },
287
- };
288
-
289
- const saveOutputSchema: Record<string, unknown> = {
290
- type: 'object',
291
- required: ['id', 'created'],
292
- properties: { id: { type: 'string' }, created: { type: 'boolean' } },
293
- };
294
-
295
- const deleteInputSchema = getInputSchema;
296
-
297
- const deleteOutputSchema: Record<string, unknown> = {
298
- type: 'object',
299
- required: ['deleted'],
300
- properties: { deleted: { type: 'boolean' } },
301
- };
302
-
303
- // --- Response types ---------------------------------------------------------
304
-
305
- interface ListResponse {
306
- drafts: DraftRecord[];
307
- }
308
-
309
- type GetResponse = DraftRecord | { notFound: true; id: string };
310
-
311
- // --- Registration -----------------------------------------------------------
312
-
313
- export interface RegisterDraftsOptions {
314
- /** Override the sqlite filename (tests). */
315
- fileName?: string;
316
- /** Inject a pre-built store (tests). When provided, no credential store is built. */
317
- store?: DraftSyncStore;
318
- }
319
-
320
- /**
321
- * Register the channels.drafts.* operator methods against the catalog in ctx.
322
- * Returns an Unregister that removes all four methods.
323
- *
324
- * The store is created eagerly but initialized lazily on first invocation
325
- * (SQLite WASM load is deferred until a draft method is actually called). Every
326
- * mutating handler persists the store via save() after the mutation so the
327
- * mirror survives daemon restarts.
328
- */
329
- export function registerDraftsMethods(
330
- ctx: OperatorContext,
331
- options: RegisterDraftsOptions = {},
332
- ): Unregister {
333
- const store =
334
- options.store
335
- ?? new DraftSyncStore({
336
- workingDirectory: ctx.workingDirectory,
337
- cipher: createAtRestCipher(createDaemonCredentialStore(ctx.secrets)),
338
- ...(options.fileName !== undefined ? { fileName: options.fileName } : {}),
339
- });
340
-
341
- let initPromise: Promise<void> | null = null;
342
- const ensureInit = (): Promise<void> => {
343
- if (!initPromise) initPromise = store.init();
344
- return initPromise;
345
- };
346
-
347
- const listHandler: OperatorHandler<unknown, ListResponse> = async ({ body }) => {
348
- await ensureInit();
349
- const query = parseListQuery(body);
350
- return { drafts: store.list(query) };
351
- };
352
-
353
- const getHandler: OperatorHandler<unknown, GetResponse> = async ({ body }) => {
354
- await ensureInit();
355
- const id = parseIdInput(body);
356
- const record = store.get(id);
357
- return record ?? { notFound: true, id };
358
- };
359
-
360
- const saveHandler: OperatorHandler<unknown, DraftSaveResult> = async ({ body }) => {
361
- await ensureInit();
362
- const input = parseSaveInput(body);
363
- const result = await store.upsert(input);
364
- await store.save();
365
- return result;
366
- };
367
-
368
- const deleteHandler: OperatorHandler<unknown, { deleted: boolean }> = async ({ body }) => {
369
- await ensureInit();
370
- const id = parseIdInput(body);
371
- const deleted = store.delete(id);
372
- if (deleted) await store.save();
373
- return { deleted };
374
- };
375
-
376
- const listDescriptor: OperatorMethodDescriptor = {
377
- id: 'channels.drafts.list',
378
- title: 'List drafts',
379
- description:
380
- 'List server-mirrored channel drafts. Bodies are never returned — only a '
381
- + 'messageDigest. Webhooks are redacted.',
382
- category: CATEGORY,
383
- access: 'operator',
384
- scopes: READ_SCOPES,
385
- effect: 'read-only',
386
- inputSchema: listInputSchema,
387
- outputSchema: listOutputSchema,
388
- };
389
-
390
- const getDescriptor: OperatorMethodDescriptor = {
391
- id: 'channels.drafts.get',
392
- title: 'Get draft',
393
- description:
394
- 'Fetch a single server-mirrored draft by id. The body is never returned — '
395
- + 'only a messageDigest. Webhook is redacted. Returns { notFound, id } when absent.',
396
- category: CATEGORY,
397
- access: 'operator',
398
- scopes: READ_SCOPES,
399
- effect: 'read-only',
400
- inputSchema: getInputSchema,
401
- outputSchema: getOutputSchema,
402
- };
403
-
404
- const saveDescriptor: OperatorMethodDescriptor = {
405
- id: 'channels.drafts.save',
406
- title: 'Save draft',
407
- description:
408
- 'Create or update a server-mirrored draft. The message body is encrypted at '
409
- + 'rest; the webhook is encrypted and redacted on read. Local state mutation, '
410
- + 'no confirmation required.',
411
- category: CATEGORY,
412
- access: 'operator',
413
- scopes: WRITE_SCOPES,
414
- effect: 'local-state-mutation',
415
- confirm: false,
416
- inputSchema: saveInputSchema,
417
- outputSchema: saveOutputSchema,
418
- };
419
-
420
- const deleteDescriptor: OperatorMethodDescriptor = {
421
- id: 'channels.drafts.delete',
422
- title: 'Delete draft',
423
- description:
424
- 'Delete a server-mirrored draft by id. Local state mutation, no confirmation '
425
- + 'required.',
426
- category: CATEGORY,
427
- access: 'operator',
428
- scopes: WRITE_SCOPES,
429
- effect: 'local-state-mutation',
430
- confirm: false,
431
- inputSchema: deleteInputSchema,
432
- outputSchema: deleteOutputSchema,
433
- };
434
-
435
- const unregisterMethods = declareOperatorMethods(ctx, [
436
- { descriptor: listDescriptor, handler: listHandler as OperatorHandler<unknown, unknown> },
437
- { descriptor: getDescriptor, handler: getHandler as OperatorHandler<unknown, unknown> },
438
- { descriptor: saveDescriptor, handler: saveHandler as OperatorHandler<unknown, unknown> },
439
- { descriptor: deleteDescriptor, handler: deleteHandler as OperatorHandler<unknown, unknown> },
440
- ]);
441
-
442
- // Teardown unregisters the methods and — only when this function owns the
443
- // store — closes it. An injected store is owned by the caller.
444
- const ownsStore = options.store === undefined;
445
- return () => {
446
- unregisterMethods();
447
- if (ownsStore) store.close();
448
- };
449
- }
@@ -1,58 +0,0 @@
1
- // Public barrel for the inbound inbox surface.
2
- //
3
- // Integration wires this surface the same way as every other daemon surface
4
- // (e.g. routing/triage): it calls the `register(ctx): Unregister` entry point
5
- // from its surface bootstrap and disposes the returned Unregister on shutdown.
6
- // `register` is the SurfaceRegister-shaped wrapper around registerInboxMethods.
7
-
8
- export { register, registerInboxMethods, INBOX_LIST_METHOD_ID, INBOX_LIST_SCOPES } from './register.ts';
9
- export type {
10
- InboxListInput,
11
- InboxListOutput,
12
- InboxProviderReport,
13
- RegisterInboxOptions,
14
- } from './register.ts';
15
-
16
- export type {
17
- InboundChannelItem,
18
- InboundProviderAdapter,
19
- ProviderPollOptions,
20
- ProviderPollResult,
21
- ProviderState,
22
- AdapterContext,
23
- AdapterFactory,
24
- RouteResolver,
25
- } from './provider-adapter.ts';
26
- export {
27
- POLL_CADENCE_MS,
28
- registerAdapterFactory,
29
- registeredProviderIds,
30
- buildAdapters,
31
- clearAdapterRegistry,
32
- } from './provider-adapter.ts';
33
-
34
- export { InboxCursorStore } from './cursor-store.ts';
35
- export type { InboxQuery } from './cursor-store.ts';
36
- export { InboundPoller } from './poller.ts';
37
- export type { ProviderStatus, PollerOptions } from './poller.ts';
38
-
39
- export { createSlackAdapter } from './providers/slack.ts';
40
- export { createDiscordAdapter } from './providers/discord.ts';
41
- export { createEmailAdapter } from './providers/email.ts';
42
- export {
43
- ImapClient,
44
- parseFetchResponse,
45
- decodeHeader,
46
- imapDate,
47
- } from './providers/imap-client.ts';
48
- export type { ImapConfig, ImapEnvelope } from './providers/imap-client.ts';
49
-
50
- export {
51
- digestSender,
52
- stripPii,
53
- stripMarkup,
54
- toSubjectPreview,
55
- toBodyPreview,
56
- SUBJECT_PREVIEW_MAX,
57
- BODY_PREVIEW_MAX,
58
- } from './mapping.ts';