@foggy-projects/deepseek-harness-plugin 0.4.0-beta.9 → 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
@@ -9,6 +9,12 @@ import { createFoggySkillProvider } from './skill-provider.js'
9
9
  import { writeJsonAtomic } from './atomic-json.js'
10
10
  import { ensurePythonRuntime, probePythonRuntime } from './python-runtime.js'
11
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'
12
18
 
13
19
  const execFileAsync = promisify(execFile)
14
20
  const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)))
@@ -131,6 +137,8 @@ async function assertSystemPrerequisites(kind) {
131
137
 
132
138
  function createPythonProgressReporter(operation, path) {
133
139
  let pending = Promise.resolve()
140
+ const runtimeStart = operation.kind === 'runtime-start'
141
+ const totalSteps = runtimeStart ? 6 : 7
134
142
  return {
135
143
  update(detail) {
136
144
  const fraction = Math.max(0, Math.min(1, Number(detail.fraction) || 0))
@@ -139,11 +147,11 @@ function createPythonProgressReporter(operation, path) {
139
147
  operationId: operation.id,
140
148
  kind: operation.kind,
141
149
  state: 'running',
142
- phase: 'python',
143
- 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',
144
152
  currentFile: detail.currentFile || null,
145
- percent: Math.round(((1 + fraction) / 7) * 100),
146
- 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 },
147
155
  startedAt: operation.startedAt,
148
156
  updatedAt: new Date().toISOString(),
149
157
  }
@@ -194,13 +202,19 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
194
202
  const statePath = join(roots.installRoot, 'install-state.json')
195
203
  const runtimeStatePath = join(roots.dataRoot, 'runtime-state.json')
196
204
  const progressPath = join(roots.dataRoot, 'operation-progress.json')
205
+ const portConflictPath = join(roots.dataRoot, 'last-runtime-port-conflict.json')
197
206
  const manifest = await readJson(versionsFile)
207
+ const runtimeSettings = await readRuntimeSettings(roots.dataRoot, {
208
+ defaultPort: manifest.defaults?.port ?? DEFAULT_RUNTIME_PORT,
209
+ })
198
210
  const python = await probePythonRuntime({ installRoot: roots.installRoot, manifest })
199
211
  const java = compatible(await commandVersion(process.env.JAVA_EXE || 'java', ['-version']), '17.0')
200
212
  let state = null
201
213
  let runtime = null
214
+ let lastRuntimePortConflict = null
202
215
  try { state = await readJson(statePath) } catch {}
203
216
  try { runtime = await readJson(runtimeStatePath) } catch {}
217
+ try { lastRuntimePortConflict = await readJson(portConflictPath) } catch {}
204
218
  let onboarding = { success: true, profiles: [], profileCount: 0 }
205
219
  let profileMigration = { success: true, entries: [], pendingCount: 0, conflictCount: 0, profileStore: roots.profileStore }
206
220
  if (state) {
@@ -247,6 +261,7 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
247
261
  && components.analysisSkill.installed
248
262
  && components.onboardingSkill.installed
249
263
  const running = Boolean(runtime && processRunning(Number(runtime.pid)))
264
+ const activePort = running && Number.isInteger(Number(runtime?.port)) ? Number(runtime.port) : null
250
265
  const progress = await readOptionalJson(progressPath)
251
266
  let operation = operationView(this.operation)
252
267
  if (operation.state === 'running' && progress?.operationId === operation.id) {
@@ -270,6 +285,14 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
270
285
  installed,
271
286
  running,
272
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
+ },
273
296
  roots,
274
297
  components,
275
298
  operation,
@@ -330,6 +353,10 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
330
353
  async diagnostics() {
331
354
  const roots = defaultRoots()
332
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
+ )
333
360
  let doctor
334
361
  try {
335
362
  doctor = await runOnboarding(['doctor', '--no-fail'])
@@ -348,6 +375,8 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
348
375
  components: status.components,
349
376
  onboarding: status.onboarding,
350
377
  profileMigration: status.profileMigration,
378
+ runtimeSettings: status.runtimeSettings,
379
+ lastRuntimeStartFailure,
351
380
  doctor,
352
381
  }
353
382
  const directory = join(roots.dataRoot, 'diagnostics')
@@ -358,11 +387,61 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
358
387
  }
359
388
 
360
389
  async runtimeStart() {
361
- 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
+ ])
362
409
  }
363
410
 
364
411
  async runtimeStop() {
365
- 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
+ }
366
445
  }
367
446
 
368
447
  startOperation(kind, replaceSkill, explicitArgs) {
@@ -371,7 +450,7 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
371
450
  }
372
451
  const id = `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`
373
452
  const args = explicitArgs ?? ['install']
374
- const reportsProgress = args[0] === 'install'
453
+ const reportsProgress = args[0] === 'install' || args[0] === 'runtime-start'
375
454
  if (reportsProgress) {
376
455
  const roots = defaultRoots()
377
456
  args.push('--progress-file', join(roots.dataRoot, 'operation-progress.json'), '--operation-id', id, '--operation-kind', kind)
@@ -380,6 +459,7 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
380
459
  for (const cacheDir of (process.env.FOGGY_ASSET_CACHE_DIRS || '').split(delimiter).filter(Boolean)) {
381
460
  args.push('--asset-cache-dir', cacheDir)
382
461
  }
462
+ const runtimeStart = kind === 'runtime-start'
383
463
  const operation = {
384
464
  id,
385
465
  kind,
@@ -394,10 +474,10 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
394
474
  kind,
395
475
  state: 'running',
396
476
  phase: 'preflight',
397
- message: 'Starting Foggy initialization',
477
+ message: runtimeStart ? 'Starting Foggy Runtime' : 'Starting Foggy initialization',
398
478
  currentFile: null,
399
479
  percent: 0,
400
- step: { index: 1, total: 7 },
480
+ step: { index: 1, total: runtimeStart ? 6 : 7 },
401
481
  } : null,
402
482
  promise: null,
403
483
  }
@@ -422,8 +502,10 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
422
502
  operation.progress = {
423
503
  ...operation.progress,
424
504
  state: operation.state,
425
- phase: operation.state === 'succeeded' ? 'complete' : operation.progress.phase,
426
- 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'),
427
509
  percent: operation.state === 'succeeded' ? 100 : operation.progress.percent,
428
510
  }
429
511
  }
@@ -437,7 +519,7 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
437
519
  operation.progress = {
438
520
  ...operation.progress,
439
521
  state: 'failed',
440
- message: 'Foggy initialization failed',
522
+ message: runtimeStart ? 'Foggy Runtime startup failed' : 'Foggy initialization failed',
441
523
  }
442
524
  }
443
525
  })
@@ -450,6 +532,7 @@ const markerInitializers = []
450
532
  for (const method of [
451
533
  'status', 'plan', 'initialize', 'repair', 'repairPython', 'repairCli', 'repairLauncher', 'repairAnalysisSkill',
452
534
  'migrateProfiles', 'diagnostics', 'runtimeStart', 'runtimeStop',
535
+ 'saveRuntimeSettings',
453
536
  ]) {
454
537
  Remote(method)(FoggyIntegrationGateway.prototype[method], {
455
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.9",
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/atomic-json.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
  }