@pellux/goodvibes-agent 0.1.56 → 0.1.58

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 (95) hide show
  1. package/.goodvibes/GOODVIBES.md +1 -1
  2. package/CHANGELOG.md +18 -9
  3. package/README.md +3 -3
  4. package/docs/README.md +1 -1
  5. package/docs/getting-started.md +3 -3
  6. package/docs/release-and-publishing.md +2 -2
  7. package/package.json +1 -3
  8. package/src/agent/routine-schedule-args.ts +219 -0
  9. package/src/agent/routine-schedule-format.ts +173 -0
  10. package/src/agent/routine-schedule-promotion.ts +3 -811
  11. package/src/agent/routine-schedule-receipts.ts +502 -0
  12. package/src/cli/agent-knowledge-command.ts +6 -6
  13. package/src/cli/help.ts +3 -25
  14. package/src/cli/package-verification.ts +23 -16
  15. package/src/cli/redaction.ts +4 -1
  16. package/src/cli/routines-command.ts +10 -6
  17. package/src/cli/service-posture.ts +47 -280
  18. package/src/cli/status.ts +0 -1
  19. package/src/cli/tui-startup.ts +23 -0
  20. package/src/config/secret-config.ts +0 -2
  21. package/src/input/agent-workspace-categories.ts +219 -0
  22. package/src/input/agent-workspace-editors.ts +143 -0
  23. package/src/input/agent-workspace-snapshot.ts +265 -0
  24. package/src/input/agent-workspace-types.ts +142 -0
  25. package/src/input/agent-workspace.ts +22 -766
  26. package/src/input/commands/agent-runtime-profile-runtime.ts +1 -1
  27. package/src/input/commands/delegation-runtime.ts +1 -1
  28. package/src/input/commands/experience-runtime.ts +3 -4
  29. package/src/input/commands/guidance-runtime.ts +1 -2
  30. package/src/input/commands/health-runtime.ts +3 -65
  31. package/src/input/commands/knowledge.ts +7 -7
  32. package/src/input/commands/local-setup-review.ts +0 -61
  33. package/src/input/commands/local-setup-transfer.ts +0 -3
  34. package/src/input/commands/local-setup.ts +2 -15
  35. package/src/input/commands/planning-runtime.ts +4 -1
  36. package/src/input/commands/platform-access-runtime.ts +1 -10
  37. package/src/input/commands/platform-services-runtime.ts +0 -1
  38. package/src/input/commands/recall-query.ts +1 -1
  39. package/src/input/commands/routines-runtime.ts +10 -6
  40. package/src/input/commands/schedule-runtime.ts +10 -6
  41. package/src/input/commands/session-workflow.ts +1 -1
  42. package/src/input/commands/tasks-runtime.ts +1 -14
  43. package/src/input/commands.ts +0 -4
  44. package/src/input/handler-onboarding.ts +10 -120
  45. package/src/input/onboarding/onboarding-wizard-apply.ts +5 -196
  46. package/src/input/onboarding/onboarding-wizard-constants.ts +8 -119
  47. package/src/input/onboarding/onboarding-wizard-helpers.ts +2 -53
  48. package/src/input/onboarding/onboarding-wizard-rules.ts +2 -236
  49. package/src/input/onboarding/onboarding-wizard-state.ts +1 -69
  50. package/src/input/onboarding/onboarding-wizard-steps.ts +584 -737
  51. package/src/input/onboarding/onboarding-wizard-types.ts +8 -26
  52. package/src/input/onboarding/onboarding-wizard.ts +4 -109
  53. package/src/input/settings-modal-agent-policy.ts +10 -0
  54. package/src/input/settings-modal-types.ts +2 -4
  55. package/src/input/settings-modal.ts +3 -1
  56. package/src/input/submission-router.ts +0 -1
  57. package/src/main.ts +13 -12
  58. package/src/panels/approval-panel.ts +1 -2
  59. package/src/panels/builtin/operations.ts +1 -2
  60. package/src/panels/knowledge-panel.ts +2 -2
  61. package/src/panels/project-planning-panel.ts +4 -1
  62. package/src/panels/provider-health-domains.ts +0 -22
  63. package/src/panels/provider-health-panel.ts +1 -5
  64. package/src/panels/session-browser-panel.ts +0 -5
  65. package/src/panels/tasks-panel.ts +2 -64
  66. package/src/renderer/agent-workspace.ts +1 -1
  67. package/src/renderer/help-overlay.ts +1 -2
  68. package/src/renderer/semantic-diff.ts +1 -1
  69. package/src/renderer/settings-modal-helpers.ts +0 -16
  70. package/src/renderer/settings-modal.ts +3 -5
  71. package/src/runtime/bootstrap-hook-bridge.ts +0 -3
  72. package/src/runtime/bootstrap-shell.ts +2 -1
  73. package/src/runtime/bootstrap.ts +1 -1
  74. package/src/runtime/index.ts +0 -1
  75. package/src/runtime/onboarding/derivation.ts +1 -28
  76. package/src/runtime/onboarding/snapshot.ts +0 -1
  77. package/src/runtime/onboarding/types.ts +1 -4
  78. package/src/runtime/services.ts +4 -23
  79. package/src/runtime/ui-read-models.ts +4 -3
  80. package/src/shell/service-settings-sync.ts +15 -244
  81. package/src/tools/agent-context-policy.ts +1 -1
  82. package/src/tools/wrfc-agent-guard.ts +3 -3
  83. package/src/verification/live-verifier.ts +11 -5
  84. package/src/verification/verification-ledger.ts +3 -6
  85. package/src/version.ts +1 -1
  86. package/src/input/commands/agent-externalized-tui.ts +0 -73
  87. package/src/input/commands/cloudflare-runtime.ts +0 -385
  88. package/src/input/handler-onboarding-cloudflare.ts +0 -322
  89. package/src/input/onboarding/onboarding-runtime-status.ts +0 -87
  90. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +0 -494
  91. package/src/input/onboarding/onboarding-wizard-cloudflare.ts +0 -199
  92. package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +0 -130
  93. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +0 -762
  94. package/src/runtime/cloudflare-control-plane.ts +0 -350
  95. package/src/runtime/sandbox-public-gaps.ts +0 -358
@@ -1,12 +1,9 @@
1
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
- import { dirname, join } from 'node:path';
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
3
  import { createBrowserGoodVibesSdk } from '@pellux/goodvibes-sdk/browser';
