@pellux/goodvibes-agent 1.1.6 → 1.2.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 (256) hide show
  1. package/CHANGELOG.md +176 -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 +132662 -91399
  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 +56 -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 +114 -45
  14. package/docs/voice-and-live-tts.md +3 -1
  15. package/package.json +3 -2
  16. package/release/release-notes.md +130 -5
  17. package/release/release-readiness.json +120 -90
  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 +296 -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-checkpoint.ts +140 -0
  42. package/src/agent/setup-wizard.ts +447 -0
  43. package/src/agent/skill-registry-types.ts +102 -0
  44. package/src/agent/skill-registry.ts +81 -107
  45. package/src/agent/vibe-confirmation-routes.ts +62 -0
  46. package/src/agent/vibe-file.ts +285 -0
  47. package/src/cli/agent-knowledge-command.ts +45 -2
  48. package/src/cli/help.ts +3 -0
  49. package/src/cli/profiles-command.ts +25 -11
  50. package/src/config/agent-settings-policy.ts +1 -18
  51. package/src/input/agent-workspace-activation.ts +53 -0
  52. package/src/input/agent-workspace-artifact-browser-editor.ts +494 -0
  53. package/src/input/agent-workspace-artifact-metadata.ts +17 -0
  54. package/src/input/agent-workspace-basic-command-editors.ts +5 -1
  55. package/src/input/agent-workspace-categories.ts +153 -155
  56. package/src/input/agent-workspace-channel-triage.ts +481 -0
  57. package/src/input/agent-workspace-channels.ts +263 -0
  58. package/src/input/agent-workspace-command-editor.ts +152 -0
  59. package/src/input/agent-workspace-context-snapshot.ts +349 -0
  60. package/src/input/agent-workspace-delegation-editor-submission.ts +8 -0
  61. package/src/input/agent-workspace-document-editor.ts +502 -0
  62. package/src/input/agent-workspace-document-ops-editor.ts +523 -0
  63. package/src/input/agent-workspace-host-category.ts +1 -1
  64. package/src/input/agent-workspace-local-library-snapshot.ts +167 -0
  65. package/src/input/agent-workspace-model-compare-editor.ts +376 -0
  66. package/src/input/agent-workspace-model-compare-prompt-submission.ts +552 -0
  67. package/src/input/agent-workspace-model-compare-types.ts +82 -0
  68. package/src/input/agent-workspace-model-compare-utils.ts +35 -0
  69. package/src/input/agent-workspace-onboarding-categories.ts +141 -0
  70. package/src/input/agent-workspace-operations-command-editor-submission.ts +53 -0
  71. package/src/input/agent-workspace-operations-command-editors.ts +22 -0
  72. package/src/input/agent-workspace-research-report-editor.ts +212 -0
  73. package/src/input/agent-workspace-research-run-editor.ts +144 -0
  74. package/src/input/agent-workspace-research-source-editor.ts +167 -0
  75. package/src/input/agent-workspace-review-packet-snapshot.ts +593 -0
  76. package/src/input/agent-workspace-review-packet-utils.ts +265 -0
  77. package/src/input/agent-workspace-settings.ts +262 -30
  78. package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
  79. package/src/input/agent-workspace-setup-snapshot.ts +198 -0
  80. package/src/input/agent-workspace-setup.ts +34 -1
  81. package/src/input/agent-workspace-snapshot.ts +162 -147
  82. package/src/input/agent-workspace-types.ts +287 -2
  83. package/src/input/agent-workspace.ts +18 -0
  84. package/src/input/command-registry.ts +9 -2
  85. package/src/input/commands/agent-runtime-profile-runtime.ts +28 -13
  86. package/src/input/commands/channels-runtime.ts +102 -114
  87. package/src/input/commands/delegation-runtime.ts +87 -24
  88. package/src/input/commands/knowledge-browser-flags.ts +12 -0
  89. package/src/input/commands/knowledge.ts +28 -14
  90. package/src/input/commands/operator-actions-runtime.ts +6 -3
  91. package/src/input/commands/schedule-runtime.ts +71 -14
  92. package/src/input/commands/session-content.ts +2 -2
  93. package/src/input/commands/shell-core.ts +2 -0
  94. package/src/input/commands/vibe-runtime.ts +140 -0
  95. package/src/input/commands.ts +2 -0
  96. package/src/input/connected-host-routes.ts +123 -0
  97. package/src/input/settings-modal-types.ts +14 -0
  98. package/src/main.ts +15 -15
  99. package/src/panels/qr-panel.ts +2 -2
  100. package/src/renderer/agent-workspace-context-lines.ts +686 -0
  101. package/src/renderer/agent-workspace.ts +30 -419
  102. package/src/renderer/settings-modal-helpers.ts +12 -0
  103. package/src/renderer/settings-modal.ts +12 -0
  104. package/src/runtime/bootstrap-command-context.ts +11 -1
  105. package/src/runtime/bootstrap-command-parts.ts +14 -3
  106. package/src/runtime/bootstrap-core.ts +43 -1
  107. package/src/runtime/bootstrap-shell.ts +6 -0
  108. package/src/runtime/bootstrap.ts +113 -30
  109. package/src/runtime/connected-host-auth.ts +3 -2
  110. package/src/runtime/execution-ledger.ts +231 -0
  111. package/src/runtime/services.ts +88 -10
  112. package/src/runtime/tool-permission-safety.ts +287 -0
  113. package/src/shell/session-continuity-hints.ts +39 -0
  114. package/src/tools/agent-artifacts-tool.ts +606 -0
  115. package/src/tools/agent-audit-tool.ts +155 -0
  116. package/src/tools/agent-autonomy-schedule-tool.ts +238 -0
  117. package/src/tools/agent-autonomy-tool.ts +140 -0
  118. package/src/tools/agent-channel-send-tool.ts +24 -2
  119. package/src/tools/agent-channels-tool.ts +140 -0
  120. package/src/tools/agent-computer-tool.ts +168 -0
  121. package/src/tools/agent-context-policy.ts +267 -10
  122. package/src/tools/agent-context-tool.ts +143 -0
  123. package/src/tools/agent-delegation-tool.ts +128 -0
  124. package/src/tools/agent-device-tool.ts +240 -0
  125. package/src/tools/agent-documents-tool.ts +684 -0
  126. package/src/tools/agent-execution-tool.ts +230 -0
  127. package/src/tools/agent-harness-agent-orchestration.ts +703 -0
  128. package/src/tools/agent-harness-autonomy-intake.ts +785 -0
  129. package/src/tools/agent-harness-autonomy-live-records.ts +588 -0
  130. package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
  131. package/src/tools/agent-harness-autonomy-queue.ts +560 -0
  132. package/src/tools/agent-harness-background-processes-types.ts +35 -0
  133. package/src/tools/agent-harness-background-processes.ts +794 -0
  134. package/src/tools/agent-harness-browser-cockpit-route.ts +219 -0
  135. package/src/tools/agent-harness-browser-control.ts +338 -0
  136. package/src/tools/agent-harness-channel-metadata.ts +168 -10
  137. package/src/tools/agent-harness-cli-command-policy.ts +110 -0
  138. package/src/tools/agent-harness-command-catalog.ts +4 -2
  139. package/src/tools/agent-harness-command-runner.ts +42 -0
  140. package/src/tools/agent-harness-connected-host-status.ts +7 -4
  141. package/src/tools/agent-harness-delegation-posture.ts +234 -19
  142. package/src/tools/agent-harness-document-ops-reviewer-readiness.ts +234 -0
  143. package/src/tools/agent-harness-document-ops-types.ts +72 -0
  144. package/src/tools/agent-harness-document-ops.ts +671 -0
  145. package/src/tools/agent-harness-execution-history.ts +489 -0
  146. package/src/tools/agent-harness-execution-posture.ts +382 -0
  147. package/src/tools/agent-harness-file-recovery.ts +135 -0
  148. package/src/tools/agent-harness-keybinding-metadata.ts +16 -12
  149. package/src/tools/agent-harness-learning-curator-common.ts +102 -0
  150. package/src/tools/agent-harness-learning-curator-consolidation.ts +295 -0
  151. package/src/tools/agent-harness-learning-curator-proposals.ts +606 -0
  152. package/src/tools/agent-harness-learning-curator-types.ts +151 -0
  153. package/src/tools/agent-harness-learning-curator.ts +417 -0
  154. package/src/tools/agent-harness-local-model-benchmarks.ts +199 -0
  155. package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
  156. package/src/tools/agent-harness-local-model-endpoints.ts +673 -0
  157. package/src/tools/agent-harness-media-posture.ts +208 -2
  158. package/src/tools/agent-harness-memory-posture.ts +556 -0
  159. package/src/tools/agent-harness-metadata.ts +100 -168
  160. package/src/tools/agent-harness-mode-catalog.ts +84 -33
  161. package/src/tools/agent-harness-model-catalog.ts +162 -0
  162. package/src/tools/agent-harness-model-provider-health.ts +186 -0
  163. package/src/tools/agent-harness-model-readiness.ts +406 -0
  164. package/src/tools/agent-harness-model-routing-types.ts +266 -0
  165. package/src/tools/agent-harness-model-routing-utils.ts +30 -0
  166. package/src/tools/agent-harness-model-routing.ts +137 -190
  167. package/src/tools/agent-harness-operator-methods.ts +115 -133
  168. package/src/tools/agent-harness-pairing-posture.ts +431 -18
  169. package/src/tools/agent-harness-personal-ops-discovery.ts +533 -0
  170. package/src/tools/agent-harness-personal-ops-intake.ts +385 -0
  171. package/src/tools/agent-harness-personal-ops-lanes.ts +204 -0
  172. package/src/tools/agent-harness-personal-ops-records.ts +732 -0
  173. package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
  174. package/src/tools/agent-harness-personal-ops-types.ts +222 -0
  175. package/src/tools/agent-harness-personal-ops.ts +606 -0
  176. package/src/tools/agent-harness-project-context.ts +144 -0
  177. package/src/tools/agent-harness-prompt-context.ts +589 -0
  178. package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
  179. package/src/tools/agent-harness-release-evidence.ts +9 -7
  180. package/src/tools/agent-harness-release-readiness.ts +9 -7
  181. package/src/tools/agent-harness-research-briefing.ts +427 -0
  182. package/src/tools/agent-harness-research-queue.ts +250 -0
  183. package/src/tools/agent-harness-research-runs.ts +295 -0
  184. package/src/tools/agent-harness-research-workflow.ts +322 -0
  185. package/src/tools/agent-harness-security-posture.ts +2 -2
  186. package/src/tools/agent-harness-service-posture.ts +9 -9
  187. package/src/tools/agent-harness-session-metadata.ts +3 -3
  188. package/src/tools/agent-harness-setup-connected-host.ts +501 -0
  189. package/src/tools/agent-harness-setup-describe.ts +557 -0
  190. package/src/tools/agent-harness-setup-handoffs.ts +502 -0
  191. package/src/tools/agent-harness-setup-model-helpers.ts +101 -0
  192. package/src/tools/agent-harness-setup-plan.ts +253 -0
  193. package/src/tools/agent-harness-setup-posture-types.ts +218 -0
  194. package/src/tools/agent-harness-setup-posture-utils.ts +173 -0
  195. package/src/tools/agent-harness-setup-posture.ts +449 -221
  196. package/src/tools/agent-harness-setup-smoke.ts +508 -0
  197. package/src/tools/agent-harness-sudo-posture.ts +114 -0
  198. package/src/tools/agent-harness-tool-schema.ts +120 -7
  199. package/src/tools/agent-harness-tool-types.ts +78 -0
  200. package/src/tools/agent-harness-tool-utils.ts +43 -0
  201. package/src/tools/agent-harness-tool.ts +311 -370
  202. package/src/tools/agent-harness-ui-surface-metadata.ts +76 -69
  203. package/src/tools/agent-harness-ui-surface-types.ts +46 -0
  204. package/src/tools/agent-harness-vibe-health.ts +105 -0
  205. package/src/tools/agent-harness-workspace-action-runner.ts +149 -0
  206. package/src/tools/agent-harness-workspace-actions.ts +114 -8
  207. package/src/tools/agent-harness-workspace-editor-execution.ts +214 -0
  208. package/src/tools/agent-harness-workspace-editor-runner.ts +755 -0
  209. package/src/tools/agent-host-tool.ts +159 -0
  210. package/src/tools/agent-knowledge-ingest-tool.ts +34 -1
  211. package/src/tools/agent-learning-consolidation-core.ts +327 -0
  212. package/src/tools/agent-learning-consolidation-tool.ts +758 -0
  213. package/src/tools/agent-memory-tool.ts +214 -0
  214. package/src/tools/agent-model-compare-export.ts +315 -0
  215. package/src/tools/agent-model-compare-handoff.ts +592 -0
  216. package/src/tools/agent-model-compare-judgment.ts +633 -0
  217. package/src/tools/agent-model-compare-run.ts +722 -0
  218. package/src/tools/agent-model-compare-tool.ts +698 -0
  219. package/src/tools/agent-model-compare-types.ts +191 -0
  220. package/src/tools/agent-model-compare-utils.ts +93 -0
  221. package/src/tools/agent-models-tool.ts +208 -0
  222. package/src/tools/agent-operator-action-tool.ts +1 -1
  223. package/src/tools/agent-operator-method-tool.ts +643 -0
  224. package/src/tools/agent-personal-ops-tool.ts +197 -0
  225. package/src/tools/agent-policy-explanation.ts +415 -0
  226. package/src/tools/agent-research-report-tool.ts +608 -0
  227. package/src/tools/agent-research-runs-tool.ts +434 -0
  228. package/src/tools/agent-research-sources-tool.ts +443 -0
  229. package/src/tools/agent-research-tool.ts +687 -0
  230. package/src/tools/agent-review-packet-presets-core.ts +757 -0
  231. package/src/tools/agent-review-packet-presets-tool.ts +466 -0
  232. package/src/tools/agent-review-packet-share-tool.ts +305 -0
  233. package/src/tools/agent-route-planner-candidates-setup.ts +411 -0
  234. package/src/tools/agent-route-planner-candidates-surfaces.ts +432 -0
  235. package/src/tools/agent-route-planner-candidates-work.ts +251 -0
  236. package/src/tools/agent-route-planner-helpers.ts +667 -0
  237. package/src/tools/agent-route-planner.ts +185 -0
  238. package/src/tools/agent-route-tool.ts +105 -0
  239. package/src/tools/agent-schedule-edit-tool.ts +210 -0
  240. package/src/tools/agent-schedule-tool.ts +282 -0
  241. package/src/tools/agent-security-tool.ts +145 -0
  242. package/src/tools/agent-sessions-tool.ts +122 -0
  243. package/src/tools/agent-settings-import-tool.ts +104 -0
  244. package/src/tools/agent-settings-tool.ts +176 -0
  245. package/src/tools/agent-setup-tool.ts +226 -0
  246. package/src/tools/agent-support-tool.ts +122 -0
  247. package/src/tools/agent-terminal-process-tools.ts +167 -0
  248. package/src/tools/agent-tool-policy-guard-types.ts +77 -0
  249. package/src/tools/agent-tool-policy-guard.ts +110 -89
  250. package/src/tools/agent-vibe-tool.ts +297 -0
  251. package/src/tools/agent-work-plan-tool.ts +265 -6
  252. package/src/tools/agent-workspace-tool.ts +305 -0
  253. package/src/tools/artifact-archive.ts +169 -0
  254. package/src/tools/tool-definition-compaction.ts +36 -0
  255. package/src/tools/tool-execution-safety.ts +41 -0
  256. package/src/version.ts +1 -1
@@ -0,0 +1,593 @@
1
+ import type { ArtifactDescriptor } from '@pellux/goodvibes-sdk/platform/artifacts';
2
+ import type { CommandContext } from './command-registry.ts';
3
+ import { AgentDocumentRegistry, type AgentDocumentRecord } from '../agent/document-registry.ts';
4
+ import type { AgentWorkspaceReviewPacketDefaults, AgentWorkspaceReviewPacketTimeline, AgentWorkspaceReviewPacketTimelineEvent, AgentWorkspaceReviewPacketWizard, AgentWorkspaceReviewPacketWizardStep, AgentWorkspaceReviewPacketWizardStepStatus, AgentWorkspaceReviewerReadinessBadge } from './agent-workspace-types.ts';
5
+ import { readArtifactMetadataNumber, readArtifactMetadataString, readArtifactMetadataStringList } from './agent-workspace-artifact-metadata.ts';
6
+ import { buildReviewPacketPresetLineage, compactTimelineText, isDocumentExportArtifact, isModelCompareArtifact, isModelCompareExportArtifact, isModelCompareJudgmentArtifact, isModelCompareRouteDecisionArtifact, isReviewerHandoffArchiveArtifact, isReviewerHandoffArtifact, isReviewPacketPresetArtifact, isoToEpoch, reviewPacketPresetFreshness, uniqueStrings } from './agent-workspace-review-packet-utils.ts';
7
+
8
+ export function readDocumentDrafts(context: CommandContext): readonly AgentDocumentRecord[] {
9
+ try {
10
+ const shellPaths = context.workspace?.shellPaths;
11
+ if (!shellPaths) return [];
12
+ return AgentDocumentRegistry.fromShellPaths(shellPaths).list();
13
+ } catch {
14
+ return [];
15
+ }
16
+ }
17
+
18
+ export function buildReviewPacketTimeline(
19
+ documents: readonly AgentDocumentRecord[],
20
+ artifacts: readonly ArtifactDescriptor[],
21
+ artifactListAvailable: boolean,
22
+ ): AgentWorkspaceReviewPacketTimeline {
23
+ const events: AgentWorkspaceReviewPacketTimelineEvent[] = [];
24
+ for (const document of documents) {
25
+ const openComments = document.comments.filter((comment) => comment.status === 'open').length;
26
+ const proposedSuggestions = document.suggestions.filter((suggestion) => suggestion.status === 'proposed').length;
27
+ events.push({
28
+ id: `document:${document.id}`,
29
+ kind: 'document',
30
+ at: isoToEpoch(document.updatedAt),
31
+ label: `Document ${document.status}: ${document.title}`,
32
+ detail: `${document.versions.length} version(s), ${openComments} open comment(s), ${proposedSuggestions} proposed suggestion(s), ${document.attachments.length} attachment(s), last artifact ${document.lastArtifactId ?? 'none'}`,
33
+ status: openComments + proposedSuggestions > 0 ? 'attention' : document.status === 'reviewed' ? 'ready' : 'info',
34
+ route: `agent_documents show documentId:"${document.id}" includeVersions:true`,
35
+ sourceId: document.id,
36
+ });
37
+ for (const comment of document.comments) {
38
+ events.push({
39
+ id: `comment:${document.id}:${comment.id}`,
40
+ kind: 'comment',
41
+ at: isoToEpoch(comment.resolvedAt ?? comment.updatedAt ?? comment.createdAt),
42
+ label: `${comment.status === 'open' ? 'Open' : 'Resolved'} comment: ${document.title}`,
43
+ detail: compactTimelineText(comment.body),
44
+ status: comment.status === 'open' ? 'attention' : 'complete',
45
+ route: comment.status === 'open'
46
+ ? `agent_documents resolveComment documentId:"${document.id}" commentId:"${comment.id}" confirm:true explicitUserRequest:"..."`
47
+ : `agent_documents show documentId:"${document.id}" includeVersions:true`,
48
+ sourceId: comment.id,
49
+ });
50
+ }
51
+ for (const suggestion of document.suggestions) {
52
+ events.push({
53
+ id: `suggestion:${document.id}:${suggestion.id}`,
54
+ kind: 'suggestion',
55
+ at: isoToEpoch(suggestion.resolvedAt ?? suggestion.updatedAt ?? suggestion.createdAt),
56
+ label: `${suggestion.status === 'proposed' ? 'Proposed' : suggestion.status === 'accepted' ? 'Accepted' : 'Rejected'} suggestion: ${suggestion.title}`,
57
+ detail: compactTimelineText(`${document.title}: ${suggestion.summary}`),
58
+ status: suggestion.status === 'proposed' ? 'attention' : 'complete',
59
+ route: suggestion.status === 'proposed'
60
+ ? `agent_documents acceptSuggestion documentId:"${document.id}" suggestionId:"${suggestion.id}" confirm:true explicitUserRequest:"..." or agent_documents rejectSuggestion documentId:"${document.id}" suggestionId:"${suggestion.id}" confirm:true explicitUserRequest:"..."`
61
+ : `agent_documents show documentId:"${document.id}" includeVersions:true`,
62
+ sourceId: suggestion.id,
63
+ });
64
+ }
65
+ for (const attachment of document.attachments) {
66
+ events.push({
67
+ id: `attachment:${document.id}:${attachment.id}`,
68
+ kind: 'attachment',
69
+ at: isoToEpoch(attachment.updatedAt ?? attachment.createdAt),
70
+ label: `Attached artifact: ${attachment.label}`,
71
+ detail: `${document.title}: ${attachment.artifactId}${attachment.note ? ` - ${compactTimelineText(attachment.note, 64)}` : ''}`,
72
+ status: 'complete',
73
+ route: `agent_artifacts show artifactId:"${attachment.artifactId}"`,
74
+ sourceId: attachment.artifactId,
75
+ });
76
+ }
77
+ }
78
+
79
+ for (const artifact of artifacts) {
80
+ const metadata = artifact.metadata;
81
+ if (isDocumentExportArtifact(artifact)) {
82
+ const documentId = readArtifactMetadataString(metadata, 'documentId') || 'unknown-document';
83
+ const versionId = readArtifactMetadataString(metadata, 'versionId') || 'unknown-version';
84
+ events.push({
85
+ id: `document-export:${artifact.id}`,
86
+ kind: 'document-export',
87
+ at: artifact.createdAt,
88
+ label: `Document export: ${documentId}`,
89
+ detail: `version ${versionId}; artifact ${artifact.id}`,
90
+ status: 'complete',
91
+ route: `agent_artifacts show artifactId:"${artifact.id}"`,
92
+ sourceId: artifact.id,
93
+ });
94
+ continue;
95
+ }
96
+ if (isModelCompareArtifact(artifact)) {
97
+ const comparisonId = readArtifactMetadataString(metadata, 'comparisonId') || artifact.id;
98
+ const completed = readArtifactMetadataNumber(metadata, 'completedCandidates');
99
+ const candidates = readArtifactMetadataNumber(metadata, 'candidateCount');
100
+ const source = readArtifactMetadataString(metadata, 'sourceArtifactId') || readArtifactMetadataString(metadata, 'documentId') || 'none';
101
+ const revealed = metadata.revealIncludedInTranscript === true;
102
+ events.push({
103
+ id: `compare:${artifact.id}`,
104
+ kind: 'compare',
105
+ at: artifact.createdAt,
106
+ label: `Blind compare ${revealed ? 'revealed' : 'hidden'}: ${comparisonId}`,
107
+ detail: `${completed ?? '?'}/${candidates ?? '?'} candidate(s) complete; source ${source}`,
108
+ status: revealed ? 'ready' : 'attention',
109
+ route: revealed ? `agent_model_compare review artifactId:"${artifact.id}" reveal:true` : `agent_model_compare reveal artifactId:"${artifact.id}"`,
110
+ sourceId: artifact.id,
111
+ });
112
+ continue;
113
+ }
114
+ if (isModelCompareJudgmentArtifact(artifact)) {
115
+ const comparisonId = readArtifactMetadataString(metadata, 'comparisonId') || 'unknown-comparison';
116
+ const winnerBlindId = readArtifactMetadataString(metadata, 'winnerBlindId') || 'unknown-winner';
117
+ const winnerModel = readArtifactMetadataString(metadata, 'winnerModel');
118
+ const revealed = metadata.revealIncludedInJudgment === true && winnerModel.length > 0;
119
+ events.push({
120
+ id: `judgment:${artifact.id}`,
121
+ kind: 'judgment',
122
+ at: artifact.createdAt,
123
+ label: `Comparison judgment ${revealed ? 'revealed' : 'hidden'}: ${comparisonId}`,
124
+ detail: revealed ? `winner ${winnerModel}; blind slot ${winnerBlindId}` : `winner blind slot ${winnerBlindId}; reveal needed before route decisions`,
125
+ status: 'attention',
126
+ route: revealed
127
+ ? `agent_model_compare apply artifactId:"${artifact.id}" confirm:true explicitUserRequest:"..."`
128
+ : `agent_model_compare review artifactId:"${artifact.id}"`,
129
+ sourceId: artifact.id,
130
+ });
131
+ continue;
132
+ }
133
+ if (isModelCompareRouteDecisionArtifact(artifact)) {
134
+ const comparisonId = readArtifactMetadataString(metadata, 'comparisonId') || 'unknown-comparison';
135
+ const decisionId = readArtifactMetadataString(metadata, 'decisionId') || artifact.id;
136
+ const decision = readArtifactMetadataString(metadata, 'decision') || 'unknown-decision';
137
+ const judgmentArtifactId = readArtifactMetadataString(metadata, 'judgmentArtifactId') || 'unknown-judgment';
138
+ const selectedModel = readArtifactMetadataString(metadata, 'selectedModel') || readArtifactMetadataString(metadata, 'currentModel') || 'unknown-model';
139
+ events.push({
140
+ id: `route-decision:${artifact.id}`,
141
+ kind: 'route-decision',
142
+ at: artifact.createdAt,
143
+ label: `Route decision: ${decisionId}`,
144
+ detail: `${comparisonId}; ${decision}; judgment ${judgmentArtifactId}; selected ${selectedModel}`,
145
+ status: 'complete',
146
+ route: `agent_artifacts show artifactId:"${artifact.id}"`,
147
+ sourceId: artifact.id,
148
+ });
149
+ continue;
150
+ }
151
+ if (isReviewPacketPresetArtifact(artifact)) {
152
+ const name = readArtifactMetadataString(metadata, 'name') || artifact.filename || artifact.id;
153
+ const summary = readArtifactMetadataString(metadata, 'summary') || 'review packet preset';
154
+ const freshness = reviewPacketPresetFreshness(artifact, artifacts);
155
+ const sourceArtifactId = readArtifactMetadataString(metadata, 'revealedJudgmentArtifactId')
156
+ || readArtifactMetadataString(metadata, 'judgmentArtifactId')
157
+ || readArtifactMetadataString(metadata, 'comparisonArtifactId')
158
+ || readArtifactMetadataString(metadata, 'documentExportArtifactId')
159
+ || 'none';
160
+ const handoffArtifactId = readArtifactMetadataString(metadata, 'handoffArtifactId') || 'none';
161
+ const relatedCount = readArtifactMetadataStringList(metadata, 'relatedArtifactIds').length;
162
+ events.push({
163
+ id: `packet-preset:${artifact.id}`,
164
+ kind: 'packet-preset',
165
+ at: artifact.createdAt,
166
+ label: `Packet preset: ${name}`,
167
+ detail: `${compactTimelineText(summary, 72)}; source ${sourceArtifactId}; handoff ${handoffArtifactId}; ${relatedCount} related artifact(s); ${freshness.summary}`,
168
+ status: freshness.status,
169
+ route: `agent_review_packet_presets show artifactId:"${artifact.id}"`,
170
+ sourceId: artifact.id,
171
+ });
172
+ continue;
173
+ }
174
+ if (isModelCompareExportArtifact(artifact)) {
175
+ const comparisonId = readArtifactMetadataString(metadata, 'comparisonId') || 'unknown-comparison';
176
+ const sourceKind = readArtifactMetadataString(metadata, 'sourceKind') || 'unknown';
177
+ events.push({
178
+ id: `compare-export:${artifact.id}`,
179
+ kind: 'compare-export',
180
+ at: artifact.createdAt,
181
+ label: `Compare report: ${comparisonId}`,
182
+ detail: `${sourceKind} report artifact ${artifact.id}`,
183
+ status: 'complete',
184
+ route: `agent_artifacts show artifactId:"${artifact.id}"`,
185
+ sourceId: artifact.id,
186
+ });
187
+ continue;
188
+ }
189
+ if (isReviewerHandoffArtifact(artifact)) {
190
+ const handoffId = readArtifactMetadataString(metadata, 'handoffId') || artifact.id;
191
+ const comparisonId = readArtifactMetadataString(metadata, 'comparisonId') || 'unknown-comparison';
192
+ const sourceKind = readArtifactMetadataString(metadata, 'sourceKind') || 'unknown';
193
+ const relatedCount = readArtifactMetadataStringList(metadata, 'relatedArtifactIds').length;
194
+ events.push({
195
+ id: `handoff:${artifact.id}`,
196
+ kind: 'handoff',
197
+ at: artifact.createdAt,
198
+ label: `Reviewer handoff: ${handoffId}`,
199
+ detail: `${comparisonId}; source ${sourceKind}; ${relatedCount} related artifact(s)`,
200
+ status: relatedCount > 0 ? 'ready' : 'attention',
201
+ route: relatedCount > 0
202
+ ? `agent_model_compare handoffDiff leftArtifactId:"${artifact.id}" rightArtifactId:"..."`
203
+ : `agent_model_compare handoff artifactId:"${readArtifactMetadataString(metadata, 'sourceArtifactId') || artifact.id}" relatedArtifactIds:"..." confirm:true explicitUserRequest:"..."`,
204
+ sourceId: artifact.id,
205
+ });
206
+ continue;
207
+ }
208
+ if (isReviewerHandoffArchiveArtifact(artifact)) {
209
+ const archiveId = readArtifactMetadataString(metadata, 'archiveId') || artifact.id;
210
+ const comparisonId = readArtifactMetadataString(metadata, 'comparisonId') || 'unknown-comparison';
211
+ const artifactCount = readArtifactMetadataNumber(metadata, 'artifactCount');
212
+ events.push({
213
+ id: `handoff-archive:${artifact.id}`,
214
+ kind: 'handoff-archive',
215
+ at: artifact.createdAt,
216
+ label: `Handoff archive: ${archiveId}`,
217
+ detail: `${comparisonId}; ${artifactCount ?? '?'} artifact(s) packaged`,
218
+ status: 'complete',
219
+ route: `agent_artifacts show artifactId:"${artifact.id}"`,
220
+ sourceId: artifact.id,
221
+ });
222
+ }
223
+ }
224
+
225
+ const ordered = events
226
+ .filter((event) => Number.isFinite(event.at))
227
+ .sort((left, right) => right.at - left.at || left.id.localeCompare(right.id));
228
+ const attention = ordered.find((event) => event.status === 'attention');
229
+ return {
230
+ available: artifactListAvailable,
231
+ count: ordered.length,
232
+ next: !artifactListAvailable
233
+ ? 'Artifact listing is unavailable; timeline shows document-local events only.'
234
+ : attention
235
+ ? `${attention.label}: ${attention.detail}`
236
+ : ordered.length > 0
237
+ ? 'Review packet timeline is clear; export, handoff, archive, or route decisions can use the visible readiness checks.'
238
+ : 'Create or attach a document, source artifact, comparison, judgment, handoff, or archive to start a review packet timeline.',
239
+ items: ordered.slice(0, 8),
240
+ };
241
+ }
242
+
243
+ export function buildReviewPacketDefaults(
244
+ documents: readonly AgentDocumentRecord[],
245
+ artifacts: readonly ArtifactDescriptor[],
246
+ ): AgentWorkspaceReviewPacketDefaults {
247
+ const latestDocument = [...documents]
248
+ .filter((document) => document.status !== 'archived')
249
+ .sort((left, right) => isoToEpoch(right.updatedAt) - isoToEpoch(left.updatedAt) || left.id.localeCompare(right.id))[0] ?? null;
250
+ const latestDocumentExport = artifacts.filter(isDocumentExportArtifact).sort((left, right) => right.createdAt - left.createdAt)[0] ?? null;
251
+ const latestComparison = artifacts.filter(isModelCompareArtifact).sort((left, right) => right.createdAt - left.createdAt)[0] ?? null;
252
+ const latestJudgment = artifacts.filter(isModelCompareJudgmentArtifact).sort((left, right) => right.createdAt - left.createdAt)[0] ?? null;
253
+ const latestRevealedJudgment = artifacts
254
+ .filter((artifact) => (
255
+ isModelCompareJudgmentArtifact(artifact)
256
+ && artifact.metadata.revealIncludedInJudgment === true
257
+ && readArtifactMetadataString(artifact.metadata, 'winnerModel').length > 0
258
+ ))
259
+ .sort((left, right) => right.createdAt - left.createdAt)[0] ?? null;
260
+ const latestHandoff = artifacts.filter(isReviewerHandoffArtifact).sort((left, right) => right.createdAt - left.createdAt)[0] ?? null;
261
+ const latestHandoffArchive = artifacts.filter(isReviewerHandoffArchiveArtifact).sort((left, right) => right.createdAt - left.createdAt)[0] ?? null;
262
+ const latestPreset = artifacts.filter(isReviewPacketPresetArtifact).sort((left, right) => right.createdAt - left.createdAt)[0] ?? null;
263
+ const presetMetadata = latestPreset?.metadata ?? {};
264
+ const latestRevealedJudgmentComparisonId = readArtifactMetadataString(latestRevealedJudgment?.metadata ?? {}, 'comparisonId');
265
+ const latestRouteDecision = artifacts
266
+ .filter(isModelCompareRouteDecisionArtifact)
267
+ .filter((artifact) => {
268
+ if (!latestRevealedJudgment) return true;
269
+ const judgmentArtifactId = readArtifactMetadataString(artifact.metadata, 'judgmentArtifactId');
270
+ const comparisonId = readArtifactMetadataString(artifact.metadata, 'comparisonId');
271
+ return judgmentArtifactId === latestRevealedJudgment.id || (latestRevealedJudgmentComparisonId.length > 0 && comparisonId === latestRevealedJudgmentComparisonId);
272
+ })
273
+ .sort((left, right) => right.createdAt - left.createdAt)[0] ?? null;
274
+ const liveRelatedArtifactIds = uniqueStrings([
275
+ latestDocumentExport?.id,
276
+ latestDocument?.lastArtifactId,
277
+ ...(latestDocument?.attachments.map((attachment) => attachment.artifactId) ?? []),
278
+ readArtifactMetadataString(latestComparison?.metadata ?? {}, 'sourceArtifactId'),
279
+ ...readArtifactMetadataStringList(latestHandoff?.metadata ?? {}, 'relatedArtifactIds'),
280
+ ], 8);
281
+ const presetRelatedArtifactIds = readArtifactMetadataStringList(presetMetadata, 'relatedArtifactIds').slice(0, 8);
282
+ const relatedArtifactIds = liveRelatedArtifactIds.length > 0 ? liveRelatedArtifactIds : presetRelatedArtifactIds;
283
+ const documentId = (latestDocument?.id ?? readArtifactMetadataString(presetMetadata, 'documentId')) || null;
284
+ const documentTitle = (latestDocument?.title ?? readArtifactMetadataString(presetMetadata, 'documentTitle')) || null;
285
+ const documentExportArtifactId = (latestDocumentExport?.id ?? readArtifactMetadataString(presetMetadata, 'documentExportArtifactId')) || null;
286
+ const comparisonArtifactId = (latestComparison?.id ?? readArtifactMetadataString(presetMetadata, 'comparisonArtifactId')) || null;
287
+ const judgmentArtifactId = (latestJudgment?.id ?? readArtifactMetadataString(presetMetadata, 'judgmentArtifactId')) || null;
288
+ const revealedJudgmentArtifactId = (latestRevealedJudgment?.id ?? readArtifactMetadataString(presetMetadata, 'revealedJudgmentArtifactId')) || null;
289
+ const routeDecisionArtifactId = (latestRouteDecision?.id ?? readArtifactMetadataString(presetMetadata, 'routeDecisionArtifactId')) || null;
290
+ const routeDecision = latestRouteDecision
291
+ ? readArtifactMetadataString(latestRouteDecision.metadata, 'decision') || null
292
+ : readArtifactMetadataString(presetMetadata, 'routeDecision') || null;
293
+ const handoffArtifactId = (latestHandoff?.id ?? readArtifactMetadataString(presetMetadata, 'handoffArtifactId')) || null;
294
+ const handoffArchiveArtifactId = (latestHandoffArchive?.id ?? readArtifactMetadataString(presetMetadata, 'handoffArchiveArtifactId')) || null;
295
+ const sourceArtifactId = revealedJudgmentArtifactId ?? judgmentArtifactId ?? comparisonArtifactId ?? null;
296
+ const reviewPacketPresetName = readArtifactMetadataString(presetMetadata, 'name') || null;
297
+ const reviewPacketPresetLineage = buildReviewPacketPresetLineage(latestPreset);
298
+ const summary = [
299
+ documentId ? `document ${documentId}` : '',
300
+ sourceArtifactId ? `source ${sourceArtifactId}` : '',
301
+ handoffArtifactId ? `handoff ${handoffArtifactId}` : '',
302
+ relatedArtifactIds.length > 0 ? `${relatedArtifactIds.length} related` : '',
303
+ latestPreset ? `preset ${latestPreset.id}` : '',
304
+ ].filter(Boolean).join('; ') || 'no packet defaults yet';
305
+ return {
306
+ documentId,
307
+ documentTitle,
308
+ documentExportArtifactId,
309
+ comparisonArtifactId,
310
+ judgmentArtifactId,
311
+ revealedJudgmentArtifactId,
312
+ routeDecisionArtifactId,
313
+ routeDecision,
314
+ handoffArtifactId,
315
+ handoffArchiveArtifactId,
316
+ reviewPacketPresetArtifactId: latestPreset?.id ?? null,
317
+ reviewPacketPresetName,
318
+ reviewPacketPresetLineage,
319
+ relatedArtifactIds,
320
+ summary,
321
+ };
322
+ }
323
+
324
+ function packetStepStatus(done: boolean, priorDone: boolean): AgentWorkspaceReviewPacketWizardStepStatus {
325
+ if (done) return 'done';
326
+ return priorDone ? 'current' : 'blocked';
327
+ }
328
+
329
+ function buildReviewPacketWizardStep(input: {
330
+ readonly id: string;
331
+ readonly label: string;
332
+ readonly status: AgentWorkspaceReviewPacketWizardStepStatus;
333
+ readonly detail: string;
334
+ readonly userRoute: string;
335
+ readonly modelRoute: string;
336
+ readonly actionId: string;
337
+ readonly backtrackRoute?: string | null;
338
+ }): AgentWorkspaceReviewPacketWizardStep {
339
+ return {
340
+ id: input.id,
341
+ label: input.label,
342
+ status: input.status,
343
+ detail: input.detail,
344
+ userRoute: input.userRoute,
345
+ modelRoute: input.modelRoute,
346
+ actionId: input.actionId,
347
+ backtrackRoute: input.backtrackRoute ?? (input.status === 'done' ? input.modelRoute : null),
348
+ };
349
+ }
350
+
351
+ export function buildReviewPacketWizard(
352
+ documents: readonly AgentDocumentRecord[],
353
+ artifactListAvailable: boolean,
354
+ defaults: AgentWorkspaceReviewPacketDefaults,
355
+ ): AgentWorkspaceReviewPacketWizard {
356
+ const latestDocument = [...documents]
357
+ .filter((document) => document.status !== 'archived')
358
+ .sort((left, right) => isoToEpoch(right.updatedAt) - isoToEpoch(left.updatedAt) || left.id.localeCompare(right.id))[0] ?? null;
359
+ const openComments = latestDocument?.comments.filter((comment) => comment.status === 'open').length ?? 0;
360
+ const proposedSuggestions = latestDocument?.suggestions.filter((suggestion) => suggestion.status === 'proposed').length ?? 0;
361
+ const documentReviewed = Boolean(latestDocument && openComments === 0 && proposedSuggestions === 0);
362
+ const documentExported = Boolean(defaults.documentExportArtifactId || latestDocument?.lastArtifactId);
363
+ const judgmentReady = Boolean(defaults.revealedJudgmentArtifactId);
364
+ const handoffReady = Boolean(defaults.handoffArtifactId && defaults.relatedArtifactIds.length > 0);
365
+ const archiveReady = Boolean(defaults.handoffArchiveArtifactId);
366
+ const routeDecisionDone = Boolean(defaults.routeDecisionArtifactId || archiveReady);
367
+ const presetLineage = defaults.reviewPacketPresetLineage;
368
+ const finalArchiveReview = archiveReady
369
+ ? [
370
+ `Inspect final archive ${defaults.handoffArchiveArtifactId}.`,
371
+ presetLineage?.refreshed
372
+ ? `Verify refreshed preset lineage: ${presetLineage.summary}.`
373
+ : presetLineage
374
+ ? `Preset lineage: ${presetLineage.summary}.`
375
+ : 'No saved packet preset lineage is attached.',
376
+ 'Use Documents & Compare -> Share review packet only after the user confirms the delivery target.',
377
+ ].join(' ')
378
+ : 'Final evidence review stays pending until a reviewer handoff ZIP archive exists.';
379
+ const documentRoute = latestDocument
380
+ ? `agent_documents show documentId:"${latestDocument.id}" includeVersions:true`
381
+ : 'agent_documents list';
382
+ const steps: AgentWorkspaceReviewPacketWizardStep[] = [];
383
+ steps.push(buildReviewPacketWizardStep({
384
+ id: 'draft-review',
385
+ label: 'Draft review',
386
+ status: documentReviewed ? 'done' : 'current',
387
+ detail: latestDocument
388
+ ? documentReviewed
389
+ ? `${latestDocument.title} has no open comments or proposed suggestions.`
390
+ : `${latestDocument.title} has ${openComments} open comment(s) and ${proposedSuggestions} proposed suggestion(s) to resolve before export.`
391
+ : 'Create or select a document draft before building a reviewer packet.',
392
+ userRoute: latestDocument ? 'Documents & Compare -> Show document draft' : 'Documents & Compare -> Create document draft',
393
+ modelRoute: latestDocument
394
+ ? documentRoute
395
+ : 'agent_harness mode:"workspace_action" actionId:"document-create-draft"',
396
+ actionId: latestDocument ? 'document-show-draft' : 'document-create-draft',
397
+ }));
398
+ steps.push(buildReviewPacketWizardStep({
399
+ id: 'document-export',
400
+ label: 'Document export',
401
+ status: packetStepStatus(documentExported, documentReviewed),
402
+ detail: documentExported
403
+ ? `Document evidence is exported as ${defaults.documentExportArtifactId ?? latestDocument?.lastArtifactId}.`
404
+ : documentReviewed
405
+ ? 'Export the reviewed draft as a saved artifact for comparison and handoff evidence.'
406
+ : 'Resolve draft review items before exporting reviewer evidence.',
407
+ userRoute: 'Documents & Compare -> Export document artifact',
408
+ modelRoute: latestDocument
409
+ ? `agent_documents export documentId:"${latestDocument.id}" confirm:true explicitUserRequest:"..."`
410
+ : 'agent_harness mode:"workspace_action" actionId:"document-export-draft"',
411
+ actionId: 'document-export-draft',
412
+ backtrackRoute: defaults.documentExportArtifactId
413
+ ? `agent_artifacts show artifactId:"${defaults.documentExportArtifactId}"`
414
+ : null,
415
+ }));
416
+ steps.push(buildReviewPacketWizardStep({
417
+ id: 'compare-judgment',
418
+ label: 'Compare judgment',
419
+ status: packetStepStatus(judgmentReady, documentExported),
420
+ detail: judgmentReady
421
+ ? `Revealed judgment ${defaults.revealedJudgmentArtifactId} is ready for route review.`
422
+ : defaults.judgmentArtifactId
423
+ ? `Judgment ${defaults.judgmentArtifactId} exists, but winner identity is not revealed for route decisions.`
424
+ : defaults.comparisonArtifactId
425
+ ? `Comparison ${defaults.comparisonArtifactId} is available; save or reveal a judgment before handoff.`
426
+ : documentExported
427
+ ? 'Run a blind comparison from the exported document evidence, then save a revealed judgment.'
428
+ : 'Export document evidence before comparing models.',
429
+ userRoute: defaults.comparisonArtifactId ? 'Documents & Compare -> Review saved compare' : 'Documents & Compare -> Run blind compare',
430
+ modelRoute: defaults.revealedJudgmentArtifactId
431
+ ? `agent_model_compare review artifactId:"${defaults.revealedJudgmentArtifactId}" reveal:true`
432
+ : defaults.judgmentArtifactId
433
+ ? `agent_model_compare review artifactId:"${defaults.judgmentArtifactId}"`
434
+ : defaults.comparisonArtifactId
435
+ ? `agent_model_compare review artifactId:"${defaults.comparisonArtifactId}"`
436
+ : 'agent_harness mode:"workspace_action" actionId:"document-run-compare"',
437
+ actionId: defaults.comparisonArtifactId ? 'document-review-compare' : 'document-run-compare',
438
+ }));
439
+ steps.push(buildReviewPacketWizardStep({
440
+ id: 'reviewer-handoff',
441
+ label: 'Reviewer handoff',
442
+ status: packetStepStatus(handoffReady, judgmentReady),
443
+ detail: handoffReady
444
+ ? `Reviewer handoff ${defaults.handoffArtifactId} includes ${defaults.relatedArtifactIds.length} related evidence artifact(s).`
445
+ : judgmentReady
446
+ ? 'Create a reviewer handoff from the revealed judgment and related document/export evidence.'
447
+ : 'Save a revealed comparison judgment before creating reviewer handoff evidence.',
448
+ userRoute: 'Documents & Compare -> Export compare report',
449
+ modelRoute: defaults.revealedJudgmentArtifactId
450
+ ? `agent_model_compare handoff artifactId:"${defaults.revealedJudgmentArtifactId}" relatedArtifactIds:${JSON.stringify(defaults.relatedArtifactIds)} confirm:true explicitUserRequest:"..."`
451
+ : 'agent_harness mode:"workspace_action" actionId:"document-export-compare"',
452
+ actionId: 'document-export-compare',
453
+ backtrackRoute: defaults.handoffArtifactId
454
+ ? `agent_artifacts show artifactId:"${defaults.handoffArtifactId}"`
455
+ : null,
456
+ }));
457
+ steps.push(buildReviewPacketWizardStep({
458
+ id: 'route-decision',
459
+ label: 'Route decision',
460
+ status: routeDecisionDone ? 'done' : handoffReady ? 'current' : judgmentReady ? 'pending' : 'blocked',
461
+ detail: routeDecisionDone
462
+ ? defaults.routeDecisionArtifactId
463
+ ? `Route decision ${defaults.routeDecisionArtifactId} records ${defaults.routeDecision ?? 'a saved decision'}.`
464
+ : 'Final packet evidence exists; route-decision review has been carried to archive/final review.'
465
+ : handoffReady
466
+ ? 'Apply the revealed winner or explicitly leave the current model unchanged before final archive review.'
467
+ : judgmentReady
468
+ ? 'Create reviewer handoff evidence before applying or leaving the revealed winning route.'
469
+ : 'A revealed judgment is required before deciding whether to update the selected model route.',
470
+ userRoute: 'Documents & Compare -> Apply compare winner or Record route decision',
471
+ modelRoute: defaults.revealedJudgmentArtifactId
472
+ ? `agent_model_compare apply artifactId:"${defaults.revealedJudgmentArtifactId}" confirm:true explicitUserRequest:"..." or agent_model_compare routeDecision artifactId:"${defaults.revealedJudgmentArtifactId}" decision:"left-unchanged" confirm:true explicitUserRequest:"..."`
473
+ : 'agent_harness mode:"workspace_action" actionId:"document-apply-compare"',
474
+ actionId: 'document-apply-compare',
475
+ backtrackRoute: defaults.routeDecisionArtifactId
476
+ ? `agent_artifacts show artifactId:"${defaults.routeDecisionArtifactId}"`
477
+ : null,
478
+ }));
479
+ steps.push(buildReviewPacketWizardStep({
480
+ id: 'final-archive-review',
481
+ label: 'Final archive review',
482
+ status: archiveReady ? 'done' : routeDecisionDone && handoffReady ? 'current' : handoffReady ? 'pending' : 'blocked',
483
+ detail: archiveReady
484
+ ? `Reviewer packet archive ${defaults.handoffArchiveArtifactId} is ready for final evidence review.`
485
+ : handoffReady
486
+ ? 'Archive the reviewer handoff as one ZIP artifact after route-decision review.'
487
+ : 'Create a reviewer handoff with related evidence before archiving the packet.',
488
+ userRoute: 'Documents & Compare -> Export compare report',
489
+ modelRoute: defaults.handoffArtifactId
490
+ ? `agent_model_compare handoffArchive artifactId:"${defaults.handoffArtifactId}" confirm:true explicitUserRequest:"..."`
491
+ : 'agent_harness mode:"workspace_action" actionId:"document-export-compare"',
492
+ actionId: 'document-export-compare',
493
+ backtrackRoute: defaults.handoffArchiveArtifactId
494
+ ? `agent_artifacts show artifactId:"${defaults.handoffArchiveArtifactId}"`
495
+ : null,
496
+ }));
497
+ const completedSteps = steps.filter((step) => step.status === 'done').length;
498
+ const current = steps.find((step) => step.status === 'current') ?? steps.find((step) => step.status === 'pending') ?? null;
499
+ const status = !artifactListAvailable && steps.length > 0
500
+ ? 'blocked'
501
+ : completedSteps === steps.length
502
+ ? 'complete'
503
+ : latestDocument
504
+ ? 'active'
505
+ : 'empty';
506
+ return {
507
+ available: artifactListAvailable,
508
+ status,
509
+ completedSteps,
510
+ totalSteps: steps.length,
511
+ currentStepId: current?.id ?? null,
512
+ currentStepLabel: current?.label ?? null,
513
+ next: current
514
+ ? `${current.label}: ${current.detail}`
515
+ : 'Review packet wizard is complete; inspect the final archive and saved packet evidence.',
516
+ finalReview: finalArchiveReview,
517
+ presetLineage,
518
+ steps,
519
+ };
520
+ }
521
+
522
+ export function reviewerReadinessBadge(
523
+ documents: readonly AgentDocumentRecord[],
524
+ artifacts: readonly ArtifactDescriptor[],
525
+ artifactListAvailable: boolean,
526
+ ): AgentWorkspaceReviewerReadinessBadge {
527
+ const reviewDocuments = documents.filter((document) => document.status !== 'archived');
528
+ const openComments = reviewDocuments.reduce((total, document) => total + document.comments.filter((comment) => comment.status === 'open').length, 0);
529
+ const proposedSuggestions = reviewDocuments.reduce((total, document) => total + document.suggestions.filter((suggestion) => suggestion.status === 'proposed').length, 0);
530
+ const documentsMissingSourceArtifacts = reviewDocuments.filter((document) => document.attachments.length === 0 && !document.lastArtifactId).length;
531
+ const comparisons = artifacts.filter(isModelCompareArtifact);
532
+ const judgments = artifacts.filter(isModelCompareJudgmentArtifact);
533
+ const routeDecisions = artifacts.filter(isModelCompareRouteDecisionArtifact);
534
+ const handoffs = artifacts.filter(isReviewerHandoffArtifact);
535
+ const revealedJudgments = judgments.filter((artifact) => (
536
+ artifact.metadata.revealIncludedInJudgment === true
537
+ && readArtifactMetadataString(artifact.metadata, 'winnerModel').length > 0
538
+ ));
539
+ const hiddenJudgments = judgments.filter((artifact) => (
540
+ artifact.metadata.revealIncludedInJudgment !== true
541
+ || readArtifactMetadataString(artifact.metadata, 'winnerModel').length === 0
542
+ ));
543
+ const routeDecisionComparisonIds = new Set(routeDecisions.map((artifact) => readArtifactMetadataString(artifact.metadata, 'comparisonId')).filter(Boolean));
544
+ const routeDecisionJudgmentArtifactIds = new Set(routeDecisions.map((artifact) => readArtifactMetadataString(artifact.metadata, 'judgmentArtifactId')).filter(Boolean));
545
+ const revealedJudgmentsWaitingOnRouteDecision = revealedJudgments.filter((artifact) => {
546
+ const comparisonId = readArtifactMetadataString(artifact.metadata, 'comparisonId');
547
+ return !routeDecisionJudgmentArtifactIds.has(artifact.id) && (!comparisonId || !routeDecisionComparisonIds.has(comparisonId));
548
+ });
549
+ const revealedComparisonIds = new Set(revealedJudgments.map((artifact) => readArtifactMetadataString(artifact.metadata, 'comparisonId')).filter(Boolean));
550
+ const unrevealedComparisons = comparisons.filter((artifact) => {
551
+ const comparisonId = readArtifactMetadataString(artifact.metadata, 'comparisonId');
552
+ return artifact.metadata.revealIncludedInTranscript !== true && (!comparisonId || !revealedComparisonIds.has(comparisonId));
553
+ });
554
+ const comparisonSourceMissing = comparisons.filter((artifact) => (
555
+ !readArtifactMetadataString(artifact.metadata, 'sourceArtifactId')
556
+ && !readArtifactMetadataString(artifact.metadata, 'documentId')
557
+ )).length;
558
+ const missingSourceArtifacts = documentsMissingSourceArtifacts + comparisonSourceMissing;
559
+ const handoffsMissingRelatedArtifacts = handoffs.filter((artifact) => readArtifactMetadataStringList(artifact.metadata, 'relatedArtifactIds').length === 0).length;
560
+ const issueCount = openComments
561
+ + proposedSuggestions
562
+ + missingSourceArtifacts
563
+ + unrevealedComparisons.length
564
+ + hiddenJudgments.length
565
+ + revealedJudgmentsWaitingOnRouteDecision.length
566
+ + handoffsMissingRelatedArtifacts;
567
+ const next = !artifactListAvailable
568
+ ? 'Artifact listing is unavailable; run Review readiness preflight before export, archive, or route update.'
569
+ : openComments + proposedSuggestions > 0
570
+ ? 'Resolve open comments or accept/reject proposed suggestions before exporting reviewer packets.'
571
+ : missingSourceArtifacts > 0
572
+ ? 'Attach source artifacts or related evidence before export, handoff, or archive.'
573
+ : unrevealedComparisons.length + hiddenJudgments.length > 0
574
+ ? 'Reveal comparison/judgment identity before applying a model route or final reviewer handoff.'
575
+ : revealedJudgmentsWaitingOnRouteDecision.length > 0
576
+ ? 'Apply the revealed winner or explicitly leave routing unchanged before archiving.'
577
+ : handoffsMissingRelatedArtifacts > 0
578
+ ? 'Recreate reviewer handoffs with related evidence before ZIP archive.'
579
+ : 'Reviewer readiness preflight is clear for export, handoff archive, or route update.';
580
+ return {
581
+ status: !artifactListAvailable ? 'needs-setup' : issueCount > 0 ? 'attention' : 'ready',
582
+ summary: `${issueCount} issue(s): ${openComments} comment(s), ${proposedSuggestions} suggestion(s), ${missingSourceArtifacts} source/evidence gap(s), ${unrevealedComparisons.length + hiddenJudgments.length} hidden comparison item(s), ${revealedJudgmentsWaitingOnRouteDecision.length} route decision(s), ${handoffsMissingRelatedArtifacts} handoff evidence gap(s).`,
583
+ next,
584
+ issueCount,
585
+ openComments,
586
+ proposedSuggestions,
587
+ missingSourceArtifacts,
588
+ unrevealedComparisons: unrevealedComparisons.length,
589
+ hiddenJudgments: hiddenJudgments.length,
590
+ revealedJudgments: revealedJudgmentsWaitingOnRouteDecision.length,
591
+ handoffsMissingRelatedArtifacts,
592
+ };
593
+ }