@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,283 @@
1
+ import type { CommandContext } from '../input/command-registry.ts';
2
+ import { previewHarnessText } from './agent-harness-text.ts';
3
+ import type { ModelProviderHealthSignal } from './agent-harness-model-routing-types.ts';
4
+ import { readBoolean, readFiniteNumber, readRecord, readString, safeIso } from './agent-harness-model-routing-utils.ts';
5
+
6
+ function readPotentialProviderHealthSnapshot(source: unknown): unknown {
7
+ if (!source || typeof source !== 'object') return null;
8
+ const record = source as Record<string, unknown>;
9
+ const directSnapshot = ['getSnapshot', 'snapshot', 'list', 'listProviders', 'listRoutes', 'listHealth']
10
+ .map((method) => {
11
+ const fn = record[method];
12
+ if (typeof fn !== 'function') return null;
13
+ const result = (fn as () => unknown).call(record);
14
+ return result instanceof Promise ? null : result;
15
+ })
16
+ .find((result) => result !== null);
17
+ return directSnapshot ?? record.snapshot ?? record.data ?? record.state ?? source;
18
+ }
19
+
20
+ const PROVIDER_HEALTH_SECRET_PATTERNS: readonly [RegExp, string][] = [
21
+ [/("?\b(?:api[-_]?key|apikey|token|secret|password|passwd|credential|authorization)\b"?\s*:\s*)("[^"]*"|'[^']*'|[^\s,}]+)/gi, '$1"<redacted>"'],
22
+ [/\b([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTHORIZATION|BEARER)[A-Z0-9_]*)=("[^"]*"|'[^']*'|[^\s]+)/gi, '$1=<redacted>'],
23
+ [/(\b(?:token|secret|password|passwd|api[-_]?key|apikey|authorization|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,}]+)/gi, '$1<redacted>'],
24
+ [/(Authorization:\s*Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, '$1<redacted>'],
25
+ ];
26
+
27
+ function redactProviderHealthText(value: string): string {
28
+ return PROVIDER_HEALTH_SECRET_PATTERNS.reduce((text, [pattern, replacement]) => text.replace(pattern, replacement), value);
29
+ }
30
+
31
+ function entryWithKey(key: unknown, entry: unknown): Record<string, unknown> {
32
+ const record = readRecord(entry);
33
+ const textKey = readString(key);
34
+ if (!textKey) return record;
35
+ const looksLikeRoute = textKey.includes(':') || textKey.includes('/');
36
+ return {
37
+ ...record,
38
+ ...(looksLikeRoute ? { modelRouteId: readString(record.modelRouteId) || readString(record.routeId) || textKey } : {}),
39
+ providerId: readString(record.providerId) || readString(record.provider) || (looksLikeRoute ? readString(record.providerId) : textKey),
40
+ };
41
+ }
42
+
43
+ function looksLikeProviderHealthRecord(record: Record<string, unknown>): boolean {
44
+ return Boolean(
45
+ readString(record.providerId)
46
+ || readString(record.provider)
47
+ || readString(record.modelRouteId)
48
+ || readString(record.routeId)
49
+ || readString(record.registryKey)
50
+ || readString(record.status)
51
+ || readFiniteNumber(record.avgLatencyMs) !== undefined
52
+ || Object.keys(readRecord(record.stats)).length > 0,
53
+ );
54
+ }
55
+
56
+ function providerHealthCandidatesFromSnapshot(snapshot: unknown): readonly Record<string, unknown>[] {
57
+ if (!snapshot || typeof snapshot !== 'object') return [];
58
+ if (snapshot instanceof Map) {
59
+ return [...snapshot.entries()].map(([key, entry]) => entryWithKey(key, entry));
60
+ }
61
+ const record = readRecord(snapshot);
62
+ for (const key of ['providers', 'providerHealth', 'routes', 'modelRoutes', 'routeHealth', 'providerRoutes', 'records', 'items', 'entries', 'health']) {
63
+ const value = record[key];
64
+ if (value instanceof Map) return [...value.entries()].map(([entryKey, entry]) => entryWithKey(entryKey, entry));
65
+ if (Array.isArray(value)) return value.map((entry) => readRecord(entry));
66
+ const valueRecord = readRecord(value);
67
+ if (Object.keys(valueRecord).length > 0) return Object.entries(valueRecord).map(([entryKey, entry]) => entryWithKey(entryKey, entry));
68
+ }
69
+ return looksLikeProviderHealthRecord(record) ? [record] : [];
70
+ }
71
+
72
+ function providerHealthCandidateProviderId(candidate: Record<string, unknown>): string {
73
+ return readString(candidate.providerId)
74
+ || readString(candidate.provider)
75
+ || readString(candidate.providerName)
76
+ || (readString(candidate.id).includes(':') ? '' : readString(candidate.id));
77
+ }
78
+
79
+ function providerHealthCandidateRouteId(candidate: Record<string, unknown>): string {
80
+ return readString(candidate.modelRouteId)
81
+ || readString(candidate.registryKey)
82
+ || readString(candidate.routeId)
83
+ || readString(candidate.modelRoute)
84
+ || (readString(candidate.id).includes(':') ? readString(candidate.id) : '');
85
+ }
86
+
87
+ function providerHealthCandidateMatchesProvider(candidate: Record<string, unknown>, providerId: string): boolean {
88
+ return providerHealthCandidateProviderId(candidate) === providerId;
89
+ }
90
+
91
+ function providerHealthRecordStats(candidate: Record<string, unknown>): Record<string, unknown> {
92
+ return readRecord(candidate.stats);
93
+ }
94
+
95
+ function providerHealthRecordRateLimit(candidate: Record<string, unknown>): Record<string, unknown> {
96
+ return readRecord(candidate.rateLimit);
97
+ }
98
+
99
+ function providerHealthRecordErrors(candidate: Record<string, unknown>): Record<string, unknown> {
100
+ return readRecord(candidate.errors);
101
+ }
102
+
103
+ function isoFromUnknown(value: unknown): string | null {
104
+ const numeric = readFiniteNumber(value);
105
+ if (numeric !== undefined) return safeIso(numeric);
106
+ const text = readString(value);
107
+ if (!text) return null;
108
+ const parsed = Date.parse(text);
109
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
110
+ }
111
+
112
+ function firstTimestamp(values: readonly unknown[]): string | null {
113
+ for (const value of values) {
114
+ const timestamp = isoFromUnknown(value);
115
+ if (timestamp) return timestamp;
116
+ }
117
+ return null;
118
+ }
119
+
120
+ function providerHealthTimestamp(candidate: Record<string, unknown>, stats: Record<string, unknown>, key: string): string | null {
121
+ return firstTimestamp([candidate[key], stats[key]]);
122
+ }
123
+
124
+ function nestedNumber(candidate: Record<string, unknown>, stats: Record<string, unknown>, nested: Record<string, unknown>, keys: readonly string[]): number | undefined {
125
+ for (const key of keys) {
126
+ const value = readFiniteNumber(candidate[key]) ?? readFiniteNumber(stats[key]) ?? readFiniteNumber(nested[key]);
127
+ if (value !== undefined) return value;
128
+ }
129
+ return undefined;
130
+ }
131
+
132
+ export function readProviderHealthSignal(context: CommandContext, providerId: string, modelRouteId?: string): ModelProviderHealthSignal {
133
+ const base: Omit<ModelProviderHealthSignal, 'status' | 'daemonPublication' | 'agentConsumption' | 'missingSignals' | 'policy'> = {
134
+ providerId,
135
+ ...(modelRouteId ? { modelRouteId } : {}),
136
+ sdkContract: {
137
+ providerHealthTypes: 'available',
138
+ importSurface: '@pellux/goodvibes-sdk/platform/runtime/ui',
139
+ note: 'The SDK publishes provider-health domain and UI enrichment types; types alone do not prove live connected-host health.',
140
+ },
141
+ };
142
+ const policy = 'Provider health is live route-condition evidence. Local benchmark artifacts remain separate task-fit evidence and must not be treated as provider-health publication.';
143
+ const platform = context.platform as unknown as Record<string, unknown>;
144
+ const readModels = readRecord(platform.readModels);
145
+ const clients = readRecord(context.clients as unknown as Record<string, unknown>);
146
+ const operator = readRecord(clients.operator);
147
+ const sourceEntries: readonly { readonly path: string; readonly source: unknown }[] = [
148
+ { path: 'context.platform.readModels.providerHealth', source: readModels.providerHealth },
149
+ { path: 'context.platform.readModels.providersHealth', source: readModels.providersHealth },
150
+ { path: 'context.platform.readModels.modelRouteHealth', source: readModels.modelRouteHealth },
151
+ { path: 'context.platform.readModels.routeHealth', source: readModels.routeHealth },
152
+ { path: 'context.platform.readModels.models.providerHealth', source: readRecord(readModels.models).providerHealth },
153
+ { path: 'context.platform.readModels.models.routeHealth', source: readRecord(readModels.models).routeHealth },
154
+ { path: 'context.platform.providerHealth', source: platform.providerHealth },
155
+ { path: 'context.clients.operator.providerHealth', source: operator.providerHealth },
156
+ ];
157
+
158
+ for (const entry of sourceEntries) {
159
+ if (entry.source === undefined || entry.source === null) continue;
160
+ try {
161
+ const snapshot = readPotentialProviderHealthSnapshot(entry.source);
162
+ const candidates = providerHealthCandidatesFromSnapshot(snapshot);
163
+ const candidate = (modelRouteId
164
+ ? candidates.find((item) => providerHealthCandidateRouteId(item) === modelRouteId)
165
+ : undefined)
166
+ ?? candidates.find((item) => providerHealthCandidateMatchesProvider(item, providerId));
167
+ if (!candidate) {
168
+ return {
169
+ ...base,
170
+ status: 'read-model-empty',
171
+ daemonPublication: {
172
+ status: 'published-read-model',
173
+ requiredPath: entry.path,
174
+ evidence: 'A provider-health read model path exists, but no matching provider record was returned.',
175
+ },
176
+ agentConsumption: {
177
+ status: 'waiting-for-published-feed',
178
+ readModelPath: entry.path,
179
+ evidence: `No provider-health record matched provider "${providerId}".`,
180
+ },
181
+ missingSignals: [`No daemon-published provider-health record matched provider "${providerId}" in ${entry.path}.`],
182
+ policy,
183
+ };
184
+ }
185
+
186
+ const stats = providerHealthRecordStats(candidate);
187
+ const rateLimit = providerHealthRecordRateLimit(candidate);
188
+ const errors = providerHealthRecordErrors(candidate);
189
+ const avgLatencyMs = readFiniteNumber(candidate.avgLatencyMs) ?? readFiniteNumber(stats.avgLatencyMs);
190
+ const minLatencyMs = readFiniteNumber(candidate.minLatencyMs) ?? readFiniteNumber(stats.minLatencyMs);
191
+ const maxLatencyMs = readFiniteNumber(candidate.maxLatencyMs) ?? readFiniteNumber(candidate.p95LatencyMs) ?? readFiniteNumber(stats.maxLatencyMs);
192
+ const healthStatus = readString(candidate.status) || 'unknown';
193
+ const rateLimitRemaining = nestedNumber(candidate, stats, rateLimit, ['rateLimitRemaining', 'remaining', 'requestsRemaining']);
194
+ const rateLimitLimit = nestedNumber(candidate, stats, rateLimit, ['rateLimitLimit', 'limit', 'requestsLimit']);
195
+ const rateLimitResetAt = firstTimestamp([candidate.rateLimitResetAt, stats.rateLimitResetAt, rateLimit.resetAt, rateLimit.resetsAt]);
196
+ const lastErrorAt = firstTimestamp([candidate.lastErrorAt, stats.lastErrorAt, errors.lastErrorAt]);
197
+ const lastErrorMessage = readString(candidate.lastErrorMessage)
198
+ || readString(stats.lastErrorMessage)
199
+ || readString(errors.lastErrorMessage)
200
+ || readString(errors.message);
201
+ const errorRate = nestedNumber(candidate, stats, errors, ['errorRate', 'recentErrorRate']);
202
+ const consecutiveErrors = nestedNumber(candidate, stats, errors, ['consecutiveErrors', 'failureStreak']);
203
+ const hasRateLimitPosture = rateLimitRemaining !== undefined || rateLimitLimit !== undefined || rateLimitResetAt !== null;
204
+ const hasErrorPosture = lastErrorAt !== null || lastErrorMessage || errorRate !== undefined || consecutiveErrors !== undefined;
205
+ return {
206
+ ...base,
207
+ status: 'record-found',
208
+ daemonPublication: {
209
+ status: 'published-read-model',
210
+ requiredPath: entry.path,
211
+ evidence: `Provider-health record found for provider "${providerId}".`,
212
+ },
213
+ agentConsumption: {
214
+ status: 'consumed',
215
+ readModelPath: entry.path,
216
+ evidence: 'Agent consumed the published provider-health record for exact model-route readiness.',
217
+ },
218
+ sourceRecordId: readString(candidate.recordId) || readString(candidate.id) || providerHealthCandidateRouteId(candidate) || providerHealthCandidateProviderId(candidate),
219
+ ...(providerHealthCandidateRouteId(candidate) ? { modelRouteId: providerHealthCandidateRouteId(candidate) } : {}),
220
+ healthStatus,
221
+ isConfigured: readBoolean(candidate.isConfigured),
222
+ isActive: readBoolean(candidate.isActive),
223
+ avgLatencyMs,
224
+ minLatencyMs,
225
+ maxLatencyMs,
226
+ lastSuccessAt: providerHealthTimestamp(candidate, stats, 'lastSuccessAt'),
227
+ lastErrorAt,
228
+ lastErrorMessage: lastErrorMessage ? previewHarnessText(redactProviderHealthText(lastErrorMessage), 160) : undefined,
229
+ errorRate,
230
+ consecutiveErrors,
231
+ lastCheckedAt: providerHealthTimestamp(candidate, stats, 'lastCheckedAt'),
232
+ rateLimitResetAt,
233
+ rateLimitRemaining,
234
+ rateLimitLimit,
235
+ missingSignals: [
236
+ ...(avgLatencyMs === undefined ? ['Provider-health record did not include average latency.'] : []),
237
+ ...(minLatencyMs === undefined ? ['Provider-health record did not include minimum latency.'] : []),
238
+ ...(maxLatencyMs === undefined ? ['Provider-health record did not include maximum or p95 latency.'] : []),
239
+ ...(hasRateLimitPosture ? [] : ['Provider-health record did not include rate-limit posture.']),
240
+ ...(hasErrorPosture ? [] : ['Provider-health record did not include error posture.']),
241
+ ],
242
+ policy,
243
+ };
244
+ } catch (error) {
245
+ return {
246
+ ...base,
247
+ status: 'read-model-error',
248
+ daemonPublication: {
249
+ status: 'published-read-model',
250
+ requiredPath: entry.path,
251
+ evidence: `Provider-health read model exists but failed while reading: ${previewHarnessText(error instanceof Error ? error.message : String(error), 120)}`,
252
+ },
253
+ agentConsumption: {
254
+ status: 'waiting-for-published-feed',
255
+ readModelPath: entry.path,
256
+ evidence: 'Agent could not safely consume this provider-health read model.',
257
+ },
258
+ missingSignals: ['Provider-health read model exists but failed while Agent was reading it.'],
259
+ policy,
260
+ };
261
+ }
262
+ }
263
+
264
+ return {
265
+ ...base,
266
+ status: 'not-reachable-in-command-context',
267
+ daemonPublication: {
268
+ status: 'not-published',
269
+ requiredPath: 'context.platform.readModels.providerHealth',
270
+ evidence: 'No daemon-published provider-health read model is reachable from Agent CommandContext.',
271
+ },
272
+ agentConsumption: {
273
+ status: 'waiting-for-published-feed',
274
+ readModelPath: null,
275
+ evidence: 'Agent cannot attach provider status or latency to exact routes until the connected host publishes a stable feed.',
276
+ },
277
+ missingSignals: [
278
+ 'SDK provider-health types are available, but no daemon-published provider.health feed is reachable in Agent CommandContext.',
279
+ 'Agent has not consumed provider status, configured state, live latency, rate-limit posture, or last error for this route.',
280
+ ],
281
+ policy,
282
+ };
283
+ }