@pellux/goodvibes-agent 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +159 -0
- package/dist/package/main.js +19016 -12684
- 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 +17 -5
- 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 +245 -9
- package/src/input/agent-workspace-categories.ts +2 -2
- package/src/input/agent-workspace-onboarding-categories.ts +4 -4
- package/src/input/agent-workspace-settings.ts +52 -1
- package/src/input/agent-workspace-setup-snapshot.ts +20 -1
- package/src/input/agent-workspace-setup.ts +32 -5
- package/src/input/agent-workspace-snapshot.ts +23 -3
- package/src/input/agent-workspace.ts +5 -1
- package/src/input/setup-wizard-live-receipts.ts +76 -0
- package/src/renderer/agent-workspace-context-lines.ts +21 -6
- package/src/renderer/agent-workspace.ts +46 -10
- 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-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,500 @@
|
|
|
1
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
2
|
+
import { previewHarnessText } from './agent-harness-text.ts';
|
|
3
|
+
|
|
4
|
+
export interface ResearchLiveRecordCertification {
|
|
5
|
+
readonly schemaStatus: 'certified' | 'legacy';
|
|
6
|
+
readonly schemaVersion?: string;
|
|
7
|
+
readonly publicationGuarantee?: string;
|
|
8
|
+
readonly publisher?: string;
|
|
9
|
+
readonly provenance?: readonly string[];
|
|
10
|
+
readonly receiptId?: string;
|
|
11
|
+
readonly cursor?: string;
|
|
12
|
+
readonly missingSignals: readonly string[];
|
|
13
|
+
readonly policy: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ResearchBrowserRunnerRecord {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
readonly runId: string | null;
|
|
19
|
+
readonly status: string;
|
|
20
|
+
readonly phase: string | null;
|
|
21
|
+
readonly progress: number | null;
|
|
22
|
+
readonly question: string | null;
|
|
23
|
+
readonly currentUrl: string | null;
|
|
24
|
+
readonly sourceReceiptIds: readonly string[];
|
|
25
|
+
readonly reportDraftId: string | null;
|
|
26
|
+
readonly reportArtifactId: string | null;
|
|
27
|
+
readonly logTail: readonly string[];
|
|
28
|
+
readonly controlRoutes: Readonly<Record<string, string>>;
|
|
29
|
+
readonly modelRoute: string;
|
|
30
|
+
readonly sourcePath: string;
|
|
31
|
+
readonly source: 'daemon-read-model' | 'sdk-read-model';
|
|
32
|
+
readonly certification: ResearchLiveRecordCertification;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ResearchVisualReportRecord {
|
|
36
|
+
readonly id: string;
|
|
37
|
+
readonly reportArtifactId: string | null;
|
|
38
|
+
readonly status: string;
|
|
39
|
+
readonly renderRoute: string;
|
|
40
|
+
readonly renderUrl: string | null;
|
|
41
|
+
readonly sections: readonly string[];
|
|
42
|
+
readonly sourceMapCount: number;
|
|
43
|
+
readonly citationCoverage: string | null;
|
|
44
|
+
readonly modelRoute: string;
|
|
45
|
+
readonly sourcePath: string;
|
|
46
|
+
readonly source: 'daemon-read-model' | 'sdk-read-model';
|
|
47
|
+
readonly certification: ResearchLiveRecordCertification;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ResearchLiveReadModelSnapshot {
|
|
51
|
+
readonly browserRunnerRecords: readonly ResearchBrowserRunnerRecord[];
|
|
52
|
+
readonly visualReportRecords: readonly ResearchVisualReportRecord[];
|
|
53
|
+
readonly sourceCounts: Readonly<Record<string, number>>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface SourceCandidate {
|
|
57
|
+
readonly path: string;
|
|
58
|
+
readonly source: unknown;
|
|
59
|
+
readonly kind: 'daemon-read-model' | 'sdk-read-model';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface CollectedRecord {
|
|
63
|
+
readonly path: string;
|
|
64
|
+
readonly kind: 'daemon-read-model' | 'sdk-read-model';
|
|
65
|
+
readonly record: Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const RUNNER_WRAPPER_KEYS = [
|
|
69
|
+
'records',
|
|
70
|
+
'items',
|
|
71
|
+
'runs',
|
|
72
|
+
'researchRuns',
|
|
73
|
+
'browserRuns',
|
|
74
|
+
'browserRunnerRuns',
|
|
75
|
+
'runnerRecords',
|
|
76
|
+
'pageReceipts',
|
|
77
|
+
'sourceReceipts',
|
|
78
|
+
] as const;
|
|
79
|
+
|
|
80
|
+
const VISUAL_WRAPPER_KEYS = [
|
|
81
|
+
'records',
|
|
82
|
+
'items',
|
|
83
|
+
'reports',
|
|
84
|
+
'visualReports',
|
|
85
|
+
'reportSurfaces',
|
|
86
|
+
'reportViews',
|
|
87
|
+
'renderedReports',
|
|
88
|
+
'visualReportPackets',
|
|
89
|
+
] as const;
|
|
90
|
+
|
|
91
|
+
const SNAPSHOT_METHODS = ['getSnapshot', 'snapshot', 'toJSON'] as const;
|
|
92
|
+
const RUNNER_METHODS = ['listBrowserRuns', 'listResearchRuns', 'listRuns', 'listSourceReceipts', 'list'] as const;
|
|
93
|
+
const VISUAL_METHODS = ['listVisualReports', 'listReportSurfaces', 'listReports', 'listRenderedReports', 'list'] as const;
|
|
94
|
+
|
|
95
|
+
function readObject(value: unknown): Record<string, unknown> {
|
|
96
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function readString(value: unknown): string {
|
|
100
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function readNumber(value: unknown): number | null {
|
|
104
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) return null;
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function readMetadata(record: Record<string, unknown>): Record<string, unknown> {
|
|
109
|
+
return readObject(record.metadata);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function nestedString(record: Record<string, unknown>, key: string): string {
|
|
113
|
+
return readString(record[key])
|
|
114
|
+
|| readString(readMetadata(record)[key])
|
|
115
|
+
|| readString(readObject(record.run)[key])
|
|
116
|
+
|| readString(readObject(record.runner)[key])
|
|
117
|
+
|| readString(readObject(record.browser)[key])
|
|
118
|
+
|| readString(readObject(record.report)[key])
|
|
119
|
+
|| readString(readObject(record.visualReport)[key])
|
|
120
|
+
|| readString(readObject(record.render)[key])
|
|
121
|
+
|| readString(readObject(record.evidence)[key]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function certificationRecords(record: Record<string, unknown>): readonly Record<string, unknown>[] {
|
|
125
|
+
return [
|
|
126
|
+
record,
|
|
127
|
+
readMetadata(record),
|
|
128
|
+
readObject(record.schema),
|
|
129
|
+
readObject(record.contract),
|
|
130
|
+
readObject(record.publication),
|
|
131
|
+
readObject(record.receipt),
|
|
132
|
+
readObject(record.certification),
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function firstAcross(records: readonly Record<string, unknown>[], keys: readonly string[]): string {
|
|
137
|
+
for (const record of records) {
|
|
138
|
+
for (const key of keys) {
|
|
139
|
+
const value = readString(record[key]);
|
|
140
|
+
if (value) return value;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return '';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function stringArray(value: unknown): readonly string[] {
|
|
147
|
+
if (Array.isArray(value)) return [...new Set(value.map((entry) => readString(entry)).filter(Boolean))].slice(0, 12);
|
|
148
|
+
const text = readString(value);
|
|
149
|
+
return text ? text.split(',').map((entry) => entry.trim()).filter(Boolean).slice(0, 12) : [];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function objectArray(value: unknown): readonly Record<string, unknown>[] {
|
|
153
|
+
return Array.isArray(value) ? value.map(readObject).filter((entry) => Object.keys(entry).length > 0) : [];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function redactText(value: string): string {
|
|
157
|
+
return value
|
|
158
|
+
.replace(/\b(bearer)\s+[a-z0-9._~+/=-]+/gi, '$1 <redacted>')
|
|
159
|
+
.replace(/\b(token|secret|password|api[_-]?key|authorization)\s*[:=]\s*[^,\s;/]+/gi, '$1=<redacted>');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function safePreview(value: string, limit: number): string {
|
|
163
|
+
return previewHarnessText(redactText(value), limit);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function safeNullablePreview(value: string, limit: number): string | null {
|
|
167
|
+
return value ? safePreview(value, limit) : null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function safeUrl(value: string): string | null {
|
|
171
|
+
if (!value) return null;
|
|
172
|
+
try {
|
|
173
|
+
const url = new URL(value);
|
|
174
|
+
for (const key of [...url.searchParams.keys()]) {
|
|
175
|
+
if (/token|secret|password|authorization|credential|api[-_]?key/i.test(key)) url.searchParams.set(key, '<redacted>');
|
|
176
|
+
}
|
|
177
|
+
return previewHarnessText(url.toString(), 180);
|
|
178
|
+
} catch {
|
|
179
|
+
return safeNullablePreview(value, 180);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function schemaStatus(records: readonly Record<string, unknown>[]): ResearchLiveRecordCertification['schemaStatus'] {
|
|
184
|
+
const explicit = firstAcross(records, ['schemaStatus', 'receiptSchemaStatus', 'certificationStatus']).toLowerCase().replace(/[_\s]+/g, '-');
|
|
185
|
+
if (['certified', 'valid', 'verified', 'schema-certified'].includes(explicit)) return 'certified';
|
|
186
|
+
return firstAcross(records, ['schemaVersion', 'receiptSchemaVersion', 'contractVersion'])
|
|
187
|
+
&& firstAcross(records, ['publicationGuarantee', 'hostPublicationGuarantee', 'researchPublicationGuarantee'])
|
|
188
|
+
&& firstAcross(records, ['publisher', 'publisherId', 'daemonId', 'hostId'])
|
|
189
|
+
? 'certified'
|
|
190
|
+
: 'legacy';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function certification(input: {
|
|
194
|
+
readonly record: Record<string, unknown>;
|
|
195
|
+
readonly sourcePath: string;
|
|
196
|
+
readonly kind: 'browser-backed research run' | 'visual report render';
|
|
197
|
+
readonly durableId: string;
|
|
198
|
+
readonly status: string;
|
|
199
|
+
readonly modelRoute: string;
|
|
200
|
+
readonly hasVisibleControls?: boolean;
|
|
201
|
+
readonly hasSourceReceipts?: boolean;
|
|
202
|
+
readonly hasBoundedLogs?: boolean;
|
|
203
|
+
readonly hasRenderRoute?: boolean;
|
|
204
|
+
readonly hasSourceMap?: boolean;
|
|
205
|
+
readonly hasReportEvidence?: boolean;
|
|
206
|
+
readonly hasVisualSections?: boolean;
|
|
207
|
+
}): ResearchLiveRecordCertification {
|
|
208
|
+
const records = certificationRecords(input.record);
|
|
209
|
+
const currentSchemaStatus = schemaStatus(records);
|
|
210
|
+
const schemaVersion = firstAcross(records, ['schemaVersion', 'receiptSchemaVersion', 'contractVersion']);
|
|
211
|
+
const publicationGuarantee = firstAcross(records, ['publicationGuarantee', 'hostPublicationGuarantee', 'researchPublicationGuarantee']);
|
|
212
|
+
const publisher = firstAcross(records, ['publisher', 'publisherId', 'daemonId', 'hostId']);
|
|
213
|
+
const receiptId = firstAcross(records, ['receiptId', 'runReceiptId', 'sourceReceiptId', 'renderReceiptId', 'reportRenderReceiptId']);
|
|
214
|
+
const cursor = firstAcross(records, ['cursor', 'freshnessCursor', 'sequence', 'checkpoint']);
|
|
215
|
+
const provenance = [...new Set([
|
|
216
|
+
...stringArray(input.record.provenance),
|
|
217
|
+
input.sourcePath ? `source ${input.sourcePath}` : '',
|
|
218
|
+
firstAcross(records, ['methodId']) ? `method ${firstAcross(records, ['methodId'])}` : '',
|
|
219
|
+
firstAcross(records, ['sourceTool']) ? `sourceTool ${firstAcross(records, ['sourceTool'])}` : '',
|
|
220
|
+
].map((entry) => safePreview(entry, 180)).filter(Boolean))].slice(0, 8);
|
|
221
|
+
const missingSignals = [
|
|
222
|
+
...(currentSchemaStatus === 'certified' ? [] : [`Certified ${input.kind} schema is not published.`]),
|
|
223
|
+
...(input.durableId ? [] : [`Durable ${input.kind} id is not published.`]),
|
|
224
|
+
...(input.status ? [] : [`${input.kind} status is not published.`]),
|
|
225
|
+
...(publicationGuarantee ? [] : [`${input.kind} publication guarantee is not published.`]),
|
|
226
|
+
...(publisher ? [] : [`${input.kind} publisher is not published.`]),
|
|
227
|
+
...(provenance.length > 0 ? [] : [`${input.kind} provenance is not published.`]),
|
|
228
|
+
...(cursor ? [] : [`${input.kind} freshness cursor is not published.`]),
|
|
229
|
+
...(input.modelRoute ? [] : [`${input.kind} inspect route is not published.`]),
|
|
230
|
+
...(input.hasVisibleControls === false ? ['Browser-backed research visible checkpoint/pause/resume/cancel controls are not published.'] : []),
|
|
231
|
+
...(input.hasSourceReceipts === false ? ['Browser-backed research source/page receipt ids are not published.'] : []),
|
|
232
|
+
...(input.hasBoundedLogs === false ? ['Browser-backed research bounded redacted log descriptors are not published.'] : []),
|
|
233
|
+
...(input.hasRenderRoute === false ? ['Visual report renderer inspect/open route is not published.'] : []),
|
|
234
|
+
...(input.hasSourceMap === false ? ['Visual report source map or citation coverage is not published.'] : []),
|
|
235
|
+
...(input.hasReportEvidence === false ? ['Visual report artifact/render evidence id is not published.'] : []),
|
|
236
|
+
...(input.hasVisualSections === false ? ['Visual report packet sections are not published.'] : []),
|
|
237
|
+
];
|
|
238
|
+
return {
|
|
239
|
+
schemaStatus: currentSchemaStatus,
|
|
240
|
+
...(schemaVersion ? { schemaVersion: safePreview(schemaVersion, 80) } : {}),
|
|
241
|
+
...(publicationGuarantee ? { publicationGuarantee: safePreview(publicationGuarantee, 220) } : {}),
|
|
242
|
+
...(publisher ? { publisher: safePreview(publisher, 80) } : {}),
|
|
243
|
+
...(provenance.length > 0 ? { provenance } : {}),
|
|
244
|
+
...(receiptId ? { receiptId: safePreview(receiptId, 96) } : {}),
|
|
245
|
+
...(cursor ? { cursor: safePreview(cursor, 96) } : {}),
|
|
246
|
+
missingSignals,
|
|
247
|
+
policy: 'Research live read models certify release readiness only when the SDK or daemon publishes schema, durable ids, publication guarantee, publisher/provenance, freshness cursor, exact inspect route, redacted source/render evidence, and user-visible controls without exposing page secrets.',
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function callMethod(source: Record<string, unknown>, method: string): unknown {
|
|
252
|
+
const fn = source[method];
|
|
253
|
+
if (typeof fn !== 'function') return undefined;
|
|
254
|
+
try {
|
|
255
|
+
return (fn as () => unknown).call(source);
|
|
256
|
+
} catch {
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function collectFromSource(
|
|
262
|
+
source: unknown,
|
|
263
|
+
path: string,
|
|
264
|
+
kind: 'daemon-read-model' | 'sdk-read-model',
|
|
265
|
+
wrapperKeys: readonly string[],
|
|
266
|
+
methods: readonly string[],
|
|
267
|
+
visited = new WeakSet<object>(),
|
|
268
|
+
): readonly CollectedRecord[] {
|
|
269
|
+
if (!source) return [];
|
|
270
|
+
if (Array.isArray(source)) {
|
|
271
|
+
return source.flatMap((entry, index) => collectFromSource(entry, `${path}[${index}]`, kind, wrapperKeys, methods, visited));
|
|
272
|
+
}
|
|
273
|
+
if (source instanceof Map) {
|
|
274
|
+
return Array.from(source.entries()).flatMap(([key, value]) => collectFromSource(value, `${path}.${String(key)}`, kind, wrapperKeys, methods, visited));
|
|
275
|
+
}
|
|
276
|
+
if (typeof source !== 'object') return [];
|
|
277
|
+
if (visited.has(source)) return [];
|
|
278
|
+
visited.add(source);
|
|
279
|
+
const record = source as Record<string, unknown>;
|
|
280
|
+
const fromSnapshots = SNAPSHOT_METHODS.flatMap((method) => {
|
|
281
|
+
const snapshot = callMethod(record, method);
|
|
282
|
+
return snapshot === undefined ? [] : collectFromSource(snapshot, `${path}.${method}()`, kind, wrapperKeys, methods, visited);
|
|
283
|
+
});
|
|
284
|
+
const fromMethods = methods.flatMap((method) => {
|
|
285
|
+
const snapshot = callMethod(record, method);
|
|
286
|
+
return snapshot === undefined ? [] : collectFromSource(snapshot, `${path}.${method}()`, kind, wrapperKeys, methods, visited);
|
|
287
|
+
});
|
|
288
|
+
const fromWrappers = wrapperKeys.flatMap((key) => {
|
|
289
|
+
if (!(key in record)) return [];
|
|
290
|
+
return collectFromSource(record[key], `${path}.${key}`, kind, wrapperKeys, methods, visited);
|
|
291
|
+
});
|
|
292
|
+
if (fromSnapshots.length > 0 || fromMethods.length > 0 || fromWrappers.length > 0) {
|
|
293
|
+
return [...fromSnapshots, ...fromMethods, ...fromWrappers];
|
|
294
|
+
}
|
|
295
|
+
return [{ path, kind, record }];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function sourceReceiptIds(record: Record<string, unknown>): readonly string[] {
|
|
299
|
+
const receiptObjects = [
|
|
300
|
+
...objectArray(record.sourceReceipts),
|
|
301
|
+
...objectArray(record.pageReceipts),
|
|
302
|
+
...objectArray(readObject(record.evidence).sourceReceipts),
|
|
303
|
+
];
|
|
304
|
+
return [...new Set([
|
|
305
|
+
...stringArray(record.sourceReceiptIds),
|
|
306
|
+
...stringArray(record.pageReceiptIds),
|
|
307
|
+
...stringArray(readObject(record.evidence).sourceReceiptIds),
|
|
308
|
+
...receiptObjects.map((entry) => readString(entry.receiptId) || readString(entry.id) || readString(entry.sourceReceiptId)),
|
|
309
|
+
].map((entry) => safePreview(entry, 96)).filter(Boolean))].slice(0, 12);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function routeMap(record: Record<string, unknown>): Readonly<Record<string, string>> {
|
|
313
|
+
const routes = { ...readObject(record.routes), ...readObject(record.controls) };
|
|
314
|
+
const output: Record<string, string> = {};
|
|
315
|
+
for (const key of ['inspect', 'checkpoint', 'pause', 'resume', 'cancel', 'report', 'saveReport', 'open', 'view']) {
|
|
316
|
+
const value = readString(routes[key]) || nestedString(record, `${key}Route`);
|
|
317
|
+
if (value) output[key] = safePreview(value, 180);
|
|
318
|
+
}
|
|
319
|
+
return output;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function modelRoute(record: Record<string, unknown>, fallback: string): string {
|
|
323
|
+
return nestedString(record, 'modelRoute')
|
|
324
|
+
|| nestedString(record, 'inspectRoute')
|
|
325
|
+
|| nestedString(record, 'reviewRoute')
|
|
326
|
+
|| nestedString(record, 'openRoute')
|
|
327
|
+
|| fallback;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function normalizeRunner(entry: CollectedRecord, index: number): ResearchBrowserRunnerRecord | null {
|
|
331
|
+
const runId = nestedString(entry.record, 'runId') || nestedString(entry.record, 'researchRunId');
|
|
332
|
+
const question = nestedString(entry.record, 'question') || nestedString(entry.record, 'task') || nestedString(entry.record, 'title');
|
|
333
|
+
const status = nestedString(entry.record, 'status') || nestedString(entry.record, 'state') || 'unknown';
|
|
334
|
+
const id = nestedString(entry.record, 'id') || nestedString(entry.record, 'receiptId') || (runId ? `research-runner:${runId}` : `research-runner:${index}`);
|
|
335
|
+
const routes = routeMap(entry.record);
|
|
336
|
+
const receipts = sourceReceiptIds(entry.record);
|
|
337
|
+
const logs = [
|
|
338
|
+
...stringArray(entry.record.logTail),
|
|
339
|
+
...stringArray(entry.record.boundedLogs),
|
|
340
|
+
...stringArray(entry.record.outputChunks),
|
|
341
|
+
].map((line) => safePreview(line, 180)).slice(0, 5);
|
|
342
|
+
const route = modelRoute(entry.record, runId ? `research action:"runner" runId:"${runId}" includeParameters:true` : 'research action:"runner" includeParameters:true');
|
|
343
|
+
return {
|
|
344
|
+
id,
|
|
345
|
+
runId: runId || null,
|
|
346
|
+
status,
|
|
347
|
+
phase: safeNullablePreview(nestedString(entry.record, 'phase'), 80),
|
|
348
|
+
progress: readNumber(entry.record.progress ?? readObject(entry.record.run).progress),
|
|
349
|
+
question: safeNullablePreview(question, 180),
|
|
350
|
+
currentUrl: safeUrl(nestedString(entry.record, 'currentUrl') || nestedString(entry.record, 'url')),
|
|
351
|
+
sourceReceiptIds: receipts,
|
|
352
|
+
reportDraftId: safeNullablePreview(nestedString(entry.record, 'reportDraftId'), 96),
|
|
353
|
+
reportArtifactId: safeNullablePreview(nestedString(entry.record, 'reportArtifactId'), 96),
|
|
354
|
+
logTail: logs,
|
|
355
|
+
controlRoutes: routes,
|
|
356
|
+
modelRoute: route,
|
|
357
|
+
sourcePath: entry.path,
|
|
358
|
+
source: entry.kind,
|
|
359
|
+
certification: certification({
|
|
360
|
+
record: entry.record,
|
|
361
|
+
sourcePath: entry.path,
|
|
362
|
+
kind: 'browser-backed research run',
|
|
363
|
+
durableId: id,
|
|
364
|
+
status,
|
|
365
|
+
modelRoute: route,
|
|
366
|
+
hasVisibleControls: ['checkpoint', 'pause', 'resume', 'cancel'].some((key) => Boolean(routes[key])),
|
|
367
|
+
hasSourceReceipts: receipts.length > 0,
|
|
368
|
+
hasBoundedLogs: logs.length > 0 || Boolean(nestedString(entry.record, 'boundedLogDescriptor') || nestedString(entry.record, 'redaction')),
|
|
369
|
+
}),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function visualSections(record: Record<string, unknown>): readonly string[] {
|
|
374
|
+
return [...new Set([
|
|
375
|
+
...stringArray(record.sections),
|
|
376
|
+
...stringArray(readObject(record.visualReport).sections),
|
|
377
|
+
...objectArray(record.packetSections).map((entry) => readString(entry.id) || readString(entry.title)),
|
|
378
|
+
].map((entry) => safePreview(entry, 80)).filter(Boolean))].slice(0, 16);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function sourceMapCount(record: Record<string, unknown>): number {
|
|
382
|
+
const explicit = readNumber(record.sourceMapCount ?? readObject(record.visualReport).sourceMapCount);
|
|
383
|
+
if (explicit !== null) return Math.max(0, Math.trunc(explicit));
|
|
384
|
+
const arrays: readonly unknown[] = [record.sourceMap, record.sourceIds, record.citations, readObject(record.visualReport).sourceMap];
|
|
385
|
+
return arrays.reduce<number>((total, value) => total + (Array.isArray(value) ? value.length : 0), 0);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function normalizeVisualReport(entry: CollectedRecord, index: number): ResearchVisualReportRecord | null {
|
|
389
|
+
const reportArtifactId = nestedString(entry.record, 'reportArtifactId') || nestedString(entry.record, 'artifactId');
|
|
390
|
+
const status = nestedString(entry.record, 'status') || nestedString(entry.record, 'state') || 'unknown';
|
|
391
|
+
const id = nestedString(entry.record, 'id') || nestedString(entry.record, 'renderReceiptId') || (reportArtifactId ? `visual-report:${reportArtifactId}` : `visual-report:${index}`);
|
|
392
|
+
const route = modelRoute(entry.record, reportArtifactId ? `research action:"report_artifact" artifactId:"${reportArtifactId}"` : 'research action:"reports" includeParameters:true');
|
|
393
|
+
const renderRoute = nestedString(entry.record, 'renderRoute') || nestedString(entry.record, 'openRoute') || route;
|
|
394
|
+
const sections = visualSections(entry.record);
|
|
395
|
+
const mapCount = sourceMapCount(entry.record);
|
|
396
|
+
return {
|
|
397
|
+
id,
|
|
398
|
+
reportArtifactId: safeNullablePreview(reportArtifactId, 96),
|
|
399
|
+
status,
|
|
400
|
+
renderRoute: safePreview(renderRoute, 180),
|
|
401
|
+
renderUrl: safeUrl(nestedString(entry.record, 'renderUrl') || nestedString(entry.record, 'viewUrl')),
|
|
402
|
+
sections,
|
|
403
|
+
sourceMapCount: mapCount,
|
|
404
|
+
citationCoverage: safeNullablePreview(nestedString(entry.record, 'citationCoverage') || nestedString(entry.record, 'coverage'), 120),
|
|
405
|
+
modelRoute: route,
|
|
406
|
+
sourcePath: entry.path,
|
|
407
|
+
source: entry.kind,
|
|
408
|
+
certification: certification({
|
|
409
|
+
record: entry.record,
|
|
410
|
+
sourcePath: entry.path,
|
|
411
|
+
kind: 'visual report render',
|
|
412
|
+
durableId: id,
|
|
413
|
+
status,
|
|
414
|
+
modelRoute: route,
|
|
415
|
+
hasRenderRoute: Boolean(renderRoute),
|
|
416
|
+
hasSourceMap: mapCount > 0 || Boolean(nestedString(entry.record, 'citationCoverage') || nestedString(entry.record, 'coverage')),
|
|
417
|
+
hasReportEvidence: Boolean(reportArtifactId || nestedString(entry.record, 'renderArtifactId')),
|
|
418
|
+
hasVisualSections: sections.length > 0,
|
|
419
|
+
}),
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function dedupeById<T extends { readonly id: string }>(records: readonly T[]): readonly T[] {
|
|
424
|
+
const seen = new Set<string>();
|
|
425
|
+
const output: T[] = [];
|
|
426
|
+
for (const record of records) {
|
|
427
|
+
if (seen.has(record.id)) continue;
|
|
428
|
+
seen.add(record.id);
|
|
429
|
+
output.push(record);
|
|
430
|
+
}
|
|
431
|
+
return output;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function sourceCounts(entries: readonly CollectedRecord[]): Readonly<Record<string, number>> {
|
|
435
|
+
const counts: Record<string, number> = {};
|
|
436
|
+
for (const entry of entries) counts[entry.path] = (counts[entry.path] ?? 0) + 1;
|
|
437
|
+
return counts;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function runnerSources(context: CommandContext): readonly SourceCandidate[] {
|
|
441
|
+
const platform = readObject(context.platform);
|
|
442
|
+
const readModels = readObject(platform.readModels);
|
|
443
|
+
const research = readObject(readModels.research);
|
|
444
|
+
const deepResearch = readObject(readModels.deepResearch);
|
|
445
|
+
const opsResearch = readObject(readObject(context.ops).research);
|
|
446
|
+
const clients = readObject(context.clients);
|
|
447
|
+
const operator = readObject(clients.operator);
|
|
448
|
+
const operatorSdk = readObject(clients.operatorSdk);
|
|
449
|
+
return [
|
|
450
|
+
{ path: 'context.platform.readModels.research.browserRuns', source: research.browserRuns, kind: 'daemon-read-model' },
|
|
451
|
+
{ path: 'context.platform.readModels.research.browserRunner', source: research.browserRunner, kind: 'daemon-read-model' },
|
|
452
|
+
{ path: 'context.platform.readModels.research.runs', source: research.runs, kind: 'daemon-read-model' },
|
|
453
|
+
{ path: 'context.platform.readModels.deepResearch.browserRuns', source: deepResearch.browserRuns, kind: 'daemon-read-model' },
|
|
454
|
+
{ path: 'context.platform.readModels.researchBrowserRuns', source: readModels.researchBrowserRuns, kind: 'daemon-read-model' },
|
|
455
|
+
{ path: 'context.ops.research.browserRuns', source: opsResearch.browserRuns, kind: 'sdk-read-model' },
|
|
456
|
+
{ path: 'context.clients.operator.research.browserRuns', source: readObject(operator.research).browserRuns, kind: 'sdk-read-model' },
|
|
457
|
+
{ path: 'context.clients.operatorSdk.research.browserRuns', source: readObject(operatorSdk.research).browserRuns, kind: 'sdk-read-model' },
|
|
458
|
+
];
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function visualSources(context: CommandContext): readonly SourceCandidate[] {
|
|
462
|
+
const platform = readObject(context.platform);
|
|
463
|
+
const readModels = readObject(platform.readModels);
|
|
464
|
+
const research = readObject(readModels.research);
|
|
465
|
+
const deepResearch = readObject(readModels.deepResearch);
|
|
466
|
+
const opsResearch = readObject(readObject(context.ops).research);
|
|
467
|
+
const clients = readObject(context.clients);
|
|
468
|
+
const operator = readObject(clients.operator);
|
|
469
|
+
const operatorSdk = readObject(clients.operatorSdk);
|
|
470
|
+
return [
|
|
471
|
+
{ path: 'context.platform.readModels.research.visualReports', source: research.visualReports, kind: 'daemon-read-model' },
|
|
472
|
+
{ path: 'context.platform.readModels.research.reportSurfaces', source: research.reportSurfaces, kind: 'daemon-read-model' },
|
|
473
|
+
{ path: 'context.platform.readModels.deepResearch.visualReports', source: deepResearch.visualReports, kind: 'daemon-read-model' },
|
|
474
|
+
{ path: 'context.platform.readModels.researchVisualReports', source: readModels.researchVisualReports, kind: 'daemon-read-model' },
|
|
475
|
+
{ path: 'context.ops.research.visualReports', source: opsResearch.visualReports, kind: 'sdk-read-model' },
|
|
476
|
+
{ path: 'context.clients.operator.research.visualReports', source: readObject(operator.research).visualReports, kind: 'sdk-read-model' },
|
|
477
|
+
{ path: 'context.clients.operatorSdk.research.visualReports', source: readObject(operatorSdk.research).visualReports, kind: 'sdk-read-model' },
|
|
478
|
+
];
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function isCertifiedResearchLiveRecord(record: { readonly certification: ResearchLiveRecordCertification }): boolean {
|
|
482
|
+
return record.certification.schemaStatus === 'certified' && record.certification.missingSignals.length === 0;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export function researchLiveReadModelSnapshot(context: CommandContext): ResearchLiveReadModelSnapshot {
|
|
486
|
+
const runnerEntries = runnerSources(context).flatMap((candidate) =>
|
|
487
|
+
collectFromSource(candidate.source, candidate.path, candidate.kind, RUNNER_WRAPPER_KEYS, RUNNER_METHODS)
|
|
488
|
+
);
|
|
489
|
+
const visualEntries = visualSources(context).flatMap((candidate) =>
|
|
490
|
+
collectFromSource(candidate.source, candidate.path, candidate.kind, VISUAL_WRAPPER_KEYS, VISUAL_METHODS)
|
|
491
|
+
);
|
|
492
|
+
return {
|
|
493
|
+
browserRunnerRecords: dedupeById(runnerEntries.map(normalizeRunner).filter((entry): entry is ResearchBrowserRunnerRecord => entry !== null)),
|
|
494
|
+
visualReportRecords: dedupeById(visualEntries.map(normalizeVisualReport).filter((entry): entry is ResearchVisualReportRecord => entry !== null)),
|
|
495
|
+
sourceCounts: {
|
|
496
|
+
...sourceCounts(runnerEntries),
|
|
497
|
+
...sourceCounts(visualEntries),
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type AgentResearchRunStatus,
|
|
8
8
|
} from '../agent/research-run-registry.ts';
|
|
9
9
|
import { browserControlPosture } from './agent-harness-browser-control.ts';
|
|
10
|
+
import { isCertifiedResearchLiveRecord, researchLiveReadModelSnapshot } from './agent-harness-research-live-read-models.ts';
|
|
10
11
|
import { previewHarnessText } from './agent-harness-text.ts';
|
|
11
12
|
|
|
12
13
|
interface AgentHarnessResearchRunArgs {
|
|
@@ -183,14 +184,19 @@ function describeRunItem(item: ResearchRunItem, includeParameters: boolean, look
|
|
|
183
184
|
|
|
184
185
|
function researchRunnerPosture(context: CommandContext, includeParameters: boolean): Record<string, unknown> {
|
|
185
186
|
const browser = browserControlPosture(context);
|
|
187
|
+
const liveResearch = researchLiveReadModelSnapshot(context);
|
|
188
|
+
const certifiedRunnerRecords = liveResearch.browserRunnerRecords.filter(isCertifiedResearchLiveRecord);
|
|
189
|
+
const certifiedVisualReports = liveResearch.visualReportRecords.filter(isCertifiedResearchLiveRecord);
|
|
186
190
|
return {
|
|
187
191
|
browserBackedResearch: {
|
|
188
|
-
status: browser.status,
|
|
189
|
-
configured: browser.configured,
|
|
192
|
+
status: certifiedRunnerRecords.length > 0 ? 'certified-live-runner' : browser.status,
|
|
193
|
+
configured: browser.configured || certifiedRunnerRecords.length > 0,
|
|
190
194
|
needsReview: browser.needsReview,
|
|
191
|
-
recommendedRoute: browser.recommendedRoute,
|
|
195
|
+
recommendedRoute: certifiedRunnerRecords[0]?.modelRoute ?? browser.recommendedRoute,
|
|
192
196
|
setupRoute: browser.setupRoute,
|
|
193
197
|
fallbackRoutes: browser.fallbackRoutes,
|
|
198
|
+
liveRecords: liveResearch.browserRunnerRecords.slice(0, includeParameters ? 8 : 3),
|
|
199
|
+
certifiedLiveRecords: certifiedRunnerRecords.slice(0, includeParameters ? 8 : 3),
|
|
194
200
|
workflows: browser.workflows.map((workflow) => ({
|
|
195
201
|
id: workflow.id,
|
|
196
202
|
label: workflow.label,
|
|
@@ -200,6 +206,12 @@ function researchRunnerPosture(context: CommandContext, includeParameters: boole
|
|
|
200
206
|
safety: previewHarnessText(workflow.safety, includeParameters ? 180 : 96),
|
|
201
207
|
})),
|
|
202
208
|
},
|
|
209
|
+
visualReportRendering: {
|
|
210
|
+
status: certifiedVisualReports.length > 0 ? 'certified-live-renderer' : 'artifact-packet-only',
|
|
211
|
+
liveRecords: liveResearch.visualReportRecords.slice(0, includeParameters ? 8 : 3),
|
|
212
|
+
certifiedLiveRecords: certifiedVisualReports.slice(0, includeParameters ? 8 : 3),
|
|
213
|
+
route: certifiedVisualReports[0]?.modelRoute ?? 'research action:"reports"',
|
|
214
|
+
},
|
|
203
215
|
sourceQueueRoute: 'research action:"sources"',
|
|
204
216
|
sourceReviewRoute: 'research action:"review_source" confirm:true explicitUserRequest:"..."',
|
|
205
217
|
reportRoute: 'research action:"report" confirm:true explicitUserRequest:"..."',
|