@foggy-projects/deepseek-harness-plugin 0.4.0-beta.11 → 0.4.0-beta.13

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/README.md CHANGED
@@ -14,7 +14,7 @@ modify `PATH`, or register Python globally. Advanced users may explicitly set
14
14
  ## Local beta installation
15
15
 
16
16
  ```powershell
17
- dsh plugin --profile web add --workspace-root ./foggy-projects-deepseek-harness-plugin-0.4.0-beta.11.tgz
17
+ dsh plugin --profile web add --workspace-root ./foggy-projects-deepseek-harness-plugin-0.4.0-beta.13.tgz
18
18
  ```
19
19
 
20
20
  Restart `dsh web`, use the browser it opens (or the complete printed URL,
@@ -60,9 +60,12 @@ and is restored by reinstalling or upgrading the plugin package.
60
60
 
61
61
  The Foggy settings tab shows the persisted database/semantic onboarding stages,
62
62
  offers pinned checks and repair for CLI, Launcher, and the managed analysis
63
- Skill, private Python, and exports a private redacted diagnostics report. Runtime start is
64
- idempotent: an already-recorded process is verified with `wait-ready` and
65
- `capabilities` instead of being treated as a failed second start.
63
+ Skill, private Python, and exports a private redacted diagnostics report. The report
64
+ includes bounded, sanitized tails of Runtime logs while refusing to read paths outside
65
+ the managed Runtime directory. Runtime start monitors the Launcher PID during
66
+ `wait-ready`, fails promptly if Java exits, and remains idempotent: an already-recorded
67
+ process is verified with `wait-ready` and `capabilities` instead of being treated as a
68
+ failed second start.
66
69
 
67
70
  This beta remains a local dev/test integration. The bundled Runtime reports
68
71
  `securityMode=none-dev-test-only` and must not be exposed to a network or used as
@@ -1,10 +1,10 @@
1
- # Public beta readiness: 0.4.0-beta.11
1
+ # Public beta readiness: 0.4.0-beta.13
2
2
 
3
3
  Assessment date: 2026-09-04
4
4
 
5
5
  ## Verdict
6
6
 
7
- `0.4.0-beta.11` is suitable for a scoped public beta on Windows 10/11 x64 and
7
+ `0.4.0-beta.13` is suitable for a scoped public beta on Windows 10/11 x64 and
8
8
  Linux/WSL2 x64. It is not a general-availability release.
9
9
 
10
10
  The plugin package stays small and downloads large components only during
@@ -21,16 +21,22 @@ system PATH, registry, or a user's existing Python installation.
21
21
  - The component installer code is unchanged from beta.9, whose Windows x64 run
22
22
  completed a cold Python download, checksum verification, extraction,
23
23
  initialization, forced Python repair, and doctor run.
24
- - DeepSeek Harness `0.1.2-rc.1` loaded the beta.11 candidate Bundle, web client,
24
+ - DeepSeek Harness `0.1.2-rc.1` loaded the beta.12 candidate Bundle, web client,
25
25
  remote status service, and native Skill provider on both Windows and WSL2.
26
26
  - WSL2 reported the existing managed Python, CLI, Launcher, and analysis Skill
27
27
  as ready; an isolated Windows profile correctly reported its fresh component
28
28
  state and rejected the host's Java 12 as below the Java 17 prerequisite.
29
29
  - The UI reported Python 3.12.13 as `Foggy private`, and CLI 0.1.23, Launcher
30
- 0.1.18, analysis Skill 0.1.17, and onboarding Skill 0.4.0-beta.11 as installed.
30
+ 0.1.18, analysis Skill 0.1.17, and onboarding Skill 0.4.0-beta.13 as installed.
31
31
  - Runtime startup now reports the current phase, elapsed/timeout seconds, and a
32
- bounded progress value. A real WSL2 launch reached readiness in 13.4 seconds;
33
- a forced timeout recorded failure evidence and cleaned up the Java process.
32
+ bounded progress value. A real Windows launch with Temurin 17 reached readiness
33
+ and passed capabilities in 18.9 seconds. A controlled post-launch Java exit was
34
+ detected after about 1 second instead of waiting for the 180-second deadline;
35
+ a restored healthy launch then passed again in 15.9 seconds.
36
+ - Diagnostic export success remains visible alongside a prior Runtime startup
37
+ error, including the generated local JSON path. Exported failures include
38
+ bounded sanitized Runtime log tails and reject log paths outside the managed
39
+ Runtime directory.
34
40
  - Interrupted downloads resume with HTTP Range when the server supports it;
35
41
  downloaded artifacts are not trusted until the complete SHA256 matches.
36
42
  - Windows progress writes tolerate transient sharing violations without losing
@@ -1,7 +1,7 @@
1
1
  # Native Windows Beta acceptance
2
2
 
3
3
  This runbook validates DeepSeek Harness `0.1.2-rc.1` with Foggy plugin
4
- `0.4.0-beta.11` on a clean 64-bit Windows 10 or Windows 11 machine. Use a local
4
+ `0.4.0-beta.13` on a clean 64-bit Windows 10 or Windows 11 machine. Use a local
5
5
  directory that is not synchronized by OneDrive.
6
6
 
7
7
  ## Prerequisites
@@ -49,15 +49,17 @@ Expected component state:
49
49
  - CLI 0.1.23;
50
50
  - Launcher 0.1.18;
51
51
  - analysis Skill 0.1.17;
52
- - onboarding Skill 0.4.0-beta.11;
52
+ - onboarding Skill 0.4.0-beta.13;
53
53
  - Java 17+ available;
54
54
  - native DSH Skill registration available.
55
55
 
56
56
  Runtime startup shows its current phase, elapsed time, and the 180-second
57
57
  readiness deadline. Typical startup is 15–30 seconds on the validated WSL2 host
58
58
  and may take up to 60 seconds on Windows with cold JVM or antivirus scanning.
59
- On timeout, export diagnostics before retrying; the report includes the startup
60
- phase, PID state, evidence directory, and Runtime log locations.
59
+ The plugin monitors the Launcher PID and fails promptly when Java exits before
60
+ readiness. On failure, export diagnostics before retrying; the report includes
61
+ the startup phase, PID state, evidence directory, Runtime log locations, and
62
+ bounded sanitized log tails.
61
63
 
62
64
  ## Database and semantic-layer acceptance
63
65
 
@@ -3,7 +3,7 @@ set -euo pipefail
3
3
 
4
4
  DSH_VERSION="0.1.2-rc.1"
5
5
  PNPM_VERSION="11.7.0"
6
- PLUGIN_VERSION="0.4.0-beta.11"
6
+ PLUGIN_VERSION="0.4.0-beta.13"
7
7
  PLUGIN_REF="v${PLUGIN_VERSION}"
8
8
  PLUGIN_REPOSITORY="https://github.com/foggy-projects/foggy-deepseek-harness-plugin.git"
9
9
 
package/lib/client.js CHANGED
@@ -413,7 +413,9 @@ window.__ModuleLoader__.load({
413
413
  ] }),
