@sqdr2/dsh-fusion 0.0.0-stage → 0.3.2
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/INSTALL.md +89 -0
- package/INSTALL.zh.md +86 -0
- package/LICENSE +201 -0
- package/README.en.md +193 -0
- package/README.md +182 -2
- package/RELEASING.md +123 -0
- package/RELEASING.zh.md +115 -0
- package/contracts/fusion.ts +279 -0
- package/contracts/storage.sql +86 -0
- package/cordis.patch.yml +3 -0
- package/docs/COMPATIBILITY.md +162 -0
- package/docs/EVIDENCE.md +141 -0
- package/dshx.yml +39 -0
- package/lib/client.js +1920 -0
- package/lib/client.js.map +1 -0
- package/lib/dsh-fusion.js +12664 -0
- package/lib/types/activity.d.ts +20 -0
- package/lib/types/approval/logical.d.ts +10 -0
- package/lib/types/benchmark/naive.d.ts +37 -0
- package/lib/types/benchmark/native-run.d.ts +68 -0
- package/lib/types/benchmark/output-limits.d.ts +19 -0
- package/lib/types/bilingual.d.ts +2 -0
- package/lib/types/client/ActivityRows.d.ts +8 -0
- package/lib/types/client/FusionCache.d.ts +4 -0
- package/lib/types/client/FusionHint.d.ts +9 -0
- package/lib/types/client/FusionHistory.d.ts +5 -0
- package/lib/types/client/FusionPanelIcon.d.ts +13 -0
- package/lib/types/client/FusionRecovery.d.ts +5 -0
- package/lib/types/client/FusionSettings.d.ts +9 -0
- package/lib/types/client/FusionStatus.d.ts +5 -0
- package/lib/types/client/FusionTool.d.ts +4 -0
- package/lib/types/client/activity-model.d.ts +14 -0
- package/lib/types/client/i18n.d.ts +15 -0
- package/lib/types/client/index.d.ts +22 -0
- package/lib/types/client/settings-events.d.ts +2 -0
- package/lib/types/client/status-polling.d.ts +9 -0
- package/lib/types/context/guard.d.ts +21 -0
- package/lib/types/context/handoff.d.ts +17 -0
- package/lib/types/contracts.d.ts +449 -0
- package/lib/types/digest.d.ts +5 -0
- package/lib/types/dsh-fusion.d.ts +13 -0
- package/lib/types/errors.d.ts +28 -0
- package/lib/types/evidence/gate.d.ts +23 -0
- package/lib/types/evidence/receipts.d.ts +65 -0
- package/lib/types/execution/write-lease.d.ts +42 -0
- package/lib/types/host/bindings.d.ts +30 -0
- package/lib/types/host/cache-defaults.d.ts +48 -0
- package/lib/types/host/cache-policy.d.ts +155 -0
- package/lib/types/host/change-evidence.d.ts +38 -0
- package/lib/types/host/compaction-routing.d.ts +15 -0
- package/lib/types/host/coordinator.d.ts +169 -0
- package/lib/types/host/evidence-page.d.ts +13 -0
- package/lib/types/host/history.d.ts +88 -0
- package/lib/types/host/http.d.ts +4 -0
- package/lib/types/host/model-output.d.ts +13 -0
- package/lib/types/host/native-activity.d.ts +26 -0
- package/lib/types/host/native-approvals.d.ts +29 -0
- package/lib/types/host/native-auxiliary.d.ts +17 -0
- package/lib/types/host/native-budget.d.ts +33 -0
- package/lib/types/host/native-checks.d.ts +104 -0
- package/lib/types/host/native-compaction.d.ts +8 -0
- package/lib/types/host/native-context.d.ts +36 -0
- package/lib/types/host/native-effects.d.ts +75 -0
- package/lib/types/host/native-evidence.d.ts +4 -0
- package/lib/types/host/native-exploration.d.ts +15 -0
- package/lib/types/host/native-keepalive.d.ts +82 -0
- package/lib/types/host/native-model-control.d.ts +99 -0
- package/lib/types/host/native-on-demand-context.d.ts +39 -0
- package/lib/types/host/native-request.d.ts +5 -0
- package/lib/types/host/native-review.d.ts +11 -0
- package/lib/types/host/native-role-sandbox.d.ts +49 -0
- package/lib/types/host/native-scopes.d.ts +49 -0
- package/lib/types/host/native-selection.d.ts +29 -0
- package/lib/types/host/native-task-context.d.ts +39 -0
- package/lib/types/host/native-usage.d.ts +46 -0
- package/lib/types/host/native-worker-brief.d.ts +74 -0
- package/lib/types/host/native-worker-notices.d.ts +15 -0
- package/lib/types/host/native-worker.d.ts +44 -0
- package/lib/types/host/native-workflow.d.ts +41 -0
- package/lib/types/host/route-transport.d.ts +22 -0
- package/lib/types/host/settings.d.ts +39 -0
- package/lib/types/host/shell.d.ts +11 -0
- package/lib/types/host/status.d.ts +7 -0
- package/lib/types/host/subagent-seam.d.ts +10 -0
- package/lib/types/host/tool-table.d.ts +35 -0
- package/lib/types/host/worker-terminal.d.ts +68 -0
- package/lib/types/host/workspace-guard.d.ts +13 -0
- package/lib/types/host/workspace.d.ts +52 -0
- package/lib/types/identity.d.ts +64 -0
- package/lib/types/index.d.ts +34 -0
- package/lib/types/message-source.d.ts +29 -0
- package/lib/types/profile/compactor.d.ts +5 -0
- package/lib/types/profile/keepalive.d.ts +3 -0
- package/lib/types/profile/resolve.d.ts +17 -0
- package/lib/types/prompts.d.ts +8 -0
- package/lib/types/recovery/reconcile.d.ts +28 -0
- package/lib/types/review/dispatch.d.ts +20 -0
- package/lib/types/review/protocol.d.ts +51 -0
- package/lib/types/review/ticket.d.ts +32 -0
- package/lib/types/status.d.ts +88 -0
- package/lib/types/store-migration.d.ts +29 -0
- package/lib/types/task/deletion.d.ts +64 -0
- package/lib/types/task/reducer.d.ts +6 -0
- package/lib/types/task/sqlite-store.d.ts +71 -0
- package/lib/types/task/state.d.ts +51 -0
- package/lib/types/task/status-index.d.ts +28 -0
- package/lib/types/task/store.d.ts +48 -0
- package/lib/types/usage/budget.d.ts +27 -0
- package/lib/types/usage/ledger.d.ts +66 -0
- package/lib/types/usage/normalize.d.ts +50 -0
- package/lib/types/usage/pricing.d.ts +18 -0
- package/package.json +178 -4
- package/profiles/fusion-auto.example.json +45 -0
- package/prompts/compact.md +7 -0
- package/prompts/lead.md +42 -0
- package/prompts/model-lead.md +13 -0
- package/prompts/model-worker.md +5 -0
- package/prompts/worker.md +21 -0
- package/src/activity.ts +23 -0
- package/src/approval/logical.ts +44 -0
- package/src/benchmark/naive.ts +216 -0
- package/src/benchmark/native-run.ts +215 -0
- package/src/benchmark/output-limits.ts +33 -0
- package/src/bilingual.ts +2 -0
- package/src/client/ActivityRows.tsx +59 -0
- package/src/client/FusionCache.tsx +130 -0
- package/src/client/FusionHint.tsx +39 -0
- package/src/client/FusionHistory.tsx +247 -0
- package/src/client/FusionPanelIcon.tsx +39 -0
- package/src/client/FusionRecovery.tsx +69 -0
- package/src/client/FusionSettings.tsx +205 -0
- package/src/client/FusionStatus.tsx +103 -0
- package/src/client/FusionTool.tsx +66 -0
- package/src/client/activity-model.ts +31 -0
- package/src/client/i18n.ts +30 -0
- package/src/client/index.ts +50 -0
- package/src/client/settings-events.ts +3 -0
- package/src/client/status-polling.ts +88 -0
- package/src/context/guard.ts +81 -0
- package/src/context/handoff.ts +28 -0
- package/src/contracts.ts +449 -0
- package/src/digest.ts +28 -0
- package/src/dsh-fusion.ts +321 -0
- package/src/errors.ts +34 -0
- package/src/evidence/gate.ts +290 -0
- package/src/evidence/receipts.ts +175 -0
- package/src/execution/write-lease.ts +187 -0
- package/src/host/bindings.ts +91 -0
- package/src/host/cache-defaults.ts +103 -0
- package/src/host/cache-policy.ts +168 -0
- package/src/host/change-evidence.ts +156 -0
- package/src/host/compaction-routing.ts +46 -0
- package/src/host/coordinator.ts +2160 -0
- package/src/host/evidence-page.ts +25 -0
- package/src/host/history.ts +114 -0
- package/src/host/http.ts +30 -0
- package/src/host/model-output.ts +22 -0
- package/src/host/native-activity.ts +199 -0
- package/src/host/native-approvals.ts +196 -0
- package/src/host/native-auxiliary.ts +19 -0
- package/src/host/native-budget.ts +112 -0
- package/src/host/native-checks.ts +474 -0
- package/src/host/native-compaction.ts +34 -0
- package/src/host/native-context.ts +204 -0
- package/src/host/native-effects.ts +260 -0
- package/src/host/native-evidence.ts +31 -0
- package/src/host/native-exploration.ts +114 -0
- package/src/host/native-keepalive.ts +292 -0
- package/src/host/native-model-control.ts +298 -0
- package/src/host/native-on-demand-context.ts +74 -0
- package/src/host/native-request.ts +12 -0
- package/src/host/native-review.ts +51 -0
- package/src/host/native-role-sandbox.ts +171 -0
- package/src/host/native-scopes.ts +203 -0
- package/src/host/native-selection.ts +110 -0
- package/src/host/native-task-context.ts +264 -0
- package/src/host/native-usage.ts +121 -0
- package/src/host/native-worker-brief.ts +122 -0
- package/src/host/native-worker-notices.ts +72 -0
- package/src/host/native-worker.ts +215 -0
- package/src/host/native-workflow.ts +103 -0
- package/src/host/route-transport.ts +54 -0
- package/src/host/settings.ts +93 -0
- package/src/host/shell.ts +13 -0
- package/src/host/status.ts +112 -0
- package/src/host/subagent-seam.ts +20 -0
- package/src/host/tool-table.ts +56 -0
- package/src/host/worker-terminal.ts +91 -0
- package/src/host/workspace-guard.ts +17 -0
- package/src/host/workspace.ts +184 -0
- package/src/identity.ts +76 -0
- package/src/index.ts +50 -0
- package/src/message-source.ts +31 -0
- package/src/profile/compactor.ts +26 -0
- package/src/profile/keepalive.ts +13 -0
- package/src/profile/resolve.ts +131 -0
- package/src/prompts.ts +32 -0
- package/src/recovery/reconcile.ts +38 -0
- package/src/review/dispatch.ts +48 -0
- package/src/review/protocol.ts +212 -0
- package/src/review/ticket.ts +166 -0
- package/src/status.ts +55 -0
- package/src/store-migration.ts +65 -0
- package/src/task/deletion.ts +105 -0
- package/src/task/reducer.ts +679 -0
- package/src/task/sqlite-store.ts +386 -0
- package/src/task/state.ts +72 -0
- package/src/task/status-index.ts +198 -0
- package/src/task/store.ts +287 -0
- package/src/usage/budget.ts +88 -0
- package/src/usage/ledger.ts +218 -0
- package/src/usage/normalize.ts +308 -0
- package/src/usage/pricing.ts +95 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { RoleContextPolicy, TokenMeasurement } from '../contracts.js'
|
|
2
|
+
import { digestOf } from '../digest.js'
|
|
3
|
+
import { FUSION_CONTEXT_BUDGET, FusionError } from '../errors.js'
|
|
4
|
+
|
|
5
|
+
export interface RequestParts {
|
|
6
|
+
systemTokens: number
|
|
7
|
+
messageTokens: number
|
|
8
|
+
toolSchemaTokens: number
|
|
9
|
+
cacheReadTokens: number
|
|
10
|
+
reservedOutputTokens: number
|
|
11
|
+
contextWindow: number
|
|
12
|
+
quality: TokenMeasurement['quality']
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function safetyTokens(contextWindow: number, policy: RoleContextPolicy, quality: TokenMeasurement['quality']): number {
|
|
16
|
+
const fromFraction = Math.ceil(contextWindow * policy.safetyFraction)
|
|
17
|
+
const base = Math.max(policy.minSafetyTokens, fromFraction)
|
|
18
|
+
if (quality === 'heuristic') return Math.max(base, Math.ceil(base * 1.25))
|
|
19
|
+
return base
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Output reservation that fits the disclosed window. Leaves half the usable window for input. */
|
|
23
|
+
export function fittedOutputReservation(contextWindow: number): number {
|
|
24
|
+
if (!Number.isSafeInteger(contextWindow) || contextWindow < 1) throw new Error('The configured model does not disclose a usable context window')
|
|
25
|
+
const safety = Math.max(2_048, Math.ceil(contextWindow * 0.05))
|
|
26
|
+
const usable = contextWindow - safety
|
|
27
|
+
if (usable < 2) throw new Error('The configured model context window cannot reserve output')
|
|
28
|
+
return Math.floor(usable / 2)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function requestOutputReservation(contextWindow: number, requested?: number, adapterDefault?: number,
|
|
32
|
+
testedCap?: number, fallback = 8_000): number {
|
|
33
|
+
const fitted = fittedOutputReservation(contextWindow)
|
|
34
|
+
for (const limit of [requested, adapterDefault, testedCap, fallback]) {
|
|
35
|
+
if (limit !== undefined && (!Number.isSafeInteger(limit) || limit < 1)) throw new Error('Invalid model output token limit')
|
|
36
|
+
}
|
|
37
|
+
// The context window alone does not disclose a provider's output allowance.
|
|
38
|
+
// RC.2 exposes configured defaults, not a universal maximum-output field.
|
|
39
|
+
// With no disclosed/tested cap, use a bounded policy default, never half of
|
|
40
|
+
// a million-token input window as a fabricated provider output allowance.
|
|
41
|
+
return Math.min(requested ?? adapterDefault ?? testedCap ?? fallback,
|
|
42
|
+
adapterDefault ?? Infinity, testedCap ?? Infinity, fitted)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function inputBudget(policy: RoleContextPolicy, contextWindow: number, maxOutput: number, quality: TokenMeasurement['quality'] = 'exact'): number {
|
|
46
|
+
const safety = safetyTokens(contextWindow, policy, quality)
|
|
47
|
+
const hard = contextWindow - maxOutput - safety
|
|
48
|
+
return Math.max(0, Math.min(policy.targetInputTokens, hard))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function measureRequest(parts: RequestParts, policy: RoleContextPolicy): TokenMeasurement {
|
|
52
|
+
const inputTokens = parts.systemTokens + parts.messageTokens + parts.toolSchemaTokens + parts.cacheReadTokens
|
|
53
|
+
return {
|
|
54
|
+
inputTokens,
|
|
55
|
+
reservedOutputTokens: parts.reservedOutputTokens,
|
|
56
|
+
contextWindow: parts.contextWindow,
|
|
57
|
+
safetyTokens: safetyTokens(parts.contextWindow, policy, parts.quality),
|
|
58
|
+
quality: parts.quality,
|
|
59
|
+
requestDigest: digestOf(parts),
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function assertFinalBudget(measurement: TokenMeasurement, policy: RoleContextPolicy): void {
|
|
64
|
+
const budget = inputBudget(policy, measurement.contextWindow, measurement.reservedOutputTokens, measurement.quality)
|
|
65
|
+
if (measurement.inputTokens > budget) {
|
|
66
|
+
throw new FusionError(
|
|
67
|
+
FUSION_CONTEXT_BUDGET,
|
|
68
|
+
`final request ${measurement.inputTokens} exceeds budget ${budget} (${measurement.quality})`,
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface RecoveryAttempt {
|
|
74
|
+
readonly projectionTokens: number
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function boundedOverflowRecovery(attempts: readonly RecoveryAttempt[]): 'continue' | 'needs-decision' {
|
|
78
|
+
if (attempts.length > 2) return 'needs-decision'
|
|
79
|
+
if (attempts.length === 2 && attempts[1]!.projectionTokens >= attempts[0]!.projectionTokens) return 'needs-decision'
|
|
80
|
+
return 'continue'
|
|
81
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upper bound on recent conversation text carried into a Sidekick brief after compaction: about 10k tokens,
|
|
3
|
+
* enough for the latest exchanges while leaving most of the Sidekick's context for the task itself.
|
|
4
|
+
*/
|
|
5
|
+
export const HANDOFF_CONTENT_BYTES = 40_000
|
|
6
|
+
|
|
7
|
+
/** Content bytes only: envelopes and independently retained task facts are extra. */
|
|
8
|
+
export function recentHandoffSuffix<T extends { readonly content: string }>(records: readonly T[]) {
|
|
9
|
+
const lengths = records.map(record => Buffer.byteLength(record.content, 'utf8'))
|
|
10
|
+
let first = records.length, remaining = HANDOFF_CONTENT_BYTES
|
|
11
|
+
for (let index = records.length - 1; index >= 0; index--) {
|
|
12
|
+
const bytes = lengths[index]!
|
|
13
|
+
// Keep the newest whole record, even when it alone exceeds the allowance.
|
|
14
|
+
// Stop at the first older non-fitting record; do not skip it to fill gaps.
|
|
15
|
+
if (index !== records.length - 1 && bytes > remaining) break
|
|
16
|
+
first = index
|
|
17
|
+
remaining = Math.max(0, remaining - bytes)
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
policy: 'recent-whole-records-utf8-v1' as const,
|
|
21
|
+
contentByteBudget: HANDOFF_CONTENT_BYTES,
|
|
22
|
+
totalRecords: records.length,
|
|
23
|
+
omittedRecords: first,
|
|
24
|
+
totalContentBytes: lengths.reduce((sum, bytes) => sum + bytes, 0),
|
|
25
|
+
retainedContentBytes: lengths.slice(first).reduce((sum, bytes) => sum + bytes, 0),
|
|
26
|
+
records: records.slice(first),
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/contracts.ts
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import type {} from './message-source.js'
|
|
2
|
+
/**
|
|
3
|
+
* Plugin-domain contracts for dsh-fusion.
|
|
4
|
+
* These are proposed types, not existing DSH API declarations.
|
|
5
|
+
* Convert DSH branded IDs only in a version-pinned bridge (not landed).
|
|
6
|
+
*/
|
|
7
|
+
export type Brand<T, B extends string> = T & { readonly __brand: B }
|
|
8
|
+
export type TaskId = Brand<string, 'FusionTaskId'>
|
|
9
|
+
export type SessionId = Brand<string, 'DshSessionId'>
|
|
10
|
+
export type WorkOrderId = Brand<string, 'FusionWorkOrderId'>
|
|
11
|
+
export type OperationId = Brand<string, 'FusionOperationId'>
|
|
12
|
+
export type ArtifactId = Brand<string, 'FusionArtifactId'>
|
|
13
|
+
export type SnapshotId = Brand<string, 'FusionSnapshotId'>
|
|
14
|
+
export type Digest = Brand<string, 'Sha256Hex'>
|
|
15
|
+
export type UsdDecimal = Brand<string, 'UsdDecimal'>
|
|
16
|
+
export type EpochId = Brand<string, 'ContextEpochId'>
|
|
17
|
+
|
|
18
|
+
function asBrand<B extends string>(raw: string): Brand<string, B> {
|
|
19
|
+
if (!raw || raw.includes('\0')) throw new TypeError('empty branded id')
|
|
20
|
+
return raw as Brand<string, B>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const TaskId = (raw: string): TaskId => asBrand(raw)
|
|
24
|
+
export const SessionId = (raw: string): SessionId => asBrand(raw)
|
|
25
|
+
export const WorkOrderId = (raw: string): WorkOrderId => asBrand(raw)
|
|
26
|
+
export const OperationId = (raw: string): OperationId => asBrand(raw)
|
|
27
|
+
export const ArtifactId = (raw: string): ArtifactId => asBrand(raw)
|
|
28
|
+
export const SnapshotId = (raw: string): SnapshotId => asBrand(raw)
|
|
29
|
+
export const EpochId = (raw: string): EpochId => asBrand(raw)
|
|
30
|
+
export const UsdDecimal = (raw: string): UsdDecimal => raw as UsdDecimal
|
|
31
|
+
|
|
32
|
+
export interface PhysicalRoute {
|
|
33
|
+
provider: string
|
|
34
|
+
model: string
|
|
35
|
+
reasoningEffort?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type RunSelection =
|
|
39
|
+
| { kind: 'model'; route: PhysicalRoute }
|
|
40
|
+
| { kind: 'profile'; profileId: string; requestedVersion?: string }
|
|
41
|
+
|
|
42
|
+
export type Role = 'lead' | 'worker'
|
|
43
|
+
export type Phase =
|
|
44
|
+
| 'READY'
|
|
45
|
+
| 'PLANNING'
|
|
46
|
+
| 'DIRECT'
|
|
47
|
+
| 'WORKER_RUNNING'
|
|
48
|
+
| 'REVIEWING'
|
|
49
|
+
| 'REWORK'
|
|
50
|
+
| 'WAITING_APPROVAL'
|
|
51
|
+
| 'WAITING_USER'
|
|
52
|
+
| 'WAITING_BUDGET'
|
|
53
|
+
| 'RECOVERING'
|
|
54
|
+
| 'PAUSED'
|
|
55
|
+
| 'STOPPING'
|
|
56
|
+
| 'NEEDS_DECISION'
|
|
57
|
+
| 'FAILED'
|
|
58
|
+
| 'COMPLETED'
|
|
59
|
+
| 'CANCELLED'
|
|
60
|
+
|
|
61
|
+
export type VerificationLevel = 'unverified' | 'partial' | 'verified'
|
|
62
|
+
export type ExecutionIntent = 'DIRECT' | 'DELEGATE'
|
|
63
|
+
|
|
64
|
+
export interface SourceRef {
|
|
65
|
+
sessionId: SessionId
|
|
66
|
+
eventSeq: number
|
|
67
|
+
messageId?: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ArtifactRef {
|
|
71
|
+
id: ArtifactId
|
|
72
|
+
digest: Digest
|
|
73
|
+
ownerTaskId: TaskId
|
|
74
|
+
mediaType: string
|
|
75
|
+
bytes: number
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface Constraint {
|
|
79
|
+
id: string
|
|
80
|
+
text: string
|
|
81
|
+
source: SourceRef
|
|
82
|
+
provenance: 'user' | 'policy' | 'inferred'
|
|
83
|
+
mandatory: boolean
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface AcceptanceCriterion {
|
|
87
|
+
id: string
|
|
88
|
+
description: string
|
|
89
|
+
verificationKind: 'test' | 'static-check' | 'human' | 'review'
|
|
90
|
+
mandatory: boolean
|
|
91
|
+
/** Frozen verifier id. When set, only that registered verifier may satisfy the criterion. */
|
|
92
|
+
verifierId?: string
|
|
93
|
+
/** Frozen CheckPlan id. Test/static criteria require a registered plan. */
|
|
94
|
+
planId?: string
|
|
95
|
+
planDigest?: Digest
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface ResourcePolicy {
|
|
99
|
+
maxWorkerSteps: number
|
|
100
|
+
maxReworkRounds: number
|
|
101
|
+
maxCapabilityUpgrades: number
|
|
102
|
+
modelSpendAuthorizationId?: string
|
|
103
|
+
maxApiCostUsd?: UsdDecimal
|
|
104
|
+
/** Legacy serialized planning field, not enforced by the native coordinator.
|
|
105
|
+
* Native runs use the separately approved request/reservation ledger and omit
|
|
106
|
+
* this field; old work orders retain it as historical data, not a hard cap. */
|
|
107
|
+
maxTotalOutputTokens?: number
|
|
108
|
+
commandMaxSeconds: number
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface WorkOrder {
|
|
112
|
+
schemaVersion: 1
|
|
113
|
+
/** Older work orders are implementation work. Exploration never grants a writer. */
|
|
114
|
+
mode?: 'explore' | 'implement' | 'text'
|
|
115
|
+
taskId: TaskId
|
|
116
|
+
id: WorkOrderId
|
|
117
|
+
operationId: OperationId
|
|
118
|
+
revision: number
|
|
119
|
+
goal: string
|
|
120
|
+
constraints: readonly Constraint[]
|
|
121
|
+
acceptance: readonly AcceptanceCriterion[]
|
|
122
|
+
allowedPaths: readonly string[]
|
|
123
|
+
forbiddenActions: readonly string[]
|
|
124
|
+
baseSnapshot: SnapshotId
|
|
125
|
+
evidence: readonly ArtifactRef[]
|
|
126
|
+
decisions: readonly { text: string; source: SourceRef }[]
|
|
127
|
+
uncertainties: readonly string[]
|
|
128
|
+
policy: ResourcePolicy
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface ExplorationSource {
|
|
132
|
+
path: string
|
|
133
|
+
startLine: number
|
|
134
|
+
endLine: number
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** File excerpts are captured by the Host, not accepted from model narration. */
|
|
138
|
+
export interface ExplorationReport {
|
|
139
|
+
schemaVersion: 1
|
|
140
|
+
workOrderId: WorkOrderId
|
|
141
|
+
revision: number
|
|
142
|
+
status: WorkerReport['status']
|
|
143
|
+
summary: string
|
|
144
|
+
unresolved: readonly string[]
|
|
145
|
+
snapshot: SnapshotId
|
|
146
|
+
sources: readonly (ExplorationSource & { fileDigest: string; excerpt: ArtifactRef })[]
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface CoverageClaim {
|
|
150
|
+
criterionId: string
|
|
151
|
+
state: 'satisfied' | 'not-satisfied' | 'not-verified'
|
|
152
|
+
evidenceIds: readonly ArtifactId[]
|
|
153
|
+
explanation: string
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface WorkerReport {
|
|
157
|
+
schemaVersion: 1
|
|
158
|
+
workOrderId: WorkOrderId
|
|
159
|
+
revision: number
|
|
160
|
+
status: 'completed' | 'blocked' | 'needs-decision'
|
|
161
|
+
summary: string
|
|
162
|
+
snapshot: SnapshotId
|
|
163
|
+
changeManifest: ArtifactRef
|
|
164
|
+
coverage: readonly CoverageClaim[]
|
|
165
|
+
verification: readonly ArtifactRef[]
|
|
166
|
+
unresolved: readonly string[]
|
|
167
|
+
questions: readonly string[]
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export type EvidenceKind = 'tool' | 'human-decision' | 'review-accept'
|
|
171
|
+
|
|
172
|
+
export interface ToolEvidence {
|
|
173
|
+
schemaVersion: 1
|
|
174
|
+
taskId: TaskId
|
|
175
|
+
operationId: OperationId
|
|
176
|
+
actor: SessionId
|
|
177
|
+
nativeToolCallId: string
|
|
178
|
+
argvDigest: Digest
|
|
179
|
+
cwdDigest: Digest
|
|
180
|
+
inputSnapshot: SnapshotId
|
|
181
|
+
outputSnapshot?: SnapshotId
|
|
182
|
+
exitCode: number | null
|
|
183
|
+
startedAt: string
|
|
184
|
+
endedAt: string | null
|
|
185
|
+
stdout: ArtifactRef
|
|
186
|
+
stderr: ArtifactRef
|
|
187
|
+
state: 'started' | 'completed' | 'cancelled' | 'outcome-unknown'
|
|
188
|
+
/** Default `tool`. Human/review criteria require their own kinds. */
|
|
189
|
+
kind?: EvidenceKind
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface ContextCheckpoint {
|
|
193
|
+
schemaVersion: 1
|
|
194
|
+
epochId: EpochId
|
|
195
|
+
taskId: TaskId
|
|
196
|
+
revision: number
|
|
197
|
+
goal: string
|
|
198
|
+
mandatoryConstraints: readonly Constraint[]
|
|
199
|
+
snapshot: SnapshotId
|
|
200
|
+
decisions: readonly { text: string; source: SourceRef }[]
|
|
201
|
+
coverage: readonly CoverageClaim[]
|
|
202
|
+
pendingApprovalIds: readonly string[]
|
|
203
|
+
openQuestions: readonly string[]
|
|
204
|
+
evidence: readonly ArtifactRef[]
|
|
205
|
+
sourceSurfaceSeqs: readonly number[]
|
|
206
|
+
digest: Digest
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface RoleContextPolicy {
|
|
210
|
+
targetInputTokens: number
|
|
211
|
+
reserveOutputTokens: number
|
|
212
|
+
minSafetyTokens: number
|
|
213
|
+
safetyFraction: number
|
|
214
|
+
compactToFraction: number
|
|
215
|
+
evidenceReadTokens: number
|
|
216
|
+
maxToolVisibleTokens: number
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface PairProfile {
|
|
220
|
+
schemaVersion: 1
|
|
221
|
+
id: string
|
|
222
|
+
version: string
|
|
223
|
+
digest: Digest
|
|
224
|
+
enabled: boolean
|
|
225
|
+
quality: 'experimental' | 'validated' | 'deprecated'
|
|
226
|
+
/** Missing on frozen v1 sessions; only new selections use native model behavior. */
|
|
227
|
+
interactionMode?: 'model-like'
|
|
228
|
+
/** Frozen per selection. Absence preserves pre-enforcement sessions. */
|
|
229
|
+
workflowPolicy?: 'enforced-v1' | 'enforced-v2' | 'enforced-v3'
|
|
230
|
+
lead: PhysicalRoute
|
|
231
|
+
worker: PhysicalRoute
|
|
232
|
+
workerUpgradePath: readonly PhysicalRoute[]
|
|
233
|
+
promptDigests: Readonly<Record<'lead' | 'worker' | 'compact', Digest>>
|
|
234
|
+
context: Readonly<Record<Role, RoleContextPolicy>>
|
|
235
|
+
/** Explicit per-route opt-in; absence preserves old frozen profile digests. */
|
|
236
|
+
/** Per role: on, off, or 'auto' (armed once the route reports cache reads). Unset: Lead auto, Worker off. */
|
|
237
|
+
cacheKeepalive?: Readonly<Record<Role, boolean | 'auto'>>
|
|
238
|
+
/** Optional explicit summary route; absence preserves the role model and old digests. */
|
|
239
|
+
compactor?: { readonly route: PhysicalRoute; readonly maxOutputTokens: number }
|
|
240
|
+
dataPolicyId: string
|
|
241
|
+
evidenceCampaignIds: readonly string[]
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface LogicalApproval {
|
|
245
|
+
id: string
|
|
246
|
+
taskId: TaskId
|
|
247
|
+
revision: number
|
|
248
|
+
operationId: OperationId
|
|
249
|
+
nativeRequestId?: string
|
|
250
|
+
requestingAgent?: SessionId
|
|
251
|
+
toolName?: string
|
|
252
|
+
callId?: string
|
|
253
|
+
role?: Role
|
|
254
|
+
reason?: string
|
|
255
|
+
argsDigest: Digest
|
|
256
|
+
snapshot: SnapshotId
|
|
257
|
+
permissionPolicyDigest: Digest
|
|
258
|
+
state: 'pending' | 'approved' | 'rejected' | 'withdrawn' | 'consumed'
|
|
259
|
+
decisionBy?: 'human' | 'preauthorized-policy'
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface WriteLease {
|
|
263
|
+
workspaceId: string
|
|
264
|
+
holder: SessionId
|
|
265
|
+
taskId: TaskId
|
|
266
|
+
generation: number
|
|
267
|
+
activeOperationIds: readonly OperationId[]
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export interface TokenMeasurement {
|
|
271
|
+
inputTokens: number
|
|
272
|
+
reservedOutputTokens: number
|
|
273
|
+
contextWindow: number
|
|
274
|
+
safetyTokens: number
|
|
275
|
+
quality: 'exact' | 'provider-estimated' | 'heuristic'
|
|
276
|
+
requestDigest: Digest
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export type UsageApplicability = 'not_applicable' | 'unknown' | 'known'
|
|
280
|
+
|
|
281
|
+
export interface NormalizedUsage {
|
|
282
|
+
uncachedInput: number | null
|
|
283
|
+
cacheRead: number | null
|
|
284
|
+
cacheReadStatus: UsageApplicability
|
|
285
|
+
cacheWrite: Readonly<Record<string, number | null>>
|
|
286
|
+
cacheWriteStatus: Readonly<Record<string, UsageApplicability>>
|
|
287
|
+
output: number | null
|
|
288
|
+
/** Informational subset already inside `output`. Do not charge again. */
|
|
289
|
+
reasoningOutputSubset: number | null
|
|
290
|
+
/** Separately billed reasoning. Charge at the reasoning or output rate. */
|
|
291
|
+
reasoningSeparate: number | null
|
|
292
|
+
/** Reasoning tokens whose include-in-output contract is unknown. Never assume free or included. */
|
|
293
|
+
reasoningUnspecified: number | null
|
|
294
|
+
/** Explicit billing relation. Never infer not_applicable from a null quantity. */
|
|
295
|
+
reasoningBilling: ReasoningBilling
|
|
296
|
+
/** Null when the provider contract was not stated. */
|
|
297
|
+
inputIncludesCacheRead: boolean | null
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export interface ReasoningBilling {
|
|
301
|
+
kind: 'included' | 'separate' | 'unknown' | 'not_applicable'
|
|
302
|
+
tokens: number | null
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export interface UsageAttempt {
|
|
306
|
+
attemptId: string
|
|
307
|
+
taskId: TaskId
|
|
308
|
+
agentId: SessionId
|
|
309
|
+
role: Role
|
|
310
|
+
purpose: 'conversation' | 'compaction' | 'review' | 'routing' | 'other'
|
|
311
|
+
route: PhysicalRoute
|
|
312
|
+
epochId: EpochId
|
|
313
|
+
serviceTier?: string
|
|
314
|
+
rawUsage?: ArtifactRef
|
|
315
|
+
normalized: NormalizedUsage
|
|
316
|
+
priceCardDigest?: Digest
|
|
317
|
+
apiEquivalentUsd: UsdDecimal | null
|
|
318
|
+
actualBilledUsd: UsdDecimal | null
|
|
319
|
+
status: 'pending' | 'complete' | 'partial' | 'unknown'
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export interface EventEnvelope<T extends string, P> {
|
|
323
|
+
schemaVersion: 1
|
|
324
|
+
id: string
|
|
325
|
+
taskId: TaskId
|
|
326
|
+
seq: number
|
|
327
|
+
revision: number
|
|
328
|
+
type: T
|
|
329
|
+
createdAt: string
|
|
330
|
+
causeId: string
|
|
331
|
+
payload: P
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export interface ReviewGate {
|
|
335
|
+
taskId: TaskId
|
|
336
|
+
workOrderId: WorkOrderId
|
|
337
|
+
revision: number
|
|
338
|
+
reportDigest: Digest
|
|
339
|
+
snapshot: SnapshotId
|
|
340
|
+
reviewAttemptId: string
|
|
341
|
+
decision: 'accept' | 'rework' | 'needs-decision' | 'REVIEW_INCOMPLETE'
|
|
342
|
+
terminalStatus: 'accepting' | 'incomplete' | 'rework' | 'needs-decision' | 'legacy-unbound'
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export interface ReportSubject {
|
|
346
|
+
taskId: TaskId
|
|
347
|
+
workOrderId: WorkOrderId
|
|
348
|
+
revision: number
|
|
349
|
+
reportId: string
|
|
350
|
+
reportDigest: Digest
|
|
351
|
+
snapshot: SnapshotId
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export interface ReviewReadinessReceipt {
|
|
355
|
+
subject: ReportSubject
|
|
356
|
+
receiptId: string
|
|
357
|
+
receiptDigest: Digest
|
|
358
|
+
verdict: 'review-ready'
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export interface ReviewTicket {
|
|
362
|
+
id: string
|
|
363
|
+
requestId: string
|
|
364
|
+
generation: number
|
|
365
|
+
subject: ReportSubject
|
|
366
|
+
validationDigest: Digest
|
|
367
|
+
/** Digest of the work order frozen at request time. */
|
|
368
|
+
workOrderDigest: Digest
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export interface ReviewResultV2 {
|
|
372
|
+
ticketId: string
|
|
373
|
+
requestId: string
|
|
374
|
+
generation: number
|
|
375
|
+
subject: ReportSubject
|
|
376
|
+
terminalEvidenceRef: string
|
|
377
|
+
decision: 'accept' | 'rework' | 'needs-decision' | 'REVIEW_INCOMPLETE'
|
|
378
|
+
protocolValid: boolean
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export interface ControlState {
|
|
382
|
+
mode: 'running' | 'paused' | 'stop-requested' | 'cancelled' | 'completed'
|
|
383
|
+
pendingApprovalIds: readonly string[]
|
|
384
|
+
budgetBlocked: boolean
|
|
385
|
+
recovering: boolean
|
|
386
|
+
outcomeUnknown: boolean
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export interface ReviewCompletedPayload {
|
|
390
|
+
decision: 'accept' | 'rework' | 'needs-decision' | 'REVIEW_INCOMPLETE'
|
|
391
|
+
/** Required for a new completion. Decision-only payloads are legacy-unbound. */
|
|
392
|
+
binding?: ReviewResultV2
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export type FusionEvent =
|
|
396
|
+
| EventEnvelope<'task/created', { parent: SessionId; selection: RunSelection }>
|
|
397
|
+
| EventEnvelope<'requirements/revised', { reason: string }>
|
|
398
|
+
| EventEnvelope<'profile/frozen', { digest: Digest; profileId: string; version: string }>
|
|
399
|
+
| EventEnvelope<'intent/chosen', { intent: ExecutionIntent }>
|
|
400
|
+
| EventEnvelope<'work-order/prepared', { order: WorkOrder; reservedChild: SessionId }>
|
|
401
|
+
| EventEnvelope<'work-order/acceptance-amended', { workOrderId: WorkOrderId; acceptance: readonly AcceptanceCriterion[]; reason: string }>
|
|
402
|
+
| EventEnvelope<'work-order/scope-expanded', { workOrderId: WorkOrderId; allowedPaths: readonly string[]; added: readonly string[]; reason: string }>
|
|
403
|
+
| EventEnvelope<'child/accepted', { operationId: OperationId; child: SessionId; messageId: string }>
|
|
404
|
+
| EventEnvelope<'child/claimed', { operationId: OperationId; child: SessionId }>
|
|
405
|
+
| EventEnvelope<'exploration/recorded', { report: ExplorationReport }>
|
|
406
|
+
| EventEnvelope<'report/submitted', { operationId: OperationId; report: WorkerReport }>
|
|
407
|
+
| EventEnvelope<'report/validated', { operationId: OperationId; report: WorkerReport }>
|
|
408
|
+
| EventEnvelope<'review/requested', { ticket: ReviewTicket }>
|
|
409
|
+
| EventEnvelope<'review/completed', ReviewCompletedPayload>
|
|
410
|
+
| EventEnvelope<'lease/acquired', { lease: WriteLease }>
|
|
411
|
+
| EventEnvelope<'lease/released', { workspaceId: string; generation: number }>
|
|
412
|
+
| EventEnvelope<'approval/pending', { approval: LogicalApproval }>
|
|
413
|
+
| EventEnvelope<'approval/answered', { approvalId: string; state: 'approved' | 'rejected' | 'withdrawn' }>
|
|
414
|
+
| EventEnvelope<'budget/blocked', { reason: string }>
|
|
415
|
+
| EventEnvelope<'budget/unblocked', { authorizationId: string }>
|
|
416
|
+
| EventEnvelope<'checkpoint/committed', { checkpoint: ContextCheckpoint }>
|
|
417
|
+
| EventEnvelope<'recovery/needed', { reason: string }>
|
|
418
|
+
| EventEnvelope<'recovery/reconciled', { snapshot: SnapshotId; evidenceRef: string }>
|
|
419
|
+
| EventEnvelope<'effect/outcome-unknown', { operationId: OperationId; reason: string }>
|
|
420
|
+
| EventEnvelope<'effects/reconciled', { snapshot: SnapshotId; evidenceRef: string; quiescent: true }>
|
|
421
|
+
| EventEnvelope<'task/stop-requested', { intent: 'pause' | 'cancel' }>
|
|
422
|
+
| EventEnvelope<'task/paused', { reason: string }>
|
|
423
|
+
| EventEnvelope<'task/resumed', { reason: string }>
|
|
424
|
+
| EventEnvelope<'task/completed', { snapshot: SnapshotId; verification: VerificationLevel }>
|
|
425
|
+
| EventEnvelope<'task/cancelled', { reason: string }>
|
|
426
|
+
|
|
427
|
+
export type OutboxState =
|
|
428
|
+
| 'prepared'
|
|
429
|
+
| 'dispatched'
|
|
430
|
+
| 'accepted'
|
|
431
|
+
| 'claimed'
|
|
432
|
+
| 'result-recorded'
|
|
433
|
+
| 'delivered'
|
|
434
|
+
| 'acknowledged'
|
|
435
|
+
|
|
436
|
+
export interface OutboxRow {
|
|
437
|
+
operationId: OperationId
|
|
438
|
+
taskId: TaskId
|
|
439
|
+
kind: string
|
|
440
|
+
state: OutboxState
|
|
441
|
+
reservedChild?: SessionId
|
|
442
|
+
nativeMessageId?: string
|
|
443
|
+
resultDigest?: Digest
|
|
444
|
+
payloadRef?: string
|
|
445
|
+
requestId?: string
|
|
446
|
+
ticket?: ReviewTicket
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export const TERMINAL_PHASES = new Set<Phase>(['COMPLETED', 'CANCELLED', 'FAILED'])
|
package/src/digest.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import type { Digest } from './contracts.js'
|
|
3
|
+
|
|
4
|
+
export function sha256Hex(data: string | Uint8Array): Digest {
|
|
5
|
+
return createHash('sha256').update(data).digest('hex') as Digest
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function sortKeys(value: unknown): unknown {
|
|
9
|
+
if (Array.isArray(value)) return value.map(sortKeys)
|
|
10
|
+
if (value && typeof value === 'object') {
|
|
11
|
+
const record = value as Record<string, unknown>
|
|
12
|
+
return Object.fromEntries(
|
|
13
|
+
Object.keys(record)
|
|
14
|
+
.filter(key => !key.startsWith('_'))
|
|
15
|
+
.sort()
|
|
16
|
+
.map(key => [key, sortKeys(record[key])]),
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
return value
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function canonicalJson(value: unknown): string {
|
|
23
|
+
return JSON.stringify(sortKeys(value))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function digestOf(value: unknown): Digest {
|
|
27
|
+
return sha256Hex(canonicalJson(value))
|
|
28
|
+
}
|