@pellux/goodvibes-agent 1.1.7 → 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 (274) hide show
  1. package/CHANGELOG.md +329 -0
  2. package/README.md +46 -30
  3. package/dist/package/{ast-grep-napi.linux-x64-gnu-mkk8xwww.node → ast-grep-napi.linux-x64-gnu-swtppvy9.node} +0 -0
  4. package/dist/package/{ast-grep-napi.linux-x64-musl-ryqtgdv6.node → ast-grep-napi.linux-x64-musl-ttfcdtap.node} +0 -0
  5. package/dist/package/main.js +138903 -91477
  6. package/docs/README.md +14 -7
  7. package/docs/channels-remote-and-api.md +8 -5
  8. package/docs/connected-host.md +14 -12
  9. package/docs/getting-started.md +58 -30
  10. package/docs/knowledge-artifacts-and-multimodal.md +7 -5
  11. package/docs/project-planning.md +2 -2
  12. package/docs/providers-and-routing.md +11 -3
  13. package/docs/tools-and-commands.md +116 -45
  14. package/docs/voice-and-live-tts.md +3 -1
  15. package/package.json +3 -2
  16. package/release/release-notes.md +142 -5
  17. package/release/release-readiness.json +124 -94
  18. package/src/agent/assistant-cockpit.ts +247 -0
  19. package/src/agent/autonomy-schedule-format.ts +96 -0
  20. package/src/agent/autonomy-schedule.ts +514 -0
  21. package/src/agent/channel-delivery-receipts.ts +292 -0
  22. package/src/agent/competitive-feature-inventory.ts +294 -0
  23. package/src/agent/document-registry-types.ts +106 -0
  24. package/src/agent/document-registry.ts +734 -0
  25. package/src/agent/harness-control.ts +2 -2
  26. package/src/agent/memory-prompt.ts +2 -2
  27. package/src/agent/operator-actions.ts +50 -6
  28. package/src/agent/persona-registry.ts +8 -0
  29. package/src/agent/project-context-files.ts +273 -0
  30. package/src/agent/prompt-context-receipts.ts +502 -0
  31. package/src/agent/reminder-schedule-format.ts +16 -2
  32. package/src/agent/research-run-registry.ts +582 -0
  33. package/src/agent/research-source-registry.ts +441 -0
  34. package/src/agent/routine-registry.ts +19 -3
  35. package/src/agent/routine-schedule-format.ts +27 -2
  36. package/src/agent/runtime-profile-starters.ts +7 -0
  37. package/src/agent/runtime-profile.ts +106 -4
  38. package/src/agent/schedule-edit-format.ts +129 -0
  39. package/src/agent/schedule-edit.ts +496 -0
  40. package/src/agent/schedule-next-routes.ts +42 -0
  41. package/src/agent/setup-wizard-artifact-receipts.ts +366 -0
  42. package/src/agent/setup-wizard-checkpoint.ts +140 -0
  43. package/src/agent/setup-wizard.ts +683 -0
  44. package/src/agent/skill-registry-types.ts +102 -0
  45. package/src/agent/skill-registry.ts +81 -107
  46. package/src/agent/vibe-confirmation-routes.ts +62 -0
  47. package/src/agent/vibe-file.ts +285 -0
  48. package/src/cli/agent-knowledge-command.ts +45 -2
  49. package/src/cli/help.ts +3 -0
  50. package/src/cli/profiles-command.ts +25 -11
  51. package/src/config/agent-settings-policy.ts +1 -18
  52. package/src/input/agent-workspace-activation.ts +53 -0
  53. package/src/input/agent-workspace-artifact-browser-editor.ts +494 -0
  54. package/src/input/agent-workspace-artifact-metadata.ts +17 -0
  55. package/src/input/agent-workspace-basic-command-editors.ts +5 -1
  56. package/src/input/agent-workspace-categories.ts +153 -155
  57. package/src/input/agent-workspace-channel-triage.ts +481 -0
  58. package/src/input/agent-workspace-channels.ts +263 -0
  59. package/src/input/agent-workspace-command-editor.ts +152 -0
  60. package/src/input/agent-workspace-context-snapshot.ts +349 -0
  61. package/src/input/agent-workspace-delegation-editor-submission.ts +8 -0
  62. package/src/input/agent-workspace-document-editor.ts +502 -0
  63. package/src/input/agent-workspace-document-ops-editor.ts +523 -0
  64. package/src/input/agent-workspace-host-category.ts +1 -1
  65. package/src/input/agent-workspace-local-library-snapshot.ts +167 -0
  66. package/src/input/agent-workspace-model-compare-editor.ts +376 -0
  67. package/src/input/agent-workspace-model-compare-prompt-submission.ts +552 -0
  68. package/src/input/agent-workspace-model-compare-types.ts +82 -0
  69. package/src/input/agent-workspace-model-compare-utils.ts +35 -0
  70. package/src/input/agent-workspace-onboarding-categories.ts +141 -0
  71. package/src/input/agent-workspace-operations-command-editor-submission.ts +53 -0
  72. package/src/input/agent-workspace-operations-command-editors.ts +22 -0
  73. package/src/input/agent-workspace-research-report-editor.ts +212 -0
  74. package/src/input/agent-workspace-research-run-editor.ts +144 -0
  75. package/src/input/agent-workspace-research-source-editor.ts +167 -0
  76. package/src/input/agent-workspace-review-packet-snapshot.ts +593 -0
  77. package/src/input/agent-workspace-review-packet-utils.ts +265 -0
  78. package/src/input/agent-workspace-settings.ts +313 -30
  79. package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
  80. package/src/input/agent-workspace-setup-snapshot.ts +217 -0
  81. package/src/input/agent-workspace-setup.ts +61 -1
  82. package/src/input/agent-workspace-snapshot.ts +184 -149
  83. package/src/input/agent-workspace-types.ts +287 -2
  84. package/src/input/agent-workspace.ts +23 -1
  85. package/src/input/command-registry.ts +9 -2
  86. package/src/input/commands/agent-runtime-profile-runtime.ts +28 -13
  87. package/src/input/commands/channels-runtime.ts +102 -114
  88. package/src/input/commands/delegation-runtime.ts +87 -24
  89. package/src/input/commands/knowledge-browser-flags.ts +12 -0
  90. package/src/input/commands/knowledge.ts +28 -14
  91. package/src/input/commands/operator-actions-runtime.ts +6 -3
  92. package/src/input/commands/schedule-runtime.ts +71 -14
  93. package/src/input/commands/session-content.ts +2 -2
  94. package/src/input/commands/shell-core.ts +2 -0
  95. package/src/input/commands/vibe-runtime.ts +140 -0
  96. package/src/input/commands.ts +2 -0
  97. package/src/input/connected-host-routes.ts +123 -0
  98. package/src/input/settings-modal-types.ts +14 -0
  99. package/src/input/setup-wizard-live-receipts.ts +76 -0
  100. package/src/main.ts +15 -15
  101. package/src/panels/qr-panel.ts +2 -2
  102. package/src/renderer/agent-workspace-context-lines.ts +701 -0
  103. package/src/renderer/agent-workspace.ts +68 -421
  104. package/src/renderer/settings-modal-helpers.ts +12 -0
  105. package/src/renderer/settings-modal.ts +12 -0
  106. package/src/runtime/bootstrap-command-context.ts +11 -1
  107. package/src/runtime/bootstrap-command-parts.ts +14 -3
  108. package/src/runtime/bootstrap-core.ts +39 -1
  109. package/src/runtime/bootstrap-shell.ts +6 -0
  110. package/src/runtime/bootstrap.ts +113 -30
  111. package/src/runtime/connected-host-auth.ts +3 -2
  112. package/src/runtime/execution-ledger.ts +231 -0
  113. package/src/runtime/services.ts +88 -10
  114. package/src/runtime/tool-permission-safety.ts +199 -5
  115. package/src/shell/session-continuity-hints.ts +39 -0
  116. package/src/tools/agent-artifacts-tool.ts +606 -0
  117. package/src/tools/agent-audit-tool.ts +155 -0
  118. package/src/tools/agent-autonomy-schedule-tool.ts +238 -0
  119. package/src/tools/agent-autonomy-tool.ts +140 -0
  120. package/src/tools/agent-channel-send-tool.ts +24 -2
  121. package/src/tools/agent-channels-tool.ts +140 -0
  122. package/src/tools/agent-computer-tool.ts +168 -0
  123. package/src/tools/agent-context-policy.ts +137 -7
  124. package/src/tools/agent-context-tool.ts +143 -0
  125. package/src/tools/agent-delegation-tool.ts +128 -0
  126. package/src/tools/agent-device-tool.ts +240 -0
  127. package/src/tools/agent-documents-tool.ts +684 -0
  128. package/src/tools/agent-execution-tool.ts +230 -0
  129. package/src/tools/agent-harness-agent-orchestration-policy.ts +75 -0
  130. package/src/tools/agent-harness-agent-orchestration.ts +791 -0
  131. package/src/tools/agent-harness-autonomy-intake.ts +785 -0
  132. package/src/tools/agent-harness-autonomy-live-records.ts +742 -0
  133. package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
  134. package/src/tools/agent-harness-autonomy-queue.ts +571 -0
  135. package/src/tools/agent-harness-autonomy-watcher-read-models.ts +509 -0
  136. package/src/tools/agent-harness-background-processes-types.ts +35 -0
  137. package/src/tools/agent-harness-background-processes.ts +798 -0
  138. package/src/tools/agent-harness-browser-cockpit-route.ts +373 -0
  139. package/src/tools/agent-harness-browser-control.ts +346 -0
  140. package/src/tools/agent-harness-browser-pwa-read-models.ts +622 -0
  141. package/src/tools/agent-harness-channel-metadata.ts +168 -10
  142. package/src/tools/agent-harness-cli-command-policy.ts +110 -0
  143. package/src/tools/agent-harness-command-catalog.ts +4 -2
  144. package/src/tools/agent-harness-command-runner.ts +42 -0
  145. package/src/tools/agent-harness-connected-host-status.ts +7 -4
  146. package/src/tools/agent-harness-delegation-posture.ts +234 -19
  147. package/src/tools/agent-harness-device-live-read-models.ts +366 -0
  148. package/src/tools/agent-harness-document-ops-reviewer-readiness.ts +234 -0
  149. package/src/tools/agent-harness-document-ops-types.ts +72 -0
  150. package/src/tools/agent-harness-document-ops.ts +671 -0
  151. package/src/tools/agent-harness-execution-history.ts +489 -0
  152. package/src/tools/agent-harness-execution-posture.ts +385 -0
  153. package/src/tools/agent-harness-file-recovery.ts +135 -0
  154. package/src/tools/agent-harness-interactive-runtime-records.ts +421 -0
  155. package/src/tools/agent-harness-keybinding-metadata.ts +16 -12
  156. package/src/tools/agent-harness-learning-curator-common.ts +102 -0
  157. package/src/tools/agent-harness-learning-curator-consolidation.ts +295 -0
  158. package/src/tools/agent-harness-learning-curator-proposals.ts +606 -0
  159. package/src/tools/agent-harness-learning-curator-types.ts +151 -0
  160. package/src/tools/agent-harness-learning-curator.ts +417 -0
  161. package/src/tools/agent-harness-local-model-benchmarks.ts +269 -0
  162. package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
  163. package/src/tools/agent-harness-local-model-endpoints.ts +788 -0
  164. package/src/tools/agent-harness-local-model-smoke.ts +277 -0
  165. package/src/tools/agent-harness-local-model-url.ts +78 -0
  166. package/src/tools/agent-harness-media-posture.ts +223 -2
  167. package/src/tools/agent-harness-memory-external-providers.ts +796 -0
  168. package/src/tools/agent-harness-memory-posture.ts +672 -0
  169. package/src/tools/agent-harness-memory-provider-certification.ts +219 -0
  170. package/src/tools/agent-harness-memory-refinement.ts +340 -0
  171. package/src/tools/agent-harness-metadata.ts +100 -168
  172. package/src/tools/agent-harness-mode-catalog.ts +86 -33
  173. package/src/tools/agent-harness-model-catalog.ts +162 -0
  174. package/src/tools/agent-harness-model-provider-health.ts +283 -0
  175. package/src/tools/agent-harness-model-readiness.ts +432 -0
  176. package/src/tools/agent-harness-model-routing-types.ts +327 -0
  177. package/src/tools/agent-harness-model-routing-utils.ts +30 -0
  178. package/src/tools/agent-harness-model-routing.ts +165 -193
  179. package/src/tools/agent-harness-operator-methods.ts +115 -133
  180. package/src/tools/agent-harness-pairing-posture.ts +452 -18
  181. package/src/tools/agent-harness-personal-ops-certification.ts +116 -0
  182. package/src/tools/agent-harness-personal-ops-discovery.ts +533 -0
  183. package/src/tools/agent-harness-personal-ops-intake.ts +385 -0
  184. package/src/tools/agent-harness-personal-ops-lanes.ts +270 -0
  185. package/src/tools/agent-harness-personal-ops-operations.ts +225 -0
  186. package/src/tools/agent-harness-personal-ops-provider-records.ts +358 -0
  187. package/src/tools/agent-harness-personal-ops-provider-task-records.ts +321 -0
  188. package/src/tools/agent-harness-personal-ops-records.ts +684 -0
  189. package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
  190. package/src/tools/agent-harness-personal-ops-types.ts +240 -0
  191. package/src/tools/agent-harness-personal-ops.ts +613 -0
  192. package/src/tools/agent-harness-project-context.ts +144 -0
  193. package/src/tools/agent-harness-prompt-context.ts +589 -0
  194. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  195. package/src/tools/agent-harness-release-evidence.ts +9 -7
  196. package/src/tools/agent-harness-release-readiness.ts +9 -7
  197. package/src/tools/agent-harness-remote-read-models.ts +541 -0
  198. package/src/tools/agent-harness-research-briefing.ts +446 -0
  199. package/src/tools/agent-harness-research-live-read-models.ts +500 -0
  200. package/src/tools/agent-harness-research-queue.ts +250 -0
  201. package/src/tools/agent-harness-research-runs.ts +307 -0
  202. package/src/tools/agent-harness-research-workflow.ts +395 -0
  203. package/src/tools/agent-harness-security-posture.ts +2 -2
  204. package/src/tools/agent-harness-service-posture.ts +9 -9
  205. package/src/tools/agent-harness-session-metadata.ts +3 -3
  206. package/src/tools/agent-harness-setup-connected-host.ts +501 -0
  207. package/src/tools/agent-harness-setup-describe.ts +557 -0
  208. package/src/tools/agent-harness-setup-handoffs.ts +502 -0
  209. package/src/tools/agent-harness-setup-model-helpers.ts +102 -0
  210. package/src/tools/agent-harness-setup-plan.ts +253 -0
  211. package/src/tools/agent-harness-setup-posture-types.ts +218 -0
  212. package/src/tools/agent-harness-setup-posture-utils.ts +173 -0
  213. package/src/tools/agent-harness-setup-posture.ts +449 -221
  214. package/src/tools/agent-harness-setup-smoke.ts +533 -0
  215. package/src/tools/agent-harness-sudo-posture.ts +114 -0
  216. package/src/tools/agent-harness-tool-schema.ts +142 -7
  217. package/src/tools/agent-harness-tool-types.ts +83 -0
  218. package/src/tools/agent-harness-tool-utils.ts +43 -0
  219. package/src/tools/agent-harness-tool.ts +319 -370
  220. package/src/tools/agent-harness-ui-surface-metadata.ts +76 -69
  221. package/src/tools/agent-harness-ui-surface-types.ts +46 -0
  222. package/src/tools/agent-harness-vibe-health.ts +105 -0
  223. package/src/tools/agent-harness-workspace-action-runner.ts +149 -0
  224. package/src/tools/agent-harness-workspace-actions.ts +114 -8
  225. package/src/tools/agent-harness-workspace-editor-execution.ts +214 -0
  226. package/src/tools/agent-harness-workspace-editor-runner.ts +755 -0
  227. package/src/tools/agent-host-tool.ts +159 -0
  228. package/src/tools/agent-knowledge-ingest-tool.ts +34 -1
  229. package/src/tools/agent-learning-consolidation-core.ts +327 -0
  230. package/src/tools/agent-learning-consolidation-tool.ts +758 -0
  231. package/src/tools/agent-memory-tool.ts +253 -0
  232. package/src/tools/agent-model-compare-export.ts +315 -0
  233. package/src/tools/agent-model-compare-handoff.ts +592 -0
  234. package/src/tools/agent-model-compare-judgment.ts +633 -0
  235. package/src/tools/agent-model-compare-run.ts +735 -0
  236. package/src/tools/agent-model-compare-tool.ts +698 -0
  237. package/src/tools/agent-model-compare-types.ts +191 -0
  238. package/src/tools/agent-model-compare-utils.ts +93 -0
  239. package/src/tools/agent-models-tool.ts +208 -0
  240. package/src/tools/agent-operator-action-tool.ts +1 -1
  241. package/src/tools/agent-operator-method-tool.ts +643 -0
  242. package/src/tools/agent-personal-ops-tool.ts +197 -0
  243. package/src/tools/agent-policy-explanation.ts +415 -0
  244. package/src/tools/agent-research-report-tool.ts +608 -0
  245. package/src/tools/agent-research-runner.ts +367 -0
  246. package/src/tools/agent-research-runs-tool.ts +434 -0
  247. package/src/tools/agent-research-sources-tool.ts +443 -0
  248. package/src/tools/agent-research-tool.ts +515 -0
  249. package/src/tools/agent-review-packet-presets-core.ts +757 -0
  250. package/src/tools/agent-review-packet-presets-tool.ts +466 -0
  251. package/src/tools/agent-review-packet-share-tool.ts +305 -0
  252. package/src/tools/agent-route-planner-candidates-setup.ts +411 -0
  253. package/src/tools/agent-route-planner-candidates-surfaces.ts +432 -0
  254. package/src/tools/agent-route-planner-candidates-work.ts +251 -0
  255. package/src/tools/agent-route-planner-helpers.ts +667 -0
  256. package/src/tools/agent-route-planner.ts +185 -0
  257. package/src/tools/agent-route-tool.ts +105 -0
  258. package/src/tools/agent-schedule-edit-tool.ts +210 -0
  259. package/src/tools/agent-schedule-tool.ts +282 -0
  260. package/src/tools/agent-security-tool.ts +145 -0
  261. package/src/tools/agent-sessions-tool.ts +122 -0
  262. package/src/tools/agent-settings-import-tool.ts +104 -0
  263. package/src/tools/agent-settings-tool.ts +176 -0
  264. package/src/tools/agent-setup-tool.ts +226 -0
  265. package/src/tools/agent-support-tool.ts +122 -0
  266. package/src/tools/agent-terminal-process-tools.ts +167 -0
  267. package/src/tools/agent-tool-policy-guard-types.ts +77 -0
  268. package/src/tools/agent-tool-policy-guard.ts +106 -85
  269. package/src/tools/agent-vibe-tool.ts +297 -0
  270. package/src/tools/agent-work-plan-tool.ts +265 -6
  271. package/src/tools/agent-workspace-tool.ts +305 -0
  272. package/src/tools/artifact-archive.ts +169 -0
  273. package/src/tools/tool-definition-compaction.ts +36 -0
  274. package/src/version.ts +1 -1
