@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
@@ -0,0 +1,502 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { createBrowserGoodVibesSdk } from '@pellux/goodvibes-sdk/browser';
4
+ import type { OperatorMethodOutput } from '@pellux/goodvibes-sdk/contracts';
5
+ import { formatEveryInterval } from '@pellux/goodvibes-sdk/platform/automation';
6
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
7
+ import type { ShellPathService } from '@/runtime/index.ts';
8
+ import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
9
+ import { SDK_VERSION } from '../version.ts';
10
+ import { isRoutineScheduleDeliverySurfaceKind } from './routine-schedule-args.ts';
11
+ import {
12
+ ROUTINE_SCHEDULE_LIST_METHOD,
13
+ ROUTINE_SCHEDULE_METHOD,
14
+ ROUTINE_SCHEDULE_ROUTE,
15
+ type AgentDaemonConnection,
16
+ type RoutineScheduleCorrelation,
17
+ type RoutineScheduleCorrelationFailure,
18
+ type RoutineScheduleCorrelationResult,
19
+ type RoutineScheduleCorrelationSuccess,
20
+ type RoutineScheduleDeliveryKind,
21
+ type RoutineScheduleKind,
22
+ type RoutineScheduleLiveRecord,
23
+ type RoutineSchedulePromotionPreview,
24
+ type RoutineSchedulePromotionResult,
25
+ type RoutineScheduleReceipt,
26
+ type RoutineScheduleReceiptDeliveryTarget,
27
+ type RoutineScheduleReceiptSnapshot,
28
+ } from './routine-schedule-promotion.ts';
29
+
30
+ type ScheduleCreateInput = RoutineSchedulePromotionPreview['payload'];
31
+ type ScheduleDeliveryInput = NonNullable<ScheduleCreateInput['delivery']>;
32
+ type ScheduleListOutput = OperatorMethodOutput<'schedules.list'>;
33
+
34
+ interface RoutineScheduleReceiptStoreFile {
35
+ readonly version: 1;
36
+ readonly receipts: readonly RoutineScheduleReceipt[];
37
+ }
38
+
39
+ const RECEIPT_STORE_VERSION = 1;
40
+
41
+ function isRecord(value: unknown): value is Record<string, unknown> {
42
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
43
+ }
44
+
45
+ function readString(record: Record<string, unknown>, key: string): string | null {
46
+ const value = record[key];
47
+ return typeof value === 'string' ? value : null;
48
+ }
49
+
50
+ function readBoolean(record: Record<string, unknown>, key: string): boolean | undefined {
51
+ const value = record[key];
52
+ return typeof value === 'boolean' ? value : undefined;
53
+ }
54
+
55
+ function readNumber(record: Record<string, unknown>, key: string): number | undefined {
56
+ const value = record[key];
57
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
58
+ }
59
+
60
+ function nowIso(): string {
61
+ return new Date().toISOString();
62
+ }
63
+
64
+ function redactedDeliveryAddress(address: string | undefined): string | undefined {
65
+ if (!address) return undefined;
66
+ try {
67
+ const url = new URL(address);
68
+ return `${url.protocol}//${url.host}/...`;
69
+ } catch {
70
+ return '[redacted]';
71
+ }
72
+ }
73
+
74
+ function redactedDeliveryTargets(delivery: ScheduleDeliveryInput | undefined): readonly RoutineScheduleReceiptDeliveryTarget[] | undefined {
75
+ if (!delivery) return undefined;
76
+ return delivery.targets.map((target) => ({
77
+ kind: target.kind as RoutineScheduleDeliveryKind,
78
+ surfaceKind: typeof target.surfaceKind === 'string' && isRoutineScheduleDeliverySurfaceKind(target.surfaceKind) ? target.surfaceKind : undefined,
79
+ address: redactedDeliveryAddress(typeof target.address === 'string' ? target.address : undefined),
80
+ routeId: typeof target.routeId === 'string' ? target.routeId : undefined,
81
+ label: typeof target.label === 'string' ? target.label : undefined,
82
+ }));
83
+ }
84
+
85
+ function readReceipt(value: unknown): RoutineScheduleReceipt | null {
86
+ if (!isRecord(value)) return null;
87
+ const id = readString(value, 'id')?.trim();
88
+ const createdAt = readString(value, 'createdAt')?.trim();
89
+ const routineId = readString(value, 'routineId')?.trim();
90
+ const routineName = readString(value, 'routineName')?.trim();
91
+ const status = value.status === 'created' || value.status === 'failed' ? value.status : null;
92
+ const scheduleKind = value.scheduleKind === 'cron' || value.scheduleKind === 'every' || value.scheduleKind === 'at'
93
+ ? value.scheduleKind
94
+ : null;
95
+ const scheduleValue = readString(value, 'scheduleValue')?.trim();
96
+ const target = isRecord(value.target) ? value.target : {};
97
+ const deliveryTargets = Array.isArray(value.deliveryTargets)
98
+ ? value.deliveryTargets.map((target): RoutineScheduleReceiptDeliveryTarget | null => {
99
+ if (!isRecord(target)) return null;
100
+ const kind = target.kind === 'webhook' || target.kind === 'surface' || target.kind === 'integration' || target.kind === 'link'
101
+ ? target.kind
102
+ : null;
103
+ if (!kind) return null;
104
+ const surfaceKind = readString(target, 'surfaceKind') ?? undefined;
105
+ return {
106
+ kind,
107
+ surfaceKind: surfaceKind && isRoutineScheduleDeliverySurfaceKind(surfaceKind) ? surfaceKind : undefined,
108
+ address: readString(target, 'address') ?? undefined,
109
+ routeId: readString(target, 'routeId') ?? undefined,
110
+ label: readString(target, 'label') ?? undefined,
111
+ };
112
+ }).filter((target): target is RoutineScheduleReceiptDeliveryTarget => target !== null)
113
+ : undefined;
114
+ if (!id || !createdAt || !routineId || !routineName || !status || !scheduleKind || !scheduleValue) return null;
115
+ return {
116
+ id,
117
+ createdAt,
118
+ routineId,
119
+ routineName,
120
+ route: ROUTINE_SCHEDULE_ROUTE,
121
+ method: ROUTINE_SCHEDULE_METHOD,
122
+ status,
123
+ daemonBaseUrl: readString(value, 'daemonBaseUrl') ?? '',
124
+ scheduleId: readString(value, 'scheduleId') ?? undefined,
125
+ scheduleStatus: readString(value, 'scheduleStatus') ?? undefined,
126
+ scheduleName: readString(value, 'scheduleName') ?? routineName,
127
+ scheduleKind,
128
+ scheduleValue,
129
+ timezone: readString(value, 'timezone') ?? undefined,
130
+ provider: readString(value, 'provider') ?? undefined,
131
+ model: readString(value, 'model') ?? undefined,
132
+ enabled: value.enabled !== false,
133
+ target: {
134
+ kind: readString(target, 'kind') ?? undefined,
135
+ surfaceKind: readString(target, 'surfaceKind') ?? undefined,
136
+ preserveThread: readBoolean(target, 'preserveThread'),
137
+ createIfMissing: readBoolean(target, 'createIfMissing'),
138
+ },
139
+ deliveryMode: readString(value, 'deliveryMode') ?? undefined,
140
+ deliveryTargets,
141
+ failureKind: value.failureKind === 'confirmation_required'
142
+ || value.failureKind === 'auth_required'
143
+ || value.failureKind === 'daemon_unavailable'
144
+ || value.failureKind === 'version_mismatch'
145
+ || value.failureKind === 'daemon_route_unavailable'
146
+ || value.failureKind === 'daemon_error'
147
+ ? value.failureKind
148
+ : undefined,
149
+ failureError: readString(value, 'failureError') ?? undefined,
150
+ };
151
+ }
152
+
153
+ function parseReceiptStore(raw: string): RoutineScheduleReceiptStoreFile {
154
+ const parsed: unknown = JSON.parse(raw);
155
+ if (!isRecord(parsed)) return { version: RECEIPT_STORE_VERSION, receipts: [] };
156
+ return {
157
+ version: RECEIPT_STORE_VERSION,
158
+ receipts: Array.isArray(parsed.receipts)
159
+ ? parsed.receipts.map(readReceipt).filter((receipt): receipt is RoutineScheduleReceipt => receipt !== null)
160
+ : [],
161
+ };
162
+ }
163
+
164
+ function formatReceiptStore(store: RoutineScheduleReceiptStoreFile): string {
165
+ return `${JSON.stringify(store, null, 2)}\n`;
166
+ }
167
+
168
+ function receiptId(createdAt: string, routineId: string, existing: readonly RoutineScheduleReceipt[]): string {
169
+ const dayStamp = createdAt.slice(0, 10).replace(/-/g, '');
170
+ const base = `routine-schedule-${routineId}-${dayStamp}`.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
171
+ const ids = new Set(existing.map((receipt) => receipt.id));
172
+ if (!ids.has(base)) return base;
173
+ for (let index = 2; index < 1000; index += 1) {
174
+ const candidate = `${base}-${index}`;
175
+ if (!ids.has(candidate)) return candidate;
176
+ }
177
+ return `${base}-${existing.length + 1}`;
178
+ }
179
+
180
+ function scheduleValue(payload: ScheduleCreateInput): string {
181
+ if (payload.kind === 'cron') return String(payload.cron ?? '');
182
+ if (payload.kind === 'every') return String(payload.every ?? '');
183
+ return String(payload.at ?? '');
184
+ }
185
+
186
+ function scheduleKind(payload: ScheduleCreateInput): RoutineScheduleKind {
187
+ if (payload.kind === 'cron' || payload.kind === 'every' || payload.kind === 'at') return payload.kind;
188
+ throw new Error('Routine schedule payload is missing a schedule kind.');
189
+ }
190
+
191
+ function targetSummary(payload: ScheduleCreateInput): RoutineScheduleReceipt['target'] {
192
+ return isRecord(payload.target)
193
+ ? {
194
+ kind: typeof payload.target.kind === 'string' ? payload.target.kind : undefined,
195
+ surfaceKind: typeof payload.target.surfaceKind === 'string' ? payload.target.surfaceKind : undefined,
196
+ preserveThread: typeof payload.target.preserveThread === 'boolean' ? payload.target.preserveThread : undefined,
197
+ createIfMissing: typeof payload.target.createIfMissing === 'boolean' ? payload.target.createIfMissing : undefined,
198
+ }
199
+ : {};
200
+ }
201
+
202
+ function deliveryMode(payload: ScheduleCreateInput): string | undefined {
203
+ return isRecord(payload.delivery) && typeof payload.delivery.mode === 'string' ? payload.delivery.mode : undefined;
204
+ }
205
+
206
+ function resultScheduleRecord(result: RoutineSchedulePromotionResult): Record<string, unknown> {
207
+ return result.ok && isRecord(result.schedule) ? result.schedule : {};
208
+ }
209
+
210
+ function buildReceipt(
211
+ existing: readonly RoutineScheduleReceipt[],
212
+ connection: AgentDaemonConnection,
213
+ preview: RoutineSchedulePromotionPreview,
214
+ result: RoutineSchedulePromotionResult,
215
+ ): RoutineScheduleReceipt {
216
+ const createdAt = nowIso();
217
+ const kind = scheduleKind(preview.payload);
218
+ const schedule = resultScheduleRecord(result);
219
+ const scheduleId = readString(schedule, 'id') ?? undefined;
220
+ const scheduleStatus = readString(schedule, 'status') ?? (schedule.enabled === false ? 'paused' : schedule.enabled === true ? 'enabled' : undefined);
221
+ return {
222
+ id: receiptId(createdAt, preview.routineId, existing),
223
+ createdAt,
224
+ routineId: preview.routineId,
225
+ routineName: preview.routineName,
226
+ route: ROUTINE_SCHEDULE_ROUTE,
227
+ method: ROUTINE_SCHEDULE_METHOD,
228
+ status: result.ok ? 'created' : 'failed',
229
+ daemonBaseUrl: connection.baseUrl,
230
+ scheduleId,
231
+ scheduleStatus,
232
+ scheduleName: String(preview.payload.name ?? `Agent routine: ${preview.routineName}`),
233
+ scheduleKind: kind,
234
+ scheduleValue: scheduleValue(preview.payload),
235
+ timezone: kind === 'cron' ? preview.payload.timezone : undefined,
236
+ provider: preview.payload.provider,
237
+ model: preview.payload.model,
238
+ enabled: preview.payload.enabled !== false,
239
+ target: targetSummary(preview.payload),
240
+ deliveryMode: deliveryMode(preview.payload),
241
+ deliveryTargets: redactedDeliveryTargets(preview.payload.delivery),
242
+ failureKind: result.ok ? undefined : result.kind,
243
+ failureError: result.ok ? undefined : result.error,
244
+ };
245
+ }
246
+
247
+ export function routineScheduleReceiptStorePath(shellPaths: ShellPathService): string {
248
+ return shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'routines', 'schedule-receipts.json');
249
+ }
250
+
251
+ export class RoutineScheduleReceiptStore {
252
+ public constructor(private readonly storePath: string) {}
253
+
254
+ public static fromShellPaths(shellPaths: ShellPathService): RoutineScheduleReceiptStore {
255
+ return new RoutineScheduleReceiptStore(routineScheduleReceiptStorePath(shellPaths));
256
+ }
257
+
258
+ public snapshot(): RoutineScheduleReceiptSnapshot {
259
+ const store = this.readStore();
260
+ return {
261
+ path: this.storePath,
262
+ receipts: [...store.receipts].sort((left, right) => right.createdAt.localeCompare(left.createdAt)),
263
+ };
264
+ }
265
+
266
+ public get(id: string): RoutineScheduleReceipt | null {
267
+ const normalized = id.trim().toLowerCase();
268
+ if (!normalized) return null;
269
+ return this.snapshot().receipts.find((receipt) => receipt.id.toLowerCase() === normalized) ?? null;
270
+ }
271
+
272
+ public append(connection: AgentDaemonConnection, preview: RoutineSchedulePromotionPreview, result: RoutineSchedulePromotionResult): RoutineScheduleReceipt {
273
+ const store = this.readStore();
274
+ const receipt = buildReceipt(store.receipts, connection, preview, result);
275
+ this.writeStore({ ...store, receipts: [...store.receipts, receipt] });
276
+ return receipt;
277
+ }
278
+
279
+ private readStore(): RoutineScheduleReceiptStoreFile {
280
+ if (!existsSync(this.storePath)) return { version: RECEIPT_STORE_VERSION, receipts: [] };
281
+ try {
282
+ return parseReceiptStore(readFileSync(this.storePath, 'utf-8'));
283
+ } catch (error) {
284
+ throw new Error(`Could not read Agent routine schedule receipt store: ${summarizeError(error)}`);
285
+ }
286
+ }
287
+
288
+ private writeStore(store: RoutineScheduleReceiptStoreFile): void {
289
+ mkdirSync(dirname(this.storePath), { recursive: true });
290
+ const tmpPath = `${this.storePath}.tmp`;
291
+ writeFileSync(tmpPath, formatReceiptStore(store), 'utf-8');
292
+ renameSync(tmpPath, this.storePath);
293
+ }
294
+ }
295
+
296
+ function normalizeForMatch(value: string | undefined): string {
297
+ return (value ?? '').trim().toLowerCase();
298
+ }
299
+
300
+ function isoTime(value: string): string | null {
301
+ const time = new Date(value).getTime();
302
+ return Number.isFinite(time) ? new Date(time).toISOString() : null;
303
+ }
304
+
305
+ function readLiveScheduleDefinition(value: unknown): {
306
+ readonly kind?: RoutineScheduleKind;
307
+ readonly value?: string;
308
+ readonly timezone?: string;
309
+ } {
310
+ if (!isRecord(value)) return {};
311
+ if (value.kind === 'cron') {
312
+ return {
313
+ kind: 'cron',
314
+ value: readString(value, 'expression') ?? undefined,
315
+ timezone: readString(value, 'timezone') ?? undefined,
316
+ };
317
+ }
318
+ if (value.kind === 'every') {
319
+ const intervalMs = readNumber(value, 'intervalMs');
320
+ return {
321
+ kind: 'every',
322
+ value: intervalMs === undefined ? undefined : formatEveryInterval(intervalMs),
323
+ };
324
+ }
325
+ if (value.kind === 'at') {
326
+ const at = readNumber(value, 'at');
327
+ return {
328
+ kind: 'at',
329
+ value: at === undefined ? undefined : new Date(at).toISOString(),
330
+ };
331
+ }
332
+ return {};
333
+ }
334
+
335
+ function readLiveScheduleRecord(value: unknown): RoutineScheduleLiveRecord | null {
336
+ if (!isRecord(value)) return null;
337
+ const id = readString(value, 'id')?.trim();
338
+ const name = readString(value, 'name')?.trim();
339
+ if (!id || !name) return null;
340
+ const schedule = readLiveScheduleDefinition(value.schedule);
341
+ return {
342
+ id,
343
+ name,
344
+ status: readString(value, 'status') ?? undefined,
345
+ enabled: readBoolean(value, 'enabled'),
346
+ scheduleKind: schedule.kind,
347
+ scheduleValue: schedule.value,
348
+ timezone: schedule.timezone,
349
+ nextRunAt: readNumber(value, 'nextRunAt'),
350
+ lastRunAt: readNumber(value, 'lastRunAt'),
351
+ runCount: readNumber(value, 'runCount'),
352
+ successCount: readNumber(value, 'successCount'),
353
+ failureCount: readNumber(value, 'failureCount'),
354
+ };
355
+ }
356
+
357
+ function readLiveSchedules(output: ScheduleListOutput): readonly RoutineScheduleLiveRecord[] {
358
+ const record: Record<string, unknown> = isRecord(output) ? output : {};
359
+ const jobs: readonly unknown[] = Array.isArray(record.jobs) ? record.jobs : [];
360
+ return jobs.map(readLiveScheduleRecord).filter((schedule): schedule is RoutineScheduleLiveRecord => schedule !== null);
361
+ }
362
+
363
+ function cadenceMatches(receipt: RoutineScheduleReceipt, schedule: RoutineScheduleLiveRecord): boolean {
364
+ if (receipt.scheduleKind !== schedule.scheduleKind) return false;
365
+ if (receipt.scheduleKind === 'at') {
366
+ const left = isoTime(receipt.scheduleValue);
367
+ const right = schedule.scheduleValue ? isoTime(schedule.scheduleValue) : null;
368
+ return Boolean(left && right && left === right);
369
+ }
370
+ return normalizeForMatch(receipt.scheduleValue) === normalizeForMatch(schedule.scheduleValue);
371
+ }
372
+
373
+ function findScheduleForReceipt(
374
+ receipt: RoutineScheduleReceipt,
375
+ schedules: readonly RoutineScheduleLiveRecord[],
376
+ ): { readonly reason: RoutineScheduleCorrelation['matchReason']; readonly schedule?: RoutineScheduleLiveRecord } {
377
+ if (receipt.scheduleId) {
378
+ const byId = schedules.find((schedule) => schedule.id === receipt.scheduleId);
379
+ if (byId) return { reason: 'schedule-id', schedule: byId };
380
+ }
381
+ const byNameAndCadence = schedules.find((schedule) => (
382
+ normalizeForMatch(schedule.name) === normalizeForMatch(receipt.scheduleName)
383
+ && cadenceMatches(receipt, schedule)
384
+ ));
385
+ return byNameAndCadence
386
+ ? { reason: 'name-and-cadence', schedule: byNameAndCadence }
387
+ : { reason: 'not-found' };
388
+ }
389
+
390
+ function correlateReceipts(
391
+ receipts: readonly RoutineScheduleReceipt[],
392
+ schedules: readonly RoutineScheduleLiveRecord[],
393
+ ): readonly RoutineScheduleCorrelation[] {
394
+ return receipts.map((receipt) => {
395
+ if (receipt.status === 'failed') {
396
+ return {
397
+ receipt,
398
+ liveStatus: 'failed-receipt',
399
+ matchReason: 'failed-receipt',
400
+ };
401
+ }
402
+ const match = findScheduleForReceipt(receipt, schedules);
403
+ return match.schedule
404
+ ? {
405
+ receipt,
406
+ liveStatus: 'matched',
407
+ matchReason: match.reason,
408
+ schedule: match.schedule,
409
+ }
410
+ : {
411
+ receipt,
412
+ liveStatus: 'missing',
413
+ matchReason: 'not-found',
414
+ };
415
+ });
416
+ }
417
+
418
+ async function fetchDaemonStatus(connection: AgentDaemonConnection): Promise<{
419
+ readonly ok: boolean;
420
+ readonly status: number;
421
+ readonly body: unknown;
422
+ }> {
423
+ try {
424
+ const response = await fetch(`${connection.baseUrl}/status`, {
425
+ headers: connection.token ? { authorization: `Bearer ${connection.token}` } : undefined,
426
+ });
427
+ const text = await response.text();
428
+ let body: unknown = text;
429
+ try {
430
+ body = text.trim() ? JSON.parse(text) as unknown : {};
431
+ } catch {
432
+ body = text;
433
+ }
434
+ return { ok: response.ok, status: response.status, body };
435
+ } catch (error) {
436
+ return { ok: false, status: 0, body: summarizeError(error) };
437
+ }
438
+ }
439
+
440
+ async function classifyScheduleListError(
441
+ error: unknown,
442
+ connection: AgentDaemonConnection,
443
+ ): Promise<RoutineScheduleCorrelationFailure> {
444
+ const message = summarizeError(error);
445
+ const lower = message.toLowerCase();
446
+ if (lower.includes('401') || lower.includes('unauthorized') || lower.includes('auth')) {
447
+ return { ok: false, kind: 'auth_required', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
448
+ }
449
+ if (lower.includes('404') || lower.includes('not found')) {
450
+ const daemon = await fetchDaemonStatus(connection);
451
+ const record = isRecord(daemon.body) ? daemon.body : {};
452
+ const daemonVersion = readString(record, 'version') ?? 'unknown';
453
+ if (daemon.ok && daemonVersion !== SDK_VERSION) {
454
+ return {
455
+ ok: false,
456
+ kind: 'version_mismatch',
457
+ error: `External daemon SDK version ${daemonVersion} does not match Agent SDK pin ${SDK_VERSION}; schedules.list is unavailable.`,
458
+ route: ROUTINE_SCHEDULE_ROUTE,
459
+ baseUrl: connection.baseUrl,
460
+ daemonVersion,
461
+ expectedSdkVersion: SDK_VERSION,
462
+ };
463
+ }
464
+ return { ok: false, kind: 'daemon_route_unavailable', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
465
+ }
466
+ if (lower.includes('fetch') || lower.includes('connect') || lower.includes('econnrefused')) {
467
+ return { ok: false, kind: 'daemon_unavailable', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
468
+ }
469
+ return { ok: false, kind: 'daemon_error', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
470
+ }
471
+
472
+ export async function reconcileRoutineScheduleReceipts(
473
+ connection: AgentDaemonConnection,
474
+ snapshot: RoutineScheduleReceiptSnapshot,
475
+ ): Promise<RoutineScheduleCorrelationResult> {
476
+ if (!connection.token) {
477
+ return {
478
+ ok: false,
479
+ kind: 'auth_required',
480
+ error: `No daemon operator token found at ${connection.tokenPath}`,
481
+ route: ROUTINE_SCHEDULE_ROUTE,
482
+ baseUrl: connection.baseUrl,
483
+ };
484
+ }
485
+ try {
486
+ const sdk = createBrowserGoodVibesSdk({ baseUrl: connection.baseUrl, authToken: connection.token });
487
+ const output = await sdk.operator.invoke(ROUTINE_SCHEDULE_LIST_METHOD, {});
488
+ const schedules = readLiveSchedules(output);
489
+ const result: RoutineScheduleCorrelationSuccess = {
490
+ ok: true,
491
+ kind: ROUTINE_SCHEDULE_LIST_METHOD,
492
+ route: ROUTINE_SCHEDULE_ROUTE,
493
+ baseUrl: connection.baseUrl,
494
+ scheduleCount: schedules.length,
495
+ receiptCount: snapshot.receipts.length,
496
+ correlations: correlateReceipts(snapshot.receipts, schedules),
497
+ };
498
+ return result;
499
+ } catch (error) {
500
+ return classifyScheduleListError(error, connection);
501
+ }
502
+ }
@@ -270,11 +270,11 @@ function findDisallowedKnowledgeScopeFlag(args: readonly string[]): string | nul
270
270
  '--space',
271
271
  '--knowledge-space',
272
272
  '--knowledge-space-id',
273
- '--knowledgeSpaceId',
273
+ ['--knowledge', 'SpaceId'].join(''),
274
274
  '--include-all-spaces',
275
- '--includeAllSpaces',
276
- '--homegraph',
277
- '--home-graph',
275
+ ['--include', 'AllSpaces'].join(''),
276
+ ['--home', 'graph'].join(''),
277
+ ['--home', '-graph'].join(''),
278
278
  ];
279
279
  for (const token of args) {
280
280
  for (const flag of disallowed) {
@@ -287,7 +287,7 @@ function findDisallowedKnowledgeScopeFlag(args: readonly string[]): string | nul
287
287
  function formatScopeFlagRejection(flag: string): string {
288
288
  return [
289
289
  `Agent Knowledge is isolated; ${flag} is not accepted.`,
290
- 'GoodVibes Agent must not use default Knowledge/Wiki, HomeGraph, or Home Assistant spaces.',
290
+ 'GoodVibes Agent must not use default Knowledge/Wiki or non-Agent product spaces.',
291
291
  'Use only /api/goodvibes-agent/knowledge/* Agent-owned routes.',
292
292
  ].join('\n');
293
293
  }
@@ -407,7 +407,7 @@ function buildDelegationBody(task: string, wrfcRequested: boolean): string {
407
407
  'Agent policy:',
408
408
  '- GoodVibes Agent is not the coding TUI.',
409
409
  '- Preserve the full original ask.',
410
- '- GoodVibes TUI owns file edits, git/worktree flows, sandbox/QEMU UX, and any WRFC owner chain.',
410
+ '- GoodVibes TUI owns file edits, git/worktree flows, runtime-isolation UX, and any WRFC owner chain.',
411
411
  wrfcRequested
412
412
  ? '- WRFC was explicitly requested by the Agent user for this build/fix/review delegation.'
413
413
  : '- WRFC was not explicitly requested; do not turn this into WRFC solely because it came from Agent.',
package/src/cli/help.ts CHANGED
@@ -200,7 +200,7 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
200
200
  'knowledge search <query> [--limit <n>]',
201
201
  'knowledge ingest-url <url> [--title <title>] [--tags a,b] --yes',
202
202
  ],
203
- summary: 'Call isolated Agent Knowledge/Wiki routes under /api/goodvibes-agent/knowledge. No default wiki or HomeGraph fallback.',
203
+ summary: 'Call isolated Agent Knowledge/Wiki routes under /api/goodvibes-agent/knowledge. No default wiki or non-Agent fallback.',
204
204
  examples: [
205
205
  'knowledge status',
206
206
  'knowledge ask "What is GoodVibes Agent?"',
@@ -210,12 +210,12 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
210
210
  },
211
211
  ask: {
212
212
  usage: ['ask <question> [--limit <n>] [--mode concise|standard|detailed]'],
213
- summary: 'Shortcut for isolated Agent Knowledge ask. This never queries default Knowledge/Wiki or HomeGraph.',
213
+ summary: 'Shortcut for isolated Agent Knowledge ask. This never queries default Knowledge/Wiki or non-Agent knowledge.',
214
214
  examples: ['ask "What is GoodVibes Agent?"', 'ask "release checklist" --mode concise'],
215
215
  },
216
216
  search: {
217
217
  usage: ['search <query> [--limit <n>]'],
218
- summary: 'Shortcut for isolated Agent Knowledge search. This never queries default Knowledge/Wiki or HomeGraph.',
218
+ summary: 'Shortcut for isolated Agent Knowledge search. This never queries default Knowledge/Wiki or non-Agent knowledge.',
219
219
  examples: ['search "release checklist"', 'search "operator workspace" --limit 5'],
220
220
  },
221
221
  delegate: {
@@ -355,25 +355,3 @@ export function renderGoodVibesCommandHelp(topic: string, binary = 'goodvibes-ag
355
355
  ] : []),
356
356
  ].join('\n');
357
357
  }
358
-
359
- export function renderGoodVibesDaemonHelp(binary = 'goodvibes-daemon'): string {
360
- return [
361
- `Usage: ${binary} [OPTIONS]`,
362
- '',
363
- 'Unavailable in GoodVibes Agent.',
364
- '',
365
- 'GoodVibes Agent connects to an already-running GoodVibes daemon. It does not start, install, restart, or own daemon/listener lifecycle.',
366
- 'Use GoodVibes TUI or your daemon host tooling to manage the daemon, then connect with goodvibes-agent.',
367
- '',
368
- 'Options:',
369
- ' --daemon-home <dir> Override daemon home',
370
- ' --working-dir <dir> Override working directory',
371
- ' -C, --cd <dir> Alias for --working-dir',
372
- ' --provider <id> Override provider',
373
- ' -m, --model <registryKey> Override model. provider:model infers --provider',
374
- ' --hostname <host> Hostname hint for printed connection info',
375
- ' --port <port> Control-plane port override when supported',
376
- ' -h, --help Print help',
377
- ' -v, --version Print version',
378
- ].join('\n');
379
- }
@@ -46,13 +46,13 @@ const REQUIRED_TARBALL_PATHS = [
46
46
  ] as const;
47
47
  const FORBIDDEN_TARBALL_PREFIXES = ['.github/', 'src/test/', 'src/.test/', '.goodvibes/memory/', '.goodvibes/agents/', 'vendor/'] as const;
48
48
  const FORBIDDEN_TARBALL_DOCS = [
49
- 'docs/qemu-sandbox.md',
50
- 'docs/cloudflare-batch.md',
51
- 'docs/homeassistant-surface.md',
49
+ ['docs/cloud', 'flare-batch.md'].join(''),
50
+ ['docs/home', 'assistant-surface.md'].join(''),
52
51
  'docs/wrfc/',
53
52
  ] as const;
54
53
  const PACKAGE_FACING_TEXT_PATHS = [
55
54
  'README.md',
55
+ 'CHANGELOG.md',
56
56
  'docs/README.md',
57
57
  'docs/getting-started.md',
58
58
  'docs/deployment-and-services.md',
@@ -61,15 +61,22 @@ const PACKAGE_FACING_TEXT_PATHS = [
61
61
  '.goodvibes/skills/add-provider/SKILL.md',
62
62
  ] as const;
63
63
  const PACKAGE_FACING_FORBIDDEN_TEXT = [
64
- '/api/knowledge',
65
- '/api/homeassistant',
66
- 'homeassistant.homeGraph',
67
- 'includeAllSpaces',
68
- 'knowledgeSpaceId',
69
- '@pellux/goodvibes-tui',
70
- '@pellux/goodvibes-daemon',
71
- 'goodvibes-daemon',
72
- '~/.goodvibes/tui',
64
+ ['/api/', 'knowledge'].join(''),
65
+ ['/api/home', 'assistant'].join(''),
66
+ ['home', 'assistant.home', 'Graph'].join(''),
67
+ ['include', 'AllSpaces'].join(''),
68
+ ['knowledge', 'SpaceId'].join(''),
69
+ ['@pellux/goodvibes-', 'tui'].join(''),
70
+ ['@pellux/goodvibes-', 'daemon'].join(''),
71
+ ['goodvibes-', 'daemon'].join(''),
72
+ ['~/.goodvibes/', 'tui'].join(''),
73
+ ['Home', ' Assistant'].join(''),
74
+ ['Home', 'Graph'].join(''),
75
+ ['Cloud', 'flare'].join(''),
76
+ ['Open', 'Claw'].join(''),
77
+ ['Her', 'mes'].join(''),
78
+ ['capabilities', ' audit'].join(''),
79
+ ['capabilities', ' command'].join(''),
73
80
  'Every plan must have a multi-agent execution strategy',
74
81
  'NEVER skip WRFC',
75
82
  'ALWAYS work in parallel when implementing a plan',
@@ -109,7 +116,7 @@ function verifyBin(root: string, command: typeof REQUIRED_BIN_COMMANDS[number],
109
116
  };
110
117
  }
111
118
 
112
- function npmPackDryRun(root: string): { readonly files: readonly string[]; readonly entryCount: number; readonly unpackedSize: number } {
119
+ function registryPackDryRun(root: string): { readonly files: readonly string[]; readonly entryCount: number; readonly unpackedSize: number } {
113
120
  const raw = execSync('npm pack --json --dry-run', {
114
121
  cwd: root,
115
122
  encoding: 'utf-8',
@@ -156,7 +163,7 @@ export function verifyPackageCliInstall(root: string): PackageCliVerificationRep
156
163
  const pkg = readPackageJson(root);
157
164
  const bin = pkg.bin && typeof pkg.bin === 'object' ? pkg.bin as Record<string, string | undefined> : {};
158
165
  const bins = REQUIRED_BIN_COMMANDS.map((command) => verifyBin(root, command, bin[command]));
159
- const pack = npmPackDryRun(root);
166
+ const pack = registryPackDryRun(root);
160
167
  const requiredPathsPresent = REQUIRED_TARBALL_PATHS.filter((path) => pack.files.includes(path));
161
168
  const forbiddenPaths = pack.files.filter((path) => {
162
169
  if (FORBIDDEN_TARBALL_PREFIXES.some((prefix) => path.startsWith(prefix))) return true;
@@ -173,10 +180,10 @@ export function verifyPackageCliInstall(root: string): PackageCliVerificationRep
173
180
  if (!item.hasSourceEntrypoint) issues.push(`bin target does not import the Agent source entrypoint: ${item.command}`);
174
181
  }
175
182
  for (const path of REQUIRED_TARBALL_PATHS) {
176
- if (!pack.files.includes(path)) issues.push(`npm tarball missing required path: ${path}`);
183
+ if (!pack.files.includes(path)) issues.push(`registry tarball missing required path: ${path}`);
177
184
  }
178
185
  for (const path of forbiddenPaths) {
179
- issues.push(`npm tarball includes forbidden path: ${path}`);
186
+ issues.push(`registry tarball includes forbidden path: ${path}`);
180
187
  }
181
188
  for (const failure of packageFacingText.failures) {
182
189
  issues.push(failure);
@@ -64,7 +64,10 @@ export function redactConfig<T>(config: T): RedactedConfigResult<T> {
64
64
  }
65
65
 
66
66
  export function redactText(input: string): string {
67
- let output = input;
67
+ let output = input.replace(
68
+ /\b([A-Za-z0-9_]*(?:token|secret|password|api[_-]?key|key)[A-Za-z0-9_]*\s*=\s*)([^ \t\r\n"'`]+)/gi,
69
+ `$1${REDACTED_VALUE}`,
70
+ );
68
71
  for (const pattern of SECRET_LIKE_TEXT_PATTERNS) {
69
72
  output = output.replace(pattern, REDACTED_VALUE);
70
73
  }