fuli-context 0.5.0 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/README.md +370 -202
  2. package/README.zh-CN.md +448 -0
  3. package/compose.graphiti.yml +6 -6
  4. package/dist/web/assets/AboutPage-DXh7Fwxt.css +1 -0
  5. package/dist/web/assets/AboutPage-qeO_vm0T.js +1 -0
  6. package/dist/web/assets/ConnectionsPage-CBHAdZXT.css +1 -0
  7. package/dist/web/assets/ConnectionsPage-DO0GR9Xg.js +1 -0
  8. package/dist/web/assets/KnowledgeInspector-Bjhdu2mf.css +1 -0
  9. package/dist/web/assets/KnowledgeInspector-DEW5lA1m.js +1 -0
  10. package/dist/web/assets/KnowledgeOrganizerPage-BkM568Ld.js +1 -0
  11. package/dist/web/assets/KnowledgeOrganizerPage-Cx1w1jb2.css +1 -0
  12. package/dist/web/assets/KnowledgePage-BAT4aZIm.js +1 -0
  13. package/dist/web/assets/KnowledgeWorkspace-DyGtBz--.js +4 -0
  14. package/dist/web/assets/OverviewPage-CmCcDCAs.js +1 -0
  15. package/dist/web/assets/PersonalProfilePage-C42mwKN8.js +1 -0
  16. package/dist/web/assets/{PersonalProfilePage-S8ZcG2LU.css → PersonalProfilePage-CdmEzyvZ.css} +1 -1
  17. package/dist/web/assets/PersonalProjectsPage-BwaXR62K.js +1 -0
  18. package/dist/web/assets/PublicProjectsPage-egKBulOq.js +1 -0
  19. package/dist/web/assets/ReviewPage-B3vnZ6gn.js +1 -0
  20. package/dist/web/assets/SearchableSelect-ih69qbcS.js +1 -0
  21. package/dist/web/assets/SettingsPage-C6ouU0sR.css +1 -0
  22. package/dist/web/assets/SettingsPage-oLE8mNs5.js +1 -0
  23. package/dist/web/assets/_plugin-vue_export-helper-BDNMzG2s.js +1 -0
  24. package/dist/web/assets/console-DE40hXGC.js +1 -0
  25. package/dist/web/assets/fuli-logo-Bu9WBuQe.js +1 -0
  26. package/dist/web/assets/fuli-logo-ER1wQ8Kk.png +0 -0
  27. package/dist/web/assets/i18n-CDxBs1sz.js +5 -0
  28. package/dist/web/assets/identity-BCxb1mcy.js +1 -0
  29. package/dist/web/assets/index-BzhNfzU_.js +2 -0
  30. package/dist/web/assets/index-CCOGfZzE.css +1 -0
  31. package/dist/web/assets/model-CCMdt10Q.js +1 -0
  32. package/dist/web/assets/paths-DwHeiGMT.js +1 -0
  33. package/dist/web/assets/useMinimumLoadingDisplay-BpCyBeko.css +1 -0
  34. package/dist/web/assets/useMinimumLoadingDisplay-HdI7XAmu.js +1 -0
  35. package/dist/web/assets/vue-router-Drfl1F8i.js +1 -0
  36. package/dist/web/index.html +9 -6
  37. package/docs/external-knowledge-architecture.md +279 -0
  38. package/examples/external-knowledge/markdown-folder.mjs +235 -0
  39. package/graph-provider/fuli_graph/app.py +57 -1
  40. package/graph-provider/fuli_graph/graph_models.py +3 -0
  41. package/graph-provider/fuli_graph/graph_query.py +62 -16
  42. package/graph-provider/fuli_graph/knowledge_review.py +657 -0
  43. package/graph-provider/fuli_graph/knowledge_review_models.py +133 -0
  44. package/graph-provider/fuli_graph/knowledge_search.py +51 -4
  45. package/graph-provider/fuli_graph/models.py +19 -0
  46. package/graph-provider/fuli_graph/runtime.py +15 -0
  47. package/graph-provider/fuli_graph/store_knowledge.py +2 -1
  48. package/graph-provider/pyproject.toml +1 -1
  49. package/npm-shrinkwrap.json +5 -407
  50. package/package.json +7 -5
  51. package/skills/capturing-session-knowledge/SKILL.md +24 -4
  52. package/skills/flreview/SKILL.md +100 -0
  53. package/skills/flreview/agents/openai.yaml +6 -0
  54. package/src/agent-tools/graph-definitions.js +81 -6
  55. package/src/agent-tools/graph-handlers.js +12 -0
  56. package/src/cli/command-registry.js +3 -3
  57. package/src/cli/local-runtime-command.js +51 -28
  58. package/src/cli/local-runtime-options.js +14 -8
  59. package/src/cli/setup-agent-selection.js +12 -12
  60. package/src/cli/setup-command.js +41 -18
  61. package/src/cli/shell-argument.js +10 -0
  62. package/src/cli/uninstall-command.js +17 -15
  63. package/src/cli/update-command.js +38 -30
  64. package/src/external-knowledge/catalog.js +61 -0
  65. package/src/external-knowledge/conflict-policy.js +58 -0
  66. package/src/external-knowledge/connected-search.js +166 -0
  67. package/src/external-knowledge/connectors/custom.js +133 -0
  68. package/src/external-knowledge/connectors/feishu.js +461 -0
  69. package/src/external-knowledge/connectors/mcp.js +438 -0
  70. package/src/external-knowledge/connectors/notion.js +307 -0
  71. package/src/external-knowledge/connectors/retrieval-api.js +222 -0
  72. package/src/external-knowledge/document-mapping.js +151 -0
  73. package/src/external-knowledge/graph-projection.js +17 -0
  74. package/src/external-knowledge/index.js +8 -0
  75. package/src/external-knowledge/registry.js +91 -0
  76. package/src/external-knowledge/runtime.js +68 -0
  77. package/src/external-knowledge/safe-diagnostic.js +14 -0
  78. package/src/external-knowledge/service.js +811 -0
  79. package/src/graphiti/agent-knowledge-workflows.js +7 -2
  80. package/src/graphiti/federated-application.js +68 -42
  81. package/src/graphiti/knowledge-item-role.js +21 -0
  82. package/src/graphiti/knowledge-review.js +43 -0
  83. package/src/graphiti/project-profile-mapping.js +32 -0
  84. package/src/graphiti/provider-client.js +22 -1
  85. package/src/graphiti/source-marker.js +54 -3
  86. package/src/graphiti/task-knowledge-recall.js +250 -0
  87. package/src/http/api-router.js +23 -108
  88. package/src/http/external-knowledge-api-router.js +104 -0
  89. package/src/http/graph-api-router.js +12 -1
  90. package/src/http/request-policy.js +1 -1
  91. package/src/local-runtime/lifecycle.js +26 -15
  92. package/src/mcp/create-mcp-server.js +4 -1
  93. package/src/mcp/instructions.js +7 -7
  94. package/src/mcp/tool-annotations.js +6 -0
  95. package/src/mcp/tool-result.js +1 -0
  96. package/src/server/application-lifecycle.js +2 -11
  97. package/src/server/blocked-ports.js +11 -0
  98. package/src/server.js +36 -17
  99. package/src/setup/agent-installation-status.js +2 -1
  100. package/src/setup/agents.js +2 -1
  101. package/src/setup/claude-code-config.js +2 -1
  102. package/src/setup/codex-bootstrap.js +12 -7
  103. package/src/setup/container-runtime.js +25 -21
  104. package/src/setup/graph-runtime.js +148 -56
  105. package/src/setup/node-runtime.js +3 -3
  106. package/src/setup/options.js +2 -4
  107. package/src/setup/paths.js +18 -3
  108. package/src/setup/review-skill-adapters.js +49 -0
  109. package/src/setup/setup.js +26 -2
  110. package/src/setup/uninstall.js +4 -2
  111. package/src/system/resource-monitor.js +317 -0
  112. package/src/system/runtime-settings.js +131 -0
  113. package/src/system/system-service.js +119 -0
  114. package/README.en.md +0 -336
  115. package/dist/web/assets/ConnectionsPage-CCKWm-6p.js +0 -1
  116. package/dist/web/assets/KnowledgeInspector-BZkB2wJn.js +0 -1
  117. package/dist/web/assets/KnowledgeInspector-CrGYxEob.css +0 -1
  118. package/dist/web/assets/KnowledgeOrganizerPage-CZAF_OlN.css +0 -1
  119. package/dist/web/assets/KnowledgeOrganizerPage-DlvcLwlL.js +0 -1
  120. package/dist/web/assets/KnowledgePage-CNQXd15y.js +0 -1
  121. package/dist/web/assets/KnowledgeWorkspace-CXPE4GCD.js +0 -4
  122. package/dist/web/assets/OverviewPage-ul7g7HXh.js +0 -1
  123. package/dist/web/assets/PersonalProfilePage-CC40Pw2L.js +0 -1
  124. package/dist/web/assets/PersonalProjectsPage-CPIEcShD.js +0 -1
  125. package/dist/web/assets/PublicProjectsPage-C700sR7z.js +0 -1
  126. package/dist/web/assets/ReviewPage-7MDDUDgA.js +0 -1
  127. package/dist/web/assets/console-B_Ena2DS.js +0 -5
  128. package/dist/web/assets/fuli-logo-DGuyqchQ.png +0 -0
  129. package/dist/web/assets/identity-CjBwYSMX.js +0 -1
  130. package/dist/web/assets/index-CpLDx3of.css +0 -1
  131. package/dist/web/assets/index-DAzeFJSc.js +0 -2
  132. package/dist/web/assets/model-C-5RVIJ2.js +0 -1
  133. package/dist/web/assets/paths-BkULqmGI.js +0 -1
  134. package/dist/web/assets/vue-router-DnD3hUm7.js +0 -1
  135. package/src/agent/candidate-read-service.js +0 -53
  136. package/src/agent/context-pack-service.js +0 -83
  137. package/src/agent/public-read-policy.js +0 -39
  138. package/src/agent/public-read-service.js +0 -69
  139. package/src/agent/read-projection.js +0 -107
  140. package/src/agent/search-service.js +0 -61
  141. package/src/agent/space-boundary.js +0 -33
  142. package/src/app/bootstrap.js +0 -8
  143. package/src/app/create-application.js +0 -143
  144. package/src/app/state-service.js +0 -38
  145. package/src/candidates.js +0 -78
  146. package/src/classifier.js +0 -63
  147. package/src/cli/command-arguments.js +0 -14
  148. package/src/context-pack.js +0 -206
  149. package/src/extractor.js +0 -77
  150. package/src/fact-history.js +0 -39
  151. package/src/facts/fact-service.js +0 -89
  152. package/src/http/bounded-json-array.js +0 -19
  153. package/src/http/lens-route.js +0 -50
  154. package/src/ingestion.js +0 -161
  155. package/src/lens/lens-correction-service.js +0 -81
  156. package/src/lens/lens-fact-writer.js +0 -85
  157. package/src/lens/lens-input.js +0 -62
  158. package/src/lens/lens-query-projection.js +0 -87
  159. package/src/lens/lens-query-ranking.js +0 -62
  160. package/src/lens/lens-query.js +0 -143
  161. package/src/lens/lens-resource-projection.js +0 -93
  162. package/src/lens/lens-resource-selection.js +0 -45
  163. package/src/lens/lens-resource-service.js +0 -103
  164. package/src/lens/lens-search-query.js +0 -122
  165. package/src/lens/lens-service.js +0 -183
  166. package/src/models.js +0 -44
  167. package/src/observer.js +0 -83
  168. package/src/project-rules.js +0 -33
  169. package/src/publication/outbox-service.js +0 -46
  170. package/src/publication/publication-envelope-schema.js +0 -75
  171. package/src/publication/publication-envelope.js +0 -37
  172. package/src/publication/publication-policy.js +0 -82
  173. package/src/publication/publication-service.js +0 -48
  174. package/src/router.js +0 -58
  175. package/src/runtime-options.js +0 -138
  176. package/src/security/episode-visibility.js +0 -14
  177. package/src/security/fact-visibility.js +0 -15
  178. package/src/security/source-metadata-policy.js +0 -8
  179. package/src/storage/episode-evidence-preview.js +0 -100
  180. package/src/storage/fact-search-order.js +0 -18
  181. package/src/storage/file/episode-evidence.js +0 -37
  182. package/src/storage/file/episode-index.js +0 -59
  183. package/src/storage/file/fact-search.js +0 -60
  184. package/src/storage/file-store.js +0 -437
  185. package/src/storage/migrate.js +0 -128
  186. package/src/storage/migrations/001-initial.sql +0 -83
  187. package/src/storage/migrations/002-storage-integrity.sql +0 -113
  188. package/src/storage/record-validation.js +0 -46
  189. package/src/storage/snapshot-defaults.js +0 -54
  190. package/src/storage/sqlite/candidates-repository.js +0 -84
  191. package/src/storage/sqlite/episode-evidence-repository.js +0 -154
  192. package/src/storage/sqlite/episode-privacy-sql.js +0 -33
  193. package/src/storage/sqlite/episodes-repository.js +0 -52
  194. package/src/storage/sqlite/fact-search-repository.js +0 -162
  195. package/src/storage/sqlite/facts-repository.js +0 -146
  196. package/src/storage/sqlite/imports-repository.js +0 -39
  197. package/src/storage/sqlite/mapper.js +0 -84
  198. package/src/storage/sqlite/open-database.js +0 -15
  199. package/src/storage/sqlite/outbox-repository.js +0 -103
  200. package/src/storage/sqlite/snapshot-repository.js +0 -69
  201. package/src/storage/sqlite/spaces-repository.js +0 -52
  202. package/src/storage/sqlite/subscriptions-repository.js +0 -54
  203. package/src/storage/sqlite-store.js +0 -159
  204. package/src/storage/stable-json.js +0 -24
  205. package/src/storage/store-port.js +0 -20
  206. package/src/store.js +0 -2
