@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
@@ -0,0 +1,316 @@
1
+ // Handler registration for the CalDAV calendar surface.
2
+ //
3
+ // Attaches host handlers to the five SDK-registered calendar gateway method
4
+ // descriptors BY ID (the SDK auto-registers them with handler:undefined). No
5
+ // method id, descriptor, or schema is authored here — `registerCatalogHandlers`
6
+ // looks up the canonical descriptor via `catalog.get(id)` and re-registers it
7
+ // with the wrapped handler. Method IDs handled (exactly):
8
+ // calendar.events.list read:calendar no confirm
9
+ // calendar.events.get read:calendar no confirm
10
+ // calendar.events.create write:calendar confirm:true + explicitUserRequest
11
+ // calendar.ics.import write:calendar confirm:true + explicitUserRequest
12
+ // calendar.ics.export read:calendar no confirm
13
+ //
14
+ // SECURITY: CalDAV credentials and authenticated URLs NEVER appear in any
15
+ // response. `calendarId` is a logical id. Attendees are surfaced as display
16
+ // names only (no raw addresses). Outputs are stripped to exactly the SDK output
17
+ // schema fields (the SDK schemas set additionalProperties:false).
18
+
19
+ import { HandlerError } from '../errors.ts';
20
+ import type { HandlerContext } from '../context.ts';
21
+ import {
22
+ registerCatalogHandlers,
23
+ type CatalogHandlerEntry,
24
+ type TypedHandler,
25
+ type Unregister,
26
+ } from '../register.ts';
27
+ import {
28
+ createCalDavClient,
29
+ resolveCalDavConfig,
30
+ type CalDavClient,
31
+ type CalDavConfigContext,
32
+ type CalDavEvent,
33
+ } from './caldav-client.ts';
34
+
35
+ /**
36
+ * The exact set of method IDs this surface publishes — a single source of truth
37
+ * for the calendar capability set, consumed by integration wiring and tests.
38
+ */
39
+ export const CALENDAR_METHOD_IDS = [
40
+ 'calendar.events.list',
41
+ 'calendar.events.get',
42
+ 'calendar.events.create',
43
+ 'calendar.ics.import',
44
+ 'calendar.ics.export',
45
+ ] as const;
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Client factory injection (real client by default; tests inject a stub).
49
+ // ---------------------------------------------------------------------------
50
+
51
+ export type CalDavClientFactory = (ctx: CalDavConfigContext) => Promise<CalDavClient>;
52
+
53
+ const realClientFactory: CalDavClientFactory = async (ctx) => {
54
+ const config = await resolveCalDavConfig(ctx);
55
+ return createCalDavClient({ config });
56
+ };
57
+
58
+ export interface RegisterCalendarOptions {
59
+ /** Override the CalDAV client factory (for tests). */
60
+ clientFactory?: CalDavClientFactory;
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Input validation helpers
65
+ // ---------------------------------------------------------------------------
66
+
67
+ function asRecord(body: unknown): Record<string, unknown> {
68
+ if (body === null || typeof body !== 'object' || Array.isArray(body)) {
69
+ throw new HandlerError('Request body must be an object.', 'CALENDAR_BAD_INPUT', 400);
70
+ }
71
+ return body as Record<string, unknown>;
72
+ }
73
+
74
+ function optionalString(record: Record<string, unknown>, key: string): string | undefined {
75
+ const value = record[key];
76
+ if (value === undefined || value === null) return undefined;
77
+ if (typeof value !== 'string') {
78
+ throw new HandlerError(`Field '${key}' must be a string.`, 'CALENDAR_BAD_INPUT', 400);
79
+ }
80
+ const trimmed = value.trim();
81
+ return trimmed.length > 0 ? trimmed : undefined;
82
+ }
83
+
84
+ function requiredString(record: Record<string, unknown>, key: string): string {
85
+ const value = optionalString(record, key);
86
+ if (value === undefined) {
87
+ throw new HandlerError(`Field '${key}' is required.`, 'CALENDAR_BAD_INPUT', 400);
88
+ }
89
+ return value;
90
+ }
91
+
92
+ function optionalNumber(record: Record<string, unknown>, key: string): number | undefined {
93
+ const value = record[key];
94
+ if (value === undefined || value === null) return undefined;
95
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
96
+ throw new HandlerError(`Field '${key}' must be a number.`, 'CALENDAR_BAD_INPUT', 400);
97
+ }
98
+ return value;
99
+ }
100
+
101
+ function optionalStringArray(record: Record<string, unknown>, key: string): string[] | undefined {
102
+ const value = record[key];
103
+ if (value === undefined || value === null) return undefined;
104
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
105
+ throw new HandlerError(`Field '${key}' must be an array of strings.`, 'CALENDAR_BAD_INPUT', 400);
106
+ }
107
+ return (value as string[]).map((item) => item.trim()).filter((item) => item.length > 0);
108
+ }
109
+
110
+ function validateIsoDate(value: string, field: string): string {
111
+ if (Number.isNaN(new Date(value).getTime())) {
112
+ throw new HandlerError(`Field '${field}' must be a valid ISO-8601 date.`, 'CALENDAR_BAD_INPUT', 400);
113
+ }
114
+ return value;
115
+ }
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // Response mapping (credential-free, PII-stripped, schema-exact)
119
+ // ---------------------------------------------------------------------------
120
+
121
+ /**
122
+ * Wire shape for `calendar.events.list` items — EXACTLY the SDK
123
+ * CALENDAR_EVENT_SUMMARY_SCHEMA (additionalProperties:false). Optional fields
124
+ * are present only when populated. `calendarId`/`allDay`/organizer are NOT in
125
+ * the SDK schema and are intentionally dropped.
126
+ */
127
+ export interface CalendarEventSummary {
128
+ id: string;
129
+ title: string;
130
+ start: string;
131
+ end: string;
132
+ location?: string;
133
+ description?: string;
134
+ attendees?: string[];
135
+ }
136
+
137
+ function displayAttendees(event: CalDavEvent): string[] {
138
+ return event.attendees.map((a) => a.displayName).filter((name) => name.length > 0);
139
+ }
140
+
141
+ function toSummary(event: CalDavEvent): CalendarEventSummary {
142
+ const attendees = displayAttendees(event);
143
+ const summary: CalendarEventSummary = {
144
+ id: event.href || event.uid,
145
+ title: event.summary,
146
+ start: event.start,
147
+ end: event.end,
148
+ };
149
+ if (event.location !== undefined) summary.location = event.location;
150
+ if (event.description !== undefined) summary.description = event.description;
151
+ if (attendees.length > 0) summary.attendees = attendees;
152
+ return summary;
153
+ }
154
+
155
+ /**
156
+ * Wire shape for `calendar.events.get` — EXACTLY the SDK
157
+ * CALENDAR_EVENT_DETAIL_SCHEMA (additionalProperties:false), returned directly
158
+ * (not wrapped). `uid` carries the raw iCalendar UID; attendees are display
159
+ * names only.
160
+ */
161
+ export interface CalendarEventDetail {
162
+ id: string;
163
+ uid: string;
164
+ title: string;
165
+ start: string;
166
+ end: string;
167
+ location?: string;
168
+ description?: string;
169
+ attendees?: string[];
170
+ recurrence?: string;
171
+ }
172
+
173
+ function toDetail(event: CalDavEvent): CalendarEventDetail {
174
+ const attendees = displayAttendees(event);
175
+ const detail: CalendarEventDetail = {
176
+ id: event.href || event.uid,
177
+ uid: event.uid,
178
+ title: event.summary,
179
+ start: event.start,
180
+ end: event.end,
181
+ };
182
+ if (event.location !== undefined) detail.location = event.location;
183
+ if (event.description !== undefined) detail.description = event.description;
184
+ if (attendees.length > 0) detail.attendees = attendees;
185
+ if (event.recurrence !== undefined) detail.recurrence = event.recurrence;
186
+ return detail;
187
+ }
188
+
189
+ // ---------------------------------------------------------------------------
190
+ // Handler bodies
191
+ // ---------------------------------------------------------------------------
192
+
193
+ interface ListBody {
194
+ calendarId?: string;
195
+ from?: string;
196
+ to?: string;
197
+ limit?: number;
198
+ }
199
+
200
+ interface GetBody {
201
+ eventId: string;
202
+ calendarId?: string;
203
+ }
204
+
205
+ interface ExportBody {
206
+ calendarId?: string;
207
+ from?: string;
208
+ to?: string;
209
+ }
210
+
211
+ /**
212
+ * Register all five calendar handlers against the SDK catalog held by `ctx`.
213
+ * Returns a single Unregister that detaches them in reverse order.
214
+ */
215
+ export function registerCalendarMethods(
216
+ ctx: HandlerContext,
217
+ options: RegisterCalendarOptions = {},
218
+ ): Unregister {
219
+ const clientFactory = options.clientFactory ?? realClientFactory;
220
+ const getClient = (): Promise<CalDavClient> => clientFactory(ctx);
221
+
222
+ const listHandler: TypedHandler<unknown, { events: CalendarEventSummary[] }> = async ({ body }) => {
223
+ const record = body === undefined || body === null ? {} : asRecord(body);
224
+ const input: ListBody = {
225
+ calendarId: optionalString(record, 'calendarId'),
226
+ from: optionalString(record, 'from'),
227
+ to: optionalString(record, 'to'),
228
+ limit: optionalNumber(record, 'limit'),
229
+ };
230
+ if (input.from) validateIsoDate(input.from, 'from');
231
+ if (input.to) validateIsoDate(input.to, 'to');
232
+ const limit = input.limit !== undefined ? Math.max(1, Math.min(200, Math.floor(input.limit))) : 20;
233
+ const client = await getClient();
234
+ const events = await client.listEvents({ ...input, limit });
235
+ return { events: events.map(toSummary) };
236
+ };
237
+
238
+ const getHandler: TypedHandler<unknown, CalendarEventDetail> = async ({ body }) => {
239
+ const record = asRecord(body);
240
+ const input: GetBody = {
241
+ eventId: requiredString(record, 'eventId'),
242
+ calendarId: optionalString(record, 'calendarId'),
243
+ };
244
+ const client = await getClient();
245
+ const event = await client.getEvent(input.eventId, input.calendarId);
246
+ if (!event) {
247
+ throw new HandlerError(`Event not found: ${input.eventId}`, 'CALENDAR_NOT_FOUND', 404);
248
+ }
249
+ return toDetail(event);
250
+ };
251
+
252
+ const createHandler: TypedHandler<unknown, { eventId: string; uid: string; createdAt: string }> = async ({ body }) => {
253
+ const record = asRecord(body);
254
+ const title = requiredString(record, 'title');
255
+ const start = validateIsoDate(requiredString(record, 'start'), 'start');
256
+ const end = validateIsoDate(requiredString(record, 'end'), 'end');
257
+ if (new Date(end).getTime() < new Date(start).getTime()) {
258
+ throw new HandlerError("Field 'end' must not be before 'start'.", 'CALENDAR_BAD_INPUT', 400);
259
+ }
260
+ const client = await getClient();
261
+ const created = await client.createEvent({
262
+ title,
263
+ start,
264
+ end,
265
+ description: optionalString(record, 'description'),
266
+ attendees: optionalStringArray(record, 'attendees'),
267
+ location: optionalString(record, 'location'),
268
+ calendarId: optionalString(record, 'calendarId'),
269
+ });
270
+ return { eventId: created.eventId, uid: created.uid, createdAt: created.createdAt };
271
+ };
272
+
273
+ const importHandler: TypedHandler<unknown, { imported: number; eventIds: string[]; errors: string[] }> = async ({ body }) => {
274
+ const record = asRecord(body);
275
+ const icsContent = requiredString(record, 'icsContent');
276
+ const calendarId = optionalString(record, 'calendarId');
277
+ const client = await getClient();
278
+ return client.importIcs(icsContent, calendarId);
279
+ };
280
+
281
+ const exportHandler: TypedHandler<unknown, { icsContent: string; eventCount: number }> = async ({ body }) => {
282
+ const record = body === undefined || body === null ? {} : asRecord(body);
283
+ const input: ExportBody = {
284
+ calendarId: optionalString(record, 'calendarId'),
285
+ from: optionalString(record, 'from'),
286
+ to: optionalString(record, 'to'),
287
+ };
288
+ if (input.from) validateIsoDate(input.from, 'from');
289
+ if (input.to) validateIsoDate(input.to, 'to');
290
+ const client = await getClient();
291
+ return client.exportIcs(input);
292
+ };
293
+
294
+ const entries: CatalogHandlerEntry[] = [
295
+ { id: 'calendar.events.list', handler: listHandler as TypedHandler<unknown, unknown> },
296
+ { id: 'calendar.events.get', handler: getHandler as TypedHandler<unknown, unknown> },
297
+ {
298
+ id: 'calendar.events.create',
299
+ handler: createHandler as TypedHandler<unknown, unknown>,
300
+ options: { confirm: true },
301
+ },
302
+ {
303
+ id: 'calendar.ics.import',
304
+ handler: importHandler as TypedHandler<unknown, unknown>,
305
+ options: { confirm: true },
306
+ },
307
+ { id: 'calendar.ics.export', handler: exportHandler as TypedHandler<unknown, unknown> },
308
+ ];
309
+
310
+ return registerCatalogHandlers(ctx.catalog, entries);
311
+ }
312
+
313
+ /** SurfaceRegister-compatible entry point used by the daemon composition root. */
314
+ export function registerCalendar(ctx: HandlerContext): Unregister {
315
+ return registerCalendarMethods(ctx);
316
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Host-facing context passed to every surface register function. Replaces the
3
+ * former OperatorContext. It carries the SDK gateway catalog (handlers attach
4
+ * to it), the daemon credential store, a read-only slice of the config manager,
5
+ * resolved directories, and a logger. No SDK descriptor or schema is declared
6
+ * here — the catalog type is re-exported through the contracts seam.
7
+ */
8
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
9
+ import type { GatewayMethodCatalog } from './contracts.ts';
10
+ import type { DaemonCredentialStore } from './credentials.ts';
11
+ import type { Unregister } from './register.ts';
12
+
13
+ export interface HandlerLogger {
14
+ info(message: string, meta?: unknown): void;
15
+ warn(message: string, meta?: unknown): void;
16
+ error(message: string, meta?: unknown): void;
17
+ }
18
+
19
+ export interface HandlerContext {
20
+ readonly catalog: GatewayMethodCatalog;
21
+ readonly credentials: DaemonCredentialStore;
22
+ readonly configManager: Pick<ConfigManager, 'get' | 'getCategory'>;
23
+ readonly workingDirectory: string;
24
+ readonly homeDirectory: string;
25
+ readonly logger: HandlerLogger;
26
+ }
27
+
28
+ /** Every surface module exports a register function of this shape. */
29
+ export type SurfaceRegister = (ctx: HandlerContext) => Unregister;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Single SDK-contract import seam for the daemon handler layer.
3
+ *
4
+ * Every other module under `src/daemon/handlers/` imports SDK contract
5
+ * identifiers from HERE and nowhere else. Concentrating the SDK imports in one
6
+ * concrete-submodule module keeps the rest of the layer free of barrel cycles
7
+ * and guarantees the host NEVER re-declares an SDK id, descriptor, or schema —
8
+ * it only attaches handlers to the descriptors the SDK already registered.
9
+ */
10
+
11
+ // Catalog + invocation contract types (concrete control-plane subpath, not a project barrel).
12
+ export type {
13
+ GatewayMethodCatalog,
14
+ GatewayMethodDescriptor,
15
+ GatewayMethodInvocation,
16
+ GatewayMethodInvocationContext,
17
+ GatewayMethodHandler,
18
+ } from '@pellux/goodvibes-sdk/platform/control-plane';
19
+
20
+ // Channel domain types reused in handler signatures (read-only SDK interfaces; never re-declared).
21
+ export type {
22
+ ChannelIdentity,
23
+ ChannelResolvedTarget,
24
+ ChannelAccountRecord,
25
+ } from '@pellux/goodvibes-sdk/platform/channels';
26
+
27
+ /**
28
+ * Per-peer authentication envelope passed to peer-scoped remote routes.
29
+ *
30
+ * The daemon-sdk ships `RemotePeerAuth` only as an UNEXPORTED local alias
31
+ * inside `@pellux/goodvibes-daemon-sdk/remote-routes` (it is `unknown` there),
32
+ * so it cannot be re-exported. We mirror that exact shape here for the host
33
+ * implementation. This is an implementable runtime contract, not a method
34
+ * descriptor or schema — declaring it does not violate the no-re-declaration
35
+ * rule for catalog methods.
36
+ */
37
+ export type RemotePeerAuth = unknown;
38
+
39
+ /**
40
+ * Remote distributed-runtime route service the HOST must implement and supply
41
+ * as `RuntimeServices.distributedRuntime`. The SDK facade injects the instance
42
+ * into `DaemonRemoteRouteContext.distributedRuntime` so the published
43
+ * `remote.peers.*` HTTP routes can dispatch to it.
44
+ *
45
+ * The daemon-sdk declares this interface locally in
46
+ * `@pellux/goodvibes-daemon-sdk/remote-routes` but does NOT export it (the
47
+ * module ends with `export {}`), so it cannot be imported. This declaration
48
+ * mirrors the SDK's exact structural shape (17 methods, verbatim signatures)
49
+ * so a host implementation is assignable to the SDK's context field. The SDK
50
+ * ships no docker/ssh/cloud backend — the host owns the implementation.
51
+ */
52
+ export interface DistributedRuntimeRouteService {
53
+ listPairRequests(): unknown;
54
+ approvePairRequest(requestId: string, input: Record<string, unknown>): Promise<unknown | null>;
55
+ rejectPairRequest(requestId: string, input: Record<string, unknown>): Promise<unknown | null>;
56
+ listPeers(): unknown;
57
+ rotatePeerToken(peerId: string, input: Record<string, unknown>): Promise<unknown | null>;
58
+ revokePeerToken(peerId: string, input: Record<string, unknown>): Promise<unknown | null>;
59
+ disconnectPeer(peerId: string, input: Record<string, unknown>): Promise<unknown | null>;
60
+ listWork(): unknown;
61
+ invokePeer(input: Record<string, unknown>): Promise<unknown>;
62
+ cancelWork(workId: string, input: Record<string, unknown>): Promise<unknown | null>;
63
+ getNodeHostContract(): unknown;
64
+ requestPairing(input: Record<string, unknown>): Promise<unknown>;
65
+ verifyPairRequest(
66
+ requestId: string,
67
+ challenge: string,
68
+ input: Record<string, unknown>,
69
+ ): Promise<unknown | null>;
70
+ heartbeatPeer(auth: RemotePeerAuth, input: Record<string, unknown>): Promise<unknown>;
71
+ claimWork(auth: RemotePeerAuth, input: Record<string, unknown>): Promise<unknown>;
72
+ completeWork(
73
+ auth: RemotePeerAuth,
74
+ workId: string,
75
+ input: Record<string, unknown>,
76
+ ): Promise<unknown | null>;
77
+ }