@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,366 @@
1
+ import type { CommandContext } from '../input/command-registry.ts';
2
+ import { previewHarnessText } from './agent-harness-text.ts';
3
+
4
+ export interface DeviceLiveRecordCertification {
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 DeviceLiveCapabilityRecord {
17
+ readonly id: string;
18
+ readonly capabilityId: string;
19
+ readonly label: string | null;
20
+ readonly domain: string;
21
+ readonly status: string;
22
+ readonly summary: string | null;
23
+ readonly capabilities: readonly string[];
24
+ readonly permissionScope: string | null;
25
+ readonly route: string | null;
26
+ readonly modelRoute: string;
27
+ readonly controlRoutes: Readonly<Record<string, string>>;
28
+ readonly sourcePath: string;
29
+ readonly source: 'daemon-read-model' | 'sdk-read-model';
30
+ readonly certification: DeviceLiveRecordCertification;
31
+ }
32
+
33
+ export interface DeviceLiveReadModelSnapshot {
34
+ readonly capabilities: readonly DeviceLiveCapabilityRecord[];
35
+ readonly sourceCounts: Readonly<Record<string, number>>;
36
+ }
37
+
38
+ interface SourceCandidate {
39
+ readonly path: string;
40
+ readonly source: unknown;
41
+ readonly kind: 'daemon-read-model' | 'sdk-read-model';
42
+ }
43
+
44
+ interface CollectedRecord {
45
+ readonly path: string;
46
+ readonly kind: 'daemon-read-model' | 'sdk-read-model';
47
+ readonly record: Record<string, unknown>;
48
+ }
49
+
50
+ const WRAPPER_KEYS = [
51
+ 'records',
52
+ 'items',
53
+ 'capabilities',
54
+ 'deviceCapabilities',
55
+ 'mobileCapabilities',
56
+ 'voiceWorkflows',
57
+ 'workflows',
58
+ 'sensors',
59
+ 'sensorRoutes',
60
+ ] as const;
61
+
62
+ const SNAPSHOT_METHODS = ['getSnapshot', 'snapshot', 'toJSON'] as const;
63
+ const LIST_METHODS = ['listCapabilities', 'listDeviceCapabilities', 'listSensors', 'listVoiceWorkflows', 'list'] as const;
64
+
65
+ function readObject(value: unknown): Record<string, unknown> {
66
+ return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
67
+ }
68
+
69
+ function readString(value: unknown): string {
70
+ return typeof value === 'string' ? value.trim() : '';
71
+ }
72
+
73
+ function readMetadata(record: Record<string, unknown>): Record<string, unknown> {
74
+ return readObject(record.metadata);
75
+ }
76
+
77
+ function nestedString(record: Record<string, unknown>, key: string): string {
78
+ return readString(record[key])
79
+ || readString(readMetadata(record)[key])
80
+ || readString(readObject(record.capability)[key])
81
+ || readString(readObject(record.device)[key])
82
+ || readString(readObject(record.permission)[key])
83
+ || readString(readObject(record.evidence)[key])
84
+ || readString(readObject(record.route)[key]);
85
+ }
86
+
87
+ function certificationRecords(record: Record<string, unknown>): readonly Record<string, unknown>[] {
88
+ return [
89
+ record,
90
+ readMetadata(record),
91
+ readObject(record.schema),
92
+ readObject(record.contract),
93
+ readObject(record.publication),
94
+ readObject(record.receipt),
95
+ readObject(record.certification),
96
+ ];
97
+ }
98
+
99
+ function firstAcross(records: readonly Record<string, unknown>[], keys: readonly string[]): string {
100
+ for (const record of records) {
101
+ for (const key of keys) {
102
+ const value = readString(record[key]);
103
+ if (value) return value;
104
+ }
105
+ }
106
+ return '';
107
+ }
108
+
109
+ function stringArray(value: unknown): readonly string[] {
110
+ if (Array.isArray(value)) return [...new Set(value.map((entry) => readString(entry)).filter(Boolean))].slice(0, 16);
111
+ const text = readString(value);
112
+ return text ? text.split(',').map((entry) => entry.trim()).filter(Boolean).slice(0, 16) : [];
113
+ }
114
+
115
+ function redactText(value: string): string {
116
+ return value
117
+ .replace(/\b(bearer)\s+[a-z0-9._~+/=-]+/gi, '$1 <redacted>')
118
+ .replace(/\b(token|secret|password|api[_-]?key|authorization)\s*[:=]\s*[^,\s;/]+/gi, '$1=<redacted>');
119
+ }
120
+
121
+ function safePreview(value: string, limit: number): string {
122
+ return previewHarnessText(redactText(value), limit);
123
+ }
124
+
125
+ function safeNullablePreview(value: string, limit: number): string | null {
126
+ return value ? safePreview(value, limit) : null;
127
+ }
128
+
129
+ function schemaStatus(records: readonly Record<string, unknown>[]): DeviceLiveRecordCertification['schemaStatus'] {
130
+ const explicit = firstAcross(records, ['schemaStatus', 'receiptSchemaStatus', 'certificationStatus']).toLowerCase().replace(/[_\s]+/g, '-');
131
+ if (['certified', 'valid', 'verified', 'schema-certified'].includes(explicit)) return 'certified';
132
+ return firstAcross(records, ['schemaVersion', 'receiptSchemaVersion', 'contractVersion'])
133
+ && firstAcross(records, ['publicationGuarantee', 'hostPublicationGuarantee', 'devicePublicationGuarantee'])
134
+ && firstAcross(records, ['publisher', 'publisherId', 'daemonId', 'hostId'])
135
+ ? 'certified'
136
+ : 'legacy';
137
+ }
138
+
139
+ function routeMap(record: Record<string, unknown>): Readonly<Record<string, string>> {
140
+ const routes = { ...readObject(record.routes), ...readObject(record.controls) };
141
+ const output: Record<string, string> = {};
142
+ for (const key of ['inspect', 'open', 'run', 'capture', 'repairPermission', 'grantPermission', 'revokePermission', 'test']) {
143
+ const value = readString(routes[key]) || nestedString(record, `${key}Route`);
144
+ if (value) output[key] = safePreview(value, 180);
145
+ }
146
+ return output;
147
+ }
148
+
149
+ function certification(input: {
150
+ readonly record: Record<string, unknown>;
151
+ readonly sourcePath: string;
152
+ readonly id: string;
153
+ readonly capabilityId: string;
154
+ readonly status: string;
155
+ readonly permissionScope: string;
156
+ readonly modelRoute: string;
157
+ readonly hasRoutes: boolean;
158
+ }): DeviceLiveRecordCertification {
159
+ const records = certificationRecords(input.record);
160
+ const currentSchemaStatus = schemaStatus(records);
161
+ const schemaVersion = firstAcross(records, ['schemaVersion', 'receiptSchemaVersion', 'contractVersion']);
162
+ const publicationGuarantee = firstAcross(records, ['publicationGuarantee', 'hostPublicationGuarantee', 'devicePublicationGuarantee']);
163
+ const publisher = firstAcross(records, ['publisher', 'publisherId', 'daemonId', 'hostId']);
164
+ const receiptId = firstAcross(records, ['receiptId', 'capabilityReceiptId', 'permissionReceiptId', 'routeReceiptId']);
165
+ const cursor = firstAcross(records, ['cursor', 'freshnessCursor', 'sequence', 'checkpoint']);
166
+ const provenance = [...new Set([
167
+ ...stringArray(input.record.provenance),
168
+ input.sourcePath ? `source ${input.sourcePath}` : '',
169
+ firstAcross(records, ['methodId']) ? `method ${firstAcross(records, ['methodId'])}` : '',
170
+ firstAcross(records, ['sourceTool']) ? `sourceTool ${firstAcross(records, ['sourceTool'])}` : '',
171
+ ].map((entry) => safePreview(entry, 180)).filter(Boolean))].slice(0, 8);
172
+ const missingSignals = [
173
+ ...(currentSchemaStatus === 'certified' ? [] : ['Certified device capability schema is not published.']),
174
+ ...(input.id ? [] : ['Durable device capability record id is not published.']),
175
+ ...(input.capabilityId ? [] : ['Device capability id is not published.']),
176
+ ...(input.status ? [] : ['Device capability status is not published.']),
177
+ ...(publicationGuarantee ? [] : ['Device capability publication guarantee is not published.']),
178
+ ...(publisher ? [] : ['Device capability publisher is not published.']),
179
+ ...(provenance.length > 0 ? [] : ['Device capability provenance is not published.']),
180
+ ...(cursor ? [] : ['Device capability freshness cursor is not published.']),
181
+ ...(input.permissionScope ? [] : ['Permission scope is not published.']),
182
+ ...(input.modelRoute && input.hasRoutes ? [] : ['Exact inspect/control route is not published.']),
183
+ ];
184
+ return {
185
+ schemaStatus: currentSchemaStatus,
186
+ ...(schemaVersion ? { schemaVersion: safePreview(schemaVersion, 80) } : {}),
187
+ ...(publicationGuarantee ? { publicationGuarantee: safePreview(publicationGuarantee, 220) } : {}),
188
+ ...(publisher ? { publisher: safePreview(publisher, 80) } : {}),
189
+ ...(provenance.length > 0 ? { provenance } : {}),
190
+ ...(receiptId ? { receiptId: safePreview(receiptId, 96) } : {}),
191
+ ...(cursor ? { cursor: safePreview(cursor, 96) } : {}),
192
+ missingSignals,
193
+ policy: 'Device capability read models certify release readiness only when the SDK or daemon publishes schema, durable capability ids, permission scope, publication guarantee, publisher/provenance, freshness cursor, exact inspect/control routes, and redacted receipt evidence.',
194
+ };
195
+ }
196
+
197
+ function callMethod(source: Record<string, unknown>, method: string): unknown {
198
+ const fn = source[method];
199
+ if (typeof fn !== 'function') return undefined;
200
+ try {
201
+ return (fn as () => unknown).call(source);
202
+ } catch {
203
+ return undefined;
204
+ }
205
+ }
206
+
207
+ function collectFromSource(
208
+ source: unknown,
209
+ path: string,
210
+ kind: 'daemon-read-model' | 'sdk-read-model',
211
+ visited = new WeakSet<object>(),
212
+ ): readonly CollectedRecord[] {
213
+ if (!source) return [];
214
+ if (Array.isArray(source)) return source.flatMap((entry, index) => collectFromSource(entry, `${path}[${index}]`, kind, visited));
215
+ if (source instanceof Map) return Array.from(source.entries()).flatMap(([key, value]) => collectFromSource(value, `${path}.${String(key)}`, kind, visited));
216
+ if (typeof source !== 'object') return [];
217
+ if (visited.has(source)) return [];
218
+ visited.add(source);
219
+ const record = source as Record<string, unknown>;
220
+ const fromSnapshots = SNAPSHOT_METHODS.flatMap((method) => {
221
+ const snapshot = callMethod(record, method);
222
+ return snapshot === undefined ? [] : collectFromSource(snapshot, `${path}.${method}()`, kind, visited);
223
+ });
224
+ const fromMethods = LIST_METHODS.flatMap((method) => {
225
+ const snapshot = callMethod(record, method);
226
+ return snapshot === undefined ? [] : collectFromSource(snapshot, `${path}.${method}()`, kind, visited);
227
+ });
228
+ const fromWrappers = WRAPPER_KEYS.flatMap((key) => {
229
+ if (!(key in record)) return [];
230
+ return collectFromSource(record[key], `${path}.${key}`, kind, visited);
231
+ });
232
+ if (fromSnapshots.length > 0 || fromMethods.length > 0 || fromWrappers.length > 0) {
233
+ return [...fromSnapshots, ...fromMethods, ...fromWrappers];
234
+ }
235
+ return [{ path, kind, record }];
236
+ }
237
+
238
+ function modelRoute(record: Record<string, unknown>, capabilityId: string): string {
239
+ const routes = readObject(record.routes);
240
+ return nestedString(record, 'modelRoute')
241
+ || nestedString(record, 'inspectRoute')
242
+ || nestedString(record, 'route')
243
+ || readString(routes.inspect)
244
+ || readString(routes.open)
245
+ || readString(routes.run)
246
+ || `device action:"capability" capabilityId:"${capabilityId}" includeParameters:true`;
247
+ }
248
+
249
+ function capabilityId(record: Record<string, unknown>, index: number): string {
250
+ return nestedString(record, 'capabilityId')
251
+ || nestedString(record, 'id')
252
+ || nestedString(record, 'kind')
253
+ || `device-capability-${index}`;
254
+ }
255
+
256
+ function normalizeCapability(entry: CollectedRecord, index: number): DeviceLiveCapabilityRecord | null {
257
+ const capId = capabilityId(entry.record, index);
258
+ if (!capId) return null;
259
+ const id = nestedString(entry.record, 'id') || nestedString(entry.record, 'receiptId') || `device-live:${capId}:${index}`;
260
+ const routes = routeMap(entry.record);
261
+ const route = nestedString(entry.record, 'route') || nestedString(entry.record, 'openRoute') || routes.open || routes.run || routes.inspect || null;
262
+ const routeForModel = modelRoute(entry.record, capId);
263
+ const permissionScope = nestedString(entry.record, 'permissionScope') || nestedString(entry.record, 'scope') || nestedString(entry.record, 'permission');
264
+ const status = nestedString(entry.record, 'status') || nestedString(entry.record, 'state') || 'unknown';
265
+ return {
266
+ id,
267
+ capabilityId: capId,
268
+ label: safeNullablePreview(nestedString(entry.record, 'label') || nestedString(entry.record, 'title'), 120),
269
+ domain: nestedString(entry.record, 'domain') || nestedString(entry.record, 'surface') || 'device',
270
+ status,
271
+ summary: safeNullablePreview(nestedString(entry.record, 'summary') || nestedString(entry.record, 'detail'), 220),
272
+ capabilities: [...new Set([
273
+ ...stringArray(entry.record.capabilities),
274
+ ...stringArray(entry.record.features),
275
+ ...stringArray(readObject(entry.record.capability).capabilities),
276
+ capId,
277
+ ].map((value) => safePreview(value, 80)).filter(Boolean))].slice(0, 16),
278
+ permissionScope: safeNullablePreview(permissionScope, 120),
279
+ route: route ? safePreview(route, 180) : null,
280
+ modelRoute: routeForModel,
281
+ controlRoutes: routes,
282
+ sourcePath: entry.path,
283
+ source: entry.kind,
284
+ certification: certification({
285
+ record: entry.record,
286
+ sourcePath: entry.path,
287
+ id,
288
+ capabilityId: capId,
289
+ status,
290
+ permissionScope,
291
+ modelRoute: routeForModel,
292
+ hasRoutes: Boolean(route || Object.keys(routes).length > 0),
293
+ }),
294
+ };
295
+ }
296
+
297
+ function sourceCounts(entries: readonly CollectedRecord[]): Readonly<Record<string, number>> {
298
+ const counts: Record<string, number> = {};
299
+ for (const entry of entries) counts[entry.path] = (counts[entry.path] ?? 0) + 1;
300
+ return counts;
301
+ }
302
+
303
+ function sources(context: CommandContext): readonly SourceCandidate[] {
304
+ const platform = readObject(context.platform);
305
+ const readModels = readObject(platform.readModels);
306
+ const device = readObject(readModels.device);
307
+ const companion = readObject(readModels.companion);
308
+ const voice = readObject(readModels.voice);
309
+ const opsDevice = readObject(readObject(context.ops).device);
310
+ const clients = readObject(context.clients);
311
+ const operator = readObject(clients.operator);
312
+ const operatorSdk = readObject(clients.operatorSdk);
313
+ return [
314
+ { path: 'context.platform.readModels.device.capabilities', source: device.capabilities, kind: 'daemon-read-model' },
315
+ { path: 'context.platform.readModels.device.sensors', source: device.sensors, kind: 'daemon-read-model' },
316
+ { path: 'context.platform.readModels.companion.capabilities', source: companion.capabilities, kind: 'daemon-read-model' },
317
+ { path: 'context.platform.readModels.voice.workflows', source: voice.workflows, kind: 'daemon-read-model' },
318
+ { path: 'context.platform.readModels.deviceCapabilities', source: readModels.deviceCapabilities, kind: 'daemon-read-model' },
319
+ { path: 'context.platform.readModels.companionCapabilities', source: readModels.companionCapabilities, kind: 'daemon-read-model' },
320
+ { path: 'context.ops.device.capabilities', source: opsDevice.capabilities, kind: 'sdk-read-model' },
321
+ { path: 'context.clients.operator.device.capabilities', source: readObject(operator.device).capabilities, kind: 'sdk-read-model' },
322
+ { path: 'context.clients.operatorSdk.device.capabilities', source: readObject(operatorSdk.device).capabilities, kind: 'sdk-read-model' },
323
+ ];
324
+ }
325
+
326
+ export function isCertifiedDeviceLiveRecord(record: DeviceLiveCapabilityRecord): boolean {
327
+ return record.certification.schemaStatus === 'certified' && record.certification.missingSignals.length === 0;
328
+ }
329
+
330
+ export function certifiedDeviceLiveRecords(
331
+ snapshot: DeviceLiveReadModelSnapshot,
332
+ capabilityId: string,
333
+ terms: readonly string[] = [],
334
+ ): readonly DeviceLiveCapabilityRecord[] {
335
+ const expected = [capabilityId, ...terms].map((term) => term.toLowerCase());
336
+ return snapshot.capabilities.filter((record) => {
337
+ if (!isCertifiedDeviceLiveRecord(record)) return false;
338
+ const text = [
339
+ record.id,
340
+ record.capabilityId,
341
+ record.label ?? '',
342
+ record.domain,
343
+ record.status,
344
+ record.summary ?? '',
345
+ record.permissionScope ?? '',
346
+ ...record.capabilities,
347
+ ].join('\n').toLowerCase();
348
+ return expected.some((term) => text.includes(term));
349
+ });
350
+ }
351
+
352
+ export function deviceLiveReadModelSnapshot(context: CommandContext): DeviceLiveReadModelSnapshot {
353
+ const entries = sources(context).flatMap((candidate) => collectFromSource(candidate.source, candidate.path, candidate.kind));
354
+ const seen = new Set<string>();
355
+ const capabilities: DeviceLiveCapabilityRecord[] = [];
356
+ for (const record of entries.map(normalizeCapability).filter((entry): entry is DeviceLiveCapabilityRecord => entry !== null)) {
357
+ const key = `${record.capabilityId}:${record.id}`;
358
+ if (seen.has(key)) continue;
359
+ seen.add(key);
360
+ capabilities.push(record);
361
+ }
362
+ return {
363
+ capabilities,
364
+ sourceCounts: sourceCounts(entries),
365
+ };
366
+ }
@@ -0,0 +1,234 @@
1
+ import type { ArtifactDescriptor } from '@pellux/goodvibes-sdk/platform/artifacts';
2
+ import type { CommandContext } from '../input/command-registry.ts';
3
+ import { AgentDocumentRegistry, type AgentDocumentRecord } from '../agent/document-registry.ts';
4
+ import type { ReviewerReadinessCheck, ReviewerReadinessChecklist, ReviewerReadinessStatus } from './agent-harness-document-ops-types.ts';
5
+
6
+ function readMetadataString(value: unknown): string {
7
+ return typeof value === 'string' ? value.trim() : '';
8
+ }
9
+
10
+ function readMetadataStringList(value: unknown): readonly string[] {
11
+ if (typeof value === 'string') {
12
+ return value.split(/[,\n]/).map((entry) => entry.trim()).filter(Boolean);
13
+ }
14
+ if (!Array.isArray(value)) return [];
15
+ return value.filter((entry): entry is string => typeof entry === 'string').map((entry) => entry.trim()).filter(Boolean);
16
+ }
17
+
18
+ function artifactPurpose(artifact: ArtifactDescriptor): string {
19
+ return readMetadataString(artifact.metadata.purpose);
20
+ }
21
+
22
+ function isModelCompareArtifact(artifact: ArtifactDescriptor): boolean {
23
+ const purpose = artifactPurpose(artifact);
24
+ if (purpose === 'agent-model-compare') return true;
25
+ if (purpose) return false;
26
+ return readMetadataString(artifact.filename).startsWith('blind-model-comparison-cmp_');
27
+ }
28
+
29
+ function isModelCompareJudgmentArtifact(artifact: ArtifactDescriptor): boolean {
30
+ const purpose = artifactPurpose(artifact);
31
+ if (purpose === 'agent-model-compare-judgment') return true;
32
+ if (purpose) return false;
33
+ return readMetadataString(artifact.filename).startsWith('blind-model-comparison-judgment-');
34
+ }
35
+
36
+ function isModelCompareRouteDecisionArtifact(artifact: ArtifactDescriptor): boolean {
37
+ return artifactPurpose(artifact) === 'agent-model-compare-route-decision';
38
+ }
39
+
40
+ function isModelCompareHandoffArtifact(artifact: ArtifactDescriptor): boolean {
41
+ const purpose = artifactPurpose(artifact);
42
+ if (purpose === 'agent-model-compare-handoff') return true;
43
+ if (purpose) return false;
44
+ return readMetadataString(artifact.filename).startsWith('blind-model-comparison-handoff-');
45
+ }
46
+
47
+ function readDocuments(context: CommandContext): readonly AgentDocumentRecord[] {
48
+ const shellPaths = context.workspace?.shellPaths;
49
+ if (!shellPaths) return [];
50
+ try {
51
+ return AgentDocumentRegistry.fromShellPaths(shellPaths).list();
52
+ } catch {
53
+ return [];
54
+ }
55
+ }
56
+
57
+ function readArtifacts(context: CommandContext): readonly ArtifactDescriptor[] {
58
+ try {
59
+ return context.platform.artifactStore?.list?.(100) ?? [];
60
+ } catch {
61
+ return [];
62
+ }
63
+ }
64
+
65
+ function reviewerReadinessStatus(checks: readonly ReviewerReadinessCheck[]): ReviewerReadinessStatus {
66
+ if (checks.some((check) => check.status === 'needs-setup')) return 'needs-setup';
67
+ if (checks.some((check) => check.status === 'attention')) return 'attention';
68
+ return 'ready';
69
+ }
70
+
71
+ export function buildReviewerReadinessChecklist(
72
+ context: CommandContext,
73
+ options: {
74
+ readonly documentsReady: boolean;
75
+ readonly modelCompareReady: boolean;
76
+ readonly artifactBrowserReady: boolean;
77
+ },
78
+ ): ReviewerReadinessChecklist {
79
+ const documents = readDocuments(context);
80
+ const reviewDocuments = documents.filter((document) => document.status !== 'archived');
81
+ const openComments = reviewDocuments.reduce((total, document) => total + document.comments.filter((comment) => comment.status === 'open').length, 0);
82
+ const proposedSuggestions = reviewDocuments.reduce((total, document) => total + document.suggestions.filter((suggestion) => suggestion.status === 'proposed').length, 0);
83
+ const documentsMissingSourceArtifacts = reviewDocuments.filter((document) => document.attachments.length === 0 && !document.lastArtifactId).length;
84
+ const artifacts = readArtifacts(context);
85
+ const comparisons = artifacts.filter(isModelCompareArtifact);
86
+ const judgments = artifacts.filter(isModelCompareJudgmentArtifact);
87
+ const routeDecisions = artifacts.filter(isModelCompareRouteDecisionArtifact);
88
+ const handoffs = artifacts.filter(isModelCompareHandoffArtifact);
89
+ const revealedJudgments = judgments.filter((artifact) => (
90
+ artifact.metadata.revealIncludedInJudgment === true
91
+ && readMetadataString(artifact.metadata.winnerModel).length > 0
92
+ ));
93
+ const hiddenJudgments = judgments.filter((artifact) => (
94
+ artifact.metadata.revealIncludedInJudgment !== true
95
+ || readMetadataString(artifact.metadata.winnerModel).length === 0
96
+ ));
97
+ const routeDecisionComparisonIds = new Set(routeDecisions.map((artifact) => readMetadataString(artifact.metadata.comparisonId)).filter(Boolean));
98
+ const routeDecisionJudgmentArtifactIds = new Set(routeDecisions.map((artifact) => readMetadataString(artifact.metadata.judgmentArtifactId)).filter(Boolean));
99
+ const revealedJudgmentsWaitingOnRouteDecision = revealedJudgments.filter((artifact) => {
100
+ const comparisonId = readMetadataString(artifact.metadata.comparisonId);
101
+ return !routeDecisionJudgmentArtifactIds.has(artifact.id) && (!comparisonId || !routeDecisionComparisonIds.has(comparisonId));
102
+ });
103
+ const revealedComparisonIds = new Set(revealedJudgments.map((artifact) => readMetadataString(artifact.metadata.comparisonId)).filter(Boolean));
104
+ const unrevealedComparisons = comparisons.filter((artifact) => {
105
+ const comparisonId = readMetadataString(artifact.metadata.comparisonId);
106
+ return artifact.metadata.revealIncludedInTranscript !== true && (!comparisonId || !revealedComparisonIds.has(comparisonId));
107
+ });
108
+ const handoffsMissingRelatedArtifacts = handoffs.filter((artifact) => readMetadataStringList(artifact.metadata.relatedArtifactIds).length === 0).length;
109
+ const comparisonSourceMissing = comparisons.filter((artifact) => (
110
+ !readMetadataString(artifact.metadata.sourceArtifactId)
111
+ && !readMetadataString(artifact.metadata.documentId)
112
+ )).length;
113
+ const missingSourceArtifacts = documentsMissingSourceArtifacts + comparisonSourceMissing;
114
+ const firstOpenCommentDocument = reviewDocuments.find((document) => document.comments.some((comment) => comment.status === 'open'));
115
+ const firstProposedSuggestionDocument = reviewDocuments.find((document) => document.suggestions.some((suggestion) => suggestion.status === 'proposed'));
116
+ const firstUnrevealedComparison = unrevealedComparisons[0];
117
+ const firstHiddenJudgment = hiddenJudgments[0];
118
+ const firstRevealedJudgment = revealedJudgmentsWaitingOnRouteDecision[0];
119
+ const firstHandoffMissingRelated = handoffs.find((artifact) => readMetadataStringList(artifact.metadata.relatedArtifactIds).length === 0);
120
+
121
+ const checks: ReviewerReadinessCheck[] = [
122
+ {
123
+ id: 'review-tooling',
124
+ label: 'Review tooling',
125
+ status: options.documentsReady && options.modelCompareReady && options.artifactBrowserReady ? 'pass' : 'needs-setup',
126
+ count: Number(options.documentsReady) + Number(options.modelCompareReady) + Number(options.artifactBrowserReady),
127
+ detail: options.documentsReady && options.modelCompareReady && options.artifactBrowserReady
128
+ ? 'Document, artifact, and blind comparison routes are available.'
129
+ : 'Document, artifact, and blind comparison routes must be available before a reviewer-ready export can be trusted.',
130
+ inspectRoute: 'agent_harness mode:"document_ops"',
131
+ repairRoute: 'agent_harness mode:"workspace_actions" categoryId:"documents"',
132
+ },
133
+ {
134
+ id: 'document-review-state',
135
+ label: 'Document comments and suggestions',
136
+ status: openComments + proposedSuggestions > 0 ? 'attention' : 'pass',
137
+ count: openComments + proposedSuggestions,
138
+ detail: openComments + proposedSuggestions > 0
139
+ ? `${openComments} open comment(s) and ${proposedSuggestions} proposed suggestion(s) need a visible resolve, accept, or reject decision.`
140
+ : 'No open document comments or proposed AI suggestions are waiting on reviewer action.',
141
+ inspectRoute: firstOpenCommentDocument || firstProposedSuggestionDocument
142
+ ? `agent_documents show documentId:"${firstOpenCommentDocument?.id ?? firstProposedSuggestionDocument?.id}" includeVersions:true`
143
+ : 'agent_documents list',
144
+ repairRoute: firstOpenCommentDocument
145
+ ? `agent_documents resolveComment documentId:"${firstOpenCommentDocument.id}" commentId:"..." confirm:true explicitUserRequest:"..."`
146
+ : firstProposedSuggestionDocument
147
+ ? `agent_documents acceptSuggestion documentId:"${firstProposedSuggestionDocument.id}" suggestionId:"..." confirm:true explicitUserRequest:"..." or agent_documents rejectSuggestion documentId:"${firstProposedSuggestionDocument.id}" suggestionId:"..." confirm:true explicitUserRequest:"..."`
148
+ : undefined,
149
+ },
150
+ {
151
+ id: 'source-artifacts',
152
+ label: 'Source artifacts',
153
+ status: missingSourceArtifacts > 0 ? 'attention' : 'pass',
154
+ count: missingSourceArtifacts,
155
+ detail: missingSourceArtifacts > 0
156
+ ? `${documentsMissingSourceArtifacts} document draft(s) and ${comparisonSourceMissing} comparison artifact(s) have no attached source artifact or document source marker.`
157
+ : 'Document drafts and saved comparisons have source artifacts or explicit document/source markers where the current stores can verify them.',
158
+ inspectRoute: 'agent_harness mode:"document_ops_lane" laneId:"artifact_browser"',
159
+ repairRoute: missingSourceArtifacts > 0
160
+ ? 'agent_documents attachArtifact documentId:"..." artifactId:"..." confirm:true explicitUserRequest:"..." or agent_model_compare handoff artifactId:"..." relatedArtifactIds:"..." confirm:true explicitUserRequest:"..."'
161
+ : undefined,
162
+ },
163
+ {
164
+ id: 'comparison-reveal',
165
+ label: 'Blind comparison reveal',
166
+ status: unrevealedComparisons.length + hiddenJudgments.length > 0 ? 'attention' : 'pass',
167
+ count: unrevealedComparisons.length + hiddenJudgments.length,
168
+ detail: unrevealedComparisons.length + hiddenJudgments.length > 0
169
+ ? `${unrevealedComparisons.length} saved comparison(s) and ${hiddenJudgments.length} judgment(s) still hide winner identity before reviewer handoff.`
170
+ : 'Saved comparison judgments with route-change implications are revealed, or no hidden comparison artifacts are pending.',
171
+ inspectRoute: firstUnrevealedComparison
172
+ ? `agent_model_compare review artifactId:"${firstUnrevealedComparison.id}"`
173
+ : firstHiddenJudgment
174
+ ? `agent_model_compare review artifactId:"${firstHiddenJudgment.id}"`
175
+ : 'agent_model_compare review',
176
+ repairRoute: firstUnrevealedComparison
177
+ ? `agent_model_compare reveal artifactId:"${firstUnrevealedComparison.id}"`
178
+ : firstHiddenJudgment
179
+ ? `agent_model_compare judge artifactId:"${readMetadataString(firstHiddenJudgment.metadata.sourceArtifactId) || firstHiddenJudgment.id}" winnerBlindId:"${readMetadataString(firstHiddenJudgment.metadata.winnerBlindId) || '...'}" reveal:true confirm:true explicitUserRequest:"..."`
180
+ : undefined,
181
+ },
182
+ {
183
+ id: 'route-change-decision',
184
+ label: 'Route-change decision',
185
+ status: revealedJudgmentsWaitingOnRouteDecision.length > 0 ? 'attention' : 'pass',
186
+ count: revealedJudgmentsWaitingOnRouteDecision.length,
187
+ detail: revealedJudgmentsWaitingOnRouteDecision.length > 0
188
+ ? `${revealedJudgmentsWaitingOnRouteDecision.length} revealed judgment(s) can drive a confirmed model route update, or the user can explicitly leave routing unchanged before archiving.`
189
+ : 'No revealed model comparison judgment is waiting on an apply-or-leave-unchanged decision.',
190
+ inspectRoute: firstRevealedJudgment
191
+ ? `agent_model_compare review artifactId:"${firstRevealedJudgment.id}" reveal:true`
192
+ : 'models action:"status"',
193
+ repairRoute: firstRevealedJudgment
194
+ ? `agent_model_compare apply artifactId:"${firstRevealedJudgment.id}" confirm:true explicitUserRequest:"..." or agent_model_compare routeDecision artifactId:"${firstRevealedJudgment.id}" decision:"left-unchanged" confirm:true explicitUserRequest:"..."`
195
+ : undefined,
196
+ },
197
+ {
198
+ id: 'handoff-archive-evidence',
199
+ label: 'Reviewer handoff evidence',
200
+ status: handoffsMissingRelatedArtifacts > 0 ? 'attention' : 'pass',
201
+ count: handoffsMissingRelatedArtifacts,
202
+ detail: handoffsMissingRelatedArtifacts > 0
203
+ ? `${handoffsMissingRelatedArtifacts} reviewer handoff artifact(s) have no related evidence artifact ids before archive.`
204
+ : 'Reviewer handoff artifacts include related evidence ids, or no reviewer handoff archive is pending.',
205
+ inspectRoute: firstHandoffMissingRelated
206
+ ? `agent_model_compare review artifactId:"${firstHandoffMissingRelated.id}"`
207
+ : 'agent_model_compare review',
208
+ repairRoute: firstHandoffMissingRelated
209
+ ? `agent_model_compare handoff artifactId:"${readMetadataString(firstHandoffMissingRelated.metadata.sourceArtifactId) || firstHandoffMissingRelated.id}" relatedArtifactIds:"..." confirm:true explicitUserRequest:"..."`
210
+ : undefined,
211
+ },
212
+ ];
213
+ const status = reviewerReadinessStatus(checks);
214
+ const firstAction = checks.find((check) => check.status === 'attention' || check.status === 'needs-setup');
215
+ return {
216
+ status,
217
+ next: firstAction
218
+ ? `${firstAction.label}: ${firstAction.detail}`
219
+ : 'Export or archive reviewer packets through the returned confirmed document, artifact, or comparison routes.',
220
+ summary: {
221
+ documents: documents.length,
222
+ openComments,
223
+ proposedSuggestions,
224
+ documentsMissingSourceArtifacts,
225
+ savedComparisons: comparisons.length,
226
+ unrevealedComparisons: unrevealedComparisons.length,
227
+ hiddenJudgments: hiddenJudgments.length,
228
+ revealedJudgments: revealedJudgmentsWaitingOnRouteDecision.length,
229
+ handoffsMissingRelatedArtifacts,
230
+ },
231
+ checks,
232
+ policy: 'Reviewer readiness is read-only. It flags unresolved review work and returns exact existing routes; document exports, comparison handoffs, archives, and model route updates still require explicit confirmation.',
233
+ };
234
+ }
@@ -0,0 +1,72 @@
1
+ import type { AgentWorkspaceReviewPacketWizard } from '../input/agent-workspace-types.ts';
2
+
3
+ export type DocumentOpsLaneId =
4
+ | 'documents'
5
+ | 'uploads'
6
+ | 'exports'
7
+ | 'reviewer_readiness'
8
+ | 'review_packet_timeline'
9
+ | 'review_packet_wizard'
10
+ | 'source_library'
11
+ | 'media_artifacts'
12
+ | 'artifact_browser'
13
+ | 'model_compare';
14
+
15
+ export type DocumentOpsStatus = 'ready' | 'attention' | 'partial' | 'needs-setup' | 'gap';
16
+ export type ReviewerReadinessStatus = 'ready' | 'attention' | 'needs-setup';
17
+ export type ReviewerReadinessCheckStatus = 'pass' | 'attention' | 'needs-setup';
18
+
19
+ export interface AgentHarnessDocumentOpsArgs {
20
+ readonly laneId?: unknown;
21
+ readonly target?: unknown;
22
+ readonly query?: unknown;
23
+ readonly includeParameters?: unknown;
24
+ }
25
+
26
+ export interface DocumentOpsLane {
27
+ readonly id: DocumentOpsLaneId;
28
+ readonly label: string;
29
+ readonly status: DocumentOpsStatus;
30
+ readonly outcome: string;
31
+ readonly current: string;
32
+ readonly next: string;
33
+ readonly userRoute: string;
34
+ readonly modelRoute: string;
35
+ readonly signals: readonly string[];
36
+ readonly actionIds: readonly string[];
37
+ readonly reviewerReadiness?: ReviewerReadinessChecklist;
38
+ readonly reviewPacketWizard?: AgentWorkspaceReviewPacketWizard;
39
+ }
40
+
41
+ export interface ReviewerReadinessChecklist {
42
+ readonly status: ReviewerReadinessStatus;
43
+ readonly next: string;
44
+ readonly summary: {
45
+ readonly documents: number;
46
+ readonly openComments: number;
47
+ readonly proposedSuggestions: number;
48
+ readonly documentsMissingSourceArtifacts: number;
49
+ readonly savedComparisons: number;
50
+ readonly unrevealedComparisons: number;
51
+ readonly hiddenJudgments: number;
52
+ readonly revealedJudgments: number;
53
+ readonly handoffsMissingRelatedArtifacts: number;
54
+ };
55
+ readonly checks: readonly ReviewerReadinessCheck[];
56
+ readonly policy: string;
57
+ }
58
+
59
+ export interface ReviewerReadinessCheck {
60
+ readonly id: string;
61
+ readonly label: string;
62
+ readonly status: ReviewerReadinessCheckStatus;
63
+ readonly count: number;
64
+ readonly detail: string;
65
+ readonly inspectRoute: string;
66
+ readonly repairRoute?: string;
67
+ }
68
+
69
+ export type DocumentOpsLaneResolution =
70
+ | { readonly status: 'found'; readonly lane: Record<string, unknown> }
71
+ | { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Record<string, unknown>[] }
72
+ | { readonly status: 'missing_lookup'; readonly usage: string };