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

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/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: [
14
- 'status', 'plan', 'initialize', 'repair', 'repairPython', 'repairCli', 'repairLauncher', 'repairAnalysisSkill',
37
+ ...[
38
+ 'status', 'plan', 'initialize', 'repair', 'repairPython', 'repairCli', 'repairLauncher', 'repairAnalysisSkill', 'repairSemanticQuerySkill',
15
39
  'migrateProfiles', 'diagnostics', 'runtimeStart', 'runtimeStop',
16
- ].map((method) => ({
17
- id: `@foggy-projects/deepseek-harness-plugin#foggyIntegration/${method}`,
18
- service: 'foggyIntegration',
19
- namespace: 'foggyIntegration',
20
- method,
21
- invocation: { kind: 'direct' },
22
- parameters: [],
23
- result: {
24
- mode: 'strict',
25
- typeSymbol: '@foggy-projects/deepseek-harness-plugin#FoggyIntegrationResult',
26
- schema: passthroughSchema,
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 = {
@@ -44,6 +66,7 @@ window.__ModuleLoader__.load({
44
66
  cli: 'CLI',
45
67
  launcher: 'Launcher',
46
68
  analysisSkill: '分析 Skill',
69
+ semanticQuerySkill: '语义查询 Skill',
47
70
  onboardingSkill: '引导 Skill',
48
71
  skillRegistry: 'DSH Skill 注册',
49
72
  python: 'Python',
@@ -59,6 +82,7 @@ window.__ModuleLoader__.load({
59
82
  repairCli: '检查 / 修复 CLI',
60
83
  repairLauncher: '检查 / 修复 Launcher',
61
84
  repairAnalysisSkill: '检查 / 修复分析 Skill',
85
+ repairSemanticQuerySkill: '检查 / 修复语义查询 Skill',
62
86
  diagnostics: '导出诊断报告',
63
87
  diagnosticsSaved: '诊断报告已保存',
64
88
  start: '启动 Runtime',
@@ -70,10 +94,20 @@ window.__ModuleLoader__.load({
70
94
  progressCli: '准备 CLI',
71
95
  progressLauncher: '准备 Launcher',
72
96
  progressAnalysis: '准备分析 Skill',
97
+ progressSemanticQuery: '准备语义查询 Skill',
73
98
  progressSkills: '注册 DSH Skills',
74
99
  progressState: '写入安装状态',
75
100
  progressComplete: '初始化完成',
101
+ progressRuntimePreflight: '检查 Runtime 状态',
102
+ progressRuntimeLauncher: '启动 Java Launcher',
103
+ progressRuntimeReadiness: '等待 Runtime 就绪',
104
+ progressRuntimeCapabilities: '验证 Runtime 能力',
105
+ progressRuntimeState: '保存 Runtime 状态',
106
+ progressRuntimeComplete: 'Runtime 已就绪',
76
107
  progressFiles: '文件',
108
+ progressElapsed: '已等待',
109
+ progressTimeout: '最长',
110
+ progressSeconds: '秒',
77
111
  managedPrivate: 'Foggy 私有',
78
112
  configuredOverride: '外部指定',
79
113
  accepted: '操作已开始;页面会自动刷新状态。',
@@ -90,6 +124,10 @@ window.__ModuleLoader__.load({
90
124
  noOnboarding: '尚无数据库引导记录;请在对话中调用 Foggy onboarding Skill。',
91
125
  projectRoot: '工作区',
92
126
  resumeInChat: '未完成步骤应回到对应工作区,在对话中继续。',
127
+ queryWarningTitle: '查询输入已自动兼容',
128
+ queryWarningSummary: 'Runtime 在 WARN 模式下忽略了未知的查询属性,查询仍已执行。请检查告警代码与证据后决定是否修正模型或请求。',
129
+ queryWarningCodes: '告警代码',
130
+ queryWarningEvidence: '证据目录',
93
131
  stepPlanned: '规划',
94
132
  stepDatasourceConfigured: '数据库连接',
95
133
  stepDatasourceVerified: '连接验证',
@@ -99,6 +137,21 @@ window.__ModuleLoader__.load({
99
137
  stepSemanticPublished: '发布',
100
138
  stepSemanticVerified: '首次查询',
101
139
  runtimeUrl: 'Runtime 地址',
140
+ runtimeSettingsTitle: 'Runtime 连接设置',
141
+ runtimePort: 'Runtime 端口',
142
+ runtimePortHint: '端口会持久保存,CLI 与 Skill 将使用同一个 Runtime 地址。',
143
+ configuredRuntimeUrl: '下次启动地址',
144
+ saveRuntimePort: '保存端口',
145
+ runtimePortSaved: 'Runtime 端口已保存',
146
+ stopToChangePort: 'Runtime 运行时不能修改端口,请先停止 Runtime。',
147
+ errorTitle: 'Foggy 操作失败',
148
+ portUnavailableTitle: 'Runtime 端口被占用',
149
+ portUnavailableMessage: '已被其他程序或 Windows 端口代理占用,Runtime 无法启动。',
150
+ portUnavailableHelp: '请更换端口后保存并重新启动;也可以先释放占用该端口的程序或 Windows 端口代理。',
151
+ portInvalidTitle: '端口配置无效',
152
+ portInvalidMessage: '当前值无法保存为 Runtime 端口。',
153
+ portInvalidHelp: '请输入 1024 到 65535 之间的整数端口。',
154
+ settingsInvalidTitle: 'Runtime 设置文件异常',
102
155
  nextTitle: '后续配置',
103
156
  nextCopy: 'Skills 已通过 DeepSeek Harness 原生注册表提供给所有工作区;每个会话直接使用自己的工作目录。Runtime 启动成功后,将继续进入数据库连接与语义层向导。',
104
157
  beta: 'Beta',
@@ -119,6 +172,7 @@ window.__ModuleLoader__.load({
119
172
  cli: 'CLI',
120
173
  launcher: 'Launcher',
121
174
  analysisSkill: 'Analysis Skill',
175
+ semanticQuerySkill: 'Semantic Query Skill',
122
176
  onboardingSkill: 'Onboarding Skill',
123
177
  skillRegistry: 'DSH Skill registry',
124
178
  python: 'Python',
@@ -134,6 +188,7 @@ window.__ModuleLoader__.load({
134
188
  repairCli: 'Check / repair CLI',
135
189
  repairLauncher: 'Check / repair Launcher',
136
190
  repairAnalysisSkill: 'Check / repair analysis Skill',
191
+ repairSemanticQuerySkill: 'Check / repair semantic query Skill',
137
192
  diagnostics: 'Export diagnostics',
138
193
  diagnosticsSaved: 'Diagnostics saved',
139
194
  start: 'Start Runtime',
@@ -145,10 +200,20 @@ window.__ModuleLoader__.load({
145
200
  progressCli: 'Preparing CLI',
146
201
  progressLauncher: 'Preparing Launcher',
147
202
  progressAnalysis: 'Preparing analysis Skill',
203
+ progressSemanticQuery: 'Preparing semantic query Skill',
148
204
  progressSkills: 'Registering DSH Skills',
149
205
  progressState: 'Writing install state',
150
206
  progressComplete: 'Initialization complete',
207
+ progressRuntimePreflight: 'Checking Runtime state',
208
+ progressRuntimeLauncher: 'Starting Java Launcher',
209
+ progressRuntimeReadiness: 'Waiting for Runtime readiness',
210
+ progressRuntimeCapabilities: 'Verifying Runtime capabilities',
211
+ progressRuntimeState: 'Saving Runtime state',
212
+ progressRuntimeComplete: 'Runtime ready',
151
213
  progressFiles: 'files',
214
+ progressElapsed: 'elapsed',
215
+ progressTimeout: 'timeout',
216
+ progressSeconds: 's',
152
217
  managedPrivate: 'Foggy private',
153
218
  configuredOverride: 'Configured override',
154
219
  accepted: 'The operation started; this page will refresh automatically.',
@@ -165,6 +230,10 @@ window.__ModuleLoader__.load({
165
230
  noOnboarding: 'No database onboarding record yet. Invoke the Foggy onboarding Skill in a conversation.',
166
231
  projectRoot: 'Workspace',
167
232
  resumeInChat: 'Resume incomplete steps from a conversation in the matching workspace.',
233
+ queryWarningTitle: 'Query input was normalized',
234
+ 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.',
235
+ queryWarningCodes: 'Warning codes',
236
+ queryWarningEvidence: 'Evidence directory',
168
237
  stepPlanned: 'Plan',
169
238
  stepDatasourceConfigured: 'Database connection',
170
239
  stepDatasourceVerified: 'Connection test',
@@ -174,6 +243,21 @@ window.__ModuleLoader__.load({
174
243
  stepSemanticPublished: 'Publish',
175
244
  stepSemanticVerified: 'First query',
176
245
  runtimeUrl: 'Runtime URL',
246
+ runtimeSettingsTitle: 'Runtime connection settings',
247
+ runtimePort: 'Runtime port',
248
+ runtimePortHint: 'The port is persisted so the CLI and Skills use one stable Runtime URL.',
249
+ configuredRuntimeUrl: 'Next startup URL',
250
+ saveRuntimePort: 'Save port',
251
+ runtimePortSaved: 'Runtime port saved',
252
+ stopToChangePort: 'Stop Runtime before changing its port.',
253
+ errorTitle: 'Foggy operation failed',
254
+ portUnavailableTitle: 'Runtime port is already in use',
255
+ portUnavailableMessage: 'is already used by another application or Windows port proxy, so Runtime cannot start.',
256
+ portUnavailableHelp: 'Save a different port and start again, or release the application or Windows port proxy using this port.',
257
+ portInvalidTitle: 'Invalid port setting',
258
+ portInvalidMessage: 'The current value cannot be saved as the Runtime port.',
259
+ portInvalidHelp: 'Enter an integer port between 1024 and 65535.',
260
+ settingsInvalidTitle: 'Runtime settings file is invalid',
177
261
  nextTitle: 'Next configuration',
178
262
  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
263
  beta: 'Beta',
@@ -188,8 +272,10 @@ window.__ModuleLoader__.load({
188
272
  .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
273
  .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
274
  .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-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}
192
- @media (width<=680px){.foggy-grid{grid-template-columns:minmax(0,1fr)}}
275
+ .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)}
276
+ .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}
277
+ .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}
278
+ @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
279
  `
194
280
 
195
281
  if (typeof document !== 'undefined' && !document.querySelector('style[data-foggy-settings]')) {
@@ -244,24 +330,37 @@ window.__ModuleLoader__.load({
244
330
  cli: 'progressCli',
245
331
  launcher: 'progressLauncher',
246
332
  'analysis-skill': 'progressAnalysis',
333
+ 'semantic-query-skill': 'progressSemanticQuery',
247
334
  'workspace-skills': 'progressSkills',
248
335
  state: 'progressState',
249
336
  complete: 'progressComplete',
337
+ 'runtime-preflight': 'progressRuntimePreflight',
338
+ 'runtime-launcher': 'progressRuntimeLauncher',
339
+ 'runtime-readiness': 'progressRuntimeReadiness',
340
+ 'runtime-capabilities': 'progressRuntimeCapabilities',
341
+ 'runtime-state': 'progressRuntimeState',
342
+ 'runtime-complete': 'progressRuntimeComplete',
250
343
  }
251
344
  const phase = t(phaseKeys[progress.phase] || 'progressWorking')
345
+ const headline = String(progress.phase || '').startsWith('runtime-') ? phase : progress.message || phase
252
346
  const step = progress.step?.index && progress.step?.total
253
347
  ? `${progress.step.index}/${progress.step.total}`
254
348
  : ''
255
349
  const files = progress.files?.total
256
350
  ? `${progress.files.completed || 0}/${progress.files.total} ${t('progressFiles')}`
257
351
  : ''
352
+ const elapsed = Number(progress.timing?.elapsedSeconds)
353
+ const timeout = Number(progress.timing?.timeoutSeconds)
354
+ const timing = Number.isFinite(elapsed)
355
+ ? `${t('progressElapsed')} ${elapsed}${t('progressSeconds')}${Number.isFinite(timeout) ? ` / ${t('progressTimeout')} ${timeout}${t('progressSeconds')}` : ''}`
356
+ : ''
258
357
  return jsxs('div', {
259
358
  className: 'foggy-progress',
260
359
  role: 'status',
261
360
  'aria-live': 'polite',
262
361
  children: [
263
362
  jsxs('div', { className: 'foggy-progress-head', children: [
264
- jsx('strong', { children: progress.message || phase }),
363
+ jsx('strong', { children: headline }),
265
364
  jsx('span', { className: 'foggy-progress-percent', children: `${percent}%` }),
266
365
  ] }),
267
366
  jsx('div', {
@@ -275,7 +374,7 @@ window.__ModuleLoader__.load({
275
374
  }),
276
375
  jsxs('div', { className: 'foggy-progress-meta', children: [
277
376
  jsx('span', { children: phase }),
278
- jsx('span', { children: [step, files].filter(Boolean).join(' · ') }),
377
+ jsx('span', { children: [step, files, timing].filter(Boolean).join(' · ') }),
279
378
  ] }),
280
379
  progress.currentFile ? jsx('div', { className: 'foggy-progress-meta foggy-progress-file', title: progress.currentFile, children: progress.currentFile }) : null,
281
380
  ],
@@ -313,22 +412,54 @@ window.__ModuleLoader__.load({
313
412
  title: profile.steps?.[name]?.status || 'pending',
314
413
  children: t(label),
315
414
  }, name)) }),
415
+ profile.warningCount > 0 ? jsxs('div', { className: 'foggy-warning foggy-query-warning', role: 'status', children: [
416
+ jsx('h4', { children: `${t('queryWarningTitle')} · ${profile.warningCount}` }),
417
+ jsx('p', { children: t('queryWarningSummary') }),
418
+ profile.warningCodes?.length ? jsxs('p', { children: [`${t('queryWarningCodes')}: `, jsx('code', { children: profile.warningCodes.join(', ') })] }) : null,
419
+ profile.warningEvidence ? jsxs('p', { children: [`${t('queryWarningEvidence')}: `, jsx('code', { children: profile.warningEvidence })] }) : null,
420
+ ] }) : null,
316
421
  profile.next?.status !== 'completed' ? jsx('p', { style: { marginTop: '9px' }, children: t('resumeInChat') }) : null,
317
422
  ] })
318
423
  }
319
424
 
425
+ function errorPanel(title, message, help) {
426
+ if (!message) return null
427
+ return jsxs('div', {
428
+ className: 'foggy-error-panel',
429
+ role: 'alert',
430
+ 'aria-live': 'assertive',
431
+ children: [
432
+ jsxs('div', { className: 'foggy-error-panel-head', children: [
433
+ jsx('span', { className: 'foggy-error-icon', 'aria-hidden': true, children: '!' }),
434
+ jsx('strong', { children: title }),
435
+ ] }),
436
+ jsx('p', { children: message }),
437
+ help ? jsx('p', { className: 'foggy-error-help', children: help }) : null,
438
+ ],
439
+ })
440
+ }
441
+
320
442
  function FoggySettingsTab({ api, t }) {
321
- const [view, setView] = useState({ phase: 'loading', status: null, message: '', error: false })
443
+ const [view, setView] = useState({ phase: 'loading', status: null, message: '', error: false, errorCode: null })
444
+ const [portDraft, setPortDraft] = useState('')
445
+ const [portDirty, setPortDirty] = useState(false)
322
446
 
323
447
  const refresh = useCallback(async () => {
324
448
  setView((current) => ({ ...current, phase: current.status ? 'ready' : 'loading', error: false }))
325
449
  try {
326
450
  const status = unwrap(await api.status(), 'status')
327
- setView((current) => ({ ...current, phase: 'ready', status, error: false }))
451
+ setView((current) => ({
452
+ ...current,
453
+ phase: 'ready',
454
+ status,
455
+ error: false,
456
+ message: current.message === t('accepted') && status.operation?.state !== 'running' ? '' : current.message,
457
+ }))
458
+ if (!portDirty) setPortDraft(String(status.runtimeSettings?.port ?? 18166))
328
459
  } catch (error) {
329
460
  setView((current) => ({ ...current, phase: current.status ? 'ready' : 'error', message: String(error.message || error), error: true }))
330
461
  }
331
- }, [api])
462
+ }, [api, portDirty, t])
332
463
 
333
464
  useEffect(() => { refresh() }, [refresh])
334
465
  useEffect(() => {
@@ -338,18 +469,40 @@ window.__ModuleLoader__.load({
338
469
  }, [refresh, view.status?.operation?.state])
339
470
 
340
471
  const run = async (method) => {
341
- setView((current) => ({ ...current, message: t('working'), error: false }))
472
+ setView((current) => ({ ...current, message: t('working'), error: false, errorCode: null }))
342
473
  try {
343
474
  const result = unwrap(await api[method](), method)
344
475
  const message = result.accepted
345
476
  ? t('accepted')
346
477
  : result.path
347
478
  ? `${t('diagnosticsSaved')}: ${result.path}`
348
- : ''
349
- setView((current) => ({ ...current, message, error: result.success === false }))
479
+ : result.error?.message || ''
480
+ setView((current) => ({ ...current, message, error: result.success === false, errorCode: result.error?.code || null }))
350
481
  await refresh()
351
482
  } catch (error) {
352
- setView((current) => ({ ...current, message: `${t('actionFailed')}: ${String(error.message || error)}`, error: true }))
483
+ setView((current) => ({ ...current, message: `${t('actionFailed')}: ${String(error.message || error)}`, error: true, errorCode: 'REMOTE_ERROR' }))
484
+ }
485
+ }
486
+
487
+ const saveRuntimePort = async () => {
488
+ const port = Number(portDraft)
489
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) {
490
+ setView((current) => ({ ...current, message: t('portInvalidMessage'), error: true, errorCode: 'RUNTIME_PORT_INVALID' }))
491
+ return
492
+ }
493
+ setView((current) => ({ ...current, message: t('working'), error: false, errorCode: null }))
494
+ try {
495
+ const result = unwrap(await api.saveRuntimeSettings({ port }), 'saveRuntimeSettings')
496
+ if (result.success === false) {
497
+ setView((current) => ({ ...current, message: result.error?.message || t('actionFailed'), error: true, errorCode: result.error?.code || null }))
498
+ return
499
+ }
500
+ setPortDirty(false)
501
+ setPortDraft(String(result.settings.port))
502
+ setView((current) => ({ ...current, message: `${t('runtimePortSaved')}: ${result.settings.runtimeUrl}`, error: false, errorCode: null }))
503
+ await refresh()
504
+ } catch (error) {
505
+ setView((current) => ({ ...current, message: `${t('actionFailed')}: ${String(error.message || error)}`, error: true, errorCode: 'REMOTE_ERROR' }))
353
506
  }
354
507
  }
355
508
 
@@ -361,9 +514,38 @@ window.__ModuleLoader__.load({
361
514
 
362
515
  const status = view.status
363
516
  const busy = status.operation?.state === 'running'
517
+ const portNumber = Number(portDraft)
518
+ const portValid = Number.isInteger(portNumber) && portNumber >= 1024 && portNumber <= 65535
364
519
  const operationError = status.operation?.state === 'failed'
365
520
  ? status.operation.error || status.operation.result?.error?.message || t('actionFailed')
366
521
  : ''
522
+ const operationErrorCode = status.operation?.state === 'failed' ? status.operation.result?.error?.code || null : null
523
+ const settingsError = status.runtimeSettings?.valid === false
524
+ ? status.runtimeSettings.error || t('settingsInvalidTitle')
525
+ : ''
526
+ const portConflict = status.runtimeSettings?.conflictApplies ? status.runtimeSettings.lastConflict : null
527
+ const validationError = portDirty && !portValid ? t('portInvalidMessage') : ''
528
+ const rawAlertMessage = validationError || (view.error ? view.message : '') || operationError || settingsError || portConflict?.message || ''
529
+ const alertCode = validationError
530
+ ? 'RUNTIME_PORT_INVALID'
531
+ : view.error
532
+ ? view.errorCode
533
+ : operationErrorCode || (settingsError ? 'RUNTIME_SETTINGS_INVALID' : portConflict ? 'RUNTIME_PORT_UNAVAILABLE' : null)
534
+ const alertTitle = alertCode === 'RUNTIME_PORT_UNAVAILABLE'
535
+ ? t('portUnavailableTitle')
536
+ : alertCode === 'RUNTIME_PORT_INVALID'
537
+ ? t('portInvalidTitle')
538
+ : alertCode === 'RUNTIME_SETTINGS_INVALID'
539
+ ? t('settingsInvalidTitle')
540
+ : t('errorTitle')
541
+ const alertMessage = alertCode === 'RUNTIME_PORT_UNAVAILABLE'
542
+ ? `${t('runtimePort')} ${portConflict?.port ?? status.runtimeSettings?.port ?? portNumber} ${t('portUnavailableMessage')}`
543
+ : rawAlertMessage
544
+ const alertHelp = alertCode === 'RUNTIME_PORT_UNAVAILABLE'
545
+ ? t('portUnavailableHelp')
546
+ : alertCode === 'RUNTIME_PORT_INVALID'
547
+ ? t('portInvalidHelp')
548
+ : ''
367
549
  const progress = busy ? progressPanel(status.operation?.progress, t) : null
368
550
  const stateLabels = { 'not-installed': 'notInstalled', degraded: 'degraded', ready: 'ready', running: 'running' }
369
551
  return jsxs('section', { className: 'foggy-settings', children: [
@@ -382,14 +564,53 @@ window.__ModuleLoader__.load({
382
564
  status.running ? jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('runtimeStop'), children: t('stop') }) : null,
383
565
  ] }),
384
566
  ] }),
567
+ errorPanel(alertTitle, alertMessage, alertHelp),
385
568
  progress,
386
- busy || operationError || view.message ? jsx('p', { className: 'foggy-message', 'data-error': operationError || view.error ? 'true' : undefined, children: busy ? t('working') : operationError || view.message }) : null,
569
+ busy ? jsx('p', { className: 'foggy-message', children: t('working') }) : null,
570
+ !busy && view.message && !view.error && !operationError ? jsx('p', { className: 'foggy-message', children: view.message }) : null,
571
+ jsxs('div', { className: 'foggy-runtime-settings', children: [
572
+ jsx('h4', { children: t('runtimeSettingsTitle') }),
573
+ jsxs('div', { className: 'foggy-runtime-settings-row', children: [
574
+ jsxs('div', { className: 'foggy-field', children: [
575
+ jsx('label', { htmlFor: 'foggy-runtime-port', children: t('runtimePort') }),
576
+ jsx('input', {
577
+ id: 'foggy-runtime-port',
578
+ className: 'foggy-input',
579
+ type: 'number',
580
+ min: 1024,
581
+ max: 65535,
582
+ step: 1,
583
+ value: portDraft,
584
+ disabled: busy || status.running,
585
+ 'aria-invalid': portDirty && !portValid ? 'true' : undefined,
586
+ onChange: (event) => {
587
+ setPortDraft(event.target.value)
588
+ setPortDirty(true)
589
+ setView((current) => ({ ...current, message: '', error: false, errorCode: null }))
590
+ },
591
+ }),
592
+ ] }),
593
+ jsxs('div', { className: 'foggy-setting-meta', children: [
594
+ jsx('span', { children: t('runtimePortHint') }),
595
+ jsx('code', { children: `${t('configuredRuntimeUrl')}: http://127.0.0.1:${portValid ? portNumber : '—'}` }),
596
+ status.running ? jsx('span', { children: t('stopToChangePort') }) : null,
597
+ ] }),
598
+ jsx('button', {
599
+ className: 'foggy-button foggy-button-primary',
600
+ type: 'button',
601
+ disabled: busy || status.running || !portDirty || !portValid,
602
+ onClick: saveRuntimePort,
603
+ children: t('saveRuntimePort'),
604
+ }),
605
+ ] }),
606
+ ] }),
387
607
  jsxs('div', { className: 'foggy-grid', children: [
388
608
  componentCard(t('python'), status.components.python, 'tool', t),
389
609
  componentCard(t('java'), status.components.java, 'tool', t),
390
610
  componentCard(t('cli'), status.components.cli, 'runtime', t),
391
611
  componentCard(t('launcher'), status.components.launcher, 'runtime', t),
392
612
  componentCard(t('analysisSkill'), status.components.analysisSkill, 'runtime', t),
613
+ componentCard(t('semanticQuerySkill'), status.components.semanticQuerySkill, 'runtime', t),
393
614
  componentCard(t('onboardingSkill'), status.components.onboardingSkill, 'runtime', t),
394
615
  componentCard(t('skillRegistry'), { installed: status.components.onboardingSkill?.provider === 'foggy-managed-skills', version: 'native' }, 'runtime', t),
395
616
  ] }),
@@ -400,6 +621,7 @@ window.__ModuleLoader__.load({
400
621
  jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('repairCli'), children: t('repairCli') }),
401
622
  jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('repairLauncher'), children: t('repairLauncher') }),
402
623
  jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('repairAnalysisSkill'), children: t('repairAnalysisSkill') }),
624
+ jsx('button', { className: 'foggy-button', type: 'button', disabled: busy, onClick: () => run('repairSemanticQuerySkill'), children: t('repairSemanticQuerySkill') }),
403
625
  ] }),
404
626
  ] }),
405
627
  status.profileMigration?.pendingCount || status.profileMigration?.conflictCount ? jsxs('div', { className: 'foggy-warning', children: [
@@ -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
+ }