@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.
Files changed (213) hide show
  1. package/INSTALL.md +89 -0
  2. package/INSTALL.zh.md +86 -0
  3. package/LICENSE +201 -0
  4. package/README.en.md +193 -0
  5. package/README.md +182 -2
  6. package/RELEASING.md +123 -0
  7. package/RELEASING.zh.md +115 -0
  8. package/contracts/fusion.ts +279 -0
  9. package/contracts/storage.sql +86 -0
  10. package/cordis.patch.yml +3 -0
  11. package/docs/COMPATIBILITY.md +162 -0
  12. package/docs/EVIDENCE.md +141 -0
  13. package/dshx.yml +39 -0
  14. package/lib/client.js +1920 -0
  15. package/lib/client.js.map +1 -0
  16. package/lib/dsh-fusion.js +12664 -0
  17. package/lib/types/activity.d.ts +20 -0
  18. package/lib/types/approval/logical.d.ts +10 -0
  19. package/lib/types/benchmark/naive.d.ts +37 -0
  20. package/lib/types/benchmark/native-run.d.ts +68 -0
  21. package/lib/types/benchmark/output-limits.d.ts +19 -0
  22. package/lib/types/bilingual.d.ts +2 -0
  23. package/lib/types/client/ActivityRows.d.ts +8 -0
  24. package/lib/types/client/FusionCache.d.ts +4 -0
  25. package/lib/types/client/FusionHint.d.ts +9 -0
  26. package/lib/types/client/FusionHistory.d.ts +5 -0
  27. package/lib/types/client/FusionPanelIcon.d.ts +13 -0
  28. package/lib/types/client/FusionRecovery.d.ts +5 -0
  29. package/lib/types/client/FusionSettings.d.ts +9 -0
  30. package/lib/types/client/FusionStatus.d.ts +5 -0
  31. package/lib/types/client/FusionTool.d.ts +4 -0
  32. package/lib/types/client/activity-model.d.ts +14 -0
  33. package/lib/types/client/i18n.d.ts +15 -0
  34. package/lib/types/client/index.d.ts +22 -0
  35. package/lib/types/client/settings-events.d.ts +2 -0
  36. package/lib/types/client/status-polling.d.ts +9 -0
  37. package/lib/types/context/guard.d.ts +21 -0
  38. package/lib/types/context/handoff.d.ts +17 -0
  39. package/lib/types/contracts.d.ts +449 -0
  40. package/lib/types/digest.d.ts +5 -0
  41. package/lib/types/dsh-fusion.d.ts +13 -0
  42. package/lib/types/errors.d.ts +28 -0
  43. package/lib/types/evidence/gate.d.ts +23 -0
  44. package/lib/types/evidence/receipts.d.ts +65 -0
  45. package/lib/types/execution/write-lease.d.ts +42 -0
  46. package/lib/types/host/bindings.d.ts +30 -0
  47. package/lib/types/host/cache-defaults.d.ts +48 -0
  48. package/lib/types/host/cache-policy.d.ts +155 -0
  49. package/lib/types/host/change-evidence.d.ts +38 -0
  50. package/lib/types/host/compaction-routing.d.ts +15 -0
  51. package/lib/types/host/coordinator.d.ts +169 -0
  52. package/lib/types/host/evidence-page.d.ts +13 -0
  53. package/lib/types/host/history.d.ts +88 -0
  54. package/lib/types/host/http.d.ts +4 -0
  55. package/lib/types/host/model-output.d.ts +13 -0
  56. package/lib/types/host/native-activity.d.ts +26 -0
  57. package/lib/types/host/native-approvals.d.ts +29 -0
  58. package/lib/types/host/native-auxiliary.d.ts +17 -0
  59. package/lib/types/host/native-budget.d.ts +33 -0
  60. package/lib/types/host/native-checks.d.ts +104 -0
  61. package/lib/types/host/native-compaction.d.ts +8 -0
  62. package/lib/types/host/native-context.d.ts +36 -0
  63. package/lib/types/host/native-effects.d.ts +75 -0
  64. package/lib/types/host/native-evidence.d.ts +4 -0
  65. package/lib/types/host/native-exploration.d.ts +15 -0
  66. package/lib/types/host/native-keepalive.d.ts +82 -0
  67. package/lib/types/host/native-model-control.d.ts +99 -0
  68. package/lib/types/host/native-on-demand-context.d.ts +39 -0
  69. package/lib/types/host/native-request.d.ts +5 -0
  70. package/lib/types/host/native-review.d.ts +11 -0
  71. package/lib/types/host/native-role-sandbox.d.ts +49 -0
  72. package/lib/types/host/native-scopes.d.ts +49 -0
  73. package/lib/types/host/native-selection.d.ts +29 -0
  74. package/lib/types/host/native-task-context.d.ts +39 -0
  75. package/lib/types/host/native-usage.d.ts +46 -0
  76. package/lib/types/host/native-worker-brief.d.ts +74 -0
  77. package/lib/types/host/native-worker-notices.d.ts +15 -0
  78. package/lib/types/host/native-worker.d.ts +44 -0
  79. package/lib/types/host/native-workflow.d.ts +41 -0
  80. package/lib/types/host/route-transport.d.ts +22 -0
  81. package/lib/types/host/settings.d.ts +39 -0
  82. package/lib/types/host/shell.d.ts +11 -0
  83. package/lib/types/host/status.d.ts +7 -0
  84. package/lib/types/host/subagent-seam.d.ts +10 -0
  85. package/lib/types/host/tool-table.d.ts +35 -0
  86. package/lib/types/host/worker-terminal.d.ts +68 -0
  87. package/lib/types/host/workspace-guard.d.ts +13 -0
  88. package/lib/types/host/workspace.d.ts +52 -0
  89. package/lib/types/identity.d.ts +64 -0
  90. package/lib/types/index.d.ts +34 -0
  91. package/lib/types/message-source.d.ts +29 -0
  92. package/lib/types/profile/compactor.d.ts +5 -0
  93. package/lib/types/profile/keepalive.d.ts +3 -0
  94. package/lib/types/profile/resolve.d.ts +17 -0
  95. package/lib/types/prompts.d.ts +8 -0
  96. package/lib/types/recovery/reconcile.d.ts +28 -0
  97. package/lib/types/review/dispatch.d.ts +20 -0
  98. package/lib/types/review/protocol.d.ts +51 -0
  99. package/lib/types/review/ticket.d.ts +32 -0
  100. package/lib/types/status.d.ts +88 -0
  101. package/lib/types/store-migration.d.ts +29 -0
  102. package/lib/types/task/deletion.d.ts +64 -0
  103. package/lib/types/task/reducer.d.ts +6 -0
  104. package/lib/types/task/sqlite-store.d.ts +71 -0
  105. package/lib/types/task/state.d.ts +51 -0
  106. package/lib/types/task/status-index.d.ts +28 -0
  107. package/lib/types/task/store.d.ts +48 -0
  108. package/lib/types/usage/budget.d.ts +27 -0
  109. package/lib/types/usage/ledger.d.ts +66 -0
  110. package/lib/types/usage/normalize.d.ts +50 -0
  111. package/lib/types/usage/pricing.d.ts +18 -0
  112. package/package.json +178 -4
  113. package/profiles/fusion-auto.example.json +45 -0
  114. package/prompts/compact.md +7 -0
  115. package/prompts/lead.md +42 -0
  116. package/prompts/model-lead.md +13 -0
  117. package/prompts/model-worker.md +5 -0
  118. package/prompts/worker.md +21 -0
  119. package/src/activity.ts +23 -0
  120. package/src/approval/logical.ts +44 -0
  121. package/src/benchmark/naive.ts +216 -0
  122. package/src/benchmark/native-run.ts +215 -0
  123. package/src/benchmark/output-limits.ts +33 -0
  124. package/src/bilingual.ts +2 -0
  125. package/src/client/ActivityRows.tsx +59 -0
  126. package/src/client/FusionCache.tsx +130 -0
  127. package/src/client/FusionHint.tsx +39 -0
  128. package/src/client/FusionHistory.tsx +247 -0
  129. package/src/client/FusionPanelIcon.tsx +39 -0
  130. package/src/client/FusionRecovery.tsx +69 -0
  131. package/src/client/FusionSettings.tsx +205 -0
  132. package/src/client/FusionStatus.tsx +103 -0
  133. package/src/client/FusionTool.tsx +66 -0
  134. package/src/client/activity-model.ts +31 -0
  135. package/src/client/i18n.ts +30 -0
  136. package/src/client/index.ts +50 -0
  137. package/src/client/settings-events.ts +3 -0
  138. package/src/client/status-polling.ts +88 -0
  139. package/src/context/guard.ts +81 -0
  140. package/src/context/handoff.ts +28 -0
  141. package/src/contracts.ts +449 -0
  142. package/src/digest.ts +28 -0
  143. package/src/dsh-fusion.ts +321 -0
  144. package/src/errors.ts +34 -0
  145. package/src/evidence/gate.ts +290 -0
  146. package/src/evidence/receipts.ts +175 -0
  147. package/src/execution/write-lease.ts +187 -0
  148. package/src/host/bindings.ts +91 -0
  149. package/src/host/cache-defaults.ts +103 -0
  150. package/src/host/cache-policy.ts +168 -0
  151. package/src/host/change-evidence.ts +156 -0
  152. package/src/host/compaction-routing.ts +46 -0
  153. package/src/host/coordinator.ts +2160 -0
  154. package/src/host/evidence-page.ts +25 -0
  155. package/src/host/history.ts +114 -0
  156. package/src/host/http.ts +30 -0
  157. package/src/host/model-output.ts +22 -0
  158. package/src/host/native-activity.ts +199 -0
  159. package/src/host/native-approvals.ts +196 -0
  160. package/src/host/native-auxiliary.ts +19 -0
  161. package/src/host/native-budget.ts +112 -0
  162. package/src/host/native-checks.ts +474 -0
  163. package/src/host/native-compaction.ts +34 -0
  164. package/src/host/native-context.ts +204 -0
  165. package/src/host/native-effects.ts +260 -0
  166. package/src/host/native-evidence.ts +31 -0
  167. package/src/host/native-exploration.ts +114 -0
  168. package/src/host/native-keepalive.ts +292 -0
  169. package/src/host/native-model-control.ts +298 -0
  170. package/src/host/native-on-demand-context.ts +74 -0
  171. package/src/host/native-request.ts +12 -0
  172. package/src/host/native-review.ts +51 -0
  173. package/src/host/native-role-sandbox.ts +171 -0
  174. package/src/host/native-scopes.ts +203 -0
  175. package/src/host/native-selection.ts +110 -0
  176. package/src/host/native-task-context.ts +264 -0
  177. package/src/host/native-usage.ts +121 -0
  178. package/src/host/native-worker-brief.ts +122 -0
  179. package/src/host/native-worker-notices.ts +72 -0
  180. package/src/host/native-worker.ts +215 -0
  181. package/src/host/native-workflow.ts +103 -0
  182. package/src/host/route-transport.ts +54 -0
  183. package/src/host/settings.ts +93 -0
  184. package/src/host/shell.ts +13 -0
  185. package/src/host/status.ts +112 -0
  186. package/src/host/subagent-seam.ts +20 -0
  187. package/src/host/tool-table.ts +56 -0
  188. package/src/host/worker-terminal.ts +91 -0
  189. package/src/host/workspace-guard.ts +17 -0
  190. package/src/host/workspace.ts +184 -0
  191. package/src/identity.ts +76 -0
  192. package/src/index.ts +50 -0
  193. package/src/message-source.ts +31 -0
  194. package/src/profile/compactor.ts +26 -0
  195. package/src/profile/keepalive.ts +13 -0
  196. package/src/profile/resolve.ts +131 -0
  197. package/src/prompts.ts +32 -0
  198. package/src/recovery/reconcile.ts +38 -0
  199. package/src/review/dispatch.ts +48 -0
  200. package/src/review/protocol.ts +212 -0
  201. package/src/review/ticket.ts +166 -0
  202. package/src/status.ts +55 -0
  203. package/src/store-migration.ts +65 -0
  204. package/src/task/deletion.ts +105 -0
  205. package/src/task/reducer.ts +679 -0
  206. package/src/task/sqlite-store.ts +386 -0
  207. package/src/task/state.ts +72 -0
  208. package/src/task/status-index.ts +198 -0
  209. package/src/task/store.ts +287 -0
  210. package/src/usage/budget.ts +88 -0
  211. package/src/usage/ledger.ts +218 -0
  212. package/src/usage/normalize.ts +308 -0
  213. package/src/usage/pricing.ts +95 -0
