@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.
- package/CHANGELOG.md +176 -0
- package/README.md +46 -30
- package/dist/package/{ast-grep-napi.linux-x64-gnu-mkk8xwww.node → ast-grep-napi.linux-x64-gnu-swtppvy9.node} +0 -0
- package/dist/package/{ast-grep-napi.linux-x64-musl-ryqtgdv6.node → ast-grep-napi.linux-x64-musl-ttfcdtap.node} +0 -0
- package/dist/package/main.js +132662 -91399
- package/docs/README.md +14 -7
- package/docs/channels-remote-and-api.md +8 -5
- package/docs/connected-host.md +14 -12
- package/docs/getting-started.md +56 -30
- package/docs/knowledge-artifacts-and-multimodal.md +7 -5
- package/docs/project-planning.md +2 -2
- package/docs/providers-and-routing.md +11 -3
- package/docs/tools-and-commands.md +114 -45
- package/docs/voice-and-live-tts.md +3 -1
- package/package.json +3 -2
- package/release/release-notes.md +130 -5
- package/release/release-readiness.json +120 -90
- package/src/agent/assistant-cockpit.ts +247 -0
- package/src/agent/autonomy-schedule-format.ts +96 -0
- package/src/agent/autonomy-schedule.ts +514 -0
- package/src/agent/channel-delivery-receipts.ts +292 -0
- package/src/agent/competitive-feature-inventory.ts +296 -0
- package/src/agent/document-registry-types.ts +106 -0
- package/src/agent/document-registry.ts +734 -0
- package/src/agent/harness-control.ts +2 -2
- package/src/agent/memory-prompt.ts +2 -2
- package/src/agent/operator-actions.ts +50 -6
- package/src/agent/persona-registry.ts +8 -0
- package/src/agent/project-context-files.ts +273 -0
- package/src/agent/prompt-context-receipts.ts +502 -0
- package/src/agent/reminder-schedule-format.ts +16 -2
- package/src/agent/research-run-registry.ts +582 -0
- package/src/agent/research-source-registry.ts +441 -0
- package/src/agent/routine-registry.ts +19 -3
- package/src/agent/routine-schedule-format.ts +27 -2
- package/src/agent/runtime-profile-starters.ts +7 -0
- package/src/agent/runtime-profile.ts +106 -4
- package/src/agent/schedule-edit-format.ts +129 -0
- package/src/agent/schedule-edit.ts +496 -0
- package/src/agent/schedule-next-routes.ts +42 -0
- package/src/agent/setup-wizard-checkpoint.ts +140 -0
- package/src/agent/setup-wizard.ts +447 -0
- package/src/agent/skill-registry-types.ts +102 -0
- package/src/agent/skill-registry.ts +81 -107
- package/src/agent/vibe-confirmation-routes.ts +62 -0
- package/src/agent/vibe-file.ts +285 -0
- package/src/cli/agent-knowledge-command.ts +45 -2
- package/src/cli/help.ts +3 -0
- package/src/cli/profiles-command.ts +25 -11
- package/src/config/agent-settings-policy.ts +1 -18
- package/src/input/agent-workspace-activation.ts +53 -0
- package/src/input/agent-workspace-artifact-browser-editor.ts +494 -0
- package/src/input/agent-workspace-artifact-metadata.ts +17 -0
- package/src/input/agent-workspace-basic-command-editors.ts +5 -1
- package/src/input/agent-workspace-categories.ts +153 -155
- package/src/input/agent-workspace-channel-triage.ts +481 -0
- package/src/input/agent-workspace-channels.ts +263 -0
- package/src/input/agent-workspace-command-editor.ts +152 -0
- package/src/input/agent-workspace-context-snapshot.ts +349 -0
- package/src/input/agent-workspace-delegation-editor-submission.ts +8 -0
- package/src/input/agent-workspace-document-editor.ts +502 -0
- package/src/input/agent-workspace-document-ops-editor.ts +523 -0
- package/src/input/agent-workspace-host-category.ts +1 -1
- package/src/input/agent-workspace-local-library-snapshot.ts +167 -0
- package/src/input/agent-workspace-model-compare-editor.ts +376 -0
- package/src/input/agent-workspace-model-compare-prompt-submission.ts +552 -0
- package/src/input/agent-workspace-model-compare-types.ts +82 -0
- package/src/input/agent-workspace-model-compare-utils.ts +35 -0
- package/src/input/agent-workspace-onboarding-categories.ts +141 -0
- package/src/input/agent-workspace-operations-command-editor-submission.ts +53 -0
- package/src/input/agent-workspace-operations-command-editors.ts +22 -0
- package/src/input/agent-workspace-research-report-editor.ts +212 -0
- package/src/input/agent-workspace-research-run-editor.ts +144 -0
- package/src/input/agent-workspace-research-source-editor.ts +167 -0
- package/src/input/agent-workspace-review-packet-snapshot.ts +593 -0
- package/src/input/agent-workspace-review-packet-utils.ts +265 -0
- package/src/input/agent-workspace-settings.ts +262 -30
- package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
- package/src/input/agent-workspace-setup-snapshot.ts +198 -0
- package/src/input/agent-workspace-setup.ts +34 -1
- package/src/input/agent-workspace-snapshot.ts +162 -147
- package/src/input/agent-workspace-types.ts +287 -2
- package/src/input/agent-workspace.ts +18 -0
- package/src/input/command-registry.ts +9 -2
- package/src/input/commands/agent-runtime-profile-runtime.ts +28 -13
- package/src/input/commands/channels-runtime.ts +102 -114
- package/src/input/commands/delegation-runtime.ts +87 -24
- package/src/input/commands/knowledge-browser-flags.ts +12 -0
- package/src/input/commands/knowledge.ts +28 -14
- package/src/input/commands/operator-actions-runtime.ts +6 -3
- package/src/input/commands/schedule-runtime.ts +71 -14
- package/src/input/commands/session-content.ts +2 -2
- package/src/input/commands/shell-core.ts +2 -0
- package/src/input/commands/vibe-runtime.ts +140 -0
- package/src/input/commands.ts +2 -0
- package/src/input/connected-host-routes.ts +123 -0
- package/src/input/settings-modal-types.ts +14 -0
- package/src/main.ts +15 -15
- package/src/panels/qr-panel.ts +2 -2
- package/src/renderer/agent-workspace-context-lines.ts +686 -0
- package/src/renderer/agent-workspace.ts +30 -419
- package/src/renderer/settings-modal-helpers.ts +12 -0
- package/src/renderer/settings-modal.ts +12 -0
- package/src/runtime/bootstrap-command-context.ts +11 -1
- package/src/runtime/bootstrap-command-parts.ts +14 -3
- package/src/runtime/bootstrap-core.ts +43 -1
- package/src/runtime/bootstrap-shell.ts +6 -0
- package/src/runtime/bootstrap.ts +113 -30
- package/src/runtime/connected-host-auth.ts +3 -2
- package/src/runtime/execution-ledger.ts +231 -0
- package/src/runtime/services.ts +88 -10
- package/src/runtime/tool-permission-safety.ts +287 -0
- package/src/shell/session-continuity-hints.ts +39 -0
- package/src/tools/agent-artifacts-tool.ts +606 -0
- package/src/tools/agent-audit-tool.ts +155 -0
- package/src/tools/agent-autonomy-schedule-tool.ts +238 -0
- package/src/tools/agent-autonomy-tool.ts +140 -0
- package/src/tools/agent-channel-send-tool.ts +24 -2
- package/src/tools/agent-channels-tool.ts +140 -0
- package/src/tools/agent-computer-tool.ts +168 -0
- package/src/tools/agent-context-policy.ts +267 -10
- package/src/tools/agent-context-tool.ts +143 -0
- package/src/tools/agent-delegation-tool.ts +128 -0
- package/src/tools/agent-device-tool.ts +240 -0
- package/src/tools/agent-documents-tool.ts +684 -0
- package/src/tools/agent-execution-tool.ts +230 -0
- package/src/tools/agent-harness-agent-orchestration.ts +703 -0
- package/src/tools/agent-harness-autonomy-intake.ts +785 -0
- package/src/tools/agent-harness-autonomy-live-records.ts +588 -0
- package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
- package/src/tools/agent-harness-autonomy-queue.ts +560 -0
- package/src/tools/agent-harness-background-processes-types.ts +35 -0
- package/src/tools/agent-harness-background-processes.ts +794 -0
- package/src/tools/agent-harness-browser-cockpit-route.ts +219 -0
- package/src/tools/agent-harness-browser-control.ts +338 -0
- package/src/tools/agent-harness-channel-metadata.ts +168 -10
- package/src/tools/agent-harness-cli-command-policy.ts +110 -0
- package/src/tools/agent-harness-command-catalog.ts +4 -2
- package/src/tools/agent-harness-command-runner.ts +42 -0
- package/src/tools/agent-harness-connected-host-status.ts +7 -4
- package/src/tools/agent-harness-delegation-posture.ts +234 -19
- package/src/tools/agent-harness-document-ops-reviewer-readiness.ts +234 -0
- package/src/tools/agent-harness-document-ops-types.ts +72 -0
- package/src/tools/agent-harness-document-ops.ts +671 -0
- package/src/tools/agent-harness-execution-history.ts +489 -0
- package/src/tools/agent-harness-execution-posture.ts +382 -0
- package/src/tools/agent-harness-file-recovery.ts +135 -0
- package/src/tools/agent-harness-keybinding-metadata.ts +16 -12
- package/src/tools/agent-harness-learning-curator-common.ts +102 -0
- package/src/tools/agent-harness-learning-curator-consolidation.ts +295 -0
- package/src/tools/agent-harness-learning-curator-proposals.ts +606 -0
- package/src/tools/agent-harness-learning-curator-types.ts +151 -0
- package/src/tools/agent-harness-learning-curator.ts +417 -0
- package/src/tools/agent-harness-local-model-benchmarks.ts +199 -0
- package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
- package/src/tools/agent-harness-local-model-endpoints.ts +673 -0
- package/src/tools/agent-harness-media-posture.ts +208 -2
- package/src/tools/agent-harness-memory-posture.ts +556 -0
- package/src/tools/agent-harness-metadata.ts +100 -168
- package/src/tools/agent-harness-mode-catalog.ts +84 -33
- package/src/tools/agent-harness-model-catalog.ts +162 -0
- package/src/tools/agent-harness-model-provider-health.ts +186 -0
- package/src/tools/agent-harness-model-readiness.ts +406 -0
- package/src/tools/agent-harness-model-routing-types.ts +266 -0
- package/src/tools/agent-harness-model-routing-utils.ts +30 -0
- package/src/tools/agent-harness-model-routing.ts +137 -190
- package/src/tools/agent-harness-operator-methods.ts +115 -133
- package/src/tools/agent-harness-pairing-posture.ts +431 -18
- package/src/tools/agent-harness-personal-ops-discovery.ts +533 -0
- package/src/tools/agent-harness-personal-ops-intake.ts +385 -0
- package/src/tools/agent-harness-personal-ops-lanes.ts +204 -0
- package/src/tools/agent-harness-personal-ops-records.ts +732 -0
- package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
- package/src/tools/agent-harness-personal-ops-types.ts +222 -0
- package/src/tools/agent-harness-personal-ops.ts +606 -0
- package/src/tools/agent-harness-project-context.ts +144 -0
- package/src/tools/agent-harness-prompt-context.ts +589 -0
- package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
- package/src/tools/agent-harness-release-evidence.ts +9 -7
- package/src/tools/agent-harness-release-readiness.ts +9 -7
- package/src/tools/agent-harness-research-briefing.ts +427 -0
- package/src/tools/agent-harness-research-queue.ts +250 -0
- package/src/tools/agent-harness-research-runs.ts +295 -0
- package/src/tools/agent-harness-research-workflow.ts +322 -0
- package/src/tools/agent-harness-security-posture.ts +2 -2
- package/src/tools/agent-harness-service-posture.ts +9 -9
- package/src/tools/agent-harness-session-metadata.ts +3 -3
- package/src/tools/agent-harness-setup-connected-host.ts +501 -0
- package/src/tools/agent-harness-setup-describe.ts +557 -0
- package/src/tools/agent-harness-setup-handoffs.ts +502 -0
- package/src/tools/agent-harness-setup-model-helpers.ts +101 -0
- package/src/tools/agent-harness-setup-plan.ts +253 -0
- package/src/tools/agent-harness-setup-posture-types.ts +218 -0
- package/src/tools/agent-harness-setup-posture-utils.ts +173 -0
- package/src/tools/agent-harness-setup-posture.ts +449 -221
- package/src/tools/agent-harness-setup-smoke.ts +508 -0
- package/src/tools/agent-harness-sudo-posture.ts +114 -0
- package/src/tools/agent-harness-tool-schema.ts +120 -7
- package/src/tools/agent-harness-tool-types.ts +78 -0
- package/src/tools/agent-harness-tool-utils.ts +43 -0
- package/src/tools/agent-harness-tool.ts +311 -370
- package/src/tools/agent-harness-ui-surface-metadata.ts +76 -69
- package/src/tools/agent-harness-ui-surface-types.ts +46 -0
- package/src/tools/agent-harness-vibe-health.ts +105 -0
- package/src/tools/agent-harness-workspace-action-runner.ts +149 -0
- package/src/tools/agent-harness-workspace-actions.ts +114 -8
- package/src/tools/agent-harness-workspace-editor-execution.ts +214 -0
- package/src/tools/agent-harness-workspace-editor-runner.ts +755 -0
- package/src/tools/agent-host-tool.ts +159 -0
- package/src/tools/agent-knowledge-ingest-tool.ts +34 -1
- package/src/tools/agent-learning-consolidation-core.ts +327 -0
- package/src/tools/agent-learning-consolidation-tool.ts +758 -0
- package/src/tools/agent-memory-tool.ts +214 -0
- package/src/tools/agent-model-compare-export.ts +315 -0
- package/src/tools/agent-model-compare-handoff.ts +592 -0
- package/src/tools/agent-model-compare-judgment.ts +633 -0
- package/src/tools/agent-model-compare-run.ts +722 -0
- package/src/tools/agent-model-compare-tool.ts +698 -0
- package/src/tools/agent-model-compare-types.ts +191 -0
- package/src/tools/agent-model-compare-utils.ts +93 -0
- package/src/tools/agent-models-tool.ts +208 -0
- package/src/tools/agent-operator-action-tool.ts +1 -1
- package/src/tools/agent-operator-method-tool.ts +643 -0
- package/src/tools/agent-personal-ops-tool.ts +197 -0
- package/src/tools/agent-policy-explanation.ts +415 -0
- package/src/tools/agent-research-report-tool.ts +608 -0
- package/src/tools/agent-research-runs-tool.ts +434 -0
- package/src/tools/agent-research-sources-tool.ts +443 -0
- package/src/tools/agent-research-tool.ts +687 -0
- package/src/tools/agent-review-packet-presets-core.ts +757 -0
- package/src/tools/agent-review-packet-presets-tool.ts +466 -0
- package/src/tools/agent-review-packet-share-tool.ts +305 -0
- package/src/tools/agent-route-planner-candidates-setup.ts +411 -0
- package/src/tools/agent-route-planner-candidates-surfaces.ts +432 -0
- package/src/tools/agent-route-planner-candidates-work.ts +251 -0
- package/src/tools/agent-route-planner-helpers.ts +667 -0
- package/src/tools/agent-route-planner.ts +185 -0
- package/src/tools/agent-route-tool.ts +105 -0
- package/src/tools/agent-schedule-edit-tool.ts +210 -0
- package/src/tools/agent-schedule-tool.ts +282 -0
- package/src/tools/agent-security-tool.ts +145 -0
- package/src/tools/agent-sessions-tool.ts +122 -0
- package/src/tools/agent-settings-import-tool.ts +104 -0
- package/src/tools/agent-settings-tool.ts +176 -0
- package/src/tools/agent-setup-tool.ts +226 -0
- package/src/tools/agent-support-tool.ts +122 -0
- package/src/tools/agent-terminal-process-tools.ts +167 -0
- package/src/tools/agent-tool-policy-guard-types.ts +77 -0
- package/src/tools/agent-tool-policy-guard.ts +110 -89
- package/src/tools/agent-vibe-tool.ts +297 -0
- package/src/tools/agent-work-plan-tool.ts +265 -6
- package/src/tools/agent-workspace-tool.ts +305 -0
- package/src/tools/artifact-archive.ts +169 -0
- package/src/tools/tool-definition-compaction.ts +36 -0
- package/src/tools/tool-execution-safety.ts +41 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import type { ArtifactRecord } from '@pellux/goodvibes-sdk/platform/artifacts';
|
|
3
|
+
import { createZipArchive, packageArtifactFilename, sanitizeArtifactMetadata, sanitizeArtifactSourceUri, type ArtifactPackageEntry } from './artifact-archive.ts';
|
|
4
|
+
import type { AgentModelCompareArtifactStore, LoadedComparisonHandoff, SavedComparisonArtifact } from './agent-model-compare-types.ts';
|
|
5
|
+
import { MAX_HANDOFF_ARCHIVE_ARTIFACTS, MAX_HANDOFF_ARTIFACT_BYTES, MAX_HANDOFF_DIFF_INPUT_LINES, MAX_HANDOFF_DIFF_ROWS, MAX_HANDOFF_DIFF_SECTION_PREVIEW_CHARS } from './agent-model-compare-types.ts';
|
|
6
|
+
import { isModelCompareHandoffArtifact, isModelCompareRouteDecisionArtifact, toSavedComparisonArtifact } from './agent-model-compare-run.ts';
|
|
7
|
+
import { isTextLike, readString, readStringList, previewText } from './agent-model-compare-utils.ts';
|
|
8
|
+
|
|
9
|
+
export interface LoadedHandoffArchiveArtifact {
|
|
10
|
+
readonly role: 'handoff' | 'source' | 'related' | 'route-decision';
|
|
11
|
+
readonly record: ArtifactRecord;
|
|
12
|
+
readonly buffer: Buffer;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ComparisonHandoffArchivePayload {
|
|
16
|
+
readonly artifactCount: number;
|
|
17
|
+
readonly sourceBytes: number;
|
|
18
|
+
readonly includedArtifactIds: readonly string[];
|
|
19
|
+
readonly routeDecisionArtifactIds: readonly string[];
|
|
20
|
+
readonly entries: readonly ArtifactPackageEntry[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function formatArchiveBytes(value: number): string {
|
|
24
|
+
if (!Number.isFinite(value) || value < 0) return '(unknown)';
|
|
25
|
+
if (value < 1024) return `${value} B`;
|
|
26
|
+
if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
|
|
27
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MB`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function loadHandoffFromArtifact(
|
|
31
|
+
artifactStore: AgentModelCompareArtifactStore | undefined,
|
|
32
|
+
artifactId: string,
|
|
33
|
+
): Promise<LoadedComparisonHandoff | null> {
|
|
34
|
+
if (!artifactStore?.readContent) return null;
|
|
35
|
+
const { record } = await artifactStore.readContent(artifactId);
|
|
36
|
+
if (!isModelCompareHandoffArtifact(record)) return null;
|
|
37
|
+
const sourceArtifactId = readString(record.metadata.sourceArtifactId);
|
|
38
|
+
if (!sourceArtifactId) {
|
|
39
|
+
throw new Error(`Reviewer handoff ${record.id} is missing sourceArtifactId metadata. Recreate the handoff with mode:"handoff".`);
|
|
40
|
+
}
|
|
41
|
+
const sourceKind = readString(record.metadata.sourceKind) === 'judgment' ? 'judgment' : 'comparison';
|
|
42
|
+
return {
|
|
43
|
+
artifact: toSavedComparisonArtifact(record),
|
|
44
|
+
handoffId: readString(record.metadata.handoffId) || `handoff_from_${record.id}`,
|
|
45
|
+
sourceArtifactId,
|
|
46
|
+
sourceKind,
|
|
47
|
+
comparisonId: readString(record.metadata.comparisonId) || 'unknown-comparison',
|
|
48
|
+
relatedArtifactIds: readStringList(record.metadata.relatedArtifactIds).filter((relatedId) => relatedId !== record.id && relatedId !== sourceArtifactId),
|
|
49
|
+
revealIncludedInHandoff: record.metadata.revealIncludedInHandoff === true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface LoadedHandoffDiffArtifact {
|
|
54
|
+
readonly handoff: LoadedComparisonHandoff;
|
|
55
|
+
readonly text: string;
|
|
56
|
+
readonly truncatedBytes: number;
|
|
57
|
+
readonly originalLineCount: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface HandoffDiffRow {
|
|
61
|
+
readonly kind: 'same' | 'left' | 'right';
|
|
62
|
+
readonly text: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function loadHandoffDiffArtifact(
|
|
66
|
+
artifactStore: AgentModelCompareArtifactStore | undefined,
|
|
67
|
+
artifactId: string,
|
|
68
|
+
): Promise<LoadedHandoffDiffArtifact | null> {
|
|
69
|
+
if (!artifactStore?.readContent) return null;
|
|
70
|
+
const { record, buffer } = await artifactStore.readContent(artifactId);
|
|
71
|
+
if (!isModelCompareHandoffArtifact(record)) return null;
|
|
72
|
+
if (!isTextLike(record.mimeType)) {
|
|
73
|
+
throw new Error(`Reviewer handoff ${record.id} is ${record.mimeType}; diff can only compare text-like handoff artifacts.`);
|
|
74
|
+
}
|
|
75
|
+
const handoff = await loadHandoffFromArtifact(artifactStore, artifactId);
|
|
76
|
+
if (!handoff) return null;
|
|
77
|
+
const sliced = buffer.subarray(0, Math.min(buffer.byteLength, MAX_HANDOFF_ARTIFACT_BYTES));
|
|
78
|
+
const text = sliced.toString('utf-8').replace(/\0/g, '').trimEnd();
|
|
79
|
+
return {
|
|
80
|
+
handoff,
|
|
81
|
+
text,
|
|
82
|
+
truncatedBytes: Math.max(0, buffer.byteLength - sliced.byteLength),
|
|
83
|
+
originalLineCount: text.split('\n').length,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function normalizedSectionContent(lines: readonly string[]): string {
|
|
88
|
+
return lines.join('\n').replace(/\s+/g, ' ').trim();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function handoffSectionMap(text: string): Map<string, string> {
|
|
92
|
+
const lineMap = handoffSectionLineMap(text);
|
|
93
|
+
const normalized = new Map<string, string>();
|
|
94
|
+
for (const [section, lines] of lineMap) {
|
|
95
|
+
normalized.set(section, normalizedSectionContent(lines));
|
|
96
|
+
}
|
|
97
|
+
return normalized;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function handoffSectionLineMap(text: string): Map<string, string[]> {
|
|
101
|
+
const sections = new Map<string, string[]>();
|
|
102
|
+
let current = '(preamble)';
|
|
103
|
+
let inCodeFence = false;
|
|
104
|
+
sections.set(current, []);
|
|
105
|
+
for (const line of text.split('\n')) {
|
|
106
|
+
if (line.trimStart().startsWith('```')) {
|
|
107
|
+
sections.get(current)?.push(line);
|
|
108
|
+
inCodeFence = !inCodeFence;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const heading = inCodeFence ? null : /^(#{1,6})\s+(.+?)\s*$/.exec(line);
|
|
112
|
+
const headingText = heading?.[2] ?? '';
|
|
113
|
+
const opensPacketSection = Boolean(heading) && (
|
|
114
|
+
heading![1]!.length === 2
|
|
115
|
+
|| (heading![1]!.length === 1 && headingText === 'Blind Model Comparison Reviewer Handoff')
|
|
116
|
+
);
|
|
117
|
+
if (opensPacketSection) {
|
|
118
|
+
current = headingText || '(untitled)';
|
|
119
|
+
if (!sections.has(current)) sections.set(current, []);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
sections.get(current)?.push(line);
|
|
123
|
+
}
|
|
124
|
+
return sections;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function normalizeHandoffSectionAlias(value: string): string {
|
|
128
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, '');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function resolveHandoffSectionName(sectionId: string, lineMap: Map<string, string[]>): string | null {
|
|
132
|
+
const normalized = normalizeHandoffSectionAlias(sectionId);
|
|
133
|
+
if (!normalized || normalized === 'all' || normalized === 'full') return null;
|
|
134
|
+
if (normalized === 'metadata' || normalized === 'metadatadelta') return 'Metadata delta';
|
|
135
|
+
const aliases = new Map<string, string>([
|
|
136
|
+
['preamble', '(preamble)'],
|
|
137
|
+
['header', 'Blind Model Comparison Reviewer Handoff'],
|
|
138
|
+
['summary', 'Blind Model Comparison Reviewer Handoff'],
|
|
139
|
+
['overview', 'Blind Model Comparison Reviewer Handoff'],
|
|
140
|
+
['policy', 'Handoff Policy'],
|
|
141
|
+
['handoffpolicy', 'Handoff Policy'],
|
|
142
|
+
['related', 'Related Artifacts'],
|
|
143
|
+
['relatedartifacts', 'Related Artifacts'],
|
|
144
|
+
['artifacts', 'Related Artifacts'],
|
|
145
|
+
['comparison', 'Comparison Evidence'],
|
|
146
|
+
['comparisonevidence', 'Comparison Evidence'],
|
|
147
|
+
['evidence', 'Comparison Evidence'],
|
|
148
|
+
]);
|
|
149
|
+
const aliased = aliases.get(normalized);
|
|
150
|
+
if (aliased && lineMap.has(aliased)) return aliased;
|
|
151
|
+
for (const section of lineMap.keys()) {
|
|
152
|
+
if (normalizeHandoffSectionAlias(section) === normalized) return section;
|
|
153
|
+
}
|
|
154
|
+
return '';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function formatHandoffMetadataDelta(
|
|
158
|
+
label: string,
|
|
159
|
+
left: string | readonly string[] | boolean,
|
|
160
|
+
right: string | readonly string[] | boolean,
|
|
161
|
+
): string {
|
|
162
|
+
const leftValue = Array.isArray(left) ? left.join(', ') || '(none)' : String(left === '' ? '(none)' : left);
|
|
163
|
+
const rightValue = Array.isArray(right) ? right.join(', ') || '(none)' : String(right === '' ? '(none)' : right);
|
|
164
|
+
return leftValue === rightValue
|
|
165
|
+
? ` ${label}: same (${leftValue})`
|
|
166
|
+
: ` ${label}: changed ${leftValue} -> ${rightValue}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function buildLineDiff(left: readonly string[], right: readonly string[]): readonly HandoffDiffRow[] {
|
|
170
|
+
const rows = Array.from({ length: left.length + 1 }, () => Array<number>(right.length + 1).fill(0));
|
|
171
|
+
for (let leftIndex = left.length - 1; leftIndex >= 0; leftIndex -= 1) {
|
|
172
|
+
for (let rightIndex = right.length - 1; rightIndex >= 0; rightIndex -= 1) {
|
|
173
|
+
rows[leftIndex]![rightIndex] = left[leftIndex] === right[rightIndex]
|
|
174
|
+
? rows[leftIndex + 1]![rightIndex + 1]! + 1
|
|
175
|
+
: Math.max(rows[leftIndex + 1]![rightIndex]!, rows[leftIndex]![rightIndex + 1]!);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const diff: HandoffDiffRow[] = [];
|
|
180
|
+
let leftIndex = 0;
|
|
181
|
+
let rightIndex = 0;
|
|
182
|
+
while (leftIndex < left.length && rightIndex < right.length) {
|
|
183
|
+
if (left[leftIndex] === right[rightIndex]) {
|
|
184
|
+
diff.push({ kind: 'same', text: left[leftIndex]! });
|
|
185
|
+
leftIndex += 1;
|
|
186
|
+
rightIndex += 1;
|
|
187
|
+
} else if (rows[leftIndex + 1]![rightIndex]! >= rows[leftIndex]![rightIndex + 1]!) {
|
|
188
|
+
diff.push({ kind: 'left', text: left[leftIndex]! });
|
|
189
|
+
leftIndex += 1;
|
|
190
|
+
} else {
|
|
191
|
+
diff.push({ kind: 'right', text: right[rightIndex]! });
|
|
192
|
+
rightIndex += 1;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
while (leftIndex < left.length) {
|
|
196
|
+
diff.push({ kind: 'left', text: left[leftIndex]! });
|
|
197
|
+
leftIndex += 1;
|
|
198
|
+
}
|
|
199
|
+
while (rightIndex < right.length) {
|
|
200
|
+
diff.push({ kind: 'right', text: right[rightIndex]! });
|
|
201
|
+
rightIndex += 1;
|
|
202
|
+
}
|
|
203
|
+
return diff;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function formatHandoffDiffRows(rows: readonly HandoffDiffRow[]): readonly string[] {
|
|
207
|
+
const lines: string[] = [];
|
|
208
|
+
let hiddenUnchanged = 0;
|
|
209
|
+
let emittedChanges = 0;
|
|
210
|
+
let omittedChanges = 0;
|
|
211
|
+
for (const row of rows) {
|
|
212
|
+
if (row.kind === 'same') {
|
|
213
|
+
hiddenUnchanged += 1;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (emittedChanges >= MAX_HANDOFF_DIFF_ROWS) {
|
|
217
|
+
omittedChanges += 1;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (hiddenUnchanged > 0) {
|
|
221
|
+
lines.push(` ... ${hiddenUnchanged} unchanged line(s) hidden`);
|
|
222
|
+
hiddenUnchanged = 0;
|
|
223
|
+
}
|
|
224
|
+
const prefix = row.kind === 'left' ? '- ' : '+ ';
|
|
225
|
+
lines.push(`${prefix}${previewText(row.text || '(blank)', 220)}`);
|
|
226
|
+
emittedChanges += 1;
|
|
227
|
+
}
|
|
228
|
+
if (hiddenUnchanged > 0 && lines.length > 0) lines.push(` ... ${hiddenUnchanged} unchanged line(s) hidden`);
|
|
229
|
+
if (omittedChanges > 0) lines.push(` ... ${omittedChanges} changed line(s) omitted by diff row cap`);
|
|
230
|
+
return lines.length > 0 ? lines : [' No textual changes detected in the bounded handoff preview.'];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function formatHandoffSectionDiff(
|
|
234
|
+
left: LoadedHandoffDiffArtifact,
|
|
235
|
+
right: LoadedHandoffDiffArtifact,
|
|
236
|
+
): readonly string[] {
|
|
237
|
+
const leftSections = handoffSectionMap(left.text);
|
|
238
|
+
const rightSections = handoffSectionMap(right.text);
|
|
239
|
+
const sectionNames = Array.from(new Set([...leftSections.keys(), ...rightSections.keys()]));
|
|
240
|
+
if (sectionNames.length === 0) return [' No markdown sections detected.'];
|
|
241
|
+
return sectionNames.map((section) => {
|
|
242
|
+
const leftValue = leftSections.get(section);
|
|
243
|
+
const rightValue = rightSections.get(section);
|
|
244
|
+
if (leftValue === undefined) return ` ${section}: added on right (${previewText(rightValue ?? '', MAX_HANDOFF_DIFF_SECTION_PREVIEW_CHARS) || 'empty'})`;
|
|
245
|
+
if (rightValue === undefined) return ` ${section}: removed from right (${previewText(leftValue, MAX_HANDOFF_DIFF_SECTION_PREVIEW_CHARS) || 'empty'})`;
|
|
246
|
+
if (leftValue === rightValue) return ` ${section}: same`;
|
|
247
|
+
return ` ${section}: changed`;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function formatHandoffDiff(input: {
|
|
252
|
+
readonly left: LoadedHandoffDiffArtifact;
|
|
253
|
+
readonly right: LoadedHandoffDiffArtifact;
|
|
254
|
+
readonly sectionId?: string;
|
|
255
|
+
}): string {
|
|
256
|
+
const leftSectionLines = handoffSectionLineMap(input.left.text);
|
|
257
|
+
const rightSectionLines = handoffSectionLineMap(input.right.text);
|
|
258
|
+
const sectionName = resolveHandoffSectionName(
|
|
259
|
+
input.sectionId ?? '',
|
|
260
|
+
new Map([...leftSectionLines, ...rightSectionLines]),
|
|
261
|
+
);
|
|
262
|
+
const sectionAvailable = sectionName !== '';
|
|
263
|
+
const allSections = ['Metadata delta', ...Array.from(new Set([...leftSectionLines.keys(), ...rightSectionLines.keys()]))]
|
|
264
|
+
.filter((section) => section !== '(preamble)')
|
|
265
|
+
.join(', ');
|
|
266
|
+
const leftRawLines = sectionName
|
|
267
|
+
? leftSectionLines.get(sectionName) ?? []
|
|
268
|
+
: input.left.text.split('\n');
|
|
269
|
+
const rightRawLines = sectionName
|
|
270
|
+
? rightSectionLines.get(sectionName) ?? []
|
|
271
|
+
: input.right.text.split('\n');
|
|
272
|
+
const leftLines = leftRawLines.slice(0, MAX_HANDOFF_DIFF_INPUT_LINES);
|
|
273
|
+
const rightLines = rightRawLines.slice(0, MAX_HANDOFF_DIFF_INPUT_LINES);
|
|
274
|
+
const diffRows = buildLineDiff(leftLines, rightLines);
|
|
275
|
+
return [
|
|
276
|
+
'Blind model comparison reviewer handoff visual diff',
|
|
277
|
+
`left ${input.left.handoff.artifact.artifactId} (${input.left.handoff.handoffId})`,
|
|
278
|
+
`right ${input.right.handoff.artifact.artifactId} (${input.right.handoff.handoffId})`,
|
|
279
|
+
`section jump ${sectionName ? sectionName : input.sectionId && !sectionAvailable ? `unmatched ${input.sectionId}` : 'all'}`,
|
|
280
|
+
`available sections ${allSections || '(none)'}`,
|
|
281
|
+
`line window ${leftLines.length}/${input.left.originalLineCount} left, ${rightLines.length}/${input.right.originalLineCount} right`,
|
|
282
|
+
...(input.left.truncatedBytes > 0 || input.right.truncatedBytes > 0
|
|
283
|
+
? [`truncated bytes left ${input.left.truncatedBytes}, right ${input.right.truncatedBytes}`]
|
|
284
|
+
: []),
|
|
285
|
+
'',
|
|
286
|
+
'Metadata delta',
|
|
287
|
+
formatHandoffMetadataDelta('comparison', input.left.handoff.comparisonId, input.right.handoff.comparisonId),
|
|
288
|
+
formatHandoffMetadataDelta('source', `${input.left.handoff.sourceArtifactId} (${input.left.handoff.sourceKind})`, `${input.right.handoff.sourceArtifactId} (${input.right.handoff.sourceKind})`),
|
|
289
|
+
formatHandoffMetadataDelta('related artifacts', input.left.handoff.relatedArtifactIds, input.right.handoff.relatedArtifactIds),
|
|
290
|
+
formatHandoffMetadataDelta('reveal included', input.left.handoff.revealIncludedInHandoff, input.right.handoff.revealIncludedInHandoff),
|
|
291
|
+
'',
|
|
292
|
+
'Section delta',
|
|
293
|
+
...formatHandoffSectionDiff(input.left, input.right),
|
|
294
|
+
'',
|
|
295
|
+
'Aligned line diff',
|
|
296
|
+
...(sectionAvailable ? formatHandoffDiffRows(diffRows) : [` Section ${input.sectionId || '(blank)'} was not found. Use all, policy, related, or comparison.`]),
|
|
297
|
+
'',
|
|
298
|
+
'No selected model was changed.',
|
|
299
|
+
].join('\n');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export async function loadHandoffArchiveArtifacts(
|
|
303
|
+
artifactStore: AgentModelCompareArtifactStore | undefined,
|
|
304
|
+
handoff: LoadedComparisonHandoff,
|
|
305
|
+
routeDecisionArtifactIds: readonly string[] = [],
|
|
306
|
+
): Promise<readonly LoadedHandoffArchiveArtifact[]> {
|
|
307
|
+
if (!artifactStore?.readContent) {
|
|
308
|
+
throw new Error('Reviewer handoff archive requires an artifact store with readContent support.');
|
|
309
|
+
}
|
|
310
|
+
const requested: Array<{ readonly id: string; readonly role: LoadedHandoffArchiveArtifact['role'] }> = [
|
|
311
|
+
{ id: handoff.artifact.artifactId, role: 'handoff' },
|
|
312
|
+
{ id: handoff.sourceArtifactId, role: 'source' },
|
|
313
|
+
...handoff.relatedArtifactIds.map((id) => ({ id, role: 'related' as const })),
|
|
314
|
+
...routeDecisionArtifactIds.map((id) => ({ id, role: 'route-decision' as const })),
|
|
315
|
+
];
|
|
316
|
+
const seen = new Set<string>();
|
|
317
|
+
const artifacts: LoadedHandoffArchiveArtifact[] = [];
|
|
318
|
+
for (const request of requested) {
|
|
319
|
+
if (!request.id || seen.has(request.id)) continue;
|
|
320
|
+
seen.add(request.id);
|
|
321
|
+
if (artifacts.length >= MAX_HANDOFF_ARCHIVE_ARTIFACTS) {
|
|
322
|
+
throw new Error(`Reviewer handoff archive supports at most ${MAX_HANDOFF_ARCHIVE_ARTIFACTS} artifacts.`);
|
|
323
|
+
}
|
|
324
|
+
const loaded = await artifactStore.readContent(request.id);
|
|
325
|
+
artifacts.push({ role: request.role, ...loaded });
|
|
326
|
+
}
|
|
327
|
+
return artifacts;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function findRouteDecisionArtifactIdsForHandoff(
|
|
331
|
+
artifactStore: AgentModelCompareArtifactStore | undefined,
|
|
332
|
+
handoff: LoadedComparisonHandoff,
|
|
333
|
+
): readonly string[] {
|
|
334
|
+
if (!artifactStore?.list) return [];
|
|
335
|
+
const seen = new Set<string>();
|
|
336
|
+
const matches: string[] = [];
|
|
337
|
+
for (const artifact of artifactStore.list(100).filter(isModelCompareRouteDecisionArtifact)) {
|
|
338
|
+
const judgmentArtifactId = readString(artifact.metadata.judgmentArtifactId);
|
|
339
|
+
const comparisonId = readString(artifact.metadata.comparisonId);
|
|
340
|
+
const matchesSource = handoff.sourceKind === 'judgment'
|
|
341
|
+
? judgmentArtifactId === handoff.sourceArtifactId
|
|
342
|
+
: comparisonId === handoff.comparisonId;
|
|
343
|
+
if (!matchesSource || seen.has(artifact.id)) continue;
|
|
344
|
+
seen.add(artifact.id);
|
|
345
|
+
matches.push(artifact.id);
|
|
346
|
+
}
|
|
347
|
+
return matches;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function buildComparisonHandoffArchivePayload(input: {
|
|
351
|
+
readonly handoff: LoadedComparisonHandoff;
|
|
352
|
+
readonly artifacts: readonly LoadedHandoffArchiveArtifact[];
|
|
353
|
+
}): ComparisonHandoffArchivePayload {
|
|
354
|
+
const usedFilenames = new Set<string>();
|
|
355
|
+
const entries: ArtifactPackageEntry[] = [];
|
|
356
|
+
const manifestArtifacts: Array<Record<string, unknown>> = [];
|
|
357
|
+
const fileLines: string[] = [];
|
|
358
|
+
let sourceBytes = 0;
|
|
359
|
+
const routeDecisionArtifactIds = input.artifacts
|
|
360
|
+
.filter((artifact) => artifact.role === 'route-decision')
|
|
361
|
+
.map((artifact) => artifact.record.id);
|
|
362
|
+
|
|
363
|
+
for (let index = 0; index < input.artifacts.length; index += 1) {
|
|
364
|
+
const artifact = input.artifacts[index]!;
|
|
365
|
+
const filename = packageArtifactFilename(artifact.record, index, usedFilenames);
|
|
366
|
+
const relativePath = `artifacts/${artifact.role}/${filename}`;
|
|
367
|
+
entries.push({ path: relativePath, buffer: artifact.buffer });
|
|
368
|
+
sourceBytes += artifact.buffer.byteLength;
|
|
369
|
+
fileLines.push(`- ${artifact.role}: ${artifact.record.id} -> ${relativePath} (${formatArchiveBytes(artifact.buffer.byteLength)}, ${artifact.record.mimeType})`);
|
|
370
|
+
manifestArtifacts.push({
|
|
371
|
+
role: artifact.role,
|
|
372
|
+
id: artifact.record.id,
|
|
373
|
+
file: relativePath,
|
|
374
|
+
originalFilename: artifact.record.filename ?? null,
|
|
375
|
+
kind: artifact.record.kind,
|
|
376
|
+
mimeType: artifact.record.mimeType,
|
|
377
|
+
sizeBytes: artifact.record.sizeBytes,
|
|
378
|
+
copiedBytes: artifact.buffer.byteLength,
|
|
379
|
+
sha256: artifact.record.sha256,
|
|
380
|
+
createdAt: new Date(artifact.record.createdAt).toISOString(),
|
|
381
|
+
expiresAt: artifact.record.expiresAt ? new Date(artifact.record.expiresAt).toISOString() : null,
|
|
382
|
+
acquisitionMode: artifact.record.acquisitionMode,
|
|
383
|
+
fetchMode: artifact.record.fetchMode,
|
|
384
|
+
sourceUri: sanitizeArtifactSourceUri(artifact.record.sourceUri) ?? null,
|
|
385
|
+
metadata: sanitizeArtifactMetadata(artifact.record.metadata),
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const createdAt = new Date().toISOString();
|
|
390
|
+
const manifest = {
|
|
391
|
+
version: 1,
|
|
392
|
+
product: 'goodvibes-agent',
|
|
393
|
+
archiveKind: 'agent-model-compare-handoff',
|
|
394
|
+
createdAt,
|
|
395
|
+
comparisonId: input.handoff.comparisonId,
|
|
396
|
+
handoff: {
|
|
397
|
+
handoffId: input.handoff.handoffId,
|
|
398
|
+
handoffArtifactId: input.handoff.artifact.artifactId,
|
|
399
|
+
sourceArtifactId: input.handoff.sourceArtifactId,
|
|
400
|
+
sourceKind: input.handoff.sourceKind,
|
|
401
|
+
relatedArtifactIds: input.handoff.relatedArtifactIds,
|
|
402
|
+
routeDecisionArtifactIds,
|
|
403
|
+
revealIncludedInHandoff: input.handoff.revealIncludedInHandoff,
|
|
404
|
+
},
|
|
405
|
+
artifactCount: input.artifacts.length,
|
|
406
|
+
sourceBytes,
|
|
407
|
+
policy: {
|
|
408
|
+
content: 'Exact saved artifact bytes copied into artifacts/.',
|
|
409
|
+
transcript: 'Artifact contents are not printed by the compare tool.',
|
|
410
|
+
metadata: 'Secret-like metadata keys and URL query parameters are redacted in this manifest.',
|
|
411
|
+
routeMutation: 'No selected model route is changed by this archive.',
|
|
412
|
+
retention: 'Original saved artifacts are retained in the Agent artifact store.',
|
|
413
|
+
},
|
|
414
|
+
artifacts: manifestArtifacts,
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
entries.push({
|
|
418
|
+
path: 'manifest.json',
|
|
419
|
+
buffer: Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, 'utf-8'),
|
|
420
|
+
});
|
|
421
|
+
entries.push({
|
|
422
|
+
path: 'README.md',
|
|
423
|
+
buffer: Buffer.from(
|
|
424
|
+
[
|
|
425
|
+
'# GoodVibes Agent Comparison Handoff Archive',
|
|
426
|
+
'',
|
|
427
|
+
`Generated: ${createdAt}`,
|
|
428
|
+
`Comparison: ${input.handoff.comparisonId}`,
|
|
429
|
+
`Handoff artifact: ${input.handoff.artifact.artifactId}`,
|
|
430
|
+
`Source artifact: ${input.handoff.sourceArtifactId} (${input.handoff.sourceKind})`,
|
|
431
|
+
`Related artifacts: ${input.handoff.relatedArtifactIds.length}`,
|
|
432
|
+
`Route-decision receipts: ${routeDecisionArtifactIds.length}`,
|
|
433
|
+
`Source bytes: ${sourceBytes}`,
|
|
434
|
+
'',
|
|
435
|
+
'Files',
|
|
436
|
+
...fileLines,
|
|
437
|
+
'',
|
|
438
|
+
'Manifest',
|
|
439
|
+
'- `manifest.json` contains redacted artifact metadata, archive policy, and file paths.',
|
|
440
|
+
'- Artifact bytes live under `artifacts/` and are copied exactly from the saved Agent artifact store.',
|
|
441
|
+
'- Original artifacts remain saved in Agent. This archive does not change the selected model.',
|
|
442
|
+
'',
|
|
443
|
+
].join('\n'),
|
|
444
|
+
'utf-8',
|
|
445
|
+
),
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
return {
|
|
449
|
+
artifactCount: input.artifacts.length,
|
|
450
|
+
sourceBytes,
|
|
451
|
+
includedArtifactIds: input.artifacts.map((artifact) => artifact.record.id),
|
|
452
|
+
routeDecisionArtifactIds,
|
|
453
|
+
entries,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export async function saveComparisonHandoffArchiveArtifact(input: {
|
|
458
|
+
readonly artifactStore?: AgentModelCompareArtifactStore;
|
|
459
|
+
readonly handoff: LoadedComparisonHandoff;
|
|
460
|
+
readonly payload: ComparisonHandoffArchivePayload;
|
|
461
|
+
readonly archive: Buffer;
|
|
462
|
+
}): Promise<SavedComparisonArtifact> {
|
|
463
|
+
if (!input.artifactStore) throw new Error('Cannot create reviewer handoff archive because the artifact store is unavailable.');
|
|
464
|
+
const archiveId = `hndarc_${randomUUID()}`;
|
|
465
|
+
const descriptor = await input.artifactStore.create({
|
|
466
|
+
kind: 'archive',
|
|
467
|
+
mimeType: 'application/zip',
|
|
468
|
+
filename: `blind-model-comparison-handoff-archive-${archiveId}.zip`,
|
|
469
|
+
dataBase64: input.archive.toString('base64'),
|
|
470
|
+
metadata: {
|
|
471
|
+
purpose: 'agent-model-compare-handoff-archive',
|
|
472
|
+
archiveId,
|
|
473
|
+
handoffArtifactId: input.handoff.artifact.artifactId,
|
|
474
|
+
handoffId: input.handoff.handoffId,
|
|
475
|
+
sourceArtifactId: input.handoff.sourceArtifactId,
|
|
476
|
+
sourceKind: input.handoff.sourceKind,
|
|
477
|
+
relatedArtifactIds: input.handoff.relatedArtifactIds,
|
|
478
|
+
routeDecisionArtifactIds: input.payload.routeDecisionArtifactIds,
|
|
479
|
+
includedArtifactIds: input.payload.includedArtifactIds,
|
|
480
|
+
comparisonId: input.handoff.comparisonId,
|
|
481
|
+
artifactCount: input.payload.artifactCount,
|
|
482
|
+
sourceBytes: input.payload.sourceBytes,
|
|
483
|
+
archiveBytes: input.archive.byteLength,
|
|
484
|
+
revealIncludedInHandoff: input.handoff.revealIncludedInHandoff,
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
return toSavedComparisonArtifact(descriptor);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export function formatExportPreview(input: {
|
|
491
|
+
readonly sourceKind: 'comparison' | 'judgment';
|
|
492
|
+
readonly sourceArtifactId: string;
|
|
493
|
+
readonly comparisonId: string;
|
|
494
|
+
readonly reveal: boolean;
|
|
495
|
+
}): string {
|
|
496
|
+
return [
|
|
497
|
+
'Agent blind model comparison export preview',
|
|
498
|
+
` source ${input.sourceArtifactId} (${input.sourceKind})`,
|
|
499
|
+
` comparison ${input.comparisonId}`,
|
|
500
|
+
` format markdown`,
|
|
501
|
+
` reveal ${input.reveal ? 'include model identities when available' : 'keep model identities hidden'}`,
|
|
502
|
+
' policy creates one local markdown artifact and does not change model routing',
|
|
503
|
+
].join('\n');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function formatHandoffPreview(input: {
|
|
507
|
+
readonly sourceKind: 'comparison' | 'judgment';
|
|
508
|
+
readonly sourceArtifactId: string;
|
|
509
|
+
readonly comparisonId: string;
|
|
510
|
+
readonly reveal: boolean;
|
|
511
|
+
readonly relatedArtifactIds: readonly string[];
|
|
512
|
+
}): string {
|
|
513
|
+
return [
|
|
514
|
+
'Agent blind model comparison reviewer handoff preview',
|
|
515
|
+
` source ${input.sourceArtifactId} (${input.sourceKind})`,
|
|
516
|
+
` comparison ${input.comparisonId}`,
|
|
517
|
+
` related artifacts ${input.relatedArtifactIds.join(', ') || '(missing)'}`,
|
|
518
|
+
` reveal ${input.reveal ? 'include model identities when available' : 'keep model identities hidden'}`,
|
|
519
|
+
' policy creates one local markdown handoff artifact and does not change model routing',
|
|
520
|
+
].join('\n');
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export function formatHandoffArchivePreview(input: {
|
|
524
|
+
readonly handoff: LoadedComparisonHandoff;
|
|
525
|
+
readonly routeDecisionArtifactIds: readonly string[];
|
|
526
|
+
}): string {
|
|
527
|
+
return [
|
|
528
|
+
'Agent blind model comparison reviewer handoff archive preview',
|
|
529
|
+
` handoff ${input.handoff.artifact.artifactId} (${input.handoff.handoffId})`,
|
|
530
|
+
` comparison ${input.handoff.comparisonId}`,
|
|
531
|
+
` source ${input.handoff.sourceArtifactId} (${input.handoff.sourceKind})`,
|
|
532
|
+
` related artifacts ${input.handoff.relatedArtifactIds.join(', ') || '(none)'}`,
|
|
533
|
+
` route-decision receipts ${input.routeDecisionArtifactIds.join(', ') || '(none)'}`,
|
|
534
|
+
` reveal ${input.handoff.revealIncludedInHandoff ? 'handoff includes model identities when available' : 'handoff keeps model identities hidden'}`,
|
|
535
|
+
' policy creates one local ZIP artifact with exact handoff/source/evidence bytes, redacted manifest metadata, and no model route change',
|
|
536
|
+
].join('\n');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export function formatExportResult(input: {
|
|
540
|
+
readonly sourceKind: 'comparison' | 'judgment';
|
|
541
|
+
readonly sourceArtifactId: string;
|
|
542
|
+
readonly comparisonId: string;
|
|
543
|
+
readonly artifact: SavedComparisonArtifact;
|
|
544
|
+
}): string {
|
|
545
|
+
return [
|
|
546
|
+
`Blind model comparison export saved for ${input.comparisonId}`,
|
|
547
|
+
`source ${input.sourceArtifactId} (${input.sourceKind})`,
|
|
548
|
+
`artifact ${input.artifact.artifactId}${input.artifact.filename ? ` ${input.artifact.filename}` : ''} (${input.artifact.mimeType}, ${input.artifact.sizeBytes} bytes)`,
|
|
549
|
+
'No selected model was changed.',
|
|
550
|
+
].join('\n');
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
export function formatHandoffResult(input: {
|
|
554
|
+
readonly sourceKind: 'comparison' | 'judgment';
|
|
555
|
+
readonly sourceArtifactId: string;
|
|
556
|
+
readonly comparisonId: string;
|
|
557
|
+
readonly relatedArtifactCount: number;
|
|
558
|
+
readonly artifact: SavedComparisonArtifact;
|
|
559
|
+
}): string {
|
|
560
|
+
return [
|
|
561
|
+
`Blind model comparison reviewer handoff saved for ${input.comparisonId}`,
|
|
562
|
+
`source ${input.sourceArtifactId} (${input.sourceKind})`,
|
|
563
|
+
`related artifacts ${input.relatedArtifactCount}`,
|
|
564
|
+
`artifact ${input.artifact.artifactId}${input.artifact.filename ? ` ${input.artifact.filename}` : ''} (${input.artifact.mimeType}, ${input.artifact.sizeBytes} bytes)`,
|
|
565
|
+
`archive agent_model_compare mode:"handoffArchive" artifactId:"${input.artifact.artifactId}" confirm:true explicitUserRequest:"..."`,
|
|
566
|
+
'No selected model was changed.',
|
|
567
|
+
].join('\n');
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export function formatHandoffArchiveResult(input: {
|
|
571
|
+
readonly handoff: LoadedComparisonHandoff;
|
|
572
|
+
readonly artifact: SavedComparisonArtifact;
|
|
573
|
+
readonly artifactCount: number;
|
|
574
|
+
readonly routeDecisionArtifactCount: number;
|
|
575
|
+
readonly sourceBytes: number;
|
|
576
|
+
readonly archiveBytes: number;
|
|
577
|
+
}): string {
|
|
578
|
+
const exportPath = `exports/${input.artifact.filename ?? `${input.artifact.artifactId}.zip`}`;
|
|
579
|
+
return [
|
|
580
|
+
`Blind model comparison reviewer handoff archive saved for ${input.handoff.comparisonId}`,
|
|
581
|
+
`handoff ${input.handoff.artifact.artifactId} (${input.handoff.handoffId})`,
|
|
582
|
+
`source ${input.handoff.sourceArtifactId} (${input.handoff.sourceKind})`,
|
|
583
|
+
`related artifacts ${input.handoff.relatedArtifactIds.length}`,
|
|
584
|
+
`route-decision receipts ${input.routeDecisionArtifactCount}`,
|
|
585
|
+
`included artifacts ${input.artifactCount}`,
|
|
586
|
+
`source bytes ${formatArchiveBytes(input.sourceBytes)}`,
|
|
587
|
+
`archive ${input.artifact.artifactId}${input.artifact.filename ? ` ${input.artifact.filename}` : ''} (${input.artifact.mimeType}, ${input.archiveBytes} bytes)`,
|
|
588
|
+
`export agent_artifacts mode:"export" artifactId:"${input.artifact.artifactId}" destinationPath:"${exportPath}" confirm:true explicitUserRequest:"..."`,
|
|
589
|
+
'policy exact saved artifact bytes packaged; manifest metadata redacted; original artifacts retained',
|
|
590
|
+
'No selected model was changed.',
|
|
591
|
+
].join('\n');
|
|
592
|
+
}
|