@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,541 @@
|
|
|
1
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
2
|
+
import { previewHarnessText } from './agent-harness-text.ts';
|
|
3
|
+
|
|
4
|
+
export interface RemoteCaptureOutcomeRecord {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly runnerId: string;
|
|
7
|
+
readonly status: string;
|
|
8
|
+
readonly kind: string;
|
|
9
|
+
readonly task: string | null;
|
|
10
|
+
readonly summary: string | null;
|
|
11
|
+
readonly captureId: string | null;
|
|
12
|
+
readonly exportId: string | null;
|
|
13
|
+
readonly artifactId: string | null;
|
|
14
|
+
readonly sourcePath: string;
|
|
15
|
+
readonly source: 'daemon-read-model' | 'sdk-read-model';
|
|
16
|
+
readonly redaction: string;
|
|
17
|
+
readonly startedAt: string | null;
|
|
18
|
+
readonly completedAt: string | null;
|
|
19
|
+
readonly modelRoute: string;
|
|
20
|
+
readonly certification: RemoteRuntimeRecordCertification;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface RemoteWorkspaceEvidenceRecord {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly runnerId: string;
|
|
26
|
+
readonly status: string;
|
|
27
|
+
readonly workspaceId: string | null;
|
|
28
|
+
readonly isolationKind: string;
|
|
29
|
+
readonly label: string | null;
|
|
30
|
+
readonly worktreeRef: string | null;
|
|
31
|
+
readonly branch: string | null;
|
|
32
|
+
readonly sourcePath: string;
|
|
33
|
+
readonly source: 'daemon-read-model' | 'sdk-read-model';
|
|
34
|
+
readonly redaction: string;
|
|
35
|
+
readonly modelRoute: string;
|
|
36
|
+
readonly certification: RemoteRuntimeRecordCertification;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface RemoteReadModelSnapshot {
|
|
40
|
+
readonly outcomes: readonly RemoteCaptureOutcomeRecord[];
|
|
41
|
+
readonly workspaces: readonly RemoteWorkspaceEvidenceRecord[];
|
|
42
|
+
readonly sourceCounts: Readonly<Record<string, number>>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface RemoteRuntimeRecordCertification {
|
|
46
|
+
readonly schemaStatus: 'certified' | 'legacy';
|
|
47
|
+
readonly schemaVersion?: string;
|
|
48
|
+
readonly publicationGuarantee?: string;
|
|
49
|
+
readonly publisher?: string;
|
|
50
|
+
readonly provenance?: readonly string[];
|
|
51
|
+
readonly receiptId?: string;
|
|
52
|
+
readonly cursor?: string;
|
|
53
|
+
readonly missingSignals: readonly string[];
|
|
54
|
+
readonly policy: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface SourceCandidate {
|
|
58
|
+
readonly path: string;
|
|
59
|
+
readonly source: unknown;
|
|
60
|
+
readonly kind: 'daemon-read-model' | 'sdk-read-model';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface CollectedRecord {
|
|
64
|
+
readonly path: string;
|
|
65
|
+
readonly kind: 'daemon-read-model' | 'sdk-read-model';
|
|
66
|
+
readonly record: Record<string, unknown>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const OUTCOME_WRAPPER_KEYS = [
|
|
70
|
+
'records',
|
|
71
|
+
'items',
|
|
72
|
+
'outcomes',
|
|
73
|
+
'captureOutcomes',
|
|
74
|
+
'captures',
|
|
75
|
+
'captureRecords',
|
|
76
|
+
'exports',
|
|
77
|
+
'exportOutcomes',
|
|
78
|
+
'closeouts',
|
|
79
|
+
'closeoutOutcomes',
|
|
80
|
+
'artifacts',
|
|
81
|
+
] as const;
|
|
82
|
+
|
|
83
|
+
const WORKSPACE_WRAPPER_KEYS = [
|
|
84
|
+
'records',
|
|
85
|
+
'items',
|
|
86
|
+
'workspaces',
|
|
87
|
+
'workspaceRecords',
|
|
88
|
+
'workspaceIsolation',
|
|
89
|
+
'workspaceEvidence',
|
|
90
|
+
'worktrees',
|
|
91
|
+
'worktreeRecords',
|
|
92
|
+
] as const;
|
|
93
|
+
|
|
94
|
+
const SNAPSHOT_METHODS = [
|
|
95
|
+
'getSnapshot',
|
|
96
|
+
'snapshot',
|
|
97
|
+
'toJSON',
|
|
98
|
+
] as const;
|
|
99
|
+
|
|
100
|
+
const OUTCOME_METHODS = [
|
|
101
|
+
'listCaptureOutcomes',
|
|
102
|
+
'listOutcomes',
|
|
103
|
+
'listCaptures',
|
|
104
|
+
'listExports',
|
|
105
|
+
'listCloseouts',
|
|
106
|
+
'listArtifacts',
|
|
107
|
+
'list',
|
|
108
|
+
] as const;
|
|
109
|
+
|
|
110
|
+
const WORKSPACE_METHODS = [
|
|
111
|
+
'listWorkspaceEvidence',
|
|
112
|
+
'listWorkspaceIsolation',
|
|
113
|
+
'listWorkspaces',
|
|
114
|
+
'listWorktrees',
|
|
115
|
+
'list',
|
|
116
|
+
] as const;
|
|
117
|
+
|
|
118
|
+
function readObject(value: unknown): Record<string, unknown> {
|
|
119
|
+
return value && typeof value === 'object' ? value as Record<string, unknown> : {};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function readString(value: unknown): string {
|
|
123
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function readNumber(value: unknown): number {
|
|
127
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function readMetadata(record: Record<string, unknown>): Record<string, unknown> {
|
|
131
|
+
return readObject(record.metadata);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function certificationRecords(record: Record<string, unknown>): readonly Record<string, unknown>[] {
|
|
135
|
+
return [
|
|
136
|
+
record,
|
|
137
|
+
readMetadata(record),
|
|
138
|
+
readObject(record.schema),
|
|
139
|
+
readObject(record.contract),
|
|
140
|
+
readObject(record.publication),
|
|
141
|
+
readObject(record.receipt),
|
|
142
|
+
readObject(record.certification),
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function nestedString(record: Record<string, unknown>, key: string): string {
|
|
147
|
+
return readString(record[key])
|
|
148
|
+
|| readString(readMetadata(record)[key])
|
|
149
|
+
|| readString(readObject(record.task)[key])
|
|
150
|
+
|| readString(readObject(record.evidence)[key])
|
|
151
|
+
|| readString(readObject(record.workspace)[key]);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function firstAcross(records: readonly Record<string, unknown>[], keys: readonly string[]): string {
|
|
155
|
+
for (const record of records) {
|
|
156
|
+
for (const key of keys) {
|
|
157
|
+
const value = readString(record[key]);
|
|
158
|
+
if (value) return value;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return '';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function stringArray(value: unknown): readonly string[] {
|
|
165
|
+
if (Array.isArray(value)) return [...new Set(value.map((entry) => readString(entry)).filter(Boolean))].slice(0, 10);
|
|
166
|
+
const text = readString(value);
|
|
167
|
+
return text ? text.split(',').map((entry) => entry.trim()).filter(Boolean).slice(0, 10) : [];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function redactText(value: string): string {
|
|
171
|
+
return value
|
|
172
|
+
.replace(/\b(bearer)\s+[a-z0-9._~+/=-]+/gi, '$1 <redacted>')
|
|
173
|
+
.replace(/\b(token|secret|password|api[_-]?key|authorization)\s*[:=]\s*[^,\s;/]+/gi, '$1=<redacted>');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function safePreview(value: string, limit: number): string {
|
|
177
|
+
return previewHarnessText(redactText(value), limit);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function safeNullablePreview(value: string, limit: number): string | null {
|
|
181
|
+
return value ? safePreview(value, limit) : null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function certificationSchemaStatus(records: readonly Record<string, unknown>[]): RemoteRuntimeRecordCertification['schemaStatus'] {
|
|
185
|
+
const explicit = firstAcross(records, ['schemaStatus', 'receiptSchemaStatus', 'certificationStatus']).toLowerCase().replace(/[_\s]+/g, '-');
|
|
186
|
+
if (['certified', 'valid', 'verified', 'schema-certified'].includes(explicit)) return 'certified';
|
|
187
|
+
return firstAcross(records, ['schemaVersion', 'receiptSchemaVersion', 'contractVersion'])
|
|
188
|
+
&& firstAcross(records, ['publicationGuarantee', 'hostPublicationGuarantee', 'remoteRuntimePublicationGuarantee'])
|
|
189
|
+
&& firstAcross(records, ['publisher', 'publisherId', 'daemonId', 'hostId', 'methodId', 'sourceTool'])
|
|
190
|
+
? 'certified'
|
|
191
|
+
: 'legacy';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function remoteCertification(input: {
|
|
195
|
+
readonly record: Record<string, unknown>;
|
|
196
|
+
readonly sourcePath: string;
|
|
197
|
+
readonly recordKind: 'remote outcome' | 'remote workspace';
|
|
198
|
+
readonly durableId: string;
|
|
199
|
+
readonly runnerId: string;
|
|
200
|
+
readonly modelRoute: string;
|
|
201
|
+
readonly hasOutcomeEvidence?: boolean;
|
|
202
|
+
readonly hasWorkspaceEvidence?: boolean;
|
|
203
|
+
readonly status: string;
|
|
204
|
+
}): RemoteRuntimeRecordCertification {
|
|
205
|
+
const records = certificationRecords(input.record);
|
|
206
|
+
const schemaStatus = certificationSchemaStatus(records);
|
|
207
|
+
const schemaVersion = firstAcross(records, ['schemaVersion', 'receiptSchemaVersion', 'contractVersion']);
|
|
208
|
+
const publicationGuarantee = firstAcross(records, ['publicationGuarantee', 'hostPublicationGuarantee', 'remoteRuntimePublicationGuarantee']);
|
|
209
|
+
const publisher = firstAcross(records, ['publisher', 'publisherId', 'daemonId', 'hostId']);
|
|
210
|
+
const receiptId = firstAcross(records, ['receiptId', 'operationReceiptId', 'captureReceiptId', 'exportReceiptId', 'closeoutReceiptId', 'workspaceReceiptId']);
|
|
211
|
+
const cursor = firstAcross(records, ['cursor', 'freshnessCursor', 'sequence', 'checkpoint']);
|
|
212
|
+
const provenance = [...new Set([
|
|
213
|
+
...stringArray(input.record.provenance),
|
|
214
|
+
input.sourcePath ? `source ${input.sourcePath}` : '',
|
|
215
|
+
firstAcross(records, ['methodId']) ? `method ${firstAcross(records, ['methodId'])}` : '',
|
|
216
|
+
firstAcross(records, ['sourceTool']) ? `sourceTool ${firstAcross(records, ['sourceTool'])}` : '',
|
|
217
|
+
].map((entry) => safePreview(entry, 180)).filter(Boolean))].slice(0, 8);
|
|
218
|
+
const missingSignals = [
|
|
219
|
+
...(schemaStatus === 'certified' ? [] : [`Certified ${input.recordKind} schema is not published.`]),
|
|
220
|
+
...(input.durableId ? [] : [`Durable ${input.recordKind} id is not published.`]),
|
|
221
|
+
...(input.runnerId ? [] : [`${input.recordKind} runner id is not published.`]),
|
|
222
|
+
...(publicationGuarantee ? [] : [`${input.recordKind} publication guarantee is not published.`]),
|
|
223
|
+
...(publisher ? [] : [`${input.recordKind} publisher is not published.`]),
|
|
224
|
+
...(provenance.length > 0 ? [] : [`${input.recordKind} provenance is not published.`]),
|
|
225
|
+
...(cursor ? [] : [`${input.recordKind} freshness cursor is not published.`]),
|
|
226
|
+
...(input.modelRoute ? [] : [`${input.recordKind} inspect route is not published.`]),
|
|
227
|
+
...(input.status ? [] : [`${input.recordKind} status is not published.`]),
|
|
228
|
+
...(input.hasOutcomeEvidence === false ? ['Remote capture/export/closeout/artifact evidence id is not published.'] : []),
|
|
229
|
+
...(input.hasWorkspaceEvidence === false ? ['Remote workspace/worktree isolation evidence is not published.'] : []),
|
|
230
|
+
];
|
|
231
|
+
return {
|
|
232
|
+
schemaStatus,
|
|
233
|
+
...(schemaVersion ? { schemaVersion: safePreview(schemaVersion, 80) } : {}),
|
|
234
|
+
...(publicationGuarantee ? { publicationGuarantee: safePreview(publicationGuarantee, 220) } : {}),
|
|
235
|
+
...(publisher ? { publisher: safePreview(publisher, 80) } : {}),
|
|
236
|
+
...(provenance.length > 0 ? { provenance } : {}),
|
|
237
|
+
...(receiptId ? { receiptId: safePreview(receiptId, 96) } : {}),
|
|
238
|
+
...(cursor ? { cursor: safePreview(cursor, 96) } : {}),
|
|
239
|
+
missingSignals,
|
|
240
|
+
policy: 'Remote runtime read models certify release readiness only when the SDK or daemon publishes schema, durable runner/evidence ids, publication guarantee, publisher/provenance, freshness cursor, exact inspect route, and redacted outcome or workspace isolation evidence.',
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function safePathRef(value: string): string | null {
|
|
245
|
+
const redacted = redactText(value);
|
|
246
|
+
if (!redacted) return null;
|
|
247
|
+
const parts = redacted.split(/[\\/]+/).filter(Boolean);
|
|
248
|
+
if (parts.length <= 2) return safePreview(redacted, 120);
|
|
249
|
+
return safePreview(`.../${parts.slice(-2).join('/')}`, 120);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function timestampString(value: unknown): string | null {
|
|
253
|
+
const raw = readString(value);
|
|
254
|
+
if (raw) return safePreview(raw, 80);
|
|
255
|
+
const numeric = readNumber(value);
|
|
256
|
+
if (!numeric) return null;
|
|
257
|
+
const date = new Date(numeric);
|
|
258
|
+
return Number.isNaN(date.getTime()) ? String(numeric) : date.toISOString();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function callMethod(source: Record<string, unknown>, method: string): unknown {
|
|
262
|
+
const fn = source[method];
|
|
263
|
+
if (typeof fn !== 'function') return undefined;
|
|
264
|
+
try {
|
|
265
|
+
return (fn as () => unknown).call(source);
|
|
266
|
+
} catch {
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function collectFromSource(
|
|
272
|
+
source: unknown,
|
|
273
|
+
path: string,
|
|
274
|
+
kind: 'daemon-read-model' | 'sdk-read-model',
|
|
275
|
+
wrapperKeys: readonly string[],
|
|
276
|
+
methods: readonly string[],
|
|
277
|
+
visited = new WeakSet<object>(),
|
|
278
|
+
): readonly CollectedRecord[] {
|
|
279
|
+
if (!source) return [];
|
|
280
|
+
if (Array.isArray(source)) {
|
|
281
|
+
return source
|
|
282
|
+
.flatMap((entry, index) => collectFromSource(entry, `${path}[${index}]`, kind, wrapperKeys, methods, visited));
|
|
283
|
+
}
|
|
284
|
+
if (source instanceof Map) {
|
|
285
|
+
return Array.from(source.entries())
|
|
286
|
+
.flatMap(([key, value]) => collectFromSource(value, `${path}.${String(key)}`, kind, wrapperKeys, methods, visited));
|
|
287
|
+
}
|
|
288
|
+
if (typeof source !== 'object') return [];
|
|
289
|
+
if (visited.has(source)) return [];
|
|
290
|
+
visited.add(source);
|
|
291
|
+
|
|
292
|
+
const record = source as Record<string, unknown>;
|
|
293
|
+
const fromSnapshots = SNAPSHOT_METHODS.flatMap((method) => {
|
|
294
|
+
const snapshot = callMethod(record, method);
|
|
295
|
+
return snapshot === undefined ? [] : collectFromSource(snapshot, `${path}.${method}()`, kind, wrapperKeys, methods, visited);
|
|
296
|
+
});
|
|
297
|
+
const fromMethods = methods.flatMap((method) => {
|
|
298
|
+
const snapshot = callMethod(record, method);
|
|
299
|
+
return snapshot === undefined ? [] : collectFromSource(snapshot, `${path}.${method}()`, kind, wrapperKeys, methods, visited);
|
|
300
|
+
});
|
|
301
|
+
const fromWrappers = wrapperKeys.flatMap((key) => {
|
|
302
|
+
if (!(key in record)) return [];
|
|
303
|
+
return collectFromSource(record[key], `${path}.${key}`, kind, wrapperKeys, methods, visited);
|
|
304
|
+
});
|
|
305
|
+
if (fromSnapshots.length > 0 || fromMethods.length > 0 || fromWrappers.length > 0) {
|
|
306
|
+
return [...fromSnapshots, ...fromMethods, ...fromWrappers];
|
|
307
|
+
}
|
|
308
|
+
return [{ path, kind, record }];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function runnerId(record: Record<string, unknown>): string {
|
|
312
|
+
return nestedString(record, 'runnerId')
|
|
313
|
+
|| nestedString(record, 'agentId')
|
|
314
|
+
|| nestedString(record, 'linkedAgentId')
|
|
315
|
+
|| nestedString(record, 'remoteRunnerId')
|
|
316
|
+
|| nestedString(record, 'runner');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function routeForRunner(record: Record<string, unknown>, runner: string): string {
|
|
320
|
+
return nestedString(record, 'modelRoute')
|
|
321
|
+
|| nestedString(record, 'inspectRoute')
|
|
322
|
+
|| nestedString(record, 'reviewRoute')
|
|
323
|
+
|| (runner ? `agent_harness mode:"agent_orchestration_agent" agentId:"${runner}" includeParameters:true` : 'agent_harness mode:"agent_orchestration" includeParameters:true');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function outcomeKind(record: Record<string, unknown>): string {
|
|
327
|
+
const raw = nestedString(record, 'kind')
|
|
328
|
+
|| nestedString(record, 'type')
|
|
329
|
+
|| nestedString(record, 'purpose')
|
|
330
|
+
|| nestedString(record, 'operation');
|
|
331
|
+
const normalized = raw.toLowerCase();
|
|
332
|
+
if (normalized.includes('export')) return 'export';
|
|
333
|
+
if (normalized.includes('closeout')) return 'closeout';
|
|
334
|
+
if (normalized.includes('artifact')) return 'artifact';
|
|
335
|
+
if (normalized.includes('capture')) return 'capture';
|
|
336
|
+
return raw || 'remote-outcome';
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function outcomeStatus(record: Record<string, unknown>): string {
|
|
340
|
+
return nestedString(record, 'status')
|
|
341
|
+
|| nestedString(record, 'outcome')
|
|
342
|
+
|| nestedString(record, 'result')
|
|
343
|
+
|| nestedString(record, 'state')
|
|
344
|
+
|| 'unknown';
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function outcomeId(record: Record<string, unknown>, runner: string, kind: string, index: number): string {
|
|
348
|
+
return nestedString(record, 'id')
|
|
349
|
+
|| nestedString(record, 'outcomeId')
|
|
350
|
+
|| nestedString(record, 'captureId')
|
|
351
|
+
|| nestedString(record, 'exportId')
|
|
352
|
+
|| nestedString(record, 'artifactId')
|
|
353
|
+
|| `remote-outcome:${runner || 'unknown'}:${kind}:${index}`;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function normalizeOutcome(entry: CollectedRecord, index: number): RemoteCaptureOutcomeRecord | null {
|
|
357
|
+
const runner = runnerId(entry.record);
|
|
358
|
+
if (!runner) return null;
|
|
359
|
+
const kind = outcomeKind(entry.record);
|
|
360
|
+
const task = nestedString(entry.record, 'task') || nestedString(entry.record, 'title') || nestedString(entry.record, 'request');
|
|
361
|
+
const summary = nestedString(entry.record, 'summary')
|
|
362
|
+
|| nestedString(entry.record, 'description')
|
|
363
|
+
|| nestedString(entry.record, 'logTail')
|
|
364
|
+
|| nestedString(entry.record, 'message');
|
|
365
|
+
const id = outcomeId(entry.record, runner, kind, index);
|
|
366
|
+
const status = outcomeStatus(entry.record);
|
|
367
|
+
const captureId = nestedString(entry.record, 'captureId') || null;
|
|
368
|
+
const exportId = nestedString(entry.record, 'exportId') || null;
|
|
369
|
+
const artifactId = nestedString(entry.record, 'artifactId') || nestedString(entry.record, 'sourceArtifactId') || null;
|
|
370
|
+
const modelRoute = routeForRunner(entry.record, runner);
|
|
371
|
+
return {
|
|
372
|
+
id,
|
|
373
|
+
runnerId: runner,
|
|
374
|
+
status,
|
|
375
|
+
kind,
|
|
376
|
+
task: safeNullablePreview(task, 140),
|
|
377
|
+
summary: safeNullablePreview(summary, 220),
|
|
378
|
+
captureId,
|
|
379
|
+
exportId,
|
|
380
|
+
artifactId,
|
|
381
|
+
sourcePath: entry.path,
|
|
382
|
+
source: entry.kind,
|
|
383
|
+
redaction: nestedString(entry.record, 'redaction') || nestedString(entry.record, 'redactionPolicy') || 'bounded-redacted-read-model',
|
|
384
|
+
startedAt: timestampString(entry.record.startedAt ?? readMetadata(entry.record).startedAt),
|
|
385
|
+
completedAt: timestampString(entry.record.completedAt ?? entry.record.finishedAt ?? readMetadata(entry.record).completedAt),
|
|
386
|
+
modelRoute,
|
|
387
|
+
certification: remoteCertification({
|
|
388
|
+
record: entry.record,
|
|
389
|
+
sourcePath: entry.path,
|
|
390
|
+
recordKind: 'remote outcome',
|
|
391
|
+
durableId: id,
|
|
392
|
+
runnerId: runner,
|
|
393
|
+
modelRoute,
|
|
394
|
+
hasOutcomeEvidence: Boolean(captureId || exportId || artifactId || kind === 'closeout'),
|
|
395
|
+
status,
|
|
396
|
+
}),
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function workspaceId(record: Record<string, unknown>): string {
|
|
401
|
+
return nestedString(record, 'workspaceId')
|
|
402
|
+
|| nestedString(record, 'worktreeId')
|
|
403
|
+
|| nestedString(record, 'id');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function workspaceStatus(record: Record<string, unknown>): string {
|
|
407
|
+
return nestedString(record, 'status')
|
|
408
|
+
|| nestedString(record, 'state')
|
|
409
|
+
|| nestedString(record, 'outcome')
|
|
410
|
+
|| 'unknown';
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function normalizeWorkspace(entry: CollectedRecord, index: number): RemoteWorkspaceEvidenceRecord | null {
|
|
414
|
+
const runner = runnerId(entry.record);
|
|
415
|
+
if (!runner) return null;
|
|
416
|
+
const id = workspaceId(entry.record) || `remote-workspace:${runner}:${index}`;
|
|
417
|
+
const path = nestedString(entry.record, 'worktreePath')
|
|
418
|
+
|| nestedString(entry.record, 'workspacePath')
|
|
419
|
+
|| nestedString(entry.record, 'path')
|
|
420
|
+
|| nestedString(entry.record, 'root')
|
|
421
|
+
|| nestedString(entry.record, 'directory');
|
|
422
|
+
const status = workspaceStatus(entry.record);
|
|
423
|
+
const modelRoute = routeForRunner(entry.record, runner);
|
|
424
|
+
const workspace = workspaceId(entry.record) || null;
|
|
425
|
+
const worktreeRef = safePathRef(path);
|
|
426
|
+
const branch = safeNullablePreview(nestedString(entry.record, 'branch') || nestedString(entry.record, 'ref') || nestedString(entry.record, 'baseRef'), 100);
|
|
427
|
+
return {
|
|
428
|
+
id,
|
|
429
|
+
runnerId: runner,
|
|
430
|
+
status,
|
|
431
|
+
workspaceId: workspace,
|
|
432
|
+
isolationKind: nestedString(entry.record, 'isolationKind')
|
|
433
|
+
|| nestedString(entry.record, 'workspaceKind')
|
|
434
|
+
|| nestedString(entry.record, 'kind')
|
|
435
|
+
|| 'workspace',
|
|
436
|
+
label: safeNullablePreview(nestedString(entry.record, 'label') || nestedString(entry.record, 'name') || nestedString(entry.record, 'summary'), 120),
|
|
437
|
+
worktreeRef,
|
|
438
|
+
branch,
|
|
439
|
+
sourcePath: entry.path,
|
|
440
|
+
source: entry.kind,
|
|
441
|
+
redaction: nestedString(entry.record, 'redaction') || nestedString(entry.record, 'redactionPolicy') || 'path-bounded-read-model',
|
|
442
|
+
modelRoute,
|
|
443
|
+
certification: remoteCertification({
|
|
444
|
+
record: entry.record,
|
|
445
|
+
sourcePath: entry.path,
|
|
446
|
+
recordKind: 'remote workspace',
|
|
447
|
+
durableId: id,
|
|
448
|
+
runnerId: runner,
|
|
449
|
+
modelRoute,
|
|
450
|
+
hasWorkspaceEvidence: Boolean(workspace || worktreeRef || branch),
|
|
451
|
+
status,
|
|
452
|
+
}),
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function dedupeById<T extends { readonly id: string; readonly runnerId: string }>(records: readonly T[]): readonly T[] {
|
|
457
|
+
const seen = new Set<string>();
|
|
458
|
+
const output: T[] = [];
|
|
459
|
+
for (const record of records) {
|
|
460
|
+
const key = `${record.runnerId}:${record.id}`;
|
|
461
|
+
if (seen.has(key)) continue;
|
|
462
|
+
seen.add(key);
|
|
463
|
+
output.push(record);
|
|
464
|
+
}
|
|
465
|
+
return output;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function sourceCounts(entries: readonly CollectedRecord[]): Readonly<Record<string, number>> {
|
|
469
|
+
const counts: Record<string, number> = {};
|
|
470
|
+
for (const entry of entries) counts[entry.path] = (counts[entry.path] ?? 0) + 1;
|
|
471
|
+
return counts;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function outcomeSources(context: CommandContext): readonly SourceCandidate[] {
|
|
475
|
+
const platform = readObject(context.platform);
|
|
476
|
+
const readModels = readObject(platform.readModels);
|
|
477
|
+
const remoteRuntime = readObject(readModels.remoteRuntime);
|
|
478
|
+
const remoteRunners = readObject(readModels.remoteRunners);
|
|
479
|
+
const remote = readObject(readModels.remote);
|
|
480
|
+
const opsRemote = readObject(readObject(context.ops).remoteRuntime);
|
|
481
|
+
const opsReadModels = readObject(opsRemote.readModels);
|
|
482
|
+
const clients = readObject(context.clients);
|
|
483
|
+
const operator = readObject(clients.operator);
|
|
484
|
+
const operatorRemote = readObject(operator.remoteRuntime);
|
|
485
|
+
return [
|
|
486
|
+
{ path: 'context.platform.readModels.remoteRuntime.captureOutcomes', source: remoteRuntime.captureOutcomes, kind: 'daemon-read-model' },
|
|
487
|
+
{ path: 'context.platform.readModels.remoteRuntime.outcomes', source: remoteRuntime.outcomes, kind: 'daemon-read-model' },
|
|
488
|
+
{ path: 'context.platform.readModels.remoteRuntime.captures', source: remoteRuntime.captures, kind: 'daemon-read-model' },
|
|
489
|
+
{ path: 'context.platform.readModels.remoteRuntime.exports', source: remoteRuntime.exports, kind: 'daemon-read-model' },
|
|
490
|
+
{ path: 'context.platform.readModels.remoteRuntime.closeouts', source: remoteRuntime.closeouts, kind: 'daemon-read-model' },
|
|
491
|
+
{ path: 'context.platform.readModels.remoteRunners.captureOutcomes', source: remoteRunners.captureOutcomes, kind: 'daemon-read-model' },
|
|
492
|
+
{ path: 'context.platform.readModels.remote.captureOutcomes', source: remote.captureOutcomes, kind: 'daemon-read-model' },
|
|
493
|
+
{ path: 'context.platform.readModels.remoteCaptureOutcomes', source: readModels.remoteCaptureOutcomes, kind: 'daemon-read-model' },
|
|
494
|
+
{ path: 'context.platform.readModels.remoteRunnerOutcomes', source: readModels.remoteRunnerOutcomes, kind: 'daemon-read-model' },
|
|
495
|
+
{ path: 'context.ops.remoteRuntime.readModels.captureOutcomes', source: opsReadModels.captureOutcomes, kind: 'sdk-read-model' },
|
|
496
|
+
{ path: 'context.clients.operator.remoteRuntime.captureOutcomes', source: operatorRemote.captureOutcomes, kind: 'sdk-read-model' },
|
|
497
|
+
];
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function workspaceSources(context: CommandContext): readonly SourceCandidate[] {
|
|
501
|
+
const platform = readObject(context.platform);
|
|
502
|
+
const readModels = readObject(platform.readModels);
|
|
503
|
+
const remoteRuntime = readObject(readModels.remoteRuntime);
|
|
504
|
+
const remoteRunners = readObject(readModels.remoteRunners);
|
|
505
|
+
const remote = readObject(readModels.remote);
|
|
506
|
+
const worktrees = readObject(readModels.worktrees);
|
|
507
|
+
const opsRemote = readObject(readObject(context.ops).remoteRuntime);
|
|
508
|
+
const opsReadModels = readObject(opsRemote.readModels);
|
|
509
|
+
const clients = readObject(context.clients);
|
|
510
|
+
const operator = readObject(clients.operator);
|
|
511
|
+
const operatorRemote = readObject(operator.remoteRuntime);
|
|
512
|
+
return [
|
|
513
|
+
{ path: 'context.platform.readModels.remoteRuntime.workspaces', source: remoteRuntime.workspaces, kind: 'daemon-read-model' },
|
|
514
|
+
{ path: 'context.platform.readModels.remoteRuntime.workspaceIsolation', source: remoteRuntime.workspaceIsolation, kind: 'daemon-read-model' },
|
|
515
|
+
{ path: 'context.platform.readModels.remoteRuntime.worktrees', source: remoteRuntime.worktrees, kind: 'daemon-read-model' },
|
|
516
|
+
{ path: 'context.platform.readModels.remoteRunners.workspaces', source: remoteRunners.workspaces, kind: 'daemon-read-model' },
|
|
517
|
+
{ path: 'context.platform.readModels.remote.workspaceIsolation', source: remote.workspaceIsolation, kind: 'daemon-read-model' },
|
|
518
|
+
{ path: 'context.platform.readModels.remoteWorkspaces', source: readModels.remoteWorkspaces, kind: 'daemon-read-model' },
|
|
519
|
+
{ path: 'context.platform.readModels.remoteWorkspaceIsolation', source: readModels.remoteWorkspaceIsolation, kind: 'daemon-read-model' },
|
|
520
|
+
{ path: 'context.platform.readModels.worktrees.remote', source: worktrees.remote, kind: 'daemon-read-model' },
|
|
521
|
+
{ path: 'context.ops.remoteRuntime.readModels.workspaces', source: opsReadModels.workspaces, kind: 'sdk-read-model' },
|
|
522
|
+
{ path: 'context.clients.operator.remoteRuntime.workspaces', source: operatorRemote.workspaces, kind: 'sdk-read-model' },
|
|
523
|
+
];
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export function remoteReadModelSnapshot(context: CommandContext): RemoteReadModelSnapshot {
|
|
527
|
+
const outcomeEntries = outcomeSources(context).flatMap((candidate) =>
|
|
528
|
+
collectFromSource(candidate.source, candidate.path, candidate.kind, OUTCOME_WRAPPER_KEYS, OUTCOME_METHODS)
|
|
529
|
+
);
|
|
530
|
+
const workspaceEntries = workspaceSources(context).flatMap((candidate) =>
|
|
531
|
+
collectFromSource(candidate.source, candidate.path, candidate.kind, WORKSPACE_WRAPPER_KEYS, WORKSPACE_METHODS)
|
|
532
|
+
);
|
|
533
|
+
return {
|
|
534
|
+
outcomes: dedupeById(outcomeEntries.map(normalizeOutcome).filter((entry): entry is RemoteCaptureOutcomeRecord => entry !== null)),
|
|
535
|
+
workspaces: dedupeById(workspaceEntries.map(normalizeWorkspace).filter((entry): entry is RemoteWorkspaceEvidenceRecord => entry !== null)),
|
|
536
|
+
sourceCounts: {
|
|
537
|
+
...sourceCounts(outcomeEntries),
|
|
538
|
+
...sourceCounts(workspaceEntries),
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type AgentResearchSourceRecord,
|
|
13
13
|
} from '../agent/research-source-registry.ts';
|
|
14
14
|
import { browserControlPosture } from './agent-harness-browser-control.ts';
|
|
15
|
+
import { isCertifiedResearchLiveRecord, researchLiveReadModelSnapshot } from './agent-harness-research-live-read-models.ts';
|
|
15
16
|
import { previewHarnessText } from './agent-harness-text.ts';
|
|
16
17
|
|
|
17
18
|
interface AgentHarnessResearchBriefingArgs {
|
|
@@ -313,30 +314,41 @@ function workflowItem(query: string, includeParameters: boolean): ResearchBriefi
|
|
|
313
314
|
|
|
314
315
|
function browserItem(context: CommandContext, includeParameters: boolean): ResearchBriefingItem {
|
|
315
316
|
const browser = browserControlPosture(context);
|
|
317
|
+
const liveResearch = researchLiveReadModelSnapshot(context);
|
|
318
|
+
const certifiedRunnerRecords = liveResearch.browserRunnerRecords.filter(isCertifiedResearchLiveRecord);
|
|
319
|
+
const certifiedVisualReports = liveResearch.visualReportRecords.filter(isCertifiedResearchLiveRecord);
|
|
320
|
+
const hasCertifiedRunner = certifiedRunnerRecords.length > 0;
|
|
316
321
|
return {
|
|
317
322
|
id: 'browser:research-runner',
|
|
318
323
|
kind: 'browser',
|
|
319
|
-
priority: browser.configured ? 60 : 28,
|
|
324
|
+
priority: hasCertifiedRunner ? 86 : browser.configured ? 60 : 28,
|
|
320
325
|
label: 'Browser-backed research runner',
|
|
321
|
-
status: browser.configured ? 'ready-with-confirmation' : 'setup-needed',
|
|
322
|
-
summary:
|
|
326
|
+
status: hasCertifiedRunner ? 'certified-live-runner' : browser.configured ? 'ready-with-confirmation' : 'setup-needed',
|
|
327
|
+
summary: hasCertifiedRunner
|
|
328
|
+
? 'The SDK/daemon published certified live browser-backed research runner evidence.'
|
|
329
|
+
: browser.configured
|
|
323
330
|
? 'Browser or desktop control is configured; use it only when public web/fetch is insufficient.'
|
|
324
331
|
: 'Browser-backed research needs setup before live browser execution should be promised.',
|
|
325
|
-
next:
|
|
332
|
+
next: hasCertifiedRunner
|
|
333
|
+
? 'Inspect certified run, source receipt, and visual-render evidence before saving reports or promoting Knowledge.'
|
|
334
|
+
: browser.configured
|
|
326
335
|
? 'Use browser-backed research only after the user confirms authenticated or interactive scope.'
|
|
327
336
|
: 'Use bounded public search now, or inspect browser setup if live browser state is required.',
|
|
328
337
|
routes: {
|
|
329
338
|
runner: 'research action:"runner"',
|
|
330
339
|
setup: browser.setupRoute,
|
|
331
|
-
recommended: browser.recommendedRoute,
|
|
340
|
+
recommended: certifiedRunnerRecords[0]?.modelRoute ?? browser.recommendedRoute,
|
|
332
341
|
fallback: browser.fallbackRoutes[0] ?? 'research action:"search"',
|
|
342
|
+
...(certifiedVisualReports[0] ? { visualReport: certifiedVisualReports[0].modelRoute } : {}),
|
|
333
343
|
},
|
|
334
344
|
confirmationBoundary: 'This briefing never opens the browser; browser handoff stays on confirmed computer/browser routes.',
|
|
335
345
|
...(includeParameters ? {
|
|
336
346
|
detail: {
|
|
337
|
-
configured: browser.configured,
|
|
347
|
+
configured: browser.configured || hasCertifiedRunner,
|
|
338
348
|
needsReview: browser.needsReview,
|
|
339
349
|
fallbackRoutes: browser.fallbackRoutes,
|
|
350
|
+
liveRunnerRecords: liveResearch.browserRunnerRecords.slice(0, 8),
|
|
351
|
+
liveVisualReportRecords: liveResearch.visualReportRecords.slice(0, 8),
|
|
340
352
|
},
|
|
341
353
|
} : {}),
|
|
342
354
|
};
|
|
@@ -347,6 +359,7 @@ export function researchBriefingCatalogStatus(context: CommandContext): Record<s
|
|
|
347
359
|
const runs = shellPaths ? AgentResearchRunRegistry.fromShellPaths(shellPaths).snapshot() : null;
|
|
348
360
|
const sources = shellPaths ? AgentResearchSourceRegistry.fromShellPaths(shellPaths).snapshot() : null;
|
|
349
361
|
const reports = reportArtifacts(context, '');
|
|
362
|
+
const liveResearch = researchLiveReadModelSnapshot(context);
|
|
350
363
|
return {
|
|
351
364
|
modes: ['research_briefing'],
|
|
352
365
|
status: shellPaths ? 'ready' : 'unavailable',
|
|
@@ -354,6 +367,8 @@ export function researchBriefingCatalogStatus(context: CommandContext): Record<s
|
|
|
354
367
|
candidateSources: sources?.candidates.length ?? 0,
|
|
355
368
|
reviewedSources: sources?.reviewed.length ?? 0,
|
|
356
369
|
savedReports: reports.length,
|
|
370
|
+
liveBrowserRuns: liveResearch.browserRunnerRecords.length,
|
|
371
|
+
liveVisualReports: liveResearch.visualReportRecords.length,
|
|
357
372
|
readOnly: true,
|
|
358
373
|
};
|
|
359
374
|
}
|
|
@@ -368,6 +383,8 @@ export function researchBriefingSummary(context: CommandContext, args: AgentHarn
|
|
|
368
383
|
const matchedRuns = runs.filter((run) => !normalized || runSearchText(run).includes(normalized));
|
|
369
384
|
const matchedSources = matchingSources(sources, matchedRuns, query);
|
|
370
385
|
const reports = reportArtifacts(context, query);
|
|
386
|
+
const liveResearch = researchLiveReadModelSnapshot(context);
|
|
387
|
+
const certifiedRunnerRecords = liveResearch.browserRunnerRecords.filter(isCertifiedResearchLiveRecord);
|
|
371
388
|
const activeRuns = matchedRuns.filter((run) => isMutableRun(run.status));
|
|
372
389
|
const candidateSources = matchedSources.filter((source) => source.status === 'candidate');
|
|
373
390
|
const reviewedSources = matchedSources.filter((source) => source.status === 'reviewed');
|
|
@@ -404,7 +421,9 @@ export function researchBriefingSummary(context: CommandContext, args: AgentHarn
|
|
|
404
421
|
candidateSources: candidateSources.length,
|
|
405
422
|
reviewedSources: reviewedSources.length,
|
|
406
423
|
savedReports: reports.length,
|
|
407
|
-
browserReady: browserControlPosture(context).configured,
|
|
424
|
+
browserReady: browserControlPosture(context).configured || certifiedRunnerRecords.length > 0,
|
|
425
|
+
liveBrowserRuns: liveResearch.browserRunnerRecords.length,
|
|
426
|
+
liveVisualReports: liveResearch.visualReportRecords.length,
|
|
408
427
|
},
|
|
409
428
|
queue: items.slice(0, limit),
|
|
410
429
|
returned: Math.min(items.length, limit),
|