414
414
  ] }),
415
415
  progress,
416
- busy || operationError || view.message ? jsx('p', { className: 'foggy-message', 'data-error': operationError || view.error ? 'true' : undefined, children: busy ? t('working') : operationError || view.message }) : null,
416
+ busy ? jsx('p', { className: 'foggy-message', children: t('working') }) : null,
417
+ operationError ? jsx('p', { className: 'foggy-message', 'data-error': 'true', children: operationError }) : null,
418
+ !busy && view.message ? jsx('p', { className: 'foggy-message', 'data-error': view.error ? 'true' : undefined, children: view.message }) : null,
417
419
  jsxs('div', { className: 'foggy-grid', children: [
418
420
  componentCard(t('python'), status.components.python, 'tool', t),
419
421
  componentCard(t('java'), status.components.java, 'tool', t),
@@ -0,0 +1,86 @@
1
+ import { open } from 'node:fs/promises'
2
+ import { isAbsolute, join, relative, resolve, sep } from 'node:path'
3
+
4
+ const DEFAULT_MAX_BYTES = 32 * 1024
5
+ const DEFAULT_MAX_LINES = 120
6
+ const ANSI_ESCAPE = /\u001b(?:\[[0-?]*[ -/]*[@-~]|\][^\u0007]*(?:\u0007|\u001b\\))/g
7
+ const BEARER_SECRET = /\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/gi
8
+ const API_KEY_SECRET = /\bsk-[A-Za-z0-9._-]{12,}/g
9
+ const JDBC_URL = /\bjdbc:[^\s"'<>]+/gi
10
+ const URL_CREDENTIALS = /(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/@:]+:[^\s/@]+@/gi
11
+ const SENSITIVE_QUERY_VALUE = /([?&](?:password|passwd|pwd|secret|token|api[_-]?key|auth[_-]?code)=)[^&#\s]*/gi
12
+ const SENSITIVE_ASSIGNMENT = /\b([A-Za-z0-9_.-]*(?:password|passwd|pwd|secret|token|api[_-]?key|authorization|auth[_-]?code)[A-Za-z0-9_.-]*)(\s*[:=]\s*)(?:"[^"]*"|'[^']*'|[^\s,;]+)/gi
13
+
14
+ export function sanitizeDiagnosticText(value) {
15
+ return String(value ?? '')
16
+ .replace(ANSI_ESCAPE, '')
17
+ .replace(BEARER_SECRET, 'Bearer [REDACTED]')
18
+ .replace(API_KEY_SECRET, '[REDACTED_API_KEY]')
19
+ .replace(JDBC_URL, 'jdbc:[REDACTED_CONNECTION_URL]')
20
+ .replace(URL_CREDENTIALS, '$1[REDACTED]@')
21
+ .replace(SENSITIVE_QUERY_VALUE, '$1[REDACTED]')
22
+ .replace(SENSITIVE_ASSIGNMENT, '$1$2[REDACTED]')
23
+ .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, '')
24
+ }
25
+
26
+ function isInside(root, candidate) {
27
+ const value = relative(resolve(root), resolve(candidate))
28
+ return value !== '' && value !== '..' && !value.startsWith(`..${sep}`) && !isAbsolute(value)
29
+ }
30
+
31
+ export async function readDiagnosticLogTail(path, {
32
+ allowedRoot,
33
+ maxBytes = DEFAULT_MAX_BYTES,
34
+ maxLines = DEFAULT_MAX_LINES,
35
+ } = {}) {
36
+ if (!path || !allowedRoot || !isInside(allowedRoot, path)) {
37
+ return { sanitizedTail: '', tailTruncated: false, tailError: 'LOG_PATH_OUTSIDE_RUNTIME_ROOT' }
38
+ }
39
+
40
+ let handle
41
+ try {
42
+ handle = await open(path, 'r')
43
+ const details = await handle.stat()
44
+ const bytesToRead = Math.min(details.size, maxBytes)
45
+ const buffer = Buffer.alloc(bytesToRead)
46
+ if (bytesToRead > 0) await handle.read(buffer, 0, bytesToRead, details.size - bytesToRead)
47
+ let text = buffer.toString('utf8')
48
+ let truncated = details.size > bytesToRead
49
+ if (truncated) {
50
+ const firstLineEnd = text.indexOf('\n')
51
+ if (firstLineEnd >= 0) text = text.slice(firstLineEnd + 1)
52
+ }
53
+ const lines = text.split(/\r?\n/)
54
+ if (lines.length > maxLines) {
55
+ text = lines.slice(-maxLines).join('\n')
56
+ truncated = true
57
+ }
58
+ return {
59
+ sanitizedTail: sanitizeDiagnosticText(text).trim(),
60
+ tailTruncated: truncated,
61
+ tailBytesRead: bytesToRead,
62
+ }
63
+ } catch (error) {
64
+ return {
65
+ sanitizedTail: '',
66
+ tailTruncated: false,
67
+ tailError: error?.code || 'LOG_READ_FAILED',
68
+ }
69
+ } finally {
70
+ await handle?.close()
71
+ }
72
+ }
73
+
74
+ export async function enrichRuntimeStartFailure(failure, dataRoot) {
75
+ if (!failure || typeof failure !== 'object') return failure
76
+ const runtimeRoot = join(dataRoot, 'runtime')
77
+ const logs = Array.isArray(failure.logs)
78
+ ? await Promise.all(failure.logs.map(async (entry) => ({
79
+ ...entry,
80
+ ...(entry?.exists && entry?.path
81
+ ? await readDiagnosticLogTail(entry.path, { allowedRoot: runtimeRoot })
82
+ : {}),
83
+ })))
84
+ : []
85
+ return { ...failure, logs }
86
+ }
package/lib/index.js CHANGED
@@ -9,6 +9,7 @@ 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'
12
13
 
13
14
  const execFileAsync = promisify(execFile)
14
15
  const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)))
@@ -332,6 +333,10 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
332
333
  async diagnostics() {
333
334
  const roots = defaultRoots()
334
335
  const status = await this.status()
336
+ const lastRuntimeStartFailure = await enrichRuntimeStartFailure(
337
+ await readOptionalJson(join(roots.dataRoot, 'last-runtime-start-failure.json')),
338
+ roots.dataRoot,
339
+ )
335
340
  let doctor
336
341
  try {
337
342
  doctor = await runOnboarding(['doctor', '--no-fail'])
@@ -350,7 +355,7 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
350
355
  components: status.components,
351
356
  onboarding: status.onboarding,
352
357
  profileMigration: status.profileMigration,
353
- lastRuntimeStartFailure: await readOptionalJson(join(roots.dataRoot, 'last-runtime-start-failure.json')),
358
+ lastRuntimeStartFailure,
354
359
  doctor,
355
360
  }
356
361
  const directory = join(roots.dataRoot, 'diagnostics')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foggy-projects/deepseek-harness-plugin",
3
- "version": "0.4.0-beta.11",
3
+ "version": "0.4.0-beta.13",
4
4
  "description": "Foggy Java data analysis engine integration for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -57,7 +57,7 @@
57
57
  "@deepseek-ai/dsh-typert-protocol": "^0.1.2-rc.1"
58
58
  },
59
59
  "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",
60
+ "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/skill-provider.js && node --check lib/client.js && node --check lib/typert.js && node --check lib/remote.js",
61
61
  "test": "node --test test/package.test.js && node test/run-onboarding-tests.js"
62
62
  }