@@ -0,0 +1,622 @@
1
+ import type { CommandContext } from '../input/command-registry.ts';
2
+ import { previewHarnessText } from './agent-harness-text.ts';
3
+
4
+ export interface BrowserPwaRecordCertification {
5
+ readonly schemaStatus: 'certified' | 'legacy';
6
+ readonly schemaVersion?: string;
7
+ readonly publicationGuarantee?: string;
8
+ readonly publisher?: string;
9
+ readonly provenance?: readonly string[];
10
+ readonly receiptId?: string;
11
+ readonly cursor?: string;
12
+ readonly missingSignals: readonly string[];
13
+ readonly policy: string;
14
+ }
15
+
16
+ export interface BrowserPwaCategoryRouteRecord {
17
+ readonly id: string;
18
+ readonly categoryId: string;
19
+ readonly categoryIds: readonly string[];
20
+ readonly laneId: string | null;
21
+ readonly label: string | null;
22
+ readonly status: string;
23
+ readonly summary: string | null;
24
+ readonly route: string;
25
+ readonly url: string | null;
26
+ readonly mobileReady: boolean;
27
+ readonly pwaReady: boolean;
28
+ readonly capabilities: readonly string[];
29
+ readonly controlRoutes: Readonly<Record<string, string>>;
30
+ readonly modelRoute: string;
31
+ readonly sourcePath: string;
32
+ readonly source: 'daemon-read-model' | 'sdk-read-model';
33
+ readonly certification: BrowserPwaRecordCertification;
34
+ }
35
+
36
+ export interface BrowserPwaFirstRunReceiptRecord {
37
+ readonly id: string;
38
+ readonly status: string;
39
+ readonly summary: string | null;
40
+ readonly url: string | null;
41
+ readonly manifestStatus: string | null;
42
+ readonly serviceWorkerStatus: string | null;
43
+ readonly installStatus: string | null;
44
+ readonly offlineStatus: string | null;
45
+ readonly capabilities: readonly string[];
46
+ readonly controlRoutes: Readonly<Record<string, string>>;
47
+ readonly modelRoute: string;
48
+ readonly sourcePath: string;
49
+ readonly source: 'daemon-read-model' | 'sdk-read-model';
50
+ readonly certification: BrowserPwaRecordCertification;
51
+ }
52
+
53
+ export interface BrowserPwaReadModelSnapshot {
54
+ readonly categoryRoutes: readonly BrowserPwaCategoryRouteRecord[];
55
+ readonly firstRunReceipts: readonly BrowserPwaFirstRunReceiptRecord[];
56
+ readonly sourceCounts: Readonly<Record<string, number>>;
57
+ }
58
+
59
+ interface SourceCandidate {
60
+ readonly path: string;
61
+ readonly source: unknown;
62
+ readonly kind: 'daemon-read-model' | 'sdk-read-model';
63
+ }
64
+
65
+ interface CollectedRecord {
66
+ readonly path: string;
67
+ readonly kind: 'daemon-read-model' | 'sdk-read-model';
68
+ readonly record: Record<string, unknown>;
69
+ }
70
+
71
+ const CATEGORY_WRAPPER_KEYS = [
72
+ 'records',
73
+ 'items',
74
+ 'categoryRoutes',
75
+ 'workspaceRoutes',
76
+ 'agentWorkspaceRoutes',
77
+ 'browserRoutes',
78
+ 'mobileRoutes',
79
+ 'surfaces',
80
+ 'lanes',
81
+ 'categories',
82
+ ] as const;
83
+
84
+ const RECEIPT_WRAPPER_KEYS = [
85
+ 'records',
86
+ 'items',
87
+ 'receipts',
88
+ 'firstRunReceipts',
89
+ 'runtimeReceipts',
90
+ 'completionReceipts',
91
+ 'pwaReceipts',
92
+ 'events',
93
+ 'eventStream',
94
+ ] as const;
95
+
96
+ const SNAPSHOT_METHODS = ['getSnapshot', 'snapshot', 'toJSON'] as const;
97
+ const CATEGORY_METHODS = ['listCategoryRoutes', 'listWorkspaceRoutes', 'listAgentWorkspaceRoutes', 'listRoutes', 'list'] as const;
98
+ const RECEIPT_METHODS = ['listFirstRunReceipts', 'listReceipts', 'listRuntimeReceipts', 'listEvents', 'list'] as const;
99
+
100
+ function readObject(value: unknown): Record<string, unknown> {
101
+ return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
102
+ }
103
+
104
+ function readString(value: unknown): string {
105
+ return typeof value === 'string' ? value.trim() : '';
106
+ }
107
+
108
+ function readMetadata(record: Record<string, unknown>): Record<string, unknown> {
109
+ return readObject(record.metadata);
110
+ }
111
+
112
+ function nestedString(record: Record<string, unknown>, key: string): string {
113
+ return readString(record[key])
114
+ || readString(readMetadata(record)[key])
115
+ || readString(readObject(record.route)[key])
116
+ || readString(readObject(record.workspace)[key])
117
+ || readString(readObject(record.category)[key])
118
+ || readString(readObject(record.lane)[key])
119
+ || readString(readObject(record.pwa)[key])
120
+ || readString(readObject(record.browser)[key])
121
+ || readString(readObject(record.evidence)[key])
122
+ || readString(readObject(record.receipt)[key]);
123
+ }
124
+
125
+ function certificationRecords(record: Record<string, unknown>): readonly Record<string, unknown>[] {
126
+ return [
127
+ record,
128
+ readMetadata(record),
129
+ readObject(record.schema),
130
+ readObject(record.contract),
131
+ readObject(record.publication),
132
+ readObject(record.receipt),
133
+ readObject(record.certification),
134
+ ];
135
+ }
136
+
137
+ function firstAcross(records: readonly Record<string, unknown>[], keys: readonly string[]): string {
138
+ for (const record of records) {
139
+ for (const key of keys) {
140
+ const value = readString(record[key]);
141
+ if (value) return value;
142
+ }
143
+ }
144
+ return '';
145
+ }
146
+
147
+ function stringArray(value: unknown): readonly string[] {
148
+ if (Array.isArray(value)) return [...new Set(value.map((entry) => readString(entry)).filter(Boolean))].slice(0, 80);
149
+ const text = readString(value);
150
+ return text ? text.split(',').map((entry) => entry.trim()).filter(Boolean).slice(0, 80) : [];
151
+ }
152
+
153
+ function redactText(value: string): string {
154
+ return value
155
+ .replace(/\b(bearer)\s+[a-z0-9._~+/=-]+/gi, '$1 <redacted>')
156
+ .replace(/\b(token|secret|password|api[_-]?key|authorization|credential)\s*[:=]\s*[^,\s;/]+/gi, '$1=<redacted>');
157
+ }
158
+
159
+ function safePreview(value: string, limit: number): string {
160
+ return previewHarnessText(redactText(value), limit);
161
+ }
162
+
163
+ function safeNullablePreview(value: string, limit: number): string | null {
164
+ return value ? safePreview(value, limit) : null;
165
+ }
166
+
167
+ function safeUrl(value: string): string | null {
168
+ if (!value) return null;
169
+ try {
170
+ const url = new URL(value);
171
+ for (const key of [...url.searchParams.keys()]) {
172
+ if (/token|secret|password|authorization|credential|api[-_]?key/i.test(key)) url.searchParams.set(key, '<redacted>');
173
+ }
174
+ return previewHarnessText(url.toString(), 180);
175
+ } catch {
176
+ return safeNullablePreview(value, 180);
177
+ }
178
+ }
179
+
180
+ function normalizeToken(value: string): string {
181
+ return value.trim().toLowerCase().replace(/[_\s]+/g, '-');
182
+ }
183
+
184
+ function readyStatus(value: string): boolean {
185
+ const status = normalizeToken(value);
186
+ return [
187
+ 'ready',
188
+ 'published',
189
+ 'complete',
190
+ 'completed',
191
+ 'ok',
192
+ 'success',
193
+ 'succeeded',
194
+ 'active',
195
+ 'available',
196
+ 'usable',
197
+ 'online',
198
+ 'healthy',
199
+ 'browser-native-ready',
200
+ 'mobile-ready',
201
+ 'pwa-ready',
202
+ ].includes(status);
203
+ }
204
+
205
+ function readBoolean(value: unknown): boolean | null {
206
+ if (typeof value === 'boolean') return value;
207
+ if (typeof value === 'string') {
208
+ const token = normalizeToken(value);
209
+ if (['true', 'yes', '1', 'ready', 'available', 'enabled', 'mobile-ready', 'pwa-ready', 'responsive'].includes(token)) return true;
210
+ if (['false', 'no', '0', 'missing', 'disabled', 'not-ready', 'setup-needed', 'blocked'].includes(token)) return false;
211
+ }
212
+ return null;
213
+ }
214
+
215
+ function certificationBase(record: Record<string, unknown>, sourcePath: string): {
216
+ readonly records: readonly Record<string, unknown>[];
217
+ readonly schemaStatus: BrowserPwaRecordCertification['schemaStatus'];
218
+ readonly schemaVersion: string;
219
+ readonly publicationGuarantee: string;
220
+ readonly publisher: string;
221
+ readonly receiptId: string;
222
+ readonly cursor: string;
223
+ readonly provenance: readonly string[];
224
+ } {
225
+ const records = certificationRecords(record);
226
+ const explicit = firstAcross(records, ['schemaStatus', 'receiptSchemaStatus', 'certificationStatus']).toLowerCase().replace(/[_\s]+/g, '-');
227
+ const schemaStatus = ['certified', 'valid', 'verified', 'schema-certified'].includes(explicit)
228
+ || (
229
+ firstAcross(records, ['schemaVersion', 'receiptSchemaVersion', 'contractVersion'])
230
+ && firstAcross(records, ['publicationGuarantee', 'hostPublicationGuarantee', 'browserPwaPublicationGuarantee', 'routePublicationGuarantee', 'firstRunPublicationGuarantee'])
231
+ && firstAcross(records, ['publisher', 'publisherId', 'daemonId', 'hostId'])
232
+ )
233
+ ? 'certified'
234
+ : 'legacy';
235
+ const schemaVersion = firstAcross(records, ['schemaVersion', 'receiptSchemaVersion', 'contractVersion']);
236
+ const publicationGuarantee = firstAcross(records, ['publicationGuarantee', 'hostPublicationGuarantee', 'browserPwaPublicationGuarantee', 'routePublicationGuarantee', 'firstRunPublicationGuarantee']);
237
+ const publisher = firstAcross(records, ['publisher', 'publisherId', 'daemonId', 'hostId']);
238
+ const receiptId = firstAcross(records, ['receiptId', 'routeReceiptId', 'categoryRouteReceiptId', 'firstRunReceiptId', 'durableReceiptId', 'eventId']);
239
+ const cursor = firstAcross(records, ['cursor', 'freshnessCursor', 'eventCursor', 'sequence', 'checkpoint', 'streamCursor']);
240
+ const provenance = [...new Set([
241
+ ...stringArray(record.provenance),
242
+ sourcePath ? `source ${sourcePath}` : '',
243
+ firstAcross(records, ['methodId']) ? `method ${firstAcross(records, ['methodId'])}` : '',
244
+ firstAcross(records, ['actionId']) ? `action ${firstAcross(records, ['actionId'])}` : '',
245
+ firstAcross(records, ['sourceTool']) ? `sourceTool ${firstAcross(records, ['sourceTool'])}` : '',
246
+ ].map((entry) => safePreview(entry, 180)).filter(Boolean))].slice(0, 8);
247
+ return {
248
+ records,
249
+ schemaStatus,
250
+ schemaVersion,
251
+ publicationGuarantee,
252
+ publisher,
253
+ receiptId,
254
+ cursor,
255
+ provenance,
256
+ };
257
+ }
258
+
259
+ function certification(input: {
260
+ readonly record: Record<string, unknown>;
261
+ readonly sourcePath: string;
262
+ readonly kind: 'browser-native workspace category route' | 'browser/PWA first-run receipt';
263
+ readonly durableId: string;
264
+ readonly status: string;
265
+ readonly modelRoute: string;
266
+ readonly hasRoute: boolean;
267
+ readonly hasCategories?: boolean;
268
+ readonly hasMobileEvidence?: boolean;
269
+ readonly hasPwaEvidence?: boolean;
270
+ }): BrowserPwaRecordCertification {
271
+ const base = certificationBase(input.record, input.sourcePath);
272
+ const missingSignals = [
273
+ ...(base.schemaStatus === 'certified' ? [] : [`Certified ${input.kind} schema is not published.`]),
274
+ ...(input.durableId ? [] : [`Durable ${input.kind} id is not published.`]),
275
+ ...(input.status ? [] : [`${input.kind} status is not published.`]),
276
+ ...(readyStatus(input.status) ? [] : [`${input.kind} status is not ready.`]),
277
+ ...(base.publicationGuarantee ? [] : [`${input.kind} publication guarantee is not published.`]),
278
+ ...(base.publisher ? [] : [`${input.kind} publisher is not published.`]),
279
+ ...(base.provenance.length > 0 ? [] : [`${input.kind} provenance is not published.`]),
280
+ ...(base.cursor ? [] : [`${input.kind} freshness cursor is not published.`]),
281
+ ...(input.modelRoute && input.hasRoute ? [] : [`Exact ${input.kind} inspect/open route is not published.`]),
282
+ ...(input.hasCategories === false ? ['Browser-native Agent workspace category ids are not published.'] : []),
283
+ ...(input.hasMobileEvidence === false ? ['Mobile/touch control evidence is not published for this browser-native route.'] : []),
284
+ ...(input.hasPwaEvidence === false ? ['PWA runtime evidence such as manifest, service-worker, install, or offline status is not published.'] : []),
285
+ ];
286
+ return {
287
+ schemaStatus: base.schemaStatus,
288
+ ...(base.schemaVersion ? { schemaVersion: safePreview(base.schemaVersion, 80) } : {}),
289
+ ...(base.publicationGuarantee ? { publicationGuarantee: safePreview(base.publicationGuarantee, 220) } : {}),
290
+ ...(base.publisher ? { publisher: safePreview(base.publisher, 80) } : {}),
291
+ ...(base.provenance.length > 0 ? { provenance: base.provenance } : {}),
292
+ ...(base.receiptId ? { receiptId: safePreview(base.receiptId, 96) } : {}),
293
+ ...(base.cursor ? { cursor: safePreview(base.cursor, 96) } : {}),
294
+ missingSignals,
295
+ policy: 'Browser/PWA read models certify release readiness only when the SDK or daemon publishes schema, durable ids, ready status, publication guarantee, publisher/provenance, freshness cursor, exact inspect/open routes, mobile/touch evidence, PWA runtime evidence where relevant, and redacted receipt metadata.',
296
+ };
297
+ }
298
+
299
+ function callMethod(source: Record<string, unknown>, method: string): unknown {
300
+ const fn = source[method];
301
+ if (typeof fn !== 'function') return undefined;
302
+ try {
303
+ return (fn as () => unknown).call(source);
304
+ } catch {
305
+ return undefined;
306
+ }
307
+ }
308
+
309
+ function collectFromSource(
310
+ source: unknown,
311
+ path: string,
312
+ kind: 'daemon-read-model' | 'sdk-read-model',
313
+ wrapperKeys: readonly string[],
314
+ methods: readonly string[],
315
+ visited = new WeakSet<object>(),
316
+ ): readonly CollectedRecord[] {
317
+ if (!source) return [];
318
+ if (Array.isArray(source)) {
319
+ return source.flatMap((entry, index) => collectFromSource(entry, `${path}[${index}]`, kind, wrapperKeys, methods, visited));
320
+ }
321
+ if (source instanceof Map) {
322
+ return Array.from(source.entries()).flatMap(([key, value]) => collectFromSource(value, `${path}.${String(key)}`, kind, wrapperKeys, methods, visited));
323
+ }
324
+ if (typeof source !== 'object') return [];
325
+ if (visited.has(source)) return [];
326
+ visited.add(source);
327
+ const record = source as Record<string, unknown>;
328
+ const fromSnapshots = SNAPSHOT_METHODS.flatMap((method) => {
329
+ const snapshot = callMethod(record, method);
330
+ return snapshot === undefined ? [] : collectFromSource(snapshot, `${path}.${method}()`, kind, wrapperKeys, methods, visited);
331
+ });
332
+ const fromMethods = methods.flatMap((method) => {
333
+ const snapshot = callMethod(record, method);
334
+ return snapshot === undefined ? [] : collectFromSource(snapshot, `${path}.${method}()`, kind, wrapperKeys, methods, visited);
335
+ });
336
+ const fromWrappers = wrapperKeys.flatMap((key) => {
337
+ if (!(key in record)) return [];
338
+ return collectFromSource(record[key], `${path}.${key}`, kind, wrapperKeys, methods, visited);
339
+ });
340
+ if (fromSnapshots.length > 0 || fromMethods.length > 0 || fromWrappers.length > 0) {
341
+ return [...fromSnapshots, ...fromMethods, ...fromWrappers];
342
+ }
343
+ return [{ path, kind, record }];
344
+ }
345
+
346
+ function routeMap(record: Record<string, unknown>): Readonly<Record<string, string>> {
347
+ const routes = { ...readObject(record.routes), ...readObject(record.controls) };
348
+ const output: Record<string, string> = {};
349
+ for (const key of ['inspect', 'open', 'chat', 'setup', 'approve', 'approvals', 'automations', 'memory', 'channels', 'install', 'manifest', 'serviceWorker', 'offline', 'finish']) {
350
+ const value = readString(routes[key]) || nestedString(record, `${key}Route`);
351
+ if (value) output[key] = safePreview(value, 180);
352
+ }
353
+ return output;
354
+ }
355
+
356
+ function routeForRecord(record: Record<string, unknown>, routes: Readonly<Record<string, string>>): string {
357
+ return nestedString(record, 'browserRoute')
358
+ || nestedString(record, 'openRoute')
359
+ || nestedString(record, 'route')
360
+ || routes.open
361
+ || routes.inspect
362
+ || '';
363
+ }
364
+
365
+ function modelRoute(record: Record<string, unknown>, fallback: string): string {
366
+ return nestedString(record, 'modelRoute')
367
+ || nestedString(record, 'inspectRoute')
368
+ || nestedString(record, 'browserRoute')
369
+ || nestedString(record, 'openRoute')
370
+ || nestedString(record, 'route')
371
+ || fallback;
372
+ }
373
+
374
+ function capabilityTokens(record: Record<string, unknown>): readonly string[] {
375
+ return [...new Set([
376
+ ...stringArray(record.capabilities),
377
+ ...stringArray(record.features),
378
+ ...stringArray(readObject(record.pwa).capabilities),
379
+ ...stringArray(readObject(record.browser).capabilities),
380
+ ...stringArray(readObject(record.evidence).capabilities),
381
+ ].map((entry) => safePreview(entry, 80)).filter(Boolean))].slice(0, 24);
382
+ }
383
+
384
+ function categoryIds(record: Record<string, unknown>, index: number): readonly string[] {
385
+ const ids = [
386
+ ...stringArray(record.categoryIds),
387
+ ...stringArray(readObject(record.route).categoryIds),
388
+ ...stringArray(readObject(record.workspace).categoryIds),
389
+ nestedString(record, 'categoryId'),
390
+ nestedString(record, 'workspaceCategoryId'),
391
+ ].map((entry) => entry.trim()).filter(Boolean);
392
+ return [...new Set(ids.length > 0 ? ids : [`browser-category-${index}`])].slice(0, 80);
393
+ }
394
+
395
+ function evidenceBoolean(record: Record<string, unknown>, keys: readonly string[], terms: readonly string[]): boolean {
396
+ for (const key of keys) {
397
+ const value = readBoolean(record[key])
398
+ ?? readBoolean(readMetadata(record)[key])
399
+ ?? readBoolean(readObject(record.pwa)[key])
400
+ ?? readBoolean(readObject(record.browser)[key])
401
+ ?? readBoolean(readObject(record.evidence)[key]);
402
+ if (value !== null) return value;
403
+ }
404
+ const haystack = [
405
+ ...capabilityTokens(record),
406
+ nestedString(record, 'summary'),
407
+ nestedString(record, 'label'),
408
+ nestedString(record, 'status'),
409
+ ].join('\n').toLowerCase();
410
+ return terms.some((term) => haystack.includes(term));
411
+ }
412
+
413
+ function normalizeCategoryRoute(entry: CollectedRecord, index: number): BrowserPwaCategoryRouteRecord | null {
414
+ const ids = categoryIds(entry.record, index);
415
+ const categoryId = ids[0] ?? `browser-category-${index}`;
416
+ const id = nestedString(entry.record, 'id')
417
+ || nestedString(entry.record, 'routeId')
418
+ || nestedString(entry.record, 'receiptId')
419
+ || `browser-category-route:${categoryId}:${index}`;
420
+ const routes = routeMap(entry.record);
421
+ const route = routeForRecord(entry.record, routes);
422
+ const status = nestedString(entry.record, 'status') || nestedString(entry.record, 'state') || 'unknown';
423
+ const routeForModel = modelRoute(entry.record, `computer action:"browser" categoryId:"${categoryId}" includeParameters:true`);
424
+ const capabilities = capabilityTokens(entry.record);
425
+ const mobileReady = evidenceBoolean(entry.record, ['mobileReady', 'touchReady', 'responsive', 'mobileStatus'], ['mobile', 'touch', 'responsive']);
426
+ const pwaReady = evidenceBoolean(entry.record, ['pwaReady', 'installReady', 'offlineReady', 'pwaStatus'], ['pwa', 'install', 'offline', 'service worker', 'manifest']);
427
+ return {
428
+ id,
429
+ categoryId,
430
+ categoryIds: ids,
431
+ laneId: safeNullablePreview(nestedString(entry.record, 'laneId'), 80),
432
+ label: safeNullablePreview(nestedString(entry.record, 'label') || nestedString(entry.record, 'title'), 120),
433
+ status,
434
+ summary: safeNullablePreview(nestedString(entry.record, 'summary') || nestedString(entry.record, 'detail'), 220),
435
+ route: route ? safePreview(route, 180) : routeForModel,
436
+ url: safeUrl(nestedString(entry.record, 'url') || nestedString(entry.record, 'browserUrl')),
437
+ mobileReady,
438
+ pwaReady,
439
+ capabilities,
440
+ controlRoutes: routes,
441
+ modelRoute: routeForModel,
442
+ sourcePath: entry.path,
443
+ source: entry.kind,
444
+ certification: certification({
445
+ record: entry.record,
446
+ sourcePath: entry.path,
447
+ kind: 'browser-native workspace category route',
448
+ durableId: id,
449
+ status,
450
+ modelRoute: routeForModel,
451
+ hasRoute: Boolean(route || Object.keys(routes).length > 0),
452
+ hasCategories: ids.length > 0,
453
+ hasMobileEvidence: mobileReady || Object.keys(routes).length > 0,
454
+ }),
455
+ };
456
+ }
457
+
458
+ function normalizeFirstRunReceipt(entry: CollectedRecord, index: number): BrowserPwaFirstRunReceiptRecord | null {
459
+ const id = nestedString(entry.record, 'id')
460
+ || nestedString(entry.record, 'receiptId')
461
+ || nestedString(entry.record, 'firstRunReceiptId')
462
+ || nestedString(entry.record, 'durableReceiptId')
463
+ || nestedString(entry.record, 'eventId')
464
+ || `browser-pwa-first-run:${index}`;
465
+ const routes = routeMap(entry.record);
466
+ const status = nestedString(entry.record, 'status')
467
+ || nestedString(entry.record, 'state')
468
+ || nestedString(entry.record, 'receiptStatus')
469
+ || nestedString(entry.record, 'result')
470
+ || nestedString(entry.record, 'outcome')
471
+ || 'unknown';
472
+ const routeForModel = modelRoute(entry.record, 'computer action:"browser" includeParameters:true');
473
+ const capabilities = capabilityTokens(entry.record);
474
+ const manifestStatus = safeNullablePreview(nestedString(entry.record, 'manifestStatus'), 80);
475
+ const serviceWorkerStatus = safeNullablePreview(nestedString(entry.record, 'serviceWorkerStatus'), 80);
476
+ const installStatus = safeNullablePreview(nestedString(entry.record, 'installStatus'), 80);
477
+ const offlineStatus = safeNullablePreview(nestedString(entry.record, 'offlineStatus'), 80);
478
+ const hasPwaEvidence = Boolean(manifestStatus || serviceWorkerStatus || installStatus || offlineStatus)
479
+ || evidenceBoolean(entry.record, ['pwaReady', 'manifestReady', 'serviceWorkerReady', 'offlineReady', 'installReady'], ['pwa', 'manifest', 'service worker', 'offline', 'install']);
480
+ return {
481
+ id,
482
+ status,
483
+ summary: safeNullablePreview(nestedString(entry.record, 'summary') || nestedString(entry.record, 'detail'), 220),
484
+ url: safeUrl(nestedString(entry.record, 'url') || nestedString(entry.record, 'browserUrl') || nestedString(entry.record, 'pwaUrl')),
485
+ manifestStatus,
486
+ serviceWorkerStatus,
487
+ installStatus,
488
+ offlineStatus,
489
+ capabilities,
490
+ controlRoutes: routes,
491
+ modelRoute: routeForModel,
492
+ sourcePath: entry.path,
493
+ source: entry.kind,
494
+ certification: certification({
495
+ record: entry.record,
496
+ sourcePath: entry.path,
497
+ kind: 'browser/PWA first-run receipt',
498
+ durableId: id,
499
+ status,
500
+ modelRoute: routeForModel,
501
+ hasRoute: Boolean(Object.keys(routes).length > 0 || routeForModel),
502
+ hasPwaEvidence,
503
+ }),
504
+ };
505
+ }
506
+
507
+ function dedupeById<T extends { readonly id: string }>(records: readonly T[]): readonly T[] {
508
+ const seen = new Set<string>();
509
+ const output: T[] = [];
510
+ for (const record of records) {
511
+ if (seen.has(record.id)) continue;
512
+ seen.add(record.id);
513
+ output.push(record);
514
+ }
515
+ return output;
516
+ }
517
+
518
+ function sourceCounts(entries: readonly CollectedRecord[]): Readonly<Record<string, number>> {
519
+ const counts: Record<string, number> = {};
520
+ for (const entry of entries) counts[entry.path] = (counts[entry.path] ?? 0) + 1;
521
+ return counts;
522
+ }
523
+
524
+ function categorySources(context: CommandContext): readonly SourceCandidate[] {
525
+ const platform = readObject(context.platform);
526
+ const readModels = readObject(platform.readModels);
527
+ const browserPwa = readObject(readModels.browserPwa);
528
+ const webDashboard = readObject(readModels.webDashboard);
529
+ const browserCockpit = readObject(readModels.browserCockpit);
530
+ const pwa = readObject(readModels.pwa);
531
+ const browser = readObject(readModels.browser);
532
+ const opsBrowserPwa = readObject(readObject(context.ops).browserPwa);
533
+ const clients = readObject(context.clients);
534
+ const operator = readObject(clients.operator);
535
+ const operatorSdk = readObject(clients.operatorSdk);
536
+ return [
537
+ { path: 'context.platform.readModels.browserPwa.categoryRoutes', source: browserPwa.categoryRoutes, kind: 'daemon-read-model' },
538
+ { path: 'context.platform.readModels.browserPwa.workspaceRoutes', source: browserPwa.workspaceRoutes, kind: 'daemon-read-model' },
539
+ { path: 'context.platform.readModels.browserPwa.agentWorkspaceRoutes', source: browserPwa.agentWorkspaceRoutes, kind: 'daemon-read-model' },
540
+ { path: 'context.platform.readModels.webDashboard.categoryRoutes', source: webDashboard.categoryRoutes, kind: 'daemon-read-model' },
541
+ { path: 'context.platform.readModels.webDashboard.workspaceRoutes', source: webDashboard.workspaceRoutes, kind: 'daemon-read-model' },
542
+ { path: 'context.platform.readModels.browserCockpit.categoryRoutes', source: browserCockpit.categoryRoutes, kind: 'daemon-read-model' },
543
+ { path: 'context.platform.readModels.pwa.workspaceRoutes', source: pwa.workspaceRoutes, kind: 'daemon-read-model' },
544
+ { path: 'context.platform.readModels.browser.categoryRoutes', source: browser.categoryRoutes, kind: 'daemon-read-model' },
545
+ { path: 'context.platform.readModels.browserPwaRoutes', source: readModels.browserPwaRoutes, kind: 'daemon-read-model' },
546
+ { path: 'context.ops.browserPwa.categoryRoutes', source: opsBrowserPwa.categoryRoutes, kind: 'sdk-read-model' },
547
+ { path: 'context.clients.operator.browserPwa.categoryRoutes', source: readObject(operator.browserPwa).categoryRoutes, kind: 'sdk-read-model' },
548
+ { path: 'context.clients.operatorSdk.browserPwa.categoryRoutes', source: readObject(operatorSdk.browserPwa).categoryRoutes, kind: 'sdk-read-model' },
549
+ ];
550
+ }
551
+
552
+ function receiptSources(context: CommandContext): readonly SourceCandidate[] {
553
+ const platform = readObject(context.platform);
554
+ const readModels = readObject(platform.readModels);
555
+ const browserPwa = readObject(readModels.browserPwa);
556
+ const webDashboard = readObject(readModels.webDashboard);
557
+ const browserCockpit = readObject(readModels.browserCockpit);
558
+ const pwa = readObject(readModels.pwa);
559
+ const firstRun = readObject(readModels.firstRun);
560
+ const opsBrowserPwa = readObject(readObject(context.ops).browserPwa);
561
+ const clients = readObject(context.clients);
562
+ const operator = readObject(clients.operator);
563
+ const operatorSdk = readObject(clients.operatorSdk);
564
+ return [
565
+ { path: 'context.platform.readModels.browserPwa.firstRunReceipts', source: browserPwa.firstRunReceipts, kind: 'daemon-read-model' },
566
+ { path: 'context.platform.readModels.browserPwa.receipts', source: browserPwa.receipts, kind: 'daemon-read-model' },
567
+ { path: 'context.platform.readModels.browserPwa.runtimeReceipts', source: browserPwa.runtimeReceipts, kind: 'daemon-read-model' },
568
+ { path: 'context.platform.readModels.webDashboard.firstRunReceipts', source: webDashboard.firstRunReceipts, kind: 'daemon-read-model' },
569
+ { path: 'context.platform.readModels.browserCockpit.firstRunReceipts', source: browserCockpit.firstRunReceipts, kind: 'daemon-read-model' },
570
+ { path: 'context.platform.readModels.pwa.firstRunReceipts', source: pwa.firstRunReceipts, kind: 'daemon-read-model' },
571
+ { path: 'context.platform.readModels.firstRun.browserPwaReceipts', source: firstRun.browserPwaReceipts, kind: 'daemon-read-model' },
572
+ { path: 'context.ops.browserPwa.firstRunReceipts', source: opsBrowserPwa.firstRunReceipts, kind: 'sdk-read-model' },
573
+ { path: 'context.clients.operator.browserPwa.firstRunReceipts', source: readObject(operator.browserPwa).firstRunReceipts, kind: 'sdk-read-model' },
574
+ { path: 'context.clients.operatorSdk.browserPwa.firstRunReceipts', source: readObject(operatorSdk.browserPwa).firstRunReceipts, kind: 'sdk-read-model' },
575
+ ];
576
+ }
577
+
578
+ export function isCertifiedBrowserPwaCategoryRoute(record: BrowserPwaCategoryRouteRecord): boolean {
579
+ return record.certification.schemaStatus === 'certified' && record.certification.missingSignals.length === 0;
580
+ }
581
+
582
+ export function isCertifiedBrowserPwaFirstRunReceipt(record: BrowserPwaFirstRunReceiptRecord): boolean {
583
+ return record.certification.schemaStatus === 'certified' && record.certification.missingSignals.length === 0;
584
+ }
585
+
586
+ export function browserPwaRouteCoversCategory(record: BrowserPwaCategoryRouteRecord, categoryId: string): boolean {
587
+ const expected = categoryId.toLowerCase();
588
+ return record.categoryIds.some((id) => {
589
+ const value = id.toLowerCase();
590
+ return value === expected || value === '*' || value === 'all' || value === 'agent-workspace';
591
+ });
592
+ }
593
+
594
+ export function certifiedBrowserPwaCategoryRouteForCategory(
595
+ snapshot: BrowserPwaReadModelSnapshot,
596
+ categoryId: string,
597
+ ): BrowserPwaCategoryRouteRecord | null {
598
+ return snapshot.categoryRoutes.find((record) => isCertifiedBrowserPwaCategoryRoute(record) && browserPwaRouteCoversCategory(record, categoryId)) ?? null;
599
+ }
600
+
601
+ export function certifiedBrowserPwaFirstRunReceipts(
602
+ snapshot: BrowserPwaReadModelSnapshot,
603
+ ): readonly BrowserPwaFirstRunReceiptRecord[] {
604
+ return snapshot.firstRunReceipts.filter(isCertifiedBrowserPwaFirstRunReceipt);
605
+ }
606
+
607
+ export function browserPwaReadModelSnapshot(context: CommandContext): BrowserPwaReadModelSnapshot {
608
+ const categoryEntries = categorySources(context).flatMap((candidate) =>
609
+ collectFromSource(candidate.source, candidate.path, candidate.kind, CATEGORY_WRAPPER_KEYS, CATEGORY_METHODS)
610
+ );
611
+ const receiptEntries = receiptSources(context).flatMap((candidate) =>
612
+ collectFromSource(candidate.source, candidate.path, candidate.kind, RECEIPT_WRAPPER_KEYS, RECEIPT_METHODS)
613
+ );
614
+ return {
615
+ categoryRoutes: dedupeById(categoryEntries.map(normalizeCategoryRoute).filter((entry): entry is BrowserPwaCategoryRouteRecord => entry !== null)),
616
+ firstRunReceipts: dedupeById(receiptEntries.map(normalizeFirstRunReceipt).filter((entry): entry is BrowserPwaFirstRunReceiptRecord => entry !== null)),
617
+ sourceCounts: {
618
+ ...sourceCounts(categoryEntries),
619
+ ...sourceCounts(receiptEntries),
620
+ },
621
+ };
622
+ }