@pellux/goodvibes-agent 1.2.0 → 1.3.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 (77) hide show
  1. package/CHANGELOG.md +159 -0
  2. package/dist/package/main.js +19016 -12684
  3. package/docs/README.md +3 -3
  4. package/docs/connected-host.md +2 -2
  5. package/docs/getting-started.md +11 -9
  6. package/docs/tools-and-commands.md +12 -10
  7. package/docs/voice-and-live-tts.md +2 -2
  8. package/package.json +1 -1
  9. package/release/release-notes.md +17 -5
  10. package/release/release-readiness.json +36 -36
  11. package/src/agent/competitive-feature-inventory.ts +42 -44
  12. package/src/agent/setup-wizard-artifact-receipts.ts +366 -0
  13. package/src/agent/setup-wizard.ts +245 -9
  14. package/src/input/agent-workspace-categories.ts +2 -2
  15. package/src/input/agent-workspace-onboarding-categories.ts +4 -4
  16. package/src/input/agent-workspace-settings.ts +52 -1
  17. package/src/input/agent-workspace-setup-snapshot.ts +20 -1
  18. package/src/input/agent-workspace-setup.ts +32 -5
  19. package/src/input/agent-workspace-snapshot.ts +23 -3
  20. package/src/input/agent-workspace.ts +5 -1
  21. package/src/input/setup-wizard-live-receipts.ts +76 -0
  22. package/src/renderer/agent-workspace-context-lines.ts +21 -6
  23. package/src/renderer/agent-workspace.ts +46 -10
  24. package/src/runtime/tool-permission-safety.ts +1 -1
  25. package/src/tools/agent-harness-agent-orchestration-policy.ts +75 -0
  26. package/src/tools/agent-harness-agent-orchestration.ts +216 -128
  27. package/src/tools/agent-harness-autonomy-live-records.ts +154 -0
  28. package/src/tools/agent-harness-autonomy-queue-types.ts +1 -1
  29. package/src/tools/agent-harness-autonomy-queue.ts +19 -8
  30. package/src/tools/agent-harness-autonomy-watcher-read-models.ts +509 -0
  31. package/src/tools/agent-harness-background-processes.ts +8 -4
  32. package/src/tools/agent-harness-browser-cockpit-route.ts +188 -34
  33. package/src/tools/agent-harness-browser-control.ts +12 -4
  34. package/src/tools/agent-harness-browser-pwa-read-models.ts +622 -0
  35. package/src/tools/agent-harness-device-live-read-models.ts +366 -0
  36. package/src/tools/agent-harness-execution-posture.ts +3 -0
  37. package/src/tools/agent-harness-interactive-runtime-records.ts +421 -0
  38. package/src/tools/agent-harness-local-model-benchmarks.ts +71 -1
  39. package/src/tools/agent-harness-local-model-endpoints.ts +469 -354
  40. package/src/tools/agent-harness-local-model-smoke.ts +277 -0
  41. package/src/tools/agent-harness-local-model-url.ts +78 -0
  42. package/src/tools/agent-harness-media-posture.ts +27 -12
  43. package/src/tools/agent-harness-memory-external-providers.ts +796 -0
  44. package/src/tools/agent-harness-memory-posture.ts +253 -137
  45. package/src/tools/agent-harness-memory-provider-certification.ts +219 -0
  46. package/src/tools/agent-harness-memory-refinement.ts +340 -0
  47. package/src/tools/agent-harness-mode-catalog.ts +4 -2
  48. package/src/tools/agent-harness-model-provider-health.ts +139 -42
  49. package/src/tools/agent-harness-model-readiness.ts +31 -5
  50. package/src/tools/agent-harness-model-routing-types.ts +61 -0
  51. package/src/tools/agent-harness-model-routing.ts +31 -6
  52. package/src/tools/agent-harness-pairing-posture.ts +30 -9
  53. package/src/tools/agent-harness-personal-ops-certification.ts +116 -0
  54. package/src/tools/agent-harness-personal-ops-lanes.ts +81 -15
  55. package/src/tools/agent-harness-personal-ops-operations.ts +225 -0
  56. package/src/tools/agent-harness-personal-ops-provider-records.ts +358 -0
  57. package/src/tools/agent-harness-personal-ops-provider-task-records.ts +321 -0
  58. package/src/tools/agent-harness-personal-ops-records.ts +176 -224
  59. package/src/tools/agent-harness-personal-ops-types.ts +19 -1
  60. package/src/tools/agent-harness-personal-ops.ts +18 -11
  61. package/src/tools/agent-harness-remote-read-models.ts +541 -0
  62. package/src/tools/agent-harness-research-briefing.ts +26 -7
  63. package/src/tools/agent-harness-research-live-read-models.ts +500 -0
  64. package/src/tools/agent-harness-research-runs.ts +15 -3
  65. package/src/tools/agent-harness-research-workflow.ts +92 -19
  66. package/src/tools/agent-harness-setup-model-helpers.ts +1 -0
  67. package/src/tools/agent-harness-setup-smoke.ts +26 -1
  68. package/src/tools/agent-harness-tool-schema.ts +23 -1
  69. package/src/tools/agent-harness-tool-types.ts +5 -0
  70. package/src/tools/agent-harness-tool.ts +9 -1
  71. package/src/tools/agent-harness-workspace-actions.ts +1 -1
  72. package/src/tools/agent-memory-tool.ts +40 -1
  73. package/src/tools/agent-model-compare-run.ts +13 -0
  74. package/src/tools/agent-research-runner.ts +367 -0
  75. package/src/tools/agent-research-tool.ts +7 -179
  76. package/src/tools/agent-route-planner-candidates-surfaces.ts +1 -1
  77. package/src/version.ts +1 -1
@@ -3,6 +3,7 @@ export type AgentSetupWizardStatus = 'complete' | 'active' | 'blocked';
3
3
  export type AgentSetupWizardStepStatus = 'done' | 'current' | 'pending' | 'blocked';
4
4
  export type AgentSetupWizardCheckpointStatus = 'available' | 'none' | 'stale' | 'unavailable';
