@yancyyu/openhermit 1.6.4 → 1.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +52 -140
  2. package/bin/hermit.mjs +39 -258
  3. package/dist-renderer/assets/{ProjectEditorOverlay-BcjkdR8y.js → ProjectEditorOverlay-14yC9eQy.js} +1 -1
  4. package/dist-renderer/assets/{TeamGraphOverlay-B9PP0b_t.js → TeamGraphOverlay-RAoJDOnS.js} +1 -1
  5. package/dist-renderer/assets/{_basePickBy-CPquAmj5.js → _basePickBy-BhDOA0cG.js} +1 -1
  6. package/dist-renderer/assets/{_baseUniq-A66EsJn2.js → _baseUniq-DjjY0tMN.js} +1 -1
  7. package/dist-renderer/assets/{arc-YLxbV3Qw.js → arc-CzoaaE90.js} +1 -1
  8. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-wwpiLSwy.js → architectureDiagram-VXUJARFQ-D7ZTVCML.js} +1 -1
  9. package/dist-renderer/assets/{blockDiagram-VD42YOAC-3CHE3NYR.js → blockDiagram-VD42YOAC-DDVOvV1H.js} +1 -1
  10. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-K8hDNmEC.js → c4Diagram-YG6GDRKO-CMswQy_R.js} +1 -1
  11. package/dist-renderer/assets/channel-DjoT-21b.js +1 -0
  12. package/dist-renderer/assets/{chunk-4BX2VUAB-5OabZrhH.js → chunk-4BX2VUAB-aYfdMo75.js} +1 -1
  13. package/dist-renderer/assets/{chunk-55IACEB6-v2kdM_aT.js → chunk-55IACEB6-DUhZJ0mV.js} +1 -1
  14. package/dist-renderer/assets/{chunk-B4BG7PRW-C0Ju56SH.js → chunk-B4BG7PRW-BrGjG-E6.js} +1 -1
  15. package/dist-renderer/assets/{chunk-DI55MBZ5-DPTWTKRm.js → chunk-DI55MBZ5-CfPUMKlq.js} +1 -1
  16. package/dist-renderer/assets/{chunk-FMBD7UC4-DSkYppkv.js → chunk-FMBD7UC4-BMr0Vrdu.js} +1 -1
  17. package/dist-renderer/assets/{chunk-QN33PNHL-C_4cCLCl.js → chunk-QN33PNHL-C9gTfFZV.js} +1 -1
  18. package/dist-renderer/assets/{chunk-QZHKN3VN-ojL7PmOD.js → chunk-QZHKN3VN-TTPdfwHP.js} +1 -1
  19. package/dist-renderer/assets/{chunk-TZMSLE5B-D1g7Vl_v.js → chunk-TZMSLE5B-DPh3DBqf.js} +1 -1
  20. package/dist-renderer/assets/classDiagram-2ON5EDUG-C5mL3TLG.js +1 -0
  21. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-C5mL3TLG.js +1 -0
  22. package/dist-renderer/assets/clone-cS8bapaK.js +1 -0
  23. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-TJnGh924.js → cose-bilkent-S5V4N54A-BFbgRWOS.js} +1 -1
  24. package/dist-renderer/assets/{dagre-6UL2VRFP-cPgfHhoX.js → dagre-6UL2VRFP-CfXdU7Il.js} +1 -1
  25. package/dist-renderer/assets/{diagram-PSM6KHXK-BS5Y-RR6.js → diagram-PSM6KHXK-MdOyrxZl.js} +1 -1
  26. package/dist-renderer/assets/{diagram-QEK2KX5R-D9AF7AGJ.js → diagram-QEK2KX5R-DpmnBR-A.js} +1 -1
  27. package/dist-renderer/assets/{diagram-S2PKOQOG-DTFUadMS.js → diagram-S2PKOQOG-JXgp2H5I.js} +1 -1
  28. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-DB_StEwC.js → erDiagram-Q2GNP2WA-CRfYO8W3.js} +1 -1
  29. package/dist-renderer/assets/{flowDiagram-NV44I4VS-DGn40aPj.js → flowDiagram-NV44I4VS-BJvmgply.js} +1 -1
  30. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-9NiFCSBT.js → ganttDiagram-JELNMOA3-BLXnpZat.js} +1 -1
  31. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-BdveeU3c.js → gitGraphDiagram-V2S2FVAM-BKtbxazQ.js} +1 -1
  32. package/dist-renderer/assets/{graph-aQYbgTDH.js → graph-D6n4zNVe.js} +1 -1
  33. package/dist-renderer/assets/{index-DmgKTZAa.js → index-3JdA9Dab.js} +529 -524
  34. package/dist-renderer/assets/{index-CaG9mf8s.css → index-C4x095x4.css} +1 -1
  35. package/dist-renderer/assets/{index-CWqPn0NY.js → index-CVdwMXdQ.js} +1 -1
  36. package/dist-renderer/assets/{index-DyEKO6GV.js → index-CkO1A9ft.js} +1 -1
  37. package/dist-renderer/assets/{index-oyepEosi.js → index-ar0tAtBS.js} +1 -1
  38. package/dist-renderer/assets/{index-CrCHolXN.js → index-c2GABSvo.js} +1 -1
  39. package/dist-renderer/assets/{index-DiAK42nd.js → index-trDFOqz-.js} +1 -1
  40. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-Dmc_xn8U.js → infoDiagram-HS3SLOUP-Bqq_toop.js} +1 -1
  41. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-D9LJr-B5.js → journeyDiagram-XKPGCS4Q-BRQs07r0.js} +1 -1
  42. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CjOWoNys.js → kanban-definition-3W4ZIXB7-DHQnAijJ.js} +1 -1
  43. package/dist-renderer/assets/{layout-D6GzYK4K.js → layout-BljiazG5.js} +1 -1
  44. package/dist-renderer/assets/{linear-Dt3GyUQf.js → linear-fx8cDfux.js} +1 -1
  45. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-XwY2hZr8.js → mindmap-definition-VGOIOE7T-DCfQbCFK.js} +1 -1
  46. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BU4nfYd7.js → pieDiagram-ADFJNKIX-DyAFYy6H.js} +1 -1
  47. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-BYk6f63x.js → quadrantDiagram-AYHSOK5B-CCvqn9gd.js} +1 -1
  48. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-kbadr_bU.js → requirementDiagram-UZGBJVZJ-JYde-Xl2.js} +1 -1
  49. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-ZstP2Vth.js → sankeyDiagram-TZEHDZUN-C2Im6-aG.js} +1 -1
  50. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-obK_-ssz.js → sequenceDiagram-WL72ISMW-X6JGIoEB.js} +1 -1
  51. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BgZDg0VT.js → stateDiagram-FKZM4ZOC-BJTDs8MY.js} +1 -1
  52. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-CMa5sz7x.js → stateDiagram-v2-4FDKWEC3-DUrYslPS.js} +1 -1
  53. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-BOmCNnab.js → timeline-definition-IT6M3QCI-C7ECznev.js} +1 -1
  54. package/dist-renderer/assets/{treemap-GDKQZRPO-BU0ha0Ww.js → treemap-GDKQZRPO-BRg3Zpk4.js} +1 -1
  55. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-BzAHNASi.js → xychartDiagram-PRI3JC2R-CoZGyc2f.js} +1 -1
  56. package/dist-renderer/index.html +2 -2
  57. package/package.json +23 -17
  58. package/src/main/server.ts +179 -23
  59. package/src/main/services/teams-mvp/TaskDispatchService.ts +440 -0
  60. package/src/main/services/teams-mvp/TeamProvisioningService.ts +36 -33
  61. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +2 -0
  62. package/src/renderer/components/settings/SettingsTabs.tsx +8 -2
  63. package/src/renderer/components/settings/SettingsView.tsx +4 -0
  64. package/src/renderer/components/settings/sections/GeneralSection.tsx +168 -206
  65. package/src/renderer/components/settings/sections/TaskBusSection.tsx +176 -0
  66. package/src/renderer/components/sidebar/SidebarSessions.tsx +31 -4
  67. package/src/renderer/components/team/kanban/KanbanTaskCard.tsx +37 -0
  68. package/src/renderer/components/team/messages/MessageComposer.tsx +36 -228
  69. package/src/renderer/components/team/messages/MessagesPanel.tsx +0 -3
  70. package/src/renderer/store/slices/teamSlice.ts +30 -1
  71. package/src/shared/types/team.ts +73 -0
  72. package/dist-renderer/assets/channel-BSWYOYIc.js +0 -1
  73. package/dist-renderer/assets/classDiagram-2ON5EDUG-mw4yABob.js +0 -1
  74. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-mw4yABob.js +0 -1
  75. package/dist-renderer/assets/clone-KtZfFt-o.js +0 -1
