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

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.8.tgz
17
+ dsh plugin --profile web add --workspace-root ./foggy-projects-deepseek-harness-plugin-0.4.0-beta.9.tgz
18
18
  ```
19
19
 
20
20
  Restart `dsh web`, open Settings → Plugins → Foggy Data Analysis, and initialize
@@ -1,10 +1,10 @@
1
- # Public beta readiness: 0.4.0-beta.8
1
+ # Public beta readiness: 0.4.0-beta.9
2
2
 
3
- Assessment date: 2026-09-03
3
+ Assessment date: 2026-09-04
4
4
 
5
5
  ## Verdict
6
6
 
7
- `0.4.0-beta.8` is suitable for a scoped public beta on Windows 10/11 x64 and
7
+ `0.4.0-beta.9` 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
@@ -23,10 +23,12 @@ system PATH, registry, or a user's existing Python installation.
23
23
  - Linux/WSL2 x64 completed managed Python extraction, initialization, and a live
24
24
  DeepSeek Harness `0.1.1-rc.2` UI status check.
25
25
  - The UI reported Python 3.12.13 as `Foggy private`, and CLI 0.1.23, Launcher
26
- 0.1.18, analysis Skill 0.1.17, and onboarding Skill 0.4.0-beta.8 as installed.
26
+ 0.1.18, analysis Skill 0.1.17, and onboarding Skill 0.4.0-beta.9 as installed.
27
27
  - Interrupted downloads resume with HTTP Range when the server supports it;
28
28
  downloaded artifacts are not trusted until the complete SHA256 matches.
29
- - Automated regression passed under Node 22.23.2: 15 Node tests and 8 Python
29
+ - Windows progress writes tolerate transient sharing violations without losing
30
+ the previous valid progress document or leaving temporary files behind.
31
+ - Automated regression passed under Node 22.23.2: 17 Node tests and 10 Python
30
32
  tests, plus shell and PowerShell syntax checks.
31
33
 
32
34
  ## Beta boundaries
@@ -1,7 +1,7 @@
1
1
  # Native Windows Beta acceptance
2
2
 
3
3
  This runbook validates DeepSeek Harness `0.1.1-rc.2` with Foggy plugin
4
- `0.4.0-beta.8` on a clean 64-bit Windows 10 or Windows 11 machine. Use a local
4
+ `0.4.0-beta.9` 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
@@ -44,7 +44,7 @@ Expected component state:
44
44
  - CLI 0.1.23;
45
45
  - Launcher 0.1.18;
46
46
  - analysis Skill 0.1.17;
47
- - onboarding Skill 0.4.0-beta.8;
47
+ - onboarding Skill 0.4.0-beta.9;
48
48
  - Java 17+ available;
49
49
  - native DSH Skill registration available.
50
50
 
@@ -68,4 +68,3 @@ Pass only when Runtime readiness and capabilities succeed, datasource metadata
68
68
  can be discovered, TM/QM models validate and publish, the bounded read-only
69
69
  query succeeds, diagnostics contain no credentials, and stopping Runtime frees
70
70
  port 18066.
71
-
@@ -3,7 +3,7 @@ set -euo pipefail
3
3
 
4
4
  DSH_VERSION="0.1.1-rc.2"
5
5
  PNPM_VERSION="11.7.0"
6
- PLUGIN_VERSION="0.4.0-beta.8"
6
+ PLUGIN_VERSION="0.4.0-beta.9"
7
7
  PLUGIN_REF="v${PLUGIN_VERSION}"
8
8
  PLUGIN_REPOSITORY="https://github.com/foggy-projects/foggy-deepseek-harness-plugin.git"
9
9
 
@@ -0,0 +1,41 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import { mkdir, rename, rm, writeFile } from 'node:fs/promises'
3
+ import { dirname } from 'node:path'
4
+
5
+ const TRANSIENT_REPLACE_CODES = new Set(['EACCES', 'EBUSY', 'EPERM'])
6
+ const TRANSIENT_WINERRORS = new Set([5, 32, 33])
7
+
8
+ function wait(milliseconds) {
9
+ return new Promise((resolve) => setTimeout(resolve, milliseconds))
10
+ }
11
+
12
+ function transientReplaceError(error) {
13
+ return TRANSIENT_REPLACE_CODES.has(error?.code) || TRANSIENT_WINERRORS.has(error?.winerror)
14
+ }
15
+
16
+ export async function replaceWithRetry(source, destination, {
17
+ replace = rename,
18
+ sleep = wait,
19
+ attempts = 12,
20
+ } = {}) {
21
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
22
+ try {
23
+ await replace(source, destination)
24
+ return
25
+ } catch (error) {
26
+ if (!transientReplaceError(error) || attempt === attempts - 1) throw error
27
+ await sleep(Math.min(25 * (2 ** attempt), 400))
28
+ }
29
+ }
30
+ }
31
+
32
+ export async function writeJsonAtomic(path, payload, options = {}) {
33
+ await mkdir(dirname(path), { recursive: true })
34
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`
35
+ try {
36
+ await writeFile(temporary, `${JSON.stringify(payload, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 })
37
+ await replaceWithRetry(temporary, path, options)
38
+ } finally {
39
+ await rm(temporary, { force: true })
40
+ }
41
+ }
package/lib/index.js CHANGED
@@ -1,11 +1,12 @@
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'
11
12
 
@@ -128,13 +129,6 @@ async function assertSystemPrerequisites(kind) {
128
129
  }
129
130
  }
130
131
 
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
132
  function createPythonProgressReporter(operation, path) {
139
133
  let pending = Promise.resolve()
140
134
  return {
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-beta.9",
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.1-rc.2"
58
58
  },
59
59
  "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",
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
61
  "test": "node --test test/package.test.js && python test/onboarding_unit.py"
62
62
  }
63
63
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": "foggy-deepseek-onboarding-versions/v1",
3
- "packageVersion": "0.4.0-beta.8",
3
+ "packageVersion": "0.4.0-beta.9",
4
4
  "validatedAt": "2026-09-03",
5
5
  "components": {
6
6
  "deepseekHarness": {
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
 
6
6
  import argparse
7
7
  import datetime as dt
8
+ import errno
8
9
  import hashlib
9
10
  import json
10
11
  import os
@@ -337,11 +338,39 @@ def venv_cli(install_root: Path) -> Path:
337
338
  return install_root / "venv" / ("Scripts/foggy-runtime.exe" if os.name == "nt" else "bin/foggy-runtime")
338
339
 
339
340
 
341
+ def replace_with_retry(source: Path, destination: Path, attempts: int = 12) -> None:
342
+ transient_errno = {errno.EACCES, errno.EBUSY, errno.EPERM}
343
+ transient_winerror = {5, 32, 33}
344
+ for attempt in range(attempts):
345
+ try:
346
+ os.replace(source, destination)
347
+ return
348
+ except OSError as exc:
349
+ transient = exc.errno in transient_errno or getattr(exc, "winerror", None) in transient_winerror
350
+ if not transient or attempt == attempts - 1:
351
+ raise
352
+ time.sleep(min(0.025 * (2 ** attempt), 0.4))
353
+
354
+
340
355
  def atomic_json(path: Path, payload: dict) -> None:
341
356
  path.parent.mkdir(parents=True, exist_ok=True)
342
- temporary = path.with_name(path.name + ".tmp")
343
- temporary.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
344
- os.replace(temporary, path)
357
+ handle = tempfile.NamedTemporaryFile(
358
+ mode="w",
359
+ encoding="utf-8",
360
+ dir=path.parent,
361
+ prefix=f".{path.name}.",
362
+ suffix=".tmp",
363
+ delete=False,
364
+ )
365
+ temporary = Path(handle.name)
366
+ try:
367
+ with handle:
368
+ json.dump(payload, handle, indent=2, ensure_ascii=False)
369
+ handle.flush()
370
+ os.fsync(handle.fileno())
371
+ replace_with_retry(temporary, path)
372
+ finally:
373
+ temporary.unlink(missing_ok=True)
345
374
 
346
375
 
347
376
  def skill_tree_digest(root: Path) -> str: