@foggy-projects/deepseek-harness-plugin 0.4.0-beta.3 → 0.4.0-beta.4
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 +16 -1
- package/experience/linux/prepare.sh +1 -1
- package/lib/client.js +13 -4
- package/lib/index.js +38 -4
- package/package.json +1 -1
- package/skills/foggy-deepseek-onboarding/SKILL.md +21 -3
- package/skills/foggy-deepseek-onboarding/assets/versions.json +1 -1
- package/skills/foggy-deepseek-onboarding/scripts/onboarding.py +247 -26
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ only when the user selects **Initialize Foggy**.
|
|
|
8
8
|
## Local beta installation
|
|
9
9
|
|
|
10
10
|
```powershell
|
|
11
|
-
dsh plugin --profile web add --workspace-root ./foggy-projects-deepseek-harness-plugin-0.4.0-beta.
|
|
11
|
+
dsh plugin --profile web add --workspace-root ./foggy-projects-deepseek-harness-plugin-0.4.0-beta.4.tgz
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Restart `dsh web`, open Settings → Plugins → Foggy Data Analysis, and initialize
|
|
@@ -27,6 +27,21 @@ asset-cache directories. Neither variable is required for a normal install.
|
|
|
27
27
|
Database credentials are deliberately outside the ordinary DSH settings
|
|
28
28
|
document. The database and semantic-layer wizard is the next Bundle milestone.
|
|
29
29
|
|
|
30
|
+
## Managed workspace contract
|
|
31
|
+
|
|
32
|
+
Initialization writes a non-secret discovery document at
|
|
33
|
+
`.foggy/deepseek-harness/context.json` in the selected project. It points agents
|
|
34
|
+
to the authoritative global install state, the managed CLI's absolute command,
|
|
35
|
+
the Runtime state, and the two workspace Skills. The managed CLI is intentionally
|
|
36
|
+
isolated and does not need to be on `PATH`.
|
|
37
|
+
|
|
38
|
+
Both `foggy-deepseek-onboarding` and `foggy-ai-analysis` carry a
|
|
39
|
+
`.foggy-managed-skill.json` marker. **Re-download / Repair** verifies their
|
|
40
|
+
managed content, backs up a modified or outdated Skill under
|
|
41
|
+
`.foggy/onboarding-backups`, restores missing content, and regenerates the
|
|
42
|
+
project context. If a Skill is restored while Harness is already running, start
|
|
43
|
+
a new task or restart Harness so its Skill registry can reload it.
|
|
44
|
+
|
|
30
45
|
## Linux and WSL2 experience
|
|
31
46
|
|
|
32
47
|
Ubuntu and WSL2 users can use the checked-in preflighted installer under
|
package/lib/client.js
CHANGED
|
@@ -41,6 +41,8 @@ window.__ModuleLoader__.load({
|
|
|
41
41
|
cli: 'CLI',
|
|
42
42
|
launcher: 'Launcher',
|
|
43
43
|
analysisSkill: '分析 Skill',
|
|
44
|
+
onboardingSkill: '引导 Skill',
|
|
45
|
+
projectContext: '项目安装上下文',
|
|
44
46
|
python: 'Python',
|
|
45
47
|
java: 'Java',
|
|
46
48
|
installed: '已安装',
|
|
@@ -68,8 +70,9 @@ window.__ModuleLoader__.load({
|
|
|
68
70
|
dataRoot: '数据目录',
|
|
69
71
|
runtimeUrl: 'Runtime 地址',
|
|
70
72
|
projectRoot: 'Skill 目标工作区',
|
|
73
|
+
contextPath: '对话发现文件',
|
|
71
74
|
nextTitle: '后续配置',
|
|
72
|
-
nextCopy: 'Runtime
|
|
75
|
+
nextCopy: '对话会从项目内固定的 context.json 读取 CLI、Runtime 与 Skill 安装状态,不再依赖 PATH 猜测。Runtime 启动成功后,将继续进入数据库连接与语义层向导。',
|
|
73
76
|
beta: 'Beta',
|
|
74
77
|
}
|
|
75
78
|
|
|
@@ -88,6 +91,8 @@ window.__ModuleLoader__.load({
|
|
|
88
91
|
cli: 'CLI',
|
|
89
92
|
launcher: 'Launcher',
|
|
90
93
|
analysisSkill: 'Analysis Skill',
|
|
94
|
+
onboardingSkill: 'Onboarding Skill',
|
|
95
|
+
projectContext: 'Project install context',
|
|
91
96
|
python: 'Python',
|
|
92
97
|
java: 'Java',
|
|
93
98
|
installed: 'Installed',
|
|
@@ -115,8 +120,9 @@ window.__ModuleLoader__.load({
|
|
|
115
120
|
dataRoot: 'Data',
|
|
116
121
|
runtimeUrl: 'Runtime URL',
|
|
117
122
|
projectRoot: 'Skill target workspace',
|
|
123
|
+
contextPath: 'Conversation discovery file',
|
|
118
124
|
nextTitle: 'Next configuration',
|
|
119
|
-
nextCopy: '
|
|
125
|
+
nextCopy: 'Conversations discover the installed CLI, Runtime, and Skills from the fixed project context.json instead of guessing from PATH. After Runtime starts, the database connection and semantic-layer wizard comes next.',
|
|
120
126
|
beta: 'Beta',
|
|
121
127
|
}
|
|
122
128
|
|
|
@@ -264,8 +270,8 @@ window.__ModuleLoader__.load({
|
|
|
264
270
|
jsxs('div', { className: 'foggy-state', children: [jsx('span', { className: 'foggy-dot', 'data-state': status.state }), jsx('span', { children: t(stateLabels[status.state] || 'notInstalled') })] }),
|
|
265
271
|
jsxs('div', { className: 'foggy-actions', children: [
|
|
266
272
|
jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: refresh, children: t('refresh') }),
|
|
267
|
-
|
|
268
|
-
status.installed ? jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('repair'), children: t('repair') }) : null,
|
|
273
|
+
status.state === 'not-installed' ? jsx('button', { className: 'foggy-button foggy-button-primary', type: 'button', disabled: busy, onClick: () => run('initialize'), children: t('initialize') }) : null,
|
|
274
|
+
status.state !== 'not-installed' ? jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('repair'), children: t('repair') }) : null,
|
|
269
275
|
status.installed && !status.running && status.components.java.available ? jsx('button', { className: 'foggy-button foggy-button-primary', type: 'button', disabled: busy, onClick: () => run('runtimeStart'), children: t('start') }) : null,
|
|
270
276
|
status.running ? jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('runtimeStop'), children: t('stop') }) : null,
|
|
271
277
|
] }),
|
|
@@ -278,6 +284,8 @@ window.__ModuleLoader__.load({
|
|
|
278
284
|
componentCard(t('cli'), status.components.cli, 'runtime', t),
|
|
279
285
|
componentCard(t('launcher'), status.components.launcher, 'runtime', t),
|
|
280
286
|
componentCard(t('analysisSkill'), status.components.analysisSkill, 'runtime', t),
|
|
287
|
+
componentCard(t('onboardingSkill'), status.components.onboardingSkill, 'runtime', t),
|
|
288
|
+
componentCard(t('projectContext'), status.components.projectContext, 'runtime', t),
|
|
281
289
|
] }),
|
|
282
290
|
jsxs('div', { className: 'foggy-paths', children: [
|
|
283
291
|
jsx('h4', { children: t('roots') }),
|
|
@@ -285,6 +293,7 @@ window.__ModuleLoader__.load({
|
|
|
285
293
|
jsx('dt', { children: t('installRoot') }), jsx('dd', { children: status.roots.installRoot }),
|
|
286
294
|
jsx('dt', { children: t('dataRoot') }), jsx('dd', { children: status.roots.dataRoot }),
|
|
287
295
|
jsx('dt', { children: t('projectRoot') }), jsx('dd', { children: status.projectRoot }),
|
|
296
|
+
jsx('dt', { children: t('contextPath') }), jsx('dd', { children: status.contextPath }),
|
|
288
297
|
status.runtimeUrl ? jsx('dt', { children: t('runtimeUrl') }) : null,
|
|
289
298
|
status.runtimeUrl ? jsx('dd', { children: status.runtimeUrl }) : null,
|
|
290
299
|
] }),
|
package/lib/index.js
CHANGED
|
@@ -129,9 +129,11 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
129
129
|
|
|
130
130
|
async status() {
|
|
131
131
|
const roots = defaultRoots()
|
|
132
|
+
const projectRoot = process.env.FOGGY_PROJECT_ROOT || process.cwd()
|
|
132
133
|
const statePath = join(roots.installRoot, 'install-state.json')
|
|
133
134
|
const runtimeStatePath = join(roots.dataRoot, 'runtime-state.json')
|
|
134
135
|
const progressPath = join(roots.dataRoot, 'operation-progress.json')
|
|
136
|
+
const contextPath = join(projectRoot, '.foggy', 'deepseek-harness', 'context.json')
|
|
135
137
|
const manifest = await readJson(versionsFile)
|
|
136
138
|
let pythonProbe
|
|
137
139
|
try {
|
|
@@ -143,10 +145,16 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
143
145
|
const java = compatible(await commandVersion(process.env.JAVA_EXE || 'java', ['-version']), '17.0')
|
|
144
146
|
let state = null
|
|
145
147
|
let runtime = null
|
|
148
|
+
let context = null
|
|
146
149
|
try { state = await readJson(statePath) } catch {}
|
|
147
150
|
try { runtime = await readJson(runtimeStatePath) } catch {}
|
|
151
|
+
try { context = await readJson(contextPath) } catch {}
|
|
148
152
|
const cliPath = state?.cli?.command
|
|
149
153
|
const launcherPath = state?.launcher?.path
|
|
154
|
+
const analysisSkillPath = state?.skills?.analysis?.path || join(projectRoot, '.agents', 'skills', 'foggy-ai-analysis')
|
|
155
|
+
const onboardingSkillPath = state?.skills?.onboarding?.path || join(projectRoot, '.agents', 'skills', 'foggy-deepseek-onboarding')
|
|
156
|
+
const analysisMarker = await readOptionalJson(join(analysisSkillPath, '.foggy-managed-skill.json'))
|
|
157
|
+
const onboardingMarker = await readOptionalJson(join(onboardingSkillPath, '.foggy-managed-skill.json'))
|
|
150
158
|
const components = {
|
|
151
159
|
python,
|
|
152
160
|
java,
|
|
@@ -159,11 +167,36 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
159
167
|
version: state?.launcher?.version ?? manifest.components.launcher.version,
|
|
160
168
|
},
|
|
161
169
|
analysisSkill: {
|
|
162
|
-
installed: Boolean(
|
|
170
|
+
installed: Boolean(
|
|
171
|
+
await exists(join(analysisSkillPath, 'SKILL.md'))
|
|
172
|
+
&& analysisMarker?.schemaVersion === 'foggy-managed-skill/v1'
|
|
173
|
+
&& analysisMarker?.componentVersion === manifest.components.analysisSkill.version
|
|
174
|
+
),
|
|
163
175
|
version: state?.skills?.analysis?.version ?? manifest.components.analysisSkill.version,
|
|
164
176
|
},
|
|
177
|
+
onboardingSkill: {
|
|
178
|
+
installed: Boolean(
|
|
179
|
+
await exists(join(onboardingSkillPath, 'SKILL.md'))
|
|
180
|
+
&& onboardingMarker?.schemaVersion === 'foggy-managed-skill/v1'
|
|
181
|
+
&& onboardingMarker?.packageVersion === manifest.packageVersion
|
|
182
|
+
),
|
|
183
|
+
version: state?.skills?.onboarding?.version ?? state?.packageVersion ?? manifest.packageVersion,
|
|
184
|
+
},
|
|
185
|
+
projectContext: {
|
|
186
|
+
installed: Boolean(
|
|
187
|
+
context?.schemaVersion === 'foggy-deepseek-harness-context/v1'
|
|
188
|
+
&& context?.packageVersion === manifest.packageVersion
|
|
189
|
+
&& context?.projectRoot === projectRoot
|
|
190
|
+
&& context?.installStatePath === statePath
|
|
191
|
+
),
|
|
192
|
+
version: context?.schemaVersion ?? 'foggy-deepseek-harness-context/v1',
|
|
193
|
+
},
|
|
165
194
|
}
|
|
166
|
-
const installed = components.cli.installed
|
|
195
|
+
const installed = components.cli.installed
|
|
196
|
+
&& components.launcher.installed
|
|
197
|
+
&& components.analysisSkill.installed
|
|
198
|
+
&& components.onboardingSkill.installed
|
|
199
|
+
&& components.projectContext.installed
|
|
167
200
|
const running = Boolean(runtime && processRunning(Number(runtime.pid)))
|
|
168
201
|
const progress = await readOptionalJson(progressPath)
|
|
169
202
|
let operation = operationView(this.operation)
|
|
@@ -189,7 +222,8 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
189
222
|
running,
|
|
190
223
|
runtimeUrl: running ? runtime.runtimeUrl ?? null : null,
|
|
191
224
|
roots,
|
|
192
|
-
projectRoot
|
|
225
|
+
projectRoot,
|
|
226
|
+
contextPath,
|
|
193
227
|
components,
|
|
194
228
|
operation,
|
|
195
229
|
next: installed ? (running ? 'configure-database' : 'start-runtime') : 'initialize',
|
|
@@ -208,7 +242,7 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
208
242
|
'create isolated Python environment',
|
|
209
243
|
'download and verify pinned CLI and Launcher assets',
|
|
210
244
|
'install Foggy analysis and onboarding Skills into the workspace',
|
|
211
|
-
'write
|
|
245
|
+
'write global install state and project-relative discovery context',
|
|
212
246
|
],
|
|
213
247
|
secretsInDshSettings: false,
|
|
214
248
|
}
|
package/package.json
CHANGED
|
@@ -7,6 +7,23 @@ description: Install and operate a pinned Foggy CLI-first dev/test environment f
|
|
|
7
7
|
|
|
8
8
|
Set up Foggy through shell and `foggy-runtime` CLI. Do not configure Foggy MCP for this local workflow.
|
|
9
9
|
|
|
10
|
+
## Installed-context gate
|
|
11
|
+
|
|
12
|
+
Before deciding that Foggy, its CLI, or either Skill is missing, look for the project-relative file
|
|
13
|
+
`.foggy/deepseek-harness/context.json` from the current workspace root:
|
|
14
|
+
|
|
15
|
+
- Treat a valid `foggy-deepseek-harness-context/v1` document as the discovery pointer to the global
|
|
16
|
+
install state, absolute managed CLI command, Runtime state, and both installed Skills.
|
|
17
|
+
- Confirm the installation with this Skill's `doctor` wrapper. The absence of `foggy-runtime` from
|
|
18
|
+
`PATH` is not evidence that the managed CLI is missing; the plugin intentionally installs it in an
|
|
19
|
+
isolated environment and records its absolute command in the context and install-state files.
|
|
20
|
+
- Do not independently download or reinstall the CLI when valid project context exists. If the
|
|
21
|
+
context, a managed marker, or a Skill is missing or invalid, ask the user to open the Foggy plugin
|
|
22
|
+
settings and use **Re-download / Repair**. That operation regenerates context and restores managed
|
|
23
|
+
Skills, preserving a backup of modified or outdated Skill directories.
|
|
24
|
+
- If the plugin reports ready but this Skill was restored during the current Harness task, tell the
|
|
25
|
+
user that a new task or Harness restart may be needed for the Skill registry to reload.
|
|
26
|
+
|
|
10
27
|
## Mandatory orchestration boundary
|
|
11
28
|
|
|
12
29
|
For every new-database onboarding session, this Skill is the orchestration authority until
|
|
@@ -42,10 +59,11 @@ For every new-database onboarding session, this Skill is the orchestration autho
|
|
|
42
59
|
|
|
43
60
|
## Workflow
|
|
44
61
|
|
|
45
|
-
1.
|
|
62
|
+
1. Read `.foggy/deepseek-harness/context.json`, then run `scripts/doctor.ps1 --project-root <root>` on Windows or
|
|
46
63
|
`bash scripts/doctor.sh --project-root <root>` on Linux.
|
|
47
|
-
2. If the pinned CLI, Launcher, or
|
|
48
|
-
|
|
64
|
+
2. If the pinned CLI, Launcher, project context, or either managed Skill is missing, use the Foggy
|
|
65
|
+
plugin's Repair action. Use the matching install script only when the plugin UI is unavailable;
|
|
66
|
+
use `--dry-run` first when paths or permissions are uncertain.
|
|
49
67
|
3. Run `runtime-start` and require successful `wait-ready` plus `capabilities`. Record engine,
|
|
50
68
|
Runtime API version, schema version, security mode, URL, namespace, PID, and evidence path.
|
|
51
69
|
4. Confirm the project contains `.agents/skills/foggy-ai-analysis/SKILL.md`.
|
|
@@ -25,6 +25,10 @@ import zipfile
|
|
|
25
25
|
STATE_SCHEMA = "foggy-deepseek-onboarding-install/v1"
|
|
26
26
|
RUNTIME_STATE_SCHEMA = "foggy-deepseek-onboarding-runtime/v1"
|
|
27
27
|
ONBOARDING_STATE_SCHEMA = "foggy-deepseek-onboarding-state/v1"
|
|
28
|
+
CONTEXT_SCHEMA = "foggy-deepseek-harness-context/v1"
|
|
29
|
+
MANAGED_SKILL_SCHEMA = "foggy-managed-skill/v1"
|
|
30
|
+
MANAGED_SKILL_MARKER = ".foggy-managed-skill.json"
|
|
31
|
+
LEGACY_SKILL_MARKER = ".foggy-onboarding-install.json"
|
|
28
32
|
CONNECTION_SCHEMA = "foggy-deepseek-connection/v1"
|
|
29
33
|
SEMANTIC_PLAN_SCHEMA = "foggy-deepseek-semantic-plan/v1"
|
|
30
34
|
PROFILE_PATTERN = re.compile(r"^[a-z0-9][a-z0-9._-]{0,62}$")
|
|
@@ -331,6 +335,145 @@ def atomic_json(path: Path, payload: dict) -> None:
|
|
|
331
335
|
os.replace(temporary, path)
|
|
332
336
|
|
|
333
337
|
|
|
338
|
+
def skill_tree_digest(root: Path) -> str:
|
|
339
|
+
"""Hash managed Skill content while ignoring generated and management files."""
|
|
340
|
+
digest = hashlib.sha256()
|
|
341
|
+
ignored_names = {MANAGED_SKILL_MARKER, LEGACY_SKILL_MARKER}
|
|
342
|
+
files = sorted(
|
|
343
|
+
path for path in root.rglob("*")
|
|
344
|
+
if path.is_file()
|
|
345
|
+
and path.name not in ignored_names
|
|
346
|
+
and "__pycache__" not in path.parts
|
|
347
|
+
and path.suffix != ".pyc"
|
|
348
|
+
)
|
|
349
|
+
for path in files:
|
|
350
|
+
relative = path.relative_to(root).as_posix().encode("utf-8")
|
|
351
|
+
digest.update(len(relative).to_bytes(4, "big"))
|
|
352
|
+
digest.update(relative)
|
|
353
|
+
with path.open("rb") as stream:
|
|
354
|
+
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
|
355
|
+
digest.update(chunk)
|
|
356
|
+
return digest.hexdigest()
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def read_skill_marker(destination: Path) -> dict | None:
|
|
360
|
+
for name in (MANAGED_SKILL_MARKER, LEGACY_SKILL_MARKER):
|
|
361
|
+
marker = destination / name
|
|
362
|
+
if marker.is_file():
|
|
363
|
+
try:
|
|
364
|
+
payload = json.loads(marker.read_text(encoding="utf-8"))
|
|
365
|
+
if isinstance(payload, dict):
|
|
366
|
+
return payload
|
|
367
|
+
except (OSError, json.JSONDecodeError):
|
|
368
|
+
return None
|
|
369
|
+
return None
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def managed_skill_status(destination: Path, kind: str, expected_version: str) -> dict:
|
|
373
|
+
marker = read_skill_marker(destination)
|
|
374
|
+
present = (destination / "SKILL.md").is_file()
|
|
375
|
+
actual_digest = skill_tree_digest(destination) if destination.is_dir() else None
|
|
376
|
+
marker_digest = marker.get("installedDigest") if marker else None
|
|
377
|
+
managed = bool(
|
|
378
|
+
marker
|
|
379
|
+
and marker.get("schemaVersion") == MANAGED_SKILL_SCHEMA
|
|
380
|
+
and marker.get("kind") == kind
|
|
381
|
+
)
|
|
382
|
+
version = marker.get("componentVersion") if marker else None
|
|
383
|
+
return {
|
|
384
|
+
"path": str(destination),
|
|
385
|
+
"present": present,
|
|
386
|
+
"markerPresent": bool(marker),
|
|
387
|
+
"managed": managed,
|
|
388
|
+
"version": version,
|
|
389
|
+
"versionMatches": version == expected_version,
|
|
390
|
+
"integrityValid": bool(marker_digest and actual_digest == marker_digest),
|
|
391
|
+
"valid": bool(present and managed and version == expected_version and marker_digest and actual_digest == marker_digest),
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def write_skill_marker(
|
|
396
|
+
destination: Path,
|
|
397
|
+
*,
|
|
398
|
+
kind: str,
|
|
399
|
+
package_version: str,
|
|
400
|
+
component_version: str,
|
|
401
|
+
source_digest: str,
|
|
402
|
+
archive_sha256: str | None = None,
|
|
403
|
+
) -> dict:
|
|
404
|
+
marker = {
|
|
405
|
+
"schemaVersion": MANAGED_SKILL_SCHEMA,
|
|
406
|
+
"kind": kind,
|
|
407
|
+
"managedBy": "@foggy-projects/deepseek-harness-plugin",
|
|
408
|
+
"packageVersion": package_version,
|
|
409
|
+
"componentVersion": component_version,
|
|
410
|
+
"sourceDigest": source_digest,
|
|
411
|
+
"installedDigest": skill_tree_digest(destination),
|
|
412
|
+
"installedAt": now_utc(),
|
|
413
|
+
}
|
|
414
|
+
if archive_sha256:
|
|
415
|
+
marker["archiveSha256"] = archive_sha256
|
|
416
|
+
atomic_json(destination / MANAGED_SKILL_MARKER, marker)
|
|
417
|
+
legacy = destination / LEGACY_SKILL_MARKER
|
|
418
|
+
if legacy.is_file():
|
|
419
|
+
legacy.unlink()
|
|
420
|
+
return marker
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def backup_skill(destination: Path, project_root: Path) -> Path:
|
|
424
|
+
backup_root = project_root / ".foggy" / "onboarding-backups"
|
|
425
|
+
backup_root.mkdir(parents=True, exist_ok=True)
|
|
426
|
+
backup = backup_root / f"{destination.name}-{dt.datetime.now().strftime('%Y%m%d-%H%M%S-%f')}"
|
|
427
|
+
shutil.move(str(destination), str(backup))
|
|
428
|
+
return backup
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def project_context_path(project_root: Path) -> Path:
|
|
432
|
+
return project_root / ".foggy" / "deepseek-harness" / "context.json"
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def project_relative(path: Path, project_root: Path) -> str:
|
|
436
|
+
try:
|
|
437
|
+
return path.resolve(strict=False).relative_to(project_root.resolve(strict=False)).as_posix()
|
|
438
|
+
except ValueError:
|
|
439
|
+
return str(path)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def write_project_context(state: dict) -> Path:
|
|
443
|
+
project_root = normalized(state["projectRoot"])
|
|
444
|
+
install_root = normalized(state["installRoot"])
|
|
445
|
+
data_root = normalized(state["dataRoot"])
|
|
446
|
+
context_path = project_context_path(project_root)
|
|
447
|
+
skills = {}
|
|
448
|
+
for kind in ("onboarding", "analysis"):
|
|
449
|
+
item = state["skills"][kind]
|
|
450
|
+
skill_path = normalized(item["path"])
|
|
451
|
+
skills[kind] = {
|
|
452
|
+
"version": item.get("version"),
|
|
453
|
+
"path": project_relative(skill_path, project_root),
|
|
454
|
+
"absolutePath": str(skill_path),
|
|
455
|
+
"markerPath": project_relative(skill_path / MANAGED_SKILL_MARKER, project_root),
|
|
456
|
+
"managed": bool(item.get("managed")),
|
|
457
|
+
}
|
|
458
|
+
payload = {
|
|
459
|
+
"schemaVersion": CONTEXT_SCHEMA,
|
|
460
|
+
"managedBy": "@foggy-projects/deepseek-harness-plugin",
|
|
461
|
+
"packageVersion": state["packageVersion"],
|
|
462
|
+
"generatedAt": now_utc(),
|
|
463
|
+
"projectRoot": str(project_root),
|
|
464
|
+
"installStatePath": str(install_root / "install-state.json"),
|
|
465
|
+
"runtimeStatePath": str(data_root / "runtime-state.json"),
|
|
466
|
+
"operationProgressPath": str(data_root / "operation-progress.json"),
|
|
467
|
+
"cli": state["cli"],
|
|
468
|
+
"launcher": state["launcher"],
|
|
469
|
+
"skills": skills,
|
|
470
|
+
"securityMode": state["securityMode"],
|
|
471
|
+
"productionReady": False,
|
|
472
|
+
}
|
|
473
|
+
atomic_json(context_path, payload)
|
|
474
|
+
return context_path
|
|
475
|
+
|
|
476
|
+
|
|
334
477
|
def read_json_object(path: Path, label: str) -> dict:
|
|
335
478
|
if not path.is_file():
|
|
336
479
|
raise OnboardingError(f"{label} not found: {path}")
|
|
@@ -621,22 +764,16 @@ def safe_extract(zip_path: Path, destination: Path) -> None:
|
|
|
621
764
|
archive.extractall(destination)
|
|
622
765
|
|
|
623
766
|
|
|
624
|
-
def install_analysis_skill(
|
|
767
|
+
def install_analysis_skill(
|
|
768
|
+
zip_path: Path,
|
|
769
|
+
project_root: Path,
|
|
770
|
+
version: str,
|
|
771
|
+
expected_hash: str,
|
|
772
|
+
package_version: str,
|
|
773
|
+
replace: bool,
|
|
774
|
+
) -> dict:
|
|
625
775
|
skills_root = project_root / ".agents" / "skills"
|
|
626
776
|
destination = skills_root / "foggy-ai-analysis"
|
|
627
|
-
marker = destination / ".foggy-onboarding-install.json"
|
|
628
|
-
if destination.exists():
|
|
629
|
-
if marker.is_file():
|
|
630
|
-
installed = json.loads(marker.read_text(encoding="utf-8"))
|
|
631
|
-
if installed.get("archiveSha256") == expected_hash:
|
|
632
|
-
return {"path": str(destination), "version": version, "action": "kept-matching"}
|
|
633
|
-
if not replace:
|
|
634
|
-
raise OnboardingError(f"Analysis Skill already exists and is not managed at {destination}; rerun with --replace-skill to back it up")
|
|
635
|
-
backup_root = project_root / ".foggy" / "onboarding-backups"
|
|
636
|
-
backup_root.mkdir(parents=True, exist_ok=True)
|
|
637
|
-
backup = backup_root / f"foggy-ai-analysis-{dt.datetime.now().strftime('%Y%m%d-%H%M%S')}"
|
|
638
|
-
shutil.move(str(destination), str(backup))
|
|
639
|
-
skills_root.mkdir(parents=True, exist_ok=True)
|
|
640
777
|
with tempfile.TemporaryDirectory(prefix="foggy-skill-") as temporary:
|
|
641
778
|
extract_root = Path(temporary)
|
|
642
779
|
safe_extract(zip_path, extract_root)
|
|
@@ -644,21 +781,81 @@ def install_analysis_skill(zip_path: Path, project_root: Path, version: str, exp
|
|
|
644
781
|
if len(candidates) != 1:
|
|
645
782
|
raise OnboardingError(f"Expected exactly one SKILL.md in analysis Skill archive, found {len(candidates)}")
|
|
646
783
|
source = candidates[0].parent
|
|
784
|
+
source_digest = skill_tree_digest(source)
|
|
785
|
+
if destination.exists():
|
|
786
|
+
installed = read_skill_marker(destination)
|
|
787
|
+
actual_digest = skill_tree_digest(destination)
|
|
788
|
+
marker_matches = bool(
|
|
789
|
+
installed
|
|
790
|
+
and installed.get("schemaVersion") in (MANAGED_SKILL_SCHEMA, "foggy-installed-skill/v1")
|
|
791
|
+
and installed.get("archiveSha256") == expected_hash
|
|
792
|
+
and (installed.get("sourceDigest") or installed.get("installedDigest") or actual_digest) == source_digest
|
|
793
|
+
and actual_digest == source_digest
|
|
794
|
+
)
|
|
795
|
+
if marker_matches:
|
|
796
|
+
marker = write_skill_marker(
|
|
797
|
+
destination,
|
|
798
|
+
kind="analysis",
|
|
799
|
+
package_version=package_version,
|
|
800
|
+
component_version=version,
|
|
801
|
+
source_digest=source_digest,
|
|
802
|
+
archive_sha256=expected_hash,
|
|
803
|
+
)
|
|
804
|
+
return {"path": str(destination), "version": version, "digest": marker["installedDigest"], "managed": True, "action": "kept-matching"}
|
|
805
|
+
if not replace:
|
|
806
|
+
raise OnboardingError(f"Analysis Skill is missing, modified, or outdated at {destination}; use the plugin Repair action to back it up and restore it")
|
|
807
|
+
backup_skill(destination, project_root)
|
|
808
|
+
skills_root.mkdir(parents=True, exist_ok=True)
|
|
647
809
|
shutil.copytree(source, destination)
|
|
648
|
-
|
|
649
|
-
|
|
810
|
+
marker = write_skill_marker(
|
|
811
|
+
destination,
|
|
812
|
+
kind="analysis",
|
|
813
|
+
package_version=package_version,
|
|
814
|
+
component_version=version,
|
|
815
|
+
source_digest=source_digest,
|
|
816
|
+
archive_sha256=expected_hash,
|
|
817
|
+
)
|
|
818
|
+
return {"path": str(destination), "version": version, "digest": marker["installedDigest"], "managed": True, "action": "installed"}
|
|
650
819
|
|
|
651
820
|
|
|
652
|
-
def install_onboarding_skill(project_root: Path) -> dict:
|
|
821
|
+
def install_onboarding_skill(project_root: Path, package_version: str, replace: bool) -> dict:
|
|
653
822
|
destination = project_root / ".agents" / "skills" / "foggy-deepseek-onboarding"
|
|
654
823
|
source = skill_root()
|
|
824
|
+
source_digest = skill_tree_digest(source)
|
|
655
825
|
if source.resolve() == destination.resolve(strict=False):
|
|
656
|
-
return {"path": str(destination), "action": "already-running-from-target"}
|
|
826
|
+
return {"path": str(destination), "version": package_version, "digest": source_digest, "managed": True, "action": "already-running-from-target"}
|
|
657
827
|
if destination.exists():
|
|
658
|
-
|
|
828
|
+
marker = read_skill_marker(destination)
|
|
829
|
+
actual_digest = skill_tree_digest(destination)
|
|
830
|
+
if (
|
|
831
|
+
marker
|
|
832
|
+
and marker.get("schemaVersion") == MANAGED_SKILL_SCHEMA
|
|
833
|
+
and marker.get("kind") == "onboarding"
|
|
834
|
+
and actual_digest == source_digest
|
|
835
|
+
and marker.get("sourceDigest") in (None, source_digest)
|
|
836
|
+
and marker.get("installedDigest") in (None, source_digest)
|
|
837
|
+
):
|
|
838
|
+
written = write_skill_marker(
|
|
839
|
+
destination,
|
|
840
|
+
kind="onboarding",
|
|
841
|
+
package_version=package_version,
|
|
842
|
+
component_version=package_version,
|
|
843
|
+
source_digest=source_digest,
|
|
844
|
+
)
|
|
845
|
+
return {"path": str(destination), "version": package_version, "digest": written["installedDigest"], "managed": True, "action": "kept-matching"}
|
|
846
|
+
if not replace:
|
|
847
|
+
raise OnboardingError(f"Onboarding Skill is missing, modified, or outdated at {destination}; use the plugin Repair action to back it up and restore it")
|
|
848
|
+
backup_skill(destination, project_root)
|
|
659
849
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
660
850
|
shutil.copytree(source, destination)
|
|
661
|
-
|
|
851
|
+
marker = write_skill_marker(
|
|
852
|
+
destination,
|
|
853
|
+
kind="onboarding",
|
|
854
|
+
package_version=package_version,
|
|
855
|
+
component_version=package_version,
|
|
856
|
+
source_digest=source_digest,
|
|
857
|
+
)
|
|
858
|
+
return {"path": str(destination), "version": package_version, "digest": marker["installedDigest"], "managed": True, "action": "installed"}
|
|
662
859
|
|
|
663
860
|
|
|
664
861
|
def read_install_state(install_root: Path, required: bool = True) -> dict | None:
|
|
@@ -820,11 +1017,12 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
820
1017
|
zip_asset = next(item for item in analysis_assets if item["role"] == "zip")
|
|
821
1018
|
progress.update("analysis-skill", 3, "Installing analysis Skill", fraction=0.9, current_file=zip_asset["file"])
|
|
822
1019
|
analysis_skill = install_analysis_skill(
|
|
823
|
-
downloads / "skill" / zip_asset["file"], project_root, components["analysisSkill"]["version"],
|
|
1020
|
+
downloads / "skill" / zip_asset["file"], project_root, components["analysisSkill"]["version"],
|
|
1021
|
+
zip_asset["sha256"], versions["packageVersion"], args.replace_skill,
|
|
824
1022
|
)
|
|
825
1023
|
progress.update("analysis-skill", 3, "Analysis Skill ready", fraction=1.0)
|
|
826
1024
|
progress.update("workspace-skills", 4, "Installing onboarding Skill", fraction=0.1)
|
|
827
|
-
onboarding_skill = install_onboarding_skill(project_root)
|
|
1025
|
+
onboarding_skill = install_onboarding_skill(project_root, versions["packageVersion"], args.replace_skill)
|
|
828
1026
|
progress.update("workspace-skills", 4, "Workspace Skills ready", fraction=1.0)
|
|
829
1027
|
state = {
|
|
830
1028
|
"schemaVersion": STATE_SCHEMA,
|
|
@@ -833,6 +1031,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
833
1031
|
"installRoot": str(install_root),
|
|
834
1032
|
"dataRoot": str(data_root),
|
|
835
1033
|
"projectRoot": str(project_root),
|
|
1034
|
+
"contextPath": str(project_context_path(project_root)),
|
|
836
1035
|
"cli": {"version": cli_component["version"], "command": str(cli_command), "mode": cli_mode},
|
|
837
1036
|
"launcher": {"version": components["launcher"]["version"], "path": str(launcher_dir)},
|
|
838
1037
|
"skills": {"onboarding": onboarding_skill, "analysis": analysis_skill},
|
|
@@ -842,6 +1041,8 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
842
1041
|
}
|
|
843
1042
|
progress.update("state", 5, "Writing install state", fraction=0.2, current_file="install-state.json")
|
|
844
1043
|
atomic_json(install_root / "install-state.json", state)
|
|
1044
|
+
progress.update("state", 5, "Writing project context", fraction=0.7, current_file=".foggy/deepseek-harness/context.json")
|
|
1045
|
+
context_path = write_project_context(state)
|
|
845
1046
|
progress.finish()
|
|
846
1047
|
ACTIVE_PROGRESS = None
|
|
847
1048
|
return {
|
|
@@ -851,6 +1052,7 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
851
1052
|
"installRoot": str(install_root),
|
|
852
1053
|
"dataRoot": str(data_root),
|
|
853
1054
|
"projectRoot": str(project_root),
|
|
1055
|
+
"contextPath": str(context_path),
|
|
854
1056
|
"cliVersion": cli_component["version"],
|
|
855
1057
|
"launcherVersion": components["launcher"]["version"],
|
|
856
1058
|
"analysisSkill": analysis_skill,
|
|
@@ -2032,8 +2234,24 @@ def doctor_command(args: argparse.Namespace) -> dict:
|
|
|
2032
2234
|
path = launcher_dir / asset["file"]
|
|
2033
2235
|
launcher_checks.append({"file": asset["file"], "present": path.is_file(), "sha256Valid": path.is_file() and sha256(path) == asset["sha256"]})
|
|
2034
2236
|
launcher_ok = bool(launcher_checks) and all(item["present"] and item["sha256Valid"] for item in launcher_checks)
|
|
2035
|
-
|
|
2036
|
-
|
|
2237
|
+
analysis_skill_root = project_root / ".agents" / "skills" / "foggy-ai-analysis"
|
|
2238
|
+
onboarding_skill_root = project_root / ".agents" / "skills" / "foggy-deepseek-onboarding"
|
|
2239
|
+
analysis_skill = managed_skill_status(analysis_skill_root, "analysis", versions["components"]["analysisSkill"]["version"])
|
|
2240
|
+
onboarding_skill = managed_skill_status(onboarding_skill_root, "onboarding", versions["packageVersion"])
|
|
2241
|
+
context_path = project_context_path(project_root)
|
|
2242
|
+
context = None
|
|
2243
|
+
if context_path.is_file():
|
|
2244
|
+
try:
|
|
2245
|
+
context = json.loads(context_path.read_text(encoding="utf-8"))
|
|
2246
|
+
except (OSError, json.JSONDecodeError):
|
|
2247
|
+
context = None
|
|
2248
|
+
context_ok = bool(
|
|
2249
|
+
context
|
|
2250
|
+
and context.get("schemaVersion") == CONTEXT_SCHEMA
|
|
2251
|
+
and context.get("packageVersion") == versions["packageVersion"]
|
|
2252
|
+
and normalized(context.get("projectRoot", "")) == project_root
|
|
2253
|
+
and normalized(context.get("installStatePath", "")) == install_root / "install-state.json"
|
|
2254
|
+
)
|
|
2037
2255
|
runtime = {"status": "stopped"}
|
|
2038
2256
|
if state:
|
|
2039
2257
|
data_root = normalized(state["dataRoot"])
|
|
@@ -2047,7 +2265,9 @@ def doctor_command(args: argparse.Namespace) -> dict:
|
|
|
2047
2265
|
"java": java_ok,
|
|
2048
2266
|
"cli": cli_ok,
|
|
2049
2267
|
"launcher": launcher_ok,
|
|
2050
|
-
"analysisSkill": analysis_skill
|
|
2268
|
+
"analysisSkill": analysis_skill["valid"],
|
|
2269
|
+
"onboardingSkill": onboarding_skill["valid"],
|
|
2270
|
+
"projectContext": context_ok,
|
|
2051
2271
|
}
|
|
2052
2272
|
if args.strict_runtime:
|
|
2053
2273
|
required["runtime"] = runtime["status"] == "running"
|
|
@@ -2061,7 +2281,8 @@ def doctor_command(args: argparse.Namespace) -> dict:
|
|
|
2061
2281
|
"java": java,
|
|
2062
2282
|
"cli": cli,
|
|
2063
2283
|
"launcherAssets": launcher_checks,
|
|
2064
|
-
"skills": {"analysis":
|
|
2284
|
+
"skills": {"analysis": analysis_skill, "onboarding": onboarding_skill},
|
|
2285
|
+
"projectContext": {"path": str(context_path), "valid": context_ok},
|
|
2065
2286
|
"runtime": runtime,
|
|
2066
2287
|
"environmentPresence": {name: bool(os.environ.get(name)) for name in ("DEEPSEEK_API_KEY", "ALIYUN_TOKEN_PLAN_API_KEY", "FOGGY_RUNTIME_API_AUTH_CODE", "FOGGY_RUNTIME_AUTHORIZATION")},
|
|
2067
2288
|
"productionReady": False,
|