@foggy-projects/deepseek-harness-plugin 0.4.0-beta.10
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/LICENSE +201 -0
- package/README.md +81 -0
- package/THIRD-PARTY-RUNTIME-NOTICES.md +16 -0
- package/cordis.patch.yml +6 -0
- package/docs/PUBLIC-BETA-READINESS.md +57 -0
- package/docs/WINDOWS-BETA-ACCEPTANCE.md +75 -0
- package/experience/linux/README.md +53 -0
- package/experience/linux/prepare.sh +188 -0
- package/lib/atomic-json.js +41 -0
- package/lib/client.js +453 -0
- package/lib/index.js +467 -0
- package/lib/python-runtime.js +339 -0
- package/lib/remote-descriptor.js +30 -0
- package/lib/remote.js +8 -0
- package/lib/skill-provider.js +111 -0
- package/lib/typert.js +9 -0
- package/lib/version.js +20 -0
- package/package.json +63 -0
- package/skills/foggy-deepseek-onboarding/SKILL.md +137 -0
- package/skills/foggy-deepseek-onboarding/assets/connection.schema.json +50 -0
- package/skills/foggy-deepseek-onboarding/assets/datasource.example.json +13 -0
- package/skills/foggy-deepseek-onboarding/assets/env.example +14 -0
- package/skills/foggy-deepseek-onboarding/assets/onboarding-state.schema.json +42 -0
- package/skills/foggy-deepseek-onboarding/assets/semantic-plan.example.json +8 -0
- package/skills/foggy-deepseek-onboarding/assets/semantic-plan.schema.json +21 -0
- package/skills/foggy-deepseek-onboarding/assets/versions.json +137 -0
- package/skills/foggy-deepseek-onboarding/references/onboarding-workflow.md +156 -0
- package/skills/foggy-deepseek-onboarding/scripts/doctor.ps1 +4 -0
- package/skills/foggy-deepseek-onboarding/scripts/doctor.sh +4 -0
- package/skills/foggy-deepseek-onboarding/scripts/install.ps1 +4 -0
- package/skills/foggy-deepseek-onboarding/scripts/install.sh +4 -0
- package/skills/foggy-deepseek-onboarding/scripts/invoke-onboarding.ps1 +30 -0
- package/skills/foggy-deepseek-onboarding/scripts/invoke-onboarding.sh +24 -0
- package/skills/foggy-deepseek-onboarding/scripts/onboard.ps1 +4 -0
- package/skills/foggy-deepseek-onboarding/scripts/onboard.sh +4 -0
- package/skills/foggy-deepseek-onboarding/scripts/onboarding.py +3027 -0
- package/skills/foggy-deepseek-onboarding/scripts/runtime-start.ps1 +4 -0
- package/skills/foggy-deepseek-onboarding/scripts/runtime-start.sh +4 -0
- package/skills/foggy-deepseek-onboarding/scripts/runtime-stop.ps1 +4 -0
- package/skills/foggy-deepseek-onboarding/scripts/runtime-stop.sh +4 -0
- package/skills/foggy-deepseek-onboarding/scripts/uninstall.ps1 +4 -0
- package/skills/foggy-deepseek-onboarding/scripts/uninstall.sh +4 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process'
|
|
2
|
+
import { access, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { constants as fsConstants } from 'node:fs'
|
|
4
|
+
import { dirname, join, delimiter } from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { promisify } from 'node:util'
|
|
7
|
+
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'
|
|
8
|
+
import { createFoggySkillProvider } from './skill-provider.js'
|
|
9
|
+
import { writeJsonAtomic } from './atomic-json.js'
|
|
10
|
+
import { ensurePythonRuntime, probePythonRuntime } from './python-runtime.js'
|
|
11
|
+
import { compatible, compatibleNode } from './version.js'
|
|
12
|
+
|
|
13
|
+
const execFileAsync = promisify(execFile)
|
|
14
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)))
|
|
15
|
+
const onboardingScript = join(packageRoot, 'skills', 'foggy-deepseek-onboarding', 'scripts', 'onboarding.py')
|
|
16
|
+
const versionsFile = join(packageRoot, 'skills', 'foggy-deepseek-onboarding', 'assets', 'versions.json')
|
|
17
|
+
|
|
18
|
+
function defaultRoots() {
|
|
19
|
+
if (process.platform === 'win32') {
|
|
20
|
+
const base = process.env.LOCALAPPDATA
|
|
21
|
+
if (!base) throw new Error('LOCALAPPDATA is not set')
|
|
22
|
+
return {
|
|
23
|
+
installRoot: join(base, 'Foggy', 'DeepSeekHarness'),
|
|
24
|
+
dataRoot: join(base, 'Foggy', 'DeepSeekHarnessData'),
|
|
25
|
+
profileStore: process.env.FOGGY_RUNTIME_PROFILE_STORE || join(base, 'Foggy', 'DeepSeekHarnessData', 'cli-profiles'),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const home = process.env.HOME
|
|
29
|
+
if (!home) throw new Error('HOME is not set')
|
|
30
|
+
const dataRoot = process.env.XDG_STATE_HOME
|
|
31
|
+
? join(process.env.XDG_STATE_HOME, 'foggy', 'deepseek-harness')
|
|
32
|
+
: join(home, '.local', 'state', 'foggy', 'deepseek-harness')
|
|
33
|
+
return {
|
|
34
|
+
installRoot: process.env.XDG_DATA_HOME
|
|
35
|
+
? join(process.env.XDG_DATA_HOME, 'foggy', 'deepseek-harness')
|
|
36
|
+
: join(home, '.local', 'share', 'foggy', 'deepseek-harness'),
|
|
37
|
+
dataRoot,
|
|
38
|
+
profileStore: process.env.FOGGY_RUNTIME_PROFILE_STORE || join(dataRoot, 'cli-profiles'),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function exists(path) {
|
|
43
|
+
try {
|
|
44
|
+
await access(path, fsConstants.F_OK)
|
|
45
|
+
return true
|
|
46
|
+
} catch {
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function readJson(path) {
|
|
52
|
+
return JSON.parse(await readFile(path, 'utf8'))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function readOptionalJson(path) {
|
|
56
|
+
try {
|
|
57
|
+
return await readJson(path)
|
|
58
|
+
} catch {
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function commandVersion(command, args) {
|
|
64
|
+
try {
|
|
65
|
+
const { stdout, stderr } = await execFileAsync(command, args, {
|
|
66
|
+
windowsHide: true,
|
|
67
|
+
timeout: 15_000,
|
|
68
|
+
maxBuffer: 256 * 1024,
|
|
69
|
+
})
|
|
70
|
+
return { available: true, output: `${stdout}\n${stderr}`.trim().split(/\r?\n/)[0] ?? '' }
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return { available: false, output: '', error: error.code ?? 'COMMAND_FAILED' }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function parseOutput(stdout, stderr) {
|
|
77
|
+
const text = String(stdout ?? '').trim()
|
|
78
|
+
if (!text) throw new Error(String(stderr ?? '').trim() || 'Foggy onboarding returned no JSON')
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(text)
|
|
81
|
+
} catch {
|
|
82
|
+
throw new Error(`Foggy onboarding returned invalid JSON: ${text.slice(0, 240)}`)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function runOnboarding(args, timeout = 15 * 60_000, options = {}) {
|
|
87
|
+
const roots = defaultRoots()
|
|
88
|
+
const manifest = await readJson(versionsFile)
|
|
89
|
+
const cacheDirs = (process.env.FOGGY_ASSET_CACHE_DIRS || '').split(delimiter).filter(Boolean)
|
|
90
|
+
const python = options.ensurePython
|
|
91
|
+
? await ensurePythonRuntime({
|
|
92
|
+
installRoot: roots.installRoot,
|
|
93
|
+
manifest,
|
|
94
|
+
cacheDirs,
|
|
95
|
+
force: options.forcePython,
|
|
96
|
+
onProgress: options.onPythonProgress,
|
|
97
|
+
})
|
|
98
|
+
: await probePythonRuntime({ installRoot: roots.installRoot, manifest })
|
|
99
|
+
if (!python.available) {
|
|
100
|
+
throw new Error('Foggy managed Python is unavailable; initialize or repair the Python component')
|
|
101
|
+
}
|
|
102
|
+
await options.flushPythonProgress?.()
|
|
103
|
+
try {
|
|
104
|
+
const { stdout, stderr } = await execFileAsync(python.path, [onboardingScript, ...args], {
|
|
105
|
+
windowsHide: true,
|
|
106
|
+
timeout,
|
|
107
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
108
|
+
cwd: process.env.FOGGY_PROJECT_ROOT || process.cwd(),
|
|
109
|
+
env: {
|
|
110
|
+
...process.env,
|
|
111
|
+
FOGGY_ONBOARDING_PYTHON: python.path,
|
|
112
|
+
FOGGY_ONBOARDING_PYTHON_SOURCE: python.source,
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
return parseOutput(stdout, stderr)
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error.stdout) return parseOutput(error.stdout, error.stderr)
|
|
118
|
+
throw error
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function assertSystemPrerequisites(kind) {
|
|
123
|
+
if (kind === 'initialize' && !compatibleNode(process.versions.node)) {
|
|
124
|
+
throw new Error(`DeepSeek Harness requires Node.js ^22.19.0 or >=24.0.0; detected ${process.versions.node}`)
|
|
125
|
+
}
|
|
126
|
+
if (kind === 'initialize' || kind === 'runtime-start') {
|
|
127
|
+
const java = compatible(await commandVersion(process.env.JAVA_EXE || 'java', ['-version']), '17.0')
|
|
128
|
+
if (!java.available) throw new Error('Java 17+ is required; install a system JRE/JDK or set JAVA_EXE')
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function createPythonProgressReporter(operation, path) {
|
|
133
|
+
let pending = Promise.resolve()
|
|
134
|
+
return {
|
|
135
|
+
update(detail) {
|
|
136
|
+
const fraction = Math.max(0, Math.min(1, Number(detail.fraction) || 0))
|
|
137
|
+
const payload = {
|
|
138
|
+
schemaVersion: 'foggy-deepseek-onboarding-progress/v1',
|
|
139
|
+
operationId: operation.id,
|
|
140
|
+
kind: operation.kind,
|
|
141
|
+
state: 'running',
|
|
142
|
+
phase: 'python',
|
|
143
|
+
message: detail.message || 'Preparing managed Python',
|
|
144
|
+
currentFile: detail.currentFile || null,
|
|
145
|
+
percent: Math.round(((1 + fraction) / 7) * 100),
|
|
146
|
+
step: { index: 2, total: 7 },
|
|
147
|
+
startedAt: operation.startedAt,
|
|
148
|
+
updatedAt: new Date().toISOString(),
|
|
149
|
+
}
|
|
150
|
+
if (detail.bytes) payload.bytes = detail.bytes
|
|
151
|
+
operation.progress = payload
|
|
152
|
+
pending = pending.then(() => writeJsonAtomic(path, payload))
|
|
153
|
+
},
|
|
154
|
+
flush() {
|
|
155
|
+
return pending
|
|
156
|
+
},
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function processRunning(pid) {
|
|
161
|
+
if (!Number.isInteger(pid) || pid <= 0) return false
|
|
162
|
+
try {
|
|
163
|
+
process.kill(pid, 0)
|
|
164
|
+
return true
|
|
165
|
+
} catch {
|
|
166
|
+
return false
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function operationView(operation) {
|
|
171
|
+
if (!operation) return { state: 'idle' }
|
|
172
|
+
const { promise: _promise, ...view } = operation
|
|
173
|
+
return view
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
177
|
+
static inject = ['skills']
|
|
178
|
+
|
|
179
|
+
operation = null
|
|
180
|
+
skillProviderControl = null
|
|
181
|
+
|
|
182
|
+
constructor(ctx) {
|
|
183
|
+
super(ctx, 'foggyIntegration')
|
|
184
|
+
const roots = defaultRoots()
|
|
185
|
+
ctx.skills.registerProvider((control) => {
|
|
186
|
+
this.skillProviderControl = control
|
|
187
|
+
return createFoggySkillProvider({ installRoot: roots.installRoot, versionsFile })
|
|
188
|
+
})
|
|
189
|
+
for (const initialize of markerInitializers) initialize.call(this)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async status() {
|
|
193
|
+
const roots = defaultRoots()
|
|
194
|
+
const statePath = join(roots.installRoot, 'install-state.json')
|
|
195
|
+
const runtimeStatePath = join(roots.dataRoot, 'runtime-state.json')
|
|
196
|
+
const progressPath = join(roots.dataRoot, 'operation-progress.json')
|
|
197
|
+
const manifest = await readJson(versionsFile)
|
|
198
|
+
const python = await probePythonRuntime({ installRoot: roots.installRoot, manifest })
|
|
199
|
+
const java = compatible(await commandVersion(process.env.JAVA_EXE || 'java', ['-version']), '17.0')
|
|
200
|
+
let state = null
|
|
201
|
+
let runtime = null
|
|
202
|
+
try { state = await readJson(statePath) } catch {}
|
|
203
|
+
try { runtime = await readJson(runtimeStatePath) } catch {}
|
|
204
|
+
let onboarding = { success: true, profiles: [], profileCount: 0 }
|
|
205
|
+
let profileMigration = { success: true, entries: [], pendingCount: 0, conflictCount: 0, profileStore: roots.profileStore }
|
|
206
|
+
if (state) {
|
|
207
|
+
try { onboarding = await runOnboarding(['onboard-list']) } catch (error) {
|
|
208
|
+
onboarding = { success: false, profiles: [], profileCount: 0, error: String(error.message ?? error) }
|
|
209
|
+
}
|
|
210
|
+
try { profileMigration = await runOnboarding(['profile-migration-status']) } catch (error) {
|
|
211
|
+
profileMigration = { success: false, entries: [], pendingCount: 0, conflictCount: 0, profileStore: roots.profileStore, error: String(error.message ?? error) }
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const cliPath = state?.cli?.command
|
|
215
|
+
const launcherPath = state?.launcher?.path
|
|
216
|
+
const analysisSkillPath = state?.skills?.analysis?.path || join(roots.installRoot, 'skills', 'foggy-ai-analysis')
|
|
217
|
+
const onboardingSkillPath = join(packageRoot, 'skills', 'foggy-deepseek-onboarding')
|
|
218
|
+
const analysisMarker = await readOptionalJson(join(analysisSkillPath, '.foggy-managed-skill.json'))
|
|
219
|
+
const components = {
|
|
220
|
+
python,
|
|
221
|
+
java,
|
|
222
|
+
cli: {
|
|
223
|
+
installed: Boolean(cliPath && await exists(cliPath)),
|
|
224
|
+
version: state?.cli?.version ?? manifest.components.cli.version,
|
|
225
|
+
},
|
|
226
|
+
launcher: {
|
|
227
|
+
installed: Boolean(launcherPath && await exists(launcherPath)),
|
|
228
|
+
version: state?.launcher?.version ?? manifest.components.launcher.version,
|
|
229
|
+
},
|
|
230
|
+
analysisSkill: {
|
|
231
|
+
installed: Boolean(
|
|
232
|
+
await exists(join(analysisSkillPath, 'SKILL.md'))
|
|
233
|
+
&& analysisMarker?.schemaVersion === 'foggy-managed-skill/v1'
|
|
234
|
+
&& analysisMarker?.componentVersion === manifest.components.analysisSkill.version
|
|
235
|
+
),
|
|
236
|
+
version: state?.skills?.analysis?.version ?? manifest.components.analysisSkill.version,
|
|
237
|
+
},
|
|
238
|
+
onboardingSkill: {
|
|
239
|
+
installed: await exists(join(onboardingSkillPath, 'SKILL.md')),
|
|
240
|
+
version: manifest.packageVersion,
|
|
241
|
+
provider: 'foggy-managed-skills',
|
|
242
|
+
},
|
|
243
|
+
}
|
|
244
|
+
const installed = components.python.available
|
|
245
|
+
&& components.cli.installed
|
|
246
|
+
&& components.launcher.installed
|
|
247
|
+
&& components.analysisSkill.installed
|
|
248
|
+
&& components.onboardingSkill.installed
|
|
249
|
+
const running = Boolean(runtime && processRunning(Number(runtime.pid)))
|
|
250
|
+
const progress = await readOptionalJson(progressPath)
|
|
251
|
+
let operation = operationView(this.operation)
|
|
252
|
+
if (operation.state === 'running' && progress?.operationId === operation.id) {
|
|
253
|
+
operation = { ...operation, progress }
|
|
254
|
+
} else if (!this.operation && progress?.state === 'running' && progress.operationId) {
|
|
255
|
+
operation = {
|
|
256
|
+
id: progress.operationId,
|
|
257
|
+
kind: progress.kind || 'initialize',
|
|
258
|
+
state: 'running',
|
|
259
|
+
startedAt: progress.startedAt || null,
|
|
260
|
+
finishedAt: null,
|
|
261
|
+
result: null,
|
|
262
|
+
error: null,
|
|
263
|
+
progress,
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
success: true,
|
|
268
|
+
packageVersion: manifest.packageVersion,
|
|
269
|
+
state: running ? 'running' : installed ? 'ready' : state ? 'degraded' : 'not-installed',
|
|
270
|
+
installed,
|
|
271
|
+
running,
|
|
272
|
+
runtimeUrl: running ? runtime.runtimeUrl ?? null : null,
|
|
273
|
+
roots,
|
|
274
|
+
components,
|
|
275
|
+
operation,
|
|
276
|
+
onboarding,
|
|
277
|
+
profileMigration,
|
|
278
|
+
next: installed ? (running ? 'configure-database' : 'start-runtime') : 'initialize',
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async plan() {
|
|
283
|
+
const roots = defaultRoots()
|
|
284
|
+
const manifest = await readJson(versionsFile)
|
|
285
|
+
return {
|
|
286
|
+
success: true,
|
|
287
|
+
roots,
|
|
288
|
+
workspaceMode: 'dsh-session-cwd',
|
|
289
|
+
versions: Object.fromEntries(Object.entries(manifest.components).map(([name, value]) => [name, value.version])),
|
|
290
|
+
operations: [
|
|
291
|
+
'download and verify a pinned private Python runtime',
|
|
292
|
+
'create isolated Python environment',
|
|
293
|
+
'download and verify pinned CLI and Launcher assets',
|
|
294
|
+
'install the Foggy analysis Skill into the global managed component directory',
|
|
295
|
+
'register onboarding and analysis Skills through the native DSH Skill registry',
|
|
296
|
+
'write global install state; resolve each workspace from the DSH session cwd',
|
|
297
|
+
],
|
|
298
|
+
secretsInDshSettings: false,
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async initialize() {
|
|
303
|
+
return this.startOperation('initialize', false)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async repair() {
|
|
307
|
+
return this.startOperation('repair', true)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async repairCli() {
|
|
311
|
+
return this.startOperation('repair-cli', false, ['install', '--repair-component', 'cli'])
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async repairPython() {
|
|
315
|
+
return this.startOperation('repair-python', false, ['install'])
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async repairLauncher() {
|
|
319
|
+
return this.startOperation('repair-launcher', false, ['install', '--repair-component', 'launcher'])
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async repairAnalysisSkill() {
|
|
323
|
+
return this.startOperation('repair-analysis-skill', true, ['install', '--repair-component', 'analysis-skill'])
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async migrateProfiles() {
|
|
327
|
+
return this.startOperation('profile-migration', false, ['profile-migrate', '--approve'])
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async diagnostics() {
|
|
331
|
+
const roots = defaultRoots()
|
|
332
|
+
const status = await this.status()
|
|
333
|
+
let doctor
|
|
334
|
+
try {
|
|
335
|
+
doctor = await runOnboarding(['doctor', '--no-fail'])
|
|
336
|
+
} catch (error) {
|
|
337
|
+
doctor = { success: false, error: String(error.message ?? error), bootstrapOnly: true }
|
|
338
|
+
}
|
|
339
|
+
const report = {
|
|
340
|
+
schemaVersion: 'foggy-deepseek-diagnostics/v1',
|
|
341
|
+
generatedAt: new Date().toISOString(),
|
|
342
|
+
packageVersion: status.packageVersion,
|
|
343
|
+
state: status.state,
|
|
344
|
+
installed: status.installed,
|
|
345
|
+
running: status.running,
|
|
346
|
+
runtimeUrl: status.runtimeUrl,
|
|
347
|
+
roots: status.roots,
|
|
348
|
+
components: status.components,
|
|
349
|
+
onboarding: status.onboarding,
|
|
350
|
+
profileMigration: status.profileMigration,
|
|
351
|
+
doctor,
|
|
352
|
+
}
|
|
353
|
+
const directory = join(roots.dataRoot, 'diagnostics')
|
|
354
|
+
await mkdir(directory, { recursive: true })
|
|
355
|
+
const path = join(directory, `diagnostics-${Date.now()}.json`)
|
|
356
|
+
await writeFile(path, `${JSON.stringify(report, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 })
|
|
357
|
+
return { success: true, path, report }
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async runtimeStart() {
|
|
361
|
+
return this.startOperation('runtime-start', false, ['runtime-start'])
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async runtimeStop() {
|
|
365
|
+
return this.startOperation('runtime-stop', false, ['runtime-stop'])
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
startOperation(kind, replaceSkill, explicitArgs) {
|
|
369
|
+
if (this.operation?.state === 'running') {
|
|
370
|
+
return { success: false, accepted: false, operation: operationView(this.operation), error: 'another Foggy operation is running' }
|
|
371
|
+
}
|
|
372
|
+
const id = `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`
|
|
373
|
+
const args = explicitArgs ?? ['install']
|
|
374
|
+
const reportsProgress = args[0] === 'install'
|
|
375
|
+
if (reportsProgress) {
|
|
376
|
+
const roots = defaultRoots()
|
|
377
|
+
args.push('--progress-file', join(roots.dataRoot, 'operation-progress.json'), '--operation-id', id, '--operation-kind', kind)
|
|
378
|
+
}
|
|
379
|
+
if (replaceSkill) args.push('--replace-skill')
|
|
380
|
+
for (const cacheDir of (process.env.FOGGY_ASSET_CACHE_DIRS || '').split(delimiter).filter(Boolean)) {
|
|
381
|
+
args.push('--asset-cache-dir', cacheDir)
|
|
382
|
+
}
|
|
383
|
+
const operation = {
|
|
384
|
+
id,
|
|
385
|
+
kind,
|
|
386
|
+
state: 'running',
|
|
387
|
+
startedAt: new Date().toISOString(),
|
|
388
|
+
finishedAt: null,
|
|
389
|
+
result: null,
|
|
390
|
+
error: null,
|
|
391
|
+
progress: reportsProgress ? {
|
|
392
|
+
schemaVersion: 'foggy-deepseek-onboarding-progress/v1',
|
|
393
|
+
operationId: id,
|
|
394
|
+
kind,
|
|
395
|
+
state: 'running',
|
|
396
|
+
phase: 'preflight',
|
|
397
|
+
message: 'Starting Foggy initialization',
|
|
398
|
+
currentFile: null,
|
|
399
|
+
percent: 0,
|
|
400
|
+
step: { index: 1, total: 7 },
|
|
401
|
+
} : null,
|
|
402
|
+
promise: null,
|
|
403
|
+
}
|
|
404
|
+
const pythonProgress = reportsProgress
|
|
405
|
+
? createPythonProgressReporter(operation, join(defaultRoots().dataRoot, 'operation-progress.json'))
|
|
406
|
+
: null
|
|
407
|
+
operation.promise = (async () => {
|
|
408
|
+
await assertSystemPrerequisites(kind)
|
|
409
|
+
return runOnboarding(args, 15 * 60_000, {
|
|
410
|
+
ensurePython: true,
|
|
411
|
+
forcePython: kind === 'repair-python',
|
|
412
|
+
onPythonProgress: (detail) => pythonProgress?.update(detail),
|
|
413
|
+
flushPythonProgress: () => pythonProgress?.flush(),
|
|
414
|
+
})
|
|
415
|
+
})()
|
|
416
|
+
.then((result) => {
|
|
417
|
+
operation.state = result.success === false ? 'failed' : 'succeeded'
|
|
418
|
+
operation.result = result
|
|
419
|
+
operation.finishedAt = new Date().toISOString()
|
|
420
|
+
this.skillProviderControl?.invalidate()
|
|
421
|
+
if (operation.progress) {
|
|
422
|
+
operation.progress = {
|
|
423
|
+
...operation.progress,
|
|
424
|
+
state: operation.state,
|
|
425
|
+
phase: operation.state === 'succeeded' ? 'complete' : operation.progress.phase,
|
|
426
|
+
message: operation.state === 'succeeded' ? 'Foggy initialization completed' : 'Foggy initialization failed',
|
|
427
|
+
percent: operation.state === 'succeeded' ? 100 : operation.progress.percent,
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
.catch((error) => {
|
|
432
|
+
operation.state = 'failed'
|
|
433
|
+
operation.error = String(error.message ?? error)
|
|
434
|
+
operation.finishedAt = new Date().toISOString()
|
|
435
|
+
this.skillProviderControl?.invalidate()
|
|
436
|
+
if (operation.progress) {
|
|
437
|
+
operation.progress = {
|
|
438
|
+
...operation.progress,
|
|
439
|
+
state: 'failed',
|
|
440
|
+
message: 'Foggy initialization failed',
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
})
|
|
444
|
+
this.operation = operation
|
|
445
|
+
return { success: true, accepted: true, operation: operationView(operation) }
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const markerInitializers = []
|
|
450
|
+
for (const method of [
|
|
451
|
+
'status', 'plan', 'initialize', 'repair', 'repairPython', 'repairCli', 'repairLauncher', 'repairAnalysisSkill',
|
|
452
|
+
'migrateProfiles', 'diagnostics', 'runtimeStart', 'runtimeStop',
|
|
453
|
+
]) {
|
|
454
|
+
Remote(method)(FoggyIntegrationGateway.prototype[method], {
|
|
455
|
+
kind: 'method',
|
|
456
|
+
name: method,
|
|
457
|
+
static: false,
|
|
458
|
+
private: false,
|
|
459
|
+
access: {
|
|
460
|
+
has: (object) => method in object,
|
|
461
|
+
get: (object) => object[method],
|
|
462
|
+
},
|
|
463
|
+
addInitializer: (initializer) => markerInitializers.push(initializer),
|
|
464
|
+
})
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export default FoggyIntegrationGateway
|