@@ -0,0 +1,29 @@
1
+ import type { ContextFormed } from '@deepseek-ai/dsh-llm';
2
+ declare module '@deepseek-ai/dsh-llm' {
3
+ interface MessageSourceMap {
4
+ 'plugin:dsh-fusion': {
5
+ kind: 'plugin:dsh-fusion';
6
+ } & ContextFormed;
7
+ /**
8
+ * The producer kind this plugin wrote before it was renamed from
9
+ * `dsh-model-fusion`. Sessions that already exist still carry it in their
10
+ * durable log, so it stays a readable alias; nothing new is written under
11
+ * it and no migration rewrites history.
12
+ */
13
+ 'plugin:dsh-model-fusion': {
14
+ kind: 'plugin:dsh-model-fusion';
15
+ } & ContextFormed;
16
+ }
17
+ }
18
+ /** The kind every new Fusion message is written under. */
19
+ export declare const FUSION_SOURCE_KIND = "plugin:dsh-fusion";
20
+ /** Kinds a Fusion-produced message may already carry in an existing session. */
21
+ export declare const FUSION_SOURCE_KINDS: readonly ["plugin:dsh-fusion", "plugin:dsh-model-fusion"];
22
+ export type FusionSourceKind = (typeof FUSION_SOURCE_KINDS)[number];
23
+ /**
24
+ * Whether a message source was produced by this plugin, under either name.
25
+ *
26
+ * Read-side call sites use this guard instead of comparing one literal, so a
27
+ * session started before the rename keeps its context across the upgrade.
28
+ */
29
+ export declare function isFusionSourceKind(kind: unknown): kind is FusionSourceKind;
@@ -0,0 +1,5 @@
1
+ import type { PairProfile, PhysicalRoute } from '../contracts.js';
2
+ /** A separate summary model is never inferred from a provider name or enabled by default. */
3
+ export declare function compactorChoice(value: unknown): PairProfile['compactor'];
4
+ /** All physical routes in a frozen configuration share the same explicit spending authorization. */
5
+ export declare function profileRoutes(profile: Pick<PairProfile, 'lead' | 'worker' | 'compactor'>): PhysicalRoute[];
@@ -0,0 +1,3 @@
1
+ import type { PairProfile } from '../contracts.js';
2
+ /** No provider-name heuristic can establish cache support for a DSH route. */
3
+ export declare function keepaliveChoice(value: unknown): PairProfile['cacheKeepalive'];
@@ -0,0 +1,17 @@
1
+ import type { PairProfile, PhysicalRoute } from '../contracts.js';
2
+ export interface PromptBundle {
3
+ lead: string;
4
+ worker: string;
5
+ compact: string;
6
+ }
7
+ export interface ResolvedProfile {
8
+ readonly profile: PairProfile;
9
+ readonly prompts: PromptBundle;
10
+ }
11
+ export interface ProfileCatalog {
12
+ readonly authorizedRoutes: readonly PhysicalRoute[];
13
+ }
14
+ export declare function promptDigests(prompts: PromptBundle): PairProfile['promptDigests'];
15
+ export declare function completeProfile(raw: Record<string, unknown>, prompts: PromptBundle): PairProfile;
16
+ export declare function resolveProfile(raw: Record<string, unknown>, prompts: PromptBundle, catalog: ProfileCatalog): ResolvedProfile;
17
+ export declare function loadJsonObject(path: string): Record<string, unknown>;
@@ -0,0 +1,8 @@
1
+ import type { PromptBundle } from './profile/resolve.js';
2
+ export declare function defaultPromptDir(): string;
3
+ export declare function loadPromptBundle(dir?: string): PromptBundle;
4
+ export declare function loadModelPromptBundle(dir?: string): PromptBundle;
5
+ export declare function promptManifest(dir?: string): {
6
+ prompts: PromptBundle;
7
+ digests: Readonly<Record<"lead" | "worker" | "compact", import("./contracts.js").Digest>>;
8
+ };
@@ -0,0 +1,28 @@
1
+ import type { OutboxRow, SessionId } from '../contracts.js';
2
+ import { NEEDS_REPAIR, OUTCOME_UNKNOWN } from '../errors.js';
3
+ export type ChildPersistence = 'present' | 'missing' | 'corrupt';
4
+ export interface RecoveryScan {
5
+ readonly outbox: OutboxRow;
6
+ readonly childPersistence: ChildPersistence;
7
+ readonly effectKnown: boolean | null;
8
+ readonly reportAlreadyRecorded: boolean;
9
+ readonly parentDelivered: boolean;
10
+ }
11
+ export type RecoveryDecision = {
12
+ action: 'retry-prepare';
13
+ child: SessionId;
14
+ } | {
15
+ action: 'resume-child';
16
+ child: SessionId;
17
+ } | {
18
+ action: 'return-stored-result';
19
+ } | {
20
+ action: 'needs-repair';
21
+ code: typeof NEEDS_REPAIR;
22
+ } | {
23
+ action: 'outcome-unknown';
24
+ code: typeof OUTCOME_UNKNOWN;
25
+ } | {
26
+ action: 'idle';
27
+ };
28
+ export declare function reconcile(scan: RecoveryScan): RecoveryDecision;
@@ -0,0 +1,20 @@
1
+ import type { ReviewTicket, TaskId } from '../contracts.js';
2
+ import type { ReviewClassification } from './protocol.js';
3
+ import type { FusionStore } from '../task/store.js';
4
+ /**
5
+ * Offline producer: persist the captured ticket and outbox row before any adapter send.
6
+ * The model callback may read the latest TaskState only for seq/CAS.
7
+ */
8
+ export declare function persistReviewRequest(store: FusionStore, taskId: TaskId, ids: {
9
+ ticketId: string;
10
+ requestId: string;
11
+ }, payloadRef: string): Readonly<ReviewTicket>;
12
+ export declare function loadReviewOutboxByRequestId(store: FusionStore, taskId: TaskId, requestId: string): {
13
+ row: import("../contracts.js").OutboxRow;
14
+ ticket: Readonly<ReviewTicket>;
15
+ };
16
+ export declare function appendCapturedReviewResult(store: FusionStore, taskId: TaskId, ticket: Readonly<ReviewTicket>, outcome: {
17
+ requestId: string;
18
+ terminalEvidenceRef: string;
19
+ classification: ReviewClassification;
20
+ }): ReturnType<FusionStore['append']>;
@@ -0,0 +1,51 @@
1
+ export type ReviewStatus = 'complete' | 'incomplete' | 'not_required' | 'failed';
2
+ export type ReviewDecision = 'approve' | 'rework' | 'needs-decision' | 'REVIEW_INCOMPLETE';
3
+ export type ExecutionPath = 'direct' | 'forced_fusion' | 'fusion_auto';
4
+ export type ReviewTermination = 'success' | 'truncated' | 'aborted' | 'error' | 'filtered' | 'unknown';
5
+ export interface ReviewClassification {
6
+ reviewStatus: ReviewStatus;
7
+ decision: ReviewDecision | 'not_required';
8
+ protocolComplete: boolean;
9
+ /** True only after a substitute artifact was actually delivered. */
10
+ fallbackUsed: boolean;
11
+ /** True when a fallback or retry is still required. */
12
+ fallbackRequired: boolean;
13
+ termination: ReviewTermination;
14
+ reasons: readonly string[];
15
+ }
16
+ export interface ReviewAttemptFields {
17
+ artifactPass: boolean;
18
+ executionPath: ExecutionPath;
19
+ leadReviewStatus: ReviewStatus;
20
+ finishReason: string | null;
21
+ protocolComplete: boolean;
22
+ fallbackUsed: boolean;
23
+ deliveredArtifactSource: string | null;
24
+ }
25
+ export declare function normalizeTermination(input: {
26
+ finishReason?: string | null;
27
+ termination?: ReviewTermination;
28
+ completeEvidence?: boolean;
29
+ }): ReviewTermination;
30
+ /**
31
+ * Lead review is fail-closed. Only an explicit success termination plus a
32
+ * versioned accept/rework/needs-decision structure can complete the protocol.
33
+ * DIRECT skips review.
34
+ */
35
+ export declare function classifyReview(input: {
36
+ executionPath: ExecutionPath;
37
+ finishReason?: string | null;
38
+ termination?: ReviewTermination;
39
+ completeEvidence?: boolean;
40
+ rawText?: string | null;
41
+ parsed?: unknown;
42
+ }): ReviewClassification;
43
+ /** Map classifier output onto the reducer event decision. */
44
+ export declare function toReducerReviewDecision(classification: ReviewClassification): 'accept' | 'rework' | 'needs-decision' | 'REVIEW_INCOMPLETE';
45
+ export declare function reviewAttemptFields(input: {
46
+ artifactPass: boolean;
47
+ executionPath: ExecutionPath;
48
+ classification: ReviewClassification;
49
+ finishReason?: string | null;
50
+ deliveredArtifactSource?: string | null;
51
+ }): ReviewAttemptFields;
@@ -0,0 +1,32 @@
1
+ import type { FusionEvent, OutboxRow, ReportSubject, ReviewReadinessReceipt, ReviewTicket, TaskId, WorkerReport } from '../contracts.js';
2
+ import type { ReviewClassification } from './protocol.js';
3
+ import type { TaskState } from '../task/state.js';
4
+ export declare function reportSubject(taskId: TaskId, report: WorkerReport): ReportSubject;
5
+ export declare function sameSubject(left: ReportSubject, right: ReportSubject): boolean;
6
+ export declare function reviewReadinessReceipt(subject: ReportSubject): ReviewReadinessReceipt;
7
+ export declare function captureTicket(ticket: ReviewTicket): Readonly<ReviewTicket>;
8
+ export declare function createReviewTicket(state: TaskState, ids: {
9
+ ticketId: string;
10
+ requestId: string;
11
+ }): ReviewTicket;
12
+ export declare function reviewRequestedEvent(state: TaskState, seq: number, ids: {
13
+ ticketId: string;
14
+ requestId: string;
15
+ }, extras?: Partial<Pick<FusionEvent, 'id' | 'revision' | 'createdAt' | 'causeId'>>): Extract<FusionEvent, {
16
+ type: 'review/requested';
17
+ }>;
18
+ export declare function reviewOutboxRow(ticket: Readonly<ReviewTicket>, payloadRef: string): OutboxRow;
19
+ /** Callback constructor. No TaskState argument: the producer cannot rebind to the latest task. */
20
+ export declare function resultFromCapturedTicket(ticket: Readonly<ReviewTicket>, outcome: {
21
+ requestId: string;
22
+ terminalEvidenceRef: string;
23
+ classification: ReviewClassification;
24
+ }, envelope: {
25
+ eventId: string;
26
+ seq: number;
27
+ createdAt: string;
28
+ }): Extract<FusionEvent, {
29
+ type: 'review/completed';
30
+ }>;
31
+ /** Call at review dispatch AND immediately before committing verified completion. */
32
+ export declare function assertCurrentSubject(state: TaskState, ticket: ReviewTicket, actualSnapshot: ReportSubject['snapshot']): void;
@@ -0,0 +1,88 @@
1
+ import type { ControlView } from './host/native-model-control.js';
2
+ import type { PhysicalRoute, Role, TokenMeasurement, VerificationLevel } from './contracts.js';
3
+ /** Bounded, read-only UI projection. No prompts, tool arguments or credentials. */
4
+ export interface FusionStatus {
5
+ schemaVersion: 1;
6
+ sessionId: string;
7
+ selected: boolean;
8
+ observedAt: string;
9
+ task?: {
10
+ id: string;
11
+ revision: number;
12
+ stage: string;
13
+ detail: string | null;
14
+ /** English copies for an English DSH; optional so older records still read. */
15
+ stageEn?: string;
16
+ detailEn?: string | null;
17
+ attention: boolean;
18
+ verification: VerificationLevel;
19
+ workerId: string | null;
20
+ modelControl?: ControlView;
21
+ models: {
22
+ lead: PhysicalRoute;
23
+ worker: PhysicalRoute;
24
+ compactor: PhysicalRoute | null;
25
+ };
26
+ usage: {
27
+ calls: number;
28
+ finalCalls: number;
29
+ provisionalCalls: number;
30
+ unreportedCalls: number;
31
+ compactionCalls: number;
32
+ keepaliveCalls: number;
33
+ /** Known subtotals only; missing request fields never become zero totals. */
34
+ input: UsageSubtotal;
35
+ cacheRead: UsageSubtotal;
36
+ output: UsageSubtotal;
37
+ actualBilledUsd: null;
38
+ upstreamHttpCalls: null;
39
+ /** Where the tokens went: the point of Fusion is that most land on the cheaper Worker. */
40
+ byRole: Record<Role, RoleUsage>;
41
+ };
42
+ /** Checks frozen on the current work order; 0 means the Lead review was the only verification. */
43
+ automatedChecks: number | null;
44
+ requests: {
45
+ id: string;
46
+ role: Role;
47
+ purpose: string;
48
+ provider: string;
49
+ model: string;
50
+ outcome: string;
51
+ authority: 'none' | 'provisional' | 'final';
52
+ startedAt: string;
53
+ }[];
54
+ tools: {
55
+ id: string;
56
+ role: Role;
57
+ name: string;
58
+ state: string;
59
+ failed: boolean;
60
+ startedAt: string;
61
+ }[];
62
+ contexts: {
63
+ role: Role;
64
+ purpose: string;
65
+ inputTokens: number;
66
+ budget: number;
67
+ quality: TokenMeasurement['quality'];
68
+ admitted: boolean;
69
+ checkedAt: string;
70
+ }[];
71
+ pendingApprovals: number;
72
+ unsettledTools: number;
73
+ /** True while a human inspection is required before continue, model switch, or leaving Fusion. */
74
+ recovering?: boolean;
75
+ outcomeUnknown?: boolean;
76
+ };
77
+ }
78
+ export interface RoleUsage {
79
+ calls: number;
80
+ input: UsageSubtotal;
81
+ cacheRead: UsageSubtotal;
82
+ output: UsageSubtotal;
83
+ }
84
+ export interface UsageSubtotal {
85
+ knownTokens: number;
86
+ reportedRequests: number;
87
+ totalRequests: number;
88
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Copy a database and its open sidecar files into a new directory.
3
+ *
4
+ * This is the fallback for an installation whose old and new plugin
5
+ * directories sit on different filesystems, where a directory move cannot
6
+ * work. Only the files this plugin owns are copied and removed; anything else
7
+ * a user left in the directory stays where it is.
8
+ *
9
+ * @param legacy - directory the previous name used.
10
+ * @param next - directory the current name uses.
11
+ * @param filename - database file name inside both directories.
12
+ * @returns whether the database was copied.
13
+ */
14
+ export declare function copyLegacyStateFile(legacy: string, next: string, filename?: string): boolean;
15
+ /**
16
+ * Carry a plugin-owned directory over to the name this release uses.
17
+ *
18
+ * A rename must keep the state it already has: the Fusion ledger holds every
19
+ * task, brief and artifact, so an upgrade that starts an empty store beside
20
+ * the old one silently loses the user's work. A directory move is tried first
21
+ * so a live database is never copied while another process has it open; a
22
+ * cross-device installation falls back to {@link copyLegacyStateFile}.
23
+ *
24
+ * @param legacy - directory the previous name used.
25
+ * @param next - directory the current name uses.
26
+ * @param filename - database file name inside both directories.
27
+ * @returns whether a legacy directory was adopted.
28
+ */
29
+ export declare function adoptLegacyStateDirectory(legacy: string, next: string, filename?: string): boolean;
@@ -0,0 +1,64 @@
1
+ import { FusionError } from '../errors.js';
2
+ /**
3
+ * Whether a task record may be cleared out of the history.
4
+ *
5
+ * The question is not "what phase did this task reach" but "is anything still
6
+ * writing into the rows a deletion removes". A phase is a projection of the
7
+ * last event a running process appended; a process that was replaced (Host
8
+ * restart, HMR reload, crash) never appends the event that would have moved
9
+ * the phase on, so a finished task keeps the phase and `control.mode` of its
10
+ * last living moment forever. Deciding on those fields makes such a record
11
+ * permanently undeletable even though nothing owns it any more.
12
+ *
13
+ * So the decision is taken from evidence of a live writer, in two classes:
14
+ *
15
+ * - `live`: the Host in this process still owns an Agent, tool call, write
16
+ * lease or stop for the task. This is the only authority that can see
17
+ * in-process work, and it is what actually keeps a running task undeletable.
18
+ * - `foreign`: a durable row (native effect, approval, check invocation) left
19
+ * in a pending state by a *different* process that is provably still alive.
20
+ * Rows written by this process are not evidence: if the in-process authority
21
+ * says nothing is live, the row is the residue of an interrupted call.
22
+ */
23
+ export interface DeletionEvidence {
24
+ /** Why this task still owns live work in this process, or `undefined` when it owns none. */
25
+ readonly live: DeletionBlocker | undefined;
26
+ /** Pending durable rows written by another process whose pid still exists. */
27
+ readonly foreign: readonly string[];
28
+ }
29
+ /** What still holds the task, named so the panel can explain it in either language. */
30
+ export type DeletionBlocker =
31
+ /** A native Agent for this task is not idle. */
32
+ 'agent'
33
+ /** A native tool call is still in flight. */
34
+ | 'tool'
35
+ /** A native job or background stop has not proved quiescence. */
36
+ | 'job'
37
+ /** The task still holds (or is releasing) the workspace write lease. */
38
+ | 'lease'
39
+ /** Only another, still-living process holds it. */
40
+ | 'foreign-process';
41
+ export type DeletionVerdict = {
42
+ readonly allowed: true;
43
+ } | {
44
+ readonly allowed: false;
45
+ readonly blocker: DeletionBlocker;
46
+ readonly detail: string;
47
+ };
48
+ export declare const DELETION_ALLOWED: DeletionVerdict;
49
+ /** The one refusal the panel already knows how to translate; the blocker rides alongside it. */
50
+ export declare class DeletionBlocked extends FusionError {
51
+ readonly blocker: DeletionBlocker;
52
+ readonly detail: string;
53
+ constructor(blocker: DeletionBlocker, detail: string);
54
+ }
55
+ /** Pure decision, so the rule is testable without a Host, a process table or a database. */
56
+ export declare function deletionVerdict(evidence: DeletionEvidence): DeletionVerdict;
57
+ /** Recorded owner of a durable row, when the row carries a usable pid. */
58
+ export declare function recordedProcessId(value: unknown): number | undefined;
59
+ /**
60
+ * A row is unfinished when its own state says so. The families disagree on
61
+ * where they record that, so each one is read through its own field and an
62
+ * unrecognized shape counts as settled rather than pinning the record forever.
63
+ */
64
+ export declare function unfinishedDocument(family: string, value: unknown): boolean;
@@ -0,0 +1,6 @@
1
+ import type { FusionEvent } from '../contracts.js';
2
+ import type { TaskState } from './state.js';
3
+ declare function assertControlAllowsExecution(state: TaskState): void;
4
+ export declare function reduce(state: TaskState | undefined, event: FusionEvent): TaskState;
5
+ export declare function reduceAll(events: readonly FusionEvent[]): TaskState;
6
+ export { assertControlAllowsExecution };
@@ -0,0 +1,71 @@
1
+ import { StatusIndex } from './status-index.js';
2
+ import type { ArtifactRef, FusionEvent, OutboxRow, OutboxState, TaskId } from '../contracts.js';
3
+ import type { PersistedTask, TaskState } from './state.js';
4
+ import type { ArtifactRecord, FusionStore } from './store.js';
5
+ import type { DeletionBlocker } from './deletion.js';
6
+ /** Durable Host backend. Every read is fresh; CAS and outbox writes share one transaction. */
7
+ export interface DeletionAuthority {
8
+ /**
9
+ * Why this task still owns live work in this process, or `undefined` when it
10
+ * owns none. Only the Host can answer this: it holds the Agent, tool call,
11
+ * lease and stop registries. A caller without a Host passes nothing live.
12
+ */
13
+ liveReason(taskId: TaskId): DeletionBlocker | undefined;
14
+ }
15
+ export declare class SqliteFusionStore implements FusionStore {
16
+ #private;
17
+ readonly filename: string;
18
+ constructor(filename: string);
19
+ statusSnapshot<T>(read: (index: StatusIndex) => T, backfill?: boolean): T;
20
+ /** HTTP reads yield between committed batches; cancellation retains resumable progress. */
21
+ statusSnapshotAsync<T>(read: (index: StatusIndex) => T, signal?: AbortSignal): Promise<T>;
22
+ close(): void;
23
+ listTaskIds(): readonly TaskId[];
24
+ listDocumentIds(prefix: string): readonly string[];
25
+ load(taskId: TaskId): TaskState | undefined;
26
+ /**
27
+ * Remove one task record and everything that belongs to it.
28
+ *
29
+ * Liveness comes from `authority`, the in-process Host that owns the Agents,
30
+ * tool calls, leases and stops, plus durable rows a *different living* process
31
+ * left open. The persisted phase is deliberately not consulted: it describes
32
+ * where an interrupted process stopped, not whether anything still runs. See
33
+ * `deletion.ts` for why that distinction decides this bug.
34
+ */
35
+ deleteTask(taskId: string, authority: DeletionAuthority): void;
36
+ events(taskId: TaskId): readonly FusionEvent[];
37
+ replay(taskId: TaskId): TaskState;
38
+ create(event: Extract<FusionEvent, {
39
+ type: 'task/created';
40
+ }>): TaskState;
41
+ transact(taskId: TaskId, expectedSeq: number, mutate: (current: PersistedTask) => {
42
+ events?: readonly FusionEvent[];
43
+ outbox?: readonly OutboxRow[];
44
+ }): TaskState;
45
+ append(taskId: TaskId, expectedRevision: number, events: readonly FusionEvent[]): TaskState;
46
+ putArtifact(taskId: TaskId, bytes: Uint8Array, mediaType: string): ArtifactRef;
47
+ /** One atomic metadata update for a bounded workspace evidence batch. */
48
+ putArtifacts(taskId: TaskId, inputs: readonly {
49
+ bytes: Uint8Array;
50
+ mediaType: string;
51
+ }[]): ArtifactRef[];
52
+ /** Load owned evidence bytes and verify content before trusting the reference. */
53
+ readArtifact(taskId: TaskId, digest: string): Uint8Array;
54
+ readArtifacts(taskId: TaskId, digests: readonly string[]): Uint8Array[];
55
+ prepareOutbox(row: OutboxRow): OutboxRow;
56
+ advanceOutbox(taskId: TaskId, operationId: string, from: OutboxState, to: OutboxState, patch?: Partial<OutboxRow>): OutboxRow;
57
+ outbox(taskId: TaskId): readonly OutboxRow[];
58
+ artifacts(taskId: TaskId): readonly ArtifactRecord[];
59
+ /** Host binding / usage documents require caller-side schema validation after loading. */
60
+ readDocument(id: string): {
61
+ revision: number;
62
+ value: unknown;
63
+ } | undefined;
64
+ /** Compare-and-swap prevents an old Host instance overwriting a newer selection or bill. */
65
+ writeDocument(id: string, expectedRevision: number, value: unknown): number;
66
+ writeDocuments(rows: readonly {
67
+ id: string;
68
+ expectedRevision: number;
69
+ value: unknown;
70
+ }[]): number[];
71
+ }
@@ -0,0 +1,51 @@
1
+ import type { ControlState, Digest, ExecutionIntent, ExplorationReport, FusionEvent, LogicalApproval, OutboxRow, Phase, ReviewGate, ReviewReadinessReceipt, ReviewResultV2, ReviewTicket, RunSelection, SessionId, SnapshotId, TaskId, VerificationLevel, WorkOrder, WorkerReport, WriteLease } from '../contracts.js';
2
+ export interface AcceptedReview {
3
+ readonly ticket: ReviewTicket;
4
+ readonly resultDigest: Digest;
5
+ }
6
+ export interface TaskState {
7
+ readonly schemaVersion: 1;
8
+ readonly taskId: TaskId;
9
+ readonly revision: number;
10
+ readonly seq: number;
11
+ readonly phase: Phase;
12
+ readonly parent: SessionId;
13
+ readonly selection: RunSelection;
14
+ readonly profileDigest?: Digest;
15
+ readonly intent?: ExecutionIntent;
16
+ readonly reservedChild?: SessionId;
17
+ readonly acceptedChild?: SessionId;
18
+ readonly acceptedMessageId?: string;
19
+ readonly currentWorkOrder?: WorkOrder;
20
+ readonly exploration?: ExplorationReport;
21
+ readonly candidateReport?: WorkerReport;
22
+ readonly validatedReport?: WorkerReport;
23
+ readonly validatedReceipt?: ReviewReadinessReceipt;
24
+ readonly activeReviewTicket?: ReviewTicket;
25
+ readonly reviewResult?: ReviewResultV2;
26
+ readonly reviewResultDigest?: Digest;
27
+ readonly acceptedReview?: AcceptedReview;
28
+ readonly reviewGeneration: number;
29
+ readonly ignoredReviewResults: readonly ReviewResultV2[];
30
+ readonly staleValidations: readonly {
31
+ readonly reportDigest: Digest;
32
+ readonly reason: string;
33
+ }[];
34
+ /** Candidate if unvalidated, otherwise the validated report. Compatibility projection. */
35
+ readonly lastReport?: WorkerReport;
36
+ readonly reviewGate?: ReviewGate;
37
+ readonly verification: VerificationLevel;
38
+ readonly pendingApprovalIds: readonly string[];
39
+ readonly pendingApprovals: Readonly<Record<string, LogicalApproval>>;
40
+ readonly control: ControlState;
41
+ readonly outcomeUnknown: boolean;
42
+ readonly resumePhase?: Phase;
43
+ readonly lease?: WriteLease;
44
+ readonly lastSnapshot?: SnapshotId;
45
+ readonly appliedEventIds: readonly string[];
46
+ }
47
+ export interface PersistedTask {
48
+ readonly state: TaskState;
49
+ readonly events: readonly FusionEvent[];
50
+ readonly outbox: readonly OutboxRow[];
51
+ }
@@ -0,0 +1,28 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { FusionStatus } from '../status.js';
3
+ type TaskStatus = NonNullable<FusionStatus['task']>;
4
+ export declare class StatusDataError extends Error {
5
+ }
6
+ export declare class StatusPending extends Error {
7
+ readonly task: string;
8
+ constructor(task: string);
9
+ }
10
+ /** Rebuildable read model. Triggers also track writes/deletes by older plugin processes. */
11
+ export declare class StatusIndex {
12
+ #private;
13
+ readonly db: DatabaseSync;
14
+ constructor(db: DatabaseSync);
15
+ /** Call inside the document transaction, after the source row is written. */
16
+ sync(id: string): void;
17
+ /** Each batch is its own transaction, so a legacy backfill does not monopolize the writer lock. */
18
+ flushBatch(task: string): boolean;
19
+ /** Bounded memory even for a legacy database; the surrounding transaction is atomic. */
20
+ flush(task?: string): void;
21
+ rebuild(): void;
22
+ read(task: string): Pick<TaskStatus, 'usage' | 'requests' | 'tools' | 'contexts' | 'unsettledTools'> & {
23
+ checking: boolean;
24
+ };
25
+ workerConversations(task: string): number;
26
+ hasConversation(task: string, session: string): boolean;
27
+ }
28
+ export {};
@@ -0,0 +1,48 @@
1
+ import type { ArtifactRef, FusionEvent, OutboxRow, OutboxState, TaskId } from '../contracts.js';
2
+ import type { PersistedTask, TaskState } from './state.js';
3
+ export interface StoreIo {
4
+ existsSync(path: string): boolean;
5
+ mkdirSync(path: string): void;
6
+ readFileSync(path: string): string;
7
+ writeFileSync(path: string, data: string | Uint8Array): void;
8
+ renameSync(from: string, to: string): void;
9
+ rmSync(path: string): void;
10
+ }
11
+ export declare const nodeIo: StoreIo;
12
+ export interface ArtifactRecord extends ArtifactRef {
13
+ readonly storageRef: string;
14
+ }
15
+ export declare const STORE_PROJECTION_VERSION = 2;
16
+ export interface DiskSnapshot {
17
+ readonly projectionVersion: typeof STORE_PROJECTION_VERSION;
18
+ readonly checksum: string;
19
+ readonly persisted: PersistedTask;
20
+ readonly artifacts: readonly ArtifactRecord[];
21
+ }
22
+ /** The reducer and review producer share this port across file and transactional stores. */
23
+ export type FusionStore = Pick<FileFusionStore, 'load' | 'replay' | 'create' | 'transact' | 'append' | 'putArtifact' | 'prepareOutbox' | 'advanceOutbox' | 'outbox' | 'artifacts'>;
24
+ export declare class FileFusionStore {
25
+ #private;
26
+ readonly root: string;
27
+ readonly io: StoreIo;
28
+ constructor(root: string, io?: StoreIo);
29
+ load(taskId: TaskId): TaskState | undefined;
30
+ replay(taskId: TaskId): TaskState;
31
+ create(event: Extract<FusionEvent, {
32
+ type: 'task/created';
33
+ }>): TaskState;
34
+ transact(taskId: TaskId, expectedSeq: number, mutate: (current: PersistedTask) => {
35
+ events?: readonly FusionEvent[];
36
+ outbox?: readonly OutboxRow[];
37
+ }): TaskState;
38
+ append(taskId: TaskId, expectedRevision: number, events: readonly FusionEvent[]): TaskState;
39
+ putArtifact(taskId: TaskId, bytes: Uint8Array, mediaType: string): ArtifactRef;
40
+ prepareOutbox(row: OutboxRow): OutboxRow;
41
+ advanceOutbox(taskId: TaskId, operationId: string, from: OutboxState, to: OutboxState, patch?: Partial<OutboxRow>): OutboxRow;
42
+ outbox(taskId: TaskId): readonly OutboxRow[];
43
+ artifacts(taskId: TaskId): readonly ArtifactRecord[];
44
+ }
45
+ /** Validate before either backend admits a persisted execution projection. */
46
+ export declare function decodeStoreSnapshot(raw: string, taskId: TaskId): DiskSnapshot;
47
+ export declare function requireStorageProjectionVersion(raw: unknown): asserts raw is DiskSnapshot;
48
+ export declare function failingRenameIo(base: StoreIo, failOnce?: boolean): StoreIo;
@@ -0,0 +1,27 @@
1
+ import type { UsdDecimal } from '../contracts.js';
2
+ export interface SpendingAuthorization {
3
+ schemaVersion: 1;
4
+ approved: boolean;
5
+ authorizationId: string | null;
6
+ currency: string;
7
+ maximumTotal: string | null;
8
+ allowedModels: readonly string[];
9
+ expiresAt: string | null;
10
+ approvedBy: string | null;
11
+ }
12
+ export interface Reservation {
13
+ readonly authorizationId: string;
14
+ readonly reserved: UsdDecimal;
15
+ readonly remaining: UsdDecimal | null;
16
+ }
17
+ export declare class BudgetLedger {
18
+ #private;
19
+ readonly authorization: SpendingAuthorization;
20
+ constructor(authorization: SpendingAuthorization);
21
+ assertAuthorized(model?: string): void;
22
+ reserve(estimatedUsd: number, model?: string): Reservation;
23
+ settle(reserved: Reservation, actualUsd: number | null): void;
24
+ get reserved(): number;
25
+ get spent(): number;
26
+ }
27
+ export declare function loadSpendingAuthorization(raw: unknown): SpendingAuthorization;