@foggy-projects/deepseek-harness-plugin 0.4.0-beta.2 → 0.4.0-beta.3
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
|
@@ -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.3.tgz
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Restart `dsh web`, open Settings → Plugins → Foggy Data Analysis, and initialize
|
package/lib/client.js
CHANGED
|
@@ -52,6 +52,15 @@ window.__ModuleLoader__.load({
|
|
|
52
52
|
start: '启动 Runtime',
|
|
53
53
|
stop: '停止 Runtime',
|
|
54
54
|
working: '操作进行中…',
|
|
55
|
+
progressWorking: '正在初始化 Foggy…',
|
|
56
|
+
progressPreflight: '检查运行环境',
|
|
57
|
+
progressCli: '准备 CLI',
|
|
58
|
+
progressLauncher: '准备 Launcher',
|
|
59
|
+
progressAnalysis: '准备分析 Skill',
|
|
60
|
+
progressSkills: '安装工作区 Skills',
|
|
61
|
+
progressState: '写入安装状态',
|
|
62
|
+
progressComplete: '初始化完成',
|
|
63
|
+
progressFiles: '文件',
|
|
55
64
|
accepted: '操作已开始;页面会自动刷新状态。',
|
|
56
65
|
actionFailed: '操作失败',
|
|
57
66
|
roots: '本地目录',
|
|
@@ -90,6 +99,15 @@ window.__ModuleLoader__.load({
|
|
|
90
99
|
start: 'Start Runtime',
|
|
91
100
|
stop: 'Stop Runtime',
|
|
92
101
|
working: 'Operation in progress…',
|
|
102
|
+
progressWorking: 'Initializing Foggy…',
|
|
103
|
+
progressPreflight: 'Checking prerequisites',
|
|
104
|
+
progressCli: 'Preparing CLI',
|
|
105
|
+
progressLauncher: 'Preparing Launcher',
|
|
106
|
+
progressAnalysis: 'Preparing analysis Skill',
|
|
107
|
+
progressSkills: 'Installing workspace Skills',
|
|
108
|
+
progressState: 'Writing install state',
|
|
109
|
+
progressComplete: 'Initialization complete',
|
|
110
|
+
progressFiles: 'files',
|
|
93
111
|
accepted: 'The operation started; this page will refresh automatically.',
|
|
94
112
|
actionFailed: 'Operation failed',
|
|
95
113
|
roots: 'Local directories',
|
|
@@ -110,6 +128,7 @@ window.__ModuleLoader__.load({
|
|
|
110
128
|
.foggy-state{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:600}.foggy-dot{width:9px;height:9px;border-radius:50%;background:var(--dsw-alias-label-tertiary)}.foggy-dot[data-state=ready],.foggy-dot[data-state=running]{background:var(--dsw-alias-state-success-primary)}.foggy-dot[data-state=degraded]{background:var(--dsw-alias-state-error-primary)}
|
|
111
129
|
.foggy-actions{display:flex;gap:8px;flex-wrap:wrap}.foggy-button{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;border-radius:8px;padding:7px 12px;cursor:pointer}.foggy-button:hover{background:var(--dsw-alias-interactive-bg-hover)}.foggy-button:disabled{opacity:.55;cursor:not-allowed}.foggy-button-primary{border-color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-primary);color:white}
|
|
112
130
|
.foggy-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.foggy-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:13px 14px}.foggy-card-head{display:flex;justify-content:space-between;gap:8px}.foggy-card strong{font-size:13px}.foggy-badge{font-size:11px;color:var(--dsw-alias-label-secondary)}.foggy-card code{display:block;margin-top:8px;color:var(--dsw-alias-label-tertiary);font-size:11px;overflow-wrap:anywhere}
|
|
131
|
+
.foggy-progress{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:13px 14px}.foggy-progress-head{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px}.foggy-progress-head strong{font-weight:600}.foggy-progress-percent{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}.foggy-progress-track{height:8px;margin-top:10px;border-radius:999px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}.foggy-progress-fill{height:100%;border-radius:inherit;background:var(--dsw-alias-state-business-primary);transition:width .25s ease}.foggy-progress-meta{display:flex;justify-content:space-between;gap:12px;margin-top:8px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}.foggy-progress-file{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
113
132
|
.foggy-message{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}.foggy-message[data-error=true]{color:var(--dsw-alias-state-error-primary)}.foggy-paths,.foggy-next{border-top:1px solid var(--dsw-alias-border-l2);padding-top:14px}.foggy-paths h4,.foggy-next h4{margin:0 0 8px;font-size:13px}.foggy-paths dl{display:grid;grid-template-columns:100px minmax(0,1fr);gap:6px 10px;margin:0}.foggy-paths dt{color:var(--dsw-alias-label-tertiary);font-size:12px}.foggy-paths dd{margin:0;min-width:0;overflow-wrap:anywhere;font-family:var(--ds-font-family-code);font-size:11px}.foggy-next p{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
|
|
114
133
|
@media (width<=680px){.foggy-grid{grid-template-columns:minmax(0,1fr)}}
|
|
115
134
|
`
|
|
@@ -146,6 +165,52 @@ window.__ModuleLoader__.load({
|
|
|
146
165
|
})
|
|
147
166
|
}
|
|
148
167
|
|
|
168
|
+
function progressPanel(progress, t) {
|
|
169
|
+
if (!progress) return null
|
|
170
|
+
const percent = Math.max(0, Math.min(100, Number(progress.percent) || 0))
|
|
171
|
+
const phaseKeys = {
|
|
172
|
+
preflight: 'progressPreflight',
|
|
173
|
+
cli: 'progressCli',
|
|
174
|
+
launcher: 'progressLauncher',
|
|
175
|
+
'analysis-skill': 'progressAnalysis',
|
|
176
|
+
'workspace-skills': 'progressSkills',
|
|
177
|
+
state: 'progressState',
|
|
178
|
+
complete: 'progressComplete',
|
|
179
|
+
}
|
|
180
|
+
const phase = t(phaseKeys[progress.phase] || 'progressWorking')
|
|
181
|
+
const step = progress.step?.index && progress.step?.total
|
|
182
|
+
? `${progress.step.index}/${progress.step.total}`
|
|
183
|
+
: ''
|
|
184
|
+
const files = progress.files?.total
|
|
185
|
+
? `${progress.files.completed || 0}/${progress.files.total} ${t('progressFiles')}`
|
|
186
|
+
: ''
|
|
187
|
+
return jsxs('div', {
|
|
188
|
+
className: 'foggy-progress',
|
|
189
|
+
role: 'status',
|
|
190
|
+
'aria-live': 'polite',
|
|
191
|
+
children: [
|
|
192
|
+
jsxs('div', { className: 'foggy-progress-head', children: [
|
|
193
|
+
jsx('strong', { children: progress.message || phase }),
|
|
194
|
+
jsx('span', { className: 'foggy-progress-percent', children: `${percent}%` }),
|
|
195
|
+
] }),
|
|
196
|
+
jsx('div', {
|
|
197
|
+
className: 'foggy-progress-track',
|
|
198
|
+
role: 'progressbar',
|
|
199
|
+
'aria-valuemin': 0,
|
|
200
|
+
'aria-valuemax': 100,
|
|
201
|
+
'aria-valuenow': percent,
|
|
202
|
+
'aria-label': phase,
|
|
203
|
+
children: jsx('div', { className: 'foggy-progress-fill', style: { width: `${percent}%` } }),
|
|
204
|
+
}),
|
|
205
|
+
jsxs('div', { className: 'foggy-progress-meta', children: [
|
|
206
|
+
jsx('span', { children: phase }),
|
|
207
|
+
jsx('span', { children: [step, files].filter(Boolean).join(' · ') }),
|
|
208
|
+
] }),
|
|
209
|
+
progress.currentFile ? jsx('div', { className: 'foggy-progress-meta foggy-progress-file', title: progress.currentFile, children: progress.currentFile }) : null,
|
|
210
|
+
],
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
149
214
|
function FoggySettingsTab({ api, t }) {
|
|
150
215
|
const [view, setView] = useState({ phase: 'loading', status: null, message: '', error: false })
|
|
151
216
|
|
|
@@ -188,6 +253,7 @@ window.__ModuleLoader__.load({
|
|
|
188
253
|
const operationError = status.operation?.state === 'failed'
|
|
189
254
|
? status.operation.error || status.operation.result?.error?.message || t('actionFailed')
|
|
190
255
|
: ''
|
|
256
|
+
const progress = busy ? progressPanel(status.operation?.progress, t) : null
|
|
191
257
|
const stateLabels = { 'not-installed': 'notInstalled', degraded: 'degraded', ready: 'ready', running: 'running' }
|
|
192
258
|
return jsxs('section', { className: 'foggy-settings', children: [
|
|
193
259
|
jsxs('div', { className: 'foggy-hero', children: [
|
|
@@ -204,6 +270,7 @@ window.__ModuleLoader__.load({
|
|
|
204
270
|
status.running ? jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('runtimeStop'), children: t('stop') }) : null,
|
|
205
271
|
] }),
|
|
206
272
|
] }),
|
|
273
|
+
progress,
|
|
207
274
|
busy || operationError || view.message ? jsx('p', { className: 'foggy-message', 'data-error': operationError || view.error ? 'true' : undefined, children: busy ? t('working') : operationError || view.message }) : null,
|
|
208
275
|
jsxs('div', { className: 'foggy-grid', children: [
|
|
209
276
|
componentCard(t('python'), status.components.python, 'tool', t),
|
package/lib/index.js
CHANGED
|
@@ -46,6 +46,14 @@ async function readJson(path) {
|
|
|
46
46
|
return JSON.parse(await readFile(path, 'utf8'))
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
async function readOptionalJson(path) {
|
|
50
|
+
try {
|
|
51
|
+
return await readJson(path)
|
|
52
|
+
} catch {
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
async function commandVersion(command, args) {
|
|
50
58
|
try {
|
|
51
59
|
const { stdout, stderr } = await execFileAsync(command, args, {
|
|
@@ -123,6 +131,7 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
123
131
|
const roots = defaultRoots()
|
|
124
132
|
const statePath = join(roots.installRoot, 'install-state.json')
|
|
125
133
|
const runtimeStatePath = join(roots.dataRoot, 'runtime-state.json')
|
|
134
|
+
const progressPath = join(roots.dataRoot, 'operation-progress.json')
|
|
126
135
|
const manifest = await readJson(versionsFile)
|
|
127
136
|
let pythonProbe
|
|
128
137
|
try {
|
|
@@ -156,6 +165,22 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
156
165
|
}
|
|
157
166
|
const installed = components.cli.installed && components.launcher.installed && components.analysisSkill.installed
|
|
158
167
|
const running = Boolean(runtime && processRunning(Number(runtime.pid)))
|
|
168
|
+
const progress = await readOptionalJson(progressPath)
|
|
169
|
+
let operation = operationView(this.operation)
|
|
170
|
+
if (operation.state === 'running' && progress?.operationId === operation.id) {
|
|
171
|
+
operation = { ...operation, progress }
|
|
172
|
+
} else if (!this.operation && progress?.state === 'running' && progress.operationId) {
|
|
173
|
+
operation = {
|
|
174
|
+
id: progress.operationId,
|
|
175
|
+
kind: progress.kind || 'initialize',
|
|
176
|
+
state: 'running',
|
|
177
|
+
startedAt: progress.startedAt || null,
|
|
178
|
+
finishedAt: null,
|
|
179
|
+
result: null,
|
|
180
|
+
error: null,
|
|
181
|
+
progress,
|
|
182
|
+
}
|
|
183
|
+
}
|
|
159
184
|
return {
|
|
160
185
|
success: true,
|
|
161
186
|
packageVersion: manifest.packageVersion,
|
|
@@ -166,7 +191,7 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
166
191
|
roots,
|
|
167
192
|
projectRoot: process.env.FOGGY_PROJECT_ROOT || process.cwd(),
|
|
168
193
|
components,
|
|
169
|
-
operation
|
|
194
|
+
operation,
|
|
170
195
|
next: installed ? (running ? 'configure-database' : 'start-runtime') : 'initialize',
|
|
171
196
|
}
|
|
172
197
|
}
|
|
@@ -211,6 +236,10 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
211
236
|
}
|
|
212
237
|
const id = `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`
|
|
213
238
|
const args = explicitArgs ?? ['install', '--project-root', process.env.FOGGY_PROJECT_ROOT || process.cwd()]
|
|
239
|
+
if (!explicitArgs) {
|
|
240
|
+
const roots = defaultRoots()
|
|
241
|
+
args.push('--progress-file', join(roots.dataRoot, 'operation-progress.json'), '--operation-id', id, '--operation-kind', kind)
|
|
242
|
+
}
|
|
214
243
|
if (replaceSkill) args.push('--replace-skill')
|
|
215
244
|
for (const cacheDir of (process.env.FOGGY_ASSET_CACHE_DIRS || '').split(delimiter).filter(Boolean)) {
|
|
216
245
|
args.push('--asset-cache-dir', cacheDir)
|
|
@@ -223,6 +252,17 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
223
252
|
finishedAt: null,
|
|
224
253
|
result: null,
|
|
225
254
|
error: null,
|
|
255
|
+
progress: !explicitArgs ? {
|
|
256
|
+
schemaVersion: 'foggy-deepseek-onboarding-progress/v1',
|
|
257
|
+
operationId: id,
|
|
258
|
+
kind,
|
|
259
|
+
state: 'running',
|
|
260
|
+
phase: 'preflight',
|
|
261
|
+
message: 'Starting Foggy initialization',
|
|
262
|
+
currentFile: null,
|
|
263
|
+
percent: 0,
|
|
264
|
+
step: { index: 1, total: 6 },
|
|
265
|
+
} : null,
|
|
226
266
|
promise: null,
|
|
227
267
|
}
|
|
228
268
|
operation.promise = runOnboarding(args)
|
|
@@ -230,11 +270,27 @@ export class FoggyIntegrationGateway extends TypertRemoteService {
|
|
|
230
270
|
operation.state = result.success === false ? 'failed' : 'succeeded'
|
|
231
271
|
operation.result = result
|
|
232
272
|
operation.finishedAt = new Date().toISOString()
|
|
273
|
+
if (operation.progress) {
|
|
274
|
+
operation.progress = {
|
|
275
|
+
...operation.progress,
|
|
276
|
+
state: operation.state,
|
|
277
|
+
phase: operation.state === 'succeeded' ? 'complete' : operation.progress.phase,
|
|
278
|
+
message: operation.state === 'succeeded' ? 'Foggy initialization completed' : 'Foggy initialization failed',
|
|
279
|
+
percent: operation.state === 'succeeded' ? 100 : operation.progress.percent,
|
|
280
|
+
}
|
|
281
|
+
}
|
|
233
282
|
})
|
|
234
283
|
.catch((error) => {
|
|
235
284
|
operation.state = 'failed'
|
|
236
285
|
operation.error = String(error.message ?? error)
|
|
237
286
|
operation.finishedAt = new Date().toISOString()
|
|
287
|
+
if (operation.progress) {
|
|
288
|
+
operation.progress = {
|
|
289
|
+
...operation.progress,
|
|
290
|
+
state: 'failed',
|
|
291
|
+
message: 'Foggy initialization failed',
|
|
292
|
+
}
|
|
293
|
+
}
|
|
238
294
|
})
|
|
239
295
|
this.operation = operation
|
|
240
296
|
return { success: true, accepted: true, operation: operationView(operation) }
|
package/package.json
CHANGED
|
@@ -38,6 +38,93 @@ class OnboardingError(RuntimeError):
|
|
|
38
38
|
pass
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
PROGRESS_SCHEMA = "foggy-deepseek-onboarding-progress/v1"
|
|
42
|
+
ACTIVE_PROGRESS: "ProgressReporter | None" = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ProgressReporter:
|
|
46
|
+
total_steps = 6
|
|
47
|
+
|
|
48
|
+
def __init__(self, path: str | None, operation_id: str | None, kind: str) -> None:
|
|
49
|
+
self.path = Path(path).expanduser() if path else None
|
|
50
|
+
self.operation_id = operation_id
|
|
51
|
+
self.kind = kind
|
|
52
|
+
self.started_at = now_utc()
|
|
53
|
+
self.phase = "preflight"
|
|
54
|
+
self.step_index = 0
|
|
55
|
+
self.fraction = 0.0
|
|
56
|
+
self.message = ""
|
|
57
|
+
self.current_file: str | None = None
|
|
58
|
+
self.completed_files: int | None = None
|
|
59
|
+
self.total_files: int | None = None
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def enabled(self) -> bool:
|
|
63
|
+
return self.path is not None
|
|
64
|
+
|
|
65
|
+
def _write(self, state: str, *, finished_at: str | None = None, error: str | None = None) -> None:
|
|
66
|
+
if not self.path:
|
|
67
|
+
return
|
|
68
|
+
bounded_step = max(0, min(self.step_index, self.total_steps))
|
|
69
|
+
bounded_fraction = max(0.0, min(float(self.fraction), 1.0))
|
|
70
|
+
percent = 100 if state == "succeeded" else round(((bounded_step + bounded_fraction) / self.total_steps) * 100)
|
|
71
|
+
payload = {
|
|
72
|
+
"schemaVersion": PROGRESS_SCHEMA,
|
|
73
|
+
"operationId": self.operation_id,
|
|
74
|
+
"kind": self.kind,
|
|
75
|
+
"state": state,
|
|
76
|
+
"phase": self.phase,
|
|
77
|
+
"message": self.message,
|
|
78
|
+
"currentFile": self.current_file,
|
|
79
|
+
"percent": percent,
|
|
80
|
+
"step": {"index": min(bounded_step + 1, self.total_steps), "total": self.total_steps},
|
|
81
|
+
"startedAt": self.started_at,
|
|
82
|
+
"updatedAt": now_utc(),
|
|
83
|
+
}
|
|
84
|
+
if self.completed_files is not None and self.total_files is not None:
|
|
85
|
+
payload["files"] = {"completed": self.completed_files, "total": self.total_files}
|
|
86
|
+
if finished_at:
|
|
87
|
+
payload["finishedAt"] = finished_at
|
|
88
|
+
if error:
|
|
89
|
+
payload["error"] = error
|
|
90
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
91
|
+
atomic_json(self.path, payload)
|
|
92
|
+
|
|
93
|
+
def update(
|
|
94
|
+
self,
|
|
95
|
+
phase: str,
|
|
96
|
+
step_index: int,
|
|
97
|
+
message: str,
|
|
98
|
+
*,
|
|
99
|
+
fraction: float = 0.0,
|
|
100
|
+
current_file: str | None = None,
|
|
101
|
+
completed_files: int | None = None,
|
|
102
|
+
total_files: int | None = None,
|
|
103
|
+
) -> None:
|
|
104
|
+
self.phase = phase
|
|
105
|
+
self.step_index = step_index
|
|
106
|
+
self.fraction = fraction
|
|
107
|
+
self.message = message
|
|
108
|
+
self.current_file = current_file
|
|
109
|
+
self.completed_files = completed_files
|
|
110
|
+
self.total_files = total_files
|
|
111
|
+
self._write("running")
|
|
112
|
+
|
|
113
|
+
def finish(self) -> None:
|
|
114
|
+
self.phase = "complete"
|
|
115
|
+
self.step_index = self.total_steps
|
|
116
|
+
self.fraction = 0.0
|
|
117
|
+
self.message = "Foggy initialization completed"
|
|
118
|
+
self.current_file = None
|
|
119
|
+
self.completed_files = None
|
|
120
|
+
self.total_files = None
|
|
121
|
+
self._write("succeeded", finished_at=now_utc())
|
|
122
|
+
|
|
123
|
+
def fail(self, error: object) -> None:
|
|
124
|
+
self.message = "Foggy initialization failed"
|
|
125
|
+
self._write("failed", finished_at=now_utc(), error=str(error))
|
|
126
|
+
|
|
127
|
+
|
|
41
128
|
def now_utc() -> str:
|
|
42
129
|
return dt.datetime.now(dt.timezone.utc).isoformat()
|
|
43
130
|
|
|
@@ -182,7 +269,18 @@ def cached_asset(name: str, expected: str, cache_dirs: list[Path]) -> Path | Non
|
|
|
182
269
|
return None
|
|
183
270
|
|
|
184
271
|
|
|
185
|
-
def materialize(
|
|
272
|
+
def materialize(
|
|
273
|
+
asset: dict,
|
|
274
|
+
destination: Path,
|
|
275
|
+
cache_dirs: list[Path],
|
|
276
|
+
*,
|
|
277
|
+
progress: ProgressReporter | None = None,
|
|
278
|
+
progress_phase: str = "",
|
|
279
|
+
progress_step: int = 0,
|
|
280
|
+
progress_index: int = 0,
|
|
281
|
+
progress_total: int = 1,
|
|
282
|
+
progress_message: str = "Downloading and verifying asset",
|
|
283
|
+
) -> dict:
|
|
186
284
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
187
285
|
if destination.is_file():
|
|
188
286
|
verify_asset(destination, asset["sha256"])
|
|
@@ -196,7 +294,18 @@ def materialize(asset: dict, destination: Path, cache_dirs: list[Path]) -> dict:
|
|
|
196
294
|
if temporary.exists():
|
|
197
295
|
temporary.unlink()
|
|
198
296
|
try:
|
|
199
|
-
|
|
297
|
+
def reporthook(block_count: int, block_size: int, total_size: int) -> None:
|
|
298
|
+
if progress is None or not progress_phase or total_size <= 0:
|
|
299
|
+
return
|
|
300
|
+
downloaded = min(block_count * block_size, total_size)
|
|
301
|
+
fraction = (progress_index + downloaded / total_size) / max(progress_total, 1)
|
|
302
|
+
progress.update(
|
|
303
|
+
progress_phase, progress_step, progress_message,
|
|
304
|
+
fraction=fraction, current_file=asset["file"],
|
|
305
|
+
completed_files=progress_index, total_files=progress_total,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
urllib.request.urlretrieve(asset["url"], temporary, reporthook=reporthook)
|
|
200
309
|
verify_asset(temporary, asset["sha256"])
|
|
201
310
|
os.replace(temporary, destination)
|
|
202
311
|
finally:
|
|
@@ -574,6 +683,7 @@ def java_probe() -> dict:
|
|
|
574
683
|
|
|
575
684
|
|
|
576
685
|
def install_command(args: argparse.Namespace) -> dict:
|
|
686
|
+
global ACTIVE_PROGRESS
|
|
577
687
|
versions = load_versions()
|
|
578
688
|
install_root = normalized(args.install_root or default_install_root())
|
|
579
689
|
data_root = normalized(args.data_root or default_data_root())
|
|
@@ -595,6 +705,13 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
595
705
|
}
|
|
596
706
|
if args.dry_run:
|
|
597
707
|
return {"success": True, "dryRun": True, "plan": plan}
|
|
708
|
+
progress = ProgressReporter(
|
|
709
|
+
getattr(args, "progress_file", None),
|
|
710
|
+
getattr(args, "operation_id", None),
|
|
711
|
+
getattr(args, "operation_kind", "initialize"),
|
|
712
|
+
)
|
|
713
|
+
ACTIVE_PROGRESS = progress
|
|
714
|
+
progress.update("preflight", 0, "Checking prerequisites")
|
|
598
715
|
if sys.version_info < (3, 11):
|
|
599
716
|
raise OnboardingError(f"Python 3.11+ required, got {sys.version.split()[0]}")
|
|
600
717
|
if not project_root.is_dir():
|
|
@@ -605,20 +722,37 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
605
722
|
verified: list[dict] = []
|
|
606
723
|
|
|
607
724
|
cli_component = components["cli"]
|
|
608
|
-
for role in ("wheel", "checksums")
|
|
609
|
-
|
|
610
|
-
|
|
725
|
+
cli_assets = [cli_component[role] for role in ("wheel", "checksums")]
|
|
726
|
+
for index, asset in enumerate(cli_assets):
|
|
727
|
+
progress.update(
|
|
728
|
+
"cli", 1, "Downloading and verifying CLI",
|
|
729
|
+
fraction=index / len(cli_assets), current_file=asset["file"],
|
|
730
|
+
completed_files=index, total_files=len(cli_assets),
|
|
731
|
+
)
|
|
732
|
+
verified.append(materialize(
|
|
733
|
+
asset, downloads / "cli" / asset["file"], cache_dirs,
|
|
734
|
+
progress=progress, progress_phase="cli", progress_step=1,
|
|
735
|
+
progress_index=index, progress_total=len(cli_assets),
|
|
736
|
+
progress_message="Downloading and verifying CLI",
|
|
737
|
+
))
|
|
738
|
+
progress.update(
|
|
739
|
+
"cli", 1, "Downloading and verifying CLI",
|
|
740
|
+
fraction=(index + 1) / len(cli_assets), current_file=asset["file"],
|
|
741
|
+
completed_files=index + 1, total_files=len(cli_assets),
|
|
742
|
+
)
|
|
611
743
|
checksum_lines = (downloads / "cli" / cli_component["checksums"]["file"]).read_text(encoding="utf-8").splitlines()
|
|
612
744
|
checksum_entries = {line.split(maxsplit=1)[1].strip(): line.split(maxsplit=1)[0].lower() for line in checksum_lines if len(line.split(maxsplit=1)) == 2}
|
|
613
745
|
wheel_asset = cli_component["wheel"]
|
|
614
746
|
if checksum_entries.get(wheel_asset["file"]) != wheel_asset["sha256"]:
|
|
615
747
|
raise OnboardingError("Pinned CLI SHA256SUMS does not match the pinned wheel hash")
|
|
616
748
|
if args.skip_cli_install:
|
|
749
|
+
progress.update("cli", 1, "Using existing CLI", fraction=0.65, current_file="foggy-runtime")
|
|
617
750
|
cli_command = normalized(args.cli_command or shutil.which("foggy-runtime") or "")
|
|
618
751
|
if not cli_command.is_file():
|
|
619
752
|
raise OnboardingError("--skip-cli-install requires --cli-command or foggy-runtime on PATH")
|
|
620
753
|
cli_mode = "external"
|
|
621
754
|
else:
|
|
755
|
+
progress.update("cli", 1, "Installing CLI", fraction=0.65, current_file="Python virtual environment")
|
|
622
756
|
python_path = venv_python(install_root)
|
|
623
757
|
if not python_path.is_file():
|
|
624
758
|
venv.EnvBuilder(with_pip=True).create(install_root / "venv")
|
|
@@ -633,27 +767,65 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
633
767
|
)
|
|
634
768
|
cli_command = venv_cli(install_root)
|
|
635
769
|
cli_mode = "managed-venv"
|
|
770
|
+
progress.update("cli", 1, "Verifying CLI version", fraction=0.9, current_file="foggy-runtime")
|
|
636
771
|
cli_version = command_result([str(cli_command), "--version"], check=True)
|
|
637
772
|
actual_cli_version = version_tuple(cli_version["stdout"])[:3]
|
|
638
773
|
pinned_cli_version = version_tuple(cli_component["version"])[:3]
|
|
639
774
|
version_matches = actual_cli_version >= pinned_cli_version if cli_mode == "external" else actual_cli_version == pinned_cli_version
|
|
640
775
|
if not version_matches:
|
|
641
776
|
raise OnboardingError(f"Unexpected CLI version: {cli_version['stdout']}")
|
|
777
|
+
progress.update("cli", 1, "CLI ready", fraction=1.0)
|
|
642
778
|
|
|
643
779
|
launcher_dir = install_root / "launcher"
|
|
644
|
-
|
|
645
|
-
|
|
780
|
+
launcher_assets = components["launcher"]["assets"]
|
|
781
|
+
for index, asset in enumerate(launcher_assets):
|
|
782
|
+
progress.update(
|
|
783
|
+
"launcher", 2, "Downloading and verifying Launcher",
|
|
784
|
+
fraction=index / len(launcher_assets), current_file=asset["file"],
|
|
785
|
+
completed_files=index, total_files=len(launcher_assets),
|
|
786
|
+
)
|
|
787
|
+
verified.append(materialize(
|
|
788
|
+
asset, launcher_dir / asset["file"], cache_dirs,
|
|
789
|
+
progress=progress, progress_phase="launcher", progress_step=2,
|
|
790
|
+
progress_index=index, progress_total=len(launcher_assets),
|
|
791
|
+
progress_message="Downloading and verifying Launcher",
|
|
792
|
+
))
|
|
793
|
+
progress.update(
|
|
794
|
+
"launcher", 2, "Downloading and verifying Launcher",
|
|
795
|
+
fraction=(index + 1) / len(launcher_assets), current_file=asset["file"],
|
|
796
|
+
completed_files=index + 1, total_files=len(launcher_assets),
|
|
797
|
+
)
|
|
646
798
|
if os.name != "nt":
|
|
647
799
|
(launcher_dir / "start-foggy-runtime.sh").chmod(0o755)
|
|
800
|
+
progress.update("launcher", 2, "Launcher ready", fraction=1.0)
|
|
648
801
|
|
|
649
802
|
analysis_assets = components["analysisSkill"]["assets"]
|
|
650
|
-
for asset in analysis_assets:
|
|
651
|
-
|
|
803
|
+
for index, asset in enumerate(analysis_assets):
|
|
804
|
+
progress.update(
|
|
805
|
+
"analysis-skill", 3, "Downloading and verifying analysis Skill",
|
|
806
|
+
fraction=index / len(analysis_assets), current_file=asset["file"],
|
|
807
|
+
completed_files=index, total_files=len(analysis_assets),
|
|
808
|
+
)
|
|
809
|
+
verified.append(materialize(
|
|
810
|
+
asset, downloads / "skill" / asset["file"], cache_dirs,
|
|
811
|
+
progress=progress, progress_phase="analysis-skill", progress_step=3,
|
|
812
|
+
progress_index=index, progress_total=len(analysis_assets),
|
|
813
|
+
progress_message="Downloading and verifying analysis Skill",
|
|
814
|
+
))
|
|
815
|
+
progress.update(
|
|
816
|
+
"analysis-skill", 3, "Downloading and verifying analysis Skill",
|
|
817
|
+
fraction=(index + 1) / len(analysis_assets), current_file=asset["file"],
|
|
818
|
+
completed_files=index + 1, total_files=len(analysis_assets),
|
|
819
|
+
)
|
|
652
820
|
zip_asset = next(item for item in analysis_assets if item["role"] == "zip")
|
|
821
|
+
progress.update("analysis-skill", 3, "Installing analysis Skill", fraction=0.9, current_file=zip_asset["file"])
|
|
653
822
|
analysis_skill = install_analysis_skill(
|
|
654
823
|
downloads / "skill" / zip_asset["file"], project_root, components["analysisSkill"]["version"], zip_asset["sha256"], args.replace_skill
|
|
655
824
|
)
|
|
825
|
+
progress.update("analysis-skill", 3, "Analysis Skill ready", fraction=1.0)
|
|
826
|
+
progress.update("workspace-skills", 4, "Installing onboarding Skill", fraction=0.1)
|
|
656
827
|
onboarding_skill = install_onboarding_skill(project_root)
|
|
828
|
+
progress.update("workspace-skills", 4, "Workspace Skills ready", fraction=1.0)
|
|
657
829
|
state = {
|
|
658
830
|
"schemaVersion": STATE_SCHEMA,
|
|
659
831
|
"installedAt": now_utc(),
|
|
@@ -668,7 +840,10 @@ def install_command(args: argparse.Namespace) -> dict:
|
|
|
668
840
|
"securityMode": versions["defaults"]["securityMode"],
|
|
669
841
|
"productionReady": False,
|
|
670
842
|
}
|
|
843
|
+
progress.update("state", 5, "Writing install state", fraction=0.2, current_file="install-state.json")
|
|
671
844
|
atomic_json(install_root / "install-state.json", state)
|
|
845
|
+
progress.finish()
|
|
846
|
+
ACTIVE_PROGRESS = None
|
|
672
847
|
return {
|
|
673
848
|
"success": True,
|
|
674
849
|
"schemaVersion": "foggy-deepseek-onboarding-install-result/v1",
|
|
@@ -1945,6 +2120,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1945
2120
|
install.add_argument("--replace-skill", action="store_true")
|
|
1946
2121
|
install.add_argument("--skip-cli-install", action="store_true")
|
|
1947
2122
|
install.add_argument("--cli-command")
|
|
2123
|
+
install.add_argument("--progress-file")
|
|
2124
|
+
install.add_argument("--operation-id")
|
|
2125
|
+
install.add_argument("--operation-kind", default="initialize")
|
|
1948
2126
|
install.add_argument("--dry-run", action="store_true")
|
|
1949
2127
|
install.set_defaults(handler=install_command)
|
|
1950
2128
|
|
|
@@ -2104,8 +2282,12 @@ def main() -> None:
|
|
|
2104
2282
|
exit_code = 1
|
|
2105
2283
|
emit(result, exit_code)
|
|
2106
2284
|
except OnboardingError as exc:
|
|
2285
|
+
if ACTIVE_PROGRESS is not None:
|
|
2286
|
+
ACTIVE_PROGRESS.fail(exc)
|
|
2107
2287
|
emit({"success": False, "error": {"code": "ONBOARDING_ERROR", "message": str(exc)}, "productionReady": False}, 1)
|
|
2108
2288
|
except Exception as exc:
|
|
2289
|
+
if ACTIVE_PROGRESS is not None:
|
|
2290
|
+
ACTIVE_PROGRESS.fail(exc)
|
|
2109
2291
|
emit({"success": False, "error": {"code": "UNEXPECTED_ERROR", "message": str(exc)}, "productionReady": False}, 1)
|
|
2110
2292
|
|
|
2111
2293
|
|