@@ -12,14 +12,17 @@ Accumulate reusable knowledge without interrupting the user's normal work. Keep
12
12
  At the start of every user task, first use the lifecycle context supplied by the host when one
13
13
  exists. A Claude Code `UserPromptSubmit` hook calls `begin_task_context`, which already loads
14
14
  collaboration preferences and resolves the exact local personal project from the current working
15
- directory. Apply that returned context and retain its opaque task token for the final checkpoint;
15
+ directory. It also uses the submitted prompt transiently for bounded automatic project-memory
16
+ recall and never stores or returns that prompt. Apply the returned `task_knowledge_recall` and
17
+ preferences, then retain the opaque task token for the final checkpoint;
16
18
  do not redundantly call `get_collaboration_preferences`.
17
19
 
18
20
  When the host has not supplied lifecycle context, call exactly
19
21
  `get_collaboration_preferences` before any other tool or answer. Pass `projectPath` as the current
20
- working directory. This is the prompt-only fallback for Agents without an equivalent lifecycle
21
- hook. Fuli uses the path only for this local MCP call, never stores or returns it, and resolves the
22
- exact local personal project itself. Do not infer or guess `personalProjectId` in the Agent.
22
+ working directory and `taskPrompt` as the current user request. This is the prompt-only fallback
23
+ for Agents without an equivalent lifecycle hook. Fuli uses both only for this local MCP call,
24
+ never stores or returns them, and resolves the exact local personal project itself. Do not infer
25
+ or guess `personalProjectId` in the Agent.
23
26
 
