@fx-data/ai-diagnostics 0.1.20261026070913 → 0.1.20261107142011
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/AGENT_SETUP.md +9 -6
- package/README.md +5 -5
- package/bin/core-ai.mjs +241 -25
- package/content/AGENTS.md +37 -57
- package/content/README.md +10 -41
- package/content/docs/architecture.md +45 -74
- package/content/docs/diagnostic-feedback.md +20 -38
- package/content/docs/diagnostic-report.md +19 -46
- package/content/docs/index.md +40 -128
- package/content/docs/scenario-guide.md +34 -52
- package/content/knowledge/README.md +49 -0
- package/content/knowledge/cases/README.md +60 -0
- package/content/knowledge/cases/dashboard-parameter-deep-binding.md +32 -0
- package/content/knowledge/common/README.md +31 -0
- package/content/knowledge/common/concepts.md +49 -0
- package/content/knowledge/common/{knowledge-applicability.md → conclusions.md} +10 -6
- package/content/knowledge/common/queries.md +98 -0
- package/content/knowledge/common/signals.md +58 -0
- package/content/knowledge/common/topics/baseline-comparison.md +23 -0
- package/content/knowledge/common/topics/large-results.md +12 -0
- package/content/knowledge/common/topics/linked-traces.md +11 -0
- package/content/knowledge/common/topics/query-budget.md +18 -0
- package/content/knowledge/common/topics/query-example.md +69 -0
- package/content/knowledge/common/topics/setup-signoz-mcp.md +102 -0
- package/content/knowledge/common/topics/start-and-anchors.md +19 -0
- package/content/knowledge/common/workflow.md +22 -0
- package/content/knowledge/component/README.md +26 -0
- package/content/knowledge/component/concepts.md +22 -0
- package/content/knowledge/component/conclusions.md +11 -0
- package/content/knowledge/component/queries.md +7 -0
- package/content/knowledge/component/signals.md +75 -0
- package/content/knowledge/component/workflow.md +23 -0
- package/content/knowledge/dashboard/README.md +31 -0
- package/content/knowledge/dashboard/concepts.md +26 -0
- package/content/knowledge/dashboard/conclusions.md +21 -0
- package/content/knowledge/dashboard/queries.md +69 -0
- package/content/knowledge/dashboard/signals.md +76 -0
- package/content/knowledge/dashboard/topics/component-comparison.md +9 -0
- package/content/knowledge/dashboard/topics/dynamic-fields.md +21 -0
- package/content/knowledge/dashboard/topics/filter-tree.md +35 -0
- package/content/knowledge/dashboard/topics/name-binding.md +9 -0
- package/content/knowledge/dashboard/workflow.md +35 -0
- package/content/knowledge/dataset/README.md +27 -0
- package/content/knowledge/dataset/concepts.md +30 -0
- package/content/knowledge/dataset/conclusions.md +11 -0
- package/content/knowledge/dataset/queries.md +7 -0
- package/content/knowledge/dataset/signals.md +61 -0
- package/content/knowledge/dataset/topics/steps.md +59 -0
- package/content/knowledge/dataset/workflow.md +27 -0
- package/content/knowledge/engine/README.md +27 -0
- package/content/knowledge/engine/concepts.md +80 -0
- package/content/knowledge/engine/conclusions.md +49 -0
- package/content/knowledge/engine/queries.md +117 -0
- package/content/knowledge/engine/signals.md +73 -0
- package/content/knowledge/engine/topics/metric-fields.md +104 -0
- package/content/knowledge/engine/topics/patterns.md +50 -0
- package/content/knowledge/engine/workflow.md +20 -0
- package/content/knowledge/signal-index.md +117 -0
- package/content/knowledge/update/README.md +39 -0
- package/content/knowledge/update/concepts.md +93 -0
- package/content/knowledge/update/conclusions.md +59 -0
- package/content/knowledge/update/queries.md +103 -0
- package/content/knowledge/update/signals.md +57 -0
- package/content/knowledge/update/topics/correlations.md +63 -0
- package/content/knowledge/update/topics/discover-executions.md +31 -0
- package/content/knowledge/update/topics/events-execution.md +136 -0
- package/content/knowledge/update/topics/events-intake.md +102 -0
- package/content/knowledge/update/topics/events-outcome.md +163 -0
- package/content/knowledge/update/topics/lifecycle.md +58 -0
- package/content/knowledge/update/topics/scene-producers.md +258 -0
- package/content/knowledge/update/workflow.md +29 -0
- package/content-manifest.json +73 -192
- package/package.json +2 -2
- package/content/.agents/skills/analyze-analysis-table-update-runtime/SKILL.md +0 -35
- package/content/.agents/skills/analyze-analysis-table-update-runtime/agents/openai.yaml +0 -7
- package/content/.agents/skills/analyze-analysis-table-update-runtime/references/discover-update-executions.md +0 -34
- package/content/.agents/skills/analyze-analysis-table-update-runtime/references/update-diagnostic-contract.md +0 -101
- package/content/.agents/skills/analyze-engine-metric/SKILL.md +0 -76
- package/content/.agents/skills/analyze-engine-metric/agents/openai.yaml +0 -13
- package/content/.agents/skills/analyze-engine-metric/scripts/analyze_engine_metric.py +0 -270
- package/content/.agents/skills/analyze-engine-metric/scripts/engine_metric_analysis/__init__.py +0 -11
- package/content/.agents/skills/analyze-engine-metric/scripts/engine_metric_analysis/metric_analyzer.py +0 -791
- package/content/.agents/skills/analyze-engine-metric/scripts/engine_metric_analysis/signoz_extract.py +0 -278
- package/content/.agents/skills/diagnose-component-performance/SKILL.md +0 -52
- package/content/.agents/skills/diagnose-component-performance/agents/openai.yaml +0 -7
- package/content/.agents/skills/diagnose-component-performance/references/experience.md +0 -34
- package/content/.agents/skills/diagnose-dashboard-performance/SKILL.md +0 -92
- package/content/.agents/skills/diagnose-dashboard-performance/agents/openai.yaml +0 -7
- package/content/.agents/skills/diagnose-dashboard-performance/references/experience.md +0 -63
- package/content/.agents/skills/diagnose-dataset-performance/SKILL.md +0 -52
- package/content/.agents/skills/diagnose-dataset-performance/agents/openai.yaml +0 -7
- package/content/.agents/skills/diagnose-dataset-performance/references/experience.md +0 -33
- package/content/.agents/skills/diagnose-performance/SKILL.md +0 -77
- package/content/.agents/skills/diagnose-performance/agents/openai.yaml +0 -7
- package/content/.agents/skills/diagnose-update-performance/SKILL.md +0 -55
- package/content/.agents/skills/diagnose-update-performance/agents/openai.yaml +0 -7
- package/content/.agents/skills/diagnose-update-performance/references/experience.md +0 -47
- package/content/.agents/skills/setup-signoz-mcp/SKILL.md +0 -30
- package/content/.agents/skills/setup-signoz-mcp/agents/openai.yaml +0 -7
- package/content/.agents/skills/setup-signoz-mcp/references/installation.md +0 -75
- package/content/.agents/skills/setup-signoz-mcp/references/windows-artifact.md +0 -14
- package/content/docs/npm-cli.md +0 -202
- package/content/docs/roadmap.md +0 -44
- package/content/docs/skill-writing-guide.md +0 -65
- package/content/knowledge/common/signoz-environments.json +0 -52
- package/content/knowledge/common/signoz-environments.md +0 -55
- package/content/knowledge/common/signoz-query-example.md +0 -41
- package/content/knowledge/common/signoz-query-policy.md +0 -145
- package/content/knowledge/common/trace-routing.md +0 -63
- package/content/knowledge/component/observability.md +0 -60
- package/content/knowledge/dashboard/observability.md +0 -168
- package/content/knowledge/dashboard/query-examples.md +0 -63
- package/content/knowledge/dataset/observability.md +0 -68
- package/content/knowledge/engine/metric-artifacts.md +0 -25
- package/content/knowledge/engine/metric-retrieval.md +0 -107
- package/content/knowledge/engine/metric-semantics.md +0 -172
- package/content/knowledge/engine/performance-patterns.md +0 -86
- package/content/knowledge/update/diagnostic/manifest.json +0 -209
- package/content/knowledge/update/diagnostic/observability/edges/edge-attempt-to-capacity-deferral.json +0 -17
- package/content/knowledge/update/diagnostic/observability/edges/edge-attempt-to-lock-requeue.json +0 -18
- package/content/knowledge/update/diagnostic/observability/edges/edge-attempt-to-merge.json +0 -17
- package/content/knowledge/update/diagnostic/observability/edges/edge-candidate-to-effective-root.json +0 -16
- package/content/knowledge/update/diagnostic/observability/edges/edge-capacity-deferral-to-attempt.json +0 -16
- package/content/knowledge/update/diagnostic/observability/edges/edge-chain-node-to-execution-detail.json +0 -18
- package/content/knowledge/update/diagnostic/observability/edges/edge-dlx-recovery-to-attempt.json +0 -16
- package/content/knowledge/update/diagnostic/observability/edges/edge-effective-execution-to-listener-before.json +0 -21
- package/content/knowledge/update/diagnostic/observability/edges/edge-effective-root-to-chain.json +0 -17
- package/content/knowledge/update/diagnostic/observability/edges/edge-engine-task-to-primary-result.json +0 -17
- package/content/knowledge/update/diagnostic/observability/edges/edge-execution-detail-to-publication.json +0 -16
- package/content/knowledge/update/diagnostic/observability/edges/edge-init-recovery-to-publication.json +0 -16
- package/content/knowledge/update/diagnostic/observability/edges/edge-listener-complete-to-child-release.json +0 -21
- package/content/knowledge/update/diagnostic/observability/edges/edge-listener-to-derived-chain.json +0 -21
- package/content/knowledge/update/diagnostic/observability/edges/edge-load-decision-to-load-operation.json +0 -16
- package/content/knowledge/update/diagnostic/observability/edges/edge-load-operation-to-engine-task.json +0 -17
- package/content/knowledge/update/diagnostic/observability/edges/edge-lock-requeue-to-attempt.json +0 -16
- package/content/knowledge/update/diagnostic/observability/edges/edge-logical-parent-to-child.json +0 -18
- package/content/knowledge/update/diagnostic/observability/edges/edge-merge-member-to-effective-execution.json +0 -18
- package/content/knowledge/update/diagnostic/observability/edges/edge-merge-to-load-decision.json +0 -17
- package/content/knowledge/update/diagnostic/observability/edges/edge-no-load-to-primary-result.json +0 -16
- package/content/knowledge/update/diagnostic/observability/edges/edge-primary-result-to-child-frontier.json +0 -18
- package/content/knowledge/update/diagnostic/observability/edges/edge-primary-result-to-listener.json +0 -23
- package/content/knowledge/update/diagnostic/observability/edges/edge-publication-to-attempt.json +0 -16
- package/content/knowledge/update/diagnostic/observability/edges/edge-ready-recovery-to-publication.json +0 -16
- package/content/knowledge/update/diagnostic/observability/edges/edge-request-to-candidate-root.json +0 -16
- package/content/knowledge/update/diagnostic/observability/identity-units/candidate-root.json +0 -13
- package/content/knowledge/update/diagnostic/observability/identity-units/dag-node.json +0 -14
- package/content/knowledge/update/diagnostic/observability/identity-units/effective-root.json +0 -14
- package/content/knowledge/update/diagnostic/observability/identity-units/engine-task.json +0 -13
- package/content/knowledge/update/diagnostic/observability/identity-units/execution-attempt.json +0 -12
- package/content/knowledge/update/diagnostic/observability/identity-units/execution-detail.json +0 -13
- package/content/knowledge/update/diagnostic/observability/identity-units/listener-invocation.json +0 -11
- package/content/knowledge/update/diagnostic/observability/identity-units/load-decision.json +0 -12
- package/content/knowledge/update/diagnostic/observability/identity-units/load-operation.json +0 -11
- package/content/knowledge/update/diagnostic/observability/identity-units/logical-dag-edge.json +0 -11
- package/content/knowledge/update/diagnostic/observability/identity-units/merge-decision.json +0 -12
- package/content/knowledge/update/diagnostic/observability/identity-units/node-primary-result.json +0 -12
- package/content/knowledge/update/diagnostic/observability/identity-units/queue-publication.json +0 -11
- package/content/knowledge/update/diagnostic/observability/identity-units/requeue.json +0 -11
- package/content/knowledge/update/diagnostic/observability/identity-units/update-chain.json +0 -13
- package/content/knowledge/update/diagnostic/observability/identity-units/update-request.json +0 -12
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-after-commit-delivery.json +0 -131
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-attempt-consumed.json +0 -175
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-candidate-root-filter.json +0 -119
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-capacity-deferred.json +0 -121
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-child-release.json +0 -146
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-dlx-recovery.json +0 -100
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-effective-root-selection.json +0 -111
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-engine-handoff.json +0 -171
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-execution-frontier-persisted.json +0 -115
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-init-recovery.json +0 -93
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-intent-accepted.json +0 -151
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-listener-derived-chain.json +0 -112
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-listener-invocation.json +0 -154
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-load-decision.json +0 -155
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-load-operation.json +0 -108
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-lock-requeued.json +0 -108
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-logical-dependency-expansion.json +0 -129
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-merge-decision.json +0 -174
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-primary-node-result.json +0 -318
- package/content/knowledge/update/diagnostic/observability/lookups/analysis-update-ready-recovery.json +0 -92
- package/content/knowledge/update/diagnostic/observability/performance-segments/analysis-update-segment-business-attempt.json +0 -33
- package/content/knowledge/update/diagnostic/observability/performance-segments/analysis-update-segment-capacity-cycle.json +0 -31
- package/content/knowledge/update/diagnostic/observability/performance-segments/analysis-update-segment-child-release.json +0 -32
- package/content/knowledge/update/diagnostic/observability/performance-segments/analysis-update-segment-commit-publication.json +0 -29
- package/content/knowledge/update/diagnostic/observability/performance-segments/analysis-update-segment-control-planning.json +0 -30
- package/content/knowledge/update/diagnostic/observability/performance-segments/analysis-update-segment-engine-task.json +0 -27
- package/content/knowledge/update/diagnostic/observability/performance-segments/analysis-update-segment-listener-callback.json +0 -27
- package/content/knowledge/update/diagnostic/observability/performance-segments/analysis-update-segment-load-operation.json +0 -27
- package/content/knowledge/update/diagnostic/observability/performance-segments/analysis-update-segment-lock-cycle.json +0 -31
- package/content/knowledge/update/diagnostic/observability/performance-segments/analysis-update-segment-main-queue-wait.json +0 -32
- package/content/knowledge/update/diagnostic/observability/performance-segments/analysis-update-segment-merge-evaluation.json +0 -29
- package/content/knowledge/update/diagnostic/observability/performance-segments/analysis-update-segment-parent-dependency-wait.json +0 -31
- package/content/knowledge/update/diagnostic/observability-index.json +0 -558
- package/content/knowledge/update/diagnostic/recipe-index.json +0 -85
- package/content/knowledge/update/diagnostic/recipes/dag-chain.json +0 -676
- package/content/knowledge/update/diagnostic/recipes/performance-window.json +0 -1128
- package/content/knowledge/update/diagnostic/recipes/scene-origin-async-message-or-callback.json +0 -438
- package/content/knowledge/update/diagnostic/recipes/scene-origin-derived-update-chain.json +0 -510
- package/content/knowledge/update/diagnostic/recipes/scene-origin-direct-service-call.json +0 -438
- package/content/knowledge/update/diagnostic/recipes/scene-origin-http.json +0 -438
- package/content/knowledge/update/diagnostic/recipes/scene-origin-internal-event.json +0 -438
- package/content/knowledge/update/diagnostic/recipes/scene-origin-job.json +0 -438
- package/content/knowledge/update/diagnostic/recipes/scene-origin-recovery-or-migration.json +0 -438
- package/content/knowledge/update/diagnostic/recipes/table-history.json +0 -508
- package/content/knowledge/update/diagnostic/recipes/table-in-chain.json +0 -595
- package/content/knowledge/update/diagnostic/scene-index.json +0 -252
- package/content/knowledge/update/diagnostic/scenes/active.json +0 -47
- package/content/knowledge/update/diagnostic/scenes/add.json +0 -140
- package/content/knowledge/update/diagnostic/scenes/agent.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/archive.json +0 -32
- package/content/knowledge/update/diagnostic/scenes/auth-change.json +0 -116
- package/content/knowledge/update/diagnostic/scenes/call-back.json +0 -32
- package/content/knowledge/update/diagnostic/scenes/case-install.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/case-replace.json +0 -7
- package/content/knowledge/update/diagnostic/scenes/cdc-schedule.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/compute-model.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/copy-data.json +0 -32
- package/content/knowledge/update/diagnostic/scenes/corp-linked.json +0 -7
- package/content/knowledge/update/diagnostic/scenes/corp-member-add.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/corp-member-del.json +0 -7
- package/content/knowledge/update/diagnostic/scenes/corp-member-modify.json +0 -32
- package/content/knowledge/update/diagnostic/scenes/corp-member-sync.json +0 -152
- package/content/knowledge/update/diagnostic/scenes/corp-setting-member-rename.json +0 -152
- package/content/knowledge/update/diagnostic/scenes/corp-setting.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/cross-project-allow.json +0 -32
- package/content/knowledge/update/diagnostic/scenes/default.json +0 -32
- package/content/knowledge/update/diagnostic/scenes/del.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/dynamic-time.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/edit.json +0 -92
- package/content/knowledge/update/diagnostic/scenes/excel-append.json +0 -44
- package/content/knowledge/update/diagnostic/scenes/excel-save.json +0 -32
- package/content/knowledge/update/diagnostic/scenes/folder-copy.json +0 -32
- package/content/knowledge/update/diagnostic/scenes/global.json +0 -56
- package/content/knowledge/update/diagnostic/scenes/handover.json +0 -32
- package/content/knowledge/update/diagnostic/scenes/history-handle.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/manual-update.json +0 -44
- package/content/knowledge/update/diagnostic/scenes/migration.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/param-change.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/polars-error.json +0 -32
- package/content/knowledge/update/diagnostic/scenes/preview-exception.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/preview-invalid.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/project-excel-move.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/re-check.json +0 -7
- package/content/knowledge/update/diagnostic/scenes/replace.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/rollback.json +0 -68
- package/content/knowledge/update/diagnostic/scenes/save-as.json +0 -44
- package/content/knowledge/update/diagnostic/scenes/schedu.json +0 -56
- package/content/knowledge/update/diagnostic/scenes/table-move-other-workspace.json +0 -20
- package/content/knowledge/update/diagnostic/scenes/user-holder-to-normal.json +0 -20
- package/content/knowledge/update/observability.md +0 -16
- package/content/knowledge/update/runtime-evidence.md +0 -32
- package/content/scripts/receive_tool_result.py +0 -57
- /package/content/{.agents/skills/setup-signoz-mcp/scripts → scripts}/install_signoz_mcp.ps1 +0 -0
- /package/content/{.agents/skills/setup-signoz-mcp/scripts → scripts}/install_signoz_mcp.py +0 -0
package/AGENT_SETUP.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# FX 数据诊断:Agent 安装与管理
|
|
2
2
|
|
|
3
|
-
本文供执行安装与管理的 Agent 读取,不作为日常诊断的前置上下文。包版本为 `0.1.
|
|
3
|
+
本文供执行安装与管理的 Agent 读取,不作为日常诊断的前置上下文。包版本为 `0.1.20261107142011`,需要 Node.js `>=22` 及 npm。用户只需表达安装、使用、更新或卸载意图,环境准备和客户端登记由 Agent 完成。
|
|
4
4
|
|
|
5
5
|
用户明确要求安装、更新、回退、卸载或检查 FX 数据诊断时,也会触发薄 Skill,并直接执行对应管理命令,不先运行 `context` 或读取诊断规则;只有开始诊断时才按薄 Skill 加载内容。普通聊天不主动加载。
|
|
6
6
|
|
|
@@ -49,20 +49,22 @@
|
|
|
49
49
|
- 独立 Node 环境下,用其绝对路径运行配套 `npm-cli.js`(Unix:`lib/node_modules/npm/bin/npm-cli.js`;Windows:`node_modules/npm/bin/npm-cli.js`)。通过同一环境的 `npm root -g` 找到包,沿用自定义 prefix,再用同一 Node 调用包内 `bin/core-ai.mjs` 登记。路径含空格时保留引号;Windows PowerShell 调用带引号的可执行路径需要 `&`。不依赖用户项目目录。
|
|
50
50
|
- 全局目录不可写时仅为本次安装指定用户目录的 `--prefix`;现有源找不到本包时仅为本次命令指定 `--registry=https://registry.npmjs.org`,不改全局 npm 配置。
|
|
51
51
|
- 安装失败时同时读取 stderr 中的错误和分项检查。Node 路径失效时修复原运行环境;内容损坏、所有权冲突或权限错误应处理具体错误,不通过第二份安装绕过。需要进一步排错时再运行 `doctor`。
|
|
52
|
-
- 复用旧版时,若 `install` 尚未返回 `checks` 和 `context`,仅此分支补跑稳定入口的 `doctor`、`context` 验证,不因此升级或重新下载。Python
|
|
52
|
+
- 复用旧版时,若 `install` 尚未返回 `checks` 和 `context`,仅此分支补跑稳定入口的 `doctor`、`context` 验证,不因此升级或重新下载。Python 缺失不阻止登记,首次运行包内 Python 脚本时再准备。
|
|
53
53
|
- WorkBuddy 的 `.workbuddy/skills` 与 `.workbuddy-ai/skills` 由 CLI 按本机线索选择;无法确认时按 CLI 提示处理,不自行扫描客户端缓存或改成两处同时登记。
|
|
54
54
|
|
|
55
55
|
## 按需使用
|
|
56
56
|
|
|
57
|
-
用户明确要求用 FX 数据诊断分析问题后,按薄 Skill
|
|
57
|
+
用户明确要求用 FX 数据诊断分析问题后,按薄 Skill 调用一次稳定入口的 `context`,读取返回的 `agent_rules`,只加载当前问题需要的 Skill 和引用。CLI 同时在后台检查更新,固定本次调查返回的版本与 `pinned_cli`;同一调查继续复用,不重复调用稳定入口的 `context`。普通聊天和管理请求无需预加载诊断内容或检查更新。
|
|
58
58
|
|
|
59
|
-
已有 SigNoZ MCP 直接复用;实时查询受阻时才按包内配置 Skill 处理,凭据只保存在本机。Python
|
|
59
|
+
已有 SigNoZ MCP 直接复用;实时查询受阻时才按包内配置 Skill 处理,凭据只保存在本机。Python 按需准备;独立解释器可通过 `FX_DATA_DIAGNOSTICS_PYTHON` 指定绝对路径。
|
|
60
60
|
|
|
61
61
|
## 更新与回退
|
|
62
62
|
|
|
63
|
-
用户要求更新时,调用稳定入口的 `update`;要求回退到上次更新前时调用 `rollback`,明确指定版本时调用 `update --version
|
|
63
|
+
用户要求更新时,调用稳定入口的 `update`;要求回退到上次更新前时调用 `rollback`,明确指定版本时调用 `update --version <版本号>`。新调查使用切换后的版本,进行中的调查保持原版本。回退不锁定后续版本,下一次新诊断仍检查最新版;需要持续使用旧版时按配置变量关闭后台更新。新增客户端只登记,不隐式更新。
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
使用时的后台更新由 CLI 完成,无需 Agent 设置定时任务或另跑 `update`。通知读取、去重和提示按 [安装副本的助手规则](content/AGENTS.md#安装副本的助手规则)执行,不轮询等待。
|
|
66
|
+
|
|
67
|
+
旧版用户需先手动升级一次才能获得后台更新能力。版本切换后按客户端要求刷新技能或新开聊天。
|
|
66
68
|
|
|
67
69
|
若旧试用版提示需要迁移,先用当前 npm 包替换全局程序,直接调用新包 CLI 的 `update`,再登记与检查。迁移保留客户端登记、诊断材料和独立 MCP 配置;旧试用版不作为回退目标。
|
|
68
70
|
|
|
@@ -83,5 +85,6 @@ CLI 使用首次安装记录的 Node/npm 和 prefix,在无网络模式下清
|
|
|
83
85
|
| `FX_DATA_DIAGNOSTICS_HOME` | 自定义共享安装目录 |
|
|
84
86
|
| `FX_DATA_DIAGNOSTICS_PYTHON` | 自定义 Python 可执行文件 |
|
|
85
87
|
| `FX_DATA_DIAGNOSTICS_AGENT_HOME` | 自定义客户端用户目录,通常无需设置 |
|
|
88
|
+
| `FX_DATA_DIAGNOSTICS_AUTO_UPDATE` | 默认开启后台更新;设为 `0` 可关闭,适用于受限环境或需要持续使用旧版时 |
|
|
86
89
|
|
|
87
90
|
显式 CLI 参数优先,其次是环境变量,未指定时使用默认目录或自动发现的运行环境。
|
package/README.md
CHANGED
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
|
|
23
23
|
## 更新
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
每次开始新诊断时,工具会在后台检查更新,不等待更新即可排查。更新成功后,各 Agent 的下一次新诊断使用新版;当前调查保持原版本,更新失败也不影响排查。Agent 会随后续使用提示更新状态,没有新版时不打扰你。
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
需要立即更新并等待完成时说:
|
|
28
28
|
|
|
29
|
-
>
|
|
29
|
+
> 更新 FX 数据诊断。
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
旧版用户先手动更新一次,即可获得后台更新能力。若新版有问题,可以说“把 FX 数据诊断回退到这次更新前的版本”;需要持续使用旧版时,同时让 Agent 关闭后台更新。
|
|
32
32
|
|
|
33
33
|
## 卸载
|
|
34
34
|
|
|
@@ -38,4 +38,4 @@
|
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
41
|
-
**给 Agent:** 按 [Agent 执行说明](https://unpkg.com/@fx-data/ai-diagnostics@0.1.
|
|
41
|
+
**给 Agent:** 按 [Agent 执行说明](https://unpkg.com/@fx-data/ai-diagnostics@0.1.20261107142011/AGENT_SETUP.md) 完成安装与管理。已有本地包时,优先读取包内 `AGENT_SETUP.md`;本地 `.tgz` 只需读取 `package/AGENT_SETUP.md`。按命令结果简短反馈,成功即结束;只有失败或结果不完整时才排错。
|
package/bin/core-ai.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Distribution only: diagnostic rules and
|
|
2
|
+
// Distribution only: diagnostic rules and scripts live in the unchanged content bundle.
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { createHash, randomUUID } from 'node:crypto';
|
|
7
|
-
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
9
9
|
|
|
10
10
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -29,7 +29,7 @@ function argumentsFor(argv) {
|
|
|
29
29
|
if (args[i] === '--activate-package') {
|
|
30
30
|
if (command !== 'update') throw new Error('--activate-package is reserved for the update subprocess');
|
|
31
31
|
options.activatePackage = true;
|
|
32
|
-
} else if (['--home', '--agent-home', '--agent', '--skills-dir', '--registry', '--version'].includes(args[i])) {
|
|
32
|
+
} else if (['--home', '--agent-home', '--agent', '--skills-dir', '--registry', '--version', '--background-job'].includes(args[i])) {
|
|
33
33
|
if (!args[i + 1] || args[i + 1].startsWith('--')) throw new Error(`Missing value for ${args[i]}`);
|
|
34
34
|
options[args[i].slice(2)] = args[++i];
|
|
35
35
|
} else rest.push(args[i]);
|
|
@@ -53,11 +53,11 @@ function commandHelp(argv) {
|
|
|
53
53
|
let target;
|
|
54
54
|
if (['help', '--help', '-h'].includes(command)) target = Object.hasOwn(commands, args[0]) ? args[0] : 'help';
|
|
55
55
|
else if (Object.hasOwn(commands, command) && args.some(arg => arg === '--help' || arg === '-h')) target = command;
|
|
56
|
-
else return null; //
|
|
56
|
+
else return null; // python keeps the script's own help arguments.
|
|
57
57
|
const note = '帮助只显示说明,不执行命令。安装和管理成功以命令返回结果为准,简短反馈后停止,无需额外 status 或 doctor。';
|
|
58
58
|
if (target !== 'help') return { ok: true, action: 'help', command: target, ...commands[target], note };
|
|
59
59
|
return { ok: true, action: 'help', name: commandName, display_name: 'FX 数据诊断',
|
|
60
|
-
commands: [...Object.values(commands).map(value => value.usage), '
|
|
60
|
+
commands: [...Object.values(commands).map(value => value.usage), 'python <script> [args...]'],
|
|
61
61
|
registry: defaultRegistry, note };
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -183,9 +183,13 @@ ${powershell} context
|
|
|
183
183
|
|
|
184
184
|
后续管理命令使用同一组 Node 和稳定脚本路径,将 context 替换为对应子命令,不依赖 PATH 中的全局 CLI 版本。
|
|
185
185
|
|
|
186
|
-
context
|
|
186
|
+
每次新诊断只调用一次稳定入口 context:立即取得并固定本次版本,同时后台检查更新,不等待网络。同一调查继续复用首次结果,不重复调用稳定入口 context;需要重新读取固定路径时使用 pinned_cli context,不触发新检查。
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
context 返回当前版本、内容路径和 agent_rules。读取 agent_rules,按问题只读取所需知识(先看对应域的 workflow.md),不预加载全部知识,也不切换用户工作区。包内仓库相对路径以 content_root 为基准,文档链接相对引用文件解析。
|
|
189
|
+
|
|
190
|
+
context、status、session 的 auto_update.notification,以及 python 标准错误中 fx_data_diagnostics.auto_update.notification,是更新提示。有提示时按 message 简短告知用户;无新版不打扰。不为获取状态额外调用 CLI 或轮询;本轮没有后续 CLI 调用时,下次使用再补报。更新成功只影响下次新诊断,失败继续使用本次版本。
|
|
191
|
+
|
|
192
|
+
当前调查固定使用首次 context 返回的 node、pinned_cli 和内容版本。脚本通过 <node> <pinned_cli> python <脚本绝对路径> ... 执行;Windows PowerShell 同样在带引号的 Node 路径前加 &。通过 session 获取临时目录,保存日志和 Metric 正文,不写入安装内容。只有包内 Python 脚本需要 Python;doctor 的 python.available=false 表示按需补齐,不代表安装失败。已有 MCP 直接复用;实时查询受阻时才读取 setup-signoz-mcp。
|
|
189
193
|
|
|
190
194
|
用户要求更新时执行稳定入口 update,默认使用 latest;要求回退到上次更新前时执行 rollback,也可用 --version 指定版本。已保留版本校验通过后直接复用。各客户端的新调查共用切换后的版本,进行中调查继续使用原 pinned_cli。更新后按客户端要求刷新技能或新开聊天;安装副本不做 Git 更新。
|
|
191
195
|
|
|
@@ -262,7 +266,7 @@ function skillChecks(targets, home, installId) {
|
|
|
262
266
|
function contextFor(root, home, pkg, manifest) {
|
|
263
267
|
const content = path.join(root, 'content');
|
|
264
268
|
return { version: pkg.version, source_commit: manifest.source_commit, content_root: content,
|
|
265
|
-
agent_rules: path.join(content, 'AGENTS.md'),
|
|
269
|
+
agent_rules: path.join(content, 'AGENTS.md'),
|
|
266
270
|
pinned_cli: path.join(root, 'bin', 'core-ai.mjs'), node: process.execPath,
|
|
267
271
|
launcher: launchers(home).executable, launcher_script: launchers(home).script };
|
|
268
272
|
}
|
|
@@ -362,6 +366,7 @@ function install(home, options, { sourceRoot = packageRoot, action = 'install' }
|
|
|
362
366
|
const { pkg, manifest } = verifyBundle(sourceRoot, { allowMacMetadata: isInstalledBundle(sourceRoot, home) });
|
|
363
367
|
noSymlinkAncestors(home);
|
|
364
368
|
const prior = ownedState(home, false);
|
|
369
|
+
if (options['background-job']) assertBackgroundJob(home, options['background-job'], pkg.version);
|
|
365
370
|
if (!prior && fs.existsSync(home) && fs.readdirSync(home).length) throw new Error('Refusing to take over a nonempty installation directory');
|
|
366
371
|
const ownership = prior?.ownership || { schema_version: 1, home, install_id: randomUUID() };
|
|
367
372
|
const registrationOnly = Boolean(action === 'install' && prior?.state?.lifecycle_protocol === lifecycleProtocol);
|
|
@@ -387,9 +392,13 @@ function install(home, options, { sourceRoot = packageRoot, action = 'install' }
|
|
|
387
392
|
if (!prior) atomicWrite(path.join(home, homeMarker), JSON.stringify(ownership));
|
|
388
393
|
const releaseLock = acquireInstallLock(home);
|
|
389
394
|
let backup = [];
|
|
395
|
+
const launcherBackup = [];
|
|
390
396
|
const release = path.join(home, 'releases', pkg.version);
|
|
391
397
|
const stage = `${release}.${randomUUID()}.tmp`;
|
|
392
398
|
try {
|
|
399
|
+
// A download may outlive its worker or a manual version change. Recheck at
|
|
400
|
+
// activation, under the installation lock, before touching shared entries.
|
|
401
|
+
if (options['background-job']) assertBackgroundJob(home, options['background-job'], pkg.version);
|
|
393
402
|
backup = writeDirectories.map(target => ({ ...target, existed: fs.existsSync(target.directory), text: fs.existsSync(path.join(target.directory, 'SKILL.md')) ? fs.readFileSync(path.join(target.directory, 'SKILL.md')) : null, owner: fs.existsSync(path.join(target.directory, ownerFile)) ? fs.readFileSync(path.join(target.directory, ownerFile)) : null }));
|
|
394
403
|
if (fs.existsSync(release)) {
|
|
395
404
|
verifyBundle(release, { allowMacMetadata: Boolean(prior?.state) });
|
|
@@ -412,15 +421,21 @@ function install(home, options, { sourceRoot = packageRoot, action = 'install' }
|
|
|
412
421
|
[path.join(home, 'bin', commandName), `#!/bin/sh\nexec ${quote(process.execPath)} ${quote(launchers(home).script)} "$@"\n`],
|
|
413
422
|
[path.join(home, 'bin', `${commandName}.cmd`), `@echo off\r\n"${process.execPath}" "%~dp0core-ai.mjs" %*\r\n`],
|
|
414
423
|
]) {
|
|
415
|
-
if (!registrationOnly || !fs.existsSync(file))
|
|
424
|
+
if (!registrationOnly || !fs.existsSync(file)) {
|
|
425
|
+
const existed = fs.existsSync(file);
|
|
426
|
+
launcherBackup.push({ file, existed, text: existed ? fs.readFileSync(file) : null,
|
|
427
|
+
mode: existed ? fs.statSync(file).mode & 0o777 : 0o755 });
|
|
428
|
+
atomicWrite(file, text, 0o755);
|
|
429
|
+
}
|
|
416
430
|
}
|
|
417
431
|
const registrationReady = performance.now();
|
|
418
432
|
const checks = { integrity: { status: 'ok' }, skills: skillChecks(targets, home, ownership.install_id), context: { status: 'ok' } };
|
|
419
433
|
if (checks.skills.status !== 'ok') throw new Error(checks.skills.entries.filter(entry => entry.status === 'error').map(entry => entry.error).join('; '));
|
|
420
434
|
const context = contextFor(release, home, pkg, manifest);
|
|
421
435
|
fs.accessSync(context.agent_rules, fs.constants.R_OK);
|
|
422
|
-
|
|
423
|
-
fs.
|
|
436
|
+
const knowledgeRoot = path.join(context.content_root, 'knowledge');
|
|
437
|
+
if (!fs.statSync(knowledgeRoot).isDirectory()) throw new Error('Diagnostic knowledge directory is unavailable');
|
|
438
|
+
fs.accessSync(knowledgeRoot, fs.constants.R_OK);
|
|
424
439
|
if (!registrationOnly) atomicWrite(path.join(home, homeMarker), JSON.stringify(ownership));
|
|
425
440
|
const releaseProtocols = { ...prior?.state?.release_protocols };
|
|
426
441
|
if (prior?.state?.lifecycle_protocol === lifecycleProtocol) releaseProtocols[prior.state.version] = lifecycleProtocol;
|
|
@@ -430,6 +445,9 @@ function install(home, options, { sourceRoot = packageRoot, action = 'install' }
|
|
|
430
445
|
const npmInstallation = prior?.state ? prior.state.npm_installation : detectNpmInstallation(sourceRoot);
|
|
431
446
|
const state = { schema_version: 1, lifecycle_protocol: lifecycleProtocol, release_protocols: releaseProtocols, install_id: ownership.install_id, package: pkg.name, version: pkg.version, previous_version: prior?.state?.version === pkg.version ? prior.state.previous_version : prior?.state?.version || null, registry, skills: targets, source_commit: manifest.source_commit,
|
|
432
447
|
...(npmInstallation ? { npm_installation: npmInstallation } : {}) };
|
|
448
|
+
// Copying and validation can outlast the background deadline. The last
|
|
449
|
+
// check belongs immediately before committing the active version too.
|
|
450
|
+
if (options['background-job']) assertBackgroundJob(home, options['background-job'], pkg.version);
|
|
433
451
|
if (JSON.stringify(state) !== JSON.stringify(prior?.state)) atomicWrite(path.join(home, 'state.json'), JSON.stringify(state, null, 2));
|
|
434
452
|
const finished = performance.now();
|
|
435
453
|
const elapsed = (from, to) => Math.round((to - from) * 100) / 100;
|
|
@@ -440,6 +458,10 @@ function install(home, options, { sourceRoot = packageRoot, action = 'install' }
|
|
|
440
458
|
agent_hint: '安装登记与文件检查已完成。按返回结果用 2–3 句话反馈版本、首次安装或复用、需要的刷新及使用提示后结束;不再运行 status/ls/doctor/context 或读取源码、客户端缓存。本工具不要求安装清单或长期记忆,仍遵循客户端强制规则。客户端发现未验证时只提示刷新或新开聊天。' } : {}),
|
|
441
459
|
refresh: 'Open a new Agent conversation if the skill has not refreshed.' });
|
|
442
460
|
} catch (error) {
|
|
461
|
+
for (const item of launcherBackup) {
|
|
462
|
+
if (item.existed) atomicWrite(item.file, item.text, item.mode);
|
|
463
|
+
else fs.rmSync(item.file, { force: true });
|
|
464
|
+
}
|
|
443
465
|
for (const item of backup) {
|
|
444
466
|
if (item.existed) { atomicWrite(path.join(item.directory, 'SKILL.md'), item.text, 0o644); atomicWrite(path.join(item.directory, ownerFile), item.owner); }
|
|
445
467
|
else if (fs.existsSync(item.directory)) fs.rmSync(item.directory, { recursive: true });
|
|
@@ -565,11 +587,187 @@ function update(home, options, action = 'update') {
|
|
|
565
587
|
for (const key of Object.keys(env)) if (key.toLowerCase() === 'path') delete env[key];
|
|
566
588
|
env.PATH = [path.dirname(process.execPath), inheritedPath].filter(Boolean).join(path.delimiter);
|
|
567
589
|
Object.assign(env, { FX_DATA_DIAGNOSTICS_HOME: home, NPM_CONFIG_USERCONFIG: path.join(npmHome, 'user.npmrc'), NPM_CONFIG_GLOBALCONFIG: path.join(npmHome, 'global.npmrc'), NPM_CONFIG_CACHE: path.join(npmHome, 'cache'), NPM_CONFIG_REGISTRY: registry, NPM_CONFIG_UPDATE_NOTIFIER: 'false' });
|
|
568
|
-
const
|
|
590
|
+
const background = options['background-job'];
|
|
591
|
+
if (background) assertBackgroundJob(home, background, version);
|
|
592
|
+
const result = spawnSync(process.execPath, [npmExecutable(), 'exec', '--yes', '--registry', registry,
|
|
593
|
+
...(background ? ['--fetch-retries=0', '--fetch-timeout=15000', '--ignore-scripts'] : []),
|
|
594
|
+
'--package', `${state.package}@${version}`, '--', commandName, 'update', '--activate-package', '--home', home, '--registry', registry,
|
|
595
|
+
...(background ? ['--background-job', background] : [])], { cwd: npmHome, env, stdio: 'inherit' });
|
|
569
596
|
if (result.error) throw result.error;
|
|
570
597
|
process.exitCode = result.status ?? 1;
|
|
571
598
|
}
|
|
572
599
|
|
|
600
|
+
// Automatic updates belong to the shared installation, not to an Agent. Only
|
|
601
|
+
// a new diagnostic entry starts a worker; pinned scripts merely carry notices.
|
|
602
|
+
const autoUpdateFile = 'auto-update.json';
|
|
603
|
+
const autoUpdateLock = '.auto-update.lock';
|
|
604
|
+
const autoUpdateNotice = 'auto-update-notice.json';
|
|
605
|
+
const autoUpdateTimeout = 60_000;
|
|
606
|
+
const autoUpdateEnabled = () => process.env.FX_DATA_DIAGNOSTICS_AUTO_UPDATE !== '0';
|
|
607
|
+
const readOptional = file => { try { return json(file); } catch { return null; } };
|
|
608
|
+
|
|
609
|
+
function deadProcess(pid) {
|
|
610
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
611
|
+
try { process.kill(pid, 0); } catch (error) { return error.code === 'ESRCH'; }
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function newerVersion(candidate, current) {
|
|
616
|
+
if (!versionPattern.test(candidate) || !versionPattern.test(current)) return false;
|
|
617
|
+
const split = value => { const [core, ...pre] = value.split('-'); return { core: core.split('.').map(BigInt), pre: pre.join('-') }; };
|
|
618
|
+
const next = split(candidate), old = split(current);
|
|
619
|
+
for (let i = 0; i < 3; i++) if (next.core[i] !== old.core[i]) return next.core[i] > old.core[i];
|
|
620
|
+
if (!next.pre || !old.pre) return Boolean(old.pre && !next.pre);
|
|
621
|
+
const left = next.pre.split('.'), right = old.pre.split('.');
|
|
622
|
+
for (let i = 0; i < Math.max(left.length, right.length); i++) {
|
|
623
|
+
if (left[i] === undefined || right[i] === undefined) return right[i] === undefined;
|
|
624
|
+
if (left[i] === right[i]) continue;
|
|
625
|
+
const numericLeft = /^\d+$/.test(left[i]), numericRight = /^\d+$/.test(right[i]);
|
|
626
|
+
if (numericLeft && numericRight) return BigInt(left[i]) > BigInt(right[i]);
|
|
627
|
+
return numericLeft !== numericRight ? !numericLeft : left[i] > right[i];
|
|
628
|
+
}
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function updateJobOwned(home, id) {
|
|
633
|
+
const lock = readOptional(path.join(home, autoUpdateLock));
|
|
634
|
+
return lock?.id === id && lock.hostname === os.hostname();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function releaseUpdateJob(home, id) {
|
|
638
|
+
try {
|
|
639
|
+
if (updateJobOwned(home, id)) fs.rmSync(path.join(home, autoUpdateLock), { force: true });
|
|
640
|
+
} catch { /* Optional background bookkeeping cannot fail a diagnostic call. */ }
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function saveUpdateJob(home, job, status) {
|
|
644
|
+
if (!updateJobOwned(home, job.id) || ownedState(home, false)?.state?.install_id !== job.install_id) return;
|
|
645
|
+
job.status = status;
|
|
646
|
+
atomicWrite(path.join(home, autoUpdateFile), JSON.stringify(job));
|
|
647
|
+
const message = status === 'updating'
|
|
648
|
+
? `发现 FX 数据诊断新版 ${job.target_version},正在后台更新。本次分析继续使用已固定版本,更新成功后下次诊断生效。`
|
|
649
|
+
: status === 'updated'
|
|
650
|
+
? `FX 数据诊断已后台更新至 ${job.target_version},供下次新诊断使用;进行中的分析仍使用各自已固定的版本。`
|
|
651
|
+
: status === 'failed'
|
|
652
|
+
? 'FX 数据诊断后台检查或更新未完成,继续使用当前可用版本,不影响本次排查。'
|
|
653
|
+
: null;
|
|
654
|
+
if (message) atomicWrite(path.join(home, autoUpdateNotice), JSON.stringify({ id: `${job.id}:${status}`, install_id: job.install_id, status, message }));
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function autoUpdateDetails(home) {
|
|
658
|
+
if (!autoUpdateEnabled()) return null;
|
|
659
|
+
try {
|
|
660
|
+
const state = ownedState(home, false)?.state;
|
|
661
|
+
if (!state) return null;
|
|
662
|
+
const job = readOptional(path.join(home, autoUpdateFile));
|
|
663
|
+
const details = job?.install_id === state.install_id
|
|
664
|
+
? { status: job.status, ...(job.target_version ? { target_version: job.target_version } : {}) } : null;
|
|
665
|
+
// Claim one notice without deleting a newer notice written concurrently.
|
|
666
|
+
// Other clients may receive the next state; the worker never writes to a
|
|
667
|
+
// parent's terminal and diagnostic/script stdout keeps its original shape.
|
|
668
|
+
const claimed = path.join(home, `${autoUpdateNotice}.${randomUUID()}.read`);
|
|
669
|
+
try {
|
|
670
|
+
fs.renameSync(path.join(home, autoUpdateNotice), claimed);
|
|
671
|
+
const notice = readOptional(claimed);
|
|
672
|
+
if (notice?.install_id === state.install_id) return { ...details, notification: { id: notice.id, status: notice.status, message: notice.message } };
|
|
673
|
+
} catch { /* No notice, or another CLI already delivered it. */ }
|
|
674
|
+
finally { fs.rmSync(claimed, { force: true }); }
|
|
675
|
+
return details;
|
|
676
|
+
} catch { return null; } // Update bookkeeping must never block diagnosis.
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function assertBackgroundJob(home, id, version) {
|
|
680
|
+
const job = readOptional(path.join(home, autoUpdateFile));
|
|
681
|
+
const state = ownedState(home, false)?.state;
|
|
682
|
+
if (!updateJobOwned(home, id) || job?.id !== id || job.status !== 'updating'
|
|
683
|
+
|| Date.now() >= job.expires_at || deadProcess(job.pid)
|
|
684
|
+
|| !state || state.install_id !== job.install_id || state.version !== job.base_version
|
|
685
|
+
|| version !== job.target_version) throw new Error('Background update is no longer applicable; keeping the current installation');
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function startAutoUpdate(home) {
|
|
689
|
+
if (!autoUpdateEnabled()) return;
|
|
690
|
+
let job;
|
|
691
|
+
try {
|
|
692
|
+
const { state } = ownedState(home);
|
|
693
|
+
if (!state) return;
|
|
694
|
+
const lockPath = path.join(home, autoUpdateLock);
|
|
695
|
+
const existing = readOptional(lockPath);
|
|
696
|
+
if (fs.existsSync(lockPath)) {
|
|
697
|
+
// Age alone does not permit stealing a live worker's lock. Failed jobs
|
|
698
|
+
// have no cooldown; after a dead worker the next diagnosis can try again.
|
|
699
|
+
if (existing?.hostname !== os.hostname() || !deadProcess(existing.pid)) return;
|
|
700
|
+
if (readOptional(lockPath)?.id !== existing.id) return;
|
|
701
|
+
fs.unlinkSync(lockPath);
|
|
702
|
+
}
|
|
703
|
+
job = { id: randomUUID(), install_id: state.install_id, base_version: state.version,
|
|
704
|
+
pid: process.pid, hostname: os.hostname(), status: 'checking', expires_at: Date.now() + autoUpdateTimeout };
|
|
705
|
+
const fd = fs.openSync(lockPath, 'wx', 0o600);
|
|
706
|
+
try { fs.writeFileSync(fd, JSON.stringify(job)); } finally { fs.closeSync(fd); }
|
|
707
|
+
saveUpdateJob(home, job, 'checking');
|
|
708
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !/^(NPM_TOKEN|NODE_AUTH_TOKEN)$/i.test(key)));
|
|
709
|
+
const child = spawn(process.execPath, [path.join(home, 'releases', state.version, 'bin/core-ai.mjs'), '__background-update', job.id, '--home', home],
|
|
710
|
+
{ cwd: home, env, detached: true, stdio: 'ignore', windowsHide: true });
|
|
711
|
+
child.on('error', () => {
|
|
712
|
+
try { saveUpdateJob(home, job, 'failed'); } catch { /* Diagnosis still works. */ }
|
|
713
|
+
finally { releaseUpdateJob(home, job.id); }
|
|
714
|
+
});
|
|
715
|
+
if (child.pid) {
|
|
716
|
+
// Hand the live lock to the worker before the launching CLI exits.
|
|
717
|
+
job.pid = child.pid;
|
|
718
|
+
atomicWrite(lockPath, JSON.stringify(job));
|
|
719
|
+
}
|
|
720
|
+
child.unref();
|
|
721
|
+
} catch (error) {
|
|
722
|
+
if (job && updateJobOwned(home, job.id)) {
|
|
723
|
+
try { saveUpdateJob(home, job, 'failed'); } catch { /* Best effort only. */ }
|
|
724
|
+
finally { releaseUpdateJob(home, job.id); }
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
async function backgroundUpdate(home, id) {
|
|
730
|
+
const job = readOptional(path.join(home, autoUpdateFile));
|
|
731
|
+
if (job?.id !== id || !updateJobOwned(home, id)) return;
|
|
732
|
+
job.pid = process.pid;
|
|
733
|
+
try {
|
|
734
|
+
const state = ownedState(home).state;
|
|
735
|
+
if (!state || state.install_id !== job.install_id || state.version !== job.base_version) return;
|
|
736
|
+
saveUpdateJob(home, job, 'checking');
|
|
737
|
+
const registry = registryOrigin(state.registry);
|
|
738
|
+
const response = await fetch(`${registry}/${encodeURIComponent(state.package)}/latest`,
|
|
739
|
+
{ signal: AbortSignal.timeout(3_000), redirect: 'error', headers: { Accept: 'application/json', 'Cache-Control': 'no-cache' } });
|
|
740
|
+
if (!response.ok) throw new Error('Version check failed');
|
|
741
|
+
const latest = await response.json();
|
|
742
|
+
if (latest.name !== state.package || typeof latest.version !== 'string' || !versionPattern.test(latest.version)) throw new Error('Invalid latest version');
|
|
743
|
+
if (!newerVersion(latest.version, state.version)) { saveUpdateJob(home, job, 'current'); return; }
|
|
744
|
+
job.target_version = latest.version;
|
|
745
|
+
saveUpdateJob(home, job, 'updating');
|
|
746
|
+
assertBackgroundJob(home, id, latest.version);
|
|
747
|
+
// Reuse the explicit-version updater in an isolated child. Its output must
|
|
748
|
+
// not keep the initiating Agent tool open or mix with diagnostic results.
|
|
749
|
+
await new Promise((resolve, reject) => {
|
|
750
|
+
const child = spawn(process.execPath, [fileURLToPath(import.meta.url), 'update', '--version', latest.version,
|
|
751
|
+
'--home', home, '--background-job', id], { cwd: home, env: process.env, stdio: 'ignore', windowsHide: true });
|
|
752
|
+
const timer = setTimeout(() => {
|
|
753
|
+
// Invalidate activation before stopping the child: npm may have its own
|
|
754
|
+
// subprocess, which must not activate a release after this deadline.
|
|
755
|
+
try { saveUpdateJob(home, job, 'failed'); } catch { /* Best effort. */ }
|
|
756
|
+
child.kill('SIGKILL');
|
|
757
|
+
reject(new Error('Background update timed out'));
|
|
758
|
+
}, Math.max(1, job.expires_at - Date.now()));
|
|
759
|
+
child.once('error', error => { clearTimeout(timer); reject(error); });
|
|
760
|
+
child.once('exit', code => { clearTimeout(timer); code === 0 ? resolve() : reject(new Error('Background update failed')); });
|
|
761
|
+
});
|
|
762
|
+
if (ownedState(home, false)?.state?.version !== latest.version) throw new Error('Version activation was not confirmed');
|
|
763
|
+
saveUpdateJob(home, job, 'updated');
|
|
764
|
+
} catch {
|
|
765
|
+
try { if (job.status !== 'failed') saveUpdateJob(home, job, 'failed'); } catch { /* Never disturb the active release. */ }
|
|
766
|
+
} finally {
|
|
767
|
+
releaseUpdateJob(home, id);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
573
771
|
function python() {
|
|
574
772
|
const configured = process.env.FX_DATA_DIAGNOSTICS_PYTHON;
|
|
575
773
|
const candidates = configured ? [[configured]] : process.platform === 'win32' ? [['py', '-3'], ['python'], ['python3']] : [['python3'], ['python']];
|
|
@@ -577,7 +775,7 @@ function python() {
|
|
|
577
775
|
const probe = spawnSync(args[0], [...args.slice(1), '-I', '-B', '-c', 'import sys; print(sys.executable); sys.exit(0 if sys.version_info >= (3,9) else 1)'], { encoding: 'utf8' });
|
|
578
776
|
if (probe.status === 0) return { executable: probe.stdout.trim(), available: true };
|
|
579
777
|
}
|
|
580
|
-
return { available: false, requirement: 'Python 3.9+ is required
|
|
778
|
+
return { available: false, requirement: 'Python 3.9+ is required to run bundled scripts.' };
|
|
581
779
|
}
|
|
582
780
|
|
|
583
781
|
function uninstall(home) {
|
|
@@ -595,7 +793,7 @@ function uninstall(home) {
|
|
|
595
793
|
// Remove only owned program files. Configuration, npm cache and evidence are retained.
|
|
596
794
|
// Keep the stable command and state until version cleanup completes, so a
|
|
597
795
|
// client-denied recursive deletion can be confirmed and retried normally.
|
|
598
|
-
for (const name of ['releases', 'bin', 'state.json']) fs.rmSync(path.join(home, name), { recursive: true, force: true });
|
|
796
|
+
for (const name of [autoUpdateLock, autoUpdateFile, autoUpdateNotice, 'releases', 'bin', 'state.json']) fs.rmSync(path.join(home, name), { recursive: true, force: true });
|
|
599
797
|
const finished = performance.now();
|
|
600
798
|
const elapsed = (from, to) => Math.round((to - from) * 100) / 100;
|
|
601
799
|
emit({ ok: true, action: 'uninstall', complete: npmPackage.status !== 'unconfirmed', home,
|
|
@@ -625,7 +823,7 @@ function doctor(home) {
|
|
|
625
823
|
checks.python = { status: runtime.available ? 'ok' : 'unavailable', ...runtime };
|
|
626
824
|
const errors = [integrityError, ...(checks.skills.entries || []).filter(entry => entry.status === 'error').map(entry => entry.error)].filter(Boolean);
|
|
627
825
|
const result = { ok: errors.length === 0, version: installed?.state?.version ?? null, integrity: checks.integrity.status,
|
|
628
|
-
python: runtime,
|
|
826
|
+
python: runtime, skills: installed?.state?.skills || [], checks };
|
|
629
827
|
if (result.ok) emit(result);
|
|
630
828
|
else { process.stderr.write(`${JSON.stringify({ ...result, error: errors.join('; ') })}\n`); process.exitCode = 1; }
|
|
631
829
|
}
|
|
@@ -637,11 +835,13 @@ async function main() {
|
|
|
637
835
|
if (help) return emit(help);
|
|
638
836
|
const { command, options, rest } = argumentsFor(process.argv.slice(2));
|
|
639
837
|
const home = normalizedPath(options.home || process.env.FX_DATA_DIAGNOSTICS_HOME || installedBundleHome(packageRoot) || defaultHome());
|
|
838
|
+
if (command === '__background-update') return backgroundUpdate(home, rest[0]);
|
|
839
|
+
if (options['background-job'] && command !== 'update') throw new Error('--background-job is reserved for background updates');
|
|
640
840
|
let current;
|
|
641
841
|
try { current = ownedState(home, false); }
|
|
642
842
|
catch (error) { if (command === 'doctor') return doctor(home); throw error; }
|
|
643
843
|
const activeRoot = current?.state ? path.join(home, 'releases', current.state.version) : null;
|
|
644
|
-
const pinned = isInstalledBundle(packageRoot, home) && ['context', 'python'
|
|
844
|
+
const pinned = isInstalledBundle(packageRoot, home) && ['context', 'python'].includes(command);
|
|
645
845
|
const migrationRequired = Boolean(current?.state && current.state.lifecycle_protocol !== lifecycleProtocol);
|
|
646
846
|
if (command === 'doctor') {
|
|
647
847
|
if (!activeRoot || packageRoot === activeRoot || migrationRequired) return doctor(home);
|
|
@@ -649,7 +849,7 @@ async function main() {
|
|
|
649
849
|
// diagnostic content is damaged. Fall back here only if the CLI is unsafe.
|
|
650
850
|
try { bundleMetadata(activeRoot); } catch { return doctor(home); }
|
|
651
851
|
}
|
|
652
|
-
if (migrationRequired && (command === 'install' || (!pinned && ['context', 'python', '
|
|
852
|
+
if (migrationRequired && (command === 'install' || (!pinned && ['context', 'python', 'version', '--version'].includes(command)))) {
|
|
653
853
|
throw new Error('Existing installation requires migration: run npm install -g @fx-data/ai-diagnostics, then use the new global fx-data-diagnostics update command');
|
|
654
854
|
}
|
|
655
855
|
// npm installation is a bootstrap. Re-running it must not switch an existing
|
|
@@ -674,17 +874,33 @@ async function main() {
|
|
|
674
874
|
if (command === 'update') return update(home, options);
|
|
675
875
|
if (command === 'rollback') return update(home, options, 'rollback');
|
|
676
876
|
if (command === 'uninstall') return uninstall(home);
|
|
677
|
-
const { pkg, manifest
|
|
877
|
+
const { pkg, manifest } = verifyBundle(packageRoot, { allowMacMetadata: isInstalledBundle(packageRoot, home) });
|
|
678
878
|
if (command === '--version' || command === 'version') return emit({ version: pkg.version, package: pkg.name });
|
|
679
|
-
if (command === 'status')
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
879
|
+
if (command === 'status') {
|
|
880
|
+
const auto = autoUpdateDetails(home);
|
|
881
|
+
return emit({ ok: true, installed: Boolean(current?.state), home, ...current?.state, lifecycle_protocol: current?.state?.lifecycle_protocol ?? null, migration_required: migrationRequired, launcher: current?.state ? launchers(home).executable : null, launcher_script: current?.state ? launchers(home).script : null, node: process.execPath, ...(auto ? { auto_update: auto } : {}) });
|
|
882
|
+
}
|
|
883
|
+
if (command === 'context') {
|
|
884
|
+
// Capture paths before starting the worker. Direct calls to a pinned release
|
|
885
|
+
// are continuations, even if that release is still the current one.
|
|
886
|
+
const context = contextFor(packageRoot, home, pkg, manifest);
|
|
887
|
+
const auto = autoUpdateDetails(home);
|
|
888
|
+
const directPinned = isInstalledBundle(packageRoot, home)
|
|
889
|
+
&& fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
890
|
+
if (current?.state && !directPinned) startAutoUpdate(home);
|
|
891
|
+
return emit({ ...context, ...(auto ? { auto_update: auto } : {}) });
|
|
892
|
+
}
|
|
893
|
+
if (command === 'session') {
|
|
894
|
+
const auto = autoUpdateDetails(home);
|
|
895
|
+
return emit({ directory: fs.mkdtempSync(path.join(os.tmpdir(), 'core-ai-')), ...(auto ? { auto_update: auto } : {}) });
|
|
896
|
+
}
|
|
897
|
+
if (command === 'python') {
|
|
683
898
|
const runtime = python();
|
|
684
899
|
if (!runtime.available) throw new Error(runtime.requirement);
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
900
|
+
if (!rest.length) throw new Error('A script or Python option is required');
|
|
901
|
+
const auto = autoUpdateDetails(home);
|
|
902
|
+
if (auto?.notification) process.stderr.write(`${JSON.stringify({ fx_data_diagnostics: { auto_update: auto } })}\n`);
|
|
903
|
+
const result = spawnSync(runtime.executable, ['-I', '-B', ...rest], { stdio: 'inherit' });
|
|
688
904
|
if (result.error) throw result.error;
|
|
689
905
|
process.exitCode = result.status ?? 1;
|
|
690
906
|
return;
|