@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,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proposed plugin-domain contracts, NOT existing DSH API declarations.
|
|
3
|
+
* No runtime behavior or authorization is supplied by these types.
|
|
4
|
+
* Convert DSH branded IDs only in the version-pinned bridge layer.
|
|
5
|
+
*/
|
|
6
|
+
export type Brand<T, B extends string> = T & { readonly __brand: B };
|
|
7
|
+
export type TaskId = Brand<string, 'FusionTaskId'>;
|
|
8
|
+
export type SessionId = Brand<string, 'DshSessionId'>;
|
|
9
|
+
export type WorkOrderId = Brand<string, 'FusionWorkOrderId'>;
|
|
10
|
+
export type OperationId = Brand<string, 'FusionOperationId'>;
|
|
11
|
+
export type ArtifactId = Brand<string, 'FusionArtifactId'>;
|
|
12
|
+
export type SnapshotId = Brand<string, 'FusionSnapshotId'>;
|
|
13
|
+
export type Digest = Brand<string, 'Sha256Hex'>;
|
|
14
|
+
/** Decimal string avoids binary-float money and is JSON serializable. */
|
|
15
|
+
export type UsdDecimal = Brand<string, 'UsdDecimal'>;
|
|
16
|
+
export type EpochId = Brand<string, 'ContextEpochId'>;
|
|
17
|
+
|
|
18
|
+
export interface PhysicalRoute {
|
|
19
|
+
provider: string;
|
|
20
|
+
model: string;
|
|
21
|
+
reasoningEffort?: string; // Validate against the owning adapter's opaque IDs.
|
|
22
|
+
}
|
|
23
|
+
export type RunSelection =
|
|
24
|
+
| { kind: 'model'; route: PhysicalRoute }
|
|
25
|
+
| { kind: 'profile'; profileId: string; requestedVersion?: string };
|
|
26
|
+
export type Role = 'lead' | 'worker';
|
|
27
|
+
export type Phase =
|
|
28
|
+
| 'READY' | 'PLANNING' | 'DIRECT' | 'WORKER_RUNNING' | 'REVIEWING'
|
|
29
|
+
| 'REWORK' | 'WAITING_APPROVAL' | 'WAITING_USER' | 'WAITING_BUDGET'
|
|
30
|
+
| 'RECOVERING' | 'PAUSED' | 'STOPPING' | 'NEEDS_DECISION'
|
|
31
|
+
| 'FAILED' | 'COMPLETED' | 'CANCELLED';
|
|
32
|
+
export type VerificationLevel = 'unverified' | 'partial' | 'verified';
|
|
33
|
+
|
|
34
|
+
export interface SourceRef {
|
|
35
|
+
sessionId: SessionId;
|
|
36
|
+
eventSeq: number;
|
|
37
|
+
messageId?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface ArtifactRef {
|
|
40
|
+
id: ArtifactId;
|
|
41
|
+
digest: Digest;
|
|
42
|
+
ownerTaskId: TaskId;
|
|
43
|
+
mediaType: string;
|
|
44
|
+
bytes: number;
|
|
45
|
+
}
|
|
46
|
+
export interface Constraint {
|
|
47
|
+
id: string;
|
|
48
|
+
text: string;
|
|
49
|
+
source: SourceRef;
|
|
50
|
+
provenance: 'user' | 'policy' | 'inferred';
|
|
51
|
+
mandatory: boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface AcceptanceCriterion {
|
|
54
|
+
id: string;
|
|
55
|
+
description: string;
|
|
56
|
+
verificationKind: 'test' | 'static-check' | 'human' | 'review';
|
|
57
|
+
mandatory: boolean;
|
|
58
|
+
verifierId?: string;
|
|
59
|
+
planId?: string;
|
|
60
|
+
planDigest?: Digest;
|
|
61
|
+
}
|
|
62
|
+
export interface ResourcePolicy {
|
|
63
|
+
maxWorkerSteps: number;
|
|
64
|
+
maxReworkRounds: number;
|
|
65
|
+
maxCapabilityUpgrades: number;
|
|
66
|
+
modelSpendAuthorizationId?: string;
|
|
67
|
+
maxApiCostUsd?: UsdDecimal;
|
|
68
|
+
maxTotalOutputTokens: number;
|
|
69
|
+
/** Human approval has no expiry; only machine execution has a deadline. */
|
|
70
|
+
commandMaxSeconds: number;
|
|
71
|
+
}
|
|
72
|
+
export interface WorkOrder {
|
|
73
|
+
schemaVersion: 1;
|
|
74
|
+
taskId: TaskId;
|
|
75
|
+
id: WorkOrderId;
|
|
76
|
+
operationId: OperationId;
|
|
77
|
+
revision: number;
|
|
78
|
+
goal: string;
|
|
79
|
+
constraints: readonly Constraint[];
|
|
80
|
+
acceptance: readonly AcceptanceCriterion[];
|
|
81
|
+
allowedPaths: readonly string[];
|
|
82
|
+
forbiddenActions: readonly string[];
|
|
83
|
+
baseSnapshot: SnapshotId;
|
|
84
|
+
evidence: readonly ArtifactRef[];
|
|
85
|
+
decisions: readonly { text: string; source: SourceRef }[];
|
|
86
|
+
uncertainties: readonly string[];
|
|
87
|
+
policy: ResourcePolicy;
|
|
88
|
+
}
|
|
89
|
+
export interface CoverageClaim {
|
|
90
|
+
criterionId: string;
|
|
91
|
+
state: 'satisfied' | 'not-satisfied' | 'not-verified';
|
|
92
|
+
evidenceIds: readonly ArtifactId[];
|
|
93
|
+
explanation: string;
|
|
94
|
+
}
|
|
95
|
+
export interface WorkerReport {
|
|
96
|
+
schemaVersion: 1;
|
|
97
|
+
workOrderId: WorkOrderId;
|
|
98
|
+
revision: number;
|
|
99
|
+
status: 'completed' | 'blocked' | 'needs-decision';
|
|
100
|
+
summary: string;
|
|
101
|
+
snapshot: SnapshotId;
|
|
102
|
+
changeManifest: ArtifactRef;
|
|
103
|
+
coverage: readonly CoverageClaim[];
|
|
104
|
+
verification: readonly ArtifactRef[];
|
|
105
|
+
unresolved: readonly string[];
|
|
106
|
+
questions: readonly string[];
|
|
107
|
+
}
|
|
108
|
+
export interface ToolEvidence {
|
|
109
|
+
schemaVersion: 1;
|
|
110
|
+
taskId: TaskId;
|
|
111
|
+
operationId: OperationId;
|
|
112
|
+
actor: SessionId;
|
|
113
|
+
nativeToolCallId: string;
|
|
114
|
+
argvDigest: Digest;
|
|
115
|
+
cwdDigest: Digest;
|
|
116
|
+
inputSnapshot: SnapshotId;
|
|
117
|
+
outputSnapshot?: SnapshotId;
|
|
118
|
+
exitCode: number | null;
|
|
119
|
+
startedAt: string;
|
|
120
|
+
endedAt: string | null;
|
|
121
|
+
stdout: ArtifactRef;
|
|
122
|
+
stderr: ArtifactRef;
|
|
123
|
+
state: 'started' | 'completed' | 'cancelled' | 'outcome-unknown';
|
|
124
|
+
kind?: 'tool' | 'human-decision' | 'review-accept';
|
|
125
|
+
}
|
|
126
|
+
export interface ContextCheckpoint {
|
|
127
|
+
schemaVersion: 1;
|
|
128
|
+
epochId: EpochId;
|
|
129
|
+
taskId: TaskId;
|
|
130
|
+
revision: number;
|
|
131
|
+
goal: string;
|
|
132
|
+
mandatoryConstraints: readonly Constraint[];
|
|
133
|
+
snapshot: SnapshotId;
|
|
134
|
+
decisions: readonly { text: string; source: SourceRef }[];
|
|
135
|
+
coverage: readonly CoverageClaim[];
|
|
136
|
+
pendingApprovalIds: readonly string[];
|
|
137
|
+
openQuestions: readonly string[];
|
|
138
|
+
evidence: readonly ArtifactRef[];
|
|
139
|
+
sourceSurfaceSeqs: readonly number[]; // Surface order, NOT numeric sorting.
|
|
140
|
+
digest: Digest;
|
|
141
|
+
}
|
|
142
|
+
export interface RoleContextPolicy {
|
|
143
|
+
targetInputTokens: number;
|
|
144
|
+
reserveOutputTokens: number;
|
|
145
|
+
minSafetyTokens: number;
|
|
146
|
+
safetyFraction: number;
|
|
147
|
+
compactToFraction: number;
|
|
148
|
+
evidenceReadTokens: number;
|
|
149
|
+
maxToolVisibleTokens: number;
|
|
150
|
+
}
|
|
151
|
+
export interface PairProfile {
|
|
152
|
+
schemaVersion: 1;
|
|
153
|
+
id: string;
|
|
154
|
+
version: string;
|
|
155
|
+
digest: Digest;
|
|
156
|
+
enabled: boolean;
|
|
157
|
+
quality: 'experimental' | 'validated' | 'deprecated';
|
|
158
|
+
lead: PhysicalRoute;
|
|
159
|
+
worker: PhysicalRoute;
|
|
160
|
+
workerUpgradePath: readonly PhysicalRoute[];
|
|
161
|
+
promptDigests: Readonly<Record<'lead' | 'worker' | 'compact', Digest>>;
|
|
162
|
+
context: Readonly<Record<Role, RoleContextPolicy>>;
|
|
163
|
+
/** Experimental auxiliary calls, explicitly enabled for the frozen routes. */
|
|
164
|
+
cacheKeepalive?: Readonly<Record<Role, boolean>>;
|
|
165
|
+
/** Optional explicit summary route. Missing means the current role model. */
|
|
166
|
+
compactor?: { readonly route: PhysicalRoute; readonly maxOutputTokens: number };
|
|
167
|
+
dataPolicyId: string;
|
|
168
|
+
evidenceCampaignIds: readonly string[];
|
|
169
|
+
}
|
|
170
|
+
export interface LogicalApproval {
|
|
171
|
+
id: string;
|
|
172
|
+
taskId: TaskId;
|
|
173
|
+
revision: number;
|
|
174
|
+
operationId: OperationId;
|
|
175
|
+
nativeRequestId?: string;
|
|
176
|
+
argsDigest: Digest;
|
|
177
|
+
snapshot: SnapshotId;
|
|
178
|
+
permissionPolicyDigest: Digest;
|
|
179
|
+
state: 'pending' | 'approved' | 'rejected' | 'withdrawn' | 'consumed';
|
|
180
|
+
decisionBy?: 'human' | 'preauthorized-policy';
|
|
181
|
+
/** No expiresAt: pending human work does not expire automatically. */
|
|
182
|
+
}
|
|
183
|
+
export interface WriteLease {
|
|
184
|
+
workspaceId: string;
|
|
185
|
+
holder: SessionId;
|
|
186
|
+
taskId: TaskId;
|
|
187
|
+
generation: number;
|
|
188
|
+
activeOperationIds: readonly OperationId[];
|
|
189
|
+
}
|
|
190
|
+
export interface TokenMeasurement {
|
|
191
|
+
inputTokens: number;
|
|
192
|
+
reservedOutputTokens: number;
|
|
193
|
+
contextWindow: number;
|
|
194
|
+
safetyTokens: number;
|
|
195
|
+
quality: 'exact' | 'provider-estimated' | 'heuristic';
|
|
196
|
+
requestDigest: Digest;
|
|
197
|
+
}
|
|
198
|
+
/** Mutually exclusive buckets. Null means unavailable, never zero. */
|
|
199
|
+
export type UsageApplicability = 'not_applicable' | 'unknown' | 'known';
|
|
200
|
+
export interface NormalizedUsage {
|
|
201
|
+
uncachedInput: number | null;
|
|
202
|
+
cacheRead: number | null;
|
|
203
|
+
cacheReadStatus: UsageApplicability;
|
|
204
|
+
cacheWrite: Readonly<Record<string, number | null>>;
|
|
205
|
+
cacheWriteStatus: Readonly<Record<string, UsageApplicability>>;
|
|
206
|
+
output: number | null;
|
|
207
|
+
/** Informational subset; do not charge again if included in output. */
|
|
208
|
+
reasoningOutputSubset: number | null;
|
|
209
|
+
/** Separately billed reasoning. Charge at the reasoning or output rate. */
|
|
210
|
+
reasoningSeparate: number | null;
|
|
211
|
+
/** Reasoning tokens whose include-in-output contract is unknown. */
|
|
212
|
+
reasoningUnspecified: number | null;
|
|
213
|
+
reasoningBilling: {
|
|
214
|
+
kind: 'included' | 'separate' | 'unknown' | 'not_applicable';
|
|
215
|
+
tokens: number | null;
|
|
216
|
+
};
|
|
217
|
+
inputIncludesCacheRead: boolean | null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface ReportSubject {
|
|
221
|
+
taskId: TaskId;
|
|
222
|
+
workOrderId: WorkOrderId;
|
|
223
|
+
revision: number;
|
|
224
|
+
reportId: string;
|
|
225
|
+
reportDigest: Digest;
|
|
226
|
+
snapshot: SnapshotId;
|
|
227
|
+
}
|
|
228
|
+
export interface ReviewTicket {
|
|
229
|
+
id: string;
|
|
230
|
+
requestId: string;
|
|
231
|
+
generation: number;
|
|
232
|
+
subject: ReportSubject;
|
|
233
|
+
validationDigest: Digest;
|
|
234
|
+
workOrderDigest: Digest;
|
|
235
|
+
}
|
|
236
|
+
export interface ControlState {
|
|
237
|
+
mode: 'running' | 'paused' | 'stop-requested' | 'cancelled' | 'completed';
|
|
238
|
+
pendingApprovalIds: readonly string[];
|
|
239
|
+
budgetBlocked: boolean;
|
|
240
|
+
recovering: boolean;
|
|
241
|
+
outcomeUnknown: boolean;
|
|
242
|
+
}
|
|
243
|
+
export interface UsageAttempt {
|
|
244
|
+
attemptId: string;
|
|
245
|
+
taskId: TaskId;
|
|
246
|
+
agentId: SessionId;
|
|
247
|
+
role: Role;
|
|
248
|
+
purpose: 'conversation' | 'compaction' | 'review' | 'routing' | 'other';
|
|
249
|
+
route: PhysicalRoute;
|
|
250
|
+
epochId: EpochId;
|
|
251
|
+
serviceTier?: string;
|
|
252
|
+
rawUsage?: ArtifactRef;
|
|
253
|
+
normalized: NormalizedUsage;
|
|
254
|
+
priceCardDigest?: Digest;
|
|
255
|
+
apiEquivalentUsd: UsdDecimal | null;
|
|
256
|
+
actualBilledUsd: UsdDecimal | null;
|
|
257
|
+
status: 'pending' | 'complete' | 'partial' | 'unknown';
|
|
258
|
+
}
|
|
259
|
+
export interface EventEnvelope<T extends string, P> {
|
|
260
|
+
schemaVersion: 1;
|
|
261
|
+
id: string;
|
|
262
|
+
taskId: TaskId;
|
|
263
|
+
seq: number;
|
|
264
|
+
revision: number;
|
|
265
|
+
type: T;
|
|
266
|
+
createdAt: string;
|
|
267
|
+
causeId: string;
|
|
268
|
+
payload: P;
|
|
269
|
+
}
|
|
270
|
+
export type FusionEvent =
|
|
271
|
+
| EventEnvelope<'task/created', { parent: SessionId; selection: RunSelection }>
|
|
272
|
+
| EventEnvelope<'work-order/prepared', { order: WorkOrder; reservedChild: SessionId }>
|
|
273
|
+
| EventEnvelope<'child/accepted', { operationId: OperationId; child: SessionId; messageId: string }>
|
|
274
|
+
| EventEnvelope<'report/validated', { operationId: OperationId; report: WorkerReport }>
|
|
275
|
+
| EventEnvelope<'approval/pending', { approval: LogicalApproval }>
|
|
276
|
+
| EventEnvelope<'effect/outcome-unknown', { operationId: OperationId; reason: string }>
|
|
277
|
+
| EventEnvelope<'task/completed', { snapshot: SnapshotId; verification: VerificationLevel }>
|
|
278
|
+
| EventEnvelope<'task/paused', { reason: string }>;
|
|
279
|
+
// Expand this union when implementing all specified event kinds; do not use any.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
-- Optional SQLite task store. Does not replace or share a transaction with DSH Session.
|
|
2
|
+
PRAGMA foreign_keys = ON;
|
|
3
|
+
PRAGMA journal_mode = WAL;
|
|
4
|
+
CREATE TABLE IF NOT EXISTS schema_meta(version INTEGER NOT NULL);
|
|
5
|
+
INSERT INTO schema_meta(version) SELECT 1 WHERE NOT EXISTS (SELECT 1 FROM schema_meta);
|
|
6
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
7
|
+
task_id TEXT PRIMARY KEY,
|
|
8
|
+
revision INTEGER NOT NULL CHECK(revision >= 1),
|
|
9
|
+
state TEXT NOT NULL,
|
|
10
|
+
parent_session_id TEXT NOT NULL,
|
|
11
|
+
profile_digest TEXT NOT NULL,
|
|
12
|
+
state_seq INTEGER NOT NULL DEFAULT 0 CHECK(state_seq >= 0),
|
|
13
|
+
created_at TEXT NOT NULL,
|
|
14
|
+
updated_at TEXT NOT NULL
|
|
15
|
+
);
|
|
16
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
17
|
+
event_id TEXT PRIMARY KEY,
|
|
18
|
+
task_id TEXT NOT NULL REFERENCES tasks(task_id),
|
|
19
|
+
seq INTEGER NOT NULL CHECK(seq >= 1),
|
|
20
|
+
revision INTEGER NOT NULL CHECK(revision >= 1),
|
|
21
|
+
type TEXT NOT NULL,
|
|
22
|
+
payload_version INTEGER NOT NULL,
|
|
23
|
+
payload_json TEXT NOT NULL,
|
|
24
|
+
cause_id TEXT NOT NULL,
|
|
25
|
+
created_at TEXT NOT NULL,
|
|
26
|
+
UNIQUE(task_id,seq)
|
|
27
|
+
);
|
|
28
|
+
CREATE TABLE IF NOT EXISTS artifacts (
|
|
29
|
+
artifact_id TEXT PRIMARY KEY,
|
|
30
|
+
task_id TEXT NOT NULL REFERENCES tasks(task_id),
|
|
31
|
+
content_digest TEXT NOT NULL,
|
|
32
|
+
storage_ref TEXT NOT NULL,
|
|
33
|
+
bytes INTEGER NOT NULL CHECK(bytes >= 0),
|
|
34
|
+
media_type TEXT NOT NULL
|
|
35
|
+
);
|
|
36
|
+
CREATE TABLE IF NOT EXISTS work_orders (
|
|
37
|
+
work_order_id TEXT PRIMARY KEY,
|
|
38
|
+
task_id TEXT NOT NULL REFERENCES tasks(task_id),
|
|
39
|
+
revision INTEGER NOT NULL,
|
|
40
|
+
child_session_id TEXT NOT NULL,
|
|
41
|
+
status TEXT NOT NULL,
|
|
42
|
+
brief_artifact_id TEXT NOT NULL REFERENCES artifacts(artifact_id)
|
|
43
|
+
);
|
|
44
|
+
CREATE TABLE IF NOT EXISTS outbox (
|
|
45
|
+
operation_id TEXT PRIMARY KEY,
|
|
46
|
+
task_id TEXT NOT NULL REFERENCES tasks(task_id),
|
|
47
|
+
kind TEXT NOT NULL,
|
|
48
|
+
state TEXT NOT NULL,
|
|
49
|
+
payload_artifact_id TEXT NOT NULL REFERENCES artifacts(artifact_id),
|
|
50
|
+
native_message_id TEXT,
|
|
51
|
+
result_artifact_id TEXT REFERENCES artifacts(artifact_id)
|
|
52
|
+
);
|
|
53
|
+
CREATE TABLE IF NOT EXISTS effects (
|
|
54
|
+
effect_id TEXT PRIMARY KEY,
|
|
55
|
+
operation_id TEXT NOT NULL UNIQUE REFERENCES outbox(operation_id),
|
|
56
|
+
args_digest TEXT NOT NULL,
|
|
57
|
+
snapshot_id TEXT NOT NULL,
|
|
58
|
+
stage TEXT NOT NULL,
|
|
59
|
+
outcome_artifact_id TEXT REFERENCES artifacts(artifact_id)
|
|
60
|
+
);
|
|
61
|
+
CREATE TABLE IF NOT EXISTS logical_approvals (
|
|
62
|
+
logical_id TEXT PRIMARY KEY,
|
|
63
|
+
effect_id TEXT NOT NULL UNIQUE REFERENCES effects(effect_id),
|
|
64
|
+
decision TEXT NOT NULL CHECK(decision IN ('pending','approved','rejected','withdrawn','consumed')),
|
|
65
|
+
native_request_id TEXT,
|
|
66
|
+
decision_scope_digest TEXT NOT NULL,
|
|
67
|
+
updated_at TEXT NOT NULL
|
|
68
|
+
-- Intentionally no expiry column for pending human decisions.
|
|
69
|
+
);
|
|
70
|
+
CREATE TABLE IF NOT EXISTS usage_attempts (
|
|
71
|
+
attempt_id TEXT PRIMARY KEY,
|
|
72
|
+
task_id TEXT NOT NULL REFERENCES tasks(task_id),
|
|
73
|
+
accounting_status TEXT NOT NULL,
|
|
74
|
+
normalized_json TEXT NOT NULL,
|
|
75
|
+
raw_usage_artifact_id TEXT REFERENCES artifacts(artifact_id)
|
|
76
|
+
);
|
|
77
|
+
CREATE TABLE IF NOT EXISTS leases (
|
|
78
|
+
workspace_id TEXT PRIMARY KEY,
|
|
79
|
+
generation INTEGER NOT NULL CHECK(generation >= 1),
|
|
80
|
+
owner_task_id TEXT NOT NULL REFERENCES tasks(task_id),
|
|
81
|
+
live_process_refs_json TEXT NOT NULL
|
|
82
|
+
);
|
|
83
|
+
CREATE INDEX IF NOT EXISTS events_by_task ON events(task_id,seq);
|
|
84
|
+
CREATE INDEX IF NOT EXISTS outbox_pending ON outbox(state,task_id);
|
|
85
|
+
-- Application must perform state/version CAS + event append + outbox write in one transaction.
|
|
86
|
+
-- SQL uniqueness is not proof of exactly-once external side effects.
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# DSH compatibility
|
|
2
|
+
|
|
3
|
+
Fusion supports **DeepSeek Harness 0.2.0-rc.2 and 0.2.1-rc.2**. It is an ordinary external plugin: it uses only DSH's public
|
|
4
|
+
plugin APIs and never patches, replaces or rebuilds DSH itself. `scripts/build.mjs` accepts either of those two Host
|
|
5
|
+
checkouts, `engines.dsh` declares the supported runtime, and every `@deepseek-ai/dsh*` peer uses the same range.
|
|
6
|
+
|
|
7
|
+
## What Fusion depends on
|
|
8
|
+
|
|
9
|
+
Public services always used (`ctx.*`): `agents`, `subagents`, `sessions`, `sessionQuery`, `sessionController`
|
|
10
|
+
(`modelCatalog`, `selectModel`, `resolveAgent`), `llm` (`stream` hooks, `resolveModelInfo`, `registerAdapter`),
|
|
11
|
+
`tools` (`guard`, `execute`, per-agent catalog), `commands`, `webServer`, `agentDefaultModel`, `logger`.
|
|
12
|
+
|
|
13
|
+
Services Fusion looks up at runtime (`ctx.get`), with what happens when one is missing:
|
|
14
|
+
|
|
15
|
+
| Service | Used for | Without it |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| `sandboxPolicy` | the Lead's read-only sandbox (enforced-v3) | every Lead shell command is refused; the Lead still reads with file and search tools |
|
|
18
|
+
| `jobs` | the Sidekick's background commands | background commands are refused |
|
|
19
|
+
| `tokenMeter` | measuring each request against the context window | Fusion requests stop with an error |
|
|
20
|
+
| `compaction`, `agentPresets` | compacting a long Fusion conversation | Fusion pauses when the context is full, keeping the full record |
|
|
21
|
+
| `approval` | routing Sidekick approvals through DSH's own approval policy | commands that need approval fail |
|
|
22
|
+
| `locale` (client) | Chinese / English UI | the UI follows the browser language |
|
|
23
|
+
|
|
24
|
+
Packages imported: `cordis`, `schemastery`, `dsh-agent`, `dsh-llm`, `dsh-session`, `dsh-session-query`,
|
|
25
|
+
`dsh-subagent`, `dsh-tools`, `dsh-jobs`, `dsh-commands`, `dsh-host-webserver`, `dsh-api-session-controller`,
|
|
26
|
+
`dsh-agent-default-model`, `dsh-agent-preset-registry`, `dsh-compaction`, `dsh-token-meter`, `dsh-user-approval` and the
|
|
27
|
+
client packages `dsh-client-ui-*` and `dsh-client-store`. Only package entry points and their documented `/client` and
|
|
28
|
+
`/types` subpaths are imported.
|
|
29
|
+
|
|
30
|
+
Fusion writes only public session events (messages, tool calls and results, and `sandbox/mode` for the Lead's read-only
|
|
31
|
+
sandbox) and stores its own records in its plugin directory.
|
|
32
|
+
|
|
33
|
+
## Changes from 0.2.0-rc.2 to 0.2.1-rc.2
|
|
34
|
+
|
|
35
|
+
The subagent seam was renamed and widened; the delegation composition knobs moved. Everything else Fusion imports
|
|
36
|
+
kept its shape — all 25 non-client `@deepseek-ai/*` symbols Fusion imports still exist with unchanged signatures.
|
|
37
|
+
|
|
38
|
+
| 0.2.0-rc.2 | 0.2.1-rc.2 | Fusion's handling |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `ctx.subagents.startContinuable(spec)` | `ctx.subagents.startActivation(spec)` | if `startActivation` exists, call it with `delivery: 'parent'` and no persona or toolFilter; otherwise call `startContinuable` exactly as on 0.2.0 |
|
|
41
|
+
| `ContinuableStartSpec`, `ContinuableStart` | `SubagentActivationSpec`, `SubagentActivation` | spec gained a required `delivery: 'parent' \| 'caller'`; the return gained `result`/`dispose()` and made `messageId` optional |
|
|
42
|
+
| `ctx.subagents.drainContinuableChildren(parent, childIds)` | `ctx.subagents.drainChildren(parent, childIds)` | call whichever function exists; `drainDescendants(parents)` is the broader form |
|
|
43
|
+
| `ctx.subagents.start(name, request)` | removed | the benchmark baseline uses `start` on 0.2.0 and `startActivation` with `delivery: 'caller'` on 0.2.1 |
|
|
44
|
+
| `SubagentRun.localAgent` | removed | 0.2.0 reads `localAgent`; 0.2.1 reads `ctx.agents.get(childId)` |
|
|
45
|
+
| `request.persona`, `request.toolFilter` | removed (DSH directs callers to an Agent Preset) | kept on the 0.2.0 start; see below for 0.2.1 |
|
|
46
|
+
| `TerminalBlockLabels` (client) | gained a required `commandLine` | always passed in `src/client/ActivityRows.tsx`; 0.2.0 ignores the extra field |
|
|
47
|
+
|
|
48
|
+
Detection is the presence of the function, not a version string. If neither start exists, or neither drain exists,
|
|
49
|
+
Fusion throws and names 0.2.0-rc.2 and 0.2.1-rc.2 as the supported range. `delivery: 'parent'` preserves the 0.2.0
|
|
50
|
+
behaviour: the Worker's settlement reaches the Lead's inbox, which is what the Lead reconciles from the session log.
|
|
51
|
+
|
|
52
|
+
**On 0.2.1 the persona and tool-ceiling knobs have no start-time replacement.** 0.2.1 rejects them outright
|
|
53
|
+
(`tool-subagent: persona is no longer supported; configure persona and tool plugins in an Agent Preset and select it
|
|
54
|
+
with agentPreset`) and `SubagentStartRequest` no longer declares them. The sanctioned replacement is an Agent Preset,
|
|
55
|
+
but Fusion computes the Worker's persona and its tool set per work order at runtime, so a preset cannot express them —
|
|
56
|
+
and requiring a user-configured preset would silently change Fusion's contract. On that host both live inside the
|
|
57
|
+
native role scope Fusion already owns:
|
|
58
|
+
|
|
59
|
+
- the Worker's persona prompt was already redundant: `NativeFusionScopes` installs the `fusion-role` system-prompt
|
|
60
|
+
section carrying `binding.prompts[role]` on the child's own context;
|
|
61
|
+
- the Worker's tool ceiling was **not** redundant. In `implement` mode `readOnlyTools` used to return `undefined` and
|
|
62
|
+
the ceiling came from the start-time `toolFilter` alone. When `startActivation` exists, `readOnlyTools` returns
|
|
63
|
+
`workerTools` for implement mode, so `NativeFusionScopes` narrows the inherited tool set. The scope is installed at
|
|
64
|
+
`agent/created`, before the child's first request, and the existing deny-only guard remains the second gate.
|
|
65
|
+
|
|
66
|
+
That return value also runs `tools.presentAs('native')`, so an implement-mode Worker on 0.2.1 is presented natively,
|
|
67
|
+
as the read-only modes already were. The advertised names stay `workerTools`. On 0.2.0 implement mode still returns
|
|
68
|
+
`undefined`. The ceiling stays the start-time `toolFilter`, and `presentAs('native')` does not start applying to those
|
|
69
|
+
Workers, so 0.2.0 behaviour is unchanged. Text and explore modes already returned an allowlist on both hosts.
|
|
70
|
+
|
|
71
|
+
### Version declarations
|
|
72
|
+
|
|
73
|
+
The old `>=0.2.0-rc.1 <0.2.1` range was **not** what blocked 0.2.1. DSH evaluates plugin requirements with
|
|
74
|
+
`includePrerelease: true`, and `0.2.1-rc.2` sorts *below* `0.2.1`, so that range already satisfied the 0.2.1 gate.
|
|
75
|
+
What broke was the code (`startContinuable` is gone). `scripts/link-host.mjs` checks the same peers with **plain**
|
|
76
|
+
semver, where a prerelease only matches a comparator that has a prerelease on the same major.minor.patch, so
|
|
77
|
+
`>=0.2.0-rc.1 <0.2.1` rejects `0.2.1-rc.2`.
|
|
78
|
+
|
|
79
|
+
`engines.dsh` and every `@deepseek-ai/dsh*` peer therefore use:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
>=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-rc.2 <0.3.0-0
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Dev dependencies and `pnpm-lock.yaml` stay on the published 0.2.0-rc.2 packages (the CI devkit pins that tag). The
|
|
86
|
+
0.2.1-only methods are local structural types, so typecheck does not need the 0.2.1 packages installed.
|
|
87
|
+
|
|
88
|
+
| Version | plain `satisfies` | `{ includePrerelease: true }` |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| `0.2.0-rc.1` | accept | accept |
|
|
91
|
+
| `0.2.0-rc.2` | accept | accept |
|
|
92
|
+
| `0.2.1-rc.1` | reject | reject |
|
|
93
|
+
| `0.2.1-rc.2` | accept | accept |
|
|
94
|
+
| `0.2.1-rc.3` | accept | accept |
|
|
95
|
+
| `0.2.1` | accept | accept |
|
|
96
|
+
| `0.3.0-rc.1` | reject | reject |
|
|
97
|
+
| `0.3.0` | reject | reject |
|
|
98
|
+
|
|
99
|
+
## Changes from 0.1.5 to 0.1.7 (an example of what an upgrade touches)
|
|
100
|
+
|
|
101
|
+
- Plugin message sources became `{ kind: 'plugin:<id>' }` (was `{ kind: 'plugin', plugin: '<id>' }`); Fusion declares its
|
|
102
|
+
kind in `src/message-source.ts`.
|
|
103
|
+
- Jobs: `JobSnapshot`/`onJobDone` became `JobView`/`events.subscribe`, and job calls take the calling agent's id.
|
|
104
|
+
- `dsh-agent-presets` became `dsh-agent-preset-registry`.
|
|
105
|
+
- Tool results became `role: 'tool'` messages that carry `toolCallId` and `isError` themselves.
|
|
106
|
+
- Session logs are `session.v4.jsonl.zstd` (benchmark tooling reads v3 and v4).
|
|
107
|
+
- Plugins declare a bundle patch (`dsh.bundle.patch` → `cordis.patch.yml`).
|
|
108
|
+
|
|
109
|
+
## Adapting to a new DSH release (for example 0.1.8)
|
|
110
|
+
|
|
111
|
+
1. Check out the new Harness release and point `DSHX_HARNESS` at it.
|
|
112
|
+
2. Update the version check in `scripts/build.mjs`, `engines.dsh` and the `@deepseek-ai/*` `peerDependencies`; relink
|
|
113
|
+
with `node scripts/link-host.mjs <harness>`. Pick the range carefully: DSH's gate uses `includePrerelease`, where a
|
|
114
|
+
prerelease sorts below its own release, so a stale upper bound like `<0.2.1` still admits `0.2.1-rc.2` and silently
|
|
115
|
+
passes — while `scripts/link-host.mjs` uses plain semver and rejects it. A prerelease only matches a plain
|
|
116
|
+
comparator that carries a prerelease on the same major.minor.patch, which is why the current span is
|
|
117
|
+
`>=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-rc.2 <0.3.0-0`. Verify both consumers.
|
|
118
|
+
3. Read the release notes and run `dshx check` for compatibility rules; fix type errors (`pnpm typecheck`). Renames in
|
|
119
|
+
the subagent seam are not caught by type checking when the old name is simply gone — grep `ctx.subagents` calls by
|
|
120
|
+
hand, and check the client packages too (`TerminalBlockLabels` gained a required field this time).
|
|
121
|
+
4. Run `pnpm test` and `pnpm test:host` against the new Harness; the Host suite exercises the real Agent, session,
|
|
122
|
+
tool, job, sandbox and subagent services.
|
|
123
|
+
5. Cold-boot the plugin alone in a temporary home: `dshx verify-boot <plugin dir>`.
|
|
124
|
+
6. Install into a real DSH and run a short task with a Fusion pair; check the Lead read-only probe from the README.
|
|
125
|
+
7. Update the supported version in `README.md`, `README.en.md`, `INSTALL*.md` and this file.
|
|
126
|
+
|
|
127
|
+
## Other plugins that rewrite the tool table
|
|
128
|
+
|
|
129
|
+
Fusion registers `fusion_*` tools on the agent and then filters `system-prompt/assemble` down to the role allowlist.
|
|
130
|
+
That filter only removes tools. A plugin that **replaces** `assembled.tools` inside the same hook — `dsh-tool-search` does
|
|
131
|
+
this, keeping an eager set such as `bash`, `glob`, `grep`, `read` and moving everything else, including `fusion_*` and
|
|
132
|
+
often `job_output`, out of the request — used to leave the Lead with no delegation tool. The task could then end
|
|
133
|
+
`COMPLETED` without any Fusion work.
|
|
134
|
+
|
|
135
|
+
Fusion now rebuilds the request table as **allowlist ∪ its own registry** after `next()` returns. Names the role is
|
|
136
|
+
allowed to see are copied back from `tools.schemas(agent)` when another plugin dropped them. If a required `fusion_*`
|
|
137
|
+
tool is not in that registry either, assembly throws `FUSION_TOOL_TABLE_INCOMPLETE` instead of sending the request.
|
|
138
|
+
Putting those names in `dsh-tool-search`'s `core` list is unnecessary once this build is installed; it remains harmless.
|
|
139
|
+
|
|
140
|
+
## Host behaviour Fusion does not own
|
|
141
|
+
|
|
142
|
+
These show up next to Fusion's own gates. Fusion does not patch the host to remove them.
|
|
143
|
+
|
|
144
|
+
- **Sandbox read-only denial.** `[sandbox: file access denied under read-only mode]` is the DSH sandbox backend.
|
|
145
|
+
enforced-v3 keeps the Lead's session read-only on purpose. The Sidekick is given the mode the user chose for the
|
|
146
|
+
conversation. Fusion applies that mode when the user appends `sandbox/mode`, including onto a resident Sidekick, and
|
|
147
|
+
again on the Sidekick's next model request. It never widens the Lead, and it cannot grant a mode the user did not set.
|
|
148
|
+
A Sidekick that has already been disposed has no session left to update; the next delegation seeds a new one.
|
|
149
|
+
- **Reload recovery after a quota stop.** A Worker that stops on provider quota often leaves its `native-worker` outbox
|
|
150
|
+
row `accepted` because no report was acknowledged. The next host start treats that row like any other unacknowledged
|
|
151
|
+
dispatch and sets `recovering`. Continue and role switch both run only after `settled()`, which requires
|
|
152
|
+
`recovering` to be clear, and the route is not written if that check fails. That inspection stays: Fusion cannot see
|
|
153
|
+
host file or process effects that were not in its own journal. Use `/fusion recover <inspection note>`, then continue
|
|
154
|
+
or switch the Sidekick from the recovery panel. Saving a new pair in Settings changes the default for new
|
|
155
|
+
conversations only.
|
|
156
|
+
- **Natural Worker disposal.** DSH `0.2.0-rc.2` disposes a continuable child in `finishDisposal` and only then sends
|
|
157
|
+
`subagent-settled`. `fusion_wait` reads the persisted task when that handle is already gone. It asks for recovery
|
|
158
|
+
only when a native effect is still pending or the outcome is unknown. An ended Worker with no report is
|
|
159
|
+
`WORKFLOW_INCOMPLETE`, not a quota or provider outage. On `0.2.1-rc.2` the same settlement flushes the child log
|
|
160
|
+
and then closes the persistence handle. A later `stop` that sees `SessionHandleClosedError` treats that log as
|
|
161
|
+
already durable, kills any recorded job, and releases the writer. Host unload while `runtime.background` is set
|
|
162
|
+
records `recovery/needed` before that settlement can clear the flag.
|
package/docs/EVIDENCE.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Evidence: does Fusion give frontier results at a discount?
|
|
2
|
+
|
|
3
|
+
Short answer, on the tasks measured: **yes for GPT-6 Astra + GLM-5.3-Flash** — more tasks resolved than GPT-6 Astra
|
|
4
|
+
alone, at 46% of its cost. The sample is small; read the limitations below.
|
|
5
|
+
|
|
6
|
+
## Result
|
|
7
|
+
|
|
8
|
+
Nine real SWE-rebench issues, two attempts each (18 per condition), graded by the maintainers' hidden tests.
|
|
9
|
+
Plugin 0.2.0-preview.11, DSH Studio 0.1.7-rc.2 on macOS. Costs are API-equivalent at list prices, not bills.
|
|
10
|
+
|
|
11
|
+
| Condition | Resolved | Total cost | Paired cost vs Astra alone (median / total) | Lead cost (cache hits) | Sidekick cost | Attempt time |
|
|
12
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
13
|
+
| GPT-6 Astra alone (round 4) | 9/18 | $39.17 | — | — | — | 1.6 h |
|
|
14
|
+
| **Fusion: Astra + GLM-5.3-Flash** | **11/18** | **$18.07** | **0.51 / 0.46** | $13.70 (85%) | $4.37 | 8.3 h |
|
|
15
|
+
| Fusion: Astra + Grok 4.6 | 13/18 | $41.27 | 0.85 / 1.05 | $13.34 (86%) | $27.93 | 3.4 h |
|
|
16
|
+
|
|
17
|
+
- Astra + Flash met both preregistered rules: quality at least Astra alone − 2 (it resolved 2 more), and a median
|
|
18
|
+
paired cost ratio ≤ 0.6.
|
|
19
|
+
- Astra + Grok 4.6 was the most accurate pair (13/18 in rounds 4 and 5) but not cheaper: the Grok Sidekick costs about
|
|
20
|
+
what the Lead saves.
|
|
21
|
+
- The Lead never needed to take over the code: 0 takeovers in 72 Fusion attempts (rounds 4 and 5).
|
|
22
|
+
- Fusion is slower: about 5× the attempt time of Astra alone, mostly Sidekick generation (Flash makes ~80 calls per
|
|
23
|
+
task).
|
|
24
|
+
|
|
25
|
+
Per task (Fusion from round 5, Astra alone from round 4; ✓ = resolved, two attempts each, cost of both):
|
|
26
|
+
|
|
27
|
+
| Task | Astra alone | Astra + Flash | Astra + Grok 4.6 |
|
|
28
|
+
| --- | --- | --- | --- |
|
|
29
|
+
| pypa__twine-1309 | ✗ ✗ $2.83 | ✗ ✗ $1.16 | ✓ ✓ $2.16 |
|
|
30
|
+
| delgan__loguru-1451 | ✓ ✓ $3.35 | ✓ ✓ $2.07 | ✓ ✓ $2.40 |
|
|
31
|
+
| sqlfluff__sqlfluff-7615 | ✗ ✗ $8.28 | ✓ ✗ $2.03 | ✓ ✓ $6.93 |
|
|
32
|
+
| pycqa__isort-2491 | ✗ ✗ $5.19 | ✗ ✗ $3.01 | ✗ ✗ $12.41 |
|
|
33
|
+
| holoviz__param-1117 | ✓ ✓ $4.57 | ✓ ✓ $2.52 | ✓ ✓ $4.40 |
|
|
34
|
+
| ucfopen__canvasapi-716 | ✓ ✗ $2.79 | ✓ ✓ $1.16 | ✓ ✓ $1.56 |
|
|
35
|
+
| pallets__click-3239 | ✓ ✓ $4.35 | ✓ ✓ $2.51 | ✓ ✓ $4.10 |
|
|
36
|
+
| python-scim__scim2-models-139_interface | ✓ ✓ $5.47 | ✓ ✓ $2.51 | ✗ ✓ $5.02 |
|
|
37
|
+
| pallets-eco__wtforms-892_interface | ✗ ✗ $2.34 | ✗ ✗ $1.12 | ✗ ✗ $2.30 |
|
|
38
|
+
|
|
39
|
+
## Against Devin's own Fusion (round devin-r1, 2026-09-29)
|
|
40
|
+
|
|
41
|
+
This round compared the plugin (0.2.0) with Fusion inside Devin, using the same pair: GPT-6 Astra (high) as Lead and
|
|
42
|
+
SWE-2 (medium) as Sidekick. Both got the same prompt, the same fresh checkouts and the same hidden-test grading. Each
|
|
43
|
+
single model ran alone as a baseline.
|
|
44
|
+
|
|
45
|
+
Seven attempts were excluded as contaminated:
|
|
46
|
+
- Six Devin reruns started in workspaces that still held the hidden tests from grading an earlier, quota-blocked
|
|
47
|
+
attempt.
|
|
48
|
+
- One DSH Sidekick downloaded the upstream fix. It still failed.
|
|
49
|
+
|
|
50
|
+
The four tasks that were clean in every condition give this result:
|
|
51
|
+
|
|
52
|
+
| Condition | Resolved | Cost | Mean minutes |
|
|
53
|
+
| --- | --- | --- | --- |
|
|
54
|
+
| **DSH Fusion: Astra + SWE-2** | **4/4** | **$2.73** | 20.1 |
|
|
55
|
+
| Devin Fusion: Astra + SWE-2 | 3/4 | $4.81 | 6.0 |
|
|
56
|
+
| Devin, Astra alone | 3/4 | $8.13 | 5.1 |
|
|
57
|
+
| DSH, Astra alone (round 4) | 3/4 | $10.11 | 5.8 |
|
|
58
|
+
|
|
59
|
+
- Fusion was cheaper than the single model on both harnesses: 41% less on Devin and 73% less on DSH.
|
|
60
|
+
- The one task only DSH Fusion solved was sqlfluff-7615.
|
|
61
|
+
- Four tasks with one attempt each cannot rank the two systems. It is no claim of parity or superiority.
|
|
62
|
+
- SWE-2 is free in Devin's catalog and is priced at $0 on both sides, so these costs are the Lead's.
|
|
63
|
+
- The plugin's Sidekick used about 4× Devin's Sidekick calls, and the plugin took about 3.4× Devin's wall time.
|
|
64
|
+
- Freshly processed prompt input was 5% of all prompt tokens for the plugin and 19% for Devin Fusion.
|
|
65
|
+
- Protocol, deviations and root causes: [REBENCH_DEVIN.md](REBENCH_DEVIN.md) and
|
|
66
|
+
`benchmark/rebench/results-devin-r1/`.
|
|
67
|
+
|
|
68
|
+
## Setup
|
|
69
|
+
|
|
70
|
+
**Tasks.** Nine instances from `nebius/SWE-rebench-leaderboard` (split 2026_03): real GitHub issues with the
|
|
71
|
+
maintainers' fix. Each was validated without a model: its FAIL_TO_PASS tests fail at the base commit, and every
|
|
72
|
+
FAIL_TO_PASS and PASS_TO_PASS test passes with the maintainers' patch.
|
|
73
|
+
|
|
74
|
+
| Tier | Instance | Maintainers' patch | FAIL_TO_PASS / PASS_TO_PASS |
|
|
75
|
+
| --- | --- | --- | --- |
|
|
76
|
+
| easy | pypa__twine-1309 | 1 file, 15 lines | 1 / 3 |
|
|
77
|
+
| easy | delgan__loguru-1451 | 1 file, 16 lines | 2 / 22 |
|
|
78
|
+
| easy | sqlfluff__sqlfluff-7615 | 2 files, 12 lines | 1 / 24 |
|
|
79
|
+
| medium | pycqa__isort-2491 | 1 file, 50 lines | 1 / 73 |
|
|
80
|
+
| medium | holoviz__param-1117 | 1 file, 47 lines | 2 / 94 |
|
|
81
|
+
| medium | ucfopen__canvasapi-716 | 1 file, 43 lines | 2 / 60 |
|
|
82
|
+
| hard | pallets__click-3239 | 3 files, 131 lines | 4 / 694 |
|
|
83
|
+
| hard | python-scim__scim2-models-139_interface | 5 files, 279 lines | 9 / 0 |
|
|
84
|
+
| hard | pallets-eco__wtforms-892_interface | 8 files, 107 lines | 10 / 0 |
|
|
85
|
+
|
|
86
|
+
**Protocol.**
|
|
87
|
+
- Each attempt starts from a fresh checkout at the base commit with dependencies in `./.venv`.
|
|
88
|
+
- The hidden tests and the maintainers' patch never enter a workspace.
|
|
89
|
+
- Every condition gets the same prompt: the issue text and how to use `./.venv`, with no hints and no mention of
|
|
90
|
+
Fusion or delegation.
|
|
91
|
+
- Grading follows the official harness: restore the files the hidden test patch touches, apply it, run the tests.
|
|
92
|
+
An attempt is resolved only if every FAIL_TO_PASS and PASS_TO_PASS test passes.
|
|
93
|
+
- No model outcome is retried. Infrastructure or provider failures are rerun once and listed.
|
|
94
|
+
- Nobody edits a candidate, answers a model's question or changes the plugin during a round.
|
|
95
|
+
- Conditions rotate per task; concurrency 3; 6-hour limit per attempt (never reached).
|
|
96
|
+
- Models: GPT-6 Astra through the ChatGPT subscription route, GLM-5.3-Flash and Grok 4.6 through their own routes, all
|
|
97
|
+
at reasoning effort `high`, permission preset "approve for me".
|
|
98
|
+
|
|
99
|
+
**Cost.** Tokens come from DSH's own session logs for the Lead and every Sidekick session it started, converted with
|
|
100
|
+
list prices (USD per 1M tokens, uncached input / cache read / output): gpt-6-astra 10 / 1 / 50, grok-4.6 2 / 0.3 / 6,
|
|
101
|
+
glm-5.3-flash 0.15 / 0.03 / 0.50. The ratio that the rules use is the median over the 18 paired attempts (same task,
|
|
102
|
+
same repetition) of Fusion cost ÷ Astra-alone cost.
|
|
103
|
+
|
|
104
|
+
## How the result was reached
|
|
105
|
+
|
|
106
|
+
Each round was preregistered (question, conditions and decision rules written before the first model call).
|
|
107
|
+
|
|
108
|
+
| Round | Question | Outcome |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| 1–2 | Fusion with a GLM-5.3 (max) Lead | Found plugin defects (Lead blocking delegation, attempts where the Sidekick never ran). A GLM Lead is too close in price to the Sidekick to show a saving. Not included here. |
|
|
111
|
+
| 3 | Astra + Flash, one attempt per task ([preregistration](REBENCH_ROUND3.md), [conclusion](../benchmark/rebench/results-r3/CONCLUSION.md)) | 6/9 vs Astra 5/9; cost ratio 0.67. Showed that exact wording in requirements could get lost between brief and implementation → verbatim requirements with per-requirement verdicts. |
|
|
112
|
+
| 4 | Which Sidekick: Grok 4.6 or Flash ([preregistration](REBENCH_ROUND4.md), [conclusion](../benchmark/rebench/results-r4/CONCLUSION.md)) | Both beat Astra on quality; neither reached the 0.6 cost rule. Cause: the Lead's prompt cache (69% hits) — its tool list changed with the task phase, and keepalive was off. |
|
|
113
|
+
| 5 | Same after the cache fixes ([conclusion](../benchmark/rebench/results-r5/CONCLUSION.md)) | Lead cache hits 85%, Lead cost −37%; Astra + Flash meets both rules. |
|
|
114
|
+
|
|
115
|
+
Round 5 also corrected a measurement error: Sidekick sessions are attributed by their `parentSession`, since repeated
|
|
116
|
+
rounds reuse workspace paths. `results.raw.jsonl` keeps the uncorrected rows.
|
|
117
|
+
|
|
118
|
+
## Limitations
|
|
119
|
+
|
|
120
|
+
- Small sample: nine tasks, two attempts each. A difference of one or two tasks is within noise.
|
|
121
|
+
- Python only, and all from one benchmark split. The issues are public and may be in the models' training data.
|
|
122
|
+
- One machine, one user's subscriptions; Astra alone comes from round 4 (a day earlier), not from round 5.
|
|
123
|
+
- Costs are list-price estimates. Subscription plans bill differently.
|
|
124
|
+
- The Lead takeover path never triggered in a real run; only automated tests cover it.
|
|
125
|
+
|
|
126
|
+
## Reproduce
|
|
127
|
+
|
|
128
|
+
Requires DSH 0.1.7-rc.2 running with Fusion installed and the models signed in, a checkout of the same Harness release
|
|
129
|
+
(`DSH_ROOT`, used to talk to the running Host), Node 22, Python 3 and `uv` (task environments use Python 3.13).
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
export DSH_ROOT=/path/to/deepseek-harness-0.1.7-rc.2
|
|
133
|
+
cd benchmark/rebench
|
|
134
|
+
python3 harness.py fetch && python3 harness.py validate # download and validate the nine tasks (no model calls)
|
|
135
|
+
python3 run.py --plan plan-r5.json --out results.jsonl --dry-run # grade the maintainers' patches: proves the setup
|
|
136
|
+
python3 run.py --plan plan-r5.json --out results.jsonl --cleanup # the real run
|
|
137
|
+
python3 report.py results.jsonl
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`plan-r5.json` names the providers used here; change them to your own routes. Raw rows for every round are in
|
|
141
|
+
`benchmark/rebench/results-r*/`.
|