@foggy-projects/deepseek-harness-plugin 0.4.0-beta.8 → 0.4.0-rc.1

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/lib/index.js CHANGED
@@ -1,13 +1,20 @@
1
1
  import { execFile } from 'node:child_process'
2
- import { access, mkdir, readFile, rename, writeFile } from 'node:fs/promises'
2
+ import { access, mkdir, readFile, writeFile } from 'node:fs/promises'
3
3
  import { constants as fsConstants } from 'node:fs'
4
4
  import { dirname, join, delimiter } from 'node:path'
5
5
  import { fileURLToPath } from 'node:url'
6
6
  import { promisify } from 'node:util'
7
7
  import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'
8
8
  import { createFoggySkillProvider } from './skill-provider.js'
9
+ import { writeJsonAtomic } from './atomic-json.js'
9
10
  import { ensurePythonRuntime, probePythonRuntime } from './python-runtime.js'
10
11
  import { compatible, compatibleNode } from './version.js'
12
+ import { enrichRuntimeStartFailure } from './diagnostics.js'
13
+ import {
14
+ DEFAULT_RUNTIME_PORT,
15
+ readRuntimeSettings,
16
+ writeRuntimeSettings,
17
+ } from './runtime-settings.js'
11
18
 
12
19
  const execFileAsync = promisify(execFile)
13
20
  const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)))
@@ -128,15 +135,10 @@ async function assertSystemPrerequisites(kind) {
128
135
  }
129
136
  }
130
137
 
131
- async function writeJsonAtomic(path, payload) {
132
- await mkdir(dirname(path), { recursive: true })
133
- const temporary = `${path}.${process.pid}.${Date.now()}.tmp`
134
- await writeFile(temporary, `${JSON.stringify(payload, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 })
135
- await rename(temporary, path)
136
- }
137
-
138
138
  function createPythonProgressReporter(operation, path) {
139
139
  let pending = Promise.resolve()
140
+ const runtimeStart = operation.kind === 'runtime-start'
141
+ const totalSteps = runtimeStart ? 6 : 7
140
142
  return {
141
143
  update(detail) {
142
144
  const fraction = Math.max(0, Math.min(1, Number(detail.fraction) || 0))
@@ -145,11 +147,11 @@ function createPythonProgressReporter(operation, path) {
145
147
  operationId: operation.id,
146
148
  kind: operation.kind,
147
149
  state: 'running',
148
- phase: 'python',
149
- message: detail.message || 'Preparing managed Python',
150
+ phase: runtimeStart ? 'runtime-preflight' : 'python',
151
+ message: runtimeStart ? 'Preparing the Foggy Runtime controller' : detail.message || 'Preparing managed Python',
150
152
  currentFile: detail.currentFile || null,
151
- percent: Math.round(((1 + fraction) / 7) * 100),
152
- step: { index: 2, total: 7 },
153
+ percent: runtimeStart ? Math.round(fraction * 3) : Math.round(((1 + fraction) / totalSteps) * 100),
154
+ step: { index: runtimeStart ? 1 : 2, total: totalSteps },
153
155
  startedAt: operation.startedAt,
154
156
  updatedAt: new Date().toISOString(),
155
157
  }
@@ -200,13 +202,19 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
200
202
  const statePath = join(roots.installRoot, 'install-state.json')
201
203
  const runtimeStatePath = join(roots.dataRoot, 'runtime-state.json')
202
204
  const progressPath = join(roots.dataRoot, 'operation-progress.json')
205
+ const portConflictPath = join(roots.dataRoot, 'last-runtime-port-conflict.json')
203
206
  const manifest = await readJson(versionsFile)
207
+ const runtimeSettings = await readRuntimeSettings(roots.dataRoot, {
208
+ defaultPort: manifest.defaults?.port ?? DEFAULT_RUNTIME_PORT,
209
+ })
204
210
  const python = await probePythonRuntime({ installRoot: roots.installRoot, manifest })
205
211
  const java = compatible(await commandVersion(process.env.JAVA_EXE || 'java', ['-version']), '17.0')
206
212
  let state = null
207
213
  let runtime = null
214
+ let lastRuntimePortConflict = null
208
215
  try { state = await readJson(statePath) } catch {}
209
216
  try { runtime = await readJson(runtimeStatePath) } catch {}
217
+ try { lastRuntimePortConflict = await readJson(portConflictPath) } catch {}
210
218
  let onboarding = { success: true, profiles: [], profileCount: 0 }
211
219
  let profileMigration = { success: true, entries: [], pendingCount: 0, conflictCount: 0, profileStore: roots.profileStore }
212
220
  if (state) {
@@ -253,6 +261,7 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
253
261
  && components.analysisSkill.installed
254
262
  && components.onboardingSkill.installed
255
263
  const running = Boolean(runtime && processRunning(Number(runtime.pid)))
264
+ const activePort = running && Number.isInteger(Number(runtime?.port)) ? Number(runtime.port) : null
256
265
  const progress = await readOptionalJson(progressPath)
257
266
  let operation = operationView(this.operation)
258
267
  if (operation.state === 'running' && progress?.operationId === operation.id) {
@@ -276,6 +285,14 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
276
285
  installed,
277
286
  running,
278
287
  runtimeUrl: running ? runtime.runtimeUrl ?? null : null,
288
+ runtimeSettings: {
289
+ ...runtimeSettings,
290
+ activePort,
291
+ activeRuntimeUrl: running ? runtime.runtimeUrl ?? null : null,
292
+ pendingRestart: Boolean(running && activePort && activePort !== runtimeSettings.port),
293
+ lastConflict: lastRuntimePortConflict,
294
+ conflictApplies: Number(lastRuntimePortConflict?.port) === runtimeSettings.port,
295
+ },
279
296
  roots,
280
297
  components,
281
298
  operation,
@@ -336,6 +353,10 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
336
353
  async diagnostics() {
337
354
  const roots = defaultRoots()
338
355
  const status = await this.status()
356
+ const lastRuntimeStartFailure = await enrichRuntimeStartFailure(
357
+ await readOptionalJson(join(roots.dataRoot, 'last-runtime-start-failure.json')),
358
+ roots.dataRoot,
359
+ )
339
360
  let doctor
340
361
  try {
341
362
  doctor = await runOnboarding(['doctor', '--no-fail'])
@@ -354,6 +375,8 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
354
375
  components: status.components,
355
376
  onboarding: status.onboarding,
356
377
  profileMigration: status.profileMigration,
378
+ runtimeSettings: status.runtimeSettings,
379
+ lastRuntimeStartFailure,
357
380
  doctor,
358
381
  }
359
382
  const directory = join(roots.dataRoot, 'diagnostics')
@@ -364,11 +387,61 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
364
387
  }
365
388
 
366
389
  async runtimeStart() {
367
- return this.startOperation('runtime-start', false, ['runtime-start'])
390
+ const roots = defaultRoots()
391
+ const manifest = await readJson(versionsFile)
392
+ const settings = await readRuntimeSettings(roots.dataRoot, {
393
+ defaultPort: manifest.defaults?.port ?? DEFAULT_RUNTIME_PORT,
394
+ })
395
+ if (!settings.valid) {
396
+ return {
397
+ success: false,
398
+ accepted: false,
399
+ error: {
400
+ code: 'RUNTIME_SETTINGS_INVALID',
401
+ message: `Runtime settings are invalid: ${settings.error}. Save a valid port in Foggy plugin settings.`,
402
+ },
403
+ }
404
+ }
405
+ return this.startOperation('runtime-start', false, [
406
+ 'runtime-start', '--install-root', roots.installRoot, '--data-root', roots.dataRoot,
407
+ '--port', String(settings.port),
408
+ ])
368
409
  }
369
410
 
370
411
  async runtimeStop() {
371
- return this.startOperation('runtime-stop', false, ['runtime-stop'])
412
+ const roots = defaultRoots()
413
+ return this.startOperation('runtime-stop', false, [
414
+ 'runtime-stop', '--install-root', roots.installRoot, '--data-root', roots.dataRoot,
415
+ ])
416
+ }
417
+
418
+ async saveRuntimeSettings(input) {
419
+ const roots = defaultRoots()
420
+ if (this.operation?.state === 'running') {
421
+ return {
422
+ success: false,
423
+ error: { code: 'OPERATION_RUNNING', message: 'Wait for the current Foggy operation to finish before changing the Runtime port.' },
424
+ }
425
+ }
426
+ const runtime = await readOptionalJson(join(roots.dataRoot, 'runtime-state.json'))
427
+ if (runtime && processRunning(Number(runtime.pid))) {
428
+ return {
429
+ success: false,
430
+ error: { code: 'RUNTIME_RUNNING', message: 'Stop Foggy Runtime before changing its port.' },
431
+ }
432
+ }
433
+ try {
434
+ const settings = await writeRuntimeSettings(roots.dataRoot, input)
435
+ if (this.operation?.state === 'failed' && this.operation.result?.error?.code === 'RUNTIME_PORT_UNAVAILABLE') {
436
+ this.operation = null
437
+ }
438
+ return { success: true, settings }
439
+ } catch (error) {
440
+ return {
441
+ success: false,
442
+ error: { code: 'RUNTIME_PORT_INVALID', message: String(error?.message ?? error) },
443
+ }
444
+ }
372
445
  }
373
446
 
374
447
  startOperation(kind, replaceSkill, explicitArgs) {
@@ -377,7 +450,7 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
377
450
  }
378
451
  const id = `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`
379
452
  const args = explicitArgs ?? ['install']
380
- const reportsProgress = args[0] === 'install'
453
+ const reportsProgress = args[0] === 'install' || args[0] === 'runtime-start'
381
454
  if (reportsProgress) {
382
455
  const roots = defaultRoots()
383
456
  args.push('--progress-file', join(roots.dataRoot, 'operation-progress.json'), '--operation-id', id, '--operation-kind', kind)
@@ -386,6 +459,7 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
386
459
  for (const cacheDir of (process.env.FOGGY_ASSET_CACHE_DIRS || '').split(delimiter).filter(Boolean)) {
387
460
  args.push('--asset-cache-dir', cacheDir)
388
461
  }
462
+ const runtimeStart = kind === 'runtime-start'
389
463
  const operation = {
390
464
  id,
391
465
  kind,
@@ -400,10 +474,10 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
400
474
  kind,
401
475
  state: 'running',
402
476
  phase: 'preflight',
403
- message: 'Starting Foggy initialization',
477
+ message: runtimeStart ? 'Starting Foggy Runtime' : 'Starting Foggy initialization',
404
478
  currentFile: null,
405
479
  percent: 0,
406
- step: { index: 1, total: 7 },
480
+ step: { index: 1, total: runtimeStart ? 6 : 7 },
407
481
  } : null,
408
482
  promise: null,
409
483
  }
@@ -428,8 +502,10 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
428
502
  operation.progress = {
429
503
  ...operation.progress,
430
504
  state: operation.state,
431
- phase: operation.state === 'succeeded' ? 'complete' : operation.progress.phase,
432
- message: operation.state === 'succeeded' ? 'Foggy initialization completed' : 'Foggy initialization failed',
505
+ phase: operation.state === 'succeeded' ? (runtimeStart ? 'runtime-complete' : 'complete') : operation.progress.phase,
506
+ message: operation.state === 'succeeded'
507
+ ? (runtimeStart ? 'Foggy Runtime is ready' : 'Foggy initialization completed')
508
+ : (runtimeStart ? 'Foggy Runtime startup failed' : 'Foggy initialization failed'),
433
509
  percent: operation.state === 'succeeded' ? 100 : operation.progress.percent,
434
510
  }
435
511
  }
@@ -443,7 +519,7 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
443
519
  operation.progress = {
444
520
  ...operation.progress,
445
521
  state: 'failed',
446
- message: 'Foggy initialization failed',
522
+ message: runtimeStart ? 'Foggy Runtime startup failed' : 'Foggy initialization failed',
447
523
  }
448
524
  }
449
525
  })
@@ -456,6 +532,7 @@ const markerInitializers = []
456
532
  for (const method of [
457
533
  'status', 'plan', 'initialize', 'repair', 'repairPython', 'repairCli', 'repairLauncher', 'repairAnalysisSkill',
458
534
  'migrateProfiles', 'diagnostics', 'runtimeStart', 'runtimeStop',
535
+ 'saveRuntimeSettings',
459
536
  ]) {
460
537
  Remote(method)(FoggyIntegrationGateway.prototype[method], {
461
538
  kind: 'method',
@@ -1,15 +1,18 @@
1
1
  import { z } from 'zod'
2
2
 
3
3
  const resultSchema = z.unknown()
4
+ const runtimeSettingsInputSchema = z.object({
5
+ port: z.number().int().min(1024).max(65535),
6
+ }).strict()
4
7
 
5
- function descriptor(method) {
8
+ function descriptor(method, parameters = []) {
6
9
  return {
7
10
  id: `@foggy-projects/deepseek-harness-plugin#foggyIntegration/${method}`,
8
11
  service: 'foggyIntegration',
9
12
  namespace: 'foggyIntegration',
10
13
  method,
11
14
  invocation: { kind: 'direct' },
12
- parameters: [],
15
+ parameters,
13
16
  result: {
14
17
  mode: 'strict',
15
18
  typeSymbol: '@foggy-projects/deepseek-harness-plugin#FoggyIntegrationResult',
@@ -27,4 +30,14 @@ export const descriptors = [
27
30
  descriptor('repairPython'),
28
31
  descriptor('runtimeStart'),
29
32
  descriptor('runtimeStop'),
33
+ descriptor('saveRuntimeSettings', [{
34
+ name: 'input',
35
+ wire: 'input',
36
+ source: 'json',
37
+ codec: {
38
+ mode: 'strict',
39
+ typeSymbol: '@foggy-projects/deepseek-harness-plugin#FoggyRuntimeSettingsInput',
40
+ schema: runtimeSettingsInputSchema,
41
+ },
42
+ }]),
30
43
  ]
@@ -0,0 +1,89 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import { join } from 'node:path'
3
+ import { writeJsonAtomic } from './atomic-json.js'
4
+
5
+ export const RUNTIME_SETTINGS_SCHEMA = 'foggy-deepseek-runtime-settings/v1'
6
+ export const DEFAULT_RUNTIME_PORT = 18166
7
+ export const MIN_RUNTIME_PORT = 1024
8
+ export const MAX_RUNTIME_PORT = 65535
9
+
10
+ export function normalizeRuntimePort(value) {
11
+ const port = typeof value === 'string' && value.trim() !== '' ? Number(value) : value
12
+ if (!Number.isInteger(port) || port < MIN_RUNTIME_PORT || port > MAX_RUNTIME_PORT) {
13
+ throw new Error(`Runtime port must be an integer between ${MIN_RUNTIME_PORT} and ${MAX_RUNTIME_PORT}`)
14
+ }
15
+ return port
16
+ }
17
+
18
+ export function runtimeUrlForPort(port) {
19
+ return `http://127.0.0.1:${normalizeRuntimePort(port)}`
20
+ }
21
+
22
+ export function runtimeSettingsPath(dataRoot) {
23
+ return join(dataRoot, 'runtime-settings.json')
24
+ }
25
+
26
+ export async function readRuntimeSettings(dataRoot, { defaultPort = DEFAULT_RUNTIME_PORT } = {}) {
27
+ const path = runtimeSettingsPath(dataRoot)
28
+ const fallbackPort = normalizeRuntimePort(defaultPort)
29
+ try {
30
+ const payload = JSON.parse(await readFile(path, 'utf8'))
31
+ if (payload?.schemaVersion !== RUNTIME_SETTINGS_SCHEMA) {
32
+ throw new Error(`expected schemaVersion ${RUNTIME_SETTINGS_SCHEMA}`)
33
+ }
34
+ const port = normalizeRuntimePort(payload.runtimePort)
35
+ return {
36
+ valid: true,
37
+ source: 'configured',
38
+ schemaVersion: RUNTIME_SETTINGS_SCHEMA,
39
+ port,
40
+ runtimeUrl: runtimeUrlForPort(port),
41
+ updatedAt: payload.updatedAt ?? null,
42
+ path,
43
+ error: null,
44
+ }
45
+ } catch (error) {
46
+ if (error?.code === 'ENOENT') {
47
+ return {
48
+ valid: true,
49
+ source: 'default',
50
+ schemaVersion: RUNTIME_SETTINGS_SCHEMA,
51
+ port: fallbackPort,
52
+ runtimeUrl: runtimeUrlForPort(fallbackPort),
53
+ updatedAt: null,
54
+ path,
55
+ error: null,
56
+ }
57
+ }
58
+ return {
59
+ valid: false,
60
+ source: 'invalid',
61
+ schemaVersion: RUNTIME_SETTINGS_SCHEMA,
62
+ port: fallbackPort,
63
+ runtimeUrl: runtimeUrlForPort(fallbackPort),
64
+ updatedAt: null,
65
+ path,
66
+ error: String(error?.message ?? error),
67
+ }
68
+ }
69
+ }
70
+
71
+ export async function writeRuntimeSettings(dataRoot, input) {
72
+ const runtimePort = normalizeRuntimePort(input?.port)
73
+ const payload = {
74
+ schemaVersion: RUNTIME_SETTINGS_SCHEMA,
75
+ runtimePort,
76
+ updatedAt: new Date().toISOString(),
77
+ }
78
+ const path = runtimeSettingsPath(dataRoot)
79
+ await writeJsonAtomic(path, payload)
80
+ return {
81
+ valid: true,
82
+ source: 'configured',
83
+ ...payload,
84
+ port: runtimePort,
85
+ runtimeUrl: runtimeUrlForPort(runtimePort),
86
+ path,
87
+ error: null,
88
+ }
89
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foggy-projects/deepseek-harness-plugin",
3
- "version": "0.4.0-beta.8",
3
+ "version": "0.4.0-rc.1",
4
4
  "description": "Foggy Java data analysis engine integration for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -20,6 +20,7 @@
20
20
  "cordis.patch.yml",
21
21
  "experience/linux/**",
22
22
  "docs/PUBLIC-BETA-READINESS.md",
23
+ "docs/RELEASE-CANDIDATE-0.4.0-RC.1.md",
23
24
  "docs/WINDOWS-BETA-ACCEPTANCE.md",
24
25
  "THIRD-PARTY-RUNTIME-NOTICES.md",
25
26
  "README.md"
@@ -48,16 +49,16 @@
48
49
  },
49
50
  "peerDependencies": {
50
51
  "@deepseek-ai/cordis": "^4.0.1",
51
- "@deepseek-ai/dsh-api-remotes": "^0.1.1-rc.2",
52
- "@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
53
- "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
54
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
55
- "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.1-rc.2",
56
- "@deepseek-ai/dsh-skill": "^0.1.1-rc.2",
57
- "@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2"
52
+ "@deepseek-ai/dsh-api-remotes": "^0.1.2-rc.1",
53
+ "@deepseek-ai/dsh-client-locale": "^0.1.2-rc.1",
54
+ "@deepseek-ai/dsh-client-runtime": "^0.1.2-rc.1",
55
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.2-rc.1",
56
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.2-rc.1",
57
+ "@deepseek-ai/dsh-skill": "^0.1.2-rc.1",
58
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.2-rc.1"
58
59
  },
59
60
  "scripts": {
60
- "check": "node --check lib/index.js && node --check lib/python-runtime.js && node --check lib/skill-provider.js && node --check lib/client.js && node --check lib/typert.js && node --check lib/remote.js",
61
- "test": "node --test test/package.test.js && python test/onboarding_unit.py"
61
+ "check": "node --check lib/index.js && node --check lib/atomic-json.js && node --check lib/diagnostics.js && node --check lib/python-runtime.js && node --check lib/runtime-settings.js && node --check lib/skill-provider.js && node --check lib/client.js && node --check lib/typert.js && node --check lib/remote.js",
62
+ "test": "node --test test/package.test.js && node test/run-onboarding-tests.js"
62
63
  }
63
64
  }