@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,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
|
+
}
|