@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,65 @@
1
+ import { cpSync, existsSync, mkdirSync, renameSync, rmSync, rmdirSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+
4
+ /** Files SQLite may keep open beside the database itself. */
5
+ const SQLITE_SIDECARS = ['-wal', '-shm', '-journal'] as const
6
+
7
+ /**
8
+ * Copy a database and its open sidecar files into a new directory.
9
+ *
10
+ * This is the fallback for an installation whose old and new plugin
11
+ * directories sit on different filesystems, where a directory move cannot
12
+ * work. Only the files this plugin owns are copied and removed; anything else
13
+ * a user left in the directory stays where it is.
14
+ *
15
+ * @param legacy - directory the previous name used.
16
+ * @param next - directory the current name uses.
17
+ * @param filename - database file name inside both directories.
18
+ * @returns whether the database was copied.
19
+ */
20
+ export function copyLegacyStateFile(legacy: string, next: string, filename = 'state.sqlite'): boolean {
21
+ const source = join(legacy, filename), target = join(next, filename)
22
+ if (existsSync(target) || !existsSync(source)) return false
23
+ try {
24
+ mkdirSync(next, { recursive: true })
25
+ cpSync(source, target)
26
+ for (const suffix of SQLITE_SIDECARS) {
27
+ if (existsSync(`${source}${suffix}`)) cpSync(`${source}${suffix}`, `${target}${suffix}`)
28
+ }
29
+ } catch {
30
+ for (const suffix of ['', ...SQLITE_SIDECARS]) rmSync(`${target}${suffix}`, { force: true })
31
+ return false
32
+ }
33
+ for (const suffix of ['', ...SQLITE_SIDECARS]) rmSync(`${source}${suffix}`, { force: true })
34
+ try {
35
+ rmdirSync(legacy)
36
+ } catch {
37
+ // Another file lives there; the copied database is complete either way.
38
+ }
39
+ return true
40
+ }
41
+
42
+ /**
43
+ * Carry a plugin-owned directory over to the name this release uses.
44
+ *
45
+ * A rename must keep the state it already has: the Fusion ledger holds every
46
+ * task, brief and artifact, so an upgrade that starts an empty store beside
47
+ * the old one silently loses the user's work. A directory move is tried first
48
+ * so a live database is never copied while another process has it open; a
49
+ * cross-device installation falls back to {@link copyLegacyStateFile}.
50
+ *
51
+ * @param legacy - directory the previous name used.
52
+ * @param next - directory the current name uses.
53
+ * @param filename - database file name inside both directories.
54
+ * @returns whether a legacy directory was adopted.
55
+ */
56
+ export function adoptLegacyStateDirectory(legacy: string, next: string, filename = 'state.sqlite'): boolean {
57
+ if (existsSync(next) || !existsSync(legacy)) return false
58
+ try {
59
+ renameSync(legacy, next)
60
+ return true
61
+ } catch {
62
+ // Cross-device or a locked directory: copy the database files instead.
63
+ }
64
+ return copyLegacyStateFile(legacy, next, filename)
65
+ }
@@ -0,0 +1,105 @@
1
+ import { FusionError, TASK_BUSY } from '../errors.js'
2
+
3
+ /**
4
+ * Whether a task record may be cleared out of the history.
5
+ *
6
+ * The question is not "what phase did this task reach" but "is anything still
7
+ * writing into the rows a deletion removes". A phase is a projection of the
8
+ * last event a running process appended; a process that was replaced (Host
9
+ * restart, HMR reload, crash) never appends the event that would have moved
10
+ * the phase on, so a finished task keeps the phase and `control.mode` of its
11
+ * last living moment forever. Deciding on those fields makes such a record
12
+ * permanently undeletable even though nothing owns it any more.
13
+ *
14
+ * So the decision is taken from evidence of a live writer, in two classes:
15
+ *
16
+ * - `live`: the Host in this process still owns an Agent, tool call, write
17
+ * lease or stop for the task. This is the only authority that can see
18
+ * in-process work, and it is what actually keeps a running task undeletable.
19
+ * - `foreign`: a durable row (native effect, approval, check invocation) left
20
+ * in a pending state by a *different* process that is provably still alive.
21
+ * Rows written by this process are not evidence: if the in-process authority
22
+ * says nothing is live, the row is the residue of an interrupted call.
23
+ */
24
+ export interface DeletionEvidence {
25
+ /** Why this task still owns live work in this process, or `undefined` when it owns none. */
26
+ readonly live: DeletionBlocker | undefined
27
+ /** Pending durable rows written by another process whose pid still exists. */
28
+ readonly foreign: readonly string[]
29
+ }
30
+
31
+ /** What still holds the task, named so the panel can explain it in either language. */
32
+ export type DeletionBlocker =
33
+ /** A native Agent for this task is not idle. */
34
+ | 'agent'
35
+ /** A native tool call is still in flight. */
36
+ | 'tool'
37
+ /** A native job or background stop has not proved quiescence. */
38
+ | 'job'
39
+ /** The task still holds (or is releasing) the workspace write lease. */
40
+ | 'lease'
41
+ /** Only another, still-living process holds it. */
42
+ | 'foreign-process'
43
+
44
+ export type DeletionVerdict =
45
+ | { readonly allowed: true }
46
+ | { readonly allowed: false; readonly blocker: DeletionBlocker; readonly detail: string }
47
+
48
+ export const DELETION_ALLOWED: DeletionVerdict = { allowed: true }
49
+
50
+ /** The one refusal the panel already knows how to translate; the blocker rides alongside it. */
51
+ export class DeletionBlocked extends FusionError {
52
+ constructor(readonly blocker: DeletionBlocker, readonly detail: string) {
53
+ super(TASK_BUSY, 'Cannot delete a running task')
54
+ }
55
+ }
56
+
57
+ /** Pure decision, so the rule is testable without a Host, a process table or a database. */
58
+ export function deletionVerdict(evidence: DeletionEvidence): DeletionVerdict {
59
+ if (evidence.live) return { allowed: false, blocker: evidence.live, detail: LIVE_DETAIL[evidence.live] }
60
+ if (evidence.foreign.length) {
61
+ const shown = evidence.foreign.slice(0, 3).join(', ')
62
+ const more = evidence.foreign.length > 3 ? ` and ${evidence.foreign.length - 3} more` : ''
63
+ return { allowed: false, blocker: 'foreign-process', detail: `another Host process is still working on it (${shown}${more})` }
64
+ }
65
+ return DELETION_ALLOWED
66
+ }
67
+
68
+ const LIVE_DETAIL: Readonly<Record<DeletionBlocker, string>> = {
69
+ agent: 'its Lead or Sidekick is not idle',
70
+ tool: 'a native tool call is still in flight',
71
+ job: 'a native job is still running',
72
+ lease: 'it still holds the workspace write lease',
73
+ 'foreign-process': 'another Host process is still working on it',
74
+ }
75
+
76
+ /** Recorded owner of a durable row, when the row carries a usable pid. */
77
+ export function recordedProcessId(value: unknown): number | undefined {
78
+ if (!value || typeof value !== 'object') return undefined
79
+ const pid = (value as { processId?: unknown }).processId
80
+ return typeof pid === 'number' && Number.isSafeInteger(pid) && pid > 0 ? pid : undefined
81
+ }
82
+
83
+ /** Durable state names that mean the row was still open when its process stopped. */
84
+ const OPEN_EFFECT = new Set(['dispatch-started', 'outcome-unknown'])
85
+
86
+ /**
87
+ * A row is unfinished when its own state says so. The families disagree on
88
+ * where they record that, so each one is read through its own field and an
89
+ * unrecognized shape counts as settled rather than pinning the record forever.
90
+ */
91
+ export function unfinishedDocument(family: string, value: unknown): boolean {
92
+ if (!value || typeof value !== 'object') return false
93
+ const row = value as Record<string, unknown>
94
+ switch (family) {
95
+ case 'native-effect': return typeof row.state === 'string' && OPEN_EFFECT.has(row.state)
96
+ case 'native-approval': return row.state === 'pending'
97
+ case 'check-invocation': {
98
+ const evidence = row.evidence
99
+ if (!evidence || typeof evidence !== 'object') return false
100
+ const state = (evidence as { state?: unknown }).state
101
+ return state === 'started' || state === 'outcome-unknown'
102
+ }
103
+ default: return false
104
+ }
105
+ }