24
27
  Apply only `effective_preferences`. Personal-global preferences apply in every user task;
25
28
  project-scoped preferences layer on only for the exact selected project. Do not apply items
@@ -42,6 +45,9 @@ current working directory. It resolves the exact active project without requirin
42
45
  copy an ID, searches that child project first, and then includes only inheritable knowledge from
43
46
  authorized parents or shared-source projects. Use `search_knowledge_graph` for personal-global
44
47
  context, explicitly named extra projects, subscribed public projects, or advanced scoped queries.
48
+ Use `search_connected_knowledge` for a project-bound third-party source or explicitly selected
49
+ public project. Keep its source sets separate and obey the project's conflict policy;
50
+ `agent_decide` affects only the current response and never mutates or confirms a source.
45
51
 
46
52
  Search when prior durable context can materially improve the task.
47
53
  Before saying that a stable fact is unknown or asking the user to provide it again, search
@@ -50,6 +56,15 @@ requirements, terminology, architecture, prior decisions, runbooks, rationale, a
50
56
  personal preferences. Do not search for a fully self-contained task or use Fuli as proof of live
51
57
  external state.
52
58
 
59
+ First inspect `task_knowledge_recall` returned by the lifecycle or preference tool. Its automatic
60
+ query plan is deliberately bounded and based on implementation trigger categories, not on a
61
+ claim that every returned candidate supports the task. If it misses or its candidates are
62
+ irrelevant while durable context may still matter, call `search_current_project_knowledge`
63
+ yourself. Derive one to four focused queries from the action, artifact, target system, distinctive
64
+ identifier, and likely runbook category. Never pass the full conversational request as the sole
65
+ query. For example, a version-release request should search release/publish and push/commit
66
+ runbook concepts separately instead of searching the entire sentence.
67
+
53
68
  Pending knowledge is eligible for on-demand retrieval and must remain visibly marked.
54
69
  `agent_confirmed` knowledge is usable but ranks below user or authoritative-source
55
70
  confirmation.
@@ -156,6 +171,11 @@ Capture confirmed, stable information:
156
171
  - Personal: the user's enduring preferences, boundaries, habits, and working methods. Project-specific personal rules must explicitly link to that project instead of becoming global defaults.
157
172
  - Project: PRD facts, requirements, terminology, decisions, constraints, architecture, routes, APIs, metrics, and document-derived knowledge.
158
173
 
174
+ For a runbook, decision, route, or other item likely to be recalled from shorthand, add a bounded
175
+ `attributes.searchTerms` string array containing only neutral retrieval phrases supported by the
176
+ item and observed task vocabulary. Search terms are retrieval metadata, not new factual claims;
177
+ do not add speculative synonyms, credentials, or unrelated product names.
178
+
159
179
  Do not capture credentials, tokens, cookies, private keys, raw transcripts, temporary logs, command output, speculative conclusions, or disposable implementation details. Treat a correction as a replacement, not an additional conflicting fact.
160
180
 