package/README.md CHANGED
@@ -5,152 +5,84 @@
5
5
  <h1 align="center">openHermit</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>给一人公司和小团队用的本地 AI Agent 控制台</strong><br/>
9
- 把 Claude Code、Codex、Gemini、Qoder 等 Agent 放进同一个看板、消息和审查流程里。
8
+ <strong>超级个体的 AI 基础设施:用代码重构公司形态</strong><br/>
9
+ 告别“玩具级”的提示词角色扮演。用状态机接管工作流,让一个人成为一支军队。
10
10
  </p>
11
11
 
12
12
  <p align="center">
13
- <a href="https://github.com/yancyuu/Hermit/releases/latest"><img src="https://img.shields.io/github/v/release/yancyuu/Hermit?style=flat-square&label=version&color=blue" alt="最新版本" /></a>
14
- <a href="LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0-blue?style=flat-square" alt="许可证" /></a>
13
+ <a href="https://github.com/yancyuu/Hermit/releases/latest"><img src="https://img.shields.io/github/v/release/yancyuu/Hermit?style=flat-square&label=version&color=black" alt="最新版本" /></a>
14
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0-black?style=flat-square" alt="许可证" /></a>
15
15
  </p>
16
16
 
17
17
  ---
18
18
 
19
- ## openHermit 是什么
19
+ ## 💡 设计哲学:为什么我们需要 openHermit
20
20
 
21
- openHermit 是一个本地运行的 AI Agent 工作台。它适合一人公司、小团队,以及每天同时使用多个 AI Coding Agent 的开发者。
21
+ 目前的 AI Agent 赛道充满了一个巨大的误区:**大家都在试图用人类的“HR 组织架构”来管理 AI。**
22
+ 让大模型扮演“资深前端”或“产品经理”,本质上是在模仿人类的**职责驱动**(因岗设人)。这注定会走向死锁——因为人类有精力上限和认知边界,才需要划分部门和扯皮;而 AI 没有。
22
23
 
23
- 你可以把它理解成一个组织,不同的团队有不同的工作空间(文件即记忆),不同团队可以相互通信。
24
+ **openHermit 是一场对传统协作模式的降维打击。** 它的核心哲学只有两条:
24
25
 
25
- 它不提供模型,也不托管你的代码。openHermit 基于 cc-connect 启动和管理 Claude Code、Codex、Gemini、Qoder 等运行时,项目代码、任务数据和配置默认都留在本机。
26
+ ### 1. 对创业者:组织形态的“资产化” (OPC 范式)
27
+ **OPC(One Person Company)** 不再是一个浪漫的口号,而是最具杠杆率的商业形态。
28
+ 你不需要招聘、不需要对齐价值观、不需要处理情绪内耗。你是唯一的决策者,AI 是绝对服从的执行网格。openHermit 为你提供 **TPC(Team · Process · Channel)** 协作结构,将产品、开发、测试、运营转化为可并发现程。你的每一次业务跑通,都是在积累固化的“数字资产”,而不是随员工离职而流失的经验。
26
29
 
27
- ## 为什么需要它
28
-
29
- AI Coding Agent 已经能完成越来越多实际开发工作,但当你同时开多个终端、多个项目、多个 Agent 时,问题很快会出现:
30
-
31
- - 哪个 Agent 正在做什么,靠脑子记不住。
32
- - 同一个需求被拆成多个子任务后,缺少统一看板。
33
- - Agent 的执行过程散在终端日志里,不方便复盘。
34
- - 代码改动可以生成,但缺少明确的审查入口。
35
- - 团队模板、Skills 和项目经验沉淀不下来。
36
-
37
- openHermit 做的事很简单:把多个 Agent 的工作过程放进一个本地控制台里,让一个人也能按“团队”的方式分配任务、查看进度、接收结果和审查改动。
30
+ ### 2. 对开发者与工程师:去中心化的“状态机”驱动 (State-Driven DAG)
31
+ **用管理机器的方式管理 Agent,而不是用管理人的方式。**
32
+ Agent 的协作本质不该是“角色扮演”,而是**分布式状态机**。任务流是一张 DAG,每个节点代表一种确定的状态(Pending Running → Review → Done)。
33
+ * **拒绝死锁**:遇到网络波动或反爬环境,状态机自动 `Fail-Over` 切换运行时,流水线永远不会因为“超出角色职责”而卡死。
34
+ * **Zero-Trust Local-First**:真正的生产力工具不能是黑盒。openHermit 坚持本地优先的零信任架构。配置、项目代码、长短期记忆同步都在你的本地机器完成,彻底掌控数据主权。
35
+ * **即用即走,上下文随状态流转**:Agent 不拥有固定岗位,只有当任务状态流转到 `Ready` 时,控制面才会将当前状态、上下文(Context)和能力(Skills)动态注入给对应的 Agent 运行时。
38
36
 
