@foggy-projects/deepseek-harness-plugin 0.4.0-beta.8 → 0.4.0-rc.1
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 +55 -20
- package/docs/PUBLIC-BETA-READINESS.md +73 -15
- package/docs/RELEASE-CANDIDATE-0.4.0-RC.1.md +61 -0
- package/docs/WINDOWS-BETA-ACCEPTANCE.md +56 -22
- package/experience/linux/prepare.sh +2 -2
- package/lib/atomic-json.js +41 -0
- package/lib/client.js +239 -26
- package/lib/diagnostics.js +86 -0
- package/lib/index.js +97 -20
- package/lib/remote-descriptor.js +15 -2
- package/lib/runtime-settings.js +89 -0
- package/package.json +11 -10
- package/skills/foggy-deepseek-onboarding/SKILL.md +126 -127
- package/skills/foggy-deepseek-onboarding/assets/connection.schema.json +6 -8
- package/skills/foggy-deepseek-onboarding/assets/datasource.example.json +3 -2
- package/skills/foggy-deepseek-onboarding/assets/env.example +4 -4
- package/skills/foggy-deepseek-onboarding/assets/onboarding-state.schema.json +30 -1
- package/skills/foggy-deepseek-onboarding/assets/versions.json +18 -18
- package/skills/foggy-deepseek-onboarding/references/onboarding-workflow.md +134 -122
- package/skills/foggy-deepseek-onboarding/scripts/invoke-onboarding.ps1 +2 -0
- package/skills/foggy-deepseek-onboarding/scripts/onboarding.py +668 -48
package/lib/client.js
CHANGED
|
@@ -8,25 +8,47 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
|
|
9
9
|
const NS = 'settings.foggy'
|
|
10
10
|
const passthroughSchema = { parse: (value) => value }
|
|
11
|
+
const runtimeSettingsInputSchema = {
|
|
12
|
+
parse: (value) => {
|
|
13
|
+
const port = value?.port
|
|
14
|
+
if (!Number.isInteger(port) || port < 1024 || port > 65535 || Object.keys(value || {}).some((key) => key !== 'port')) {
|
|
15
|
+
throw new Error('Runtime port must be an integer between 1024 and 65535')
|
|
16
|
+
}
|
|
17
|
+
return { port }
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
const descriptor = (method, parameters = []) => ({
|
|
21
|
+
id: `@foggy-projects/deepseek-harness-plugin#foggyIntegration/${method}`,
|
|
22
|
+
service: 'foggyIntegration',
|
|
23
|
+
namespace: 'foggyIntegration',
|
|
24
|
+
method,
|
|
25
|
+
invocation: { kind: 'direct' },
|
|
26
|
+
parameters,
|
|
27
|
+
result: {
|
|
28
|
+
mode: 'strict',
|
|
29
|
+
typeSymbol: '@foggy-projects/deepseek-harness-plugin#FoggyIntegrationResult',
|
|
30
|
+
schema: passthroughSchema,
|
|
31
|
+
},
|
|
32
|
+
sourceLocation: { file: 'lib/index.js', line: 1, column: 1 },
|
|
33
|
+
})
|
|
11
34
|
const TYPERT_REMOTE = {
|
|
12
35
|
package: '@foggy-projects/deepseek-harness-plugin',
|
|
13
36
|
descriptors: [
|
|
37
|
+
...[
|
|
14
38
|
'status', 'plan', 'initialize', 'repair', 'repairPython', 'repairCli', 'repairLauncher', 'repairAnalysisSkill',
|
|
15
39
|
'migrateProfiles', 'diagnostics', 'runtimeStart', 'runtimeStop',
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
sourceLocation: { file: 'lib/index.js', line: 1, column: 1 },
|
|
29
|
-
})),
|
|
40
|
+
].map((method) => descriptor(method)),
|
|
41
|
+
descriptor('saveRuntimeSettings', [{
|
|
42
|
+
name: 'input',
|
|
43
|
+
wire: 'input',
|
|
44
|
+
source: 'json',
|
|
45
|
+
codec: {
|
|
46
|
+
mode: 'strict',
|
|
47
|
+
typeSymbol: '@foggy-projects/deepseek-harness-plugin#FoggyRuntimeSettingsInput',
|
|
48
|
+
schema: runtimeSettingsInputSchema,
|
|
49
|
+
},
|
|
50
|
+
}]),
|
|
51
|
+
],
|
|
30
52
|
}
|
|
31
53
|
|
|
32
54
|
const zh = {
|
|
@@ -73,7 +95,16 @@ window.__ModuleLoader__.load({
|
|
|
73
95
|
progressSkills: '注册 DSH Skills',
|
|
74
96
|
progressState: '写入安装状态',
|
|
75
97
|
progressComplete: '初始化完成',
|
|
98
|
+
progressRuntimePreflight: '检查 Runtime 状态',
|
|
99
|
+
progressRuntimeLauncher: '启动 Java Launcher',
|
|
100
|
+
progressRuntimeReadiness: '等待 Runtime 就绪',
|
|
101
|
+
progressRuntimeCapabilities: '验证 Runtime 能力',
|
|
102
|
+
progressRuntimeState: '保存 Runtime 状态',
|
|
103
|
+
progressRuntimeComplete: 'Runtime 已就绪',
|
|
76
104
|
progressFiles: '文件',
|
|
105
|
+
progressElapsed: '已等待',
|
|
106
|
+
progressTimeout: '最长',
|
|
107
|
+
progressSeconds: '秒',
|
|
77
108
|
managedPrivate: 'Foggy 私有',
|
|
78
109
|
configuredOverride: '外部指定',
|
|
79
110
|
accepted: '操作已开始;页面会自动刷新状态。',
|
|
@@ -90,6 +121,10 @@ window.__ModuleLoader__.load({
|
|
|
90
121
|
noOnboarding: '尚无数据库引导记录;请在对话中调用 Foggy onboarding Skill。',
|
|
91
122
|
projectRoot: '工作区',
|
|
92
123
|
resumeInChat: '未完成步骤应回到对应工作区,在对话中继续。',
|
|
124
|
+
queryWarningTitle: '查询输入已自动兼容',
|
|
125
|
+
queryWarningSummary: 'Runtime 在 WARN 模式下忽略了未知的查询属性,查询仍已执行。请检查告警代码与证据后决定是否修正模型或请求。',
|
|
126
|
+
queryWarningCodes: '告警代码',
|
|
127
|
+
queryWarningEvidence: '证据目录',
|
|
93
128
|
stepPlanned: '规划',
|
|
94
129
|
stepDatasourceConfigured: '数据库连接',
|
|
95
130
|
stepDatasourceVerified: '连接验证',
|
|
@@ -99,6 +134,21 @@ window.__ModuleLoader__.load({
|
|
|
99
134
|
stepSemanticPublished: '发布',
|
|
100
135
|
stepSemanticVerified: '首次查询',
|
|
101
136
|
runtimeUrl: 'Runtime 地址',
|
|
137
|
+
runtimeSettingsTitle: 'Runtime 连接设置',
|
|
138
|
+
runtimePort: 'Runtime 端口',
|
|
139
|
+
runtimePortHint: '端口会持久保存,CLI 与 Skill 将使用同一个 Runtime 地址。',
|
|
140
|
+
configuredRuntimeUrl: '下次启动地址',
|
|
141
|
+
saveRuntimePort: '保存端口',
|
|
142
|
+
runtimePortSaved: 'Runtime 端口已保存',
|
|
143
|
+
stopToChangePort: 'Runtime 运行时不能修改端口,请先停止 Runtime。',
|
|
144
|
+
errorTitle: 'Foggy 操作失败',
|
|
145
|
+
portUnavailableTitle: 'Runtime 端口被占用',
|
|
146
|
+
portUnavailableMessage: '已被其他程序或 Windows 端口代理占用,Runtime 无法启动。',
|
|
147
|
+
portUnavailableHelp: '请更换端口后保存并重新启动;也可以先释放占用该端口的程序或 Windows 端口代理。',
|
|
148
|
+
portInvalidTitle: '端口配置无效',
|
|
149
|
+
portInvalidMessage: '当前值无法保存为 Runtime 端口。',
|
|
150
|
+
portInvalidHelp: '请输入 1024 到 65535 之间的整数端口。',
|
|
151
|
+
settingsInvalidTitle: 'Runtime 设置文件异常',
|
|
102
152
|
nextTitle: '后续配置',
|
|
103
153
|
nextCopy: 'Skills 已通过 DeepSeek Harness 原生注册表提供给所有工作区;每个会话直接使用自己的工作目录。Runtime 启动成功后,将继续进入数据库连接与语义层向导。',
|
|
104
154
|
beta: 'Beta',
|
|
@@ -148,7 +198,16 @@ window.__ModuleLoader__.load({
|
|
|
148
198
|
progressSkills: 'Registering DSH Skills',
|
|
149
199
|
progressState: 'Writing install state',
|
|
150
200
|
progressComplete: 'Initialization complete',
|
|
201
|
+
progressRuntimePreflight: 'Checking Runtime state',
|
|
202
|
+
progressRuntimeLauncher: 'Starting Java Launcher',
|
|
203
|
+
progressRuntimeReadiness: 'Waiting for Runtime readiness',
|
|
204
|
+
progressRuntimeCapabilities: 'Verifying Runtime capabilities',
|
|
205
|
+
progressRuntimeState: 'Saving Runtime state',
|
|
206
|
+
progressRuntimeComplete: 'Runtime ready',
|
|
151
207
|
progressFiles: 'files',
|
|
208
|
+
progressElapsed: 'elapsed',
|
|
209
|
+
progressTimeout: 'timeout',
|
|
210
|
+
progressSeconds: 's',
|
|
152
211
|
managedPrivate: 'Foggy private',
|
|
153
212
|
configuredOverride: 'Configured override',
|
|
154
213
|
accepted: 'The operation started; this page will refresh automatically.',
|
|
@@ -165,6 +224,10 @@ window.__ModuleLoader__.load({
|
|
|
165
224
|
noOnboarding: 'No database onboarding record yet. Invoke the Foggy onboarding Skill in a conversation.',
|
|
166
225
|
projectRoot: 'Workspace',
|
|
167
226
|
resumeInChat: 'Resume incomplete steps from a conversation in the matching workspace.',
|
|
227
|
+
queryWarningTitle: 'Query input was normalized',
|
|
228
|
+
queryWarningSummary: 'In WARN mode, Runtime ignored unknown query properties and continued. Review the warning codes and evidence before deciding whether to change the model or request.',
|
|
229
|
+
queryWarningCodes: 'Warning codes',
|
|
230
|
+
queryWarningEvidence: 'Evidence directory',
|
|
168
231
|
stepPlanned: 'Plan',
|
|
169
232
|
stepDatasourceConfigured: 'Database connection',
|
|
170
233
|
stepDatasourceVerified: 'Connection test',
|
|
@@ -174,6 +237,21 @@ window.__ModuleLoader__.load({
|
|
|
174
237
|
stepSemanticPublished: 'Publish',
|
|
175
238
|
stepSemanticVerified: 'First query',
|
|
176
239
|
runtimeUrl: 'Runtime URL',
|
|
240
|
+
runtimeSettingsTitle: 'Runtime connection settings',
|
|
241
|
+
runtimePort: 'Runtime port',
|
|
242
|
+
runtimePortHint: 'The port is persisted so the CLI and Skills use one stable Runtime URL.',
|
|
243
|
+
configuredRuntimeUrl: 'Next startup URL',
|
|
244
|
+
saveRuntimePort: 'Save port',
|
|
245
|
+
runtimePortSaved: 'Runtime port saved',
|
|
246
|
+
stopToChangePort: 'Stop Runtime before changing its port.',
|
|
247
|
+
errorTitle: 'Foggy operation failed',
|
|
248
|
+
portUnavailableTitle: 'Runtime port is already in use',
|
|
249
|
+
portUnavailableMessage: 'is already used by another application or Windows port proxy, so Runtime cannot start.',
|
|
250
|
+
portUnavailableHelp: 'Save a different port and start again, or release the application or Windows port proxy using this port.',
|
|
251
|
+
portInvalidTitle: 'Invalid port setting',
|
|
252
|
+
portInvalidMessage: 'The current value cannot be saved as the Runtime port.',
|
|
253
|
+
portInvalidHelp: 'Enter an integer port between 1024 and 65535.',
|
|
254
|
+
settingsInvalidTitle: 'Runtime settings file is invalid',
|
|
177
255
|
nextTitle: 'Next configuration',
|
|
178
256
|
nextCopy: 'Skills are provided to every workspace through the native DeepSeek Harness registry, and each session uses its own working directory. After Runtime starts, the database connection and semantic-layer wizard comes next.',
|
|
179
257
|
beta: 'Beta',
|
|
@@ -188,8 +266,10 @@ window.__ModuleLoader__.load({
|
|
|
188
266
|
.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}
|
|
189
267
|
.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}
|
|
190
268
|
.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}
|
|
191
|
-
.foggy-
|
|
192
|
-
|
|
269
|
+
.foggy-runtime-settings{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:14px}.foggy-runtime-settings h4{margin:0;font-size:13px}.foggy-runtime-settings-row{display:grid;grid-template-columns:minmax(150px,220px) minmax(0,1fr) auto;align-items:end;gap:10px;margin-top:10px}.foggy-field{display:flex;flex-direction:column;gap:6px}.foggy-field label{font-size:12px;font-weight:600}.foggy-input{width:100%;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-variant-numeric:tabular-nums;padding:8px 10px;outline:none}.foggy-input:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-state-business-primary) 18%,transparent)}.foggy-input[aria-invalid=true]{border-color:var(--dsw-alias-state-error-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-state-error-primary) 14%,transparent)}.foggy-setting-meta{display:flex;flex-direction:column;gap:4px;min-width:0;color:var(--dsw-alias-label-tertiary);font-size:11px}.foggy-setting-meta code{overflow-wrap:anywhere;color:var(--dsw-alias-label-secondary)}
|
|
270
|
+
.foggy-error-panel{border:2px solid var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,var(--dsw-alias-bg-layer-3));border-radius:10px;padding:13px 14px;box-shadow:0 6px 18px color-mix(in srgb,var(--dsw-alias-state-error-primary) 13%,transparent)}.foggy-error-panel-head{display:flex;align-items:center;gap:8px;color:var(--dsw-alias-state-error-primary);font-size:14px;font-weight:700}.foggy-error-icon{display:grid;place-items:center;width:22px;height:22px;border-radius:50%;background:var(--dsw-alias-state-error-primary);color:white;font-size:14px}.foggy-error-panel p{margin:7px 0 0;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;overflow-wrap:anywhere}.foggy-error-help{color:var(--dsw-alias-state-error-primary)!important;font-weight:600}
|
|
271
|
+
.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,.foggy-section{border-top:1px solid var(--dsw-alias-border-l2);padding-top:14px}.foggy-paths h4,.foggy-next h4,.foggy-section 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,.foggy-section p{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}.foggy-section-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}.foggy-onboarding-head{display:flex;justify-content:space-between;gap:12px;align-items:start}.foggy-onboarding-head code{font-size:11px;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere}.foggy-step-list{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:7px;margin-top:10px}.foggy-step{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px;font-size:11px;color:var(--dsw-alias-label-tertiary)}.foggy-step[data-state=completed]{border-color:color-mix(in srgb,var(--dsw-alias-state-success-primary) 45%,var(--dsw-alias-border-l2));color:var(--dsw-alias-state-success-primary)}.foggy-step[data-state=failed],.foggy-step[data-state=invalid]{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.foggy-warning{border:1px solid color-mix(in srgb,var(--dsw-alias-state-warning-primary) 55%,var(--dsw-alias-border-l2));background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 8%,transparent);border-radius:10px;padding:12px}.foggy-query-warning{margin-top:10px}.foggy-query-warning p+p{margin-top:5px}.foggy-query-warning code{overflow-wrap:anywhere}
|
|
272
|
+
@media (width<=680px){.foggy-grid{grid-template-columns:minmax(0,1fr)}.foggy-runtime-settings-row{grid-template-columns:minmax(0,1fr)}.foggy-runtime-settings-row .foggy-button{width:100%}}
|
|
193
273
|
`
|
|
194
274
|
|
|
195
275
|
if (typeof document !== 'undefined' && !document.querySelector('style[data-foggy-settings]')) {
|
|
@@ -247,21 +327,33 @@ window.__ModuleLoader__.load({
|
|
|
247
327
|
'workspace-skills': 'progressSkills',
|
|
248
328
|
state: 'progressState',
|
|
249
329
|
complete: 'progressComplete',
|
|
330
|
+
'runtime-preflight': 'progressRuntimePreflight',
|
|
331
|
+
'runtime-launcher': 'progressRuntimeLauncher',
|
|
332
|
+
'runtime-readiness': 'progressRuntimeReadiness',
|
|
333
|
+
'runtime-capabilities': 'progressRuntimeCapabilities',
|
|
334
|
+
'runtime-state': 'progressRuntimeState',
|
|
335
|
+
'runtime-complete': 'progressRuntimeComplete',
|
|
250
336
|
}
|
|
251
337
|
const phase = t(phaseKeys[progress.phase] || 'progressWorking')
|
|
338
|
+
const headline = String(progress.phase || '').startsWith('runtime-') ? phase : progress.message || phase
|
|
252
339
|
const step = progress.step?.index && progress.step?.total
|
|
253
340
|
? `${progress.step.index}/${progress.step.total}`
|
|
254
341
|
: ''
|
|
255
342
|
const files = progress.files?.total
|
|
256
343
|
? `${progress.files.completed || 0}/${progress.files.total} ${t('progressFiles')}`
|
|
257
344
|
: ''
|
|
345
|
+
const elapsed = Number(progress.timing?.elapsedSeconds)
|
|
346
|
+
const timeout = Number(progress.timing?.timeoutSeconds)
|
|
347
|
+
const timing = Number.isFinite(elapsed)
|
|
348
|
+
? `${t('progressElapsed')} ${elapsed}${t('progressSeconds')}${Number.isFinite(timeout) ? ` / ${t('progressTimeout')} ${timeout}${t('progressSeconds')}` : ''}`
|
|
349
|
+
: ''
|
|
258
350
|
return jsxs('div', {
|
|
259
351
|
className: 'foggy-progress',
|
|
260
352
|
role: 'status',
|
|
261
353
|
'aria-live': 'polite',
|
|
262
354
|
children: [
|
|
263
355
|
jsxs('div', { className: 'foggy-progress-head', children: [
|
|
264
|
-
jsx('strong', { children:
|
|
356
|
+
jsx('strong', { children: headline }),
|
|
265
357
|
jsx('span', { className: 'foggy-progress-percent', children: `${percent}%` }),
|
|
266
358
|
] }),
|
|
267
359
|
jsx('div', {
|
|
@@ -275,7 +367,7 @@ window.__ModuleLoader__.load({
|
|
|
275
367
|
}),
|
|
276
368
|
jsxs('div', { className: 'foggy-progress-meta', children: [
|
|
277
369
|
jsx('span', { children: phase }),
|
|
278
|
-
jsx('span', { children: [step, files].filter(Boolean).join(' · ') }),
|
|
370
|
+
jsx('span', { children: [step, files, timing].filter(Boolean).join(' · ') }),
|
|
279
371
|
] }),
|
|
280
372
|
progress.currentFile ? jsx('div', { className: 'foggy-progress-meta foggy-progress-file', title: progress.currentFile, children: progress.currentFile }) : null,
|
|
281
373
|
],
|
|
@@ -313,22 +405,54 @@ window.__ModuleLoader__.load({
|
|
|
313
405
|
title: profile.steps?.[name]?.status || 'pending',
|
|
314
406
|
children: t(label),
|
|
315
407
|
}, name)) }),
|
|
408
|
+
profile.warningCount > 0 ? jsxs('div', { className: 'foggy-warning foggy-query-warning', role: 'status', children: [
|
|
409
|
+
jsx('h4', { children: `${t('queryWarningTitle')} · ${profile.warningCount}` }),
|
|
410
|
+
jsx('p', { children: t('queryWarningSummary') }),
|
|
411
|
+
profile.warningCodes?.length ? jsxs('p', { children: [`${t('queryWarningCodes')}: `, jsx('code', { children: profile.warningCodes.join(', ') })] }) : null,
|
|
412
|
+
profile.warningEvidence ? jsxs('p', { children: [`${t('queryWarningEvidence')}: `, jsx('code', { children: profile.warningEvidence })] }) : null,
|
|
413
|
+
] }) : null,
|
|
316
414
|
profile.next?.status !== 'completed' ? jsx('p', { style: { marginTop: '9px' }, children: t('resumeInChat') }) : null,
|
|
317
415
|
] })
|
|
318
416
|
}
|
|
319
417
|
|
|
418
|
+
function errorPanel(title, message, help) {
|
|
419
|
+
if (!message) return null
|
|
420
|
+
return jsxs('div', {
|
|
421
|
+
className: 'foggy-error-panel',
|
|
422
|
+
role: 'alert',
|
|
423
|
+
'aria-live': 'assertive',
|
|
424
|
+
children: [
|
|
425
|
+
jsxs('div', { className: 'foggy-error-panel-head', children: [
|
|
426
|
+
jsx('span', { className: 'foggy-error-icon', 'aria-hidden': true, children: '!' }),
|
|
427
|
+
jsx('strong', { children: title }),
|
|
428
|
+
] }),
|
|
429
|
+
jsx('p', { children: message }),
|
|
430
|
+
help ? jsx('p', { className: 'foggy-error-help', children: help }) : null,
|
|
431
|
+
],
|
|
432
|
+
})
|
|
433
|
+
}
|
|
434
|
+
|
|
320
435
|
function FoggySettingsTab({ api, t }) {
|
|
321
|
-
const [view, setView] = useState({ phase: 'loading', status: null, message: '', error: false })
|
|
436
|
+
const [view, setView] = useState({ phase: 'loading', status: null, message: '', error: false, errorCode: null })
|
|
437
|
+
const [portDraft, setPortDraft] = useState('')
|
|
438
|
+
const [portDirty, setPortDirty] = useState(false)
|
|
322
439
|
|
|
323
440
|
const refresh = useCallback(async () => {
|
|
324
441
|
setView((current) => ({ ...current, phase: current.status ? 'ready' : 'loading', error: false }))
|
|
325
442
|
try {
|
|
326
443
|
const status = unwrap(await api.status(), 'status')
|
|
327
|
-
setView((current) => ({
|
|
444
|
+
setView((current) => ({
|
|
445
|
+
...current,
|
|
446
|
+
phase: 'ready',
|
|
447
|
+
status,
|
|
448
|
+
error: false,
|
|
449
|
+
message: current.message === t('accepted') && status.operation?.state !== 'running' ? '' : current.message,
|
|
450
|
+
}))
|
|
451
|
+
if (!portDirty) setPortDraft(String(status.runtimeSettings?.port ?? 18166))
|
|
328
452
|
} catch (error) {
|
|
329
453
|
setView((current) => ({ ...current, phase: current.status ? 'ready' : 'error', message: String(error.message || error), error: true }))
|
|
330
454
|
}
|
|
331
|
-
}, [api])
|
|
455
|
+
}, [api, portDirty, t])
|
|
332
456
|
|
|
333
457
|
useEffect(() => { refresh() }, [refresh])
|
|
334
458
|
useEffect(() => {
|
|
@@ -338,18 +462,40 @@ window.__ModuleLoader__.load({
|
|
|
338
462
|
}, [refresh, view.status?.operation?.state])
|
|
339
463
|
|
|
340
464
|
const run = async (method) => {
|
|
341
|
-
setView((current) => ({ ...current, message: t('working'), error: false }))
|
|
465
|
+
setView((current) => ({ ...current, message: t('working'), error: false, errorCode: null }))
|
|
342
466
|
try {
|
|
343
467
|
const result = unwrap(await api[method](), method)
|
|
344
468
|
const message = result.accepted
|
|
345
469
|
? t('accepted')
|
|
346
470
|
: result.path
|
|
347
471
|
? `${t('diagnosticsSaved')}: ${result.path}`
|
|
348
|
-
: ''
|
|
349
|
-
setView((current) => ({ ...current, message, error: result.success === false }))
|
|
472
|
+
: result.error?.message || ''
|
|
473
|
+
setView((current) => ({ ...current, message, error: result.success === false, errorCode: result.error?.code || null }))
|
|
350
474
|
await refresh()
|
|
351
475
|
} catch (error) {
|
|
352
|
-
setView((current) => ({ ...current, message: `${t('actionFailed')}: ${String(error.message || error)}`, error: true }))
|
|
476
|
+
setView((current) => ({ ...current, message: `${t('actionFailed')}: ${String(error.message || error)}`, error: true, errorCode: 'REMOTE_ERROR' }))
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const saveRuntimePort = async () => {
|
|
481
|
+
const port = Number(portDraft)
|
|
482
|
+
if (!Number.isInteger(port) || port < 1024 || port > 65535) {
|
|
483
|
+
setView((current) => ({ ...current, message: t('portInvalidMessage'), error: true, errorCode: 'RUNTIME_PORT_INVALID' }))
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
setView((current) => ({ ...current, message: t('working'), error: false, errorCode: null }))
|
|
487
|
+
try {
|
|
488
|
+
const result = unwrap(await api.saveRuntimeSettings({ port }), 'saveRuntimeSettings')
|
|
489
|
+
if (result.success === false) {
|
|
490
|
+
setView((current) => ({ ...current, message: result.error?.message || t('actionFailed'), error: true, errorCode: result.error?.code || null }))
|
|
491
|
+
return
|
|
492
|
+
}
|
|
493
|
+
setPortDirty(false)
|
|
494
|
+
setPortDraft(String(result.settings.port))
|
|
495
|
+
setView((current) => ({ ...current, message: `${t('runtimePortSaved')}: ${result.settings.runtimeUrl}`, error: false, errorCode: null }))
|
|
496
|
+
await refresh()
|
|
497
|
+
} catch (error) {
|
|
498
|
+
setView((current) => ({ ...current, message: `${t('actionFailed')}: ${String(error.message || error)}`, error: true, errorCode: 'REMOTE_ERROR' }))
|
|
353
499
|
}
|
|
354
500
|
}
|
|
355
501
|
|
|
@@ -361,9 +507,38 @@ window.__ModuleLoader__.load({
|
|
|
361
507
|
|
|
362
508
|
const status = view.status
|
|
363
509
|
const busy = status.operation?.state === 'running'
|
|
510
|
+
const portNumber = Number(portDraft)
|
|
511
|
+
const portValid = Number.isInteger(portNumber) && portNumber >= 1024 && portNumber <= 65535
|
|
364
512
|
const operationError = status.operation?.state === 'failed'
|
|
365
513
|
? status.operation.error || status.operation.result?.error?.message || t('actionFailed')
|
|
366
514
|
: ''
|
|
515
|
+
const operationErrorCode = status.operation?.state === 'failed' ? status.operation.result?.error?.code || null : null
|
|
516
|
+
const settingsError = status.runtimeSettings?.valid === false
|
|
517
|
+
? status.runtimeSettings.error || t('settingsInvalidTitle')
|
|
518
|
+
: ''
|
|
519
|
+
const portConflict = status.runtimeSettings?.conflictApplies ? status.runtimeSettings.lastConflict : null
|
|
520
|
+
const validationError = portDirty && !portValid ? t('portInvalidMessage') : ''
|
|
521
|
+
const rawAlertMessage = validationError || (view.error ? view.message : '') || operationError || settingsError || portConflict?.message || ''
|
|
522
|
+
const alertCode = validationError
|
|
523
|
+
? 'RUNTIME_PORT_INVALID'
|
|
524
|
+
: view.error
|
|
525
|
+
? view.errorCode
|
|
526
|
+
: operationErrorCode || (settingsError ? 'RUNTIME_SETTINGS_INVALID' : portConflict ? 'RUNTIME_PORT_UNAVAILABLE' : null)
|
|
527
|
+
const alertTitle = alertCode === 'RUNTIME_PORT_UNAVAILABLE'
|
|
528
|
+
? t('portUnavailableTitle')
|
|
529
|
+
: alertCode === 'RUNTIME_PORT_INVALID'
|
|
530
|
+
? t('portInvalidTitle')
|
|
531
|
+
: alertCode === 'RUNTIME_SETTINGS_INVALID'
|
|
532
|
+
? t('settingsInvalidTitle')
|
|
533
|
+
: t('errorTitle')
|
|
534
|
+
const alertMessage = alertCode === 'RUNTIME_PORT_UNAVAILABLE'
|
|
535
|
+
? `${t('runtimePort')} ${portConflict?.port ?? status.runtimeSettings?.port ?? portNumber} ${t('portUnavailableMessage')}`
|
|
536
|
+
: rawAlertMessage
|
|
537
|
+
const alertHelp = alertCode === 'RUNTIME_PORT_UNAVAILABLE'
|
|
538
|
+
? t('portUnavailableHelp')
|
|
539
|
+
: alertCode === 'RUNTIME_PORT_INVALID'
|
|
540
|
+
? t('portInvalidHelp')
|
|
541
|
+
: ''
|
|
367
542
|
const progress = busy ? progressPanel(status.operation?.progress, t) : null
|
|
368
543
|
const stateLabels = { 'not-installed': 'notInstalled', degraded: 'degraded', ready: 'ready', running: 'running' }
|
|
369
544
|
return jsxs('section', { className: 'foggy-settings', children: [
|
|
@@ -382,8 +557,46 @@ window.__ModuleLoader__.load({
|
|
|
382
557
|
status.running ? jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('runtimeStop'), children: t('stop') }) : null,
|
|
383
558
|
] }),
|
|
384
559
|
] }),
|
|
560
|
+
errorPanel(alertTitle, alertMessage, alertHelp),
|
|
385
561
|
progress,
|
|
386
|
-
busy
|
|
562
|
+
busy ? jsx('p', { className: 'foggy-message', children: t('working') }) : null,
|
|
563
|
+
!busy && view.message && !view.error && !operationError ? jsx('p', { className: 'foggy-message', children: view.message }) : null,
|
|
564
|
+
jsxs('div', { className: 'foggy-runtime-settings', children: [
|
|
565
|
+
jsx('h4', { children: t('runtimeSettingsTitle') }),
|
|
566
|
+
jsxs('div', { className: 'foggy-runtime-settings-row', children: [
|
|
567
|
+
jsxs('div', { className: 'foggy-field', children: [
|
|
568
|
+
jsx('label', { htmlFor: 'foggy-runtime-port', children: t('runtimePort') }),
|
|
569
|
+
jsx('input', {
|
|
570
|
+
id: 'foggy-runtime-port',
|
|
571
|
+
className: 'foggy-input',
|
|
572
|
+
type: 'number',
|
|
573
|
+
min: 1024,
|
|
574
|
+
max: 65535,
|
|
575
|
+
step: 1,
|
|
576
|
+
value: portDraft,
|
|
577
|
+
disabled: busy || status.running,
|
|
578
|
+
'aria-invalid': portDirty && !portValid ? 'true' : undefined,
|
|
579
|
+
onChange: (event) => {
|
|
580
|
+
setPortDraft(event.target.value)
|
|
581
|
+
setPortDirty(true)
|
|
582
|
+
setView((current) => ({ ...current, message: '', error: false, errorCode: null }))
|
|
583
|
+
},
|
|
584
|
+
}),
|
|
585
|
+
] }),
|
|
586
|
+
jsxs('div', { className: 'foggy-setting-meta', children: [
|
|
587
|
+
jsx('span', { children: t('runtimePortHint') }),
|
|
588
|
+
jsx('code', { children: `${t('configuredRuntimeUrl')}: http://127.0.0.1:${portValid ? portNumber : '—'}` }),
|
|
589
|
+
status.running ? jsx('span', { children: t('stopToChangePort') }) : null,
|
|
590
|
+
] }),
|
|
591
|
+
jsx('button', {
|
|
592
|
+
className: 'foggy-button foggy-button-primary',
|
|
593
|
+
type: 'button',
|
|
594
|
+
disabled: busy || status.running || !portDirty || !portValid,
|
|
595
|
+
onClick: saveRuntimePort,
|
|
596
|
+
children: t('saveRuntimePort'),
|
|
597
|
+
}),
|
|
598
|
+
] }),
|
|
599
|
+
] }),
|
|
387
600
|
jsxs('div', { className: 'foggy-grid', children: [
|
|
388
601
|
componentCard(t('python'), status.components.python, 'tool', t),
|
|
389
602
|
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
|
+
}
|