@pellux/goodvibes-agent 1.2.0 → 1.4.1
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 +194 -0
- package/dist/package/{ast-grep-napi.linux-x64-gnu-swtppvy9.node → ast-grep-napi.linux-x64-gnu-mkk8xwww.node} +0 -0
- package/dist/package/{ast-grep-napi.linux-x64-musl-ttfcdtap.node → ast-grep-napi.linux-x64-musl-ryqtgdv6.node} +0 -0
- package/dist/package/main.js +65740 -57156
- package/docs/README.md +3 -3
- package/docs/connected-host.md +2 -2
- package/docs/getting-started.md +11 -9
- package/docs/tools-and-commands.md +12 -10
- package/docs/voice-and-live-tts.md +2 -2
- package/package.json +1 -1
- package/release/release-notes.md +15 -144
- package/release/release-readiness.json +36 -36
- package/src/agent/competitive-feature-inventory.ts +42 -44
- package/src/agent/setup-wizard-artifact-receipts.ts +366 -0
- package/src/agent/setup-wizard.ts +267 -24
- package/src/cli/tui-startup.ts +2 -2
- package/src/input/agent-workspace-activation.ts +2 -2
- package/src/input/agent-workspace-categories.ts +2 -2
- package/src/input/agent-workspace-onboarding-categories.ts +4 -4
- package/src/input/agent-workspace-onboarding-finish.ts +21 -0
- package/src/input/agent-workspace-settings.ts +47 -1
- package/src/input/agent-workspace-setup-snapshot.ts +17 -6
- package/src/input/agent-workspace-setup.ts +20 -26
- package/src/input/agent-workspace-snapshot.ts +16 -3
- package/src/input/agent-workspace-types.ts +5 -2
- package/src/input/agent-workspace.ts +21 -6
- package/src/input/handler.ts +67 -5
- package/src/input/setup-wizard-live-receipts.ts +76 -0
- package/src/renderer/agent-workspace-context-lines.ts +17 -33
- package/src/renderer/agent-workspace.ts +434 -21
- package/src/runtime/tool-permission-safety.ts +1 -1
- package/src/tools/agent-harness-agent-orchestration-policy.ts +75 -0
- package/src/tools/agent-harness-agent-orchestration.ts +216 -128
- package/src/tools/agent-harness-autonomy-live-records.ts +154 -0
- package/src/tools/agent-harness-autonomy-queue-types.ts +1 -1
- package/src/tools/agent-harness-autonomy-queue.ts +19 -8
- package/src/tools/agent-harness-autonomy-watcher-read-models.ts +509 -0
- package/src/tools/agent-harness-background-processes.ts +8 -4
- package/src/tools/agent-harness-browser-cockpit-route.ts +188 -34
- package/src/tools/agent-harness-browser-control.ts +12 -4
- package/src/tools/agent-harness-browser-pwa-read-models.ts +622 -0
- package/src/tools/agent-harness-device-live-read-models.ts +366 -0
- package/src/tools/agent-harness-execution-posture.ts +3 -0
- package/src/tools/agent-harness-interactive-runtime-records.ts +421 -0
- package/src/tools/agent-harness-local-model-benchmarks.ts +71 -1
- package/src/tools/agent-harness-local-model-endpoints.ts +469 -354
- package/src/tools/agent-harness-local-model-smoke.ts +277 -0
- package/src/tools/agent-harness-local-model-url.ts +78 -0
- package/src/tools/agent-harness-media-posture.ts +27 -12
- package/src/tools/agent-harness-memory-external-providers.ts +796 -0
- package/src/tools/agent-harness-memory-posture.ts +253 -137
- package/src/tools/agent-harness-memory-provider-certification.ts +219 -0
- package/src/tools/agent-harness-memory-refinement.ts +340 -0
- package/src/tools/agent-harness-mode-catalog.ts +4 -2
- package/src/tools/agent-harness-model-provider-health.ts +139 -42
- package/src/tools/agent-harness-model-readiness.ts +31 -5
- package/src/tools/agent-harness-model-routing-types.ts +61 -0
- package/src/tools/agent-harness-model-routing.ts +31 -6
- package/src/tools/agent-harness-pairing-posture.ts +30 -9
- package/src/tools/agent-harness-personal-ops-certification.ts +116 -0
- package/src/tools/agent-harness-personal-ops-lanes.ts +81 -15
- package/src/tools/agent-harness-personal-ops-operations.ts +225 -0
- package/src/tools/agent-harness-personal-ops-provider-records.ts +358 -0
- package/src/tools/agent-harness-personal-ops-provider-task-records.ts +321 -0
- package/src/tools/agent-harness-personal-ops-records.ts +176 -224
- package/src/tools/agent-harness-personal-ops-types.ts +19 -1
- package/src/tools/agent-harness-personal-ops.ts +18 -11
- package/src/tools/agent-harness-remote-read-models.ts +541 -0
- package/src/tools/agent-harness-research-briefing.ts +26 -7
- package/src/tools/agent-harness-research-live-read-models.ts +500 -0
- package/src/tools/agent-harness-research-runs.ts +15 -3
- package/src/tools/agent-harness-research-workflow.ts +92 -19
- package/src/tools/agent-harness-setup-model-helpers.ts +1 -0
- package/src/tools/agent-harness-setup-posture.ts +7 -7
- package/src/tools/agent-harness-setup-smoke.ts +26 -1
- package/src/tools/agent-harness-tool-schema.ts +23 -1
- package/src/tools/agent-harness-tool-types.ts +5 -0
- package/src/tools/agent-harness-tool.ts +9 -1
- package/src/tools/agent-harness-workspace-actions.ts +1 -1
- package/src/tools/agent-memory-tool.ts +40 -1
- package/src/tools/agent-model-compare-run.ts +13 -0
- package/src/tools/agent-research-runner.ts +367 -0
- package/src/tools/agent-research-tool.ts +7 -179
- package/src/tools/agent-route-planner-candidates-surfaces.ts +1 -1
- package/src/version.ts +1 -1
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import type { ArtifactDescriptor } from '@pellux/goodvibes-sdk/platform/artifacts';
|
|
2
|
+
import type { AgentSetupWizardDurableReceipt, AgentSetupWizardDurableReceiptStatus, AgentSetupWizardReceiptSchemaStatus } from './setup-wizard.ts';
|
|
3
|
+
|
|
4
|
+
interface SetupReceiptInput {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly filename?: string | null;
|
|
7
|
+
readonly createdAt?: unknown;
|
|
8
|
+
readonly metadata: Readonly<Record<string, unknown>>;
|
|
9
|
+
readonly inspectRoute: string | null;
|
|
10
|
+
readonly source?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const SETUP_RECEIPT_PURPOSES = new Set([
|
|
14
|
+
'agent-setup-receipt',
|
|
15
|
+
'goodvibes-setup-receipt',
|
|
16
|
+
'connected-host-setup-receipt',
|
|
17
|
+
'connected-host-service-receipt',
|
|
18
|
+
'connected-host-auth-receipt',
|
|
19
|
+
'connected-host-browser-pwa-receipt',
|
|
20
|
+
'browser-pwa-first-run-receipt',
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const STEP_LABELS: Readonly<Record<string, string>> = {
|
|
24
|
+
'connected-host-readiness': 'Connected host',
|
|
25
|
+
runtime: 'Connected host',
|
|
26
|
+
'connected-host-auth': 'Connected-host auth',
|
|
27
|
+
'install-smoke': 'Install smoke',
|
|
28
|
+
'browser-pwa': 'Browser/PWA',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const SETUP_RECEIPT_SECRET_PATTERNS: readonly [RegExp, string][] = [
|
|
32
|
+
[/("?\b(?:api[-_]?key|apikey|token|secret|password|passwd|credential|authorization)\b"?\s*:\s*)("[^"]*"|'[^']*'|[^\s,}]+)/gi, '$1"<redacted>"'],
|
|
33
|
+
[/\b([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTHORIZATION|BEARER)[A-Z0-9_]*)=("[^"]*"|'[^']*'|[^\s]+)/gi, '$1=<redacted>'],
|
|
34
|
+
[/(\b(?:token|secret|password|passwd|api[-_]?key|apikey|authorization|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,}]+)/gi, '$1<redacted>'],
|
|
35
|
+
[/(Authorization:\s*Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, '$1<redacted>'],
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
function metadataString(metadata: Readonly<Record<string, unknown>>, key: string): string {
|
|
39
|
+
const value = metadata[key];
|
|
40
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function firstMetadataString(metadata: Readonly<Record<string, unknown>>, keys: readonly string[]): string {
|
|
44
|
+
for (const key of keys) {
|
|
45
|
+
const value = metadataString(metadata, key);
|
|
46
|
+
if (value) return value;
|
|
47
|
+
}
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function metadataBoolean(metadata: Readonly<Record<string, unknown>>, key: string): boolean {
|
|
52
|
+
const value = metadata[key];
|
|
53
|
+
if (typeof value === 'boolean') return value;
|
|
54
|
+
if (typeof value === 'string') return ['true', 'yes', '1'].includes(value.trim().toLowerCase());
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function metadataNumber(metadata: Readonly<Record<string, unknown>>, key: string): number | undefined {
|
|
59
|
+
const value = metadata[key];
|
|
60
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
61
|
+
if (typeof value === 'string' && value.trim()) {
|
|
62
|
+
const parsed = Number(value);
|
|
63
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isoFromValue(value: unknown): string | null {
|
|
69
|
+
if (typeof value === 'number' && Number.isFinite(value)) return new Date(value).toISOString();
|
|
70
|
+
if (typeof value !== 'string' || value.trim().length === 0) return null;
|
|
71
|
+
const parsed = Date.parse(value);
|
|
72
|
+
return Number.isNaN(parsed) ? null : new Date(parsed).toISOString();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function receiptRecordedAt(input: SetupReceiptInput): string | null {
|
|
76
|
+
for (const key of ['recordedAt', 'capturedAt', 'completedAt', 'createdAt', 'timestamp']) {
|
|
77
|
+
const value = isoFromValue(input.metadata[key]);
|
|
78
|
+
if (value) return value;
|
|
79
|
+
}
|
|
80
|
+
return isoFromValue(input.createdAt);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeToken(value: string): string {
|
|
84
|
+
return value.trim().toLowerCase().replace(/[_\s]+/g, '-');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeStatus(raw: string): AgentSetupWizardDurableReceiptStatus {
|
|
88
|
+
const value = normalizeToken(raw);
|
|
89
|
+
if (!value) return 'unknown';
|
|
90
|
+
if ([
|
|
91
|
+
'ready',
|
|
92
|
+
'ready-for-user-run',
|
|
93
|
+
'complete',
|
|
94
|
+
'completed',
|
|
95
|
+
'ok',
|
|
96
|
+
'success',
|
|
97
|
+
'succeeded',
|
|
98
|
+
'passed',
|
|
99
|
+
'reachable',
|
|
100
|
+
'authenticated',
|
|
101
|
+
'published',
|
|
102
|
+
'usable',
|
|
103
|
+
'active',
|
|
104
|
+
'running',
|
|
105
|
+
'online',
|
|
106
|
+
'healthy',
|
|
107
|
+
].includes(value)) return 'ready';
|
|
108
|
+
if ([
|
|
109
|
+
'blocked',
|
|
110
|
+
'not-ready',
|
|
111
|
+
'setup-needed',
|
|
112
|
+
'needs-setup',
|
|
113
|
+
'missing',
|
|
114
|
+
'unavailable',
|
|
115
|
+
'unreachable',
|
|
116
|
+
'unauthenticated',
|
|
117
|
+
'pending',
|
|
118
|
+
].includes(value)) return 'blocked';
|
|
119
|
+
if (['failed', 'failure', 'error', 'errored'].includes(value)) return 'failed';
|
|
120
|
+
return 'unknown';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeSchemaStatus(metadata: Readonly<Record<string, unknown>>): AgentSetupWizardReceiptSchemaStatus {
|
|
124
|
+
const explicit = normalizeToken(firstMetadataString(metadata, ['schemaStatus', 'receiptSchemaStatus', 'certificationStatus']));
|
|
125
|
+
if (['certified', 'valid', 'verified', 'schema-certified'].includes(explicit)) return 'certified';
|
|
126
|
+
const schemaVersion = firstMetadataString(metadata, ['schemaVersion', 'receiptSchemaVersion', 'contractVersion']);
|
|
127
|
+
const provenance = firstMetadataString(metadata, ['repairRoute', 'actionRoute', 'methodId', 'actionId', 'sourceTool']);
|
|
128
|
+
const publicationGuarantee = firstMetadataString(metadata, ['publicationGuarantee', 'hostPublicationGuarantee', 'firstRunPublicationGuarantee']);
|
|
129
|
+
return schemaVersion && provenance && publicationGuarantee ? 'certified' : 'legacy';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function normalizeStepId(raw: string): string {
|
|
133
|
+
const value = normalizeToken(raw);
|
|
134
|
+
if (value === 'runtime') return 'connected-host-readiness';
|
|
135
|
+
if (value === 'connected-host' || value === 'connected-host-status' || value === 'service-status' || value === 'services-status') {
|
|
136
|
+
return 'connected-host-readiness';
|
|
137
|
+
}
|
|
138
|
+
if (value === 'connected-host-token' || value === 'operator-token' || value === 'auth') return 'connected-host-auth';
|
|
139
|
+
if (value === 'setup-smoke' || value === 'smoke') return 'install-smoke';
|
|
140
|
+
if (value === 'browser' || value === 'pwa' || value === 'browser-first-run' || value === 'web-cockpit') return 'browser-pwa';
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function inferStepIdFromMetadata(metadata: Readonly<Record<string, unknown>>, filename = ''): string {
|
|
145
|
+
const explicit = firstMetadataString(metadata, ['setupStepId', 'setupItemId', 'stepId', 'wizardStepId']);
|
|
146
|
+
if (explicit) return normalizeStepId(explicit);
|
|
147
|
+
|
|
148
|
+
const evidence = [
|
|
149
|
+
metadataString(metadata, 'receiptKind'),
|
|
150
|
+
metadataString(metadata, 'methodId'),
|
|
151
|
+
metadataString(metadata, 'purpose'),
|
|
152
|
+
metadataString(metadata, 'source'),
|
|
153
|
+
metadataString(metadata, 'label'),
|
|
154
|
+
metadataString(metadata, 'summary'),
|
|
155
|
+
filename,
|
|
156
|
+
].join(' ').toLowerCase();
|
|
157
|
+
|
|
158
|
+
if (/\b(browser-pwa|browser|pwa|web-cockpit)\b/.test(evidence)) return 'browser-pwa';
|
|
159
|
+
if (/\b(install-smoke|setup-smoke|smoke|first-assistant-turn)\b/.test(evidence)) return 'install-smoke';
|
|
160
|
+
if (/\b(connected-host-auth|operator-token|auth|token|authenticated)\b/.test(evidence)) return 'connected-host-auth';
|
|
161
|
+
if (/\b(services?\.status|connected-host-readiness|connected-host-status|service-status|runtime|host-status)\b/.test(evidence)) {
|
|
162
|
+
return 'connected-host-readiness';
|
|
163
|
+
}
|
|
164
|
+
return '';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function isSetupReceiptArtifact(artifact: ArtifactDescriptor): boolean {
|
|
168
|
+
const purpose = metadataString(artifact.metadata, 'purpose');
|
|
169
|
+
return SETUP_RECEIPT_PURPOSES.has(purpose) || metadataBoolean(artifact.metadata, 'setupReceipt');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function isSetupReceiptRecord(metadata: Readonly<Record<string, unknown>>): boolean {
|
|
173
|
+
const purpose = metadataString(metadata, 'purpose');
|
|
174
|
+
if (SETUP_RECEIPT_PURPOSES.has(purpose) || metadataBoolean(metadata, 'setupReceipt')) return true;
|
|
175
|
+
return Boolean(firstMetadataString(metadata, ['setupStepId', 'setupItemId', 'stepId', 'wizardStepId', 'receiptId', 'durableReceiptId', 'eventId']));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function redactSetupReceiptText(value: string): string {
|
|
179
|
+
return SETUP_RECEIPT_SECRET_PATTERNS.reduce((text, [pattern, replacement]) => text.replace(pattern, replacement), value);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function redactedMetadataString(metadata: Readonly<Record<string, unknown>>, key: string): string {
|
|
183
|
+
const value = metadataString(metadata, key);
|
|
184
|
+
return value ? redactSetupReceiptText(value) : '';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function receiptProvenance(metadata: Readonly<Record<string, unknown>>): readonly string[] {
|
|
188
|
+
return [
|
|
189
|
+
redactedMetadataString(metadata, 'methodId') ? `method ${redactedMetadataString(metadata, 'methodId')}` : '',
|
|
190
|
+
redactedMetadataString(metadata, 'actionId') ? `action ${redactedMetadataString(metadata, 'actionId')}` : '',
|
|
191
|
+
redactedMetadataString(metadata, 'repairRoute') ? `repair ${redactedMetadataString(metadata, 'repairRoute')}` : '',
|
|
192
|
+
redactedMetadataString(metadata, 'actionRoute') ? `actionRoute ${redactedMetadataString(metadata, 'actionRoute')}` : '',
|
|
193
|
+
redactedMetadataString(metadata, 'sourceTool') ? `sourceTool ${redactedMetadataString(metadata, 'sourceTool')}` : '',
|
|
194
|
+
].filter(Boolean).slice(0, 8);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function receiptSummary(
|
|
198
|
+
metadata: Readonly<Record<string, unknown>>,
|
|
199
|
+
stepId: string,
|
|
200
|
+
status: AgentSetupWizardDurableReceiptStatus,
|
|
201
|
+
): string {
|
|
202
|
+
return redactSetupReceiptText(firstMetadataString(metadata, ['summary', 'receiptSummary', 'label']))
|
|
203
|
+
|| `${STEP_LABELS[stepId] ?? stepId} durable setup receipt is ${status}.`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function buildSetupWizardDurableReceipt(input: SetupReceiptInput): AgentSetupWizardDurableReceipt | null {
|
|
207
|
+
if (!isSetupReceiptRecord(input.metadata)) return null;
|
|
208
|
+
const stepId = inferStepIdFromMetadata(input.metadata, input.filename ?? '');
|
|
209
|
+
if (!stepId) return null;
|
|
210
|
+
const recordedAt = receiptRecordedAt(input);
|
|
211
|
+
if (!recordedAt) return null;
|
|
212
|
+
const status = normalizeStatus(firstMetadataString(input.metadata, ['receiptStatus', 'status', 'result', 'outcome', 'state']));
|
|
213
|
+
const receiptId = firstMetadataString(input.metadata, ['receiptId', 'durableReceiptId', 'eventId', 'id']) || input.id;
|
|
214
|
+
const inspectRoute = firstMetadataString(input.metadata, ['inspectRoute', 'modelRoute', 'route'])
|
|
215
|
+
|| input.inspectRoute
|
|
216
|
+
|| `setup action:"item" setupItemId:"${stepId}" includeParameters:true`;
|
|
217
|
+
const schemaStatus = normalizeSchemaStatus(input.metadata);
|
|
218
|
+
const schemaVersion = firstMetadataString(input.metadata, ['schemaVersion', 'receiptSchemaVersion', 'contractVersion']);
|
|
219
|
+
const publicationGuarantee = redactedMetadataString(input.metadata, 'publicationGuarantee')
|
|
220
|
+
|| redactedMetadataString(input.metadata, 'hostPublicationGuarantee')
|
|
221
|
+
|| redactedMetadataString(input.metadata, 'firstRunPublicationGuarantee');
|
|
222
|
+
const eventCursor = firstMetadataString(input.metadata, ['eventCursor', 'cursor', 'streamCursor']);
|
|
223
|
+
const eventSequence = metadataNumber(input.metadata, 'eventSequence')
|
|
224
|
+
?? metadataNumber(input.metadata, 'sequence')
|
|
225
|
+
?? metadataNumber(input.metadata, 'streamSequence')
|
|
226
|
+
?? metadataNumber(input.metadata, 'offset');
|
|
227
|
+
const publisher = firstMetadataString(input.metadata, ['publisher', 'publisherId', 'daemonId', 'hostId']);
|
|
228
|
+
const provenance = receiptProvenance(input.metadata);
|
|
229
|
+
return {
|
|
230
|
+
stepId,
|
|
231
|
+
stepLabel: metadataString(input.metadata, 'stepLabel') || STEP_LABELS[stepId],
|
|
232
|
+
status,
|
|
233
|
+
receiptId,
|
|
234
|
+
recordedAt,
|
|
235
|
+
summary: receiptSummary(input.metadata, stepId, status),
|
|
236
|
+
inspectRoute,
|
|
237
|
+
source: input.source || metadataString(input.metadata, 'source') || undefined,
|
|
238
|
+
schemaStatus,
|
|
239
|
+
...(schemaVersion ? { schemaVersion } : {}),
|
|
240
|
+
...(provenance.length > 0 ? { provenance } : {}),
|
|
241
|
+
...(publicationGuarantee ? { publicationGuarantee } : {}),
|
|
242
|
+
...(eventCursor ? { eventCursor } : {}),
|
|
243
|
+
...(eventSequence !== undefined ? { eventSequence } : {}),
|
|
244
|
+
...(publisher ? { publisher } : {}),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function buildSetupWizardDurableReceipts(
|
|
249
|
+
artifacts: readonly ArtifactDescriptor[],
|
|
250
|
+
): readonly AgentSetupWizardDurableReceipt[] {
|
|
251
|
+
return artifacts.flatMap((artifact) => {
|
|
252
|
+
if (!isSetupReceiptArtifact(artifact)) return [];
|
|
253
|
+
const receipt = buildSetupWizardDurableReceipt({
|
|
254
|
+
id: artifact.id,
|
|
255
|
+
filename: artifact.filename,
|
|
256
|
+
createdAt: artifact.createdAt,
|
|
257
|
+
metadata: artifact.metadata,
|
|
258
|
+
inspectRoute: `agent_artifacts show artifactId:"${artifact.id}" includeContent:false`,
|
|
259
|
+
source: metadataString(artifact.metadata, 'source') || metadataString(artifact.metadata, 'purpose') || undefined,
|
|
260
|
+
});
|
|
261
|
+
return receipt ? [receipt] : [];
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function readRecord(value: unknown): Record<string, unknown> {
|
|
266
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function recordsFromSetupReadModelSnapshot(snapshot: unknown, inherited: Readonly<Record<string, unknown>> = {}): readonly unknown[] {
|
|
270
|
+
if (Array.isArray(snapshot)) {
|
|
271
|
+
return snapshot.map((entry, index) => {
|
|
272
|
+
const entryRecord = readRecord(entry);
|
|
273
|
+
if (Object.keys(entryRecord).length === 0) return entry;
|
|
274
|
+
return {
|
|
275
|
+
...inherited,
|
|
276
|
+
...entryRecord,
|
|
277
|
+
...(metadataNumber(entryRecord, 'eventSequence') === undefined && metadataString(inherited, 'eventStream') ? { eventSequence: index } : {}),
|
|
278
|
+
};
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
const record = readRecord(snapshot);
|
|
282
|
+
const inheritedCursor = firstMetadataString(record, ['eventCursor', 'cursor', 'streamCursor']);
|
|
283
|
+
const inheritedSequence = metadataNumber(record, 'eventSequence')
|
|
284
|
+
?? metadataNumber(record, 'sequence')
|
|
285
|
+
?? metadataNumber(record, 'streamSequence')
|
|
286
|
+
?? metadataNumber(record, 'offset');
|
|
287
|
+
const baseInherited = {
|
|
288
|
+
...inherited,
|
|
289
|
+
...(inheritedCursor ? { eventCursor: inheritedCursor } : {}),
|
|
290
|
+
...(inheritedSequence !== undefined ? { eventSequence: inheritedSequence } : {}),
|
|
291
|
+
};
|
|
292
|
+
for (const key of ['setupReceipts', 'receipts', 'durableReceipts', 'receiptEvents', 'setupReceiptEvents', 'eventStream', 'stream', 'events', 'records', 'items', 'entries']) {
|
|
293
|
+
const value = record[key];
|
|
294
|
+
const eventStream = /event|stream/i.test(key);
|
|
295
|
+
const childInherited = eventStream ? { ...baseInherited, eventStream: 'true' } : baseInherited;
|
|
296
|
+
if (Array.isArray(value)) return recordsFromSetupReadModelSnapshot(value, childInherited);
|
|
297
|
+
const valueRecord = readRecord(value);
|
|
298
|
+
if (Object.keys(valueRecord).length > 0) {
|
|
299
|
+
return Object.entries(valueRecord).map(([id, entry]) => {
|
|
300
|
+
const entryRecord = readRecord(entry);
|
|
301
|
+
if (Object.keys(entryRecord).length === 0 || metadataString(entryRecord, 'id') || metadataString(entryRecord, 'receiptId')) return entry;
|
|
302
|
+
return { ...childInherited, ...entryRecord, id };
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return isSetupReceiptRecord(record) ? [{ ...baseInherited, ...record }] : [];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function buildSetupWizardDurableReceiptsFromReadModel(
|
|
310
|
+
snapshot: unknown,
|
|
311
|
+
source: string,
|
|
312
|
+
): readonly AgentSetupWizardDurableReceipt[] {
|
|
313
|
+
return recordsFromSetupReadModelSnapshot(snapshot).flatMap((value) => {
|
|
314
|
+
const record = readRecord(value);
|
|
315
|
+
if (Object.keys(record).length === 0) return [];
|
|
316
|
+
const metadata = {
|
|
317
|
+
...readRecord(record.metadata),
|
|
318
|
+
...record,
|
|
319
|
+
};
|
|
320
|
+
const receipt = buildSetupWizardDurableReceipt({
|
|
321
|
+
id: firstMetadataString(metadata, ['id', 'receiptId', 'durableReceiptId', 'eventId']) || source,
|
|
322
|
+
filename: metadataString(metadata, 'filename') || null,
|
|
323
|
+
createdAt: metadata.createdAt,
|
|
324
|
+
metadata,
|
|
325
|
+
inspectRoute: metadataString(metadata, 'inspectRoute') || null,
|
|
326
|
+
source,
|
|
327
|
+
});
|
|
328
|
+
return receipt ? [receipt] : [];
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function receiptRank(receipt: AgentSetupWizardDurableReceipt): number {
|
|
333
|
+
if (receipt.status === 'ready') return 3;
|
|
334
|
+
if (receipt.status === 'blocked') return 2;
|
|
335
|
+
if (receipt.status === 'failed') return 1;
|
|
336
|
+
return 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function preferReceipt(
|
|
340
|
+
current: AgentSetupWizardDurableReceipt,
|
|
341
|
+
candidate: AgentSetupWizardDurableReceipt,
|
|
342
|
+
): AgentSetupWizardDurableReceipt {
|
|
343
|
+
const rankDelta = receiptRank(candidate) - receiptRank(current);
|
|
344
|
+
if (rankDelta > 0) return candidate;
|
|
345
|
+
if (rankDelta < 0) return current;
|
|
346
|
+
const currentTime = Date.parse(current.recordedAt);
|
|
347
|
+
const candidateTime = Date.parse(candidate.recordedAt);
|
|
348
|
+
if (candidateTime > currentTime) return candidate;
|
|
349
|
+
return current;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function mergeSetupWizardDurableReceipts(
|
|
353
|
+
...receiptLists: readonly (readonly AgentSetupWizardDurableReceipt[])[]
|
|
354
|
+
): readonly AgentSetupWizardDurableReceipt[] {
|
|
355
|
+
const byKey = new Map<string, AgentSetupWizardDurableReceipt>();
|
|
356
|
+
for (const receipt of receiptLists.flat()) {
|
|
357
|
+
const key = `${receipt.stepId}:${receipt.receiptId}`;
|
|
358
|
+
const current = byKey.get(key);
|
|
359
|
+
byKey.set(key, current ? preferReceipt(current, receipt) : receipt);
|
|
360
|
+
}
|
|
361
|
+
return [...byKey.values()].sort((left, right) => {
|
|
362
|
+
const timeDelta = Date.parse(right.recordedAt) - Date.parse(left.recordedAt);
|
|
363
|
+
if (timeDelta !== 0) return timeDelta;
|
|
364
|
+
return left.receiptId.localeCompare(right.receiptId);
|
|
365
|
+
});
|
|
366
|
+
}
|