@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.
Files changed (42) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +81 -0
  3. package/THIRD-PARTY-RUNTIME-NOTICES.md +16 -0
  4. package/cordis.patch.yml +6 -0
  5. package/docs/PUBLIC-BETA-READINESS.md +57 -0
  6. package/docs/WINDOWS-BETA-ACCEPTANCE.md +75 -0
  7. package/experience/linux/README.md +53 -0
  8. package/experience/linux/prepare.sh +188 -0
  9. package/lib/atomic-json.js +41 -0
  10. package/lib/client.js +453 -0
  11. package/lib/index.js +467 -0
  12. package/lib/python-runtime.js +339 -0
  13. package/lib/remote-descriptor.js +30 -0
  14. package/lib/remote.js +8 -0
  15. package/lib/skill-provider.js +111 -0
  16. package/lib/typert.js +9 -0
  17. package/lib/version.js +20 -0
  18. package/package.json +63 -0
  19. package/skills/foggy-deepseek-onboarding/SKILL.md +137 -0
  20. package/skills/foggy-deepseek-onboarding/assets/connection.schema.json +50 -0
  21. package/skills/foggy-deepseek-onboarding/assets/datasource.example.json +13 -0
  22. package/skills/foggy-deepseek-onboarding/assets/env.example +14 -0
  23. package/skills/foggy-deepseek-onboarding/assets/onboarding-state.schema.json +42 -0
  24. package/skills/foggy-deepseek-onboarding/assets/semantic-plan.example.json +8 -0
  25. package/skills/foggy-deepseek-onboarding/assets/semantic-plan.schema.json +21 -0
  26. package/skills/foggy-deepseek-onboarding/assets/versions.json +137 -0
  27. package/skills/foggy-deepseek-onboarding/references/onboarding-workflow.md +156 -0
  28. package/skills/foggy-deepseek-onboarding/scripts/doctor.ps1 +4 -0
  29. package/skills/foggy-deepseek-onboarding/scripts/doctor.sh +4 -0
  30. package/skills/foggy-deepseek-onboarding/scripts/install.ps1 +4 -0
  31. package/skills/foggy-deepseek-onboarding/scripts/install.sh +4 -0
  32. package/skills/foggy-deepseek-onboarding/scripts/invoke-onboarding.ps1 +30 -0
  33. package/skills/foggy-deepseek-onboarding/scripts/invoke-onboarding.sh +24 -0
  34. package/skills/foggy-deepseek-onboarding/scripts/onboard.ps1 +4 -0
  35. package/skills/foggy-deepseek-onboarding/scripts/onboard.sh +4 -0
  36. package/skills/foggy-deepseek-onboarding/scripts/onboarding.py +3027 -0
  37. package/skills/foggy-deepseek-onboarding/scripts/runtime-start.ps1 +4 -0
  38. package/skills/foggy-deepseek-onboarding/scripts/runtime-start.sh +4 -0
  39. package/skills/foggy-deepseek-onboarding/scripts/runtime-stop.ps1 +4 -0
  40. package/skills/foggy-deepseek-onboarding/scripts/runtime-stop.sh +4 -0
  41. package/skills/foggy-deepseek-onboarding/scripts/uninstall.ps1 +4 -0
  42. 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