63
63
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": "foggy-deepseek-onboarding-versions/v1",
3
- "packageVersion": "0.4.0-beta.11",
3
+ "packageVersion": "0.4.0-beta.13",
4
4
  "validatedAt": "2026-09-04",
5
5
  "components": {
6
6
  "deepseekHarness": {
@@ -277,6 +277,46 @@ def command_result(command: list[str], timeout: int = 30, check: bool = False) -
277
277
  return payload
278
278
 
279
279
 
280
+ def process_running(pid: int) -> bool:
281
+ """Check liveness without spawning a shell or waiting on the target process."""
282
+ if pid <= 0:
283
+ return False
284
+ if os.name == "nt":
285
+ import ctypes
286
+
287
+ synchronize = 0x00100000
288
+ wait_timeout = 0x00000102
289
+ kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
290
+ kernel32.OpenProcess.argtypes = [ctypes.c_uint32, ctypes.c_int, ctypes.c_uint32]
291
+ kernel32.OpenProcess.restype = ctypes.c_void_p
292
+ kernel32.WaitForSingleObject.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
293
+ kernel32.WaitForSingleObject.restype = ctypes.c_uint32
294
+ kernel32.CloseHandle.argtypes = [ctypes.c_void_p]
295
+ handle = kernel32.OpenProcess(synchronize, False, pid)
296
+ if not handle:
297
+ return ctypes.get_last_error() == 5 # Access denied still proves the PID exists.
298
+ try:
299
+ return kernel32.WaitForSingleObject(handle, 0) == wait_timeout
300
+ finally:
301
+ kernel32.CloseHandle(handle)
302
+ proc_stat = Path("/proc") / str(pid) / "stat"
303
+ if proc_stat.is_file():
304
+ try:
305
+ fields = proc_stat.read_text(encoding="utf-8", errors="replace").rsplit(") ", 1)[1]
306
+ return bool(fields) and fields[0] != "Z"
307
+ except (OSError, IndexError):
308
+ pass
309
+ try:
310
+ os.kill(pid, 0)
311
+ except ProcessLookupError:
312
+ return False
313
+ except PermissionError:
314
+ return True
315
+ except OSError:
316
+ return False
317
+ return True
318
+
319
+
280
320
  def command_result_with_progress(
281
321
  command: list[str],
282
322
  timeout: int,
@@ -289,6 +329,8 @@ def command_result_with_progress(
289
329
  start_percent: int,
290
330
  end_percent: int,
291
331
  poll_interval: float = 1.0,
332
+ watched_pid: int | None = None,
333
+ watched_label: str = "Runtime",
292
334
  ) -> dict:
293
335
  started = time.monotonic()
294
336
  try:
@@ -318,6 +360,13 @@ def command_result_with_progress(
318
360
  break
319
361
  except subprocess.TimeoutExpired:
320
362
  elapsed = time.monotonic() - started
363
+ if watched_pid and not process_running(watched_pid):
364
+ process.kill()
365
+ process.communicate()
366
+ raise OnboardingError(
367
+ f"{watched_label} process PID {watched_pid} exited before becoming ready "
368
+ f"after {round(elapsed * 1000)} ms"
369
+ )
321
370
  readiness_fraction = min(elapsed / max(readiness_timeout, 1), 1.0)
322
371
  percent = round(start_percent + (end_percent - start_percent) * readiness_fraction)
323
372
  progress.update(
@@ -1463,6 +1512,7 @@ def runtime_start_command(args: argparse.Namespace) -> dict:
1463
1512
  readiness_timeout=readiness_timeout,
1464
1513
  start_percent=30,
1465
1514
  end_percent=85,
1515
+ watched_pid=int(prior["pid"]),
1466
1516
  )
1467
1517
  wait_payload = parse_json_output(wait_result, "wait-ready")
1468
1518
  if wait_payload.get("success") is not True:
@@ -1577,6 +1627,7 @@ def runtime_start_command(args: argparse.Namespace) -> dict:
1577
1627
  readiness_timeout=readiness_timeout,
1578
1628
  start_percent=30,
1579
1629
  end_percent=85,
1630
+ watched_pid=pid,
1580
1631
  )
1581
1632
  wait_payload = parse_json_output(wait_result, "wait-ready")
1582
1633
  if wait_payload.get("success") is not True: