pi-background-tasks 0.9.0 → 1.0.4
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/BACKGROUND-TASKS-INSTRUCTIONS.md +63 -0
- package/PUBLISHING.md +43 -29
- package/README.md +233 -441
- package/TESTING.md +16 -10
- package/TEST_PLAN.md +43 -17
- package/docs/INDEX.md +157 -0
- package/docs/api/eventbus-v1.md +166 -0
- package/docs/assets/architecture.svg +78 -0
- package/docs/assets/footer-dock.svg +47 -0
- package/docs/assets/logo.svg +49 -0
- package/docs/attestations.json +189 -0
- package/docs/choose-a-workflow.md +98 -0
- package/docs/commands/bg-clear.md +70 -0
- package/docs/commands/bg-update.md +82 -0
- package/docs/commands/bg.md +90 -0
- package/docs/commands/fusion-models.md +70 -0
- package/docs/commands/fusion.md +69 -0
- package/docs/commands/jobs.md +74 -0
- package/docs/commands/kill.md +82 -0
- package/docs/commands/logs.md +90 -0
- package/docs/commands/task-manager.md +109 -0
- package/docs/concepts/completion-delivery.md +66 -0
- package/docs/concepts/context-projection-and-budgeting.md +79 -0
- package/docs/getting-started.md +122 -0
- package/docs/manifest.json +1825 -0
- package/docs/operations/configuration.md +110 -0
- package/docs/operations/releasing.md +67 -0
- package/docs/operations/testing.md +101 -0
- package/docs/operations/troubleshooting.md +38 -0
- package/docs/read-before-edit.md +94 -0
- package/docs/reference/runtime-contracts.md +213 -0
- package/docs/reference/shortcuts-and-dock.md +70 -0
- package/docs/subsystems/attested-pi-runs.md +141 -0
- package/docs/subsystems/background-task-runtime.md +85 -0
- package/docs/subsystems/child-launch-durability-and-safety.md +57 -0
- package/docs/subsystems/delegation.md +190 -0
- package/docs/subsystems/docs-freshness-gate.md +26 -0
- package/docs/subsystems/fusion.md +123 -0
- package/docs/subsystems/host-ui-and-telemetry.md +83 -0
- package/docs/tools/bg_delegate.md +193 -0
- package/docs/tools/bg_kill.md +114 -0
- package/docs/tools/bg_logs.md +133 -0
- package/docs/tools/bg_result.md +120 -0
- package/docs/tools/bg_run.md +168 -0
- package/docs/tools/bg_run_pi_attested.md +170 -0
- package/docs/tools/bg_status.md +111 -0
- package/docs/tools/fusion_investigate.md +116 -0
- package/docs/tools/fusion_reason.md +75 -0
- package/docs/tools/fusion_research.md +162 -0
- package/docs/tools/fusion_validate.md +206 -0
- package/logo.png +0 -0
- package/package.json +27 -9
- package/src/core/delegate/budget.ts +1 -1
- package/src/core/delegate/launch.ts +5 -0
- package/src/core/fusion/artifacts.ts +34 -4
- package/src/core/fusion/budget.ts +112 -20
- package/src/core/fusion/child-protocol.ts +82 -0
- package/src/core/fusion/clean-context.ts +91 -0
- package/src/core/fusion/config.ts +124 -35
- package/src/core/fusion/context.ts +29 -7
- package/src/core/fusion/evaluation.ts +392 -15
- package/src/core/fusion/orchestrator.ts +217 -23
- package/src/core/fusion/pi-child.ts +227 -24
- package/src/core/fusion/prompts.ts +39 -26
- package/src/core/fusion/source-policy.ts +257 -0
- package/src/core/fusion/types.ts +156 -11
- package/src/core/fusion/web-fetch.ts +104 -15
- package/src/core/fusion/workflows.ts +119 -65
- package/src/extension.ts +3 -3
- package/src/fusion-child-extension.ts +375 -141
- package/src/fusion-extension.ts +585 -240
- package/src/testing/normalize.ts +0 -22
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { open } from 'node:fs/promises';
|
|
4
|
+
import { isIP } from 'node:net';
|
|
5
|
+
import { canonicalJson } from '../attested-pi-run.js';
|
|
6
|
+
import { isJsonObject, parseJsonText } from '../common.js';
|
|
7
|
+
import {
|
|
8
|
+
FUSION_SOURCE_POLICY_SCHEMA_VERSION,
|
|
9
|
+
FusionError,
|
|
10
|
+
type FusionDeclaredSourceV1,
|
|
11
|
+
type FusionSourcePolicyV1,
|
|
12
|
+
} from './types.js';
|
|
13
|
+
|
|
14
|
+
const SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
15
|
+
const O_NOFOLLOW = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
|
|
16
|
+
|
|
17
|
+
function sha256Text(value: string): string {
|
|
18
|
+
return createHash('sha256').update(value, 'utf8').digest('hex');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function stripIpv6Brackets(hostname: string): string {
|
|
22
|
+
return hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isBlockedHostname(hostname: string): boolean {
|
|
26
|
+
const lower = hostname.toLowerCase();
|
|
27
|
+
return (
|
|
28
|
+
lower === 'localhost' ||
|
|
29
|
+
lower.endsWith('.localhost') ||
|
|
30
|
+
lower === 'metadata' ||
|
|
31
|
+
lower === 'metadata.local' ||
|
|
32
|
+
lower === 'metadata.google.internal' ||
|
|
33
|
+
lower === 'metadata.goog' ||
|
|
34
|
+
lower === 'instance-data' ||
|
|
35
|
+
lower === 'instance-data.ec2.internal'
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isBlockedIpv4(host: string): boolean {
|
|
40
|
+
const parts = host.split('.').map((part) => Number.parseInt(part, 10));
|
|
41
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255))
|
|
42
|
+
return true;
|
|
43
|
+
const [a = 0, b = 0, c = 0] = parts;
|
|
44
|
+
if (a === 0 || a === 10 || a === 127) return true;
|
|
45
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
46
|
+
if (a === 169 && b === 254) return true;
|
|
47
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
48
|
+
if (a === 192 && b === 168) return true;
|
|
49
|
+
if (a === 192 && b === 0 && c === 0) return true;
|
|
50
|
+
if (a === 192 && b === 0 && c === 2) return true;
|
|
51
|
+
if (a === 198 && (b === 18 || b === 19)) return true;
|
|
52
|
+
if (a === 198 && b === 51 && c === 100) return true;
|
|
53
|
+
if (a === 203 && b === 0 && c === 113) return true;
|
|
54
|
+
if (a >= 224) return true;
|
|
55
|
+
return host === '255.255.255.255' || host === '168.63.129.16';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isBlockedIpv6(host: string): boolean {
|
|
59
|
+
const lower = host.toLowerCase();
|
|
60
|
+
return (
|
|
61
|
+
lower === '::' ||
|
|
62
|
+
lower === '::1' ||
|
|
63
|
+
lower.startsWith('::ffff:') ||
|
|
64
|
+
lower.startsWith('64:ff9b::') ||
|
|
65
|
+
lower.startsWith('64:ff9b:1:') ||
|
|
66
|
+
lower.startsWith('100:') ||
|
|
67
|
+
lower.startsWith('2001:2:') ||
|
|
68
|
+
lower.startsWith('2001:db8:') ||
|
|
69
|
+
lower.startsWith('2002:') ||
|
|
70
|
+
lower.startsWith('fc') ||
|
|
71
|
+
lower.startsWith('fd') ||
|
|
72
|
+
lower.startsWith('fe8') ||
|
|
73
|
+
lower.startsWith('fe9') ||
|
|
74
|
+
lower.startsWith('fea') ||
|
|
75
|
+
lower.startsWith('feb') ||
|
|
76
|
+
lower.startsWith('ff')
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function canonicalizeFusionPublicUrl(value: string): string {
|
|
81
|
+
let url: URL;
|
|
82
|
+
try {
|
|
83
|
+
url = new URL(value);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
throw new FusionError(
|
|
86
|
+
`fusion research declared source URL is malformed: ${error instanceof Error ? error.message : String(error)}`,
|
|
87
|
+
{ code: 'orchestration_failed', childCreated: false },
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (url.username !== '' || url.password !== '') {
|
|
91
|
+
throw new FusionError('fusion research source URL must not contain credentials', {
|
|
92
|
+
code: 'orchestration_failed',
|
|
93
|
+
childCreated: false,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
97
|
+
throw new FusionError('fusion research source URL must use http or https', {
|
|
98
|
+
code: 'orchestration_failed',
|
|
99
|
+
childCreated: false,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
url.username = '';
|
|
103
|
+
url.password = '';
|
|
104
|
+
url.hash = '';
|
|
105
|
+
const normalizedHost = stripIpv6Brackets(url.hostname.toLowerCase().replace(/\.+$/u, ''));
|
|
106
|
+
url.hostname = normalizedHost;
|
|
107
|
+
if (isBlockedHostname(normalizedHost)) {
|
|
108
|
+
throw new FusionError('fusion research source URL must be public, not localhost/metadata', {
|
|
109
|
+
code: 'orchestration_failed',
|
|
110
|
+
childCreated: false,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const ipKind = isIP(normalizedHost);
|
|
114
|
+
if ((ipKind === 4 && isBlockedIpv4(normalizedHost)) || (ipKind === 6 && isBlockedIpv6(normalizedHost))) {
|
|
115
|
+
throw new FusionError('fusion research source URL must be public, not private/reserved', {
|
|
116
|
+
code: 'orchestration_failed',
|
|
117
|
+
childCreated: false,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if ((url.protocol === 'http:' && url.port === '80') || (url.protocol === 'https:' && url.port === '443')) {
|
|
121
|
+
url.port = '';
|
|
122
|
+
}
|
|
123
|
+
return url.toString();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface DeclaredFusionSourceInput {
|
|
127
|
+
url: string;
|
|
128
|
+
purpose: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function normalizeFusionDeclaredSources(
|
|
132
|
+
sources: readonly DeclaredFusionSourceInput[] = [],
|
|
133
|
+
): readonly FusionDeclaredSourceV1[] {
|
|
134
|
+
const seen = new Map<string, number>();
|
|
135
|
+
return sources.map((source, index) => {
|
|
136
|
+
if (typeof source.url !== 'string' || source.url.trim().length === 0) {
|
|
137
|
+
throw new FusionError(`fusion research source ${String(index)} requires non-blank URL`, {
|
|
138
|
+
code: 'orchestration_failed',
|
|
139
|
+
childCreated: false,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (typeof source.purpose !== 'string' || source.purpose.trim().length === 0) {
|
|
143
|
+
throw new FusionError(`fusion research source ${String(index)} requires non-blank purpose`, {
|
|
144
|
+
code: 'orchestration_failed',
|
|
145
|
+
childCreated: false,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const canonicalUrl = canonicalizeFusionPublicUrl(source.url.trim());
|
|
149
|
+
const previous = seen.get(canonicalUrl);
|
|
150
|
+
if (previous !== undefined) {
|
|
151
|
+
throw new FusionError(
|
|
152
|
+
`fusion research source ${String(index)} duplicates canonical URL from source ${String(previous)}: ${canonicalUrl}`,
|
|
153
|
+
{ code: 'orchestration_failed', childCreated: false },
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
seen.set(canonicalUrl, index);
|
|
157
|
+
const purpose = source.purpose.trim();
|
|
158
|
+
return {
|
|
159
|
+
url: canonicalUrl,
|
|
160
|
+
canonical_url: canonicalUrl,
|
|
161
|
+
purpose,
|
|
162
|
+
sha256: sha256Text(`${canonicalUrl}\u0000${purpose}`),
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function buildFusionSourcePolicy(
|
|
168
|
+
cwd: string,
|
|
169
|
+
sources: readonly FusionDeclaredSourceV1[],
|
|
170
|
+
): FusionSourcePolicyV1 {
|
|
171
|
+
const body = {
|
|
172
|
+
schema_version: FUSION_SOURCE_POLICY_SCHEMA_VERSION,
|
|
173
|
+
workflow: 'research' as const,
|
|
174
|
+
cwd,
|
|
175
|
+
sources,
|
|
176
|
+
} as const;
|
|
177
|
+
return { ...body, root_sha256: sha256Text(canonicalJson(body)) };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function sourcePolicyCanonicalBytes(policy: FusionSourcePolicyV1): string {
|
|
181
|
+
return canonicalJson(policy);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function requireString(record: Record<PropertyKey, unknown>, key: string, label: string): string {
|
|
185
|
+
const value = record[key];
|
|
186
|
+
if (typeof value !== 'string' || value.length === 0) throw new Error(`${label}.${key} must be non-blank string`);
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function parseFusionSourcePolicy(value: unknown): FusionSourcePolicyV1 {
|
|
191
|
+
if (!isJsonObject(value) || Array.isArray(value)) throw new Error('fusion source policy must be object');
|
|
192
|
+
const keys = Object.keys(value).sort();
|
|
193
|
+
const expected = ['cwd', 'root_sha256', 'schema_version', 'sources', 'workflow'];
|
|
194
|
+
if (keys.join('\0') !== expected.join('\0')) throw new Error('fusion source policy keys mismatch');
|
|
195
|
+
if (value['schema_version'] !== FUSION_SOURCE_POLICY_SCHEMA_VERSION) throw new Error('fusion source policy schema_version mismatch');
|
|
196
|
+
if (value['workflow'] !== 'research') throw new Error('fusion source policy workflow must be research');
|
|
197
|
+
const cwd = requireString(value, 'cwd', 'fusion source policy');
|
|
198
|
+
const rootSha256 = requireString(value, 'root_sha256', 'fusion source policy');
|
|
199
|
+
if (!SHA256_HEX.test(rootSha256)) throw new Error('fusion source policy.root_sha256 must be sha256');
|
|
200
|
+
if (!Array.isArray(value['sources'])) throw new Error('fusion source policy.sources must be array');
|
|
201
|
+
const sources = value['sources'].map((item, index): FusionDeclaredSourceV1 => {
|
|
202
|
+
const label = `fusion source policy.sources[${String(index)}]`;
|
|
203
|
+
if (!isJsonObject(item) || Array.isArray(item)) throw new Error(`${label} must be object`);
|
|
204
|
+
const itemKeys = Object.keys(item).sort();
|
|
205
|
+
const itemExpected = ['canonical_url', 'purpose', 'sha256', 'url'];
|
|
206
|
+
if (itemKeys.join('\0') !== itemExpected.join('\0')) throw new Error(`${label} keys mismatch`);
|
|
207
|
+
const url = requireString(item, 'url', label);
|
|
208
|
+
const purpose = requireString(item, 'purpose', label);
|
|
209
|
+
const canonical_url = requireString(item, 'canonical_url', label);
|
|
210
|
+
const sha256 = requireString(item, 'sha256', label);
|
|
211
|
+
if (!SHA256_HEX.test(sha256)) throw new Error(`${label}.sha256 must be sha256`);
|
|
212
|
+
if (canonicalizeFusionPublicUrl(url) !== canonical_url) throw new Error(`${label}.canonical_url mismatch`);
|
|
213
|
+
if (url !== canonical_url) throw new Error(`${label}.url must equal canonical_url`);
|
|
214
|
+
if (purpose.trim() !== purpose) throw new Error(`${label}.purpose must be trimmed`);
|
|
215
|
+
if (sha256Text(`${canonical_url}\u0000${purpose}`) !== sha256) throw new Error(`${label}.sha256 mismatch`);
|
|
216
|
+
return { url, canonical_url, purpose, sha256 };
|
|
217
|
+
});
|
|
218
|
+
const seen = new Set<string>();
|
|
219
|
+
for (const [index, source] of sources.entries()) {
|
|
220
|
+
if (seen.has(source.canonical_url)) {
|
|
221
|
+
throw new Error(`fusion source policy.sources[${String(index)}].canonical_url duplicate`);
|
|
222
|
+
}
|
|
223
|
+
seen.add(source.canonical_url);
|
|
224
|
+
}
|
|
225
|
+
const body = { schema_version: FUSION_SOURCE_POLICY_SCHEMA_VERSION, workflow: 'research' as const, cwd, sources } as const;
|
|
226
|
+
if (sha256Text(canonicalJson(body)) !== rootSha256) throw new Error('fusion source policy root_sha256 mismatch');
|
|
227
|
+
return { ...body, root_sha256: rootSha256 };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function readRegularFileNoSymlink(path: string, label: string): Promise<Buffer> {
|
|
231
|
+
let handle: Awaited<ReturnType<typeof open>>;
|
|
232
|
+
try {
|
|
233
|
+
handle = await open(path, constants.O_RDONLY | O_NOFOLLOW);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
if (isJsonObject(error) && error['code'] === 'ELOOP') {
|
|
236
|
+
throw new Error(`${label} at ${path} is a symlink; refusing to follow it`);
|
|
237
|
+
}
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const stats = await handle.stat();
|
|
242
|
+
if (!stats.isFile()) throw new Error(`${label} at ${path} is not a regular file`);
|
|
243
|
+
return await handle.readFile();
|
|
244
|
+
} finally {
|
|
245
|
+
await handle.close();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export async function readFusionSourcePolicyFile(path: string, expectedSha256: string): Promise<FusionSourcePolicyV1> {
|
|
250
|
+
if (!SHA256_HEX.test(expectedSha256)) throw new Error('fusion source policy expected hash is malformed');
|
|
251
|
+
const bytes = await readRegularFileNoSymlink(path, 'fusion source policy');
|
|
252
|
+
const actual = createHash('sha256').update(bytes).digest('hex');
|
|
253
|
+
if (actual !== expectedSha256) throw new Error('fusion source policy artifact hash mismatch');
|
|
254
|
+
const text = bytes.toString('utf8');
|
|
255
|
+
if (!Buffer.from(text, 'utf8').equals(bytes)) throw new Error('fusion source policy is not UTF-8');
|
|
256
|
+
return parseFusionSourcePolicy(parseJsonText(text));
|
|
257
|
+
}
|
package/src/core/fusion/types.ts
CHANGED
|
@@ -11,11 +11,16 @@ import type {
|
|
|
11
11
|
export type FusionThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
12
12
|
|
|
13
13
|
export const FUSION_MODEL_CONFIG_SCHEMA_VERSION = 'pi-background-tasks.fusion-models.v1';
|
|
14
|
-
export const
|
|
14
|
+
export const FUSION_LEGACY_INPUT_SCHEMA_VERSION = 'pi-background-tasks.fusion-input.v4';
|
|
15
|
+
export const FUSION_INPUT_SCHEMA_VERSION = 'pi-background-tasks.fusion-input.v5';
|
|
15
16
|
export const FUSION_EVALUATION_SCHEMA_VERSION = 'pi-background-tasks.fusion-evaluation.v1';
|
|
16
|
-
export const
|
|
17
|
-
export const
|
|
17
|
+
export const FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION = 'pi-background-tasks.fusion-validation-candidate.v1';
|
|
18
|
+
export const FUSION_LEGACY_RESULT_SCHEMA_VERSION = 'pi-background-tasks.fusion-result.v4';
|
|
19
|
+
export const FUSION_RESULT_SCHEMA_VERSION = 'pi-background-tasks.fusion-result.v5';
|
|
20
|
+
export const FUSION_LEGACY_MANIFEST_SCHEMA_VERSION = 'pi-background-tasks.fusion-manifest.v3';
|
|
21
|
+
export const FUSION_MANIFEST_SCHEMA_VERSION = 'pi-background-tasks.fusion-manifest.v4';
|
|
18
22
|
export const FUSION_CONTEXT_LEDGER_SCHEMA_VERSION = 'pi-background-tasks.fusion-context-ledger.v2';
|
|
23
|
+
export const FUSION_SOURCE_POLICY_SCHEMA_VERSION = 'pi-background-tasks.fusion-source-policy.v1';
|
|
19
24
|
export const FUSION_BUDGET_PLAN_SCHEMA_VERSION = 'pi-background-tasks.fusion-budget-plan.v3';
|
|
20
25
|
export const FUSION_CALIBRATION_VIOLATION_SCHEMA_VERSION =
|
|
21
26
|
'pi-background-tasks.fusion-calibration-violation.v1';
|
|
@@ -46,26 +51,51 @@ export type FusionStage = (typeof FUSION_STAGE_VALUES)[number];
|
|
|
46
51
|
|
|
47
52
|
export const FUSION_CAPABILITY_VALUES = Object.freeze(['reason', 'inspect', 'research'] as const);
|
|
48
53
|
export type FusionCapability = (typeof FUSION_CAPABILITY_VALUES)[number];
|
|
49
|
-
|
|
54
|
+
|
|
55
|
+
/** No-tools capability for reason candidates, evaluator, repair, and merger. */
|
|
56
|
+
export const FUSION_NO_TOOLS_CAPABILITY: FusionCapability = 'reason';
|
|
57
|
+
/** Legacy default retained for old type imports only. New workflows never default. */
|
|
58
|
+
export const FUSION_BRAINSTORM_DEFAULT_CAPABILITY: FusionCapability = 'inspect';
|
|
59
|
+
/** @deprecated New v5 workflows have no caller capability default. */
|
|
60
|
+
export const FUSION_DEFAULT_CAPABILITY: FusionCapability = FUSION_BRAINSTORM_DEFAULT_CAPABILITY;
|
|
50
61
|
|
|
51
62
|
export const FUSION_WEB_FETCH_TOOL_NAME = 'fusion_web_fetch' as const;
|
|
52
63
|
export const FUSION_INSPECT_TOOLS = Object.freeze(['read', 'grep', 'find', 'ls'] as const);
|
|
64
|
+
export const FUSION_RESEARCH_TOOLS = Object.freeze([
|
|
65
|
+
'read',
|
|
66
|
+
'grep',
|
|
67
|
+
'find',
|
|
68
|
+
'ls',
|
|
69
|
+
FUSION_WEB_FETCH_TOOL_NAME,
|
|
70
|
+
] as const);
|
|
53
71
|
|
|
54
72
|
/**
|
|
55
73
|
* Workflow identities sharing one orchestrator, one context projection, one
|
|
56
74
|
* evaluation schema, and one artifact store. A workflow selects stage framing and
|
|
57
75
|
* capability policy only; it never changes the canonical input schema.
|
|
58
76
|
*/
|
|
59
|
-
export const FUSION_WORKFLOW_IDS = Object.freeze([
|
|
77
|
+
export const FUSION_WORKFLOW_IDS = Object.freeze([
|
|
78
|
+
'reason',
|
|
79
|
+
'investigate',
|
|
80
|
+
'research',
|
|
81
|
+
'validate',
|
|
82
|
+
] as const);
|
|
60
83
|
export type FusionWorkflowId = (typeof FUSION_WORKFLOW_IDS)[number];
|
|
84
|
+
export const FUSION_PUBLIC_WORKFLOW_NAMES = Object.freeze([
|
|
85
|
+
'fusion_reason',
|
|
86
|
+
'fusion_investigate',
|
|
87
|
+
'fusion_research',
|
|
88
|
+
'fusion_validate',
|
|
89
|
+
] as const);
|
|
90
|
+
export type FusionPublicWorkflowName = (typeof FUSION_PUBLIC_WORKFLOW_NAMES)[number];
|
|
91
|
+
export type FusionContextKind = 'session_projection' | 'clean_task';
|
|
61
92
|
|
|
62
93
|
/**
|
|
63
94
|
* The single capability the validate workflow ever runs candidates with.
|
|
64
95
|
*
|
|
65
|
-
* Deliberately separate from
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* fixed workflow policy and not a default a caller may override.
|
|
96
|
+
* Deliberately separate from the caller-selectable brainstorm default. Although
|
|
97
|
+
* both workflows currently give candidates read-only inspection, validation pins
|
|
98
|
+
* that capability as fixed policy rather than exposing a caller override.
|
|
69
99
|
*/
|
|
70
100
|
export const FUSION_VALIDATE_CAPABILITY: FusionCapability = 'inspect';
|
|
71
101
|
|
|
@@ -74,6 +104,9 @@ export const FUSION_FORBIDDEN_TOOLS = Object.freeze([
|
|
|
74
104
|
'edit',
|
|
75
105
|
'write',
|
|
76
106
|
'fusion_brainstorm',
|
|
107
|
+
'fusion_reason',
|
|
108
|
+
'fusion_investigate',
|
|
109
|
+
'fusion_research',
|
|
77
110
|
'fusion_validate',
|
|
78
111
|
'bg_delegate',
|
|
79
112
|
'bg_result',
|
|
@@ -302,15 +335,73 @@ export interface FusionConversationProjectionV4 {
|
|
|
302
335
|
|
|
303
336
|
export type FusionConversationProjectionV3 = FusionConversationProjectionV4;
|
|
304
337
|
|
|
305
|
-
export interface
|
|
338
|
+
export interface FusionDeclaredSourceV1 {
|
|
339
|
+
url: string;
|
|
340
|
+
canonical_url: string;
|
|
341
|
+
purpose: string;
|
|
342
|
+
sha256: string;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export interface FusionCleanTaskContextV1 {
|
|
346
|
+
kind: 'clean_task';
|
|
347
|
+
policy_id: 'fusion-clean-task-v1';
|
|
348
|
+
declared_sources: readonly FusionDeclaredSourceV1[];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export interface FusionSessionProjectionContextV1 {
|
|
352
|
+
kind: 'session_projection';
|
|
353
|
+
policy_id: 'fusion-session-projection-v1';
|
|
354
|
+
system_prompt: string;
|
|
355
|
+
conversation_projection: FusionConversationProjectionV4;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export interface FusionCanonicalInputV5Base {
|
|
306
359
|
schema_version: typeof FUSION_INPUT_SCHEMA_VERSION;
|
|
360
|
+
workflow?: FusionWorkflowId | undefined;
|
|
361
|
+
cwd: string;
|
|
362
|
+
request: FusionCanonicalRequestV3;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export interface FusionSessionProjectionCanonicalInputV5 extends FusionCanonicalInputV5Base {
|
|
366
|
+
workflow?: FusionWorkflowId | undefined;
|
|
367
|
+
/** @deprecated v4 readability alias. Present only for session-projection inputs at runtime. */
|
|
368
|
+
system_prompt: string;
|
|
369
|
+
/** @deprecated v4 readability alias. Present only for session-projection inputs at runtime. */
|
|
370
|
+
conversation_projection: FusionConversationProjectionV4;
|
|
371
|
+
context?: FusionSessionProjectionContextV1 | undefined;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export interface FusionCleanTaskCanonicalInputV5 extends FusionCanonicalInputV5Base {
|
|
375
|
+
workflow: Exclude<FusionWorkflowId, 'reason'>;
|
|
376
|
+
context: FusionCleanTaskContextV1;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export type FusionCanonicalInputV5 =
|
|
380
|
+
| FusionSessionProjectionCanonicalInputV5
|
|
381
|
+
| FusionCleanTaskCanonicalInputV5;
|
|
382
|
+
|
|
383
|
+
/** Legacy v4 shape retained for frozen golden fixtures/readability only. */
|
|
384
|
+
export interface FusionCanonicalInputV4 {
|
|
385
|
+
schema_version: typeof FUSION_LEGACY_INPUT_SCHEMA_VERSION;
|
|
307
386
|
cwd: string;
|
|
308
387
|
system_prompt: string;
|
|
309
388
|
request: FusionCanonicalRequestV3;
|
|
310
389
|
conversation_projection: FusionConversationProjectionV4;
|
|
311
390
|
}
|
|
312
391
|
|
|
313
|
-
export type FusionCanonicalInputV3 =
|
|
392
|
+
export type FusionCanonicalInputV3 = FusionCanonicalInputV5;
|
|
393
|
+
|
|
394
|
+
export interface FusionSourcePolicyV1 {
|
|
395
|
+
schema_version: typeof FUSION_SOURCE_POLICY_SCHEMA_VERSION;
|
|
396
|
+
workflow: 'research';
|
|
397
|
+
cwd: string;
|
|
398
|
+
sources: readonly FusionDeclaredSourceV1[];
|
|
399
|
+
root_sha256: string;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export interface FusionSourcePolicyArtifactRef extends FusionArtifactRef {
|
|
403
|
+
root_sha256: string;
|
|
404
|
+
}
|
|
314
405
|
|
|
315
406
|
export interface CandidateAssessment {
|
|
316
407
|
candidate_id: FusionCandidateId;
|
|
@@ -343,12 +434,56 @@ export interface FusionSynthesisPlan {
|
|
|
343
434
|
must_avoid: readonly string[];
|
|
344
435
|
}
|
|
345
436
|
|
|
437
|
+
export type FusionValidationSeverity = 'critical' | 'high' | 'minor';
|
|
438
|
+
|
|
439
|
+
export interface FusionValidationFindingRecord {
|
|
440
|
+
id: string;
|
|
441
|
+
candidate_id: FusionCandidateId;
|
|
442
|
+
severity: FusionValidationSeverity;
|
|
443
|
+
location: string;
|
|
444
|
+
evidence: string;
|
|
445
|
+
impact: string;
|
|
446
|
+
summary: string;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export interface FusionValidationFindingDecision {
|
|
450
|
+
source_id: string;
|
|
451
|
+
disposition: 'include' | 'exclude';
|
|
452
|
+
rationale: string;
|
|
453
|
+
group_id?: string | undefined;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export interface FusionValidationFindingGroup {
|
|
457
|
+
group_id: string;
|
|
458
|
+
source_ids: readonly string[];
|
|
459
|
+
severity: FusionValidationSeverity;
|
|
460
|
+
location: string;
|
|
461
|
+
evidence: string;
|
|
462
|
+
impact: string;
|
|
463
|
+
summary: string;
|
|
464
|
+
rationale: string;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export interface FusionValidationFindingAccounting {
|
|
468
|
+
findings: readonly FusionValidationFindingRecord[];
|
|
469
|
+
decisions: readonly FusionValidationFindingDecision[];
|
|
470
|
+
groups: readonly FusionValidationFindingGroup[];
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export interface FusionValidationCandidateReportV1 {
|
|
474
|
+
schema_version: typeof FUSION_VALIDATE_CANDIDATE_SCHEMA_VERSION;
|
|
475
|
+
findings: readonly Omit<FusionValidationFindingRecord, 'id' | 'candidate_id'>[];
|
|
476
|
+
verified: readonly string[];
|
|
477
|
+
limitations: readonly string[];
|
|
478
|
+
}
|
|
479
|
+
|
|
346
480
|
export interface FusionEvaluationV1 {
|
|
347
481
|
schema_version: typeof FUSION_EVALUATION_SCHEMA_VERSION;
|
|
348
482
|
candidate_assessments: readonly [CandidateAssessment, CandidateAssessment, CandidateAssessment];
|
|
349
483
|
agreements: readonly string[];
|
|
350
484
|
conflicts: readonly FusionConflict[];
|
|
351
485
|
synthesis_plan: FusionSynthesisPlan;
|
|
486
|
+
validation_accounting?: FusionValidationFindingAccounting | undefined;
|
|
352
487
|
}
|
|
353
488
|
|
|
354
489
|
/** Exact Pi usage contract used at the child, artifact, and host tool-result boundaries. */
|
|
@@ -420,6 +555,8 @@ export interface FusionResultDetails {
|
|
|
420
555
|
workflow: FusionWorkflowId;
|
|
421
556
|
source: FusionSource;
|
|
422
557
|
status: 'completed';
|
|
558
|
+
context: { kind: FusionContextKind; policy_id: string };
|
|
559
|
+
tool_policy: { candidate_tools: readonly string[]; evaluation_tools: readonly []; merge_tools: readonly [] };
|
|
423
560
|
artifact_dir: string;
|
|
424
561
|
models: {
|
|
425
562
|
candidates: readonly [string, string, string];
|
|
@@ -596,6 +733,8 @@ export interface FusionToolCallLogRecord {
|
|
|
596
733
|
status: FusionToolCallLogStatus;
|
|
597
734
|
duration_ms: number;
|
|
598
735
|
url?: string | undefined;
|
|
736
|
+
/** SHA-256 of a rejected attempted fetch URL; raw rejected URLs are never persisted. */
|
|
737
|
+
rejected_url_sha256?: string | undefined;
|
|
599
738
|
final_url?: string | undefined;
|
|
600
739
|
http_status?: number | undefined;
|
|
601
740
|
response_bytes?: number | undefined;
|
|
@@ -676,6 +815,8 @@ export interface FusionArtifactManifest {
|
|
|
676
815
|
evaluation: FusionCapability;
|
|
677
816
|
merge: FusionCapability;
|
|
678
817
|
};
|
|
818
|
+
context: { kind: FusionContextKind; policy_id: string; ledger_artifact?: string; source_policy_artifact?: string };
|
|
819
|
+
tool_policy: { candidate_tools: readonly string[]; evaluation_tools: readonly []; merge_tools: readonly [] };
|
|
679
820
|
usage: FusionUsage;
|
|
680
821
|
attempts: readonly FusionAttemptArtifactRecord[];
|
|
681
822
|
artifacts: Readonly<Record<string, FusionArtifactRef>>;
|
|
@@ -756,6 +897,10 @@ export interface FusionBudgetEmptyRequestVerdict {
|
|
|
756
897
|
|
|
757
898
|
export interface FusionBudgetPlanV1 {
|
|
758
899
|
schema_version: typeof FUSION_BUDGET_PLAN_SCHEMA_VERSION;
|
|
900
|
+
workflow: FusionWorkflowId;
|
|
901
|
+
context: { kind: FusionContextKind; policy_id: string };
|
|
902
|
+
fixed_candidate_policy: { capability: FusionCapability; tools: readonly string[] };
|
|
903
|
+
tool_policy: { candidate_tools: readonly string[]; evaluation_tools: readonly []; merge_tools: readonly [] };
|
|
759
904
|
policy: FusionBudgetPolicyDescriptor;
|
|
760
905
|
routes: readonly FusionRouteCapacity[];
|
|
761
906
|
stages: readonly FusionStageBudgetPlanEntry[];
|