@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 +7 -4
- package/docs/PUBLIC-BETA-READINESS.md +12 -6
- package/docs/WINDOWS-BETA-ACCEPTANCE.md +6 -4
- package/experience/linux/prepare.sh +1 -1
- package/lib/client.js +3 -1
- package/lib/diagnostics.js +86 -0
- package/lib/index.js +6 -1
- package/package.json +2 -2
- package/skills/foggy-deepseek-onboarding/assets/versions.json +1 -1
- package/skills/foggy-deepseek-onboarding/scripts/onboarding.py +51 -0
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.
|
|
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.
|
|
64
|
-
|
|
65
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
33
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
60
|
-
|
|
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
|
|
package/lib/client.js
CHANGED
|
@@ -413,7 +413,9 @@ window.__ModuleLoader__.load({
|
|
|
413
413
|
] }),
|
|
414
414
|
] }),
|
|
415
415
|
progress,
|
|
416
|
-
busy
|
|
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
|
|
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.
|
|
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
|
}
|
|
@@ -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:
|