39
37
  ---
40
38
 
41
- ## 你可以用它做什么
42
-
43
- - **创建 AI 团队**:为前端、后端、测试、调研等工作创建不同团队或角色。
44
- - **用看板管任务**:任务有状态、负责人、执行记录和结果,不再散在聊天里。
45
- - **给 Agent 发消息**:向负责人或具体成员补充指令,保留上下文。
46
- - **审查代码改动**:Agent 产生的变更进入审查流程,而不是直接混进代码库。
47
- - **查看运行状态**:会话、日志、错误和启动状态集中展示,便于排障。
48
- - **接入外部渠道**:通过 cc-connect 接入飞书、微信、Telegram 等消息来源。
49
- - **复用团队配置**:把团队模板、Skills 和工作方式沉淀下来,下次直接复用。
50
-
51
- ---
39
+ ## ⚙️ 核心架构:TPC 引擎
52
40
 
53
- ## 支持的 Agent 运行时
41
+ 想要让 OPC 跑起来,你需要的不是一堆散落的终端窗口,而是 TPC 架构:
54
42
 
55
- openHermit 通过 cc-connect 管理 Agent 运行时。当前常用类型包括:
56
-
57
- | Harness | 标识 | MCP 自动注入 | 适合场景 |
58
- |---|---|---|---|
59
- | **Claude Code** | `claudecode` | ✅ 自动 | 默认推荐的编码运行时 |
60
- | **Qoder** | `qoder` | ✅ 自动 | Claude Code 兼容运行时 |
61
- | **Codex** | `codex` | 手动配置 | OpenAI/Codex 生态 |
62
- | **Gemini** | `gemini` | 手动配置 | Google/Gemini 生态 |
63
- | **OpenCode** | `opencode` | 手动配置 | 多 provider 开源运行时 |
64
- | **Cursor** | `cursor` | 手动配置 | Cursor 相关运行时 |
65
- | **Kimi** | `kimi` | 手动配置 | 长文本和文档任务 |
66
-
67
- 实际可用能力取决于本机安装的 CLI、账号状态和 cc-connect 的支持情况。
43
+ * **[ T ] Team(隔离与并发)**:给不同的工作建立独立团队。前端用 Claude Code 撸代码,后端用 Codex 构架,调研交由 Kimi。每个团队有独立的隔离运行时环境,但在全局协作看板上,任务可以跨团队无缝调度。
44
+ * **[ P ] Process(状态流转)**:彻底放弃“岗位 KPI”。一切以任务的原子化状态为核心,MCP Server 动态向 Agent 注入当前所需工具。
45
+ * **[ C ] Channel(全渠道触达)**:支持飞书、微信、Telegram、Discord、Slack 10+ 渠道接入。消息即指令,外部输入自动路由至对应 Agent 团队,支持独立上下文串或共享群会话,将你的 IM 变成公司级控制台。
68
46
 
69
47
  ---
70
48
 
71
- ## 支持的消息渠道
72
-
73
- openHermit 复用 cc-connect 的渠道能力。同一个 cc-connect project 可以绑定一个或多个渠道:
74
-
75
- | 渠道 | 场景 |
76
- |---|---|
77
- | **飞书** | 企业级,支持消息卡片 |
78
- | **微信** | 个人/小团队最顺手 |
79
- | **Telegram** | 海外 / 技术向首选 |
80
- | **Bridge** | openHermit 内部和自定义集成 |
81
-
82
- ---
83
-
84
- ## 主要流程
85
-
86
- ### 1. 任务看板
87
-
88
- ```
89
- 用户:在看板里创建任务,例如 "重构支付模块"
90
-
91
- openHermit:把任务写入团队看板,并通知对应 Agent
92
-
93
- Agent:用 MCP 认领任务,开始执行
94
-
95
- 完成后:结果写回看板,状态更新
96
- ```
97
-
98
- ### 2. 多团队
99
-
100
- ```
101
- 前端团队 (Claude Code) ——→ UI 任务
102
- 后端团队 (Codex) ——→ API 和业务逻辑
103
- 测试团队 (Gemini) ——→ 测试和验证
104
- 调研团队 (Kimi) ——→ 文档和资料整理
105
- ```
49
+ ## 🛠️ 支持的 Agent 运行时与渠道
106
50
 
107
- 每个团队有独立配置、任务和消息记录。任务可以分配给不同团队。
51
+ openHermit 不提供闭源模型,也不劫持你的代码。它是一个高度可扩展的本地环境壳层。
108
52
 
109
- ### 3. 定时任务
53
+ ### 极客级的多端运行时支持
54
+ 创建团队时,底层 MCP Server 会自动完成零配置注入。能否启动,取决于你的算力边界。
110
55
 
111
- 可以通过 cc-connect 配置定时任务,例如日报、代码健康检查、数据拉取等。
56
+ | 标识 | 运行时说明 | 标识 | 运行时说明 |
57
+ |:---|:---|:---|:---|
58
+ | `claudecode` | Anthropic 官方 CLI | `devin` | Cognition Devin |
59
+ | `codex` | OpenAI Codex CLI | `opencode` | OpenCode CLI |
60
+ | `cursor` | Cursor IDE Agent | `qoder` | Qoder CLI |
61
+ | `gemini` | Google Gemini CLI | `pi` | Inflection Pi |
62
+ | `iflow` | iFlow CLI | `acp` | Agent Communication Protocol |
63
+ | `kimi` | Moonshot Kimi | `tmux` | 经典 Tmux Session 桥接 |
112
64
 
113
- ### 4. MCP 零配置接入
114
-
115
- Claude Code / Qoder 类运行时创建团队时会自动注入 MCP 配置,让 Agent 可以使用任务工具:
116
-
117
- ```
118
- list_tasks — 看自己有哪些任务
119
- claim_task — 认领任务,开始干活
120
- complete_task — 完成任务,写入结果
121
- create_task — 创建新任务分配给其他团队
122
- ```
123
-
124
- ---
125
-
126
- ## 架构
127
-
128
- ```
129
- 你的指令 / 飞书消息 / 微信消息 / Telegram
130
-
131
- cc-connect
132
- (渠道接入 + Agent 进程管理)
133
-
134
- openHermit
135
- (团队管理 + 任务路由 + 看板 UI)
136
-
137
- MCP Server(hermit-tasks)
138
-
139
- Claude Code / Codex / Gemini / Qoder / ...
140
- ```
141
-
142
- openHermit 和 cc-connect 都运行在本机。项目代码、任务数据和配置默认存放在本地。
65
+ ### 模块化的信使网络 (Channels)
66
+ 让外部世界无缝接入你的自动化流水线:
67
+ * **研发协同**:飞书(支持高级消息卡片)、钉钉、企业微信、Slack
68
+ * **极客与海外**:Telegram、Discord、LINE
69
+ * **私域与社群**:微信、QQ
70
+ * **自定义集成**:原生支持 openHermit 内部 Bridge 协议
143
71
 
144
72
  ---
145
73
 
146
- ## 快速开始
74
+ ## 🚀 极速部署
147
75
 
148
- ### npm 安装
76
+ ### 1. 安装启动
149
77
 
150
- 安装 CLI:
78
+ 方式一:npx(免安装直接运行)
79
+ ```bash
80
+ npx @yancyyu/openhermit@latest
81
+ ```
151
82
 
83
+ 方式二:全局安装
152
84
  ```bash
153
- npm install -g @yancyyu/openhermit
85
+ npm install -g @yancyyu/openhermit@latest --prefer-online
154
86
  openhermit
155
87
  ```
156
88
 
@@ -168,13 +100,11 @@ openhermit --daemon # 后台运行
168
100
  openhermit status # 查看后台运行状态
169
101
  openhermit stop # 停止后台服务
170
102
  openhermit --port 8080 # 指定 Web 控制台端口
171
- openhermit --no-cc-connect # 不自动启动 cc-connect
172
103
  openhermit --version # 查看版本
173
104
  openhermit update # 更新 openHermit
174
105
  ```
175
106
 
176
- 首次启动会自动创建 `~/.hermit/cc-connect/config.toml`,启用 cc-connect Management API
177
- (9820)和 Bridge(9810),并生成本地 token。
107
+ 首次启动会自动创建本地运行时配置,并生成本地 token。通常只需要打开 `http://127.0.0.1:5680` 使用 openHermit。
178
108
 
179
109
  ### 本地开发
180
110
 
@@ -187,41 +117,23 @@ pnpm dev
187
117
 
188
118
  浏览器打开 `http://localhost:5174`
189
119
 
190
- 开发模式默认连接本机 cc-connect;生产 CLI 会优先使用 openHermit 管理的
191
- `~/.hermit/cc-connect/config.toml`。
120
+ 开发模式默认连接本机运行时服务;生产 CLI 会优先使用 openHermit 管理的本地配置。
192
121
 
193
122
  ### 创建第一个 AI 团队
194
123
 
195
124
  1. 点击「新建团队」
196
125
  2. 填写团队名、选 harness(如 `claudecode`)
197
- 3. 选择对应的 cc-connect project
126
+ 3. 选择对应的本地项目和运行时
198
127
  4. 保存 → 看板就绪,任务等你分配
199
128
 
200
129
  ---
201
130
 
202
- ## 文件结构
203
-
204
- ```
205
- ~/.hermit/
206
- └── teams/
207
- ├── frontend/
208
- │ ├── team.json # 团队配置(harness、bindProject、color)
209
- │ └── tasks/board.json # 任务看板
210
- └── backend/
211
- ├── team.json
212
- └── tasks/board.json
213
- ```
214
-
215
- 所有数据存本地,可以用 Git 备份。
216
-
217
- ---
218
-
219
131
  ## 技术栈
220
132
 
221
133
  - **前端**:React + TypeScript + Tailwind CSS + Zustand
222
134
  - **后端**:Fastify(Node.js)
223
135
  - **存储**:本地文件(`~/.hermit/`)
224
- - **通信**:cc-connect Bridge WebSocket + Management HTTP API
136
+ - **通信**:本地 Bridge WebSocket + Management HTTP API
225
137
  - **协议**:MCP over HTTP(SSE + JSON-RPC)
226
138
 
227
139
  ---
package/bin/hermit.mjs CHANGED
@@ -16,11 +16,11 @@
16
16
 
17
17
  import { spawn, execSync } from 'node:child_process';
18
18
  import crypto from 'node:crypto';
19
- import { mkdirSync, writeFileSync, readFileSync, existsSync, openSync, closeSync, unlinkSync } from 'node:fs';
19
+ import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
20
20
  import { createRequire } from 'node:module';
21
21
  import os from 'node:os';
22
22
  import path from 'node:path';
23
- import { fileURLToPath, pathToFileURL } from 'node:url';
23
+ import { fileURLToPath } from 'node:url';
24
24
 
25
25
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
26
26
  const repoRoot = path.resolve(__dirname, '..');
@@ -56,23 +56,16 @@ Usage:
56
56
  Options:
57
57
  --port <number> HTTP server port (default: 5680)
58
58
  --no-cc-connect Do not auto-start bundled cc-connect
59
- --daemon Run in the background
60
59
  --version Show current version
61
60
  --help Show this help message
62
61
  update Check and install updates
63
- status Show background service status
64
- stop Stop the background service
65
62
 
66
63
  Examples:
67
- openhermit # Start on port 5680
68
- openhermit --daemon # Start in background
69
- openhermit status # Show background status
70
- openhermit stop # Stop background service
71
- openhermit --port 8080 # Start on port 8080
72
- openhermit --no-cc-connect # Start only openHermit
73
- openhermit --version # Show version
74
- openhermit update # Check for updates
75
- npx @yancyyu/openhermit # Run without installing
64
+ npx @yancyyu/openhermit # Run without installing
65
+ npx @yancyyu/openhermit --port 8080
66
+ openhermit # After global install
67
+ openhermit --version
68
+ openhermit update
76
69
  `);
77
70
  process.exit(0);
78
71
  }
@@ -87,180 +80,12 @@ const portIndex = args.indexOf('--port');
87
80
  const port = portIndex !== -1 && args[portIndex + 1] ? args[portIndex + 1] : '5680';
88
81
  const skipCcConnect = args.includes('--no-cc-connect') || process.env.HERMIT_NO_CC_CONNECT === '1';
89
82
  const hermitHome = process.env.HERMIT_HOME || path.join(os.homedir(), '.hermit');
90
- const daemonRequested = args.includes('--daemon');
91
- const daemonChild = process.env.HERMIT_DAEMON_CHILD === '1';
92
- const daemonPidPath = path.join(hermitHome, 'openhermit.pid');
93
- const daemonLogPath = path.join(hermitHome, 'logs', 'openhermit.log');
94
83
  const ccConnectConfigPath =
95
84
  process.env.HERMIT_CC_CONNECT_CONFIG ||
96
85
  process.env.CC_CONNECT_CONFIG ||
97
86
  path.join(hermitHome, 'cc-connect', 'config.toml');
98
87
  const bootstrapProjectName = '__openhermit_bootstrap__';
99
88
 
100
- function readDaemonPid() {
101
- try {
102
- const raw = readFileSync(daemonPidPath, 'utf-8').trim();
103
- const pid = Number.parseInt(raw, 10);
104
- return Number.isFinite(pid) && pid > 0 ? pid : null;
105
- } catch {
106
- return null;
107
- }
108
- }
109
-
110
- function isPidRunning(pid) {
111
- try {
112
- process.kill(pid, 0);
113
- return true;
114
- } catch {
115
- return false;
116
- }
117
- }
118
-
119
- function removeDaemonPidFile() {
120
- try {
121
- unlinkSync(daemonPidPath);
122
- } catch {
123
- // Already gone.
124
- }
125
- }
126
-
127
- function signalDaemon(pid, signal) {
128
- try {
129
- process.kill(-pid, signal);
130
- return true;
131
- } catch {
132
- // Fall back to direct process signal.
133
- }
134
- try {
135
- process.kill(pid, signal);
136
- return true;
137
- } catch {
138
- return false;
139
- }
140
- }
141
-
142
- function collectFallbackPids() {
143
- const pids = new Set();
144
- const commands = [
145
- `lsof -tiTCP:${port} -sTCP:LISTEN 2>/dev/null || true`,
146
- 'lsof -tiTCP:9810 -sTCP:LISTEN 2>/dev/null || true',
147
- 'lsof -tiTCP:9820 -sTCP:LISTEN 2>/dev/null || true',
148
- "pgrep -f '@yancyyu/openhermit|openhermit/bin/hermit\\.mjs|src/main/server\\.ts|cc-connect' 2>/dev/null || true",
149
- ];
150
-
151
- for (const command of commands) {
152
- try {
153
- const out = execSync(command, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
154
- for (const line of out.split(/\s+/)) {
155
- const pid = Number.parseInt(line, 10);
156
- if (Number.isFinite(pid) && pid > 0 && pid !== process.pid) {
157
- pids.add(pid);
158
- }
159
- }
160
- } catch {
161
- // Ignore missing lsof/pgrep or races with exiting processes.
162
- }
163
- }
164
-
165
- return [...pids];
166
- }
167
-
168
- async function stopFallbackProcesses() {
169
- const pids = collectFallbackPids();
170
- if (pids.length === 0) return false;
171
-
172
- for (const pid of pids) {
173
- signalDaemon(pid, 'SIGTERM');
174
- }
175
- await new Promise((resolve) => setTimeout(resolve, 2_000));
176
- for (const pid of pids) {
177
- if (isPidRunning(pid)) {
178
- signalDaemon(pid, 'SIGKILL');
179
- }
180
- }
181
- return true;
182
- }
183
-
184
- function printDaemonStatus() {
185
- const pid = readDaemonPid();
186
- if (pid && isPidRunning(pid)) {
187
- console.log(`[openHermit] Running in background (pid ${pid})`);
188
- console.log(`[openHermit] Log: ${daemonLogPath}`);
189
- process.exit(0);
190
- }
191
- if (pid) removeDaemonPidFile();
192
- const fallbackPids = collectFallbackPids();
193
- if (fallbackPids.length > 0) {
194
- console.log(`[openHermit] Running without daemon pidfile (pids ${fallbackPids.join(', ')})`);
195
- process.exit(0);
196
- }
197
- console.log('[openHermit] Not running');
198
- process.exit(1);
199
- }
200
-
201
- async function stopDaemon() {
202
- const pid = readDaemonPid();
203
- if (!pid || !isPidRunning(pid)) {
204
- if (pid) removeDaemonPidFile();
205
- const stoppedFallback = await stopFallbackProcesses();
206
- console.log(stoppedFallback ? '[openHermit] Stopped orphaned service processes' : '[openHermit] Not running');
207
- process.exit(0);
208
- }
209
- console.log(`[openHermit] Stopping background service (pid ${pid})...`);
210
- signalDaemon(pid, 'SIGTERM');
211
- await new Promise((resolve) => setTimeout(resolve, 2_000));
212
- if (isPidRunning(pid)) {
213
- signalDaemon(pid, 'SIGKILL');
214
- }
215
- removeDaemonPidFile();
216
- console.log('[openHermit] Stopped');
217
- process.exit(0);
218
- }
219
-
220
- function startDaemon() {
221
- const existingPid = readDaemonPid();
222
- if (existingPid && isPidRunning(existingPid)) {
223
- console.log(`[openHermit] Already running in background (pid ${existingPid})`);
224
- console.log(`[openHermit] Log: ${daemonLogPath}`);
225
- process.exit(0);
226
- }
227
-
228
- mkdirSync(path.dirname(daemonPidPath), { recursive: true });
229
- mkdirSync(path.dirname(daemonLogPath), { recursive: true });
230
- const out = openSync(daemonLogPath, 'a');
231
- const err = openSync(daemonLogPath, 'a');
232
- const childArgs = process.argv.slice(2).filter((arg) => arg !== '--daemon');
233
- const child = spawn(process.execPath, [fileURLToPath(import.meta.url), ...childArgs], {
234
- cwd: repoRoot,
235
- detached: true,
236
- env: {
237
- ...process.env,
238
- HERMIT_DAEMON_CHILD: '1',
239
- },
240
- stdio: ['ignore', out, err],
241
- });
242
- child.unref();
243
- closeSync(out);
244
- closeSync(err);
245
- writeFileSync(daemonPidPath, String(child.pid), 'utf-8');
246
- console.log(`[openHermit] Started in background (pid ${child.pid})`);
247
- console.log(`[openHermit] URL: http://127.0.0.1:${port}`);
248
- console.log(`[openHermit] Log: ${daemonLogPath}`);
249
- process.exit(0);
250
- }
251
-
252
- if (args.includes('status')) {
253
- printDaemonStatus();
254
- }
255
-
256
- if (args.includes('stop')) {
257
- await stopDaemon();
258
- }
259
-
260
- if (daemonRequested && !daemonChild) {
261
- startDaemon();
262
- }
263
-
264
89
  // ---------------------------------------------------------------------------
265
90
  // Update command
266
91
  // ---------------------------------------------------------------------------