4
4
  import type { OperatorMethodInput, OperatorMethodOutput } from '@pellux/goodvibes-sdk/contracts';
5
- import { formatEveryInterval } from '@pellux/goodvibes-sdk/platform/automation';
6
5
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
7
- import type { ShellPathService } from '@/runtime/index.ts';
8
6
  import { getModelIdFromProviderModel, getProviderIdFromModel } from '../config/provider-model.ts';
9
- import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
10
7
  import { SDK_VERSION } from '../version.ts';
11
8
  import type { AgentRoutineRecord } from './routine-registry.ts';
12
9
 
@@ -16,7 +13,6 @@ export const ROUTINE_SCHEDULE_LIST_METHOD = 'schedules.list';
16
13
 
17
14
  type ScheduleCreateInput = OperatorMethodInput<'schedules.create'>;
18
15
  type ScheduleCreateOutput = OperatorMethodOutput<'schedules.create'>;
19
- type ScheduleListOutput = OperatorMethodOutput<'schedules.list'>;
20
16
  type ScheduleDeliveryInput = NonNullable<ScheduleCreateInput['delivery']>;
21
17
  type ScheduleDeliveryTargetInput = ScheduleDeliveryInput['targets'] extends readonly (infer T)[] ? T : never;
22
18
 
@@ -210,30 +206,6 @@ export type RoutineScheduleCorrelationResult =
210
206
  | RoutineScheduleCorrelationSuccess
211
207
  | RoutineScheduleCorrelationFailure;
212
208
 
213
- interface RoutineScheduleReceiptStoreFile {
214
- readonly version: 1;
215
- readonly receipts: readonly RoutineScheduleReceipt[];
216
- }
217
-
218
- const RECEIPT_STORE_VERSION = 1;
219
- const DELIVERY_SURFACE_KINDS: readonly RoutineScheduleDeliverySurfaceKind[] = [
220
- 'tui',
221
- 'web',
222
- 'slack',
223
- 'discord',
224
- 'ntfy',
225
- 'webhook',
226
- 'telegram',
227
- 'google-chat',
228
- 'signal',
229
- 'whatsapp',
230
- 'imessage',
231
- 'msteams',
232
- 'bluebubbles',
233
- 'mattermost',
234
- 'matrix',
235
- 'service',
236
- ];
237
209
 
238
210
  function isRecord(value: unknown): value is Record<string, unknown> {
239
211
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
@@ -244,30 +216,6 @@ function readString(record: Record<string, unknown>, key: string): string | null
244
216
  return typeof value === 'string' ? value : null;
245
217
  }
246
218
 
247
- function readBoolean(record: Record<string, unknown>, key: string): boolean | undefined {
248
- const value = record[key];
249
- return typeof value === 'boolean' ? value : undefined;
250
- }
251
-
252
- function readNumber(record: Record<string, unknown>, key: string): number | undefined {
253
- const value = record[key];
254
- return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
255
- }
256
-
257
- function nowIso(): string {
258
- return new Date().toISOString();
259
- }
260
-
261
- function optionValue(args: readonly string[], index: number, inlineValue: string | undefined): {
262
- readonly value: string | undefined;
263
- readonly nextIndex: number;
264
- } {
265
- if (inlineValue !== undefined) return { value: inlineValue, nextIndex: index };
266
- const next = args[index + 1];
267
- if (next === undefined || next.startsWith('--')) return { value: undefined, nextIndex: index };
268
- return { value: next, nextIndex: index + 1 };
269
- }
270
-
271
219
  function normalizeProviderModel(provider: string | undefined, model: string | undefined): {
272
220
  readonly provider?: string;
273
221
  readonly model?: string;
@@ -280,63 +228,6 @@ function normalizeProviderModel(provider: string | undefined, model: string | un
280
228
  };
281
229
  }
282
230
 
283
- function isDeliverySurfaceKind(value: string): value is RoutineScheduleDeliverySurfaceKind {
284
- return DELIVERY_SURFACE_KINDS.includes(value as RoutineScheduleDeliverySurfaceKind);
285
- }
286
-
287
- function parseSurfaceDeliveryTarget(raw: string): RoutineScheduleDeliveryTargetSpec | string {
288
- const [surfaceKind = '', routeId, label] = raw.split(':');
289
- if (!isDeliverySurfaceKind(surfaceKind)) {
290
- return `Unsupported delivery surface "${surfaceKind}".`;
291
- }
292
- return {
293
- kind: 'surface',
294
- surfaceKind,
295
- routeId: routeId?.trim() || undefined,
296
- label: label?.trim() || undefined,
297
- };
298
- }
299
-
300
- function parseRouteDeliveryTarget(raw: string): RoutineScheduleDeliveryTargetSpec | string {
301
- const [routeId = '', label] = raw.split(':');
302
- const normalizedRouteId = routeId.trim();
303
- if (!normalizedRouteId) return '--delivery-route requires a route id.';
304
- return {
305
- kind: 'surface',
306
- routeId: normalizedRouteId,
307
- label: label?.trim() || undefined,
308
- };
309
- }
310
-
311
- function parseWebhookDeliveryTarget(raw: string): RoutineScheduleDeliveryTargetSpec | string {
312
- const normalized = raw.trim();
313
- if (!normalized) return '--delivery-webhook requires a URL.';
314
- try {
315
- const url = new URL(normalized);
316
- if (url.protocol !== 'https:' && url.protocol !== 'http:') return '--delivery-webhook must be an http(s) URL.';
317
- } catch {
318
- return '--delivery-webhook must be a valid URL.';
319
- }
320
- return {
321
- kind: 'webhook',
322
- address: normalized,
323
- };
324
- }
325
-
326
- function parseLinkDeliveryTarget(raw: string): RoutineScheduleDeliveryTargetSpec | string {
327
- const normalized = raw.trim();
328
- if (!normalized) return '--delivery-link requires a URL or label.';
329
- return {
330
- kind: 'link',
331
- address: normalized,
332
- };
333
- }
334
-
335
- function validateDeliveryTargets(targets: readonly RoutineScheduleDeliveryTargetSpec[]): string | null {
336
- const kinds = new Set(targets.map((target) => target.kind));
337
- return kinds.size > 1 ? 'Use one delivery target kind per routine promotion command.' : null;
338
- }
339
-
340
231
  function deliveryModeFromTargets(targets: readonly RoutineScheduleDeliveryTargetSpec[]): ScheduleDeliveryInput['mode'] {
341
232
  const first = targets[0];
342
233
  return first ? first.kind : 'none';
@@ -352,488 +243,6 @@ function toDeliveryTargetInput(target: RoutineScheduleDeliveryTargetSpec): Sched
352
243
  };
353
244
  }
354
245
 
355
- function redactedDeliveryAddress(address: string | undefined): string | undefined {
356
- if (!address) return undefined;
357
- try {
358
- const url = new URL(address);
359
- return `${url.protocol}//${url.host}/...`;
360
- } catch {
361
- return '[redacted]';
362
- }
363
- }
364
-
365
- function redactedDeliveryTargets(delivery: ScheduleDeliveryInput | undefined): readonly RoutineScheduleReceiptDeliveryTarget[] | undefined {
366
- if (!delivery) return undefined;
367
- return delivery.targets.map((target) => ({
368
- kind: target.kind as RoutineScheduleDeliveryKind,
369
- surfaceKind: typeof target.surfaceKind === 'string' && isDeliverySurfaceKind(target.surfaceKind) ? target.surfaceKind : undefined,
370
- address: redactedDeliveryAddress(typeof target.address === 'string' ? target.address : undefined),
371
- routeId: typeof target.routeId === 'string' ? target.routeId : undefined,
372
- label: typeof target.label === 'string' ? target.label : undefined,
373
- }));
374
- }
375
-
376
- function readReceipt(value: unknown): RoutineScheduleReceipt | null {
377
- if (!isRecord(value)) return null;
378
- const id = readString(value, 'id')?.trim();
379
- const createdAt = readString(value, 'createdAt')?.trim();
380
- const routineId = readString(value, 'routineId')?.trim();
381
- const routineName = readString(value, 'routineName')?.trim();
382
- const status = value.status === 'created' || value.status === 'failed' ? value.status : null;
383
- const scheduleKind = value.scheduleKind === 'cron' || value.scheduleKind === 'every' || value.scheduleKind === 'at'
384
- ? value.scheduleKind
385
- : null;
386
- const scheduleValue = readString(value, 'scheduleValue')?.trim();
387
- const target = isRecord(value.target) ? value.target : {};
388
- const deliveryTargets = Array.isArray(value.deliveryTargets)
389
- ? value.deliveryTargets.map((target): RoutineScheduleReceiptDeliveryTarget | null => {
390
- if (!isRecord(target)) return null;
391
- const kind = target.kind === 'webhook' || target.kind === 'surface' || target.kind === 'integration' || target.kind === 'link'
392
- ? target.kind
393
- : null;
394
- if (!kind) return null;
395
- const surfaceKind = readString(target, 'surfaceKind') ?? undefined;
396
- return {
397
- kind,
398
- surfaceKind: surfaceKind && isDeliverySurfaceKind(surfaceKind) ? surfaceKind : undefined,
399
- address: readString(target, 'address') ?? undefined,
400
- routeId: readString(target, 'routeId') ?? undefined,
401
- label: readString(target, 'label') ?? undefined,
402
- };
403
- }).filter((target): target is RoutineScheduleReceiptDeliveryTarget => target !== null)
404
- : undefined;
405
- if (!id || !createdAt || !routineId || !routineName || !status || !scheduleKind || !scheduleValue) return null;
406
- return {
407
- id,
408
- createdAt,
409
- routineId,
410
- routineName,
411
- route: ROUTINE_SCHEDULE_ROUTE,
412
- method: ROUTINE_SCHEDULE_METHOD,
413
- status,
414
- daemonBaseUrl: readString(value, 'daemonBaseUrl') ?? '',
415
- scheduleId: readString(value, 'scheduleId') ?? undefined,
416
- scheduleStatus: readString(value, 'scheduleStatus') ?? undefined,
417
- scheduleName: readString(value, 'scheduleName') ?? routineName,
418
- scheduleKind,
419
- scheduleValue,
420
- timezone: readString(value, 'timezone') ?? undefined,
421
- provider: readString(value, 'provider') ?? undefined,
422
- model: readString(value, 'model') ?? undefined,
423
- enabled: value.enabled !== false,
424
- target: {
425
- kind: readString(target, 'kind') ?? undefined,
426
- surfaceKind: readString(target, 'surfaceKind') ?? undefined,
427
- preserveThread: readBoolean(target, 'preserveThread'),
428
- createIfMissing: readBoolean(target, 'createIfMissing'),
429
- },
430
- deliveryMode: readString(value, 'deliveryMode') ?? undefined,
431
- deliveryTargets,
432
- failureKind: value.failureKind === 'confirmation_required'
433
- || value.failureKind === 'auth_required'
434
- || value.failureKind === 'daemon_unavailable'
435
- || value.failureKind === 'version_mismatch'
436
- || value.failureKind === 'daemon_route_unavailable'
437
- || value.failureKind === 'daemon_error'
438
- ? value.failureKind
439
- : undefined,
440
- failureError: readString(value, 'failureError') ?? undefined,
441
- };
442
- }
443
-
444
- function parseReceiptStore(raw: string): RoutineScheduleReceiptStoreFile {
445
- const parsed: unknown = JSON.parse(raw);
446
- if (!isRecord(parsed)) return { version: RECEIPT_STORE_VERSION, receipts: [] };
447
- return {
448
- version: RECEIPT_STORE_VERSION,
449
- receipts: Array.isArray(parsed.receipts)
450
- ? parsed.receipts.map(readReceipt).filter((receipt): receipt is RoutineScheduleReceipt => receipt !== null)
451
- : [],
452
- };
453
- }
454
-
455
- function formatReceiptStore(store: RoutineScheduleReceiptStoreFile): string {
456
- return `${JSON.stringify(store, null, 2)}\n`;
457
- }
458
-
459
- function receiptId(createdAt: string, routineId: string, existing: readonly RoutineScheduleReceipt[]): string {
460
- const dayStamp = createdAt.slice(0, 10).replace(/-/g, '');
461
- const base = `routine-schedule-${routineId}-${dayStamp}`.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
462
- const ids = new Set(existing.map((receipt) => receipt.id));
463
- if (!ids.has(base)) return base;
464
- for (let index = 2; index < 1000; index += 1) {
465
- const candidate = `${base}-${index}`;
466
- if (!ids.has(candidate)) return candidate;
467
- }
468
- return `${base}-${existing.length + 1}`;
469
- }
470
-
471
- function scheduleValue(payload: ScheduleCreateInput): string {
472
- if (payload.kind === 'cron') return String(payload.cron ?? '');
473
- if (payload.kind === 'every') return String(payload.every ?? '');
474
- return String(payload.at ?? '');
475
- }
476
-
477
- function scheduleKind(payload: ScheduleCreateInput): RoutineScheduleKind {
478
- if (payload.kind === 'cron' || payload.kind === 'every' || payload.kind === 'at') return payload.kind;
479
- throw new Error('Routine schedule payload is missing a schedule kind.');
480
- }
481
-
482
- function targetSummary(payload: ScheduleCreateInput): RoutineScheduleReceipt['target'] {
483
- return isRecord(payload.target)
484
- ? {
485
- kind: typeof payload.target.kind === 'string' ? payload.target.kind : undefined,
486
- surfaceKind: typeof payload.target.surfaceKind === 'string' ? payload.target.surfaceKind : undefined,
487
- preserveThread: typeof payload.target.preserveThread === 'boolean' ? payload.target.preserveThread : undefined,
488
- createIfMissing: typeof payload.target.createIfMissing === 'boolean' ? payload.target.createIfMissing : undefined,
489
- }
490
- : {};
491
- }
492
-
493
- function deliveryMode(payload: ScheduleCreateInput): string | undefined {
494
- return isRecord(payload.delivery) && typeof payload.delivery.mode === 'string' ? payload.delivery.mode : undefined;
495
- }
496
-
497
- function resultScheduleRecord(result: RoutineSchedulePromotionResult): Record<string, unknown> {
498
- return result.ok && isRecord(result.schedule) ? result.schedule : {};
499
- }
500
-
501
- function normalizeForMatch(value: string | undefined): string {
502
- return (value ?? '').trim().toLowerCase();
503
- }
504
-
505
- function isoTime(value: string): string | null {
506
- const time = new Date(value).getTime();
507
- return Number.isFinite(time) ? new Date(time).toISOString() : null;
508
- }
509
-
510
- function readLiveScheduleDefinition(value: unknown): {
511
- readonly kind?: RoutineScheduleKind;
512
- readonly value?: string;
513
- readonly timezone?: string;
514
- } {
515
- if (!isRecord(value)) return {};
516
- if (value.kind === 'cron') {
517
- return {
518
- kind: 'cron',
519
- value: readString(value, 'expression') ?? undefined,
520
- timezone: readString(value, 'timezone') ?? undefined,
521
- };
522
- }
523
- if (value.kind === 'every') {
524
- const intervalMs = readNumber(value, 'intervalMs');
525
- return {
526
- kind: 'every',
527
- value: intervalMs === undefined ? undefined : formatEveryInterval(intervalMs),
528
- };
529
- }
530
- if (value.kind === 'at') {
531
- const at = readNumber(value, 'at');
532
- return {
533
- kind: 'at',
534
- value: at === undefined ? undefined : new Date(at).toISOString(),
535
- };
536
- }
537
- return {};
538
- }
539
-
540
- function readLiveScheduleRecord(value: unknown): RoutineScheduleLiveRecord | null {
541
- if (!isRecord(value)) return null;
542
- const id = readString(value, 'id')?.trim();
543
- const name = readString(value, 'name')?.trim();
544
- if (!id || !name) return null;
545
- const schedule = readLiveScheduleDefinition(value.schedule);
546
- return {
547
- id,
548
- name,
549
- status: readString(value, 'status') ?? undefined,
550
- enabled: readBoolean(value, 'enabled'),
551
- scheduleKind: schedule.kind,
552
- scheduleValue: schedule.value,
553
- timezone: schedule.timezone,
554
- nextRunAt: readNumber(value, 'nextRunAt'),
555
- lastRunAt: readNumber(value, 'lastRunAt'),
556
- runCount: readNumber(value, 'runCount'),
557
- successCount: readNumber(value, 'successCount'),
558
- failureCount: readNumber(value, 'failureCount'),
559
- };
560
- }
561
-
562
- function readLiveSchedules(output: ScheduleListOutput): readonly RoutineScheduleLiveRecord[] {
563
- const record: Record<string, unknown> = isRecord(output) ? output : {};
564
- const jobs: readonly unknown[] = Array.isArray(record.jobs) ? record.jobs : [];
565
- return jobs.map(readLiveScheduleRecord).filter((schedule): schedule is RoutineScheduleLiveRecord => schedule !== null);
566
- }
567
-
568
- function cadenceMatches(receipt: RoutineScheduleReceipt, schedule: RoutineScheduleLiveRecord): boolean {
569
- if (receipt.scheduleKind !== schedule.scheduleKind) return false;
570
- if (receipt.scheduleKind === 'at') {
571
- const left = isoTime(receipt.scheduleValue);
572
- const right = schedule.scheduleValue ? isoTime(schedule.scheduleValue) : null;
573
- return Boolean(left && right && left === right);
574
- }
575
- return normalizeForMatch(receipt.scheduleValue) === normalizeForMatch(schedule.scheduleValue);
576
- }
577
-
578
- function findScheduleForReceipt(
579
- receipt: RoutineScheduleReceipt,
580
- schedules: readonly RoutineScheduleLiveRecord[],
581
- ): { readonly reason: RoutineScheduleCorrelation['matchReason']; readonly schedule?: RoutineScheduleLiveRecord } {
582
- if (receipt.scheduleId) {
583
- const byId = schedules.find((schedule) => schedule.id === receipt.scheduleId);
584
- if (byId) return { reason: 'schedule-id', schedule: byId };
585
- }
586
- const byNameAndCadence = schedules.find((schedule) => (
587
- normalizeForMatch(schedule.name) === normalizeForMatch(receipt.scheduleName)
588
- && cadenceMatches(receipt, schedule)
589
- ));
590
- return byNameAndCadence
591
- ? { reason: 'name-and-cadence', schedule: byNameAndCadence }
592
- : { reason: 'not-found' };
593
- }
594
-
595
- function correlateReceipts(
596
- receipts: readonly RoutineScheduleReceipt[],
597
- schedules: readonly RoutineScheduleLiveRecord[],
598
- ): readonly RoutineScheduleCorrelation[] {
599
- return receipts.map((receipt) => {
600
- if (receipt.status === 'failed') {
601
- return {
602
- receipt,
603
- liveStatus: 'failed-receipt',
604
- matchReason: 'failed-receipt',
605
- };
606
- }
607
- const match = findScheduleForReceipt(receipt, schedules);
608
- return match.schedule
609
- ? {
610
- receipt,
611
- liveStatus: 'matched',
612
- matchReason: match.reason,
613
- schedule: match.schedule,
614
- }
615
- : {
616
- receipt,
617
- liveStatus: 'missing',
618
- matchReason: 'not-found',
619
- };
620
- });
621
- }
622
-
623
- function buildReceipt(
624
- existing: readonly RoutineScheduleReceipt[],
625
- connection: AgentDaemonConnection,
626
- preview: RoutineSchedulePromotionPreview,
627
- result: RoutineSchedulePromotionResult,
628
- ): RoutineScheduleReceipt {
629
- const createdAt = nowIso();
630
- const kind = scheduleKind(preview.payload);
631
- const schedule = resultScheduleRecord(result);
632
- const scheduleId = readString(schedule, 'id') ?? undefined;
633
- const scheduleStatus = readString(schedule, 'status') ?? (schedule.enabled === false ? 'paused' : schedule.enabled === true ? 'enabled' : undefined);
634
- return {
635
- id: receiptId(createdAt, preview.routineId, existing),
636
- createdAt,
637
- routineId: preview.routineId,
638
- routineName: preview.routineName,
639
- route: ROUTINE_SCHEDULE_ROUTE,
640
- method: ROUTINE_SCHEDULE_METHOD,
641
- status: result.ok ? 'created' : 'failed',
642
- daemonBaseUrl: connection.baseUrl,
643
- scheduleId,
644
- scheduleStatus,
645
- scheduleName: String(preview.payload.name ?? `Agent routine: ${preview.routineName}`),
646
- scheduleKind: kind,
647
- scheduleValue: scheduleValue(preview.payload),
648
- timezone: kind === 'cron' ? preview.payload.timezone : undefined,
649
- provider: preview.payload.provider,
650
- model: preview.payload.model,
651
- enabled: preview.payload.enabled !== false,
652
- target: targetSummary(preview.payload),
653
- deliveryMode: deliveryMode(preview.payload),
654
- deliveryTargets: redactedDeliveryTargets(preview.payload.delivery),
655
- failureKind: result.ok ? undefined : result.kind,
656
- failureError: result.ok ? undefined : result.error,
657
- };
658
- }
659
-
660
- export function routineScheduleReceiptStorePath(shellPaths: ShellPathService): string {
661
- return shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'routines', 'schedule-receipts.json');
662
- }
663
-
664
- export class RoutineScheduleReceiptStore {
665
- public constructor(private readonly storePath: string) {}
666
-
667
- public static fromShellPaths(shellPaths: ShellPathService): RoutineScheduleReceiptStore {
668
- return new RoutineScheduleReceiptStore(routineScheduleReceiptStorePath(shellPaths));
669
- }
670
-
671
- public snapshot(): RoutineScheduleReceiptSnapshot {
672
- const store = this.readStore();
673
- return {
674
- path: this.storePath,
675
- receipts: [...store.receipts].sort((left, right) => right.createdAt.localeCompare(left.createdAt)),
676
- };
677
- }
678
-
679
- public get(id: string): RoutineScheduleReceipt | null {
680
- const normalized = id.trim().toLowerCase();
681
- if (!normalized) return null;
682
- return this.snapshot().receipts.find((receipt) => receipt.id.toLowerCase() === normalized) ?? null;
683
- }
684
-
685
- public append(connection: AgentDaemonConnection, preview: RoutineSchedulePromotionPreview, result: RoutineSchedulePromotionResult): RoutineScheduleReceipt {
686
- const store = this.readStore();
687
- const receipt = buildReceipt(store.receipts, connection, preview, result);
688
- this.writeStore({ ...store, receipts: [...store.receipts, receipt] });
689
- return receipt;
690
- }
691
-
692
- private readStore(): RoutineScheduleReceiptStoreFile {
693
- if (!existsSync(this.storePath)) return { version: RECEIPT_STORE_VERSION, receipts: [] };
694
- try {
695
- return parseReceiptStore(readFileSync(this.storePath, 'utf-8'));
696
- } catch (error) {
697
- throw new Error(`Could not read Agent routine schedule receipt store: ${summarizeError(error)}`);
698
- }
699
- }
700
-
701
- private writeStore(store: RoutineScheduleReceiptStoreFile): void {
702
- mkdirSync(dirname(this.storePath), { recursive: true });
703
- const tmpPath = `${this.storePath}.tmp`;
704
- writeFileSync(tmpPath, formatReceiptStore(store), 'utf-8');
705
- renameSync(tmpPath, this.storePath);
706
- }
707
- }
708
-
709
- export function parseRoutineSchedulePromotionArgs(args: readonly string[]): ParsedRoutineSchedulePromotionArgs {
710
- let routineId: string | null = null;
711
- let schedule: RoutineScheduleSpec | null = null;
712
- const deliveryTargets: RoutineScheduleDeliveryTargetSpec[] = [];
713
- let name: string | undefined;
714
- let timezone: string | undefined;
715
- let provider: string | undefined;
716
- let model: string | undefined;
717
- let enabled = true;
718
- let yes = false;
719
- const errors: string[] = [];
720
-
721
- for (let index = 0; index < args.length; index += 1) {
722
- const raw = args[index] ?? '';
723
- const equals = raw.indexOf('=');
724
- const optionName = equals >= 0 ? raw.slice(0, equals) : raw;
725
- const inlineValue = equals >= 0 ? raw.slice(equals + 1) : undefined;
726
-
727
- if (raw === '--yes') {
728
- yes = true;
729
- continue;
730
- }
731
- if (raw === '--disabled') {
732
- enabled = false;
733
- continue;
734
- }
735
- if (optionName === '--name' || optionName === '--timezone' || optionName === '--provider' || optionName === '--model') {
736
- const consumed = optionValue(args, index, inlineValue);
737
- index = consumed.nextIndex;
738
- const value = consumed.value?.trim();
739
- if (!value) {
740
- errors.push(`${optionName} requires a value.`);
741
- continue;
742
- }
743
- if (optionName === '--name') name = value;
744
- if (optionName === '--timezone') timezone = value;
745
- if (optionName === '--provider') provider = value;
746
- if (optionName === '--model') model = value;
747
- continue;
748
- }
749
- if (optionName === '--cron' || optionName === '--every' || optionName === '--at') {
750
- const consumed = optionValue(args, index, inlineValue);
751
- index = consumed.nextIndex;
752
- const value = consumed.value?.trim();
753
- if (!value) {
754
- errors.push(`${optionName} requires a value.`);
755
- continue;
756
- }
757
- if (schedule) {
758
- errors.push('Choose exactly one schedule selector: --cron, --every, or --at.');
759
- continue;
760
- }
761
- schedule = {
762
- kind: optionName === '--cron' ? 'cron' : optionName === '--every' ? 'every' : 'at',
763
- value,
764
- };
765
- continue;
766
- }
767
- if (optionName === '--delivery-surface' || optionName === '--deliver-surface') {
768
- const consumed = optionValue(args, index, inlineValue);
769
- index = consumed.nextIndex;
770
- const value = consumed.value?.trim();
771
- if (!value) {
772
- errors.push(`${optionName} requires a value.`);
773
- continue;
774
- }
775
- const target = parseSurfaceDeliveryTarget(value);
776
- if (typeof target === 'string') errors.push(target);
777
- else deliveryTargets.push(target);
778
- continue;
779
- }
780
- if (optionName === '--delivery-route' || optionName === '--deliver-route') {
781
- const consumed = optionValue(args, index, inlineValue);
782
- index = consumed.nextIndex;
783
- const value = consumed.value?.trim();
784
- if (!value) {
785
- errors.push(`${optionName} requires a value.`);
786
- continue;
787
- }
788
- const target = parseRouteDeliveryTarget(value);
789
- if (typeof target === 'string') errors.push(target);
790
- else deliveryTargets.push(target);
791
- continue;
792
- }
793
- if (optionName === '--delivery-webhook' || optionName === '--deliver-webhook') {
794
- const consumed = optionValue(args, index, inlineValue);
795
- index = consumed.nextIndex;
796
- const value = consumed.value?.trim();
797
- if (!value) {
798
- errors.push(`${optionName} requires a value.`);
799
- continue;
800
- }
801
- const target = parseWebhookDeliveryTarget(value);
802
- if (typeof target === 'string') errors.push(target);
803
- else deliveryTargets.push(target);
804
- continue;
805
- }
806
- if (optionName === '--delivery-link' || optionName === '--deliver-link') {
807
- const consumed = optionValue(args, index, inlineValue);
808
- index = consumed.nextIndex;
809
- const value = consumed.value?.trim();
810
- if (!value) {
811
- errors.push(`${optionName} requires a value.`);
812
- continue;
813
- }
814
- const target = parseLinkDeliveryTarget(value);
815
- if (typeof target === 'string') errors.push(target);
816
- else deliveryTargets.push(target);
817
- continue;
818
- }
819
- if (raw.startsWith('--')) {
820
- errors.push(`Unknown option: ${raw}`);
821
- continue;
822
- }
823
- if (!routineId) {
824
- routineId = raw;
825
- continue;
826
- }
827
- errors.push(`Unexpected argument: ${raw}`);
828
- }
829
-
830
- if (!routineId) errors.push('Routine id or name is required.');
831
- if (!schedule) errors.push('Schedule is required: use --cron <expr>, --every <interval>, or --at <iso-time>.');
832
- const deliveryError = validateDeliveryTargets(deliveryTargets);
833
- if (deliveryError) errors.push(deliveryError);
834
- return { routineId, schedule, deliveryTargets, name, timezone, provider, model, enabled, yes, errors };
835
- }
836
-
837
246
  export function resolveAgentDaemonConnection(
838
247
  configManager: AgentDaemonConfigReader,
839
248
  homeDirectory: string,
@@ -863,7 +272,7 @@ export function buildRoutineSchedulePrompt(routine: AgentRoutineRecord): string
863
272
  '',
864
273
  'Operator policy:',
865
274
  '- Run this as a serial GoodVibes Agent operator routine.',
866
- '- Use isolated Agent Knowledge routes only; never use default Knowledge/Wiki or HomeGraph as fallback.',
275
+ '- Use isolated Agent Knowledge routes only; never use default Knowledge/Wiki or non-Agent knowledge spaces as fallback.',
867
276
  '- Do not perform destructive, costly, externally visible, or secret-handling actions without explicit approval.',
868
277
  '- Do not request WRFC unless this scheduled routine explicitly delegates build/fix/review work to GoodVibes TUI.',
869
278
  '- Summarize what was checked, what changed, and what still needs user review.',
@@ -999,38 +408,6 @@ async function classifyScheduleError(
999
408
  return { ok: false, kind: 'daemon_error', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
1000
409
  }
1001
410
 
1002
- async function classifyScheduleListError(
1003
- error: unknown,
1004
- connection: AgentDaemonConnection,
1005
- ): Promise<RoutineScheduleCorrelationFailure> {
1006
- const message = summarizeError(error);
1007
- const lower = message.toLowerCase();
1008
- if (lower.includes('401') || lower.includes('unauthorized') || lower.includes('auth')) {
1009
- return { ok: false, kind: 'auth_required', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
1010
- }
1011
- if (lower.includes('404') || lower.includes('not found')) {
1012
- const daemon = await fetchDaemonStatus(connection);
1013
- const record = isRecord(daemon.body) ? daemon.body : {};
1014
- const daemonVersion = readString(record, 'version') ?? 'unknown';
1015
- if (daemon.ok && daemonVersion !== SDK_VERSION) {
1016
- return {
1017
- ok: false,
1018
- kind: 'version_mismatch',
1019
- error: `External daemon SDK version ${daemonVersion} does not match Agent SDK pin ${SDK_VERSION}; schedules.list is unavailable.`,
1020
- route: ROUTINE_SCHEDULE_ROUTE,
1021
- baseUrl: connection.baseUrl,
1022
- daemonVersion,
1023
- expectedSdkVersion: SDK_VERSION,
1024
- };
1025
- }
1026
- return { ok: false, kind: 'daemon_route_unavailable', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
1027
- }
1028
- if (lower.includes('fetch') || lower.includes('connect') || lower.includes('econnrefused')) {
1029
- return { ok: false, kind: 'daemon_unavailable', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
1030
- }
1031
- return { ok: false, kind: 'daemon_error', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
1032
- }
1033
-
1034
411
  export async function promoteRoutineToDaemonSchedule(
1035
412
  connection: AgentDaemonConnection,
1036
413
  preview: RoutineSchedulePromotionPreview,
@@ -1060,188 +437,3 @@ export async function promoteRoutineToDaemonSchedule(
1060
437
  return classifyScheduleError(error, connection);
1061
438
  }
1062
439
  }
1063
-
1064
- export async function reconcileRoutineScheduleReceipts(
1065
- connection: AgentDaemonConnection,
1066
- snapshot: RoutineScheduleReceiptSnapshot,
1067
- ): Promise<RoutineScheduleCorrelationResult> {
1068
- if (!connection.token) {
1069
- return {
1070
- ok: false,
1071
- kind: 'auth_required',
1072
- error: `No daemon operator token found at ${connection.tokenPath}`,
1073
- route: ROUTINE_SCHEDULE_ROUTE,
1074
- baseUrl: connection.baseUrl,
1075
- };
1076
- }
1077
- try {
1078
- const sdk = createBrowserGoodVibesSdk({ baseUrl: connection.baseUrl, authToken: connection.token });
1079
- const output = await sdk.operator.invoke(ROUTINE_SCHEDULE_LIST_METHOD, {});
1080
- const schedules = readLiveSchedules(output);
1081
- return {
1082
- ok: true,
1083
- kind: ROUTINE_SCHEDULE_LIST_METHOD,
1084
- route: ROUTINE_SCHEDULE_ROUTE,
1085
- baseUrl: connection.baseUrl,
1086
- scheduleCount: schedules.length,
1087
- receiptCount: snapshot.receipts.length,
1088
- correlations: correlateReceipts(snapshot.receipts, schedules),
1089
- };
1090
- } catch (error) {
1091
- return classifyScheduleListError(error, connection);
1092
- }
1093
- }
1094
-
1095
- export function formatRoutineSchedulePreview(preview: RoutineSchedulePromotionPreview): string {
1096
- const schedule = preview.payload.kind === 'cron'
1097
- ? `${preview.payload.cron}${preview.payload.timezone ? ` [${preview.payload.timezone}]` : ''}`
1098
- : preview.payload.kind === 'every'
1099
- ? String(preview.payload.every)
1100
- : String(preview.payload.at);
1101
- const delivery = preview.payload.delivery;
1102
- const deliveryTargetCount = delivery?.targets.length ?? 0;
1103
- return [
1104
- 'Daemon schedule preview for Agent routine',
1105
- ` routine: ${preview.routineName} (${preview.routineId})`,
1106
- ` route: ${preview.method} ${preview.route}`,
1107
- ` name: ${String(preview.payload.name ?? '(daemon default)')}`,
1108
- ` schedule: ${preview.payload.kind} ${schedule}`,
1109
- ` enabled: ${preview.payload.enabled === false ? 'no' : 'yes'}`,
1110
- ` delivery: ${delivery?.mode ?? 'none'}${deliveryTargetCount > 0 ? ` (${deliveryTargetCount} target${deliveryTargetCount === 1 ? '' : 's'})` : ''}`,
1111
- ' target: external daemon service/main conversation route',
1112
- ' policy: isolated Agent Knowledge only; no default wiki/HomeGraph fallback; no WRFC unless explicitly delegated',
1113
- ' next: rerun with --yes to create this daemon schedule',
1114
- ].join('\n');
1115
- }
1116
-
1117
- export function formatRoutineScheduleSuccess(result: RoutineSchedulePromotionSuccess): string {
1118
- const record: Record<string, unknown> = isRecord(result.schedule) ? result.schedule : {};
1119
- const id = readString(record, 'id') ?? '(unknown)';
1120
- const status = readString(record, 'status') ?? (record.enabled === false ? 'paused' : 'enabled');
1121
- return [
1122
- 'Created daemon schedule for Agent routine',
1123
- ` routine: ${result.routineName} (${result.routineId})`,
1124
- ` schedule: ${id}`,
1125
- ` status: ${status}`,
1126
- ` route: ${result.kind} ${result.route}`,
1127
- ' next: inspect with /schedule list or daemon schedule observability',
1128
- ].join('\n');
1129
- }
1130
-
1131
- export function formatRoutineScheduleReceipts(snapshot: RoutineScheduleReceiptSnapshot, limit = 10): string {
1132
- const receipts = snapshot.receipts.slice(0, Math.max(1, limit));
1133
- if (snapshot.receipts.length === 0) {
1134
- return [
1135
- 'Agent routine schedule receipts',
1136
- ` store: ${snapshot.path}`,
1137
- ' No routine schedule promotions have been recorded yet.',
1138
- ' Create one with /schedule promote-routine <routine-id> --cron <expr> --yes.',
1139
- ].join('\n');
1140
- }
1141
- return [
1142
- `Agent routine schedule receipts (${snapshot.receipts.length})`,
1143
- ` store: ${snapshot.path}`,
1144
- ...receipts.map((receipt) => {
1145
- const schedule = receipt.scheduleId ? ` schedule=${receipt.scheduleId}` : '';
1146
- const failure = receipt.status === 'failed' && receipt.failureKind ? ` failure=${receipt.failureKind}` : '';
1147
- return ` ${receipt.id} ${receipt.status} ${receipt.scheduleKind} ${receipt.scheduleValue} routine=${receipt.routineId}${schedule}${failure}`;
1148
- }),
1149
- snapshot.receipts.length > receipts.length ? ` ...${snapshot.receipts.length - receipts.length} more` : '',
1150
- ].filter((line): line is string => Boolean(line)).join('\n');
1151
- }
1152
-
1153
- export function formatRoutineScheduleReceipt(receipt: RoutineScheduleReceipt): string {
1154
- return [
1155
- `Agent routine schedule receipt ${receipt.id}`,
1156
- ` created: ${receipt.createdAt}`,
1157
- ` status: ${receipt.status}`,
1158
- ` routine: ${receipt.routineName} (${receipt.routineId})`,
1159
- ` route: ${receipt.method} ${receipt.route}`,
1160
- ` daemon: ${receipt.daemonBaseUrl}`,
1161
- ` schedule: ${receipt.scheduleName}${receipt.scheduleId ? ` (${receipt.scheduleId})` : ''}`,
1162
- receipt.scheduleStatus ? ` schedule status: ${receipt.scheduleStatus}` : '',
1163
- ` cadence: ${receipt.scheduleKind} ${receipt.scheduleValue}${receipt.timezone ? ` [${receipt.timezone}]` : ''}`,
1164
- ` enabled: ${receipt.enabled ? 'yes' : 'no'}`,
1165
- receipt.provider ? ` provider: ${receipt.provider}` : '',
1166
- receipt.model ? ` model: ${receipt.model}` : '',
1167
- ` target: ${receipt.target.kind ?? 'unknown'}${receipt.target.surfaceKind ? `/${receipt.target.surfaceKind}` : ''}`,
1168
- receipt.deliveryMode ? ` delivery: ${receipt.deliveryMode}` : '',
1169
- ...(receipt.deliveryTargets ?? []).map((target) => ` delivery target: ${target.kind}${target.surfaceKind ? `/${target.surfaceKind}` : ''}${target.routeId ? ` route=${target.routeId}` : ''}${target.address ? ` address=${target.address}` : ''}${target.label ? ` label=${target.label}` : ''}`),
1170
- receipt.failureKind ? ` failure: ${receipt.failureKind}` : '',
1171
- receipt.failureError ? ` error: ${receipt.failureError}` : '',
1172
- ].filter((line): line is string => Boolean(line)).join('\n');
1173
- }
1174
-
1175
- export function formatRoutineScheduleCorrelation(result: RoutineScheduleCorrelationResult, limit = 10): string {
1176
- if (!result.ok) {
1177
- return [
1178
- `Daemon schedule reconciliation error: ${result.kind}`,
1179
- ` ${result.error}`,
1180
- result.baseUrl ? ` daemon: ${result.baseUrl}` : null,
1181
- ` route: ${ROUTINE_SCHEDULE_LIST_METHOD} ${result.route}`,
1182
- result.kind === 'auth_required'
1183
- ? ' next: pair/authenticate with the externally managed GoodVibes daemon, then retry.'
1184
- : null,
1185
- result.kind === 'daemon_unavailable'
1186
- ? ' next: start/restart the external GoodVibes daemon from TUI or daemon host tooling; Agent does not own daemon lifecycle.'
1187
- : null,
1188
- result.kind === 'version_mismatch' || result.kind === 'daemon_route_unavailable'
1189
- ? ' next: update/restart the external GoodVibes daemon so public schedules.list is available.'
1190
- : null,
1191
- ].filter((line): line is string => Boolean(line)).join('\n');
1192
- }
1193
- const correlations = result.correlations.slice(0, Math.max(1, limit));
1194
- if (result.receiptCount === 0) {
1195
- return [
1196
- 'Agent routine schedule reconciliation',
1197
- ` daemon: ${result.baseUrl}`,
1198
- ` route: ${result.kind} ${result.route}`,
1199
- ` live schedules: ${result.scheduleCount}`,
1200
- ' No local routine promotion receipts exist yet.',
1201
- ' Create one with /schedule promote-routine <routine-id> --cron <expr> --yes.',
1202
- ].join('\n');
1203
- }
1204
- const matched = result.correlations.filter((entry) => entry.liveStatus === 'matched').length;
1205
- const missing = result.correlations.filter((entry) => entry.liveStatus === 'missing').length;
1206
- const failed = result.correlations.filter((entry) => entry.liveStatus === 'failed-receipt').length;
1207
- return [
1208
- 'Agent routine schedule reconciliation',
1209
- ` daemon: ${result.baseUrl}`,
1210
- ` route: ${result.kind} ${result.route}`,
1211
- ` receipts: ${result.receiptCount}; live schedules: ${result.scheduleCount}; matched: ${matched}; missing: ${missing}; failed receipts: ${failed}`,
1212
- ...correlations.map((entry) => {
1213
- const receipt = entry.receipt;
1214
- const schedule = entry.schedule;
1215
- const live = schedule
1216
- ? ` live=${schedule.id} status=${schedule.status ?? (schedule.enabled === false ? 'paused' : 'enabled')}`
1217
- : '';
1218
- const runs = schedule && schedule.runCount !== undefined
1219
- ? ` runs=${schedule.runCount}/${schedule.successCount ?? 0}/${schedule.failureCount ?? 0}`
1220
- : '';
1221
- const next = schedule?.nextRunAt ? ` next=${new Date(schedule.nextRunAt).toISOString()}` : '';
1222
- return ` ${receipt.id} ${entry.liveStatus} reason=${entry.matchReason} routine=${receipt.routineId} receiptSchedule=${receipt.scheduleId ?? '(none)'}${live}${runs}${next}`;
1223
- }),
1224
- result.correlations.length > correlations.length ? ` ...${result.correlations.length - correlations.length} more` : '',
1225
- ].filter((line): line is string => Boolean(line)).join('\n');
1226
- }
1227
-
1228
- export function formatRoutineScheduleFailure(failure: RoutineSchedulePromotionFailure): string {
1229
- return [
1230
- `Daemon schedule error: ${failure.kind}`,
1231
- ` ${failure.error}`,
1232
- failure.baseUrl ? ` daemon: ${failure.baseUrl}` : null,
1233
- ` route: ${ROUTINE_SCHEDULE_METHOD} ${failure.route}`,
1234
- failure.kind === 'version_mismatch' && failure.daemonVersion && failure.expectedSdkVersion
1235
- ? ` versions: daemon=${failure.daemonVersion} expected=${failure.expectedSdkVersion}`
1236
- : null,
1237
- failure.kind === 'auth_required'
1238
- ? ' next: pair/authenticate with the externally managed GoodVibes daemon, then retry with --yes.'
1239
- : null,
1240
- failure.kind === 'daemon_unavailable'
1241
- ? ' next: start/restart the external GoodVibes daemon from TUI or daemon host tooling; Agent does not own daemon lifecycle.'
1242
- : null,
1243
- failure.kind === 'version_mismatch' || failure.kind === 'daemon_route_unavailable'
1244
- ? ' next: update/restart the external GoodVibes daemon so public schedules.create is available.'
1245
- : null,
1246
- ].filter((line): line is string => Boolean(line)).join('\n');
1247
- }