fuli-context 0.12.1 → 0.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,8 +18,9 @@
18
18
 
19
19
  Fuli is a collaboration layer for long-lived agents across MCP-compatible AI clients. Connect
20
20
  your tools to the same accessible Fuli data, select the same agent, and continue with its identity,
21
- project responsibilities, and relevant working context. Codex, Claude Code and Cursor have
22
- automatic setup; other clients use `fuli connect`. See [client connections](docs/client-connections.md).
21
+ project responsibilities, and relevant working context. Use `fuli connect` for standard MCP
22
+ configuration, or automatic setup where a client adapter is available (Codex, Claude Code, Cursor, and Claude
23
+ Desktop with a compact chat tool profile). See [client connections](docs/client-connections.md).
23
24
 
24
25
  - **A lasting teammate:** keep an agent's role, collaboration history, and working memory between tasks.
25
26
  - **Context with boundaries:** retrieve relevant project knowledge, decisions, and preferences with
@@ -28,7 +29,11 @@ automatic setup; other clients use `fuli connect`. See [client connections](docs
28
29
  an available, authorized host client or executor.
29
30
  - **Every task has an owner:** ask for work inside a project directory; Fuli registers the project
30
31
  and staffs a lead agent. The reply ends with who led and who helped.
31
- - **A fixed PM and AR:** Jefa (project manager) and Bole (AR, Agent Resources) come with every space; no hiring needed.
32
+ - **Three built-in specialists:** Jefa manages projects, Bole handles AR (Agent Resources) for
33
+ recruiting and staffing, and Tonborg helps with judgment. Every space starts with all three.
34
+ - **Judgment guided by your preferences:** [Tonborg](docs/judgment-and-autonomy.md) reviews items
35
+ within your chosen autonomy level and recommends clients and models. Each decision has a log;
36
+ a thumbs up, thumbs down or short reason helps guide future judgments.
32
37
  - **Agents talk to each other:** through the [Roundtable](#agent-roundtable), one agent asks another
33
38
  conversation directly, and every exchange is recorded.
34
39
 
@@ -601,6 +606,7 @@ and tells you to run `fuli setup`; it never applies setup changes implicitly.
601
606
  | `--adaptive-memory` | Enable on-demand wake and staged idle sleep, coordinated by the management service |
602
607
  | `--no-adaptive-memory` | Disable idle sleep and keep graph services running |
603
608
  | `--skip-agents` | Do not change agent configuration or Skills |
609
+ | `--connected-only` | Refresh only clients that already have a Fuli MCP entry; used automatically by update |
604
610
  | `--no-start` | Initialize the Provider without starting the UI |
605
611
  | `--personal-only` | Use only the personal Provider; this is the default |
606
612
  | `--with-dev-public` | Start a development shared Provider for local integration work only |
@@ -629,14 +635,16 @@ containers; it does not shut down Rancher Desktop, Docker Desktop, Kubernetes, o
629
635
  itself. Native mode directly stops the corresponding Provider and Neo4j processes, so no shared VM
630
636
  overhead remains while idle.
631
637
 
632
- Jefa (project manager) and Bole (AR, with a native people panel for Agent distribution, current work and recruitment history) are fixed roles in every space and are never hired. **Project Agents → Hire an Agent** offers the other reusable employee templates.
638
+ Jefa (project manager), Bole (AR, Agent Resources), and Tonborg (judgment) are built-in roles in every
639
+ space and are never hired. Bole's workbench shows staffing, current work, and recruitment history;
640
+ Tonborg's workbench shows decisions and feedback. **Project Agents → Hire an Agent** offers the other reusable employee templates.
633
641
  Recruitment reuses a durable identity with separate project assignments. An installed employee workbench,
634
642
  API and A2A share the FULI port; existing FULI MCP clients discover and call its tools. Recruitment does not
635
643
  start a model or rewrite client settings. See the [employee package and extension contract](docs/employee-agents.md).
636
644
 
637
645
  Project Agent identities remain control-plane records rather than one resident process per identity.
638
646
  Roles now keep versioned, project-private working memory in the same Neo4j Provider. Task entry
639
- restores one owner across Codex, Claude Code and Cursor; lifecycle checkpoints survive separate
647
+ restores one owner across connected MCP clients; lifecycle checkpoints survive separate
640
648
  MCP processes. See [role memory, host hooks and acceptance boundaries](docs/project-agent-memory.md).
641
649
  Supported Codex and Claude Code hooks also journal visible conversations. Once unfolded messages pass
642
650
  a size threshold (64 KB by default), older ones fold into a digest; recovery stays within a byte budget. Specialist loans and artifact verification share the same Provider.
@@ -684,9 +692,15 @@ Update:
684
692
  fuli update
685
693
  ```
686
694
 
687
- The update flow verifies that it will not downgrade the installation, stops the old services,
688
- installs `fuli-context@latest`, and refreshes agent integrations. Existing knowledge, configuration
689
- backups, and Neo4j volumes remain intact. Reuse any custom setup options during updates.
695
+ The update flow shows the running CLI, Node, PATH launchers, and npm installation target. If the
696
+ current version is already latest, it exits without reinstalling or restarting services. Otherwise,
697
+ it verifies that it will not downgrade, stops the old services, installs the latest version, and
698
+ refreshes only clients with an existing Fuli MCP entry. Existing knowledge, configuration backups,
699
+ and Neo4j volumes remain intact. Reuse any custom setup options during updates.
700
+
701
+ Reconnect Fuli in already-open clients, or restart those clients and open a new task, to load the
702
+ new MCP process. To repair existing integrations without upgrading, run
703
+ `fuli setup --yes --connected-only`. Use normal `fuli setup` to connect a new client.
690
704
 
691
705
  Uninstall:
692
706
 
package/README.zh-CN.md CHANGED
@@ -17,14 +17,16 @@
17
17
  **换 AI 工具,不用换搭档。**
18
18
 
19
19
  复利是跨兼容 MCP 客户端的长期 Agent 协作层。让 AI 工具接入同一份可访问的 Fuli 数据,
20
- 选中同一位 Agent,就能延续它的身份、项目职责和相关工作脉络。Codex、Claude Code、Cursor
21
- 已有自动配置;其他客户端可用 `fuli connect`。详见[客户端接入](docs/client-connections.md)。
20
+ 选中同一位 Agent,就能延续它的身份、项目职责和相关工作脉络。运行 `fuli connect` 获取标准
21
+ MCP 配置,有适配器的客户端还支持自动配置(Codex、Claude Code、Cursor,以及使用精简聊天工具集的 Claude
22
+ 桌面版)。详见[客户端接入](docs/client-connections.md)。
22
23
 
23
24
  - **搭档留得住:**任务结束后,Agent 的职责、合作记录与工作记忆继续保留。
24
25
  - **上下文有边界:**按需取回项目知识、决定与偏好,保留来源、作用域和确认状态。
25
26
  - **需要时一起做:**协调不同专长的 Agent,通过可用且获授权的客户端或执行器开展任务,记录真实执行。
26
27
  - **每个任务都有负责人:**在项目目录里直接提需求,Fuli 自动登记项目并安排一位负责 Agent;回复最后一行写明谁负责、谁协助。
27
- - **项目经理与 AR 固定在岗:**Jefa(项目经理)和 Bole(AR,Agent Resources)随空间自带,不需要招募。
28
+ - **三位专职随时在岗:**Jefa 管项目,Bole 做 AR(Agent Resources),负责招募与人员安排,Tonborg 帮你做判断;每个空间都自带这三位。
29
+ - **按你的偏好判断:**[Tonborg](docs/judgment-and-autonomy.md) 在你设定的自主程度内协助审核、建议客户端与模型。每次判断都有记录,点赞、点踩或补一句理由,帮助它改进后续判断。
28
30
  - **Agent 之间直接对话:**通过[圆桌](#agent-圆桌),一个 Agent 可以直接去问另一段对话里的 Agent,往来全程留痕。
29
31
 
30
32
  这里的“长期”指身份和上下文持续存在。Fuli 负责组织上下文与协调任务,接入的 AI 工具负责执行。
@@ -534,6 +536,7 @@ HTTPS 公网部署。
534
536
  | `--adaptive-memory` | 启用按需唤醒和分阶段休眠;保存后由管理服务协调 |
535
537
  | `--no-adaptive-memory` | 关闭按需休眠,让图服务保持运行 |
536
538
  | `--skip-agents` | 不修改 Agent 配置或 Skills |
539
+ | `--connected-only` | 仅刷新已有 Fuli MCP 配置的客户端;update 自动使用此选项 |
537
540
  | `--no-start` | 初始化 Provider,但不启动管理界面 |
538
541
  | `--personal-only` | 只使用个人 Provider;默认模式 |
539
542
  | `--with-dev-public` | 同时启动开发用公共 Provider,仅用于开发或联调 |
@@ -559,7 +562,9 @@ start 和 restart 会继续沿用。
559
562
  Kubernetes 或整台容器虚拟机;原生模式会直接停止对应 Provider 和 Neo4j 进程,因此空闲时
560
563
  不再保留共享虚拟机开销。
561
564
 
562
- Jefa(项目经理)和 Bole(AR,原生人员面板可查看 Agent 分布、当前工作与招募历史)是每个空间固定在岗的角色,不需要招募。“Agents → 聘请 Agent”提供其他可复用的员工模板。招募复用持久身份,
565
+ Jefa(项目经理)、Bole(AR,Agent Resources)和 Tonborg(判断者)是每个空间自带的角色,
566
+ 不需要招募。Bole 的工作台可查看人员安排、当前工作与招募历史;Tonborg 的工作台可查看判断与反馈。
567
+ “Agents → 聘请 Agent”提供其他可复用的员工模板。招募复用持久身份,
563
568
  按项目单独任职;安装员工包后,侧栏工作台与 API/A2A 共用 FULI 端口,已有 FULI MCP 可发现和调用员工工具。
564
569
  招募不等于启动模型,也不改写已有客户端配置。详见[员工包安装与扩展协议](docs/employee-agents.md)。
565
570
 
@@ -606,10 +611,16 @@ dump 使用项目固定的 Neo4j 5.26 格式,适合在 Fuli 两种运行模式
606
611
  fuli update
607
612
  ```
608
613
 
609
- 更新会先确认不会降级,再停止旧服务、安装 `fuli-context@latest` 并刷新 Agent 接入。
614
+ 更新会显示当前 CLI、Node、PATH 中的启动入口和 npm 安装目标。已是最新版时直接结束,
615
+ 不会重复安装或重启服务。需要升级时先确认不会降级,再停止旧服务、安装最新版本,
616
+ 仅刷新已有 Fuli MCP 配置的客户端。
610
617
  既有知识、配置备份和 Neo4j 数据卷会保留。使用过自定义 setup 选项时,更新时应继续传入
611
618
  相同选项。
612
619
 
620
+ 已打开的客户端需要重新连接 Fuli MCP,或重启客户端后新建任务,才能加载新进程。
621
+ 只需修复已有接入时,运行 `fuli setup --yes --connected-only`;接入新客户端仍使用普通
622
+ `fuli setup`。
623
+
613
624
  卸载:
614
625
 
615
626
  ```bash
@@ -1,5 +1,5 @@
1
1
  const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/SharedJefaReportPage-Bg6lIk3E.js","assets/i18n-oJFlt8hW.js","assets/client-DnwnUBQV.js","assets/_plugin-vue_export-helper-BDNMzG2s.js","assets/client-CIuQ1icy.css","assets/useApi-CROJJdhE-7gt0QyQ5.js","assets/shared-report-CiX64ZiD.js","assets/ProjectScopePicker-CiDxM2Yx.js","assets/ProjectScopePicker-DYA7L1Ex.css","assets/shared-report-CGlCpmNd.css","assets/SharedJefaReportPage-D92FSPru.css","assets/RoundtablesPage-L3ShEWS3.js","assets/vue-router-CCvPGaLD.js","assets/RoundtablesPage-Drou-B81.css","assets/OverviewPage-3eZjQ26v.js","assets/console-Cw7TzOSC.js","assets/attention-store-CA5y8j1R.js","assets/profile-model-BQO4zNs9.js","assets/useAgentRoster-B84mzZIg.js","assets/task-evidence-DTiPCs7r.js","assets/paths-CDREouhi.js","assets/agent-work-summary-C-yW7x7A.js","assets/OverviewPage-8LBN8Shi.css","assets/PersonalProfilePage-CDQk096L.js","assets/UiButton-x8hiHNRM.js","assets/UiDialog-mjletK0y.js","assets/UiSelect-K29jnV-1.js","assets/UiSelect-Bv6R0yr1.css","assets/UiSegmented-WFfz9Evl.js","assets/KnowledgeInspector-Cj2yK4Qh.js","assets/UiDisclosure-_IxeiexW.js","assets/model-BJ-UF4cs.js","assets/conversation-launcher-settings-Yc13viwr.js","assets/KnowledgeInspector-pgDELOEf.css","assets/KnowledgeEditDialog-HmiqLC-Y.js","assets/identity-DSGS4CZc.js","assets/KnowledgeEditDialog-hT9znkoe.css","assets/personal-profile-graph-SOK0BR0w.js","assets/PersonalProfilePage-DrMDAAdy.css","assets/WritingTastePage-DCULB-d2.js","assets/WritingTastePage-B_zYD1df.css","assets/PersonalProjectsPage-CJxp4Wyz.js","assets/KnowledgeWorkspace-DA_dqVRG.js","assets/AgentName-DJpvkub2.js","assets/AgentName-BQTFCWn0.css","assets/project-team-api-C_0qJk_B.js","assets/KnowledgeWorkspace-D4a7FcAb.css","assets/KnowledgeOrganizerPage-DT3BROZm.js","assets/KnowledgeOrganizerPage-ccgscrPd.css","assets/AgentsDirectoryPage-CBFTg_IE.js","assets/AgentHand-UzVhbKUC.js","assets/AgentHand-Y83Oo36U.css","assets/agent-pins-DS4mNsNt.js","assets/catalog-D0VEfOAV.js","assets/ProjectAgentFirstTask-BkRJ0zwO.js","assets/ProjectAgentFirstTask-Bi1ZsTC1.css","assets/EmployeeRecruitDialog-D8Hvhc-M.js","assets/EmployeeRecruitDialog-CVbqCzHb.css","assets/AgentsDirectoryPage-Cv-vxw2C.css","assets/agent-profile-Bh3MJGvb.css","assets/AgentProfilePage-CFViQ8kj.js","assets/AgentConversations-Ccy41mEA.js","assets/AgentConversations-XqNEBY_2.css","assets/ProjectAgentsPage-BuNNqSEy.js","assets/ProjectAgentsPage-DWGkgIsr.css","assets/EmployeeWorkbenchPage-TBl_HNj6.js","assets/judgment-types-DR931cZ7.js","assets/EmployeeWorkbenchPage-BQes4L1f.css","assets/PublicProjectsPage-ByOT559r.js","assets/PublicProjectsPage-DKamXDV9.css","assets/KnowledgePage-C4J4VueL.js","assets/ReviewPage-C0Vdr9uq.js","assets/ConnectionsPage-uXcabab_.js","assets/ConnectionsPage-CM9C-zEI.css","assets/SettingsPage-McYuinGz.js","assets/SettingsPage-BE8pWbI6.css","assets/AboutPage-Dized8Uz.js","assets/fuli-logo-Bu9WBuQe.js","assets/AboutPage-c4dQpUiP.css","assets/NotFoundPage-x_Yd59mm.js"])))=>i.map(i=>d[i]);
2
- import{B as e,E as t,F as n,H as r,I as i,M as a,N as o,P as s,Q as c,R as l,S as u,T as d,V as f,_ as p,a as m,b as h,c as g,et as _,g as v,h as y,i as b,it as x,j as S,m as C,n as w,ot as T,r as E,rt as D,t as O,v as k,w as A,x as j,y as M}from"./i18n-oJFlt8hW.js";import{n as N,t as P}from"./console-Cw7TzOSC.js";import{r as F,s as I}from"./client-DnwnUBQV.js";import{t as L}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as R}from"./UiButton-x8hiHNRM.js";import{m as z}from"./useApi-CROJJdhE-7gt0QyQ5.js";import{i as B,n as V,r as H,t as U}from"./vue-router-CCvPGaLD.js";import{t as W}from"./fuli-logo-Bu9WBuQe.js";import{t as ee}from"./UiDialog-mjletK0y.js";import{t as te}from"./UiSelect-K29jnV-1.js";import{n as ne,t as G}from"./attention-store-CA5y8j1R.js";import{t as re}from"./AgentHand-UzVhbKUC.js";import{t as ie}from"./UiDisclosure-_IxeiexW.js";import{n as ae,t as K}from"./profile-model-BQO4zNs9.js";import{t as oe}from"./useAgentRoster-B84mzZIg.js";import{i as se,n as ce,r as q,t as le}from"./agent-pins-DS4mNsNt.js";import{n as ue,t as de}from"./paths-CDREouhi.js";(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var J={name:`fuli-context`,version:`0.12.1`,description:`Local-first collaboration relationship graph for AI agents.`,type:`module`,files:[`README.zh-CN.md`,`npm-shrinkwrap.json`,`src/`,`dist/web/`,`docs/external-knowledge-architecture.md`,`docs/public-personal-architecture.md`,`docs/project-agent-memory.md`,`docs/agent-conversations-and-collaboration.md`,`docs/employee-agents.md`,`docs/agent-interface-architecture.md`,`docs/agent-roundtable.md`,`examples/external-knowledge/markdown-folder.mjs`,`graph-provider/Dockerfile`,`graph-provider/pyproject.toml`,`graph-provider/fuli_graph/*.py`,`graph-provider/fuli_graph/**/*.py`,`skills/`,`compose.graphiti.yml`,`docs/judgment-and-autonomy.md`,`docs/client-connections.md`],bin:{fl:`src/cli.js`,fuli:`src/cli.js`},keywords:[`ai-agent`,`mcp`,`context`,`knowledge-graph`,`local-first`,`codex`,`claude-code`],scripts:{mcp:`node src/mcp-server.js`,dev:`run-p dev:server dev:web`,"dev:server":`node src/server.js`,"dev:web":`vite`,build:`run-s typecheck:web build:web`,"build:web":`vite build`,"build:site":`node scripts/build-site.js`,"typecheck:web":`vue-tsc --noEmit -p tsconfig.app.json`,"test:node":`node --test`,"test:web":`vitest run --config vitest.config.ts`,"test:package":`node scripts/package-smoke.js`,"test:knowledge:live":`node acceptance/knowledge-live.js`,"test:external-knowledge:live":`node scripts/validate-external-knowledge-live.js`,start:`node src/server.js`,"graph:setup":`node src/graphiti/setup.js`,"graph:up":`docker compose -f compose.graphiti.yml up -d --build`,"graph:down":`docker compose -f compose.graphiti.yml down`,"test:structure":`node --test test/source-structure.test.js`,pretest:`npm run build:web`,test:`run-s typecheck:web test:node test:web`,prepack:`npm run build`,prepublishOnly:`npm test`},engines:{node:`>=24.12`},repository:{type:`git`,url:`git+https://github.com/JevonsCode/fuli.git`},homepage:`https://jevonscode.github.io/fuli/`,bugs:{url:`https://github.com/JevonsCode/fuli/issues`},publishConfig:{access:`public`},license:`Apache-2.0`,dependencies:{"@a2a-js/sdk":`1.3.0`,"@hono/node-server":`2.0.12`,"@modelcontextprotocol/sdk":`^1.31.0`,"@peculiar/x509":`2.1.0`,"reflect-metadata":`0.2.2`,zod:`^4.4.3`},devDependencies:{"@types/d3":`^7.4.3`,"@types/node":`^24.13.3`,"@vitejs/plugin-vue":`^6.0.8`,"@vue/test-utils":`^2.4.11`,"brace-expansion":`5.0.12`,d3:`^7.9.0`,jsdom:`^29.1.1`,linkedom:`^0.18.12`,"npm-run-all2":`^9.0.2`,pinia:`^4.0.2`,typescript:`~6.0.0`,vite:`^8.1.5`,vitest:`^4.1.10`,vue:`^3.5.42`,"vue-i18n":`^11.4.8`,"vue-router":`^5.2.0`,"vue-tsc":`^3.3.8`},overrides:{"@modelcontextprotocol/sdk":{"@hono/node-server":`2.0.12`},"brace-expansion":`$brace-expansion`,"fast-uri":`3.1.8`,qs:`6.16.0`}}.version,fe=[`aria-label`],Y={class:`brand-mark-wrap`},pe=[`src`],me={class:`brand-copy`},he={class:`brand-title-row`},ge={class:`brand-name`},_e=[`aria-label`,`aria-expanded`],ve={class:`brand-version`},ye={key:1,class:`brand-version`},be={class:`brand-title-row`},xe={class:`brand-name`},Se={class:`brand-version`},Ce=[`aria-label`],we=[`href`],Te=L(t({__name:`BrandEasterEgg`,setup(e){let t=c(null),n=c(null),r=c(0),i=c(!1),l=c(!1),d=c(!1),f=c(null),p=k(()=>f.value?.status===`ready`&&f.value.updateAvailable&&!!f.value.latestVersion),h=k(()=>f.value?.latestVersion??``),g=k(()=>f.value?.packageUrl??`https://www.npmjs.com/package/fuli-context`),_=new AbortController,v=!1;function b(){r.value+=1,i.value=!0}function C(){i.value=!1}function w(){p.value&&(d.value=!d.value)}function E(e){v=!1,l.value=!1,d.value&&!t.value?.contains(e.target)&&(d.value=!1)}function O(e){e.key===`Tab`&&(v=!0),e.key===`Escape`&&d.value&&(e.preventDefault(),d.value=!1,S(()=>n.value?.focus()))}function A(){l.value=v}function N(){l.value=!1}return o(()=>{window.addEventListener(`pointerdown`,E,!0),window.addEventListener(`keydown`,O,!0),F(`/api/system/version`,{signal:_.signal}).then(e=>{f.value=e}).catch(()=>{})}),a(()=>{_.abort(),window.removeEventListener(`pointerdown`,E,!0),window.removeEventListener(`keydown`,O,!0)}),(e,a)=>(s(),u(`div`,{ref_key:`root`,ref:t,class:x([`brand-block`,{"is-sparkling":i.value,"is-keyboard-focused":l.value}])},[M(`button`,{class:`brand-effect-button`,type:`button`,"aria-label":D(m)(`console.brandEffect.aria`),onClick:b,onFocus:A,onBlur:N},[M(`span`,Y,[M(`img`,{class:`brand-mark`,src:D(W),alt:``,"aria-hidden":`true`},null,8,pe),i.value?(s(),u(`span`,{key:`orbit-${r.value}`,class:`brand-orbit`,"aria-hidden":`true`})):j(``,!0)])],40,fe),M(`span`,me,[M(`span`,he,[M(`span`,ge,T(D(m)(`common.brand`)),1),p.value?(s(),u(`button`,{key:0,ref_key:`versionButton`,ref:n,class:`brand-version-button`,type:`button`,"aria-label":D(m)(`console.update.aria`,{version:h.value}),"aria-expanded":d.value,"aria-controls":`brand-update-details`,onClick:w},[M(`span`,ve,`v`+T(D(J)),1),a[0]||=M(`span`,{class:`brand-update-dot`,"aria-hidden":`true`},null,-1)],8,_e)):(s(),u(`span`,ye,`v`+T(D(J)),1))]),a[1]||=M(`span`,{class:`brand-subtitle`},`Context Graph`,-1),i.value?(s(),u(`span`,{key:`copy-reflection-${r.value}`,class:`brand-copy-reflection`,"aria-hidden":`true`,onAnimationend:y(C,[`self`])},[M(`span`,be,[M(`span`,xe,T(D(m)(`common.brand`)),1),M(`span`,Se,`v`+T(D(J)),1)])],32)):j(``,!0)]),d.value?(s(),u(`div`,{key:0,id:`brand-update-details`,class:`brand-update-popover`,role:`dialog`,"aria-label":D(m)(`console.update.title`,{version:h.value})},[M(`strong`,null,T(D(m)(`console.update.title`,{version:h.value})),1),M(`p`,null,T(D(m)(`console.update.copy`)),1),a[2]||=M(`code`,null,`fuli update --yes`,-1),M(`a`,{href:g.value,target:`_blank`,rel:`noopener noreferrer`},T(D(m)(`console.update.npmLink`)),9,we)],8,Ce)):j(``,!0)],2))}}),[[`__scopeId`,`data-v-68727ec7`]]),Ee=[`aria-label`],De=[`lang`,`aria-label`,`aria-pressed`,`onClick`],Oe=t({__name:`LocaleSwitcher`,setup(e){let t=k(()=>w()),n={"zh-CN":`中`,"en-US":`EN`};function r(e){e!==t.value&&b(e)}return(e,a)=>(s(),u(`div`,{class:`locale-switcher`,role:`group`,"aria-label":D(m)(`common.language.label`)},[(s(!0),u(v,null,i(D(O),e=>(s(),u(`button`,{key:e,type:`button`,lang:e,"aria-label":D(m)(e===`zh-CN`?`common.language.chinese`:`common.language.english`),"aria-pressed":t.value===e,onClick:t=>r(e)},T(n[e]),9,De))),128))],8,Ee))}}),X=_(null);function ke(e){let t=e.onError((t,n)=>{let r=e.resolve(n).href;!r.startsWith(`/`)||r.startsWith(`//`)||(X.value={href:r,resourceFailure:/dynamically imported module|module script|loading chunk|preload css|importing a module/i.test(String(t))})}),n=e.afterEach((e,t,n)=>{n||(X.value=null)});return()=>{t(),n()}}var Ae={key:0,class:`navigation-recovery`,role:`alert`},je=[`href`],Me=L(t({__name:`NavigationRecovery`,setup(e){return(e,t)=>D(X)?(s(),u(`div`,Ae,[M(`div`,null,[M(`strong`,null,T(D(m)(`routes.loadFailure.title`)),1),M(`p`,null,T(D(m)(D(X).resourceFailure?`routes.loadFailure.resources`:`routes.loadFailure.message`)),1)]),M(`a`,{href:D(X).href,class:`quiet-button`},T(D(m)(`routes.loadFailure.reload`)),9,je),M(`button`,{type:`button`,class:`navigation-recovery-dismiss`,onClick:t[0]||=e=>X.value=null},T(D(m)(`routes.loadFailure.dismiss`)),1)])):j(``,!0)}}),[[`__scopeId`,`data-v-d154c242`]]);function Ne(e){let t=e.requestedAction.split(`
2
+ import{B as e,E as t,F as n,H as r,I as i,M as a,N as o,P as s,Q as c,R as l,S as u,T as d,V as f,_ as p,a as m,b as h,c as g,et as _,g as v,h as y,i as b,it as x,j as S,m as C,n as w,ot as T,r as E,rt as D,t as O,v as k,w as A,x as j,y as M}from"./i18n-oJFlt8hW.js";import{n as N,t as P}from"./console-Cw7TzOSC.js";import{r as F,s as I}from"./client-DnwnUBQV.js";import{t as L}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as R}from"./UiButton-x8hiHNRM.js";import{m as z}from"./useApi-CROJJdhE-7gt0QyQ5.js";import{i as B,n as V,r as H,t as U}from"./vue-router-CCvPGaLD.js";import{t as W}from"./fuli-logo-Bu9WBuQe.js";import{t as ee}from"./UiDialog-mjletK0y.js";import{t as te}from"./UiSelect-K29jnV-1.js";import{n as ne,t as G}from"./attention-store-CA5y8j1R.js";import{t as re}from"./AgentHand-UzVhbKUC.js";import{t as ie}from"./UiDisclosure-_IxeiexW.js";import{n as ae,t as K}from"./profile-model-BQO4zNs9.js";import{t as oe}from"./useAgentRoster-B84mzZIg.js";import{i as se,n as ce,r as q,t as le}from"./agent-pins-DS4mNsNt.js";import{n as ue,t as de}from"./paths-CDREouhi.js";(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var J={name:`fuli-context`,version:`0.12.3`,description:`Local-first collaboration relationship graph for AI agents.`,type:`module`,files:[`README.zh-CN.md`,`npm-shrinkwrap.json`,`src/`,`dist/web/`,`docs/external-knowledge-architecture.md`,`docs/public-personal-architecture.md`,`docs/project-agent-memory.md`,`docs/agent-conversations-and-collaboration.md`,`docs/employee-agents.md`,`docs/agent-interface-architecture.md`,`docs/agent-roundtable.md`,`examples/external-knowledge/markdown-folder.mjs`,`graph-provider/Dockerfile`,`graph-provider/pyproject.toml`,`graph-provider/fuli_graph/*.py`,`graph-provider/fuli_graph/**/*.py`,`skills/`,`compose.graphiti.yml`,`docs/judgment-and-autonomy.md`,`docs/client-connections.md`],bin:{fl:`src/cli.js`,fuli:`src/cli.js`},keywords:[`ai-agent`,`mcp`,`context`,`knowledge-graph`,`local-first`,`codex`,`claude-code`],scripts:{mcp:`node src/mcp-server.js`,dev:`run-p dev:server dev:web`,"dev:server":`node src/server.js`,"dev:web":`vite`,build:`run-s typecheck:web build:web`,"build:web":`vite build`,"build:site":`node scripts/build-site.js`,"typecheck:web":`vue-tsc --noEmit -p tsconfig.app.json`,"test:node":`node --test`,"test:web":`vitest run --config vitest.config.ts`,"test:package":`node scripts/package-smoke.js`,"test:knowledge:live":`node acceptance/knowledge-live.js`,"test:external-knowledge:live":`node scripts/validate-external-knowledge-live.js`,start:`node src/server.js`,"graph:setup":`node src/graphiti/setup.js`,"graph:up":`docker compose -f compose.graphiti.yml up -d --build`,"graph:down":`docker compose -f compose.graphiti.yml down`,"test:structure":`node --test test/source-structure.test.js`,pretest:`npm run build:web`,test:`run-s typecheck:web test:node test:web`,prepack:`npm run build`,prepublishOnly:`npm test`},engines:{node:`>=24.12`},repository:{type:`git`,url:`git+https://github.com/JevonsCode/fuli.git`},homepage:`https://jevonscode.github.io/fuli/`,bugs:{url:`https://github.com/JevonsCode/fuli/issues`},publishConfig:{access:`public`},license:`Apache-2.0`,dependencies:{"@a2a-js/sdk":`1.3.0`,"@hono/node-server":`2.0.12`,"@modelcontextprotocol/sdk":`^1.31.0`,"@peculiar/x509":`2.1.0`,"reflect-metadata":`0.2.2`,zod:`^4.4.3`},devDependencies:{"@types/d3":`^7.4.3`,"@types/node":`^24.13.3`,"@vitejs/plugin-vue":`^6.0.8`,"@vue/test-utils":`^2.4.11`,"brace-expansion":`5.0.12`,d3:`^7.9.0`,jsdom:`^29.1.1`,linkedom:`^0.18.12`,"npm-run-all2":`^9.0.2`,pinia:`^4.0.2`,typescript:`~6.0.0`,vite:`^8.1.5`,vitest:`^4.1.10`,vue:`^3.5.42`,"vue-i18n":`^11.4.8`,"vue-router":`^5.2.0`,"vue-tsc":`^3.3.8`},overrides:{"@modelcontextprotocol/sdk":{"@hono/node-server":`2.0.12`},"brace-expansion":`$brace-expansion`,"fast-uri":`3.1.8`,qs:`6.16.0`}}.version,fe=[`aria-label`],Y={class:`brand-mark-wrap`},pe=[`src`],me={class:`brand-copy`},he={class:`brand-title-row`},ge={class:`brand-name`},_e=[`aria-label`,`aria-expanded`],ve={class:`brand-version`},ye={key:1,class:`brand-version`},be={class:`brand-title-row`},xe={class:`brand-name`},Se={class:`brand-version`},Ce=[`aria-label`],we=[`href`],Te=L(t({__name:`BrandEasterEgg`,setup(e){let t=c(null),n=c(null),r=c(0),i=c(!1),l=c(!1),d=c(!1),f=c(null),p=k(()=>f.value?.status===`ready`&&f.value.updateAvailable&&!!f.value.latestVersion),h=k(()=>f.value?.latestVersion??``),g=k(()=>f.value?.packageUrl??`https://www.npmjs.com/package/fuli-context`),_=new AbortController,v=!1;function b(){r.value+=1,i.value=!0}function C(){i.value=!1}function w(){p.value&&(d.value=!d.value)}function E(e){v=!1,l.value=!1,d.value&&!t.value?.contains(e.target)&&(d.value=!1)}function O(e){e.key===`Tab`&&(v=!0),e.key===`Escape`&&d.value&&(e.preventDefault(),d.value=!1,S(()=>n.value?.focus()))}function A(){l.value=v}function N(){l.value=!1}return o(()=>{window.addEventListener(`pointerdown`,E,!0),window.addEventListener(`keydown`,O,!0),F(`/api/system/version`,{signal:_.signal}).then(e=>{f.value=e}).catch(()=>{})}),a(()=>{_.abort(),window.removeEventListener(`pointerdown`,E,!0),window.removeEventListener(`keydown`,O,!0)}),(e,a)=>(s(),u(`div`,{ref_key:`root`,ref:t,class:x([`brand-block`,{"is-sparkling":i.value,"is-keyboard-focused":l.value}])},[M(`button`,{class:`brand-effect-button`,type:`button`,"aria-label":D(m)(`console.brandEffect.aria`),onClick:b,onFocus:A,onBlur:N},[M(`span`,Y,[M(`img`,{class:`brand-mark`,src:D(W),alt:``,"aria-hidden":`true`},null,8,pe),i.value?(s(),u(`span`,{key:`orbit-${r.value}`,class:`brand-orbit`,"aria-hidden":`true`})):j(``,!0)])],40,fe),M(`span`,me,[M(`span`,he,[M(`span`,ge,T(D(m)(`common.brand`)),1),p.value?(s(),u(`button`,{key:0,ref_key:`versionButton`,ref:n,class:`brand-version-button`,type:`button`,"aria-label":D(m)(`console.update.aria`,{version:h.value}),"aria-expanded":d.value,"aria-controls":`brand-update-details`,onClick:w},[M(`span`,ve,`v`+T(D(J)),1),a[0]||=M(`span`,{class:`brand-update-dot`,"aria-hidden":`true`},null,-1)],8,_e)):(s(),u(`span`,ye,`v`+T(D(J)),1))]),a[1]||=M(`span`,{class:`brand-subtitle`},`Context Graph`,-1),i.value?(s(),u(`span`,{key:`copy-reflection-${r.value}`,class:`brand-copy-reflection`,"aria-hidden":`true`,onAnimationend:y(C,[`self`])},[M(`span`,be,[M(`span`,xe,T(D(m)(`common.brand`)),1),M(`span`,Se,`v`+T(D(J)),1)])],32)):j(``,!0)]),d.value?(s(),u(`div`,{key:0,id:`brand-update-details`,class:`brand-update-popover`,role:`dialog`,"aria-label":D(m)(`console.update.title`,{version:h.value})},[M(`strong`,null,T(D(m)(`console.update.title`,{version:h.value})),1),M(`p`,null,T(D(m)(`console.update.copy`)),1),a[2]||=M(`code`,null,`fuli update --yes`,-1),M(`a`,{href:g.value,target:`_blank`,rel:`noopener noreferrer`},T(D(m)(`console.update.npmLink`)),9,we)],8,Ce)):j(``,!0)],2))}}),[[`__scopeId`,`data-v-68727ec7`]]),Ee=[`aria-label`],De=[`lang`,`aria-label`,`aria-pressed`,`onClick`],Oe=t({__name:`LocaleSwitcher`,setup(e){let t=k(()=>w()),n={"zh-CN":`中`,"en-US":`EN`};function r(e){e!==t.value&&b(e)}return(e,a)=>(s(),u(`div`,{class:`locale-switcher`,role:`group`,"aria-label":D(m)(`common.language.label`)},[(s(!0),u(v,null,i(D(O),e=>(s(),u(`button`,{key:e,type:`button`,lang:e,"aria-label":D(m)(e===`zh-CN`?`common.language.chinese`:`common.language.english`),"aria-pressed":t.value===e,onClick:t=>r(e)},T(n[e]),9,De))),128))],8,Ee))}}),X=_(null);function ke(e){let t=e.onError((t,n)=>{let r=e.resolve(n).href;!r.startsWith(`/`)||r.startsWith(`//`)||(X.value={href:r,resourceFailure:/dynamically imported module|module script|loading chunk|preload css|importing a module/i.test(String(t))})}),n=e.afterEach((e,t,n)=>{n||(X.value=null)});return()=>{t(),n()}}var Ae={key:0,class:`navigation-recovery`,role:`alert`},je=[`href`],Me=L(t({__name:`NavigationRecovery`,setup(e){return(e,t)=>D(X)?(s(),u(`div`,Ae,[M(`div`,null,[M(`strong`,null,T(D(m)(`routes.loadFailure.title`)),1),M(`p`,null,T(D(m)(D(X).resourceFailure?`routes.loadFailure.resources`:`routes.loadFailure.message`)),1)]),M(`a`,{href:D(X).href,class:`quiet-button`},T(D(m)(`routes.loadFailure.reload`)),9,je),M(`button`,{type:`button`,class:`navigation-recovery-dismiss`,onClick:t[0]||=e=>X.value=null},T(D(m)(`routes.loadFailure.dismiss`)),1)])):j(``,!0)}}),[[`__scopeId`,`data-v-d154c242`]]);function Ne(e){let t=e.requestedAction.split(`
3
3
  `).map(e=>e.trim()).filter(e=>/^[A-F][.、))::]\s*\S/.test(e));return/(?:选择|选项|choose|select|options?|pick)/iu.test(e.requestedAction)&&t.length>=2&&t.every((e,t)=>e[0]===String.fromCharCode(65+t))?t.map((e,t)=>({id:`option-${t}`,label:e,value:e})):({approval:[`agree`,`decline`],review:[`accept`,`revise`],permission:[`agree`,`decline`],blocked:[`resolved`,`clarify`],question:[`clarify`]}[e.kind]??[`clarify`]).map(e=>({id:e,label:m(`attention.replies.${e}`),value:m(`attention.replies.${e}`)}))}function Pe(e,t,n){if(e===`other`)return t.trim();let r=n.find(t=>t.id===e);return r?[r.value,t.trim()].filter(Boolean).join(`
4
4
 
5
5
  `):``}var Fe={class:`attention-question`},Ie={class:`attention-context`},Le=[`disabled`],Re=[`name`,`value`,`checked`,`onChange`],ze=[`name`,`checked`],Be={class:`attention-input`},Ve=[`for`],He=[`id`,`value`,`required`,`disabled`,`placeholder`],Ue={key:0,role:`alert`,class:`inline-error attention-response-error`},We={key:1,role:`alert`,class:`inline-error`},Ge={class:`attention-submit`},Ke=[`disabled`],qe={key:1},Je=L(t({__name:`AttentionReply`,props:{item:{},modelValue:{},busy:{type:Boolean},refreshing:{type:Boolean},error:{}},emits:[`update:modelValue`,`submit`],setup(e,{emit:t}){let n=e,a=t,o=k(()=>Ne(n.item)),c=k(()=>Pe(n.modelValue.choice,n.modelValue.text,o.value));function l(e){a(`update:modelValue`,{...n.modelValue,...e})}function d(){!n.busy&&!n.refreshing&&c.value&&c.value.length<=4096&&a(`submit`,c.value)}return(t,n)=>(s(),u(`form`,{class:`attention-reply`,onSubmit:y(d,[`prevent`])},[M(`div`,Fe,[M(`h3`,null,T(e.item.title),1),M(`p`,null,T(e.item.requestedAction),1)]),(s(),h(ie,{key:e.item.requestId,title:D(m)(`attention.context`)},{default:r(()=>[M(`p`,Ie,T(e.item.detail),1)]),_:1},8,[`title`])),M(`fieldset`,{disabled:e.busy||e.refreshing,class:`attention-choices`},[M(`legend`,null,T(D(m)(`attention.chooseReply`)),1),(s(!0),u(v,null,i(o.value,t=>(s(),u(`label`,{key:t.id,class:x([`attention-choice`,{"is-selected":e.modelValue.choice===t.id}])},[M(`input`,{type:`radio`,name:`reply-${e.item.requestId}`,value:t.id,checked:e.modelValue.choice===t.id,onChange:e=>l({choice:t.id})},null,40,Re),M(`span`,null,T(t.label),1)],2))),128)),M(`label`,{class:x([`attention-choice`,{"is-selected":e.modelValue.choice===`other`}])},[M(`input`,{type:`radio`,name:`reply-${e.item.requestId}`,value:`other`,checked:e.modelValue.choice===`other`,onChange:n[0]||=e=>l({choice:`other`})},null,40,ze),M(`span`,null,T(D(m)(`attention.other`)),1)],2)],8,Le),M(`div`,Be,[M(`label`,{for:`attention-${e.item.requestId}`},T(e.modelValue.choice===`other`?D(m)(`attention.response`):D(m)(`attention.note`)),9,Ve),M(`textarea`,{id:`attention-${e.item.requestId}`,value:e.modelValue.text,rows:`3`,maxlength:`4096`,required:e.modelValue.choice===`other`,disabled:e.busy||e.refreshing,placeholder:D(m)(`attention.placeholder`),onInput:n[1]||=t=>l({text:t.target.value,choice:e.modelValue.choice||`other`})},null,40,He)]),e.error?(s(),u(`p`,Ue,T(e.error),1)):j(``,!0),c.value.length>4096?(s(),u(`p`,We,T(D(m)(`attention.tooLong`)),1)):j(``,!0),M(`div`,Ge,[M(`p`,null,T(D(m)(`attention.replyHint`)),1),M(`button`,{type:`submit`,class:`primary-action`,disabled:e.busy||e.refreshing||!c.value||c.value.length>4096},[e.busy?(s(),h(I,{key:0,variant:`inline`,label:D(m)(`attention.sending`)},null,8,[`label`])):(s(),u(`span`,qe,T(D(m)(`attention.send`)),1))],8,Ke)])],32))}}),[[`__scopeId`,`data-v-6b64bc2c`]]),Ye=[`aria-label`],Xe={class:`attention-nav-status`},Ze=[`title`],Qe={key:0,role:`alert`,class:`attention-error`},$e={key:2,class:`attention-empty`},et=[`aria-busy`],tt=[`aria-label`],nt={class:`attention-queue-toolbar`},rt={class:`attention-request-list`},it=[`aria-current`,`disabled`,`onClick`],at={class:`attention-request-meta`},ot={class:`attention-request-project`},st={class:`attention-detail-meta`},ct={class:`ui-badge`},lt=[`href`],ut=t({__name:`AgentAttentionCenter`,props:{personalSpaceId:{},projects:{}},setup(t){let n=t,l=ne(),f=c({}),g=c({}),_=c(``),y=c(``),b=0,S=c({}),C=k(()=>S.value[_.value]??``),w=k(()=>l.items.find(e=>e.requestId===_.value)),E=k({get:()=>g.value[_.value]??{choice:``,text:``},set:e=>{w.value&&(g.value[_.value]={...e,revision:w.value.revision})}}),O=k(()=>new Map(n.projects.map(e=>[e.project_id,e.profile.name]))),N=k(()=>l.agentId?l.counts[l.agentId]??l.filteredTotal:l.total),P=k(()=>l.items.map(e=>({value:e.requestId,label:e.title,meta:`${f.value[e.agentId]||m(`attention.agentFallback`)} · ${O.value.get(e.personalProjectId)||m(`attention.projectFallback`)}`})));e(()=>n.personalSpaceId,e=>{b++,y.value=``,f.value={},g.value={},_.value=``,S.value={},l.setSpace(e===`current`?``:e)},{immediate:!0}),e(()=>l.open,async e=>{if(!e)return;let t=l.spaceId;try{let e=await F(`/api/project-agents?${new URLSearchParams({personalSpaceId:t})}`);t===l.spaceId&&(f.value=Object.fromEntries(e.map(e=>[e.agentId,e.profile.displayName||e.profile.name])))}catch{}}),e(()=>l.items,e=>{e.some(e=>e.requestId===_.value)||(_.value=e[0]?.requestId??``);for(let t of e){let e=g.value[t.requestId];e&&e.revision!==t.revision&&(g.value[t.requestId]={choice:``,text:e.text,revision:t.revision},S.value[t.requestId]=m(`attention.updated`))}});let L;function z(){document.visibilityState===`visible`&&!l.loading&&!l.open&&l.refresh()}o(()=>{L=setInterval(z,3e4),document.addEventListener(`visibilitychange`,z)}),a(()=>{clearInterval(L),document.removeEventListener(`visibilitychange`,z),l.setSpace(``)});async function B(e,t){if(y.value||!t.trim()||t.length>4096)return;let n=l.spaceId,r=++b;y.value=e.requestId,delete S.value[e.requestId];try{await l.respond(e,t),r===b&&n===l.spaceId&&delete g.value[e.requestId]}catch(t){r===b&&n===l.spaceId&&(S.value[e.requestId]=t instanceof Error?t.message:String(t),await l.refresh())}finally{r===b&&(y.value=``)}}return(e,t)=>(s(),u(v,null,[M(`button`,{class:`space-nav-button attention-nav`,"aria-label":D(l).total?`${D(m)(`attention.title`)} · ${D(m)(`attention.count`,{count:D(l).total})}`:D(m)(`attention.title`),type:`button`,onClick:t[0]||=e=>D(l).show()},[t[9]||=M(`span`,{class:`nav-icon nav-icon-review`,"aria-hidden":`true`},null,-1),M(`span`,null,T(D(m)(`attention.title`)),1),M(`span`,Xe,[d(re,{passive:``,"count-only":``}),D(l).error?(s(),u(`span`,{key:0,title:D(m)(`attention.loadError`)},`!`,8,Ze)):j(``,!0)])],8,Ye),(s(),h(p,{to:`body`},[d(ee,{open:D(l).open,class:`attention-dialog`,size:`xl`,busy:!!y.value,title:D(m)(`attention.title`),description:N.value?D(m)(`attention.count`,{count:N.value}):void 0,onClose:t[8]||=e=>D(l).open=!1},{default:r(()=>[D(l).error?(s(),u(`div`,Qe,[M(`span`,null,T(D(m)(`attention.loadError`)),1),d(R,{size:`sm`,disabled:D(l).loading,onClick:t[1]||=e=>D(l).refresh()},{default:r(()=>[A(T(D(m)(`attention.refresh`)),1)]),_:1},8,[`disabled`])])):j(``,!0),D(l).loading&&!D(l).items.length?(s(),h(I,{key:1,variant:`page`,label:D(m)(`attention.loading`)},null,8,[`label`])):!D(l).items.length&&!D(l).error?(s(),u(`div`,$e,[t[10]||=M(`span`,{"aria-hidden":`true`},`✓`,-1),M(`p`,null,T(D(m)(`attention.empty`)),1)])):D(l).items.length?(s(),u(`div`,{key:3,class:`attention-workspace`,"aria-busy":D(l).loading},[M(`aside`,{class:`attention-queue`,"aria-label":D(m)(`attention.queue`)},[M(`div`,nt,[M(`span`,null,T(D(l).agentId?f.value[D(l).agentId]||D(m)(`attention.agentFallback`):D(m)(`attention.all`)),1),D(l).agentId||!D(l).error?(s(),h(R,{key:0,size:`sm`,variant:`ghost`,disabled:D(l).loading||!!y.value,onClick:t[2]||=e=>D(l).agentId?D(l).show():D(l).refresh()},{default:r(()=>[A(T(D(l).agentId?D(m)(`attention.all`):D(m)(`attention.refresh`)),1)]),_:1},8,[`disabled`])):j(``,!0)]),d(te,{modelValue:_.value,"onUpdate:modelValue":t[3]||=e=>_.value=e,class:`attention-mobile-picker`,field:``,label:D(m)(`attention.selectRequest`),options:P.value,disabled:!!y.value},null,8,[`modelValue`,`label`,`options`,`disabled`]),M(`div`,rt,[(s(!0),u(v,null,i(D(l).items,e=>(s(),u(`button`,{key:e.requestId,type:`button`,class:x([`attention-request`,{"is-selected":_.value===e.requestId}]),"aria-current":_.value===e.requestId?`true`:void 0,disabled:!!y.value,onClick:t=>_.value=e.requestId},[M(`span`,at,[A(T(f.value[e.agentId]||D(m)(`attention.agentFallback`)),1),M(`span`,null,T(D(m)(`attention.kinds.${e.kind}`)),1)]),M(`strong`,null,T(e.title),1),M(`span`,ot,T(O.value.get(e.personalProjectId)||D(m)(`attention.projectFallback`)),1)],10,it))),128))]),D(l).items.length<D(l).filteredTotal?(s(),h(R,{key:0,class:`attention-more`,disabled:D(l).loading||!!y.value,onClick:t[4]||=e=>D(l).more()},{default:r(()=>[A(T(D(m)(`attention.more`)),1)]),_:1},8,[`disabled`])):j(``,!0)],8,tt),w.value?(s(),u(`section`,{key:w.value.requestId,class:`attention-detail`},[M(`div`,st,[M(`span`,null,T(f.value[w.value.agentId]||D(m)(`attention.agentFallback`))+` · `+T(O.value.get(w.value.personalProjectId)||D(m)(`attention.projectFallback`)),1),M(`span`,ct,T(D(m)(`attention.kinds.${w.value.kind}`)),1)]),d(Je,{modelValue:E.value,"onUpdate:modelValue":t[5]||=e=>E.value=e,item:w.value,busy:y.value===w.value.requestId,refreshing:D(l).loading,error:C.value,onSubmit:t[6]||=e=>B(w.value,e)},null,8,[`modelValue`,`item`,`busy`,`refreshing`,`error`]),w.value.taskId&&!y.value?(s(),u(`a`,{key:0,class:`attention-task-link`,href:D(G)(w.value),onClick:t[7]||=e=>D(l).open=!1},T(D(m)(`attention.task`))+` ↗`,9,lt)):j(``,!0)])):j(``,!0)],8,et)):j(``,!0)]),_:1},8,[`open`,`busy`,`title`,`description`])]))],64))}}),dt={key:0,class:`pinned-agents-nav`,"aria-labelledby":`pinned-agents-heading`},ft={class:`pinned-agents-heading-row`},pt={id:`pinned-agents-heading`,class:`pinned-agents-heading`},mt={class:`pinned-agents-list`},ht={class:`pinned-agent-portrait`,"aria-hidden":`true`},gt={class:`pinned-agent-name`},_t={key:1,class:`pinned-agents-error`,role:`alert`},vt=L(t({__name:`PinnedAgentsNav`,props:{spaceId:{},agents:{},pinnedAgentIds:{},loading:{type:Boolean},rosterLoading:{type:Boolean},error:{}},emits:[`retry`],setup(e,{emit:t}){let n=e,a=t,o=k(()=>n.pinnedAgentIds.map(e=>n.agents.find(t=>t.agentId===e)).filter(e=>!!e));function c(e,t){return q(e,t)}let l=k(()=>c(`navigation.title`,`pin`)),f=k(()=>c(`loading`,`Reading pinned Agents…`)),p=k(()=>m(`agentProfiles.loading`)),g=k(()=>c(`retry`,`Retry`)),_=k(()=>n.loading||!!n.rosterLoading);return(t,c)=>(s(),u(v,null,[e.spaceId&&o.value.length?(s(),u(`section`,dt,[M(`div`,ft,[M(`h2`,pt,T(l.value),1),_.value&&o.value.length?(s(),h(I,{key:0,variant:`inline`,label:n.rosterLoading&&!n.loading?p.value:f.value},null,8,[`label`])):j(``,!0)]),M(`ul`,mt,[(s(!0),u(v,null,i(o.value,t=>(s(),u(`li`,{key:t.agentId},[d(D(U),{class:`pinned-agent-nav`,to:D(ae)(e.spaceId,t.agentId),"aria-label":`${D(K)(t)} · ${D(m)(`agentProfiles.profile`)}`},{default:r(()=>[M(`span`,ht,T(t.profile.occupationEmoji||D(K)(t).slice(0,1)),1),M(`span`,gt,T(D(K)(t)),1)]),_:2},1032,[`to`,`aria-label`])]))),128))])])):j(``,!0),e.spaceId&&e.error?(s(),u(`div`,_t,[M(`span`,null,T(e.error),1),M(`button`,{class:`quiet-button`,type:`button`,onClick:c[0]||=e=>a(`retry`)},T(g.value),1)])):j(``,!0)],64))}}),[[`__scopeId`,`data-v-45d6ccab`]]),yt=[`routes.`,`common.`,`employees.`,`agentProfiles.`];function bt(e,t=``){let n=typeof e==`string`&&e?e:t;return yt.some(e=>n.startsWith(e))?m(n):n}function xt(e){if(typeof document>`u`)return;let t=bt(e,`routes.overview.title`);document.title=`${t} · ${m(`common.brand`)}`}var St={class:`app-shell`},Ct=[`onKeydown`],wt=[`aria-label`,`title`],Tt=[`aria-label`],Et={class:`nav-label`},Dt={class:`nav-label`},Ot={class:`nav-label`},kt={class:`nav-label`},At={class:`nav-label`},jt={class:`nav-label`},Mt={class:`nav-section-label`},Nt={class:`nav-label`},Pt={class:`nav-label`},Ft={class:`nav-label`},It={class:`nav-label`},Lt={class:`nav-label`},Rt={class:`nav-label`},zt={class:`sidebar-foot`},Bt=[`aria-label`],Vt={class:`runtime-status`},Ht={class:`service-details`},Ut={class:`runtime-status`},Wt=[`aria-label`],Gt=[`inert`],Kt=[`aria-expanded`,`aria-label`],qt={key:0,class:`workbench-host-label`},Jt={key:1,class:`topbar-heading`},Yt={key:2,class:`topbar-actions`},Xt=[`aria-label`],Zt=L(t({__name:`ConsoleLayout`,setup(t){let i=P(),p=z(),g=c(!1),_=c(null),v=c(null),b=k(()=>i.activePersonalSpace?.id??``),w=k(()=>b.value||`current`),E=k(()=>ue(w.value,`graph`)),O=k(()=>de(`personal`,w.value,`directory`)),N=oe(b),F=se(b),I=N.agents,L=N.loading,B=N.error,H=F.agentIds,W=F.loading,ee=F.error,te=k(()=>ee.value||B.value);n(ce,N),n(le,F);let ne=k(()=>bt(p.meta.title,`routes.overview.title`)),G=k(()=>p.meta.dedicatedWorkspace===!0),re=k(()=>i.publicRuntimeStatus===`ready`?m(`console.services.publicReady`):i.publicRuntimeStatus===`error`?m(`console.services.publicError`):m(`console.services.publicOffline`)),ie=k(()=>{if(i.publicRuntimeStatus===`ready`){let e=i.state?.capabilities;return e?.browsePublicProjects&&e?.subscribeProject&&!e?.publishProject&&!e?.submitKnowledge&&!e?.reviewProposals?m(`console.services.publicBrowseSubscribe`):m(`console.services.publicAvailable`)}return i.publicRuntimeStatus===`error`?m(`console.services.localUnaffected`):m(`console.services.localOnly`)}),ae=k(()=>i.state?.capabilities?.browsePublicProjects!==!1),K=k(()=>{let e=i.state?.capabilities;return e?.submitKnowledge!==!1||e?.reviewProposals===!0});o(()=>{i.runtimeStatus===`idle`&&i.refresh(),window.addEventListener(`resize`,q)}),a(()=>window.removeEventListener(`resize`,q));function q(){window.innerWidth>920&&(g.value=!1)}function J(e){if(!g.value||e.key!==`Tab`)return;let t=[...e.currentTarget.querySelectorAll(`button:not(:disabled), a[href], input:not(:disabled), [tabindex="0"]`)].filter(e=>e.getClientRects().length>0),n=t[0],r=t.at(-1);e.shiftKey&&document.activeElement===n?(e.preventDefault(),r?.focus()):!e.shiftKey&&document.activeElement===r&&(e.preventDefault(),n?.focus())}f(()=>{xt(p.meta.title)}),e(()=>p.fullPath,()=>{g.value=!1});async function fe(){g.value=!0,await S(),v.value?.focus({preventScroll:!0})}async function Y(){g.value=!1,await S(),_.value?.focus({preventScroll:!0})}function pe(){Promise.all([N.load(),F.load({force:!0})])}return(e,t)=>(s(),u(`div`,St,[M(`aside`,{id:`console-primary-sidebar`,class:x([`sidebar`,{"is-mobile-open":g.value}]),onKeydown:[C(y(Y,[`stop`]),[`esc`]),J]},[M(`button`,{ref_key:`mobileNavCloseRef`,ref:v,class:`mobile-nav-close quiet-button`,type:`button`,"aria-label":D(m)(`console.navigation.closeMenu`),title:D(m)(`console.navigation.closeMenu`),onClick:Y},[...t[1]||=[M(`span`,{class:`mobile-nav-close-icon`,"aria-hidden":`true`},null,-1)]],8,wt),d(Te),M(`nav`,{class:`primary-nav`,"aria-label":D(m)(`console.navigation.aria`)},[d(D(U),{to:`/`,"exact-active-class":`is-active`},{default:r(()=>[t[2]||=M(`span`,{class:`nav-icon nav-icon-overview`,"aria-hidden":`true`},null,-1),M(`span`,Et,T(D(m)(`console.navigation.overview`)),1)]),_:1}),d(D(U),{to:`/project-agents`,"active-class":`is-active`},{default:r(()=>[t[3]||=M(`span`,{class:`nav-icon nav-icon-project-agent`,"aria-hidden":`true`},null,-1),M(`span`,Dt,T(D(m)(`console.navigation.projectAgents`)),1)]),_:1}),d(ut,{"personal-space-id":w.value,projects:D(i).state?.personalProjects??[]},null,8,[`personal-space-id`,`projects`]),d(D(U),{to:`/roundtables`,"active-class":`is-active`},{default:r(()=>[t[4]||=M(`span`,{class:`nav-icon nav-icon-roundtable`,"aria-hidden":`true`},null,-1),M(`span`,Ot,T(D(m)(`console.navigation.roundtables`)),1)]),_:1}),d(D(U),{to:E.value,"active-class":`is-active`},{default:r(()=>[t[5]||=M(`span`,{class:`nav-icon nav-icon-personal-project`,"aria-hidden":`true`},null,-1),M(`span`,kt,T(D(m)(`console.navigation.personalProjects`)),1)]),_:1},8,[`to`]),d(D(U),{to:`/preferences`,"active-class":`is-active`},{default:r(()=>[t[6]||=M(`span`,{class:`nav-icon nav-icon-personal-profile`,"aria-hidden":`true`},null,-1),M(`span`,At,T(D(m)(`console.navigation.preferences`)),1)]),_:1}),d(D(U),{to:O.value,"active-class":`is-active`},{default:r(()=>[t[7]||=M(`span`,{class:`nav-icon nav-icon-knowledge-graph`,"aria-hidden":`true`},null,-1),M(`span`,jt,T(D(m)(`console.navigation.knowledge`)),1)]),_:1},8,[`to`]),d(vt,{"space-id":b.value,agents:D(I),"pinned-agent-ids":D(H),loading:D(W),"roster-loading":D(L),error:te.value,onRetry:pe},null,8,[`space-id`,`agents`,`pinned-agent-ids`,`loading`,`roster-loading`,`error`]),M(`p`,Mt,T(D(m)(`console.navigation.more`)),1),d(D(U),{to:`/organize`,"active-class":`is-active`},{default:r(()=>[t[8]||=M(`span`,{class:`nav-icon nav-icon-knowledge-organizer`,"aria-hidden":`true`},null,-1),M(`span`,Nt,T(D(m)(`console.navigation.organizer`)),1)]),_:1}),K.value?(s(),h(D(U),{key:0,to:`/review`,"active-class":`is-active`},{default:r(()=>[t[9]||=M(`span`,{class:`nav-icon nav-icon-review`,"aria-hidden":`true`},null,-1),M(`span`,Pt,T(D(m)(`console.navigation.review`)),1)]),_:1})):j(``,!0),ae.value?(s(),h(D(U),{key:1,to:`/public-projects`,"active-class":`is-active`},{default:r(()=>[t[10]||=M(`span`,{class:`nav-icon nav-icon-public-project`,"aria-hidden":`true`},null,-1),M(`span`,Ft,T(D(m)(`console.navigation.publicProjects`)),1)]),_:1})):j(``,!0),d(D(U),{to:`/connections`,"active-class":`is-active`},{default:r(()=>[t[11]||=M(`span`,{class:`nav-icon nav-icon-connections`,"aria-hidden":`true`},null,-1),M(`span`,It,T(D(m)(`console.navigation.connections`)),1)]),_:1}),d(D(U),{to:`/settings`,"active-class":`is-active`},{default:r(()=>[t[12]||=M(`span`,{class:`nav-icon nav-icon-settings`,"aria-hidden":`true`},null,-1),M(`span`,Lt,T(D(m)(`console.navigation.settings`)),1)]),_:1}),d(D(U),{to:`/about`,"active-class":`is-active`},{default:r(()=>[t[13]||=M(`span`,{class:`nav-icon nav-icon-about`,"aria-hidden":`true`},null,-1),M(`span`,Rt,T(D(m)(`console.navigation.about`)),1)]),_:1})],8,Tt),M(`div`,zt,[M(`div`,{class:`service-runtime-list`,"aria-label":D(m)(`console.services.aria`)},[M(`div`,Vt,[M(`span`,{class:x([`status-dot`,D(i).runtimeStatus])},null,2),M(`div`,null,[M(`strong`,null,T(D(i).runtimeStatus===`ready`?D(m)(`console.services.localReady`):D(i).runtimeStatus===`error`?D(m)(`console.services.localError`):D(m)(`console.services.localConnecting`)),1)])]),M(`details`,Ht,[M(`summary`,null,[M(`span`,{class:x([`status-dot`,D(i).publicRuntimeStatus])},null,2),A(T(re.value),1)]),M(`div`,Ut,[M(`div`,null,[M(`small`,null,T(ie.value),1),d(D(U),{to:`/connections`},{default:r(()=>[A(T(D(m)(`console.navigation.connections`)),1)]),_:1})])])])],8,Bt)])],42,Ct),g.value?(s(),u(`button`,{key:0,class:`mobile-nav-backdrop`,type:`button`,tabindex:`-1`,"aria-label":D(m)(`console.navigation.closeMenu`),onClick:Y},null,8,Wt)):j(``,!0),M(`main`,{class:`workspace`,inert:g.value||void 0},[M(`header`,{class:x([`topbar`,{"topbar--workbench":G.value}])},[M(`button`,{ref_key:`mobileNavToggleRef`,ref:_,class:`mobile-nav-toggle quiet-button`,type:`button`,"aria-controls":`console-primary-sidebar`,"aria-expanded":g.value,"aria-label":D(m)(`console.navigation.openMenu`),onClick:fe},[...t[14]||=[M(`span`,{class:`mobile-nav-icon`,"aria-hidden":`true`},null,-1)]],8,Kt),G.value?(s(),u(`span`,qt,`FULI`)):(s(),u(`div`,Jt,[M(`h1`,null,T(ne.value),1)])),G.value?j(``,!0):(s(),u(`div`,Yt,[D(p).name===`settings`?(s(),h(R,{key:0,variant:`primary`,disabled:D(i).settingsSaving,form:`settings-form`,type:`submit`},{default:r(()=>[A(T(D(m)(`settings.save`)),1)]),_:1},8,[`disabled`])):j(``,!0),d(Oe),d(R,{variant:`ghost`,icon:``,disabled:D(i).runtimeStatus===`loading`,"aria-label":D(m)(`common.actions.refresh`),title:D(m)(`common.actions.refresh`),onClick:D(i).refresh},{default:r(()=>[...t[15]||=[M(`svg`,{viewBox:`0 0 20 20`,width:`18`,height:`18`,fill:`none`,stroke:`currentColor`,"stroke-width":`1.5`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},[M(`path`,{d:`M16 7a6.5 6.5 0 1 0 .5 5M16 3v4h-4`})],-1)]]),_:1},8,[`disabled`,`aria-label`,`title`,`onClick`])]))],2),d(Me),D(i).feedback?(s(),u(`div`,{key:0,class:x([`feedback feedback-message`,{success:D(i).feedback.tone===`success`}]),role:`status`,"aria-live":`polite`},[M(`span`,null,T(D(i).feedback.message),1),M(`button`,{class:`feedback-dismiss`,type:`button`,"aria-label":D(m)(`common.actions.close`),onClick:t[0]||=(...e)=>D(i).clearFeedback&&D(i).clearFeedback(...e)},`×`,8,Xt)],2)):j(``,!0),d(D(V),null,{default:r(({Component:e})=>[(s(),h(l(e)))]),_:1})],8,Gt)]))}}),[[`__scopeId`,`data-v-2ba97143`]]),Qt=t({__name:`App`,setup(e){let t=z();return(e,n)=>D(t).meta.publicReport?(s(),h(D(V),{key:0})):(s(),h(Zt,{key:1}))}}),$t=`modulepreload`,en=function(e){return`/`+e},tn={},Z=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=en(t,n),t=s(t),t in tn)return;tn[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:$t,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})};function nn(e){let t=e.match(/^#\/knowledge\/([^/?#]+)\/([^/?#]+)\/(entity|relationship)\/([^/?#]+)(?:\?([^#]*))?$/);if(!t)return null;let[,n,r,i,a,o]=t;return`/knowledge/${n}/${r}/directory/${i}/${a}${o?`?${o}`:``}`}var Q=H({history:B(`/`),routes:[{path:`/reports/jefa`,name:`shared-jefa-report`,component:()=>Z(()=>import(`./SharedJefaReportPage-Bg6lIk3E.js`),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10])),meta:{publicReport:!0,title:`employees.share.title`}},{path:`/roundtables/:threadId?`,name:`roundtables`,component:()=>Z(()=>import(`./RoundtablesPage-L3ShEWS3.js`),__vite__mapDeps([11,1,2,3,4,5,12,13])),meta:{title:`routes.roundtable.title`}},{path:`/`,name:`overview`,component:()=>Z(()=>import(`./OverviewPage-3eZjQ26v.js`),__vite__mapDeps([14,1,15,2,3,4,12,5,16,17,18,19,20,21,22])),meta:{eyebrow:``,title:`routes.overview.title`}},{path:`/preferences`,name:`personal-profile`,component:()=>Z(()=>import(`./PersonalProfilePage-CDQk096L.js`),__vite__mapDeps([23,1,15,2,3,4,24,12,5,25,26,27,28,29,30,31,32,33,34,35,36,37,38])),meta:{eyebrow:`PERSONAL LENS`,title:`routes.preferences.title`,description:`routes.preferences.description`}},{path:`/preferences/writing`,name:`writing-taste`,component:()=>Z(()=>import(`./WritingTastePage-DCULB-d2.js`),__vite__mapDeps([39,1,15,2,3,4,12,5,30,34,24,25,26,27,35,31,36,37,40])),meta:{eyebrow:``,title:`routes.writingTaste.title`}},{path:`/personal/:spaceId/projects/:projectId/:mode/:itemKind/:itemId`,name:`personal-project-item`,component:()=>Z(()=>import(`./PersonalProjectsPage-CJxp4Wyz.js`),__vite__mapDeps([41,1,42,15,2,3,4,24,5,12,25,26,27,30,20,43,17,44,45,35,29,31,32,33,34,36,46])),meta:{eyebrow:`PERSONAL PROJECT`,title:`routes.personalProjects.title`}},{path:`/personal/:spaceId/projects/:mode/:itemKind/:itemId`,name:`personal-projects-item`,component:()=>Z(()=>import(`./PersonalProjectsPage-CJxp4Wyz.js`),__vite__mapDeps([41,1,42,15,2,3,4,24,5,12,25,26,27,30,20,43,17,44,45,35,29,31,32,33,34,36,46])),meta:{eyebrow:`PERSONAL PROJECTS`,title:`routes.personalProjects.title`,description:`routes.personalProjects.description`}},{path:`/personal/:spaceId/projects/:mode`,name:`personal-projects`,component:()=>Z(()=>import(`./PersonalProjectsPage-CJxp4Wyz.js`),__vite__mapDeps([41,1,42,15,2,3,4,24,5,12,25,26,27,30,20,43,17,44,45,35,29,31,32,33,34,36,46])),meta:{eyebrow:`PERSONAL PROJECTS`,title:`routes.personalProjects.title`,description:`routes.personalProjects.description`}},{path:`/personal/:spaceId/projects/:projectId/:mode`,name:`personal-project`,component:()=>Z(()=>import(`./PersonalProjectsPage-CJxp4Wyz.js`),__vite__mapDeps([41,1,42,15,2,3,4,24,5,12,25,26,27,30,20,43,17,44,45,35,29,31,32,33,34,36,46])),meta:{eyebrow:`PERSONAL PROJECT`,title:`routes.personalProjects.title`}},{path:`/organize`,name:`knowledge-organizer`,component:()=>Z(()=>import(`./KnowledgeOrganizerPage-DT3BROZm.js`),__vite__mapDeps([47,1,15,2,3,4,24,25,26,27,35,29,30,31,32,33,34,36,48])),meta:{eyebrow:`KNOWLEDGE CLASSIFICATION`,title:`routes.organizer.title`}},{path:`/project-agents`,name:`project-agents`,component:()=>Z(()=>import(`./AgentsDirectoryPage-CBFTg_IE.js`),__vite__mapDeps([49,1,15,2,3,4,5,12,26,27,50,16,51,17,52,18,19,21,53,54,55,56,24,25,20,7,8,57,58,59])),meta:{eyebrow:``,title:`routes.projectAgents.title`}},{path:`/agents/:spaceId/:agentId`,name:`agent-profile`,component:()=>Z(()=>import(`./AgentProfilePage-CFViQ8kj.js`),__vite__mapDeps([60,1,15,2,3,4,5,12,26,27,30,17,19,18,20,43,44,61,62,21,53,45,59])),meta:{eyebrow:``,title:`agentProfiles.profile`}},{path:`/project-agents/manage`,name:`agent-management`,component:()=>Z(()=>import(`./ProjectAgentsPage-BuNNqSEy.js`),__vite__mapDeps([63,1,15,2,3,4,24,5,12,25,26,27,50,16,51,19,61,62,21,53,54,55,7,8,56,20,57,64])),meta:{eyebrow:``,title:`agentProfiles.advanced`}},{path:`/employees/:templateId`,name:`employee-workbench`,component:()=>Z(()=>import(`./EmployeeWorkbenchPage-TBl_HNj6.js`),__vite__mapDeps([65,1,15,2,3,4,24,5,12,25,26,27,50,16,51,17,43,44,53,7,8,56,20,57,6,9,28,66,67])),meta:{eyebrow:``,title:`routes.employeeWorkbench.title`,dedicatedWorkspace:!0}},{path:`/public-projects`,name:`public-projects`,component:()=>Z(()=>import(`./PublicProjectsPage-ByOT559r.js`),__vite__mapDeps([68,1,15,2,3,4,24,5,25,26,27,20,35,69])),meta:{eyebrow:`PUBLIC PROJECTS`,title:`routes.publicProjects.title`,description:`routes.publicProjects.description`}},{path:`/knowledge`,name:`knowledge-default`,component:()=>Z(()=>import(`./KnowledgePage-C4J4VueL.js`),__vite__mapDeps([70,1,42,15,2,3,4,24,5,12,25,26,27,30,20,43,17,44,45,35,29,31,32,33,34,36,46])),meta:{eyebrow:`KNOWLEDGE BASE`,title:`routes.knowledge.title`}},{path:`/knowledge/:scope/:spaceId/:projectId/:mode/:itemKind/:itemId`,name:`project-knowledge-item`,component:()=>Z(()=>import(`./KnowledgePage-C4J4VueL.js`),__vite__mapDeps([70,1,42,15,2,3,4,24,5,12,25,26,27,30,20,43,17,44,45,35,29,31,32,33,34,36,46])),meta:{eyebrow:`KNOWLEDGE BASE`,title:`routes.knowledge.title`}},{path:`/knowledge/:scope/:spaceId/:mode/:itemKind/:itemId`,name:`knowledge-item`,component:()=>Z(()=>import(`./KnowledgePage-C4J4VueL.js`),__vite__mapDeps([70,1,42,15,2,3,4,24,5,12,25,26,27,30,20,43,17,44,45,35,29,31,32,33,34,36,46])),meta:{eyebrow:`KNOWLEDGE BASE`,title:`routes.knowledge.title`}},{path:`/knowledge/:scope/:spaceId/:projectId/:mode`,name:`project-knowledge`,component:()=>Z(()=>import(`./KnowledgePage-C4J4VueL.js`),__vite__mapDeps([70,1,42,15,2,3,4,24,5,12,25,26,27,30,20,43,17,44,45,35,29,31,32,33,34,36,46])),meta:{eyebrow:`KNOWLEDGE BASE`,title:`routes.knowledge.title`}},{path:`/knowledge/:scope/:spaceId/:mode`,name:`knowledge`,component:()=>Z(()=>import(`./KnowledgePage-C4J4VueL.js`),__vite__mapDeps([70,1,42,15,2,3,4,24,5,12,25,26,27,30,20,43,17,44,45,35,29,31,32,33,34,36,46])),meta:{eyebrow:`KNOWLEDGE BASE`,title:`routes.knowledge.title`}},{path:`/review`,name:`review`,component:()=>Z(()=>import(`./ReviewPage-C0Vdr9uq.js`),__vite__mapDeps([71,1,15,2,3,4,26,27,35,31])),meta:{eyebrow:`REVIEW WORKSPACE`,title:`routes.review.title`,description:`routes.review.description`}},{path:`/connections`,name:`connections`,component:()=>Z(()=>import(`./ConnectionsPage-uXcabab_.js`),__vite__mapDeps([72,1,15,2,3,4,26,27,30,35,73])),meta:{eyebrow:`SERVICE CONNECTIONS`,title:`routes.connections.title`,description:`routes.connections.description`}},{path:`/settings`,name:`settings`,component:()=>Z(()=>import(`./SettingsPage-McYuinGz.js`),__vite__mapDeps([74,1,15,2,3,4,24,26,27,30,66,32,75])),meta:{eyebrow:``,title:`routes.settings.title`}},{path:`/about`,name:`about`,component:()=>Z(()=>import(`./AboutPage-Dized8Uz.js`),__vite__mapDeps([76,1,3,77,78])),meta:{eyebrow:`FULI`,title:`routes.about.title`}},{path:`/:pathMatch(.*)*`,name:`not-found`,component:()=>Z(()=>import(`./NotFoundPage-x_Yd59mm.js`),__vite__mapDeps([79,1,12,5])),meta:{title:`common.errors.pageNotFound`}}],scrollBehavior:(e,t,n)=>n??(e.path!==t.path&&{top:0})});ke(Q),Q.beforeEach(e=>e.path!==`/`||typeof window>`u`?!0:nn(window.location.hash)??!0),Q.afterEach(e=>{xt(e.meta.title)});var $=g(Qt);$.use(N()),$.use(E),$.use(Q),await Q.isReady().catch(()=>void 0),$.mount(`#app`);
@@ -9,7 +9,7 @@
9
9
  />
10
10
  <link rel="icon" type="image/png" href="/assets/fuli-logo-ER1wQ8Kk.png" />
11
11
  <title>复利 · Context Graph</title>
12
- <script type="module" crossorigin src="/assets/index-DO10_fLc.js"></script>
12
+ <script type="module" crossorigin src="/assets/index-DuxXKFrR.js"></script>
13
13
  <link rel="modulepreload" crossorigin href="/assets/i18n-oJFlt8hW.js">
14
14
  <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-BDNMzG2s.js">
15
15
  <link rel="modulepreload" crossorigin href="/assets/client-DnwnUBQV.js">
@@ -25,6 +25,28 @@ directory, or `--runtime-config FILE` for an explicit runtime configuration.
25
25
  `args` for clients with their own configuration editor. Paths with spaces remain
26
26
  individual JSON arguments; do not paste them as an unquoted shell command.
27
27
 
28
+ ## Claude Desktop and other chat apps
29
+
30
+ `fuli setup` detects Claude Desktop (Windows and macOS) and adds a `fuli` entry to
31
+ `claude_desktop_config.json`, keeping its other settings and servers and backing up the
32
+ file first. Quit and reopen Claude Desktop afterwards; it only reads the file at launch.
33
+
34
+ Chat apps load every tool schema into each conversation, so Claude Desktop gets the
35
+ **chat profile** (`--tool-profile chat`): 20 tools for preferences, project knowledge,
36
+ recording, completion checkpoints, conversation recovery and the Roundtable, about
37
+ 40 KB of schema instead of about 200 KB for all 141 tools. Management (executors,
38
+ routing, publishing, recruitment) stays in the console. Its conversations are attributed
39
+ to `claude`. For another chat-style client, print the same entry with
40
+ `fuli connect --profile chat --source-application claude` (or the matching source).
41
+
42
+ Claude Desktop has no lifecycle hooks, so task entry and the closing attribution line
43
+ depend on the Agent following FULI's instructions, and visible conversations are not
44
+ captured automatically.
45
+
46
+ The generated paths go through the active Node location (for example nvm's
47
+ `C:\nvm4w\nodejs`) rather than a versioned install folder, so switching Node
48
+ versions with a version manager does not break client configuration.
49
+
28
50
  Clients that only ask for a command can use `fuli mcp` if `fuli` is on their PATH.
29
51
  `fuli mcp --tools` lists the available tool contract without starting a task.
30
52
  Configuration export is read-only: it neither modifies client settings nor
@@ -54,7 +76,7 @@ is not proof that task entry or completion ran.
54
76
  | Capability | Generic MCP | Current dedicated adapters |
55
77
  | --- | --- | --- |
56
78
  | Tools, authorized preferences and working memory | Available through tool calls | Shared behavior |
57
- | Automatic configuration and bundled instructions | Merge generated configuration | Codex, Claude Code, Cursor |
79
+ | Automatic configuration and bundled instructions | Merge generated configuration | Codex, Claude Code, Cursor; Claude Desktop (configuration only, chat profile) |
58
80
  | Task entry and completion hooks | Agent follows instructions; no guaranteed hook | Host-specific adapters; Cursor has a limited completion reminder |
59
81
  | Automatic visible conversation capture | No | Supported Codex and Claude Code transcript formats |
60
82
  | Roundtable wake/resume | Messages can wait for the recipient's next task | Codex and Claude Code CLIs |
@@ -47,6 +47,7 @@ ProjectAgentRoutingOutcome = Literal[
47
47
  'blocked',
48
48
  ]
49
49
  ProjectAgentRoutingReason = Literal[
50
+ 'project_team_lead',
50
51
  'explicit_agent',
51
52
  'project_continuity',
52
53
  'project_default',
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fuli-graph-provider"
3
- version = "0.12.1"
3
+ version = "0.12.3"
4
4
  description = "Graphiti-backed personal and shared project provider for Fuli."
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "fuli-context",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "fuli-context",
9
- "version": "0.12.1",
9
+ "version": "0.12.3",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@a2a-js/sdk": "1.3.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fuli-context",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "description": "Local-first collaboration relationship graph for AI agents.",
5
5
  "type": "module",
6
6
  "files": [
@@ -26,7 +26,8 @@ Install and Agent connection:
26
26
  peer status|host|invite|join|share|revoke|off [--data-dir DIR] (LAN roundtable Beta: pair devices and share project leads)
27
27
  remote-mcp --personal-project-id ID --bearer-token-file FILE [--port PORT] [--data-dir DIR] [--source-application claude|cursor|other] [--allowed-origin HTTPS_ORIGIN] [--allowed-host HOST] [--max-sessions N] [--session-idle-ttl-seconds N]
28
28
  employee install BUILT_PACKAGE_DIRECTORY [--replace] [--runtime-config FILE]
29
- update [setup options]
29
+ update [setup options] (already latest: no restart; otherwise refresh existing connections only)
30
+ setup --connected-only [setup options] (repair existing connections without adding clients)
30
31
  uninstall [--yes] [--data-dir DIR]
31
32
 
32
33
  Graph data portability:
@@ -4,13 +4,13 @@ import { clientConnectionConfig } from '../setup/client-connection.js';
4
4
 
5
5
  export function runConnectCommand(args, { env = process.env, write = console.log } = {}) {
6
6
  if (args.length === 1 && ['--help', '-h'].includes(args[0])) {
7
- write('fuli connect [--format standard|vscode|server] [--data-dir DIR | --runtime-config FILE]\nPrint local MCP configuration; merge the fuli entry into your client settings. No files are changed.');
7
+ write('fuli connect [--format standard|vscode|server] [--profile full|chat] [--source-application codex|claude|claude_code|cursor|kiro|other] [--data-dir DIR | --runtime-config FILE]\nPrint local MCP configuration; merge the fuli entry into your client settings. No files are changed.\nUse --profile chat for chat apps that load every tool into each conversation. fuli setup configures Claude Desktop automatically.');
8
8
  return;
9
9
  }
10
10
  const options = new Map();
11
11
  for (let index = 0; index < args.length; index += 2) {
12
12
  const flag = args[index];
13
- if (!['--format', '--data-dir', '--runtime-config'].includes(flag)) throw new TypeError(`Unknown connect option: ${flag}`);
13
+ if (!['--format', '--profile', '--source-application', '--data-dir', '--runtime-config'].includes(flag)) throw new TypeError(`Unknown connect option: ${flag}`);
14
14
  if (options.has(flag)) throw new TypeError(`Duplicate ${flag}`);
15
15
  const value = args[index + 1];
16
16
  if (!value?.trim() || value.startsWith('--')) throw new TypeError(`Missing value for ${flag}`);
@@ -22,7 +22,10 @@ export function runConnectCommand(args, { env = process.env, write = console.log
22
22
  const paths = resolveSetupPaths({ dataDir: options.get('--data-dir'), env });
23
23
  const runtimeConfigPath = options.has('--data-dir') ? paths.graphRuntimeConfigPath
24
24
  : resolveGraphRuntimeOptions(options.has('--runtime-config') ? ['--runtime-config', options.get('--runtime-config')] : [], env).runtimeConfigPath;
25
- const config = clientConnectionConfig({ mcpServerPath: paths.mcpServerPath, runtimeConfigPath }, options.get('--format'));
25
+ const config = clientConnectionConfig({ mcpServerPath: paths.mcpServerPath, runtimeConfigPath }, options.get('--format'), {
26
+ profile: options.get('--profile') ?? 'full',
27
+ sourceApplication: options.get('--source-application') ?? 'other'
28
+ });
26
29
  write(JSON.stringify(config, null, 2));
27
30
  return config;
28
31
  }
@@ -12,6 +12,7 @@ import {
12
12
  import { runStdio } from '../mcp/runtime.js';
13
13
  import { prepareDirectAgentToolCall } from '../mcp/direct-call.js';
14
14
  import { normalizeMcpSourceApplication } from '../mcp/session-id.js';
15
+ import { toolNamesForProfile } from '../mcp/tool-profiles.js';
15
16
  import { applicationErrorMessage } from '../mcp/tool-result.js';
16
17
 
17
18
  export async function runMcpCommand(args, { env = process.env } = {}) {
@@ -42,7 +43,8 @@ export async function runMcpCommand(args, { env = process.env } = {}) {
42
43
  runtimeConfigPath,
43
44
  sourceApplication: configuredSourceApplication
44
45
  ? normalizeMcpSourceApplication(configuredSourceApplication)
45
- : undefined
46
+ : undefined,
47
+ toolNames: toolNamesForProfile(args.includes('--tool-profile') ? requiredOption(args, '--tool-profile') : 'full')
46
48
  });
47
49
  }
48
50
 
@@ -92,7 +94,7 @@ export function startupMessage(error) {
92
94
 
93
95
  function helpText() {
94
96
  return 'Usage: fuli mcp [--runtime-config <graph-runtime.json>] ' +
95
- '[--source-application <codex|claude|claude_code|cursor|kiro|other>]\n' +
97
+ '[--source-application <codex|claude|claude_code|cursor|kiro|other>] [--tool-profile <full|chat>]\n' +
96
98
  ' fuli mcp --tools\n' +
97
99
  ' fuli mcp --runtime-config <graph-runtime.json> ' +
98
100
  '--call <tool> [--input <json>]\n';
@@ -20,7 +20,11 @@ export async function runSetupCommand(args, dependencies = {}) {
20
20
  const selectAgents = dependencies.selectAgents ?? promptAgentSelection;
21
21
  const write = dependencies.write ?? writeLine;
22
22
  let plan = planSetup(options, dependencies);
23
- if (shouldPromptForAgentSelection(plan, options)) {
23
+ if (options.connectedOnly) {
24
+ plan = applyAgentSelection(plan, plan.agents.filter((agent) =>
25
+ ['current', 'outdated'].includes(agent.integrationDetails?.mcp)
26
+ ).map(({ id }) => id));
27
+ } else if (shouldPromptForAgentSelection(plan, options)) {
24
28
  const detected = plan.agents.filter(({ available }) => available);
25
29
  plan = applyAgentSelection(plan, await selectAgents(detected));
26
30
  }
@@ -36,7 +40,7 @@ export async function runSetupCommand(args, dependencies = {}) {
36
40
  onProgress: write
37
41
  });
38
42
  write(formatSetupResult(result, plan));
39
- return result;
43
+ return { ...result, exitCode: result.status === 'partial' ? 1 : 0 };
40
44
  }
41
45
 
42
46
  export function formatSetupPreview(plan, options) {
@@ -78,6 +82,12 @@ export function formatSetupPreview(plan, options) {
78
82
  'Recommendation: native mode avoids shared VM memory on this low-memory Mac; ' +
79
83
  'select it with --runtime-mode native');
80
84
  }
85
+ if (options.connectedOnly) {
86
+ for (const agent of available.filter((agent) => agent.selected === false && agent.skillPath &&
87
+ ['current', 'outdated'].includes(agent.integrationDetails?.skills))) {
88
+ lines.push(`${agent.label}: skipped because there is no Fuli MCP entry; existing Skills were left unchanged. Run fuli setup to reconnect.`);
89
+ }
90
+ }
81
91
  return lines.join('\n');
82
92
  }
83
93
 
@@ -102,11 +112,17 @@ export function formatSetupResult(result, plan) {
102
112
  lines.push(`${agent.label}: ${state}${agent.message ? ` (${agent.message})` : ''}; retry fuli setup`);
103
113
  continue;
104
114
  }
105
- lines.push(agent.newTaskRequired
106
- ? `${agent.label}: connected; create or reopen a task to load the new configuration`
107
- : `${agent.label}: connected`);
115
+ lines.push(agent.restartRequired
116
+ ? `${agent.label}: connected; quit and reopen the app to load Fuli`
117
+ : agent.newTaskRequired
118
+ ? `${agent.label}: connected; create or reopen a task to load the new configuration`
119
+ : `${agent.label}: connected`);
108
120
  for (const step of agent.nextSteps ?? []) lines.push(`${agent.label}: ${step}`);
109
121
  }
122
+ if (result.agents.some((agent) => agent.status === 'connected' &&
123
+ !agent.restartRequired && !agent.newTaskRequired)) {
124
+ lines.push('After a package upgrade, reconnect Fuli in already-open clients or restart the clients, then open a new task. Existing MCP processes are not replaced automatically.');
125
+ }
110
126
  const dataDir = plan?.paths?.dataDir;
111
127
  lines.push('Other MCP clients: run fuli connect and merge the fuli entry into your MCP settings.');
112
128
  if (dataDir) lines.push(`For this installation, use --data-dir ${JSON.stringify(dataDir)}.`);
@@ -10,6 +10,7 @@ import {
10
10
  import { quoteShellArgument } from './shell-argument.js';
11
11
  import { parseUpdateOptions } from '../setup/options.js';
12
12
  import { runLocalRuntimeCommand } from './local-runtime-command.js';
13
+ import { formatUpdateInstallation, inspectUpdateInstallation, updateEnvironment } from './update-installation.js';
13
14
  import {
14
15
  compareSemanticVersions,
15
16
  parseSemanticVersion
@@ -21,9 +22,10 @@ const LATEST_PACKAGE = `${FULI_PACKAGE_NAME}@latest`;
21
22
 
22
23
  export async function runUpdateCommand(args = [], dependencies = {}) {
23
24
  const options = parseUpdateOptions(args);
24
- const env = dependencies.env ?? process.env;
25
25
  const platform = dependencies.platform ?? process.platform;
26
26
  const nodePath = dependencies.nodePath ?? process.execPath;
27
+ const originalEnv = dependencies.env ?? process.env;
28
+ const env = updateEnvironment(originalEnv, nodePath, platform);
27
29
  const npmCommand = dependencies.npmCommand ??
28
30
  (platform === 'win32' ? 'npm.cmd' : 'npm');
29
31
  const confirm = dependencies.confirm ?? confirmInTerminal;
@@ -36,13 +38,17 @@ export async function runUpdateCommand(args = [], dependencies = {}) {
36
38
  (() => queryLatestVersion(npmCommand, runCaptured, env));
37
39
 
38
40
  write(formatUpdatePreview(options));
39
- if (!options.yes && !await confirm()) {
40
- write('Cancelled. No changes were made.');
41
- return { status: 'cancelled' };
42
- }
43
-
41
+ const inspectInstallation = dependencies.inspectInstallation ?? inspectUpdateInstallation;
42
+ const installation = inspectInstallation({ env: originalEnv, platform });
43
+ write(formatUpdateInstallation(installation, { nodePath, npmCommand }));
44
44
  const latestVersion = resolveLatestVersion();
45
- if (compareSemanticVersions(latestVersion, FULI_VERSION) < 0) {
45
+ const comparison = compareSemanticVersions(latestVersion, FULI_VERSION);
46
+ if (comparison === 0) {
47
+ const result = { status: 'current', previousVersion: FULI_VERSION, version: FULI_VERSION };
48
+ write(formatUpdateResult(result));
49
+ return result;
50
+ }
51
+ if (comparison < 0) {
46
52
  const result = {
47
53
  status: 'ahead',
48
54
  previousVersion: FULI_VERSION,
@@ -53,6 +59,22 @@ export async function runUpdateCommand(args = [], dependencies = {}) {
53
59
  return result;
54
60
  }
55
61
 
62
+ const globalRootResult = runCaptured(npmCommand, ['root', '--global'], { env });
63
+ if (!processSucceeded(globalRootResult)) {
64
+ throw new Error(`Could not locate the npm installation target (${describeProcessFailure(globalRootResult)}). Local services were not stopped.`);
65
+ }
66
+ const globalRoot = String(globalRootResult.stdout ?? '').trim();
67
+ const cliPath = resolveGlobalCliPath(globalRoot, platform);
68
+ if (!cliPath) throw new Error('npm returned an invalid global directory. Local services were not stopped.');
69
+ const recoveryCommand = formatSetupRecoveryCommand(args, platform, [nodePath, cliPath]);
70
+ write(`Update target: ${cliPath}`);
71
+ if (installation.cliPath && installation.cliPath !== cliPath) {
72
+ write('The running CLI path differs from the npm target. This command updates the npm target above.');
73
+ }
74
+ if (!options.yes && !await confirm()) {
75
+ write('Cancelled. No changes were made.');
76
+ return { status: 'cancelled' };
77
+ }
56
78
  const stopArgs = options.dataDir ? ['--data-dir', options.dataDir] : [];
57
79
  const stopped = await stopRuntime('stop', stopArgs, { env, write });
58
80
  if (stopped.status === 'partial') {
@@ -75,61 +97,53 @@ export async function runUpdateCommand(args = [], dependencies = {}) {
75
97
  throw new Error([
76
98
  `Global package installation failed (${describeProcessFailure(installed)}). ` +
77
99
  'Knowledge data was not deleted.',
78
- `Run npm install --global ${LATEST_PACKAGE}, then run fuli setup --yes.`
100
+ 'Local services were stopped and have not been restarted.',
101
+ `Run npm install --global ${selectedPackage}, then run ${recoveryCommand}.`
79
102
  ].join('\n'));
80
103
  }
81
104
 
82
- const globalRootResult = runCaptured(npmCommand, ['root', '--global'], { env });
83
- if (!processSucceeded(globalRootResult)) {
84
- throw postInstallError(
85
- `Could not locate the global npm directory (${describeProcessFailure(globalRootResult)})`,
86
- args,
87
- platform
88
- );
89
- }
90
- const globalRoot = String(globalRootResult.stdout ?? '').trim();
91
- const cliPath = resolveGlobalCliPath(globalRoot, platform);
92
- if (!cliPath || !fileExists(cliPath)) {
93
- throw postInstallError('The new CLI file is missing; reinstall the global package', args, platform);
105
+ if (!fileExists(cliPath)) {
106
+ throw postInstallError('The new CLI file is missing; reinstall the global package', recoveryCommand);
94
107
  }
95
108
 
96
109
  const versionResult = runCaptured(nodePath, [cliPath, '--version'], { env });
97
110
  if (!processSucceeded(versionResult)) {
98
111
  throw postInstallError(
99
112
  `The new CLI could not start (${describeProcessFailure(versionResult)})`,
100
- args,
101
- platform
113
+ recoveryCommand
102
114
  );
103
115
  }
104
116
  const version = String(versionResult.stdout ?? '').trim();
105
- if (!version) throw postInstallError('The new CLI returned no version', args, platform);
117
+ if (!version) throw postInstallError('The new CLI returned no version', recoveryCommand);
106
118
  if (compareSemanticVersions(version, latestVersion) !== 0) {
107
119
  throw postInstallError(
108
120
  `The new CLI version ${version} does not match the npm latest version ${latestVersion} ` +
109
121
  'confirmed before the update',
110
- args,
111
- platform
122
+ recoveryCommand
112
123
  );
113
124
  }
114
125
 
115
126
  write('Refreshing local services, Agent integrations, and Skills with the new CLI...');
116
- const setupArgs = ['setup', '--yes', ...args.filter((arg) => arg !== '--yes')];
127
+ const setupArgs = ['setup', '--yes', '--connected-only', ...args.filter((arg) =>
128
+ arg !== '--yes' && arg !== '--connected-only')];
117
129
  const setupResult = runInherited(nodePath, [cliPath, ...setupArgs], { env });
118
130
  if (!processSucceeded(setupResult)) {
119
131
  throw postInstallError(
120
132
  `The new version was installed, but setup did not complete ` +
121
133
  `(${describeProcessFailure(setupResult)})`,
122
- args,
123
- platform
134
+ recoveryCommand
124
135
  );
125
136
  }
126
137
 
127
138
  const result = {
128
- status: version === FULI_VERSION ? 'current' : 'updated',
139
+ status: 'updated',
129
140
  previousVersion: FULI_VERSION,
130
141
  version
131
142
  };
132
143
  write(formatUpdateResult(result));
144
+ if (options.skipAgents) {
145
+ write('Existing conversations may still use the previous MCP process. Reconnect Fuli in your client or restart the client, then open a new task. Running conversations were not restarted automatically.');
146
+ }
133
147
  return result;
134
148
  }
135
149
 
@@ -138,8 +152,8 @@ export function formatUpdatePreview(options) {
138
152
  'Ready to update Fuli',
139
153
  `npm package: ${LATEST_PACKAGE}`,
140
154
  options.noStart
141
- ? 'Local UI: stop safely and keep stopped after the update'
142
- : 'Local services: stop safely and restart with the new version',
155
+ ? 'If an upgrade is available: stop the local UI safely and keep it stopped'
156
+ : 'If an upgrade is available: stop local services safely and restart with the new version',
143
157
  options.dataDir
144
158
  ? `Data: preserve ${options.dataDir}`
145
159
  : 'Data: preserve the current default data directory',
@@ -149,7 +163,7 @@ export function formatUpdatePreview(options) {
149
163
  : options.adaptiveMemory ? 'enable' : 'disable'}`,
150
164
  `Agent integrations and Skills: ${options.skipAgents
151
165
  ? 'skip refresh as requested'
152
- : 'refresh through setup in the new version'}`
166
+ : 'refresh through setup in the new version; only already connected clients'}`
153
167
  ].join('\n');
154
168
  }
155
169
 
@@ -162,7 +176,7 @@ export function formatUpdateResult(result) {
162
176
  ].join('');
163
177
  }
164
178
  if (result.status === 'current') {
165
- return `Fuli is already at the latest version (${result.version}); local integrations refreshed.`;
179
+ return `Fuli is already at the latest version (${result.version}); no installation or service restart needed. To repair existing integrations, run fuli setup --yes --connected-only.`;
166
180
  }
167
181
  return `Fuli updated from ${result.previousVersion} to ${result.version}.`;
168
182
  }
@@ -173,10 +187,10 @@ export function resolveGlobalCliPath(globalRoot, platform = process.platform) {
173
187
  return path.join(globalRoot.trim(), FULI_PACKAGE_NAME, 'src', 'cli.js');
174
188
  }
175
189
 
176
- function postInstallError(message, args, platform) {
190
+ function postInstallError(message, recoveryCommand) {
177
191
  return new Error([
178
192
  `${message}. Knowledge data was not deleted.`,
179
- `Run again: ${formatSetupRecoveryCommand(args, platform)}`
193
+ `Run again: ${recoveryCommand}`
180
194
  ].join('\n'));
181
195
  }
182
196
 
@@ -204,11 +218,12 @@ function queryLatestVersion(npmCommand, runCaptured, env) {
204
218
  return version;
205
219
  }
206
220
 
207
- function formatSetupRecoveryCommand(args, platform) {
221
+ function formatSetupRecoveryCommand(args, platform, launcher) {
208
222
  const forwarded = args
209
- .filter((arg) => arg !== '--yes')
223
+ .filter((arg) => arg !== '--yes' && arg !== '--connected-only')
210
224
  .map((arg) => formatCommandArgument(arg, platform));
211
- return ['fuli', 'setup', '--yes', ...forwarded].join(' ');
225
+ return [...launcher.map((arg) => formatCommandArgument(arg, platform)),
226
+ 'setup', '--yes', '--connected-only', ...forwarded].join(' ');
212
227
  }
213
228
 
214
229
  function formatCommandArgument(value, platform) {
@@ -238,7 +253,7 @@ function describeProcessFailure(result) {
238
253
  // (CVE-2024-27980). Only fixed, shell-inert arguments may take that path.
239
254
  export function batchFileSpawnOptions(command, args) {
240
255
  if (!/\.(?:cmd|bat)$/i.test(command)) return {};
241
- const unsafe = args.find((arg) => !/^[\w@./:=-]+$/.test(arg));
256
+ const unsafe = args.find((arg) => !/^[\w@./:=+-]+$/.test(arg));
242
257
  if (unsafe !== undefined) throw new TypeError(`Refusing to pass a shell-sensitive argument to ${command}`);
243
258
  return { shell: true };
244
259
  }
@@ -0,0 +1,54 @@
1
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
2
+ import { posix, win32 } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { FULI_PACKAGE_NAME, FULI_VERSION } from '../package-metadata.js';
5
+
6
+ export function updateEnvironment(env, nodePath, platform = process.platform) {
7
+ const path = platform === 'win32' ? win32 : posix;
8
+ const key = platform === 'win32'
9
+ ? Object.keys(env).find((name) => name.toLowerCase() === 'path') ?? 'PATH'
10
+ : 'PATH';
11
+ return { ...env, [key]: [path.dirname(nodePath), env[key]].filter(Boolean).join(path.delimiter) };
12
+ }
13
+
14
+ export function inspectUpdateInstallation({ env = process.env, platform = process.platform } = {}) {
15
+ const path = platform === 'win32' ? win32 : posix;
16
+ const pathKey = Object.keys(env).find((name) => name.toLowerCase() === 'path') ?? 'PATH';
17
+ const directories = [...new Set((env[pathKey] ?? '').split(path.delimiter).filter(Boolean))];
18
+ const launchers = [];
19
+ for (const directory of directories) {
20
+ for (const name of platform === 'win32' ? ['fl.cmd', 'fuli.cmd', 'fl', 'fuli'] : ['fl', 'fuli']) {
21
+ const launcher = path.join(directory, name);
22
+ if (!existsSync(launcher)) continue;
23
+ let version = null;
24
+ let target = launcher;
25
+ try {
26
+ target = realpathSync(launcher);
27
+ // Read only the manifest for a recognized Fuli entry point; never execute PATH candidates.
28
+ if (path.basename(target) === 'cli.js' && path.basename(path.dirname(target)) === 'src') {
29
+ const manifest = JSON.parse(readFileSync(path.join(path.dirname(target), '..', 'package.json'), 'utf8'));
30
+ if (manifest.name === FULI_PACKAGE_NAME) version = manifest.version;
31
+ }
32
+ } catch { /* Broken or unreadable candidates remain visible with an unknown version. */ }
33
+ launchers.push({ path: launcher, target, version });
34
+ }
35
+ }
36
+ return { cliPath: fileURLToPath(new URL('../cli.js', import.meta.url)), launchers };
37
+ }
38
+
39
+ export function formatUpdateInstallation(installation, { nodePath, npmCommand }) {
40
+ const groups = new Map();
41
+ for (const launcher of installation.launchers) {
42
+ const group = groups.get(launcher.target) ?? { paths: [], version: launcher.version };
43
+ group.paths.push(launcher.path);
44
+ groups.set(launcher.target, group);
45
+ }
46
+ return [
47
+ `Running Fuli: ${FULI_VERSION} (${installation.cliPath})`,
48
+ `Node: ${nodePath}`,
49
+ `npm: ${npmCommand} (current Node directory first in PATH)`,
50
+ ...[...groups].map(([target, { paths, version }]) =>
51
+ `PATH launchers: ${paths.join(', ')} -> ${target} (${version ?? 'version unknown; wrapper not executed'})`),
52
+ 'If a PATH launcher belongs to another installation, it will keep its own version.'
53
+ ].join('\n');
54
+ }
package/src/cli.js CHANGED
@@ -32,8 +32,7 @@ export async function main(argv = process.argv.slice(2), env = process.env) {
32
32
  }
33
33
  if (command === 'setup') {
34
34
  assertSupportedNodeVersion();
35
- await runSetupCommand(commandArgs, { env });
36
- return;
35
+ return runSetupCommand(commandArgs, { env });
37
36
  }
38
37
  if (command === 'connect') {
39
38
  assertSupportedNodeVersion();
@@ -7,12 +7,13 @@ import {
7
7
  import { openFederatedGraphApplication } from '../graphiti/federated-application.js';
8
8
  import { createMcpServer } from './create-mcp-server.js';
9
9
 
10
- export async function runStdio({ runtimeConfigPath, sourceApplication }) {
10
+ export async function runStdio({ runtimeConfigPath, sourceApplication, toolNames = null }) {
11
11
  const app = openFederatedGraphApplication({ runtimeConfigPath });
12
12
  const leaseClient = createRuntimeLeaseClient({ runtimeConfigPath });
13
13
  bindRuntimeLeaseAgentTools(app, leaseClient);
14
14
  const server = createServerOrClose(app, createMcpServer, {
15
15
  sourceApplication,
16
+ toolNames,
16
17
  withRuntimeLease: leaseClient.withGraphLease
17
18
  });
18
19
  const transport = new StdioServerTransport();
@@ -0,0 +1,36 @@
1
+ // Named MCP tool sets. Chat clients such as Claude Desktop load every tool schema
2
+ // into each conversation, so they get only the day-to-day memory, preference and
3
+ // Roundtable tools; management stays in the console and the full profile.
4
+ const CHAT_TOOLS = Object.freeze([
5
+ 'get_collaboration_preferences',
6
+ 'get_user_taste_skill',
7
+ 'search_current_project_knowledge',
8
+ 'search_knowledge_graph',
9
+ 'capture_session_knowledge',
10
+ 'record_knowledge_usage',
11
+ 'record_decision_trace',
12
+ 'checkpoint_task_knowledge',
13
+ 'list_personal_projects',
14
+ 'list_project_agents',
15
+ 'get_project_agent_context',
16
+ 'list_agent_conversations',
17
+ 'read_agent_conversation',
18
+ 'resume_agent_conversation',
19
+ 'find_agents',
20
+ 'message_agent',
21
+ 'read_agent_messages',
22
+ 'reply_agent_message',
23
+ 'read_agent_thread',
24
+ 'get_agent_message_status',
25
+ ]);
26
+
27
+ export const MCP_TOOL_PROFILES = Object.freeze({ full: null, chat: CHAT_TOOLS });
28
+
29
+ // Returns the allow-listed tool names, or null for every tool.
30
+ export function toolNamesForProfile(profile = 'full') {
31
+ if (!Object.hasOwn(MCP_TOOL_PROFILES, profile)) {
32
+ throw new TypeError(`Unknown MCP tool profile: ${profile}. Use ${Object.keys(MCP_TOOL_PROFILES).join(' or ')}`);
33
+ }
34
+ const names = MCP_TOOL_PROFILES[profile];
35
+ return names ? [...names] : null;
36
+ }
@@ -3,6 +3,7 @@ import { existsSync, readFileSync } from 'node:fs';
3
3
  import { replaceFuliTable } from './codex-config.js';
4
4
  import { isCodexBootstrapCurrent } from './codex-bootstrap.js';
5
5
  import { hasCurrentClaudeCodeHooks } from './claude-code-config.js';
6
+ import { CLAUDE_DESKTOP_SERVER_ARGS } from './claude-desktop-config.js';
6
7
  import {
7
8
  hasCodexTomlHookDefinitions,
8
9
  hasCurrentCodexLifecycleHooks,
@@ -27,7 +28,7 @@ export function inspectAgentInstallations(agents, context, {
27
28
  [context.projectSkillPath, agent.projectSkillPath],
28
29
  [context.reviewSkillPath, agent.reviewSkillPath],
29
30
  [context.operationSkillPath, agent.operationSkillPath]
30
- ];
31
+ ].filter(([, targetPath]) => targetPath);
31
32
  const skillsPresent = skillPairs.some(([, targetPath]) => fileExists(targetPath));
32
33
  const skillsCurrent = skillPairs.every(
33
34
  ([sourcePath, targetPath]) => skillCurrent(sourcePath, targetPath)
@@ -89,8 +90,9 @@ function inspectRegistration(agent, context, readers) {
89
90
  context.mcpServerPath,
90
91
  '--runtime-config',
91
92
  context.runtimeConfigPath,
92
- '--source-application',
93
- agent.id === 'claude-code' ? 'claude_code' : agent.id
93
+ ...(agent.id === 'claude-desktop'
94
+ ? CLAUDE_DESKTOP_SERVER_ARGS
95
+ : ['--source-application', agent.id === 'claude-code' ? 'claude_code' : agent.id])
94
96
  ]
95
97
  };
96
98
  try {
@@ -7,6 +7,7 @@ import {
7
7
  connectClaudeCode,
8
8
  disconnectClaudeCode
9
9
  } from './claude-code-config.js';
10
+ import { connectClaudeDesktop, disconnectClaudeDesktop } from './claude-desktop-config.js';
10
11
  import { connectCursor, disconnectCursor } from './cursor-config.js';
11
12
  import { connectCodex, disconnectCodex } from './codex-config.js';
12
13
  import { adaptAgentForReviewSkill } from './review-skill-adapters.js';
@@ -69,32 +70,51 @@ export function discoverAgents({
69
70
  fileExists(cursorHome)
70
71
  )
71
72
  }
72
- ].map((agent) => adaptAgentForReviewSkill(agent, { homeDir, pathApi }));
73
+ ].map((agent) => adaptAgentForReviewSkill(agent, { homeDir, pathApi }))
74
+ .concat(claudeDesktop({ platform, env, homeDir, pathApi, fileExists }) ?? []);
75
+ }
76
+
77
+ // The chat app: MCP only, no hooks or Skills. Its config lives beside its app data.
78
+ function claudeDesktop({ platform, env, homeDir, pathApi, fileExists }) {
79
+ const appData = platform === 'win32' ? nonEmpty(env.APPDATA)
80
+ : platform === 'darwin' ? pathApi.join(homeDir, 'Library', 'Application Support') : null;
81
+ if (!appData) return null;
82
+ const configDir = pathApi.join(appData, 'Claude');
83
+ return {
84
+ id: 'claude-desktop',
85
+ label: 'Claude Desktop',
86
+ configPath: pathApi.join(configDir, 'claude_desktop_config.json'),
87
+ available: Boolean(fileExists(configDir))
88
+ };
73
89
  }
74
90
 
75
91
  export function connectAgent(agent, context, {
76
92
  connectCodexConfig = connectCodex,
77
93
  connectClaudeCodeConfig = connectClaudeCode,
78
- connectCursorConfig = connectCursor
94
+ connectCursorConfig = connectCursor,
95
+ connectClaudeDesktopConfig = connectClaudeDesktop
79
96
  } = {}) {
80
97
  if (agent.id === 'codex') return connectCodexConfig(agent, context);
81
98
  if (agent.id === 'claude-code') {
82
99
  return connectClaudeCodeConfig(agent, context);
83
100
  }
84
101
  if (agent.id === 'cursor') return connectCursorConfig(agent, context);
102
+ if (agent.id === 'claude-desktop') return connectClaudeDesktopConfig(agent, context);
85
103
  throw new TypeError(`Unsupported agent: ${agent.id}`);
86
104
  }
87
105
 
88
106
  export function disconnectAgent(agent, {
89
107
  disconnectCodexConfig = disconnectCodex,
90
108
  disconnectClaudeCodeConfig = disconnectClaudeCode,
91
- disconnectJsonConfig = disconnectCursor
109
+ disconnectJsonConfig = disconnectCursor,
110
+ disconnectClaudeDesktopConfig = disconnectClaudeDesktop
92
111
  } = {}) {
93
112
  if (agent.id === 'codex') return disconnectCodexConfig(agent);
94
113
  if (agent.id === 'claude-code') return disconnectClaudeCodeConfig(agent);
95
114
  if (agent.id === 'cursor') {
96
115
  return disconnectJsonConfig(agent);
97
116
  }
117
+ if (agent.id === 'claude-desktop') return disconnectClaudeDesktopConfig(agent);
98
118
  throw new TypeError(`Unsupported agent: ${agent.id}`);
99
119
  }
100
120
 
@@ -0,0 +1,60 @@
1
+ import { existsSync } from 'node:fs';
2
+
3
+ import { readJsonFile, writeJsonFileAtomic } from '../storage/json-file.js';
4
+
5
+ // Arguments after the runtime config that identify the app and keep its tool list small.
6
+ export const CLAUDE_DESKTOP_SERVER_ARGS = Object.freeze(['--source-application', 'claude', '--tool-profile', 'chat']);
7
+
8
+ // Claude Desktop (the chat app) reads stdio MCP servers from
9
+ // claude_desktop_config.json. It has no lifecycle hooks or Skill folder, and it
10
+ // loads every tool schema into each chat, so it gets the compact chat profile.
11
+ export function connectClaudeDesktop(agent, context, {
12
+ readConfig = readJsonFile,
13
+ writeConfig = writeJsonFileAtomic
14
+ } = {}) {
15
+ const current = readConfig(agent.configPath, {});
16
+ assertObject(current, 'Claude Desktop config');
17
+ const currentServers = current.mcpServers ?? {};
18
+ assertObject(currentServers, 'Claude Desktop mcpServers');
19
+ const next = {
20
+ ...current,
21
+ mcpServers: {
22
+ ...currentServers,
23
+ fuli: {
24
+ command: context.nodePath,
25
+ args: [
26
+ context.mcpServerPath,
27
+ '--runtime-config',
28
+ context.runtimeConfigPath,
29
+ ...CLAUDE_DESKTOP_SERVER_ARGS
30
+ ]
31
+ }
32
+ }
33
+ };
34
+ const changed = JSON.stringify(current) !== JSON.stringify(next);
35
+ if (changed) writeConfig(agent.configPath, next);
36
+ // The app only reads its config at launch.
37
+ return { id: agent.id, label: agent.label, status: 'connected', restartRequired: changed };
38
+ }
39
+
40
+ export function disconnectClaudeDesktop(agent, {
41
+ fileExists = existsSync,
42
+ readConfig = readJsonFile,
43
+ writeConfig = writeJsonFileAtomic
44
+ } = {}) {
45
+ if (!fileExists(agent.configPath)) return { id: agent.id, label: agent.label, status: 'not_connected' };
46
+ const current = readConfig(agent.configPath, {});
47
+ assertObject(current, 'Claude Desktop config');
48
+ const currentServers = current.mcpServers ?? {};
49
+ assertObject(currentServers, 'Claude Desktop mcpServers');
50
+ if (!Object.hasOwn(currentServers, 'fuli')) return { id: agent.id, label: agent.label, status: 'not_connected' };
51
+ const { fuli: _removed, ...remainingServers } = currentServers;
52
+ writeConfig(agent.configPath, { ...current, mcpServers: remainingServers });
53
+ return { id: agent.id, label: agent.label, status: 'disconnected' };
54
+ }
55
+
56
+ function assertObject(value, label) {
57
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
58
+ throw new TypeError(`${label} must be a JSON object`);
59
+ }
60
+ }
@@ -1,5 +1,13 @@
1
+ import { toolNamesForProfile } from '../mcp/tool-profiles.js';
2
+ import { normalizeMcpSourceApplication } from '../mcp/session-id.js';
3
+
1
4
  // Configuration only: no client discovery, file writes, credentials or model calls.
2
- export function clientConnectionConfig({ nodePath = process.execPath, mcpServerPath, runtimeConfigPath }, format = 'standard') {
5
+ export function clientConnectionConfig({ nodePath = process.execPath, mcpServerPath, runtimeConfigPath }, format = 'standard', {
6
+ profile = 'full',
7
+ sourceApplication = 'other'
8
+ } = {}) {
9
+ toolNamesForProfile(profile);
10
+ normalizeMcpSourceApplication(sourceApplication);
3
11
  if (!['standard', 'vscode', 'server'].includes(format)) {
4
12
  throw new TypeError('Connection format must be standard, vscode or server');
5
13
  }
@@ -9,7 +17,8 @@ export function clientConnectionConfig({ nodePath = process.execPath, mcpServerP
9
17
  const server = {
10
18
  type: 'stdio',
11
19
  command: nodePath,
12
- args: [mcpServerPath, '--runtime-config', runtimeConfigPath, '--source-application', 'other']
20
+ args: [mcpServerPath, '--runtime-config', runtimeConfigPath, '--source-application', sourceApplication,
21
+ ...(profile === 'full' ? [] : ['--tool-profile', profile])]
13
22
  };
14
23
  if (format === 'server') return server;
15
24
  return { [format === 'vscode' ? 'servers' : 'mcpServers']: { fuli: server } };
@@ -12,6 +12,7 @@ const BOOLEAN_OPTIONS = Object.freeze({
12
12
  '--yes': 'yes',
13
13
  '--codex-only': 'codexOnly',
14
14
  '--skip-agents': 'skipAgents',
15
+ '--connected-only': 'connectedOnly',
15
16
  '--no-start': 'noStart'
16
17
  });
17
18
 
@@ -2,7 +2,9 @@ import { homedir } from 'node:os';
2
2
  import { dirname, join, resolve } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
 
5
- const DEFAULT_PACKAGE_ROOT = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
5
+ import { stablePackageRoot } from './stable-package-root.js';
6
+
7
+ const DEFAULT_PACKAGE_ROOT = stablePackageRoot(dirname(dirname(dirname(fileURLToPath(import.meta.url)))));
6
8
 
7
9
  export function resolveSetupPaths({
8
10
  dataDir = null,
@@ -121,7 +121,7 @@ export async function applyLocalSetup(plan, options, dependencies = {}) {
121
121
  [{ ...agent, skillPath: agent.projectSkillPath }, plan.paths.projectSkillPath],
122
122
  [{ ...agent, skillPath: agent.reviewSkillPath }, plan.paths.reviewSkillPath],
123
123
  [{ ...agent, skillPath: agent.operationSkillPath }, plan.paths.operationSkillPath]
124
- ]) {
124
+ ].filter(([target]) => target.skillPath)) {
125
125
  skills.push(await installSkill(target, {
126
126
  sourcePath,
127
127
  backupDir: plan.paths.backupDir
@@ -0,0 +1,29 @@
1
+ import { existsSync, realpathSync } from 'node:fs';
2
+ import { basename, dirname, join } from 'node:path';
3
+
4
+ // Version managers such as nvm-windows install each Node version in its own
5
+ // folder and expose the active one through a symlink (C:\nvm4w\nodejs). The
6
+ // module URL resolves to the versioned folder, which breaks client configs on
7
+ // the next `nvm use`. Prefer the same package seen through the active Node's
8
+ // directory when it is the identical install.
9
+ export function stablePackageRoot(root, {
10
+ execPath = process.execPath,
11
+ exists = existsSync,
12
+ realpath = realpathSync
13
+ } = {}) {
14
+ const nodeDir = dirname(execPath);
15
+ const name = basename(root);
16
+ const candidates = [
17
+ join(nodeDir, 'node_modules', name),
18
+ join(dirname(nodeDir), 'lib', 'node_modules', name)
19
+ ];
20
+ let real;
21
+ try { real = realpath(root); } catch { return root; }
22
+ for (const candidate of candidates) {
23
+ if (candidate === root || !exists(candidate)) continue;
24
+ try {
25
+ if (realpath(candidate) === real) return candidate;
26
+ } catch { /* unreadable candidate */ }
27
+ }
28
+ return root;
29
+ }