@@ -372,8 +197,8 @@ function hasProjectEntries(raw) {
372
197
 
373
198
  function buildBootstrapProjectToml() {
374
199
  return `
375
- # Internal bootstrap project used only so cc-connect can start with an otherwise empty config.
376
- # It is safe to keep this project; users can replace or delete it after creating real teams.
200
+ # Bootstrap project cc-connect requires at least one project to start.
201
+ # This entry is automatically managed by openHermit and will be ignored at runtime.
377
202
  [[projects]]
378
203
  name = "${bootstrapProjectName}"
379
204
  disabled_commands = ["*"]
@@ -386,13 +211,7 @@ work_dir = "${escapeTomlPath(hermitHome)}"
386
211
  mode = "default"
387
212
 
388
213
  [[projects.platforms]]
389
- type = "line"
390
-
391
- [projects.platforms.options]
392
- channel_secret = "openhermit-bootstrap"
393
- channel_token = "openhermit-bootstrap"
394
- port = "0"
395
- callback_path = "/openhermit-bootstrap"
214
+ type = "bridge"
396
215
  `;
397
216
  }
398
217
 
@@ -462,42 +281,8 @@ function resolveCcConnectRunner() {
462
281
  return path.join(path.dirname(pkgPath), 'run.js');
463
282
  }
464
283
 
465
- function resolveTsxLoader() {
466
- return require.resolve('tsx');
467
- }
468
-
469
- function resolveAliasLoaderRegister() {
470
- const aliasLoaderUrl = pathToFileURL(path.join(__dirname, 'alias-loader.mjs')).href;
471
- return `data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register(${JSON.stringify(aliasLoaderUrl)}, pathToFileURL("./"));`;
472
- }
473
-
474
- function terminateProcessGroup(child, signal = 'SIGTERM') {
475
- if (!child?.pid) return;
476
- try {
477
- process.kill(-child.pid, signal);
478
- return;
479
- } catch {
480
- // Fall back to the direct child if it was not started as a process group.
481
- }
482
- try {
483
- child.kill(signal);
484
- } catch {
485
- // Already gone.
486
- }
487
- }
488
-
489
- let shuttingDown = false;
490
- function shutdown(exitCode = 0) {
491
- if (shuttingDown) return;
492
- shuttingDown = true;
493
- console.log('\n[openHermit] Shutting down...');
494
- terminateProcessGroup(serverProcess, 'SIGTERM');
495
- terminateProcessGroup(ccConnectProcess, 'SIGTERM');
496
- setTimeout(() => {
497
- terminateProcessGroup(serverProcess, 'SIGKILL');
498
- terminateProcessGroup(ccConnectProcess, 'SIGKILL');
499
- process.exit(exitCode);
500
- }, 2_000).unref();
284
+ function resolveTsxCli() {
285
+ return require.resolve('tsx/cli');
501
286
  }
502
287
 
503
288
  let ccConnectProcess = null;
@@ -517,7 +302,6 @@ if (!skipCcConnect) {
517
302
  console.log(`[openHermit] cc-connect config: ${ccConnectConfigPath}`);
518
303
  ccConnectProcess = spawn(process.execPath, [resolveCcConnectRunner(), '-config', ccConnectConfigPath], {
519
304
  cwd: repoRoot,
520
- detached: true,
521
305
  env: {
522
306
  ...process.env,
523
307
  CC_CONNECT_TOKEN: ccTokens.managementToken,
@@ -526,9 +310,9 @@ if (!skipCcConnect) {
526
310
  },
527
311
  stdio: 'inherit',
528
312
  });
529
- const ready = await waitForCcConnect(ccBaseUrl, ccTokens.managementToken, 15_000);
313
+ const ready = await waitForCcConnect(ccBaseUrl, ccTokens.managementToken, 30_000);
530
314
  if (!ready) {
531
- console.warn('[openHermit] cc-connect did not become ready within 15s; openHermit will keep trying via API.');
315
+ console.warn('[openHermit] cc-connect did not become ready within 30s; openHermit will keep trying via API.');
532
316
  }
533
317
  }
534
318
  }
@@ -570,42 +354,39 @@ if (!existsSync(distRenderererDir) || !existsSync(path.join(distRenderererDir, '
570
354
  // Start the server
571
355
  console.log('[openHermit] Launching server...\n');
572
356
 
573
- const serverProcess = spawn(
574
- process.execPath,
575
- ['--import', resolveAliasLoaderRegister(), '--import', resolveTsxLoader(), 'src/main/server.ts'],
576
- {
577
- cwd: repoRoot,
578
- detached: true,
579
- env: {
580
- ...process.env,
581
- PORT: port,
582
- HOST: process.env.HOST || '127.0.0.1',
583
- NODE_ENV: 'production',
584
- HERMIT_HOME: hermitHome,
585
- CC_CONNECT_TOKEN: ccTokens.managementToken,
586
- CC_CONNECT_MANAGEMENT_TOKEN: ccTokens.managementToken,
587
- CC_CONNECT_BRIDGE_TOKEN: ccTokens.bridgeToken,
588
- CC_CONNECT_CONFIG: ccConnectConfigPath,
589
- },
590
- stdio: 'inherit',
591
- }
592
- );
357
+ const serverProcess = spawn(process.execPath, [resolveTsxCli(), 'src/main/server.ts'], {
358
+ cwd: repoRoot,
359
+ env: {
360
+ ...process.env,
361
+ PORT: port,
362
+ HOST: process.env.HOST || '127.0.0.1',
363
+ NODE_ENV: 'production',
364
+ HERMIT_HOME: hermitHome,
365
+ CC_CONNECT_TOKEN: ccTokens.managementToken,
366
+ CC_CONNECT_MANAGEMENT_TOKEN: ccTokens.managementToken,
367
+ CC_CONNECT_BRIDGE_TOKEN: ccTokens.bridgeToken,
368
+ CC_CONNECT_CONFIG: ccConnectConfigPath,
369
+ },
370
+ stdio: 'inherit',
371
+ });
593
372
 
594
373
  serverProcess.on('exit', (code) => {
595
- if (shuttingDown) return;
596
- terminateProcessGroup(ccConnectProcess, 'SIGTERM');
597
374
  if (code !== 0) {
598
375
  console.error(`[openHermit] Server exited with code ${code}`);
599
376
  process.exit(code ?? 1);
600
377
  }
601
- process.exit(0);
602
378
  });
603
379
 
604
- process.on('SIGINT', () => shutdown(0));
605
- process.on('SIGTERM', () => shutdown(0));
606
- process.on('exit', () => {
607
- terminateProcessGroup(serverProcess, 'SIGTERM');
608
- terminateProcessGroup(ccConnectProcess, 'SIGTERM');
380
+ process.on('SIGINT', () => {
381
+ console.log('\n[openHermit] Shutting down...');
382
+ serverProcess.kill('SIGINT');
383
+ ccConnectProcess?.kill('SIGINT');
384
+ });
385
+
386
+ process.on('SIGTERM', () => {
387
+ console.log('\n[openHermit] Shutting down...');
388
+ serverProcess.kill('SIGTERM');
389
+ ccConnectProcess?.kill('SIGTERM');
609
390
  });
610
391
 
611
392
  console.log(`[openHermit] Server starting on http://127.0.0.1:${port}`);
@@ -1,4 +1,4 @@
1
- import{bD as de,bE as w,bF as Re,r as s,bG as Ce,bH as As,bI as Ls,E as pe,bJ as Os,bK as zs,q as e,bL as $s,bM as Ks,bN as Kt,bO as Gt,bP as Gs,bQ as Bs,bR as Us,bS as Vs,bT as H,bU as X,bV as sn,bW as rn,bX as q,bY as mt,bZ as he,b_ as on,b$ as P,c0 as Ws,c1 as tt,c2 as Hs,c3 as Xs,c4 as qs,c5 as Ys,c6 as Qs,c7 as Zs,c8 as Js,c9 as er,ca as tr,cb as nr,cc as sr,cd as rr,ce as or,cf as ar,cg as Bt,ch as ir,ci as an,cj as cn,ck as cr,cl as lr,cm as dr,cn as ur,co as xr,cp as je,cq as nt,cr as fr,cs as Ee,ct as hr,cu as gt,cv as ln,cw as mr,cx as dn,cy as un,cz as bt,cA as pr,cB as gr,cC as br,cD as st,cE as vr,cF as Be,cG as $,cH as Ie,cI as Cr,cJ as jr,cK as yr,cL as Nr,cM as wr,cN as Sr,cO as kr,cP as Mr,cQ as Tr,cR as Er,cS as Rr,cT as Ir,cU as Ze,cV as Dr,cW as Pr,cX as vt,cY as xn,cZ as fn,c_ as hn,c$ as _r,d0 as Fr,d1 as Ar,d2 as mn,d3 as pn,d4 as gn,d5 as Le,d6 as Oe,d7 as ze,d8 as $e,d9 as Ye,da as Qe,db as bn,dc as vn,dd as Lr,de as Ue,df as Or,dg as zr,dh as $r,di as Kr,dj as Gr,dk as Br,dl as Ur,dm as Vr,dn as Wr,dp as Hr,dq as Xr,dr as qr,ds as Yr,dt as Qr,du as Zr,dv as Fe,dw as Jr,dx as Ut,dy as eo,dz as to,dA as Vt,dB as no,dC as so,dD as ro,dE as Wt}from"./index-DmgKTZAa.js";import"./splashScene-C8lWNnm4.js";/**
1
+ import{bD as de,bE as w,bF as Re,r as s,bG as Ce,bH as As,bI as Ls,E as pe,bJ as Os,bK as zs,q as e,bL as $s,bM as Ks,bN as Kt,bO as Gt,bP as Gs,bQ as Bs,bR as Us,bS as Vs,bT as H,bU as X,bV as sn,bW as rn,bX as q,bY as mt,bZ as he,b_ as on,b$ as P,c0 as Ws,c1 as tt,c2 as Hs,c3 as Xs,c4 as qs,c5 as Ys,c6 as Qs,c7 as Zs,c8 as Js,c9 as er,ca as tr,cb as nr,cc as sr,cd as rr,ce as or,cf as ar,cg as Bt,ch as ir,ci as an,cj as cn,ck as cr,cl as lr,cm as dr,cn as ur,co as xr,cp as je,cq as nt,cr as fr,cs as Ee,ct as hr,cu as gt,cv as ln,cw as mr,cx as dn,cy as un,cz as bt,cA as pr,cB as gr,cC as br,cD as st,cE as vr,cF as Be,cG as $,cH as Ie,cI as Cr,cJ as jr,cK as yr,cL as Nr,cM as wr,cN as Sr,cO as kr,cP as Mr,cQ as Tr,cR as Er,cS as Rr,cT as Ir,cU as Ze,cV as Dr,cW as Pr,cX as vt,cY as xn,cZ as fn,c_ as hn,c$ as _r,d0 as Fr,d1 as Ar,d2 as mn,d3 as pn,d4 as gn,d5 as Le,d6 as Oe,d7 as ze,d8 as $e,d9 as Ye,da as Qe,db as bn,dc as vn,dd as Lr,de as Ue,df as Or,dg as zr,dh as $r,di as Kr,dj as Gr,dk as Br,dl as Ur,dm as Vr,dn as Wr,dp as Hr,dq as Xr,dr as qr,ds as Yr,dt as Qr,du as Zr,dv as Fe,dw as Jr,dx as Ut,dy as eo,dz as to,dA as Vt,dB as no,dC as so,dD as ro,dE as Wt}from"./index-3JdA9Dab.js";import"./splashScene-C8lWNnm4.js";/**
2
2
  * @license lucide-react v0.577.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{u as R,m as V,n as q,o as D,r as n,q as a,T as P,G as W,v as I,w as Z,x as z,y as O,z as B}from"./index-DmgKTZAa.js";import"./splashScene-C8lWNnm4.js";const M=({teamName:s,onClose:c,onPinAsTab:h,sidebarVisible:C,onToggleSidebar:v,onSendMessage:u,onOpenTaskDetail:i,onOpenMemberProfile:d})=>{const T=R(s),{openTeamPage:k,commitOwnerSlotDrop:w,commitOwnerGridOrderDrop:S,setLayoutMode:x}=V(s),{sidebarVisible:m,toggleSidebarVisible:f}=q(),{dialog:G,openCreateTaskDialog:g}=D(s),b=C??m,y=v??f,t=n.useCallback(o=>e=>window.dispatchEvent(new CustomEvent(`graph:${o}`,{detail:{teamName:s,taskId:e}})),[s]),A=n.useMemo(()=>({onStartTask:t("start-task"),onCompleteTask:t("complete-task"),onApproveTask:t("approve-task"),onRequestReview:t("request-review"),onRequestChanges:t("request-changes"),onCancelTask:t("cancel-task"),onMoveBackToDone:t("move-back-to-done"),onDeleteTask:t("delete-task")}),[t]),j=n.useCallback(()=>{k(),c()},[c,k]),H=n.useCallback(()=>{g("")},[g]),E={onNodeDoubleClick:n.useCallback(o=>{o.kind==="task"?i?.(o.taskId):o.kind==="member"&&d?.(o.memberName)},[i,d]),onSendMessage:n.useCallback(o=>u?.(o),[u]),onOpenTaskDetail:n.useCallback(o=>i?.(o),[i]),onOpenMemberProfile:n.useCallback(o=>d?.(o),[d])};return a.jsxs("div",{className:"fixed inset-0 z-50 flex overflow-hidden",style:{background:"#050510"},children:[b?a.jsx(P,{teamName:s,surface:"graph-overlay",isActive:!0,isFocused:!0}):null,a.jsx(W,{data:T,events:E,isSurfaceActive:!0,onRequestClose:c,onRequestPinAsTab:h,onOpenTeamPage:j,onCreateTask:H,onToggleSidebar:y,isSidebarVisible:b,renderTopToolbarContent:()=>a.jsx(B,{teamName:s}),onLayoutModeChange:x,onOwnerSlotDrop:w,onOwnerGridOrderDrop:S,className:"team-graph-view min-w-0 flex-1",renderHud:o=>{const e=o,{getViewportSize:r,focusNodeIds:l,filters:p}=e;return a.jsxs(a.Fragment,{children:[a.jsx(z,{teamName:s,getTransientHandoffSnapshot:e.getTransientHandoffSnapshot,getCameraZoom:e.getCameraZoom,worldToScreen:e.worldToScreen,getNodeWorldPosition:e.getNodeWorldPosition,focusNodeIds:l,focusEdgeIds:e.focusEdgeIds??null}),a.jsx(O,{teamName:s,nodes:T.nodes,getActivityWorldRect:e.getActivityWorldRect,getCameraZoom:e.getCameraZoom,worldToScreen:e.worldToScreen,getNodeWorldPosition:e.getNodeWorldPosition,getViewportSize:r,focusNodeIds:l,enabled:p?.showActivity??!0,onOpenTaskDetail:i,onOpenMemberProfile:d})]})},renderEdgeOverlay:({edge:o,sourceNode:e,targetNode:r,onClose:l,onSelectNode:p})=>a.jsx(Z,{teamName:s,edge:o,sourceNode:e,targetNode:r,onClose:l,onSelectNode:p,onOpenTaskDetail:i}),renderOverlay:({node:o,onClose:e})=>a.jsx(I,{node:o,teamName:s,onClose:e,onSendMessage:r=>{u?.(r),e()},onCreateTask:g,onOpenTaskDetail:r=>{i?.(r),e()},onOpenMemberProfile:r=>{d?.(r),e()},...A})}),G]})};export{M as TeamGraphOverlay};
1
+ import{u as R,m as V,n as q,o as D,r as n,q as a,T as P,G as W,v as I,w as Z,x as z,y as O,z as B}from"./index-3JdA9Dab.js";import"./splashScene-C8lWNnm4.js";const M=({teamName:s,onClose:c,onPinAsTab:h,sidebarVisible:C,onToggleSidebar:v,onSendMessage:u,onOpenTaskDetail:i,onOpenMemberProfile:d})=>{const T=R(s),{openTeamPage:k,commitOwnerSlotDrop:w,commitOwnerGridOrderDrop:S,setLayoutMode:x}=V(s),{sidebarVisible:m,toggleSidebarVisible:f}=q(),{dialog:G,openCreateTaskDialog:g}=D(s),b=C??m,y=v??f,t=n.useCallback(o=>e=>window.dispatchEvent(new CustomEvent(`graph:${o}`,{detail:{teamName:s,taskId:e}})),[s]),A=n.useMemo(()=>({onStartTask:t("start-task"),onCompleteTask:t("complete-task"),onApproveTask:t("approve-task"),onRequestReview:t("request-review"),onRequestChanges:t("request-changes"),onCancelTask:t("cancel-task"),onMoveBackToDone:t("move-back-to-done"),onDeleteTask:t("delete-task")}),[t]),j=n.useCallback(()=>{k(),c()},[c,k]),H=n.useCallback(()=>{g("")},[g]),E={onNodeDoubleClick:n.useCallback(o=>{o.kind==="task"?i?.(o.taskId):o.kind==="member"&&d?.(o.memberName)},[i,d]),onSendMessage:n.useCallback(o=>u?.(o),[u]),onOpenTaskDetail:n.useCallback(o=>i?.(o),[i]),onOpenMemberProfile:n.useCallback(o=>d?.(o),[d])};return a.jsxs("div",{className:"fixed inset-0 z-50 flex overflow-hidden",style:{background:"#050510"},children:[b?a.jsx(P,{teamName:s,surface:"graph-overlay",isActive:!0,isFocused:!0}):null,a.jsx(W,{data:T,events:E,isSurfaceActive:!0,onRequestClose:c,onRequestPinAsTab:h,onOpenTeamPage:j,onCreateTask:H,onToggleSidebar:y,isSidebarVisible:b,renderTopToolbarContent:()=>a.jsx(B,{teamName:s}),onLayoutModeChange:x,onOwnerSlotDrop:w,onOwnerGridOrderDrop:S,className:"team-graph-view min-w-0 flex-1",renderHud:o=>{const e=o,{getViewportSize:r,focusNodeIds:l,filters:p}=e;return a.jsxs(a.Fragment,{children:[a.jsx(z,{teamName:s,getTransientHandoffSnapshot:e.getTransientHandoffSnapshot,getCameraZoom:e.getCameraZoom,worldToScreen:e.worldToScreen,getNodeWorldPosition:e.getNodeWorldPosition,focusNodeIds:l,focusEdgeIds:e.focusEdgeIds??null}),a.jsx(O,{teamName:s,nodes:T.nodes,getActivityWorldRect:e.getActivityWorldRect,getCameraZoom:e.getCameraZoom,worldToScreen:e.worldToScreen,getNodeWorldPosition:e.getNodeWorldPosition,getViewportSize:r,focusNodeIds:l,enabled:p?.showActivity??!0,onOpenTaskDetail:i,onOpenMemberProfile:d})]})},renderEdgeOverlay:({edge:o,sourceNode:e,targetNode:r,onClose:l,onSelectNode:p})=>a.jsx(Z,{teamName:s,edge:o,sourceNode:e,targetNode:r,onClose:l,onSelectNode:p,onOpenTaskDetail:i}),renderOverlay:({node:o,onClose:e})=>a.jsx(I,{node:o,teamName:s,onClose:e,onSendMessage:r=>{u?.(r),e()},onCreateTask:g,onOpenTaskDetail:r=>{i?.(r),e()},onOpenMemberProfile:r=>{d?.(r),e()},...A})}),G]})};export{M as TeamGraphOverlay};