161
181
  For a confirmed project decision, prefer `record_decision_trace` over a flat note. Preserve the
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: flreview
3
+ description: Use when the user explicitly enters /flreview or explicitly asks to run the Fuli personal knowledge review command.
4
+ ---
5
+
6
+ # Fuli Knowledge Review
7
+
8
+ The invoking Agent owns each review session. FULI supplies durable knowledge, deterministic candidate ranking, and write tools; the Agent generates a fresh interactive review page for this invocation. There is no permanent **知识回顾** tab to open.
9
+
10
+ ## Start The Session
11
+
12
+ For every exact `/flreview` invocation or explicit request to run the Fuli knowledge review:
13
+
14
+ 1. Use `list_knowledge_spaces` to identify the active personal space, then call `list_personal_projects` so every project choice uses a real local personal project.
15
+ 2. Use the `visualize` Skill to render an inline setup page in the current task. Collect scope and review depth in that page, not as serial chat questions. Ask patience first. If the user selects **完全没耐心**, hide mood, available-time, and token-budget fields and use a small highest-priority batch. Otherwise collect mood, available time, and token comfort together on the same setup page. These answers control only batch size and explanation depth, never Provider ranking. If the user's request already gives an exact scope and depth, skip setup.
16
+ 3. On the setup page's explicit start action, call `start_knowledge_review` with the selected scope. Then call `list_knowledge_review_candidates` with a bounded page size matching the selected depth.
17
+ 4. Generate all candidates from that batch on the same interactive page. Show all candidates at once; do not reveal them one at a time.
18
+
19
+ The setup scope tree is:
20
+
21
+ - **全部** — global preferences plus every local personal project's knowledge and project-scoped preferences.
22
+ - **个人偏好** — global preferences or one selected local personal project's preferences.
23
+ - **个人项目** — one selected local personal project or all local personal projects. A project includes its knowledge and project-scoped preferences.
24
+
25
+ Never infer the current directory as the selected project. Never include subscribed or public projects.
26
+
27
+ A bounded page limit is a batch size, not a fixed total-question cap.
28
+
29
+ ## Generated Review Page
30
+
31
+ Follow the `visualize` Skill's inline-artifact rules. The artifact must not use `fetch`, XHR, WebSocket, or a direct FULI API. Every submitted action must call:
32
+
33
+ ```js
34
+ await window.openai.sendFollowUpMessage({ prompt, title })
35
+ ```
36
+
37
+ The setup action envelope contains only `action`, `personal_space_id`, scope, optional project, and depth inputs. It must not invent a `review_id`, `candidate_key`, `item_id`, or `item_kind`. Every candidate action envelope contains `personal_space_id`, `review_id`, `candidate_key`, `item_id`, and `item_kind`, plus only the fields needed for that action. Treat a valid envelope as a continuation of the active review, not as a new `/flreview` invocation.
38
+
39
+ Each candidate card shows its content, current scope and source projects, confirmation state, `current_quadrant`, selection reasons, relevant evidence counts, utility, and confidence. Provider order is authoritative; do not invent a second score. Render these actions:
40
+
41
+ - **确认保留**
42
+ - **修改**
43
+ - **调整范围**
44
+ - **失效**
45
+ - **稍后处理**
46
+ - **交给 AI 判断**
47
+
48
+ Do not present the review as one candidate at a time in chat. Chat may contain only a concise status and the generated interactive artifact.
49
+
50
+ ## Action Semantics
51
+
52
+ Do not mutate knowledge until the user submits an explicit visualization action. After any mutation, call `record_knowledge_review_progress` only after the write succeeds. If progress recording fails after a successful write, retry only progress; never repeat the mutation.
53
+
54
+ ### 确认保留
55
+
56
+ If the candidate is already confirmed, record `confirmed`. If it is pending, first call `revise_personal_knowledge` with `action: confirm`, an auditable user confirmation basis, and no content or taxonomy changes; then record `confirmed`.
57
+
58
+ ### 修改
59
+
60
+ Collect the exact replacement content and a reason in the card. Call `revise_personal_knowledge` with `action: update`, then record `updated`.
61
+
62
+ ### 调整范围
63
+
64
+ - For a global preference with exactly one source project, offer a one-click move to that source project. With multiple source projects, require a project selection. Call `set_personal_preference_scope` with `scope: project` and the selected source project.
65
+ - For a project-scoped preference, offer promotion to global and call `set_personal_preference_scope` with `scope: global`.
66
+ - Ordinary project knowledge is not a personal preference. Never offer to turn ordinary project knowledge into a global preference.
67
+
68
+ After a successful scope write, record `updated`. Never change scope automatically.
69
+
70
+ ### 失效
71
+
72
+ Offer these preset reasons and a custom reason field:
73
+
74
+ - 不应该沉淀为一条复利知识
75
+ - 不知所云
76
+ - 过期了
77
+ - 只在当时生效
78
+
79
+ Selecting a preset or entering a non-empty custom reason is sufficient authorization. There is no second confirmation and no confirmation checkbox. Call `revise_personal_knowledge` with `action: invalidate`, then record `invalidated` with the exact reason.
80
+
81
+ ### 稍后处理
82
+
83
+ Do not mutate the knowledge. Record `deferred` and remove it from the current page. FULI carries it into the next review as `deferred_from_previous` even if no other ranking threshold still matches.
84
+
85
+ ### 交给 AI 判断
86
+
87
+ This action means the user cannot responsibly classify the item now. Call `revise_personal_knowledge` with `action: update`, `currentQuadrant: unknown_unknown`, and a short `reasoningSummary` stating that the user delegated judgment during knowledge review. Never send `originQuadrant`: discovery classification is immutable evidence. The required order is `revise_personal_knowledge` before `record_knowledge_review_progress`. After that write succeeds, record `delegated_to_ai` and remove the item from the current page.
88
+
89
+ ## Continue, Pause, Or Complete
90
+
91
+ After each successful action, refresh the current review's remaining candidates and regenerate the interactive page. Load the next bounded batch when the current batch is exhausted.
92
+
93
+ Call `finish_knowledge_review` with `paused` when the user explicitly pauses. Call it with `completed` only when no candidates remain or the user explicitly finishes the selected scope. Only completion advances the last-completed watermark.
94
+
95
+ ## Guardrails
96
+
97
+ - FULI-saved knowledge is the only candidate source. Never fabricate, mock, or supplement candidates from ordinary search.
98
+ - Keep original evidence and revision history intact.
99
+ - Never silently resolve conflicts, invalidate knowledge, or change scope.
100
+ - If a required FULI or visualization capability is unavailable, report the exact missing capability and stop safely.
@@ -0,0 +1,6 @@
1
+ interface:
2
+ display_name: "Fuli Knowledge Review"
3
+ short_description: "Generate an interactive FULI knowledge review"
4
+ default_prompt: "Use $flreview to generate my interactive FULI knowledge review."
5
+ policy:
6
+ allow_implicit_invocation: true
@@ -21,6 +21,13 @@ const sourceUri = {
21
21
  pattern: '^[Hh][Tt][Tt][Pp][Ss]?://\\S+$'
22
22
  };
23
23
  const attributes = { type: 'object', additionalProperties: true };
24
+ const entityAttributes = {
25
+ type: 'object',
26
+ properties: {
27
+ searchTerms: arraySchema(boundedString(256), { minItems: 1, maxItems: 32 })
28
+ },
29
+ additionalProperties: true
30
+ };
24
31
  const epistemicQuadrant = enumSchema([
25
32
  'known_known', 'known_unknown', 'unknown_known', 'unknown_unknown'
26
33
  ]);
@@ -48,6 +55,9 @@ const confirmationBasis = objectSchema({
48
55
  agentPolicyVersion: nullableStringSchema()
49
56
  }, ['existenceReason', 'quadrantReason', 'proposedBy']);
50
57
  const knowledgeItemKind = enumSchema(['entity', 'relationship']);