5
5
  export type AgentSetupWizardCloseoutStatus = 'complete' | 'ready-to-finish' | 'needs-smoke-evidence' | 'blocked';
6
+ export type AgentSetupWizardReceiptSchemaStatus = 'certified' | 'legacy';
6
7
 
7
8
  export interface AgentSetupWizardSourceItem {
8
9
  readonly id: string;
@@ -25,6 +26,8 @@ export interface AgentSetupWizardSmokeHistory {
25
26
  readonly trend: string;
26
27
  readonly latestResult: string | null;
27
28
  readonly previousResult: string | null;
29
+ readonly latestEvidenceId?: string | null;
30
+ readonly latestEvidenceAt?: string | null;
28
31
  readonly resultCounts: Readonly<Record<string, number>>;
29
32
  readonly blockedCheckFrequency: readonly AgentSetupWizardBlockedCheckFrequency[];
30
33
  readonly inspectLatestRoute: string | null;
@@ -33,6 +36,26 @@ export interface AgentSetupWizardSmokeHistory {
33
36
  readonly reason?: string;
34
37
  }
35
38
 
39
+ export type AgentSetupWizardDurableReceiptStatus = 'ready' | 'blocked' | 'failed' | 'unknown';
40
+
41
+ export interface AgentSetupWizardDurableReceipt {
42
+ readonly stepId: string;
43
+ readonly stepLabel?: string;
44
+ readonly status: AgentSetupWizardDurableReceiptStatus;
45
+ readonly receiptId: string;
46
+ readonly recordedAt: string;
47
+ readonly summary: string;
48
+ readonly inspectRoute: string | null;
49
+ readonly source?: string;
50
+ readonly schemaStatus?: AgentSetupWizardReceiptSchemaStatus;
51
+ readonly schemaVersion?: string;
52
+ readonly provenance?: readonly string[];
53
+ readonly publicationGuarantee?: string;
54
+ readonly eventCursor?: string;
55
+ readonly eventSequence?: number;
56
+ readonly publisher?: string;
57
+ }
58
+
36
59
  export interface AgentSetupWizardRepeatedBlocker {
37
60
  readonly setupItemId: string;
38
61
  readonly checkId: string;
@@ -52,6 +75,36 @@ export interface AgentSetupWizardStep {
52
75
  readonly backtrackRoute: string | null;
53
76
  }
54
77
 
78
+ export type AgentSetupWizardStepHistoryKind = 'checkpoint' | 'setup-smoke' | 'durable-receipt';
79
+
80
+ export interface AgentSetupWizardStepHistoryEntry {
81
+ readonly id: string;
82
+ readonly stepId: string;
83
+ readonly stepLabel: string;
84
+ readonly kind: AgentSetupWizardStepHistoryKind;
85
+ readonly receiptId: string;
86
+ readonly recordedAt: string;
87
+ readonly summary: string;
88
+ readonly inspectRoute: string | null;
89
+ readonly source?: string;
90
+ readonly receiptStatus?: string;
91
+ readonly satisfiesReceipt?: boolean;
92
+ readonly schemaStatus?: AgentSetupWizardReceiptSchemaStatus;
93
+ readonly schemaVersion?: string;
94
+ readonly provenance?: readonly string[];
95
+ readonly publicationGuarantee?: string;
96
+ readonly eventCursor?: string;
97
+ readonly eventSequence?: number;
98
+ readonly publisher?: string;
99
+ }
100
+
101
+ export interface AgentSetupWizardReceiptGap {
102
+ readonly stepId: string;
103
+ readonly stepLabel: string;
104
+ readonly requiredReceipt: string;
105
+ readonly summary: string;
106
+ }
107
+
55
108
  export interface AgentSetupWizardCloseout {
56
109
  readonly status: AgentSetupWizardCloseoutStatus;
57
110
  readonly label: string;
@@ -107,6 +160,8 @@ export interface AgentSetupWizard {
107
160
  readonly reviewRoute: string;
108
161
  readonly repeatedBlocker: AgentSetupWizardRepeatedBlocker | null;
109
162
  readonly smokeHistory: AgentSetupWizardSmokeHistory;
163
+ readonly stepHistory: readonly AgentSetupWizardStepHistoryEntry[];
164
+ readonly receiptGaps: readonly AgentSetupWizardReceiptGap[];
110
165
  readonly closeout: AgentSetupWizardCloseout;
111
166
  readonly checkpoint: AgentSetupWizardCheckpoint;
112
167
  readonly steps: readonly AgentSetupWizardStep[];
@@ -120,6 +175,8 @@ export interface BuildAgentSetupWizardInput {
120
175
  readonly setupMarkerExists?: boolean;
121
176
  readonly finishRoute?: string;
122
177
  readonly finishUserRoute?: string;
178
+ readonly receiptRequiredStepIds?: readonly string[];
179
+ readonly durableReceipts?: readonly AgentSetupWizardDurableReceipt[];
123
180
  readonly repeatedBlockerAliases?: Readonly<Record<string, readonly string[]>>;
124
181
  readonly reviewRoute?: string;
125
182
  }
@@ -305,9 +362,168 @@ function buildStep(item: AgentSetupWizardSourceItem, currentId: string | null):
305
362
  };
306
363
  }
307
364
 
365
+ function validIso(value: string | null | undefined): string | null {
366
+ if (!value || Number.isNaN(Date.parse(value))) return null;
367
+ return new Date(value).toISOString();
368
+ }
369
+
370
+ function setupReceiptKind(stepId: string): string {
371
+ if (stepId === 'runtime' || stepId === 'connected-host-readiness') return 'connected-host service status receipt';
372
+ if (stepId === 'connected-host-auth') return 'connected-host auth receipt';
373
+ if (stepId === 'install-smoke') return 'setup smoke evidence receipt';
374
+ if (stepId === 'browser-pwa') return 'connected-host browser/PWA first-run receipt';
375
+ return 'durable setup receipt';
376
+ }
377
+
378
+ function setupStepAliases(stepId: string): readonly string[] {
379
+ if (stepId === 'runtime' || stepId === 'connected-host-readiness') return ['runtime', 'connected-host-readiness'];
380
+ return [stepId];
381
+ }
382
+
383
+ function setupStepMatches(left: string, right: string): boolean {
384
+ const leftAliases = new Set(setupStepAliases(left));
385
+ return setupStepAliases(right).some((alias) => leftAliases.has(alias));
386
+ }
387
+
388
+ function durableReceiptSatisfies(receipt: AgentSetupWizardDurableReceipt): boolean {
389
+ return receipt.status === 'ready' && Boolean(validIso(receipt.recordedAt)) && receipt.receiptId.trim().length > 0;
390
+ }
391
+
392
+ export function setupStepHasSatisfyingReceipt(
393
+ receipts: readonly AgentSetupWizardDurableReceipt[],
394
+ stepId: string,
395
+ ): boolean {
396
+ return receipts.some((receipt) => durableReceiptSatisfies(receipt) && setupStepMatches(stepId, receipt.stepId));
397
+ }
398
+
399
+ function latestSatisfyingReceipt(
400
+ receipts: readonly AgentSetupWizardDurableReceipt[],
401
+ stepId: string,
402
+ ): AgentSetupWizardDurableReceipt | null {
403
+ return receipts
404
+ .filter((receipt) => durableReceiptSatisfies(receipt) && setupStepMatches(stepId, receipt.stepId))
405
+ .sort((left, right) => Date.parse(right.recordedAt) - Date.parse(left.recordedAt) || left.receiptId.localeCompare(right.receiptId))[0] ?? null;
406
+ }
407
+
408
+ function applyDurableReceiptStatus(
409
+ items: readonly AgentSetupWizardSourceItem[],
410
+ receipts: readonly AgentSetupWizardDurableReceipt[],
411
+ ): readonly AgentSetupWizardSourceItem[] {
412
+ if (receipts.length === 0) return items;
413
+ return items.map((item) => {
414
+ const receipt = latestSatisfyingReceipt(receipts, item.id);
415
+ if (!receipt) return item;
416
+ return {
417
+ ...item,
418
+ status: 'ready',
419
+ detail: `${item.detail} Durable receipt ${receipt.receiptId}: ${receipt.summary}`,
420
+ };
421
+ });
422
+ }
423
+
424
+ function buildStepHistory(input: {
425
+ readonly items: readonly AgentSetupWizardSourceItem[];
426
+ readonly smokeHistory: AgentSetupWizardSmokeHistory;
427
+ readonly checkpoint: AgentSetupWizardCheckpoint;
428
+ readonly durableReceipts: readonly AgentSetupWizardDurableReceipt[];
429
+ }): readonly AgentSetupWizardStepHistoryEntry[] {
430
+ const byId = new Map(input.items.map((item) => [item.id, item]));
431
+ const history: AgentSetupWizardStepHistoryEntry[] = [];
432
+
433
+ const checkpointAt = validIso(input.checkpoint.savedAt);
434
+ if (input.checkpoint.status === 'available' && input.checkpoint.currentStepId && checkpointAt) {
435
+ const item = byId.get(input.checkpoint.currentStepId);
436
+ const stepLabel = item?.label ?? input.checkpoint.currentStepLabel ?? input.checkpoint.currentStepId;
437
+ const receiptId = `setup-wizard-checkpoint:${input.checkpoint.currentStepId}:${checkpointAt}`;
438
+ history.push({
439
+ id: `setup-step-history:${input.checkpoint.currentStepId}:checkpoint:${checkpointAt}`,
440
+ stepId: input.checkpoint.currentStepId,
441
+ stepLabel,
442
+ kind: 'checkpoint',
443
+ receiptId,
444
+ recordedAt: checkpointAt,
445
+ summary: `Saved checkpoint for ${stepLabel}.`,
446
+ inspectRoute: input.checkpoint.inspectRoute,
447
+ source: input.checkpoint.source ?? undefined,
448
+ satisfiesReceipt: false,
449
+ });
450
+ }
451
+
452
+ const smokeAt = validIso(input.smokeHistory.latestEvidenceAt);
453
+ if (input.smokeHistory.latestEvidenceId && smokeAt) {
454
+ const item = byId.get('install-smoke');
455
+ const result = input.smokeHistory.latestResult ?? 'unknown';
456
+ const ready = result === 'ready-for-user-run';
457
+ history.push({
458
+ id: `setup-step-history:install-smoke:setup-smoke:${input.smokeHistory.latestEvidenceId}`,
459
+ stepId: 'install-smoke',
460
+ stepLabel: item?.label ?? 'Install smoke',
461
+ kind: 'setup-smoke',
462
+ receiptId: input.smokeHistory.latestEvidenceId,
463
+ recordedAt: smokeAt,
464
+ summary: `Latest setup smoke evidence is ${result}.`,
465
+ inspectRoute: input.smokeHistory.inspectLatestRoute,
466
+ source: 'agent-setup-smoke-evidence',
467
+ receiptStatus: result,
468
+ satisfiesReceipt: ready,
469
+ });
470
+ }
471
+
472
+ for (const receipt of input.durableReceipts) {
473
+ const recordedAt = validIso(receipt.recordedAt);
474
+ if (!recordedAt || receipt.receiptId.trim().length === 0) continue;
475
+ const item = input.items.find((candidate) => setupStepMatches(candidate.id, receipt.stepId));
476
+ const stepLabel = receipt.stepLabel ?? item?.label ?? receipt.stepId;
477
+ history.push({
478
+ id: `setup-step-history:${receipt.stepId}:durable-receipt:${receipt.receiptId}`,
479
+ stepId: receipt.stepId,
480
+ stepLabel,
481
+ kind: 'durable-receipt',
482
+ receiptId: receipt.receiptId,
483
+ recordedAt,
484
+ summary: receipt.summary,
485
+ inspectRoute: receipt.inspectRoute,
486
+ source: receipt.source,
487
+ receiptStatus: receipt.status,
488
+ satisfiesReceipt: durableReceiptSatisfies(receipt),
489
+ schemaStatus: receipt.schemaStatus,
490
+ schemaVersion: receipt.schemaVersion,
491
+ provenance: receipt.provenance,
492
+ publicationGuarantee: receipt.publicationGuarantee,
493
+ eventCursor: receipt.eventCursor,
494
+ eventSequence: receipt.eventSequence,
495
+ publisher: receipt.publisher,
496
+ });
497
+ }
498
+
499
+ return history.sort((left, right) => Date.parse(right.recordedAt) - Date.parse(left.recordedAt) || left.id.localeCompare(right.id));
500
+ }
501
+
502
+ function buildReceiptGaps(input: {
503
+ readonly items: readonly AgentSetupWizardSourceItem[];
504
+ readonly stepHistory: readonly AgentSetupWizardStepHistoryEntry[];
505
+ readonly receiptRequiredStepIds: readonly string[];
506
+ }): readonly AgentSetupWizardReceiptGap[] {
507
+ return input.receiptRequiredStepIds
508
+ .filter((stepId) => !input.stepHistory.some((entry) => entry.satisfiesReceipt === true && setupStepMatches(stepId, entry.stepId)))
509
+ .map((stepId) => {
510
+ const item = input.items.find((candidate) => candidate.id === stepId);
511
+ const stepLabel = item?.label ?? stepId;
512
+ const requiredReceipt = setupReceiptKind(stepId);
513
+ return {
514
+ stepId,
515
+ stepLabel,
516
+ requiredReceipt,
517
+ summary: `${stepLabel} still needs a stable ${requiredReceipt} id and timestamp before release closeout can count it as durable setup evidence.`,
518
+ };
519
+ });
520
+ }
521
+
308
522
  function buildCloseout(input: {
309
523
  readonly items: readonly AgentSetupWizardSourceItem[];
310
524
  readonly smokeHistory: AgentSetupWizardSmokeHistory;
525
+ readonly setupSmokeReceiptReady: boolean;
526
+ readonly stepHistory: readonly AgentSetupWizardStepHistoryEntry[];
311
527
  readonly criticalStepIds: readonly string[];
312
528
  readonly setupMarkerExists: boolean;
313
529
  readonly reviewRoute: string;
@@ -318,14 +534,21 @@ function buildCloseout(input: {
318
534
  const primaryBlocker = input.items.find((item) => (
319
535
  critical.has(item.id) && item.status === 'blocked'
320
536
  )) ?? null;
321
- const smokeReady = input.smokeHistory.status === 'available' && input.smokeHistory.latestResult === 'ready-for-user-run';
537
+ const smokeReady = input.setupSmokeReceiptReady
538
+ || (input.smokeHistory.status === 'available' && input.smokeHistory.latestResult === 'ready-for-user-run');
322
539
  const latestSmoke = input.smokeHistory.status === 'available'
323
540
  ? input.smokeHistory.latestResult ?? 'unknown'
324
- : input.smokeHistory.status;
541
+ : input.setupSmokeReceiptReady ? 'durable receipt ready' : input.smokeHistory.status;
542
+ const satisfyingSetupReceipts = input.stepHistory.filter((entry) => entry.kind === 'durable-receipt' && entry.satisfiesReceipt === true);
543
+ const certifiedSetupReceipts = satisfyingSetupReceipts.filter((entry) => entry.schemaStatus === 'certified');
544
+ const eventStreamSetupReceipts = satisfyingSetupReceipts.filter((entry) => entry.eventCursor);
325
545
  const evidence = [
326
546
  `critical setup blockers: ${primaryBlocker ? `${primaryBlocker.label} (${primaryBlocker.status})` : 'none'}`,
327
547
  `setup marker: ${input.setupMarkerExists ? 'present' : 'missing'}`,
328
548
  `latest setup smoke: ${latestSmoke}`,
549
+ `setup smoke receipt: ${input.setupSmokeReceiptReady ? 'ready' : 'missing'}`,
550
+ `certified setup receipts: ${certifiedSetupReceipts.length}/${satisfyingSetupReceipts.length}`,
551
+ `setup receipt event streams: ${eventStreamSetupReceipts.length}`,
329
552
  `setup smoke history: ${input.smokeHistory.status}; total ${input.smokeHistory.total}; trend ${input.smokeHistory.trend}`,
330
553
  ];
331
554
  const policy = 'Setup closeout is advisory until a confirmed finish route writes the local onboarding marker. Optional setup recommendations do not block closeout, but unresolved critical blockers or missing ready setup smoke evidence do.';
@@ -401,14 +624,23 @@ function buildCloseout(input: {
401
624
  export function buildAgentSetupWizard(input: BuildAgentSetupWizardInput): AgentSetupWizard {
402
625
  const smokeHistory = input.smokeHistory ?? emptyAgentSetupSmokeHistory();
403
626
  const inputCheckpoint = input.checkpoint ?? emptyAgentSetupWizardCheckpoint();
404
- const repeated = itemFromRepeatedBlockers(input.items, smokeHistory, input.repeatedBlockerAliases ?? {});
405
- const blocking = firstBlockingItem(input.items);
406
- const rawCheckpointItem = itemFromCheckpoint(input.items, inputCheckpoint);
627
+ const durableReceipts = input.durableReceipts ?? [];
628
+ const items = applyDurableReceiptStatus(input.items, durableReceipts);
629
+ const repeated = itemFromRepeatedBlockers(items, smokeHistory, input.repeatedBlockerAliases ?? {});
630
+ const blocking = firstBlockingItem(items);
631
+ const rawCheckpointItem = itemFromCheckpoint(items, inputCheckpoint);
407
632
  const checkpointItem = blocking && rawCheckpointItem?.id !== blocking.id ? null : rawCheckpointItem;
408
- const checkpoint = buildCheckpoint(inputCheckpoint, input.items, checkpointItem, blocking);
409
- const current = repeated?.item ?? blocking ?? checkpointItem ?? firstAttentionItem(input.items);
633
+ const checkpoint = buildCheckpoint(inputCheckpoint, items, checkpointItem, blocking);
634
+ const current = repeated?.item ?? blocking ?? checkpointItem ?? firstAttentionItem(items);
410
635
  const currentId = current?.id ?? null;
411
- const steps = input.items.map((item) => buildStep(item, currentId));
636
+ const steps = items.map((item) => buildStep(item, currentId));
637
+ const stepHistory = buildStepHistory({ items, smokeHistory, checkpoint, durableReceipts });
638
+ const setupSmokeReceiptReady = stepHistory.some((entry) => entry.satisfiesReceipt === true && setupStepMatches('install-smoke', entry.stepId));
639
+ const receiptGaps = buildReceiptGaps({
640
+ items,
641
+ stepHistory,
642
+ receiptRequiredStepIds: input.receiptRequiredStepIds ?? [],
643
+ });
412
644
  const completedSteps = steps.filter((step) => step.status === 'done').length;
413
645
  const status: AgentSetupWizardStatus = completedSteps === steps.length
414
646
  ? 'complete'
@@ -432,9 +664,13 @@ export function buildAgentSetupWizard(input: BuildAgentSetupWizardInput): AgentS
432
664
  reviewRoute: input.reviewRoute ?? DEFAULT_AGENT_SETUP_WIZARD_REVIEW_ROUTE,
433
665
  repeatedBlocker: repeated?.blocker ?? null,
434
666
  smokeHistory,
667
+ stepHistory,
668
+ receiptGaps,
435
669
  closeout: buildCloseout({
436
- items: input.items,
670
+ items,
437
671
  smokeHistory,
672
+ setupSmokeReceiptReady,
673
+ stepHistory,
438
674
  criticalStepIds: input.closeoutCriticalStepIds ?? [],
439
675
  setupMarkerExists: input.setupMarkerExists === true,
440
676
  reviewRoute: input.reviewRoute ?? DEFAULT_AGENT_SETUP_WIZARD_REVIEW_ROUTE,
@@ -455,8 +455,8 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
455
455
  detail: 'Voice, media, browser tools, and image-capable flows are first-class Agent tools. Connected-host administration stays outside Agent and side effects stay explicit.',
456
456
  actions: [
457
457
  { id: 'voice-workflow-posture', label: 'Voice workflows', detail: 'Inspect push-to-talk, voice memo transcription, spoken responses, and wake-word readiness with device action:"voice".', kind: 'guidance', safety: 'read-only' },
458
- { id: 'device-capability-map', label: 'Device capability map', detail: 'Inspect pairing, mobile command routing, browser/PWA, notifications, browser/desktop control, and camera/location readiness with device action:"status" without claiming unpublished contracts.', kind: 'guidance', safety: 'read-only' },
459
- { id: 'browser-cockpit-readiness', label: 'Browser/PWA readiness', detail: 'Inspect the connected browser cockpit/PWA route, workspace category coverage, mobile controls, and first-run receipt gaps with computer action:"browser" before opening a browser.', kind: 'guidance', safety: 'read-only' },
458
+ { id: 'device-capability-map', label: 'Device capability map', detail: 'Inspect pairing, mobile command routing, browser/PWA, notifications, browser/desktop control, and certified camera/location readiness with device action:"status" without claiming uncertified contracts.', kind: 'guidance', safety: 'read-only' },
459
+ { id: 'browser-cockpit-readiness', label: 'Browser/PWA readiness', detail: 'Inspect the connected browser cockpit/PWA route, certified workspace category coverage, mobile controls, and first-run receipt evidence with computer action:"browser" before opening a browser.', kind: 'guidance', safety: 'read-only' },
460
460
  { id: 'tts-config', label: 'Configure live TTS', detail: 'Open the settings workspace at the TTS group.', command: '/config tts', kind: 'command', safety: 'safe' },
461
461
  { id: 'tts-provider', label: 'Choose TTS provider', detail: 'Open provider/model routing for spoken responses through the settings flow.', command: '/config tts.provider', kind: 'command', safety: 'safe' },
462
462
  { id: 'voice-review', label: 'Voice review', detail: 'Inspect local voice posture before enabling spoken interaction.', command: '/voice review', kind: 'command', safety: 'read-only' },
@@ -103,10 +103,10 @@ export const AGENT_WORKSPACE_ONBOARDING_DETAIL_CATEGORIES: readonly AgentWorkspa
103
103
  summary: 'Create/import memory, personas, skills, routines, notes, and Knowledge.',
104
104
  detail: 'Use this page to seed the assistant with durable local context. These forms create Agent-owned records or ingest reviewed sources.',
105
105
  actions: [
106
- { id: 'context-vibe-status', label: 'Inspect VIBE.md', detail: 'Show project/global VIBE.md personality files, blocked files, and init paths before relying on a custom assistant feel.', command: '/vibe status', kind: 'command', safety: 'read-only' },
107
- { id: 'context-project-files', label: 'Inspect project context', detail: 'List secret-scanned .hermes.md, HERMES.md, AGENTS.md, CLAUDE.md, SOUL.md, and Cursor context files with blocked/truncated status.', command: 'context action:"files"', kind: 'guidance', safety: 'read-only' },
108
- { id: 'context-project-file', label: 'Inspect one context file', detail: 'Inspect one loaded or blocked project context file by id, target, or query through the read-only context-file route.', command: 'context action:"file"', kind: 'guidance', safety: 'read-only' },
109
- { id: 'context-prompt-context', label: 'Prompt context', detail: 'Inspect the applied prompt context order, selected memory/persona/skill/routine records, suppressed context, and approximate token budget.', command: 'context action:"prompt"', kind: 'guidance', safety: 'read-only' },
106
+ { id: 'context-vibe-status', label: 'Inspect VIBE.md', detail: 'Show project/global VIBE.md personality files, blocked files, and init paths before relying on a custom assistant feel.', kind: 'guidance', safety: 'read-only' },
107
+ { id: 'context-project-files', label: 'Inspect project context', detail: 'List secret-scanned .hermes.md, HERMES.md, AGENTS.md, CLAUDE.md, SOUL.md, and Cursor context files with blocked/truncated status.', kind: 'guidance', safety: 'read-only' },
108
+ { id: 'context-project-file', label: 'Inspect one context file', detail: 'Inspect one loaded or blocked project context file by id, target, or query through the read-only context-file control.', kind: 'guidance', safety: 'read-only' },
109
+ { id: 'context-prompt-context', label: 'Prompt context', detail: 'Inspect the applied prompt context order, selected memory/persona/skill/routine records, suppressed context, and approximate token budget.', kind: 'guidance', safety: 'read-only' },
110
110
  { id: 'context-profile-from-discovered', label: 'Profile from discovered files', detail: 'Create an isolated Agent profile from reviewed local persona, skill, and routine files.', editorKind: 'profile-from-discovered', kind: 'editor', safety: 'safe' },
111
111
  { id: 'context-persona-discovery', label: 'Import persona files', detail: 'Import discovered persona files into the Agent persona registry.', editorKind: 'persona-discovery-import', kind: 'editor', safety: 'safe' },
112
112
  { id: 'context-skill-discovery', label: 'Import skill files', detail: 'Import discovered skill files into the Agent skill registry.', editorKind: 'skill-discovery-import', kind: 'editor', safety: 'safe' },
@@ -173,10 +173,31 @@ function valuesMatch(left: unknown, right: unknown): boolean {
173
173
  return JSON.stringify(left) === JSON.stringify(right);
174
174
  }
175
175
 
176
+ function isSecretLikeSettingKey(key: string): boolean {
177
+ const normalized = key.replace(/([a-z0-9])([A-Z])/g, '$1.$2').toLowerCase();
178
+ if (normalized.endsWith('secret.policy')) return false;
179
+ return /(?:secret|token|password|api[-_.]?key|api\.key|signing)/i.test(normalized);
180
+ }
181
+
182
+ function compactSettingValue(key: string, value: unknown): string {
183
+ if (value === undefined || value === null) return '(unset)';
184
+ if (isSecretLikeSettingKey(key)) return value === '' ? '(empty)' : '(secret)';
185
+ if (typeof value === 'boolean') return value ? 'true' : 'false';
186
+ if (typeof value === 'number') return Number.isFinite(value) ? String(value) : '(invalid)';
187
+ if (typeof value === 'string') {
188
+ if (value.length === 0) return '(empty)';
189
+ if (value.startsWith('goodvibes://secrets/')) return '(secret)';
190
+ return value.length > 36 ? `${value.slice(0, 33)}...` : value;
191
+ }
192
+ if (Array.isArray(value)) return `[${value.length} item${value.length === 1 ? '' : 's'}]`;
193
+ if (isRecord(value)) return '{...}';
194
+ return String(value);
195
+ }
196
+
176
197
  function redactImportValue(key: string, value: unknown): unknown {
177
198
  if (typeof value !== 'string') return value;
178
199
  if (!value) return value;
179
- if (/(?:secret|token|password|api[-_.]?key|signing)/i.test(key)) {
200
+ if (isSecretLikeSettingKey(key)) {
180
201
  return value.startsWith('goodvibes://secrets/') ? '<secret-ref>' : '<redacted>';
181
202
  }
182
203
  return value;
@@ -369,6 +390,36 @@ export function buildAgentWorkspaceSettingActionEffect(
369
390
  };
370
391
  }
371
392
 
393
+ export function buildAgentWorkspaceSettingActionPreview(
394
+ context: CommandContext | null,
395
+ action: AgentWorkspaceAction,
396
+ ): string | null {
397
+ const settingKey = action.settingKey?.trim();
398
+ const configManager = context?.platform?.configManager;
399
+ if (!settingKey || !configManager) return null;
400
+
401
+ const setting = agentWorkspaceSettingSchema(context, settingKey);
402
+ const currentValue = configManager.get(settingKey as ConfigKey);
403
+ let proposedValue: unknown;
404
+
405
+ if (action.settingValueHint !== undefined) {
406
+ proposedValue = action.settingValueHint;
407
+ } else if (setting?.type === 'boolean') {
408
+ proposedValue = !Boolean(currentValue);
409
+ } else if (setting?.type === 'enum' && setting.enumValues && setting.enumValues.length > 0) {
410
+ const currentIndex = Math.max(0, setting.enumValues.indexOf(String(currentValue)));
411
+ proposedValue = setting.enumValues[(currentIndex + 1) % setting.enumValues.length]!;
412
+ } else if (setting?.type === 'number' || setting?.type === 'string') {
413
+ proposedValue = 'edit value';
414
+ } else if (typeof currentValue === 'boolean') {
415
+ proposedValue = !currentValue;
416
+ } else {
417
+ proposedValue = 'choose value';
418
+ }
419
+
420
+ return `${settingKey}: ${compactSettingValue(settingKey, currentValue)} -> ${compactSettingValue(settingKey, proposedValue)}`;
421
+ }
422
+
372
423
  export async function applyAgentWorkspaceSettingValue(
373
424
  context: CommandContext | null,
374
425
  setting: ConfigSetting,
@@ -2,7 +2,7 @@ import type { ArtifactDescriptor } from '@pellux/goodvibes-sdk/platform/artifact
2
2
  import type { CommandContext } from './command-registry.ts';
3
3
  import { readSetupWizardCheckpoint } from '../agent/setup-wizard-checkpoint.ts';
4
4
  import { readOnboardingCompletionMarker } from '../runtime/onboarding/index.ts';
5
- import { DEFAULT_AGENT_SETUP_WIZARD_CLEAR_CHECKPOINT_ROUTE, DEFAULT_AGENT_SETUP_WIZARD_INSPECT_CHECKPOINT_ROUTE, DEFAULT_AGENT_SETUP_WIZARD_MARK_CHECKPOINT_ROUTE, DEFAULT_AGENT_SETUP_WIZARD_RERUN_SMOKE_ROUTE, DEFAULT_AGENT_SETUP_WIZARD_SAVE_SMOKE_ROUTE, buildAgentSetupWizard, emptyAgentSetupSmokeHistory, emptyAgentSetupWizardCheckpoint, type AgentSetupWizard, type AgentSetupWizardBlockedCheckFrequency, type AgentSetupWizardCheckpoint, type AgentSetupWizardSmokeHistory, type AgentSetupWizardSourceItem } from '../agent/setup-wizard.ts';
5
+ import { DEFAULT_AGENT_SETUP_WIZARD_CLEAR_CHECKPOINT_ROUTE, DEFAULT_AGENT_SETUP_WIZARD_INSPECT_CHECKPOINT_ROUTE, DEFAULT_AGENT_SETUP_WIZARD_MARK_CHECKPOINT_ROUTE, DEFAULT_AGENT_SETUP_WIZARD_RERUN_SMOKE_ROUTE, DEFAULT_AGENT_SETUP_WIZARD_SAVE_SMOKE_ROUTE, buildAgentSetupWizard, emptyAgentSetupSmokeHistory, emptyAgentSetupWizardCheckpoint, type AgentSetupWizard, type AgentSetupWizardBlockedCheckFrequency, type AgentSetupWizardCheckpoint, type AgentSetupWizardDurableReceipt, type AgentSetupWizardSmokeHistory, type AgentSetupWizardSourceItem } from '../agent/setup-wizard.ts';
6
6
  import type { AgentWorkspaceSetupChecklistItem } from './agent-workspace-setup.ts';
7
7
  import { readArtifactMetadataString, readArtifactMetadataStringList } from './agent-workspace-artifact-metadata.ts';
8
8
 
@@ -30,6 +30,12 @@ function setupSmokeEvidenceTrend(artifacts: readonly ArtifactDescriptor[]): stri
30
30
  return 'unchanged';
31
31
  }
32
32
 
33
+ function artifactCreatedAtIso(artifact: ArtifactDescriptor): string | null {
34
+ const createdAt = artifact.createdAt;
35
+ if (typeof createdAt !== 'number' || !Number.isFinite(createdAt)) return null;
36
+ return new Date(createdAt).toISOString();
37
+ }
38
+
33
39
  function setupSmokeBlockedCheckFrequency(artifacts: readonly ArtifactDescriptor[]): readonly AgentSetupWizardBlockedCheckFrequency[] {
34
40
  const counts = new Map<string, number>();
35
41
  for (const artifact of artifacts) {
@@ -67,6 +73,8 @@ export function buildSetupSmokeHistory(artifacts: readonly ArtifactDescriptor[],
67
73
  trend: setupSmokeEvidenceTrend(setupSmokeArtifacts),
68
74
  latestResult: readArtifactMetadataString(latest.metadata, 'result') || 'unknown',
69
75
  previousResult: previous ? readArtifactMetadataString(previous.metadata, 'result') || 'unknown' : null,
76
+ latestEvidenceId: latest.id,
77
+ latestEvidenceAt: artifactCreatedAtIso(latest),
70
78
  resultCounts,
71
79
  blockedCheckFrequency: setupSmokeBlockedCheckFrequency(setupSmokeArtifacts),
72
80
  inspectLatestRoute: `agent_artifacts show artifactId:"${latest.id}" includeContent:false`,
@@ -136,6 +144,7 @@ function setupChecklistModelRoute(item: AgentWorkspaceSetupChecklistItem): strin
136
144
  if (item.id === 'memory') return 'memory action:"status"';
137
145
  if (item.id === 'notes') return 'personal_ops action:"lane" laneId:"notes"';
138
146
  if (item.id === 'channels') return 'channels action:"status"';
147
+ if (item.id === 'browser-pwa') return 'computer action:"browser" includeParameters:true';
139
148
  if (item.id === 'voice-media') return 'device action:"voice"';
140
149
  return `agent_harness mode:"setup_item" setupItemId:"${item.id}"`;
141
150
  }
@@ -149,6 +158,7 @@ function setupChecklistActionId(item: AgentWorkspaceSetupChecklistItem): string
149
158
  if (item.id === 'skills') return 'skill-search';
150
159
  if (item.id === 'routines') return 'routine-search';
151
160
  if (item.id === 'channels') return 'channel-show';
161
+ if (item.id === 'browser-pwa') return 'browser-cockpit-readiness';
152
162
  if (item.id === 'voice-media') return 'voice-enable';
153
163
  return item.id;
154
164
  }
@@ -167,7 +177,9 @@ export function buildWorkspaceSetupWizard(
167
177
  smokeHistory: AgentSetupWizardSmokeHistory,
168
178
  checkpoint: AgentSetupWizardCheckpoint,
169
179
  setupMarkerExists: boolean,
180
+ durableReceipts: readonly AgentSetupWizardDurableReceipt[] = [],
170
181
  ): AgentSetupWizard {
182
+ const browserPwaNeedsReceipt = checklist.some((item) => item.id === 'browser-pwa' && item.status !== 'ready');
171
183
  const items: AgentSetupWizardSourceItem[] = checklist.map((item) => ({
172
184
  id: item.id,
173
185
  label: item.label,
@@ -182,6 +194,13 @@ export function buildWorkspaceSetupWizard(
182
194
  smokeHistory,
183
195
  checkpoint,
184
196
  closeoutCriticalStepIds: ['runtime', 'connected-host-auth', 'provider-model'],
197
+ receiptRequiredStepIds: [
198
+ 'runtime',
199
+ 'connected-host-auth',
200
+ 'install-smoke',
201
+ ...(browserPwaNeedsReceipt ? ['browser-pwa'] : []),
202
+ ],
203
+ durableReceipts,
185
204
  setupMarkerExists,
186
205
  repeatedBlockerAliases: SETUP_WIZARD_SNAPSHOT_BLOCKER_ALIASES,
187
206
  });
@@ -18,6 +18,7 @@ export interface AgentWorkspaceSetupChecklistInput {
18
18
  readonly connectedHostTokenReadable: boolean;
19
19
  readonly connectedHostTokenPath: string;
20
20
  readonly connectedHostTokenError?: string | null;
21
+ readonly connectedHostAuthReceiptReady?: boolean;
21
22
  readonly activeSubscriptionCount: number;
22
23
  readonly pendingSubscriptionCount: number;
23
24
  readonly availableSubscriptionProviderCount: number;
@@ -41,6 +42,10 @@ export interface AgentWorkspaceSetupChecklistInput {
41
42
  readonly readyChannelCount: number;
42
43
  readonly voiceProviderCount: number;
43
44
  readonly mediaProviderCount: number;
45
+ readonly installSmokeReceiptReady?: boolean;
46
+ readonly browserPwaEnabled: boolean;
47
+ readonly browserPwaPublicBaseUrl: string;
48
+ readonly browserPwaFirstRunReceiptStatus: 'published' | 'not-published';
44
49
  readonly runtimeProfileCount: number;
45
50
  readonly runtimeStarterTemplateCount: number;
46
51
  }
@@ -58,14 +63,19 @@ function sampleNames(summary: AgentBehaviorDiscoverySummary): string {
58
63
  export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChecklistInput): readonly AgentWorkspaceSetupChecklistItem[] {
59
64
  const providerReady = input.provider !== 'unknown' && input.model !== 'unknown';
60
65
  const tokenPathKnown = input.connectedHostTokenPath !== '(Agent home unavailable)';
61
- const connectedHostAuthStatus: AgentWorkspaceSetupStatus = input.connectedHostTokenReadable
66
+ const connectedHostAuthReceiptReady = input.connectedHostAuthReceiptReady === true;
67
+ const connectedHostAuthStatus: AgentWorkspaceSetupStatus = input.connectedHostTokenReadable || connectedHostAuthReceiptReady
62
68
  ? 'ready'
63
69
  : tokenPathKnown
64
70
  ? 'blocked'
65
71
  : 'recommended';
66
- const installSmokeReady = providerReady && input.connectedHostTokenReadable;
72
+ const installSmokePrerequisitesReady = providerReady && (input.connectedHostTokenReadable || connectedHostAuthReceiptReady);
73
+ const installSmokeReceiptReady = input.installSmokeReceiptReady === true;
67
74
  const hasActivePersona = input.activePersonaName !== '(none)' && input.activePersonaName !== '(unavailable)';
68
75
  const discoveredBehaviorCount = input.discoveredPersonas.count + input.discoveredSkills.count + input.discoveredRoutines.count;
76
+ const browserPwaUrl = input.browserPwaPublicBaseUrl.trim();
77
+ const browserPwaHasPublicUrl = browserPwaUrl.length > 0 && browserPwaUrl !== '(not configured)';
78
+ const browserPwaReceiptPublished = input.browserPwaFirstRunReceiptStatus === 'published';
69
79
  return [
70
80
  {
71
81
  id: 'runtime',
@@ -78,7 +88,9 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
78
88
  id: 'connected-host-auth',
79
89
  label: 'Connected-host auth',
80
90
  status: connectedHostAuthStatus,
81
- detail: input.connectedHostTokenReadable
91
+ detail: connectedHostAuthReceiptReady
92
+ ? `Durable connected-host auth receipt is ready${input.connectedHostTokenReadable ? ` and Agent has a readable operator token at ${input.connectedHostTokenPath}` : ''}.`
93
+ : input.connectedHostTokenReadable
82
94
  ? `Agent has a readable connected-host operator token at ${input.connectedHostTokenPath}.`
83
95
  : input.connectedHostTokenError
84
96
  ? `The connected-host operator token exists but cannot be read at ${input.connectedHostTokenPath}. Use the confirmed setup token provisioning route, then rerun auth review.`
@@ -99,8 +111,10 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
99
111
  {
100
112
  id: 'install-smoke',
101
113
  label: 'Install smoke',
102
- status: installSmokeReady ? 'recommended' : tokenPathKnown ? 'blocked' : 'recommended',
103
- detail: installSmokeReady
114
+ status: installSmokeReceiptReady ? 'ready' : installSmokePrerequisitesReady ? 'recommended' : tokenPathKnown ? 'blocked' : 'recommended',
115
+ detail: installSmokeReceiptReady
116
+ ? 'Durable setup smoke receipt is ready for release closeout; rerun smoke only after install, host, auth, or model changes.'
117
+ : installSmokePrerequisitesReady
104
118
  ? 'Run setup smoke after install or migration to prove package start, connected host, auth, model route, setup posture, and first assistant turn.'
105
119
  : 'Resolve connected-host auth and provider/model setup before treating the first assistant turn as install-ready.',
106
120
  command: 'Start -> Install smoke',
@@ -208,6 +222,19 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
208
222
  : 'Pair or review channels only when you want the assistant reachable outside this terminal.',
209
223
  command: 'Channels',
210
224
  },
225
+ {
226
+ id: 'browser-pwa',
227
+ label: 'Browser/PWA',
228
+ status: browserPwaReceiptPublished ? 'ready' : 'recommended',
229
+ detail: browserPwaReceiptPublished
230
+ ? `Connected-host browser/PWA first-run receipt is published${browserPwaHasPublicUrl ? ` for ${browserPwaUrl}` : ''}.`
231
+ : input.browserPwaEnabled
232
+ ? browserPwaHasPublicUrl
233
+ ? `Browser cockpit is openable at ${browserPwaUrl}, but the connected-host browser/PWA first-run completion receipt is not published yet. Terminal Agent remains primary until that receipt exists.`
234
+ : 'Browser cockpit is enabled through the connected-host web endpoint, but the connected-host browser/PWA first-run completion receipt is not published yet.'
235
+ : 'Enable the connected-host web endpoint and publish the browser/PWA first-run completion receipt before treating browser access as setup-ready.',
236
+ command: 'Voice & Media -> Browser/PWA readiness',
237
+ },
211
238
  {
212
239
  id: 'voice-media',
213
240
  label: 'Voice and media',