58
+ const knowledgeReviewScope = enumSchema([
59
+ 'all', 'preferences_global', 'preferences_project', 'projects_all', 'project'
60
+ ]);
51
61
  const humanChangeStatus = enumSchema([
52
62
  'all', 'human_changed', 'unseen', 'viewed', 'reviewed'
53
63
  ]);
@@ -84,7 +94,7 @@ const entity = objectSchema({
84
94
  type: { ...boundedString(64), pattern: '^[A-Za-z][A-Za-z0-9_]*$' },
85
95
  summary: boundedString(4096),
86
96
  ...epistemicFields,
87
- attributes
97
+ attributes: entityAttributes
88
98
  }, [
89
99
  'key', 'name', 'type', 'originQuadrant',
90
100
  'confirmationStatus', 'confirmationBasis'
@@ -202,10 +212,11 @@ export const GRAPH_TOOL_DEFINITIONS = [
202
212
  {
203
213
  name: 'begin_task_context',
204
214
  title: 'LIFECYCLE · Begin a Fuli-aware task',
205
- description: 'Lifecycle entry used by supported Agent hooks before the model processes a user prompt. It resolves the exact local project from projectPath, loads effective collaboration preferences, and returns an opaque taskContextToken. It never stores projectPath. Claude Code setup installs this as a deterministic UserPromptSubmit hook; manual Agent calls are a fallback.',
215
+ description: 'Lifecycle entry used by supported Agent hooks before the model processes a user prompt. It resolves the exact local project from projectPath, loads effective collaboration preferences, and uses taskPrompt transiently for bounded automatic recall when the request signals a stable prior fact, runbook, URL, decision, release, deployment, or authentication method. It never stores or returns projectPath or taskPrompt. Inspect task_knowledge_recall before asking the user to repeat stable context. Returns an opaque taskContextToken. Claude Code setup installs this as a deterministic UserPromptSubmit hook; manual Agent calls are a fallback.',
206
216
  inputSchema: objectSchema({
207
217
  sessionId: id,
208
- projectPath: boundedString(4096)
218
+ projectPath: boundedString(4096),
219
+ taskPrompt: boundedString(8192)
209
220
  }, ['sessionId', 'projectPath'])
210
221
  },
211
222
  {
@@ -228,9 +239,10 @@ export const GRAPH_TOOL_DEFINITIONS = [
228
239
  {
229
240
  name: 'get_collaboration_preferences',
230
241
  title: 'READ FIRST · Load task collaboration preferences',
231
- description: 'Fallback for Agents or tasks without begin_task_context hook context. When no hook context was supplied, call this exact tool name at the start of every user task before any other tool or answer; never substitute a project action tool. Do not call it redundantly when the entry hook already supplied preferences. Pass projectPath as the current working directory; Fuli uses it transiently, never stores or returns it, and layers preferences only for one exact registered local project match. Returns effective_preferences plus deferred_conflicts intentionally queued for AI. Apply effective_preferences before answering or constructing tool arguments. Human-confirmed preferences outrank explicitly marked agent-confirmed preferences. If the current task would use a deferred conflict, call resolve_deferred_preference_conflict before applying either side; ignore unrelated deferred conflicts. For write tools, enforce preferences in the actual payload; mentioning them only in the final answer is not compliance. Personal-global preferences always apply; other conflicted, pending, invalid, ambiguous-project, and unrelated-project items never auto-apply. Automatic injection does not count as usage evidence. personalProjectId remains an explicit compatibility override.',
242
+ description: 'Fallback for Agents or tasks without begin_task_context hook context. When no hook context was supplied, call this exact tool name at the start of every user task before any other tool or answer; never substitute a project action tool. Do not call it redundantly when the entry hook already supplied preferences. Pass projectPath as the current working directory and taskPrompt as the current user request. Fuli uses both transiently, never stores or returns them, resolves one exact registered local project, and performs bounded automatic recall only when the request signals stable prior context. Inspect task_knowledge_recall before asking the user to repeat a project fact or method. On a miss, search_current_project_knowledge with one to four focused action, artifact, target-system, or identifier queries; never use the full conversational request as the only query. Apply effective_preferences before answering or constructing tool arguments. Resolve only relevant deferred_conflicts first. For write tools, enforce preferences in the actual payload; the final answer is not compliance. Personal-global preferences always apply; conflicted, pending, invalid, ambiguous-project, and unrelated-project items never auto-apply. Automatic injection does not count as usage evidence. personalProjectId remains an explicit compatibility override.',
232
243
  inputSchema: objectSchema({
233
244
  projectPath: boundedString(4096),
245
+ taskPrompt: boundedString(8192),
234
246
  personalProjectId: nullableStringSchema(),
235
247
  limit: integerSchema({ minimum: 1, maximum: 200 })
236
248
  }, ['projectPath'])
@@ -323,6 +335,22 @@ export const GRAPH_TOOL_DEFINITIONS = [
323
335
  includePending: booleanSchema()
324
336
  }, ['personalSpaceId', 'query'])
325
337
  },
338
+ {
339
+ name: 'search_connected_knowledge',
340
+ title: 'READ · Search personal, public, and third-party knowledge',
341
+ description: 'Search the exact personal project, explicitly selected subscribed public projects, and live read-only third-party bindings in one request. Use all_local_confirmed only after explicit user confirmation. Results remain separated by source and preserve provenance; public-space integration is Beta. The response includes the project conflict policy. Under ask_human, surface a material conflict in the Agent conversation and ask the user before selecting durable truth. Under agent_decide, the Agent may choose for the current response only, but must explain its basis and sources. This tool never rewrites, confirms, invalidates, or publishes underlying knowledge.',
342
+ inputSchema: objectSchema({
343
+ personalSpaceId: id,
344
+ personalProjectId: id,
345
+ query: shortText,
346
+ contextPersonalProjectIds: arraySchema(id, { maxItems: 15 }),
347
+ personalProjectScope: enumSchema(['bounded', 'all_local_confirmed']),
348
+ projectIds: arraySchema(id, { maxItems: 32 }),
349
+ limit: integerSchema({ minimum: 1, maximum: 100 }),
350
+ includeHistorical: booleanSchema(),
351
+ includePending: booleanSchema()
352
+ }, ['personalSpaceId', 'personalProjectId', 'query'])
353
+ },
326
354
  {
327
355
  name: 'record_knowledge_usage',
328
356
  description: 'Record that personal knowledge materially affected the Agent final answer or completed action. Call only after the use actually occurs: cited means the answer explicitly relies on the item; applied means the item changed an implementation or decision. Retrieval, inspection, automatic preference injection, and merely appearing in context do not qualify. taskId must be the caller-stable identifier for the current user task and must be reused on retries; do not generate a fresh ID to recount the same task. Events are idempotent per knowledge item, task, use kind, and content generation. Repeated qualified use may promote pending knowledge to agent_confirmed, never to human-confirmed or public-eligible.',
@@ -463,15 +491,62 @@ export const GRAPH_TOOL_DEFINITIONS = [
463
491
  description: 'List local personal project profiles and their current evidence-backed coverage summaries.',
464
492
  inputSchema: objectSchema({ personalSpaceId: id })
465
493
  },
494
+ {
495
+ name: 'start_knowledge_review',
496
+ title: 'REVIEW · Start or resume personal knowledge review',
497
+ description: 'Start or resume the exact selected /flreview scope. A first review has no watermark and scans all in-scope history. A later review uses only the last completed run as its watermark; paused runs never advance it. Project scopes accept only local personal project IDs.',
498
+ inputSchema: objectSchema({
499
+ personalSpaceId: id,
500
+ scope: knowledgeReviewScope,
501
+ personalProjectId: nullableStringSchema()
502
+ }, ['personalSpaceId', 'scope'])
503
+ },
504
+ {
505
+ name: 'list_knowledge_review_candidates',
506
+ title: 'REVIEW · List ranked personal knowledge candidates',
507
+ description: 'List one bounded page for an active or paused review. Provider policy ranks new/changed knowledge, conflicts or attention, low-weight knowledge, then repeated cross-session patterns. Already decided items stay out of the current run. The page limit is not a total-question cap.',
508
+ inputSchema: objectSchema({
509
+ personalSpaceId: id,
510
+ reviewId: id,
511
+ limit: integerSchema({ minimum: 1, maximum: 50 })
512
+ }, ['personalSpaceId', 'reviewId'])
513
+ },
514
+ {
515
+ name: 'record_knowledge_review_progress',
516
+ title: 'WRITE · Record one review outcome',
517
+ description: 'Record one user-authorized candidate outcome after any required knowledge mutation succeeds. confirmed keeps the item, updated/invalidated follow successful writes, deferred carries it into the next review, and delegated_to_ai follows a successful current-quadrant change to unknown_unknown.',
518
+ inputSchema: objectSchema({
519
+ personalSpaceId: id,
520
+ reviewId: id,
521
+ candidateKey: {
522
+ ...boundedString(520),
523
+ pattern: '^(entity|relationship):.+'
524
+ },
525
+ outcome: enumSchema([
526
+ 'confirmed', 'updated', 'invalidated', 'deferred', 'delegated_to_ai'
527
+ ]),
528
+ note: nullableStringSchema()
529
+ }, ['personalSpaceId', 'reviewId', 'candidateKey', 'outcome'])
530
+ },
531
+ {
532
+ name: 'finish_knowledge_review',
533
+ title: 'WRITE · Pause or complete knowledge review',
534
+ description: 'Pause a review without moving its watermark, or complete it and establish the next run watermark. Use completed only when the selected scope is exhausted or the user explicitly finishes it.',
535
+ inputSchema: objectSchema({
536
+ personalSpaceId: id,
537
+ reviewId: id,
538
+ disposition: enumSchema(['paused', 'completed'])
539
+ }, ['personalSpaceId', 'reviewId', 'disposition'])
540
+ },
466
541
  {
467
542
  name: 'revise_personal_knowledge',
468
- description: 'Correct, invalidate, or restore one personal entity or relationship while preserving revision history and original evidence. This is the same personal-only operation used by the management UI.',
543
+ description: 'Confirm, correct, invalidate, or restore one personal entity or relationship while preserving revision history and original evidence. This is the same personal-only operation used by the management UI.',
469
544
  inputSchema: objectSchema({
470
545
  personalSpaceId: id,
471
546
  personalProjectId: nullableStringSchema(),
472
547
  itemKind: knowledgeItemKind,
473
548
  itemId: id,
474
- action: enumSchema(['update', 'invalidate', 'restore']),
549
+ action: enumSchema(['confirm', 'update', 'invalidate', 'restore']),
475
550
  reason: shortText,
476
551
  name: nullableStringSchema(),
477
552
  summary: nullableStringSchema(),
@@ -22,6 +22,12 @@ const HANDLERS = Object.freeze({
22
22
  agentInvocation: true,
23
23
  agentToolName: 'search_knowledge_graph'
24
24
  }),
25
+ search_connected_knowledge: (app, input) => {
26
+ if (!app.connectedKnowledge) {
27
+ throw new Error('Connected knowledge runtime is unavailable');
28
+ }
29
+ return app.connectedKnowledge.query(input);
30
+ },
25
31
  record_knowledge_usage: (app, input) => app.recordKnowledgeUsage(input),
26
32
  record_knowledge_feedback: (app, input) => app.recordKnowledgeFeedback(input),
27
33
  search_current_project_knowledge: (app, input) =>
@@ -53,6 +59,12 @@ const HANDLERS = Object.freeze({
53
59
  app.upsertPersonalProject(input)
54
60
  ),
55
61
  list_personal_projects: (app, input) => app.listPersonalProjects(input),
62
+ start_knowledge_review: (app, input) => app.startKnowledgeReview(input),
63
+ list_knowledge_review_candidates: (app, input) =>
64
+ app.listKnowledgeReviewCandidates(input),
65
+ record_knowledge_review_progress: (app, input) =>
66
+ app.recordKnowledgeReviewProgress(input),
67
+ finish_knowledge_review: (app, input) => app.finishKnowledgeReview(input),
56
68
  revise_personal_knowledge: (app, input) => app.reviseKnowledgeItem({
57
69
  ...input,
58
70
  operationActor: 'agent'
@@ -12,10 +12,10 @@ General:
12
12
  --version, -v
13
13
 
14
14
  Local service:
15
- start [--port 2727] [--open] [--lan] [--rebuild] [--data-dir DIR] [--personal-space NAME]
15
+ start [--port PORT] [--open] [--lan|--no-lan] [--rebuild] [--data-dir DIR] [--personal-space NAME]
16
16
  stop [--data-dir DIR]
17
- restart [--port 2727] [--open] [--lan] [--rebuild] [--data-dir DIR] [--personal-space NAME]
18
- status [--json] [--data-dir DIR]
17
+ restart [--port PORT] [--open] [--lan|--no-lan] [--rebuild] [--data-dir DIR] [--personal-space NAME]
18
+ status [--json] [--data-dir DIR] [--port PORT]
19
19
  open [--data-dir DIR]
20
20
 
21
21
  Install and Agent connection:
@@ -6,6 +6,11 @@ import {
6
6
  stopLocalRuntime
7
7
  } from '../local-runtime/lifecycle.js';
8
8
  import { resolveSetupPaths } from '../setup/paths.js';
9
+ import {
10
+ DEFAULT_RUNTIME_SETTINGS,
11
+ readRuntimeSettings,
12
+ runtimeSettingsWithOverrides
13
+ } from '../system/runtime-settings.js';
9
14
  import { parseLocalRuntimeOptions } from './local-runtime-options.js';
10
15
 
11
16
  export async function runLocalRuntimeCommand(command, args, dependencies = {}) {
@@ -13,8 +18,24 @@ export async function runLocalRuntimeCommand(command, args, dependencies = {}) {
13
18
  const resolvePaths = dependencies.resolvePaths ?? resolveSetupPaths;
14
19
  const env = dependencies.env ?? process.env;
15
20
  const paths = resolvePaths({ dataDir: options.dataDir, env });
21
+ const readSettings = dependencies.readSettings ?? readRuntimeSettings;
22
+ const savedSettings = paths.runtimeSettingsPath
23
+ ? readSettings(paths.runtimeSettingsPath)
24
+ : DEFAULT_RUNTIME_SETTINGS;
25
+ const runtimeSettings = runtimeSettingsWithOverrides(savedSettings, {
26
+ consolePort: options.port,
27
+ lanAccess: options.lan
28
+ });
16
29
  const write = dependencies.write ?? writeLine;
17
- const input = { ...options, paths, env, onProgress: write };
30
+ const input = {
31
+ ...options,
32
+ port: runtimeSettings.ports.console,
33
+ lan: runtimeSettings.lanAccess,
34
+ runtimeSettings,
35
+ paths,
36
+ env,
37
+ onProgress: write
38
+ };
18
39
  const handlers = {
19
40
  start: dependencies.start ?? startLocalRuntime,
20
41
  stop: dependencies.stop ?? stopLocalRuntime,
@@ -39,37 +60,39 @@ export async function runLocalRuntimeCommand(command, args, dependencies = {}) {
39
60
 
40
61
  export function formatLocalRuntimeResult(command, result) {
41
62
  if (command === 'start') {
42
- const verb = result.status === 'running' ? '已经在运行' : '已启动';
43
- return formatStartedRuntime(`Fuli 本地服务${verb}。`, result);
63
+ const title = result.status === 'running'
64
+ ? 'Fuli local services are already running.'
65
+ : 'Fuli local services started.';
66
+ return formatStartedRuntime(title, result);
44
67
  }
45
68
  if (command === 'restart') {
46
- return formatStartedRuntime('Fuli 本地服务已重启。', result);
69
+ return formatStartedRuntime('Fuli local services restarted.', result);
47
70
  }
48
71
  if (command === 'stop') {
49
72
  if (result.status === 'partial') {
50
73
  return [
51
- 'Fuli 本地 Provider 已停止,但没有终止无法验证身份的界面进程。',
52
- '请执行 fl status 检查,避免误停其他进程。'
74
+ 'The local Fuli Provider stopped, but the unverified UI process was left running.',
75
+ 'Run fl status to inspect it without risking another process.'
53
76
  ].join('\n');
54
77
  }
55
78
  return result.console === 'not_running' && result.providers === 'not_initialized'
56
- ? 'Fuli 本地服务尚未初始化。'
57
- : 'Fuli 本地服务已停止,图谱数据已保留。';
79
+ ? 'Fuli local services have not been initialized.'
80
+ : 'Fuli local services stopped. Graph data was preserved.';
58
81
  }
59
- if (command === 'open') return `已打开:${result.url}`;
82
+ if (command === 'open') return `Opened: ${result.url}`;
60
83
  if (command === 'status') return formatStatus(result);
61
84
  throw new TypeError(`Unknown local runtime command: ${command}`);
62
85
  }
63
86
 
64
87
  function formatStartedRuntime(title, result) {
65
- const lines = [title, `管理界面:${result.url}`];
88
+ const lines = [title, `Management UI: ${result.url}`];
66
89
  if (result.lan === true) {
67
90
  lines.push(
68
- '局域网界面:',
91
+ 'LAN URLs:',
69
92
  ...result.lanUrls.map((url) => ` ${url}`),
70
- `访问用户名:${result.lanAccess.username}`,
71
- `临时访问口令:${result.lanAccess.accessCode}`,
72
- '仅在可信 Wi-Fi 中使用;重新启动局域网模式会更换口令。'
93
+ `Username: ${result.lanAccess.username}`,
94
+ `Temporary access code: ${result.lanAccess.accessCode}`,
95
+ 'Use only on trusted Wi-Fi. Restarting LAN mode rotates the access code.'
73
96
  );
74
97
  }
75
98
  return lines.join('\n');
@@ -77,24 +100,24 @@ function formatStartedRuntime(title, result) {
77
100
 
78
101
  function formatStatus(result) {
79
102
  const labels = {
80
- running: '运行中',
81
- degraded: '部分可用',
82
- stopped: '已停止',
83
- not_configured: '未初始化',
84
- ready: '可用',
85
- unavailable: '不可用',
86
- not_connected: '未连接',
87
- not_configured_provider: '未配置',
88
- unverified: '身份未验证'
103
+ running: 'running',
104
+ degraded: 'degraded',
105
+ stopped: 'stopped',
106
+ not_configured: 'not initialized',
107
+ ready: 'ready',
108
+ unavailable: 'unavailable',
109
+ not_connected: 'not connected',
110
+ not_configured_provider: 'not configured',
111
+ unverified: 'identity unverified'
89
112
  };
90
113
  const publicSummary = result.public.configured
91
- ? `${labels[result.public.status] ?? result.public.status}(${result.public.providers.length} 个 Provider)`
114
+ ? `${labels[result.public.status] ?? result.public.status} (${result.public.providers.length} Providers)`
92
115
  : labels.not_connected;
93
116
  return [
94
- `Fuli 本地状态:${labels[result.status] ?? result.status}`,
95
- `管理界面:${labels[result.console.status] ?? result.console.status} · ${result.console.url}`,
96
- `个人图谱:${providerLabel(result.personal.status, labels)}`,
97
- `公共服务:${publicSummary}`
117
+ `Fuli local status: ${labels[result.status] ?? result.status}`,
118
+ `Management UI: ${labels[result.console.status] ?? result.console.status} · ${result.console.url}`,
119
+ `Personal graph: ${providerLabel(result.personal.status, labels)}`,
120
+ `Shared services: ${publicSummary}`
98
121
  ].join('\n');
99
122
  }
100
123
 
@@ -1,5 +1,3 @@
1
- import { DEFAULT_FULI_PORT } from '../defaults.js';
2
-
3
1
  const VALUE_OPTIONS = Object.freeze({
4
2
  '--data-dir': 'dataDir',
5
3
  '--personal-space': 'personalSpaceName',
@@ -9,15 +7,16 @@ const VALUE_OPTIONS = Object.freeze({
9
7
  const BOOLEAN_OPTIONS = Object.freeze({
10
8
  '--open': 'open',
11
9
  '--rebuild': 'rebuild',
12
- '--lan': 'lan',
13
10
  '--json': 'json'
14
11
  });
15
12
 
16
13
  const ALLOWED_BY_COMMAND = Object.freeze({
17
- start: new Set(['--data-dir', '--personal-space', '--port', '--open', '--rebuild', '--lan']),
14
+ start: new Set([
15
+ '--data-dir', '--personal-space', '--port', '--open', '--rebuild', '--lan', '--no-lan'
16
+ ]),
18
17
  stop: new Set(['--data-dir']),
19
18
  restart: new Set([
20
- '--data-dir', '--personal-space', '--port', '--open', '--rebuild', '--lan'
19
+ '--data-dir', '--personal-space', '--port', '--open', '--rebuild', '--lan', '--no-lan'
21
20
  ]),
22
21
  status: new Set(['--data-dir', '--port', '--json']),
23
22
  open: new Set(['--data-dir'])
@@ -28,11 +27,11 @@ export function parseLocalRuntimeOptions(command, args = []) {
28
27
  if (!allowed) throw new TypeError(`Unknown local runtime command: ${command}`);
29
28
  const result = {
30
29
  dataDir: null,
31
- personalSpaceName: '我',
32
- port: DEFAULT_FULI_PORT,
30
+ personalSpaceName: 'Personal',
31
+ port: null,
33
32
  open: false,
34
33
  rebuild: false,
35
- lan: false,
34
+ lan: null,
36
35
  json: false
37
36
  };
38
37
  const seen = new Set();
@@ -43,6 +42,13 @@ export function parseLocalRuntimeOptions(command, args = []) {
43
42
  if (seen.has(flag)) throw new TypeError(`Duplicate ${flag}`);
44
43
  seen.add(flag);
45
44
 
45
+ if (flag === '--lan' || flag === '--no-lan') {
46
+ const conflicting = flag === '--lan' ? '--no-lan' : '--lan';
47
+ if (seen.has(conflicting)) throw new TypeError('--lan and --no-lan cannot be combined');
48
+ result.lan = flag === '--lan';
49
+ continue;
50
+ }
51
+
46
52
  const booleanKey = BOOLEAN_OPTIONS[flag];
47
53
  if (booleanKey) {
48
54
  result[booleanKey] = true;
@@ -27,15 +27,15 @@ export function parseAgentSelection(value, agents) {
27
27
 
28
28
  const tokens = answer.split(/[\s,,]+/).filter(Boolean);
29
29
  if (tokens.includes('0')) {
30
- throw new TypeError('0 不能与其他编号同时使用');
30
+ throw new TypeError('0 cannot be combined with another number');
31
31
  }
32
32
  if (tokens.some((token) => !/^\d+$/.test(token))) {
33
- throw new TypeError('请输入编号,不要输入 Agent 名称');
33
+ throw new TypeError('Enter numbers, not Agent names');
34
34
  }
35
35
 
36
36
  const positions = new Set(tokens.map(Number));
37
37
  if ([...positions].some((position) => position < 1 || position > agents.length)) {
38
- throw new RangeError(`可选范围是 1-${agents.length}`);
38
+ throw new RangeError(`Choose a number from 1-${agents.length}`);
39
39
  }
40
40
  return agents
41
41
  .filter((_, index) => positions.has(index + 1))
@@ -86,21 +86,21 @@ export function selectedAgentIds(agents, state) {
86
86
 
87
87
  export function formatAgentCheckboxSelection(agents, state) {
88
88
  return [
89
- '检测到多个可接入的 Agent:',
89
+ 'Multiple supported Agents were detected:',
90
90
  ...agents.map((agent, index) => {
91
91
  const pointer = index === state.activeIndex ? '❯' : ' ';
92
92
  const checkbox = state.selected[index] ? '[x]' : '[ ]';
93
93
  const status = integrationStatusLabel(agent.integrationStatus);
94
94
  return `${pointer} ${checkbox} ${agent.label}${status ? ` ${status}` : ''}`;
95
95
  }),
96
- '↑/↓ 移动 · 空格切换 · A 全选/清空 · 回车确认'
96
+ '↑/↓ Move · Space Toggle · A Select/Clear all · Enter Confirm'
97
97
  ].join('\n');
98
98
  }
99
99
 
100
100
  function integrationStatusLabel(status) {
101
- if (status === 'connected') return '已接入';
102
- if (status === 'update_available') return '需更新';
103
- if (status === 'not_connected') return '未接入';
101
+ if (status === 'connected') return 'connected';
102
+ if (status === 'update_available') return 'update available';
103
+ if (status === 'not_connected') return 'not connected';
104
104
  return '';
105
105
  }
106
106
 
@@ -147,7 +147,7 @@ async function promptCheckboxAgentSelection(agents, { input, output }) {
147
147
  return;
148
148
  }
149
149
  if (result.action === 'cancel') {
150
- finish(() => reject(new Error('Setup 已取消')));
150
+ finish(() => reject(new Error('Setup cancelled')));
151
151
  return;
152
152
  }
153
153
  if (result.action === 'update') {
@@ -161,7 +161,7 @@ async function promptCheckboxAgentSelection(agents, { input, output }) {
161
161
 
162
162
  async function promptTextAgentSelection(agents, { input, output }) {
163
163
  const terminal = createInterface({ input, output });
164
- output.write('检测到多个可接入的 Agent:\n');
164
+ output.write('Multiple supported Agents were detected:\n');
165
165
  for (const [index, agent] of agents.entries()) {
166
166
  output.write(` ${index + 1}. ${agent.label}\n`);
167
167
  }
@@ -169,12 +169,12 @@ async function promptTextAgentSelection(agents, { input, output }) {
169
169
  try {
170
170
  while (true) {
171
171
  const answer = await terminal.question(
172
- '选择要接入的 Agent(编号以逗号分隔,回车全选,0 跳过)[全部] '
172
+ 'Select Agents (comma-separated numbers, Enter for all, 0 to skip) [all] '
173
173
  );
174
174
  try {
175
175
  return parseAgentSelection(answer, agents);
176
176
  } catch (error) {
177
- output.write(`选择无效:${error.message}\n`);
177
+ output.write(`Invalid selection: ${error.message}\n`);
178
178
  }
179
179
  }
180
180
  } finally {