@konglx/rotom 2.21.0

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 (189) hide show
  1. package/README.md +417 -0
  2. package/bin/mesh-master.sh +439 -0
  3. package/bin/rotom +29 -0
  4. package/bin/rotom-link.sh +136 -0
  5. package/bin/rotom-send-with-status +57 -0
  6. package/bin/rotom-up.sh +428 -0
  7. package/dist/cli/ask.js +62 -0
  8. package/dist/cli/common.js +321 -0
  9. package/dist/cli/config.js +65 -0
  10. package/dist/cli/directory.js +17 -0
  11. package/dist/cli/executor.js +58 -0
  12. package/dist/cli/fed.js +91 -0
  13. package/dist/cli/group.js +273 -0
  14. package/dist/cli/identity.js +62 -0
  15. package/dist/cli/init.js +268 -0
  16. package/dist/cli/issue.js +202 -0
  17. package/dist/cli/join.js +170 -0
  18. package/dist/cli/link.js +47 -0
  19. package/dist/cli/master.js +51 -0
  20. package/dist/cli/memory.js +307 -0
  21. package/dist/cli/note.js +68 -0
  22. package/dist/cli/repo.js +77 -0
  23. package/dist/cli/rotom.js +277 -0
  24. package/dist/cli/routes.js +118 -0
  25. package/dist/cli/run.js +45 -0
  26. package/dist/cli/schedule.js +237 -0
  27. package/dist/cli/skill.js +173 -0
  28. package/dist/cli/team.js +106 -0
  29. package/dist/executor/claude-code-hook.cjs +80 -0
  30. package/dist/executor/cli-executor.js +8 -0
  31. package/dist/executor/executors/claude-code.js +780 -0
  32. package/dist/executor/executors/codex.js +719 -0
  33. package/dist/executor/executors/hermes-cli.js +855 -0
  34. package/dist/executor/executors/openclaw.js +467 -0
  35. package/dist/executor/executors/pi.js +514 -0
  36. package/dist/executor/index.js +269 -0
  37. package/dist/executor/jsonrpc-transport.js +125 -0
  38. package/dist/executor/process-runner.js +101 -0
  39. package/dist/executor/reasoning-status.js +83 -0
  40. package/dist/executor/repo-cache.js +502 -0
  41. package/dist/executor/session-store.js +188 -0
  42. package/dist/executor/worker-chat.js +257 -0
  43. package/dist/executor/worker-connection.js +89 -0
  44. package/dist/executor/worker-issue.js +264 -0
  45. package/dist/executor/worker.js +877 -0
  46. package/dist/link/pending-requests.js +72 -0
  47. package/dist/link/server.js +233 -0
  48. package/dist/link/visibility-store.js +58 -0
  49. package/dist/master/api/agents.js +333 -0
  50. package/dist/master/api/artifacts.js +271 -0
  51. package/dist/master/api/domains.js +64 -0
  52. package/dist/master/api/groups.js +635 -0
  53. package/dist/master/api/guidance-templates.js +147 -0
  54. package/dist/master/api/index.js +89 -0
  55. package/dist/master/api/issues-patrol.js +172 -0
  56. package/dist/master/api/issues.js +663 -0
  57. package/dist/master/api/links-patrol.js +168 -0
  58. package/dist/master/api/links.js +114 -0
  59. package/dist/master/api/memory.js +259 -0
  60. package/dist/master/api/messages.js +157 -0
  61. package/dist/master/api/notes.js +77 -0
  62. package/dist/master/api/schedule-patterns.js +133 -0
  63. package/dist/master/api/schedules.js +272 -0
  64. package/dist/master/api/sessions.js +158 -0
  65. package/dist/master/api/share.js +269 -0
  66. package/dist/master/api/skills.js +190 -0
  67. package/dist/master/api/teams.js +122 -0
  68. package/dist/master/api/uploads.js +245 -0
  69. package/dist/master/auth.js +134 -0
  70. package/dist/master/dashboard/animations/calico-dozing.apng +0 -0
  71. package/dist/master/dashboard/animations/calico-error.apng +0 -0
  72. package/dist/master/dashboard/animations/calico-happy.apng +0 -0
  73. package/dist/master/dashboard/animations/calico-notification.apng +0 -0
  74. package/dist/master/dashboard/animations/calico-sleeping.apng +0 -0
  75. package/dist/master/dashboard/animations/calico-thinking.apng +0 -0
  76. package/dist/master/dashboard/animations/calico-waking.apng +0 -0
  77. package/dist/master/dashboard/assets/ApprovalCard-C38VV6ko.css +1 -0
  78. package/dist/master/dashboard/assets/ApprovalCard-CHPh2dmE.js +17 -0
  79. package/dist/master/dashboard/assets/ArtifactPanel-P_2gAP7v.js +1 -0
  80. package/dist/master/dashboard/assets/ArtifactPanel-aGHySny5.css +1 -0
  81. package/dist/master/dashboard/assets/css.worker-DaIe3gwK.js +84 -0
  82. package/dist/master/dashboard/assets/editor.worker-BCzxt1at.js +12 -0
  83. package/dist/master/dashboard/assets/html.worker-CKrFyw_2.js +461 -0
  84. package/dist/master/dashboard/assets/index-CChrTn81.css +32 -0
  85. package/dist/master/dashboard/assets/index-Dhu4SN1z.js +181 -0
  86. package/dist/master/dashboard/assets/json.worker-B7c_PmGb.js +49 -0
  87. package/dist/master/dashboard/assets/markdown-CeN5IgdF.js +29 -0
  88. package/dist/master/dashboard/assets/monaco-core-DyX1CsEw.css +1 -0
  89. package/dist/master/dashboard/assets/monaco-core-oQiQUisy.js +833 -0
  90. package/dist/master/dashboard/assets/monaco-setup-CiOPQdmo.js +1 -0
  91. package/dist/master/dashboard/assets/react-vendor-C8IxlyCR.js +67 -0
  92. package/dist/master/dashboard/assets/ts.worker-BhkL8olL.js +51334 -0
  93. package/dist/master/dashboard/assets/useMonaco-ILb4vyPh.js +12 -0
  94. package/dist/master/dashboard/assets/vite-preload-CxJPbCTl.js +1 -0
  95. package/dist/master/dashboard/debug-auth.html +197 -0
  96. package/dist/master/dashboard/favicon.ico +0 -0
  97. package/dist/master/dashboard/index.html +20 -0
  98. package/dist/master/dashboard/rotom-avatar.png +0 -0
  99. package/dist/master/db/agent-sessions.js +60 -0
  100. package/dist/master/db/agent-visibility.js +64 -0
  101. package/dist/master/db/agents.js +119 -0
  102. package/dist/master/db/ask-bridges.js +157 -0
  103. package/dist/master/db/build-update.js +59 -0
  104. package/dist/master/db/core.js +82 -0
  105. package/dist/master/db/domains.js +80 -0
  106. package/dist/master/db/groups.js +316 -0
  107. package/dist/master/db/guidance-templates.js +58 -0
  108. package/dist/master/db/index.js +12 -0
  109. package/dist/master/db/internal.js +45 -0
  110. package/dist/master/db/issues-patrol.js +81 -0
  111. package/dist/master/db/issues.js +373 -0
  112. package/dist/master/db/links.js +221 -0
  113. package/dist/master/db/master-node.js +43 -0
  114. package/dist/master/db/memory.js +272 -0
  115. package/dist/master/db/messages.js +210 -0
  116. package/dist/master/db/notes.js +55 -0
  117. package/dist/master/db/schedule-patterns.js +56 -0
  118. package/dist/master/db/schedules.js +135 -0
  119. package/dist/master/db/skills.js +144 -0
  120. package/dist/master/db/team.js +88 -0
  121. package/dist/master/db/types.js +10 -0
  122. package/dist/master/db.js +12 -0
  123. package/dist/master/embedded.js +133 -0
  124. package/dist/master/federation/client.js +283 -0
  125. package/dist/master/federation/identity.js +133 -0
  126. package/dist/master/federation/manager.js +267 -0
  127. package/dist/master/federation/publisher.js +87 -0
  128. package/dist/master/federation/self-publisher.js +69 -0
  129. package/dist/master/federation/server.js +487 -0
  130. package/dist/master/group-paths.js +208 -0
  131. package/dist/master/offline-queue.js +38 -0
  132. package/dist/master/opc-bootstrap.js +245 -0
  133. package/dist/master/patrol-terminal.js +275 -0
  134. package/dist/master/repo-scan.js +188 -0
  135. package/dist/master/router.js +214 -0
  136. package/dist/master/scheduler-handlers.js +510 -0
  137. package/dist/master/scheduler.js +201 -0
  138. package/dist/master/server.js +203 -0
  139. package/dist/master/services/link-collector.js +82 -0
  140. package/dist/master/services/link-patrol-bootstrap.js +50 -0
  141. package/dist/master/services/memory-extract-prompt.js +34 -0
  142. package/dist/master/services/patrol-bootstrap.js +63 -0
  143. package/dist/master/share-tokens.js +56 -0
  144. package/dist/master/terminal-hub.js +300 -0
  145. package/dist/master/uploads.js +108 -0
  146. package/dist/master/util/fs.js +100 -0
  147. package/dist/master/util/paths.js +50 -0
  148. package/dist/master/util/persona.js +10 -0
  149. package/dist/master/ws-hub/connection.js +928 -0
  150. package/dist/master/ws-hub/conversation.js +290 -0
  151. package/dist/master/ws-hub/directory.js +70 -0
  152. package/dist/master/ws-hub/dispatch-enrich.js +34 -0
  153. package/dist/master/ws-hub/hub.js +136 -0
  154. package/dist/master/ws-hub/index.js +9 -0
  155. package/dist/master/ws-hub/internal.js +35 -0
  156. package/dist/master/ws-hub/routing.js +295 -0
  157. package/dist/master/ws-hub/sessions.js +130 -0
  158. package/dist/master/ws-hub.js +11 -0
  159. package/dist/shared/agent-profile.js +44 -0
  160. package/dist/shared/constants.js +55 -0
  161. package/dist/shared/dedup.js +33 -0
  162. package/dist/shared/group-context.js +62 -0
  163. package/dist/shared/json-codec.js +33 -0
  164. package/dist/shared/logger.js +136 -0
  165. package/dist/shared/mention.js +22 -0
  166. package/dist/shared/network.js +40 -0
  167. package/dist/shared/parse.js +18 -0
  168. package/dist/shared/prompt-composer.js +171 -0
  169. package/dist/shared/protocol/client-messages.js +8 -0
  170. package/dist/shared/protocol/enums.js +6 -0
  171. package/dist/shared/protocol/federation.js +62 -0
  172. package/dist/shared/protocol/guards.js +87 -0
  173. package/dist/shared/protocol/server-messages.js +8 -0
  174. package/dist/shared/protocol/types.js +8 -0
  175. package/dist/shared/protocol.js +19 -0
  176. package/dist/shared/readonly-allowlist.js +122 -0
  177. package/dist/shared/rotom-cli-prompt.js +23 -0
  178. package/dist/shared/skill-context.js +19 -0
  179. package/dist/shared/skill-md.js +43 -0
  180. package/dist/shared/slash-commands.js +50 -0
  181. package/dist/shared/time.js +80 -0
  182. package/dist/shared/title.js +46 -0
  183. package/dist/shared/url-extractor.js +99 -0
  184. package/migrations/001-schema.sql +942 -0
  185. package/package.json +68 -0
  186. package/scripts/fix-node-pty-perms.mjs +46 -0
  187. package/skill/rotom-a2a-communicate/SKILL.md +257 -0
  188. package/skill/rotom-bus-host/SKILL.md +78 -0
  189. package/skill/rotom-bus-host/scripts/poll-replies.sh +148 -0
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Federation / Team REST API。
3
+ *
4
+ * Phase 2 MVP 只做 GET(读):
5
+ * GET /api/identity → 本机 master_node 信息(含 teamName)
6
+ * GET /api/teams → 已加入的团队列表(本机视角)
7
+ * GET /api/teams/:id/members → 团队内 agent_visibility 联合视图
8
+ * GET /api/teams/:id/peers → 团队内 peer master 列表
9
+ *
10
+ * Runtime join/leave(POST)留给 Phase 3 —— 那需要把 fedClient/fedPublisher
11
+ * 从 server.ts main 的局部变量提取成模块级单例,以便 API 层访问。
12
+ * Phase 2 用户通过 ~/.rotom/team.json + 重启 master 切换 federation 状态。
13
+ *
14
+ * 历史命名:Phase 2 叫 department,migration 058 改名 team。API 路径同步改。
15
+ */
16
+ import { getFederationManager } from "../federation/manager.js";
17
+ export function registerTeamRoutes(apiRouter, db) {
18
+ // 本机 master 身份
19
+ apiRouter.get("/identity", (_req, res) => {
20
+ const node = db.getMasterNode();
21
+ if (!node) {
22
+ res.status(500).json({ error: "master_node not initialized (OPC bootstrap failed?)" });
23
+ return;
24
+ }
25
+ res.json({
26
+ id: node.id,
27
+ hostname: node.hostname,
28
+ role: node.role,
29
+ displayName: node.display_name,
30
+ teamName: node.team_name ?? node.hostname,
31
+ endpoint: node.endpoint,
32
+ federationEnabled: node.federation_enabled !== 0,
33
+ });
34
+ });
35
+ // 已加入的团队列表
36
+ apiRouter.get("/teams", (_req, res) => {
37
+ const teams = db.listTeams();
38
+ res.json(teams.map((t) => ({
39
+ id: t.id,
40
+ name: t.name,
41
+ description: t.description,
42
+ myRole: t.my_role,
43
+ coordEndpoints: t.coord_endpoints.split(",").filter(Boolean),
44
+ joinedAt: t.joined_at,
45
+ })));
46
+ });
47
+ // 团队内可见 agent 列表(包括本机发布出去的 + 其他 member 缓存的)
48
+ apiRouter.get("/teams/:id/members", (req, res) => {
49
+ const teamId = req.params.id;
50
+ const team = db.getTeam(teamId);
51
+ if (!team) {
52
+ res.status(404).json({ error: `Team "${teamId}" not found` });
53
+ return;
54
+ }
55
+ const visible = db.listVisibleAgents(teamId);
56
+ res.json(visible.map((v) => ({
57
+ masterId: v.master_id,
58
+ hostname: v.hostname,
59
+ name: v.agent_name,
60
+ displayName: v.display_name,
61
+ isHuman: v.is_human !== 0,
62
+ online: v.online !== 0,
63
+ lastHeartbeat: v.last_heartbeat,
64
+ // 给 UI 用的复合显示键
65
+ ref: `${v.agent_name}@${v.hostname}`,
66
+ })));
67
+ });
68
+ // 团队内的 peer master 列表(协调侧权威,member 侧缓存)
69
+ apiRouter.get("/teams/:id/peers", (req, res) => {
70
+ const teamId = req.params.id;
71
+ const team = db.getTeam(teamId);
72
+ if (!team) {
73
+ res.status(404).json({ error: `Team "${teamId}" not found` });
74
+ return;
75
+ }
76
+ const peers = db.listPeers(teamId);
77
+ res.json(peers.map((p) => ({
78
+ masterId: p.master_id,
79
+ hostname: p.hostname,
80
+ endpoint: p.endpoint,
81
+ role: p.role,
82
+ lastSeenAt: p.last_seen_at,
83
+ })));
84
+ });
85
+ // ─── Runtime join / leave(无需重启 master)──────────────────────────────
86
+ // POST /api/teams/join body: { coordEndpoint: "ws://host:port", teamName?: "..." }
87
+ // 本机从 standalone 切到 member,连协调 master,加入大团队。
88
+ apiRouter.post("/teams/join", async (req, res) => {
89
+ const { coordEndpoint, teamName } = req.body || {};
90
+ if (!coordEndpoint || typeof coordEndpoint !== "string") {
91
+ res.status(400).json({ error: "coordEndpoint is required (e.g. ws://192.168.1.5:28800)" });
92
+ return;
93
+ }
94
+ const mgr = getFederationManager();
95
+ if (!mgr) {
96
+ res.status(500).json({ error: "FederationManager not initialized" });
97
+ return;
98
+ }
99
+ try {
100
+ const result = await mgr.joinTeam({ coordEndpoint, teamName });
101
+ res.json({ ok: true, teamId: result.teamId, teamName: result.teamName });
102
+ }
103
+ catch (err) {
104
+ res.status(400).json({ error: err.message });
105
+ }
106
+ });
107
+ // POST /api/teams/leave 本机离开大团队,切回 standalone
108
+ apiRouter.post("/teams/leave", (_req, res) => {
109
+ const mgr = getFederationManager();
110
+ if (!mgr) {
111
+ res.status(500).json({ error: "FederationManager not initialized" });
112
+ return;
113
+ }
114
+ try {
115
+ mgr.leaveTeam();
116
+ res.json({ ok: true });
117
+ }
118
+ catch (err) {
119
+ res.status(400).json({ error: err.message });
120
+ }
121
+ });
122
+ }
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Image upload REST endpoints.
3
+ *
4
+ * POST /api/uploads JSON body { groupId, fileName, mimeType, dataBase64 }
5
+ * → { url, name, size, mimeType }
6
+ * GET /api/uploads/:groupId/:filename raw bytes w/ Content-Type
7
+ *
8
+ * POST is JSON+base64 rather than multipart/form-data to avoid pulling in a
9
+ * new parser dep. Base64 inflation is ~33%, which is fine after the dashboard's
10
+ * client-side Canvas compression (typical <500KB per image).
11
+ *
12
+ * GET serves bytes straight from disk with a 1-year immutable cache header —
13
+ * filenames embed a random suffix so they're effectively write-once.
14
+ */
15
+ import fs from "node:fs";
16
+ import path from "node:path";
17
+ import { createLogger } from "../../shared/logger.js";
18
+ import { UPLOADS_ROOT, generateUploadFileName, mimeFromExt, safeResolveUploadPath, resolveUploadDir, validateUpload, } from "../uploads.js";
19
+ const log = createLogger("mesh-api");
20
+ const BASE64_RE = /^[A-Za-z0-9+/]+={0,2}$/;
21
+ // Filename pattern: `YYYYMMDD-HHmmss-<rand6hex>.<ext>` — see generateUploadFileName.
22
+ // The leading 15 chars encode the upload timestamp in Asia/Shanghai (UTC+8).
23
+ const FILENAME_TIMESTAMP_RE = /^(\d{8})-(\d{6})-/;
24
+ /**
25
+ * Walk `UPLOADS_ROOT` and collect all uploaded files for the gallery tab.
26
+ *
27
+ * Layout: `~/.rotom/uploads/<YYYY-MM>/<groupId>/<YYYYMMDD-HHmmss>-<rand>.<ext>`
28
+ * We scan month buckets → group dirs → files, then sort by createdAt desc.
29
+ *
30
+ * `groupIdFilter` narrows to one group; `limit` caps the page size; `cursor`
31
+ * is the last (createdAt, fileName) from the previous page for stable
32
+ * forward pagination.
33
+ */
34
+ function scanUploads(db, groupIdFilter) {
35
+ if (!fs.existsSync(UPLOADS_ROOT))
36
+ return [];
37
+ const items = [];
38
+ for (const monthEntry of fs.readdirSync(UPLOADS_ROOT)) {
39
+ if (monthEntry.startsWith("."))
40
+ continue;
41
+ const monthDir = path.join(UPLOADS_ROOT, monthEntry);
42
+ let groupEntries;
43
+ try {
44
+ groupEntries = fs.readdirSync(monthDir);
45
+ }
46
+ catch {
47
+ continue;
48
+ }
49
+ for (const groupId of groupEntries) {
50
+ if (groupId.startsWith("."))
51
+ continue;
52
+ if (groupIdFilter && groupId !== groupIdFilter)
53
+ continue;
54
+ const groupDir = path.join(monthDir, groupId);
55
+ let fileEntries;
56
+ try {
57
+ fileEntries = fs.readdirSync(groupDir);
58
+ }
59
+ catch {
60
+ continue;
61
+ }
62
+ // Resolve group name once per group dir. Archived groups still show
63
+ // their images — archive doesn't delete files.
64
+ const group = db.getGroupById(groupId);
65
+ const groupName = group
66
+ ? group.name + (group.archived_at ? " (已归档)" : "")
67
+ : "(未知群)";
68
+ for (const fileName of fileEntries) {
69
+ if (fileName.startsWith("."))
70
+ continue;
71
+ const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
72
+ const mimeType = mimeFromExt(ext);
73
+ if (!mimeType)
74
+ continue; // not an allowed image ext
75
+ const abs = path.join(groupDir, fileName);
76
+ let stat;
77
+ try {
78
+ stat = fs.statSync(abs);
79
+ }
80
+ catch {
81
+ continue;
82
+ }
83
+ if (!stat.isFile())
84
+ continue;
85
+ // Prefer the timestamp embedded in the filename (matches upload
86
+ // instant, not filesystem mtime which can shift on copy). Fall back
87
+ // to mtime if the pattern doesn't match (legacy/renamed files).
88
+ let createdAt;
89
+ const m = FILENAME_TIMESTAMP_RE.exec(fileName);
90
+ if (m) {
91
+ const [, ymd, hms] = m;
92
+ // YYYYMMDD-HHmmss interpreted as Shanghai time → UTC ISO
93
+ const y = +ymd.slice(0, 4);
94
+ const mo = +ymd.slice(4, 6);
95
+ const d = +ymd.slice(6, 8);
96
+ const h = +hms.slice(0, 2);
97
+ const mi = +hms.slice(2, 4);
98
+ const s = +hms.slice(4, 6);
99
+ // Construct as UTC then subtract 8h to get the moment that was
100
+ // "local 8" at upload time. Equivalent to treating the digits as
101
+ // Asia/Shanghai wall clock.
102
+ const utcMs = Date.UTC(y, mo - 1, d, h, mi, s) - 8 * 60 * 60 * 1000;
103
+ createdAt = new Date(utcMs).toISOString();
104
+ }
105
+ else {
106
+ createdAt = stat.mtime.toISOString();
107
+ }
108
+ items.push({
109
+ url: `/api/uploads/${encodeURIComponent(groupId)}/${encodeURIComponent(fileName)}`,
110
+ groupId,
111
+ groupName,
112
+ fileName,
113
+ mimeType,
114
+ size: stat.size,
115
+ createdAt,
116
+ });
117
+ }
118
+ }
119
+ }
120
+ // Sort by createdAt desc; tiebreak by fileName for stable pagination.
121
+ items.sort((a, b) => a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 :
122
+ a.fileName < b.fileName ? 1 : a.fileName > b.fileName ? -1 : 0);
123
+ return items;
124
+ }
125
+ /** Encode/decode a cursor of (createdAt, fileName). Opaque to the client. */
126
+ function encodeCursor(createdAt, fileName) {
127
+ return Buffer.from(`${createdAt}|${fileName}`).toString("base64url");
128
+ }
129
+ function decodeCursor(cursor) {
130
+ try {
131
+ const decoded = Buffer.from(cursor, "base64url").toString("utf8");
132
+ const idx = decoded.indexOf("|");
133
+ if (idx < 0)
134
+ return null;
135
+ return { createdAt: decoded.slice(0, idx), fileName: decoded.slice(idx + 1) };
136
+ }
137
+ catch {
138
+ return null;
139
+ }
140
+ }
141
+ export function registerUploadRoutes(apiRouter, db) {
142
+ // ── Gallery listing ───────────────────────────────────────────────────
143
+ // GET /api/uploads?groupId=&limit=&cursor= — cross-group index of all
144
+ // uploaded images, newest first. Used by the toolbox 图册 tab.
145
+ apiRouter.get("/uploads", (req, res) => {
146
+ const groupId = typeof req.query.groupId === "string" ? req.query.groupId : undefined;
147
+ let limit = 100;
148
+ if (req.query.limit) {
149
+ const n = Number(req.query.limit);
150
+ if (Number.isFinite(n) && n > 0)
151
+ limit = Math.min(Math.floor(n), 500);
152
+ }
153
+ const cursorStr = typeof req.query.cursor === "string" ? req.query.cursor : undefined;
154
+ const cursor = cursorStr ? decodeCursor(cursorStr) : null;
155
+ const all = scanUploads(db, groupId);
156
+ // Forward pagination: drop everything that sorts at or before the cursor
157
+ // (createdAt, fileName). Items are sorted desc, so we want items strictly
158
+ // "smaller" than the cursor.
159
+ let startIdx = 0;
160
+ if (cursor) {
161
+ const i = all.findIndex((it) => it.createdAt === cursor.createdAt && it.fileName === cursor.fileName);
162
+ if (i >= 0)
163
+ startIdx = i + 1;
164
+ }
165
+ const page = all.slice(startIdx, startIdx + limit);
166
+ const nextCursor = startIdx + page.length < all.length && page.length > 0
167
+ ? encodeCursor(page[page.length - 1].createdAt, page[page.length - 1].fileName)
168
+ : null;
169
+ res.json({ items: page, nextCursor });
170
+ });
171
+ apiRouter.post("/uploads", (req, res) => {
172
+ const { groupId, fileName, mimeType, dataBase64 } = req.body || {};
173
+ if (typeof groupId !== "string" || !groupId.trim()) {
174
+ res.status(400).json({ error: "groupId is required" });
175
+ return;
176
+ }
177
+ const group = db.getGroupById(groupId);
178
+ if (!group) {
179
+ res.status(404).json({ error: "Group not found" });
180
+ return;
181
+ }
182
+ if (group.archived_at) {
183
+ res.status(403).json({ error: "Group is archived, cannot upload" });
184
+ return;
185
+ }
186
+ if (typeof dataBase64 !== "string" || !BASE64_RE.test(dataBase64)) {
187
+ res.status(400).json({ error: "dataBase64 must be valid base64" });
188
+ return;
189
+ }
190
+ let buf;
191
+ try {
192
+ buf = Buffer.from(dataBase64, "base64");
193
+ }
194
+ catch (e) {
195
+ res.status(400).json({ error: `base64 decode failed: ${e?.message ?? "unknown"}` });
196
+ return;
197
+ }
198
+ const v = validateUpload(fileName, mimeType, buf.length);
199
+ if (!v.ok || !v.ext || !v.mimeType) {
200
+ res.status(400).json({ error: v.error });
201
+ return;
202
+ }
203
+ const storedName = generateUploadFileName(v.ext);
204
+ const { dir } = resolveUploadDir(groupId);
205
+ const absPath = `${dir}/${storedName}`;
206
+ try {
207
+ fs.writeFileSync(absPath, buf);
208
+ }
209
+ catch (e) {
210
+ log.error(`upload write failed: ${e?.code ?? e?.message ?? e}`);
211
+ res.status(500).json({ error: `write failed: ${e?.code ?? e?.message ?? "unknown"}` });
212
+ return;
213
+ }
214
+ const url = `/api/uploads/${encodeURIComponent(groupId)}/${encodeURIComponent(storedName)}`;
215
+ log.info(`upload stored: group=${groupId} mime=${v.mimeType} size=${buf.length} → ${url}`);
216
+ res.status(201).json({
217
+ url,
218
+ name: storedName,
219
+ size: buf.length,
220
+ mimeType: v.mimeType,
221
+ });
222
+ });
223
+ apiRouter.get("/uploads/:groupId/:filename", (req, res) => {
224
+ const { groupId, filename } = req.params;
225
+ if (typeof groupId !== "string" || typeof filename !== "string") {
226
+ res.status(400).json({ error: "invalid path" });
227
+ return;
228
+ }
229
+ const abs = safeResolveUploadPath(groupId, filename);
230
+ if (!abs) {
231
+ res.status(404).json({ error: "not found" });
232
+ return;
233
+ }
234
+ // Filename is the source of truth for extension; trust it over query
235
+ // params. Falls back to application/octet-stream for unknown exts (shouldn't
236
+ // happen given the upload whitelist, but defensive).
237
+ const ext = abs.split(".").pop()?.toLowerCase() ?? "";
238
+ const contentType = mimeFromExt(ext) ?? "application/octet-stream";
239
+ res.setHeader("Content-Type", contentType);
240
+ // Filename is write-once (random suffix + timestamp), so aggressive
241
+ // caching is safe and cuts dashboard re-fetches on history reload.
242
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
243
+ fs.createReadStream(abs).pipe(res);
244
+ });
245
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Digital Employee Mesh — Authentication
3
+ *
4
+ * Flow: registration token → sha256 verify → JWT (7d)
5
+ * Reconnect: JWT verify (no raw token needed)
6
+ */
7
+ import jwt from "jsonwebtoken";
8
+ import { randomBytes, createHash, timingSafeEqual, randomUUID } from "node:crypto";
9
+ import { JWT_EXPIRY, JWT_ALGORITHM } from "../shared/constants.js";
10
+ import { REAL_PERSONS } from "../shared/protocol/enums.js";
11
+ // ---------------------------------------------------------------------------
12
+ // Token hashing
13
+ // ---------------------------------------------------------------------------
14
+ /** Hash a registration token with sha256. */
15
+ export function hashToken(token) {
16
+ return createHash("sha256").update(token).digest("hex");
17
+ }
18
+ /** Timing-safe comparison of two hex strings. */
19
+ function safeCompare(a, b) {
20
+ if (a.length !== b.length)
21
+ return false;
22
+ return timingSafeEqual(Buffer.from(a, "utf-8"), Buffer.from(b, "utf-8"));
23
+ }
24
+ // ---------------------------------------------------------------------------
25
+ // Auth service
26
+ // ---------------------------------------------------------------------------
27
+ export class AuthService {
28
+ db;
29
+ secret;
30
+ constructor(db) {
31
+ this.db = db;
32
+ let s = db.getConfig("jwt_secret");
33
+ if (!s) {
34
+ s = randomBytes(32).toString("hex");
35
+ db.setConfig("jwt_secret", s);
36
+ }
37
+ this.secret = s;
38
+ }
39
+ /**
40
+ * First-time auth: verify registration token, return JWT.
41
+ * Returns null on failure. token 可能为 undefined(OPC 本机模式),此时一定失败 ——
42
+ * 本机信任走 `authenticateLocal`。
43
+ */
44
+ authenticate(token, name) {
45
+ if (!token)
46
+ return null;
47
+ const agent = this.db.getAgentByName(name);
48
+ if (!agent?.token_hash)
49
+ return null;
50
+ const inputHash = hashToken(token);
51
+ if (!safeCompare(inputHash, agent.token_hash))
52
+ return null;
53
+ const jwtToken = this.issueJwt(agent.id, agent.name, agent.domain || undefined);
54
+ return { jwt: jwtToken, agent: agent };
55
+ }
56
+ /** Issue a fresh JWT for a given agent. */
57
+ issueJwt(agentId, name, domain) {
58
+ const payload = { sub: agentId, name, domain };
59
+ return jwt.sign(payload, this.secret, {
60
+ expiresIn: JWT_EXPIRY,
61
+ algorithm: JWT_ALGORITHM,
62
+ });
63
+ }
64
+ /** Verify a JWT on reconnect. */
65
+ verify(token) {
66
+ try {
67
+ return jwt.verify(token, this.secret, {
68
+ algorithms: [JWT_ALGORITHM],
69
+ });
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ }
75
+ /**
76
+ * Fallback auth: find agent by token hash (ignoring name).
77
+ * Used when agent changed its display name but kept the same token.
78
+ */
79
+ authenticateByToken(token) {
80
+ if (!token)
81
+ return null;
82
+ const inputHash = hashToken(token);
83
+ const agent = this.db.getAgentByTokenHash(inputHash);
84
+ if (!agent)
85
+ return null;
86
+ const jwtToken = this.issueJwt(agent.id, agent.name, agent.domain || undefined);
87
+ return { jwt: jwtToken, agent: agent };
88
+ }
89
+ /**
90
+ * 免 token 本机信任认证:绕过 token / JWT 校验,直接按 name 查本机 agent 并签发 JWT。
91
+ *
92
+ * 仅当调用方能确保来源 IP 是 loopback(`isLoopback(req.socket.remoteAddress)`)时才能调此方法 ——
93
+ * 这是 OPC(本地 master + 本机 executor)开箱即用的关键路径,免去手写 mesh_token 配置。
94
+ *
95
+ * Agent 不存在时**自动注册一个**(本机即真人接入,允许建立任意 name 的 agent):
96
+ * - name 来自 executor / CLI / 用户指定 —— 没有限制
97
+ * - hostname 用本机 master_node 的 hostname
98
+ * - profile.category = "Agent"(区别于真人 agent)
99
+ * - token_hash 留空(走 localTrust)
100
+ *
101
+ * 仍会拒绝 `enabled = 0` 的禁用 agent(已存在但被禁用)。
102
+ */
103
+ authenticateLocal(name) {
104
+ let agent = this.db.getLocalAgentByName(name) ?? this.db.getAgentByName(name);
105
+ if (agent) {
106
+ if (agent.enabled === 0)
107
+ return null;
108
+ }
109
+ else {
110
+ // 本机即信任 —— agent 不存在则自动建一个。
111
+ const localHostname = this.db.getLocalHostname() ?? undefined;
112
+ const isRealPerson = REAL_PERSONS.includes(name);
113
+ const profile = JSON.stringify(isRealPerson ? { category: "真人" } : { category: "Agent" });
114
+ const id = randomUUID();
115
+ this.db.insertAgent({
116
+ id,
117
+ name,
118
+ hostname: localHostname,
119
+ tokenHash: "",
120
+ token: "",
121
+ profile,
122
+ });
123
+ agent = this.db.getAgentById(id);
124
+ if (!agent)
125
+ return null;
126
+ }
127
+ const jwtToken = this.issueJwt(agent.id, agent.name, agent.domain || undefined);
128
+ return { jwt: jwtToken, agent: agent };
129
+ }
130
+ /** Generate a new registration token (for API use). */
131
+ generateToken() {
132
+ return `mesh_${randomBytes(16).toString("hex")}`;
133
+ }
134
+ }
@@ -0,0 +1 @@
1
+ ._wrapper_pmhrh_1{display:inline-flex;align-items:center;gap:10px;cursor:pointer;-webkit-user-select:none;user-select:none;font-size:14px;font-weight:600;color:var(--color-ink);font-feature-settings:var(--font-feature-calt);letter-spacing:-.01em}._disabled_pmhrh_14{cursor:not-allowed;opacity:.5}._outer_pmhrh_19{position:relative;width:18px;height:18px;border-radius:50%;border:1.5px solid var(--border-color-hover);background:var(--color-canvas-elevated);display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;transition:border-color var(--duration-base) var(--ease-out),background-color var(--duration-base) var(--ease-out),box-shadow var(--duration-base) var(--ease-out),transform var(--duration-base) var(--ease-out);flex-shrink:0}._outerChecked_pmhrh_37{border-color:var(--color-wise-green);border-width:5px}._dot_pmhrh_42{position:absolute;width:8px;height:8px;border-radius:50%;background:var(--color-dark-green);display:none}._outerChecked_pmhrh_37:after{content:"";width:8px;height:8px;border-radius:50%;background:var(--color-dark-green)}._wrapper_pmhrh_1:hover:not(._disabled_pmhrh_14) ._outer_pmhrh_19{border-color:var(--color-dark-green);transform:translateY(-.5px)}._input_pmhrh_65{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}._wrapper_pmhrh_1:has(._input_pmhrh_65:focus-visible) ._outer_pmhrh_19{box-shadow:var(--shadow-inset)}._label_pmhrh_81{line-height:1.4}._approvalCard_m3zk8_1{margin:8px 0;padding:10px 12px;border-radius:var(--radius-sm);border:1px solid rgba(234,179,8,.4);border-left:3px solid var(--color-warning, #eab308);background:#eab30814;font-size:12px;color:var(--color-navy)}._approval_accepted_m3zk8_12{border-color:#22c55e66;border-left-color:var(--color-success, #22c55e);background:#22c55e0f}._approval_denied_m3zk8_18{border-color:#ef444466;border-left-color:var(--color-error, #ef4444);background:#ef44440f}._approval_answered_m3zk8_24{border-color:#22c55e66;border-left-color:var(--color-success, #22c55e);background:#22c55e0f}._approvalHeader_m3zk8_30{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px}._approvalTitle_m3zk8_38{font-weight:600;color:var(--color-navy)}._approvalStatus_m3zk8_43{font-size:11px;padding:1px 8px;border-radius:10px;font-weight:600;background:#eab3082e;color:#ca8a04}._approvalStatus_accepted_m3zk8_52{background:#22c55e26;color:#16a34a}._approvalStatus_denied_m3zk8_57{background:#ef444426;color:#dc2626}._approvalStatus_answered_m3zk8_62{background:#22c55e26;color:#16a34a}._approvalBody_m3zk8_67{margin:6px 0;padding:8px;font-family:SF Mono,Menlo,Monaco,Courier New,monospace;font-size:11px;background:#1e1e1e;color:#d4d4d4;border-radius:var(--radius-sm);white-space:pre-wrap;word-break:break-all;overflow-x:auto}._approvalFileList_m3zk8_80{margin:6px 0;padding:8px 8px 8px 24px;background:#ffffff80;border-radius:var(--radius-sm);font-family:SF Mono,Menlo,Monaco,Courier New,monospace;font-size:11px;list-style:disc}._approvalFileList_m3zk8_80 li{margin:2px 0;word-break:break-all}._diffSection_m3zk8_95{margin:8px 0 4px;border-radius:var(--radius-sm);overflow:hidden}._diffHunkLabel_m3zk8_101{font-size:11px;font-weight:600;color:var(--color-slate);padding:2px 8px;background:#64748b14;margin-bottom:2px}._diffWriteLabel_m3zk8_110{font-size:11px;font-weight:600;color:var(--color-info);padding:2px 8px;background:#3b82f614;margin-bottom:2px}._diffEditorWrap_m3zk8_119{border:1px solid var(--border-color-light);border-radius:var(--radius-sm);overflow:hidden}._diffTruncatedNote_m3zk8_125{font-size:11px;color:var(--color-warning, #eab308);margin-top:4px;font-style:italic}._approvalMeta_m3zk8_132{font-size:11px;color:var(--color-slate);margin-top:4px}._approvalError_m3zk8_138{font-size:11px;color:var(--color-error);margin-top:6px}._approvalActions_m3zk8_144{display:flex;align-items:center;gap:6px;margin-top:8px;flex-wrap:wrap}._diffEditHint_m3zk8_153{font-size:11px;color:var(--color-warm-dark);background:var(--color-accent-cyan);padding:4px 10px;border-radius:var(--radius-sm);margin:4px 0 6px;border-left:3px solid var(--color-wise-green)}._tweakOverflowWarn_m3zk8_164{font-size:11px;color:var(--color-warning);background:var(--color-warning-soft);padding:2px 8px;border-radius:var(--radius-pill);font-weight:600;cursor:help}._approvalFeedback_m3zk8_174{display:block;width:100%;min-height:64px;margin-top:8px;padding:6px 8px;font-family:inherit;font-size:12px;line-height:1.5;color:inherit;background:var(--color-surface);border:1px solid var(--border-color);border-radius:var(--radius-sm);resize:vertical;box-sizing:border-box}._approvalFeedback_m3zk8_174:focus{outline:none;border-color:var(--color-error)}._approvalFeedback_m3zk8_174:disabled{cursor:not-allowed;opacity:.6}._askQuestionBlock_m3zk8_201{margin:8px 0;padding:8px 10px;background:#ffffff8c;border:1px solid var(--border-color-light);border-radius:var(--radius-sm)}._askQuestionBlock_m3zk8_201+._askQuestionBlock_m3zk8_201{margin-top:10px}._askQuestionHeader_m3zk8_213{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--color-slate);margin-bottom:2px}._askQuestionTitle_m3zk8_222{font-size:12px;font-weight:600;color:var(--color-navy);margin-bottom:6px}._askOptionList_m3zk8_229{display:flex;flex-direction:column;gap:4px}._askOption_m3zk8_229{display:flex;align-items:flex-start;gap:6px;padding:4px 6px;border-radius:var(--radius-sm);cursor:pointer;font-size:12px}._askOption_m3zk8_229:hover{background:#3b82f60f}._askOption_m3zk8_229 input{margin-top:3px;flex-shrink:0}._askOptionLabel_m3zk8_254{font-weight:500;color:var(--color-navy)}._askOptionDesc_m3zk8_259{display:block;font-size:11px;color:var(--color-slate);margin-top:1px}._askOtherInput_m3zk8_266{margin-top:4px;width:100%;padding:4px 6px;font-family:inherit;font-size:12px;border:1px solid var(--border-color);border-radius:var(--radius-sm);box-sizing:border-box}._askAnswerPreview_m3zk8_277{margin:6px 0;padding:8px 10px;background:#22c55e14;border-left:3px solid var(--color-success, #22c55e);border-radius:var(--radius-sm);font-size:12px;white-space:pre-wrap;word-break:break-word}._askAnswerLabel_m3zk8_288{font-size:11px;font-weight:700;letter-spacing:.04em;color:var(--color-success, #16a34a);margin-bottom:4px}
@@ -0,0 +1,17 @@
1
+ import{r as k,j as e}from"./react-vendor-C8IxlyCR.js";import{u as le,w as ce}from"./useMonaco-ILb4vyPh.js";import{M as de,C as pe,I as ue,T as _e,B as w,i as q}from"./index-Dhu4SN1z.js";import"./vite-preload-CxJPbCTl.js";import"./markdown-CeN5IgdF.js";const he="_wrapper_pmhrh_1",me="_disabled_pmhrh_14",ke="_outer_pmhrh_19",ve="_outerChecked_pmhrh_37",fe="_dot_pmhrh_42",xe="_input_pmhrh_65",je="_label_pmhrh_81",x={wrapper:he,disabled:me,outer:ke,outerChecked:ve,dot:fe,input:xe,label:je};function ge({checked:m,onChange:v,label:f,disabled:c,value:y,id:L,className:j="",...N}){const A=k.useId(),S=L??A;return e.jsxs("label",{className:[x.wrapper,c&&x.disabled,j].filter(Boolean).join(" "),htmlFor:S,children:[e.jsx("span",{className:[x.outer,m&&x.outerChecked].filter(Boolean).join(" "),children:m&&e.jsx("span",{className:x.dot})}),f&&e.jsx("span",{className:x.label,children:f}),e.jsx("input",{id:S,type:"radio",className:x.input,checked:m,disabled:c,value:y,onChange:()=>v(y),...N})]})}const be="_approvalCard_m3zk8_1",we="_approval_accepted_m3zk8_12",ye="_approval_denied_m3zk8_18",Ne="_approval_answered_m3zk8_24",Se="_approvalHeader_m3zk8_30",ze="_approvalTitle_m3zk8_38",Le="_approvalStatus_m3zk8_43",Ae="_approvalStatus_accepted_m3zk8_52",Oe="_approvalStatus_denied_m3zk8_57",Te="_approvalStatus_answered_m3zk8_62",Ce="_approvalBody_m3zk8_67",Ee="_approvalFileList_m3zk8_80",$e="_diffSection_m3zk8_95",Be="_diffHunkLabel_m3zk8_101",He="_diffWriteLabel_m3zk8_110",Fe="_diffEditorWrap_m3zk8_119",Me="_diffTruncatedNote_m3zk8_125",We="_approvalMeta_m3zk8_132",Qe="_approvalError_m3zk8_138",De="_approvalActions_m3zk8_144",Ie="_diffEditHint_m3zk8_153",Pe="_tweakOverflowWarn_m3zk8_164",Re="_approvalFeedback_m3zk8_174",Ke="_askQuestionBlock_m3zk8_201",Ve="_askQuestionHeader_m3zk8_213",Ge="_askQuestionTitle_m3zk8_222",Je="_askOptionList_m3zk8_229",Ue="_askOption_m3zk8_229",Xe="_askOptionLabel_m3zk8_254",Ye="_askOptionDesc_m3zk8_259",Ze="_askOtherInput_m3zk8_266",qe="_askAnswerPreview_m3zk8_277",es="_askAnswerLabel_m3zk8_288",a={approvalCard:be,approval_accepted:we,approval_denied:ye,approval_answered:Ne,approvalHeader:Se,approvalTitle:ze,approvalStatus:Le,approvalStatus_accepted:Ae,approvalStatus_denied:Oe,approvalStatus_answered:Te,approvalBody:Ce,approvalFileList:Ee,diffSection:$e,diffHunkLabel:Be,diffWriteLabel:He,diffEditorWrap:Fe,diffTruncatedNote:Me,approvalMeta:We,approvalError:Qe,approvalActions:De,diffEditHint:Ie,tweakOverflowWarn:Pe,approvalFeedback:Re,askQuestionBlock:Ke,askQuestionHeader:Ve,askQuestionTitle:Ge,askOptionList:Je,askOption:Ue,askOptionLabel:Xe,askOptionDesc:Ye,askOtherInput:Ze,askAnswerPreview:qe,askAnswerLabel:es},ss={ts:"typescript",tsx:"typescript",js:"javascript",jsx:"javascript",json:"json",md:"markdown",html:"html",css:"css",scss:"scss",py:"python",go:"go",rs:"rust",sh:"shell",yaml:"yaml",yml:"yaml"};function as(m){const v=m.toLowerCase();if(v.endsWith(".module.css"))return"css";const f=v.split(".").pop()||"";return ss[f]||"plaintext"}const ts={pending:"等待审批",accepted:"已通过",denied:"已拒绝",answered:"已答复"},$="其他";function cs({event:m,issueId:v,onResolved:f}){const[c,y]=k.useState(null),[L,j]=k.useState(null),[N,A]=k.useState("idle"),[S,W]=k.useState(""),[O,ee]=k.useState({}),{ready:se}=le(),p=(()=>{try{return JSON.parse(m.metadata||"{}")}catch{return{}}})(),i=p.kind??"exec",B=p.status??"pending",T=i==="ask"&&B==="denied"?"answered":B,C=p.approvalId??"",Q=p.command,D=p.cwd,g=p.files??[],I=p.plan,P=p.resolvedBy,E=p.feedback,r=p.diff,z=p.questions??[],[H,R]=k.useState({}),[b,K]=k.useState(!1),ae=i==="exec"?"⚠️ 请求执行命令":i==="plan"?"📋 请求确认方案":i==="ask"?"❓ 请求询问用户":"⚠️ 请求修改文件",d=B==="pending",F=async(s,n)=>{if(!(!C||c)){y(s),j(null);try{await q.respondApproval(v,C,s,void 0,n),await f()}catch(t){j(t.message||`${s} failed`)}finally{y(null)}}},V=2e3,G=1800,J=()=>{const s=Object.keys(O).map(Number).sort((h,M)=>h-M);if(s.length===0||!r)return{text:"",truncated:!1};const n=`用户在审批时调整了你的提议(原 file_change 已被拒绝,请基于以下修改后的内容重新尝试):
2
+
3
+ `,t=g[0]?`[${g[0]}] `:"",l=[];for(const h of s){const M=r.hunks.length>1?`${t}Edit ${h+1}`:t.trim()||"修改后";l.push(`### ${M}
4
+ \`\`\`
5
+ ${O[h]}
6
+ \`\`\``)}const o=n+l.join(`
7
+
8
+ `);if(o.length<=V)return{text:o,truncated:!1};const u=`
9
+
10
+ [... 内容过长(总 ${o.length} 字符),仅保留前 ${G} 字符,请基于此重新生成完整修改 ...]`;return{text:o.slice(0,Math.max(0,G-u.length))+u,truncated:!0}},U=Object.keys(O).length>0,X=U?J().text.length:0,te=X>V,ne=async()=>{const{text:s}=J();s&&await F("deny",s)},Y=(s,n,t)=>{R(l=>{const o=l[s]??{choices:[],otherText:""},u=o.choices.includes(t),_=n?u?o.choices.filter(h=>h!==t):[...o.choices,t]:[t];return{...l,[s]:{...o,choices:_}}})},oe=(s,n)=>{R(t=>{const l=t[s]??{choices:[],otherText:""};return{...t,[s]:{...l,otherText:n}}})},re=()=>z.map((s,n)=>{const t=H[n]??{choices:[],otherText:""},l=t.choices.length>0?t.choices.join("、"):"(未选)",o=t.choices.includes($)&&t.otherText.trim()?`
11
+ 补充:${t.otherText.trim()}`:"",u=s.header?`[${s.header}] `:"";return`Q${n+1} ${u}${s.question}
12
+ → ${l}${o}`}).join(`
13
+
14
+ `),Z=z.length>0&&z.every((s,n)=>{const t=H[n];return!(!t||t.choices.length===0||t.choices.includes($)&&!t.otherText.trim())}),ie=async()=>{if(!(!C||b||!Z)){K(!0),j(null);try{await q.respondApproval(v,C,"deny",void 0,re()),await f()}catch(s){j(s.message||"submit failed")}finally{K(!1)}}};return e.jsxs("div",{className:`${a.approvalCard} ${a[`approval_${T}`]||""}`,children:[e.jsxs("div",{className:a.approvalHeader,children:[e.jsx("span",{className:a.approvalTitle,children:ae}),e.jsx("span",{className:`${a.approvalStatus} ${a[`approvalStatus_${T}`]||""}`,children:ts[T]||T})]}),i==="exec"&&Q&&e.jsx("pre",{className:a.approvalBody,children:Q}),i==="file_change"&&g.length>0&&e.jsxs(e.Fragment,{children:[e.jsx("ul",{className:a.approvalFileList,children:g.map((s,n)=>e.jsx("li",{children:s},n))}),d&&r&&r.hunks.length>0&&e.jsx("div",{className:a.diffEditHint,children:"右侧内容可直接编辑。改完点下方「应用修改并拒绝」把修改作为 feedback 回传给 agent。"}),r&&r.hunks.length>0&&r.hunks.map((s,n)=>e.jsxs("div",{className:a.diffSection,children:[r.hunks.length>1&&e.jsxs("div",{className:a.diffHunkLabel,children:["Edit ",n+1]}),e.jsx("div",{className:a.diffEditorWrap,children:se?e.jsx(ce,{height:Math.min(Math.max(s.old_string.split(`
15
+ `).length,s.new_string.split(`
16
+ `).length)*18+40,300),language:g[0]?as(g[0]):"plaintext",original:s.old_string,modified:O[n]??s.new_string,theme:"vs",onMount:t=>{const l=t.getModifiedEditor();l.onDidChangeModelContent(()=>{const o=l.getValue();ee(u=>{const _={...u};return o===s.new_string?delete _[n]:_[n]=o,_})})},options:{readOnly:!d,minimap:{enabled:!1},fontSize:11,renderSideBySide:!0,scrollBeyondLastLine:!1,automaticLayout:!0,wordWrap:"on",lineNumbers:"on"}}):e.jsx("div",{className:a.diffEditorWrap,style:{display:"flex",alignItems:"center",justifyContent:"center",color:"var(--color-text-muted)",fontSize:12},children:"编辑器加载中..."})})]},n)),r&&r.new_content&&e.jsxs("div",{className:a.diffSection,children:[e.jsxs("div",{className:a.diffWriteLabel,children:["写入新内容",r.truncated?" (已截断)":""]}),e.jsx("pre",{className:a.approvalBody,children:r.new_content.length>5e3?r.new_content.slice(0,5e3)+`
17
+ ... (内容过长,已截断显示)`:r.new_content})]}),(r==null?void 0:r.truncated)&&!r.new_content&&e.jsx("div",{className:a.diffTruncatedNote,children:"差异内容过长,已截断"})]}),i==="plan"&&I&&e.jsx("div",{className:a.approvalBody,children:e.jsx(de,{content:I})}),i==="ask"&&z.length>0&&e.jsxs(e.Fragment,{children:[z.map((s,n)=>{const t=H[n]??{choices:[],otherText:""},l=[...s.options,{label:$,description:"自由输入"}];return e.jsxs("div",{className:a.askQuestionBlock,children:[s.header&&e.jsx("div",{className:a.askQuestionHeader,children:s.header}),e.jsx("div",{className:a.askQuestionTitle,children:s.question}),e.jsx("div",{className:a.askOptionList,children:l.map((o,u)=>{const _=t.choices.includes(o.label),h=`ask-${m.id}-${n}`;return e.jsxs("label",{className:a.askOption,children:[s.multiSelect?e.jsx(pe,{name:h,checked:_,disabled:!d||b,onChange:()=>Y(n,s.multiSelect,o.label)}):e.jsx(ge,{name:h,value:o.label,checked:_,disabled:!d||b,onChange:()=>Y(n,s.multiSelect,o.label)}),e.jsxs("span",{children:[e.jsx("span",{className:a.askOptionLabel,children:o.label}),o.description&&e.jsx("span",{className:a.askOptionDesc,children:o.description})]})]},u)})}),t.choices.includes($)&&e.jsx(ue,{className:a.askOtherInput,type:"text",placeholder:"请输入",value:t.otherText,disabled:!d||b,onChange:o=>oe(n,o.target.value)})]},n)}),!d&&E&&e.jsxs("div",{className:a.askAnswerPreview,children:[e.jsx("div",{className:a.askAnswerLabel,children:"用户答复"}),E]})]}),i==="exec"&&D&&e.jsxs("div",{className:a.approvalMeta,children:["cwd: ",D]}),!d&&P&&e.jsxs("div",{className:a.approvalMeta,children:["由 ",P," 处理"]}),!d&&i!=="ask"&&E&&e.jsxs("div",{className:a.approvalMeta,children:["拒绝原因:",E]}),L&&e.jsx("div",{className:a.approvalError,children:L}),d&&i!=="ask"&&N==="composing"&&e.jsx(_e,{className:a.approvalFeedback,placeholder:"补充拒绝原因(可选,会回传给 Agent)",value:S,onChange:s=>W(s.target.value),disabled:c!==null,autoFocus:!0}),d&&i==="ask"&&e.jsx("div",{className:a.approvalActions,children:e.jsx(w,{variant:"primary",size:"sm",disabled:!Z||b,onClick:ie,children:b?"提交中…":"提交答复"})}),d&&i!=="ask"&&N==="idle"&&e.jsxs("div",{className:a.approvalActions,children:[e.jsx(w,{variant:"success",size:"sm",disabled:c!==null,onClick:()=>F("accept"),children:c==="accept"?"处理中…":"Accept"}),e.jsx(w,{variant:"danger",outline:!0,size:"sm",disabled:c!==null,onClick:()=>A("composing"),children:"Deny"}),U&&i==="file_change"&&e.jsx(w,{variant:"primary",size:"sm",disabled:c!==null,onClick:ne,title:"拒绝原提议,并把你在右侧编辑器里的修改作为 feedback 回传给 agent 重新生成",children:c==="deny"?"处理中…":"应用修改并拒绝"}),te&&e.jsx("span",{className:a.tweakOverflowWarn,title:`feedback 总长 ${X} 字符,后端会截到 2000`,children:"⚠ 将截断"})]}),d&&i!=="ask"&&N==="composing"&&e.jsxs("div",{className:a.approvalActions,children:[e.jsx(w,{variant:"secondary",size:"sm",disabled:c!==null,onClick:()=>{A("idle"),W("")},children:"取消"}),e.jsx(w,{variant:"danger",outline:!0,size:"sm",disabled:c!==null,onClick:()=>F("deny",S),children:c==="deny"?"处理中…":"确认拒绝"})]})]})}export{cs as ApprovalCard};
@@ -0,0 +1 @@
1
+ import{r as c,j as e,b as xe}from"./react-vendor-C8IxlyCR.js";import{u as we,F as je,w as be}from"./useMonaco-ILb4vyPh.js";import{a as S,g as ye,B as v,X as Ne,I as ce,S as Ee,M as Pe}from"./index-Dhu4SN1z.js";import"./vite-preload-CxJPbCTl.js";import"./markdown-CeN5IgdF.js";const T={async list(s){return S.get(`/artifacts/${s}`)},async getContent(s,n){return S.get(`/artifacts/${s}/content?path=${encodeURIComponent(n)}`)},async getOriginal(s,n,r="HEAD"){return S.get(`/artifacts/${s}/original?path=${encodeURIComponent(n)}&base=${encodeURIComponent(r)}`)},async getDiff(s,n,r="HEAD"){return S.get(`/artifacts/${s}/diff?path=${encodeURIComponent(n)}&base=${encodeURIComponent(r)}`)},async listRefs(s){return S.get(`/artifacts/${s}/refs`)}},Se="_terminalPane_1ut69_1",Ce="_expanded_1ut69_16",Be="_collapsed_1ut69_25",Me="_header_1ut69_30",Ie="_title_1ut69_40",Te="_status_1ut69_48",Re="_statusOk_1ut69_53",$e="_statusPending_1ut69_57",ke="_statusBad_1ut69_61",ze="_actions_1ut69_65",_={terminalPane:Se,expanded:Ce,collapsed:Be,header:Me,title:Ie,status:Te,statusOk:Re,statusPending:$e,statusBad:ke,actions:ze};function He({groupId:s}){const[n,r]=c.useState(0),[o,f]=c.useState(!0),[l,p]=c.useState(!1),[i,h]=c.useState("closed"),d=c.useMemo(()=>ye(s),[s]),g=c.useCallback(()=>{r(x=>x+1)},[]),k=i==="open"?"● 已连接":i==="connecting"?"○ 连接中":"× 已断开",C=i==="open"?_.statusOk:i==="connecting"?_.statusPending:_.statusBad;return e.jsxs("div",{className:`${_.terminalPane} ${o?_.collapsed:""} ${l?_.expanded:""}`,children:[e.jsxs("div",{className:_.header,children:[e.jsx("span",{className:_.title,children:"终端"}),e.jsx("span",{className:`${_.status} ${C}`,children:k}),e.jsxs("div",{className:_.actions,children:[i!=="open"&&e.jsx(v,{variant:"ghost",size:"sm",onClick:g,children:"重连"}),e.jsx(v,{variant:"ghost",size:"sm",onClick:()=>p(x=>!x),disabled:o,title:l?"恢复默认高度":"放大终端",children:l?"缩小":"放大"}),e.jsx(v,{variant:"ghost",size:"sm",onClick:()=>f(x=>!x),title:o?"展开终端":"折叠终端",children:o?"展开":"折叠"})]})]}),!o&&e.jsx(Ne,{url:d,connectToken:n,onStatusChange:h})]})}const Ae="_artifactPanel_11bcu_1",Le="_artifactHeader_11bcu_9",De="_artifactTitle_11bcu_19",We="_pathBar_11bcu_34",Fe="_pathBarPath_11bcu_93",Ue="_pathBarActions_11bcu_103",Oe="_searchRow_11bcu_110",Xe="_searchInput_11bcu_120",Ge="_searchMeta_11bcu_151",Ke="_fileTree_11bcu_158",qe="_fileItem_11bcu_169",Ye="_active_11bcu_187",Ve="_fileIcon_11bcu_196",Je="_fileName_11bcu_204",Qe="_fileSize_11bcu_212",Ze="_dirChildren_11bcu_220",et="_treeResizer_11bcu_231",tt="_treeResizerActive_11bcu_256",st="_splitLayout_11bcu_263",at="_treePane_11bcu_272",it="_treePaneCollapsed_11bcu_285",nt="_previewPane_11bcu_291",ct="_previewEmpty_11bcu_300",rt="_previewEmptyIcon_11bcu_315",ot="_previewEmptyHint_11bcu_321",lt="_artifactEmpty_11bcu_332",dt="_previewSection_11bcu_344",ut="_editorWrap_11bcu_353",pt="_previewHeader_11bcu_359",mt="_previewHeaderBottom_11bcu_371",ft="_mdPreviewWrap_11bcu_393",ht="_imagePreviewWrap_11bcu_402",_t="_imagePreviewMeta_11bcu_410",vt="_imagePreviewName_11bcu_421",gt="_imagePreviewSize_11bcu_427",xt="_imagePreviewHint_11bcu_431",wt="_imagePreviewStage_11bcu_438",jt="_imagePreviewImg_11bcu_456",bt="_lightbox_11bcu_471",yt="_lightboxImg_11bcu_483",Nt="_lightboxClose_11bcu_492",Et="_viewModeToggle_11bcu_515",Pt="_viewModeBtn_11bcu_524",St="_viewModeBtnActive_11bcu_540",Ct="_loadingText_11bcu_547",Bt="_previewActions_11bcu_554",Mt="_diffBaseSelect_11bcu_561",It="_diffBaseInput_11bcu_590",Tt="_diffHeader_11bcu_623",Rt="_diffRepo_11bcu_640",$t="_diffNote_11bcu_645",t={artifactPanel:Ae,artifactHeader:Le,artifactTitle:De,pathBar:We,pathBarPath:Fe,pathBarActions:Ue,searchRow:Oe,searchInput:Xe,searchMeta:Ge,fileTree:Ke,fileItem:qe,active:Ye,fileIcon:Ve,fileName:Je,fileSize:Qe,dirChildren:Ze,treeResizer:et,treeResizerActive:tt,splitLayout:st,treePane:at,treePaneCollapsed:it,previewPane:nt,previewEmpty:ct,previewEmptyIcon:rt,previewEmptyHint:ot,artifactEmpty:lt,previewSection:dt,editorWrap:ut,previewHeader:pt,previewHeaderBottom:mt,mdPreviewWrap:ft,imagePreviewWrap:ht,imagePreviewMeta:_t,imagePreviewName:vt,imagePreviewSize:gt,imagePreviewHint:xt,imagePreviewStage:wt,imagePreviewImg:jt,lightbox:bt,lightboxImg:yt,lightboxClose:Nt,viewModeToggle:Et,viewModeBtn:Pt,viewModeBtnActive:St,loadingText:Ct,previewActions:Bt,diffBaseSelect:Mt,diffBaseInput:It,diffHeader:Tt,diffRepo:Rt,diffNote:$t},kt=1,R=260,re=180,oe=520,G="rotom-artifact-tree-width";function zt(s,n){const r=n.trim().toLowerCase();if(!r)return s;const o=f=>{const l=[];for(const p of f){const i=p.name.toLowerCase().includes(r)||p.path.toLowerCase().includes(r);if(p.type==="directory"&&p.children){const h=o(p.children);(i||h.length>0)&&l.push({...p,children:h})}else p.type==="file"&&i&&l.push(p)}return l};return o(s)}function $(s){return s<1024?`${s}B`:s<1024*1024?`${(s/1024).toFixed(1)}KB`:`${(s/(1024*1024)).toFixed(1)}MB`}const Ht={ts:"typescript",tsx:"typescript",js:"javascript",jsx:"javascript",mjs:"javascript",cjs:"javascript",json:"json",md:"markdown",html:"html",htm:"html",css:"css",scss:"scss",less:"less",yaml:"yaml",yml:"yaml",toml:"ini",ini:"ini",py:"python",go:"go",rs:"rust",java:"java",kt:"kotlin",swift:"swift",c:"c",h:"c",cpp:"cpp",cc:"cpp",hpp:"cpp",cs:"csharp",php:"php",rb:"ruby",sh:"shell",bash:"shell",zsh:"shell",sql:"sql",xml:"xml",vue:"html",svelte:"html"},At=/\.(md|markdown)$/i,Lt=/\.(png|jpe?g|gif|webp|svg|ico|bmp)$/i,Dt={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",ico:"image/x-icon",bmp:"image/bmp"};function Wt(s){const n=s.toLowerCase();if(n.endsWith(".module.css"))return"css";const r=n.split(".").pop()||"";return Ht[r]||"plaintext"}function Ft(s){return At.test(s)}function le(s){return Lt.test(s)}function Ut(s,n){if(!le(s))return null;const r=s.toLowerCase().split(".").pop()||"",o=Dt[r];return o?n.type==="binary"?`data:${o};base64,${n.content}`:o==="image/svg+xml"&&n.type==="text"?`data:${o};utf8,${encodeURIComponent(n.content)}`:null:null}function Ot({name:s,src:n,size:r}){const[o,f]=c.useState(!1);return c.useEffect(()=>{if(!o)return;const l=i=>{i.key==="Escape"&&f(!1)};window.addEventListener("keydown",l);const p=document.body.style.overflow;return document.body.style.overflow="hidden",()=>{window.removeEventListener("keydown",l),document.body.style.overflow=p}},[o]),e.jsxs("div",{className:t.imagePreviewWrap,children:[e.jsxs("div",{className:t.imagePreviewMeta,children:[e.jsx("span",{className:t.imagePreviewName,children:s}),e.jsx("span",{className:t.imagePreviewSize,children:$(r)}),e.jsx("span",{className:t.imagePreviewHint,children:"点击图片放大"})]}),e.jsx("div",{className:t.imagePreviewStage,children:e.jsx("img",{src:n,alt:s,className:t.imagePreviewImg,loading:"lazy",onClick:()=>f(!0)})}),o&&xe.createPortal(e.jsxs("div",{className:t.lightbox,onClick:()=>f(!1),role:"dialog","aria-modal":"true","aria-label":s,children:[e.jsx("img",{src:n,alt:s,className:t.lightboxImg,onClick:l=>l.stopPropagation()}),e.jsx("button",{type:"button",className:t.lightboxClose,onClick:()=>f(!1),"aria-label":"关闭",children:"✕"})]}),document.body)]})}function Xt(s){var r;const n=((r=s.split(".").pop())==null?void 0:r.toLowerCase())||"";return["tsx","ts","jsx","js"].includes(n)?"📄":["css","scss","less"].includes(n)||s.endsWith(".module.css")?"🎨":["json","yaml","yml","toml"].includes(n)?"📦":["md","txt","doc"].includes(n)?"📝":["png","jpg","jpeg","gif","svg"].includes(n)?"🖼":"📄"}function de(s,n){for(const r of s){if(r.path===n)return r;if(r.type==="directory"&&r.children){const o=de(r.children,n);if(o)return o}}return null}function ue({file:s,selectedPath:n,onSelect:r,depth:o,forceExpand:f=!1}){const l=s.type==="directory",[p,i]=c.useState(()=>l&&o<kt),h=s.path===n,d=f||p;return e.jsxs("li",{children:[e.jsxs("div",{className:`${t.fileItem} ${h?t.active:""}`,style:{paddingLeft:10+o*12},onClick:()=>{l?i(!d):r(s)},children:[l?e.jsx("span",{className:t.fileIcon,children:d?"📂":"📁"}):e.jsx("span",{className:t.fileIcon,children:Xt(s.name)}),e.jsx("span",{className:t.fileName,children:s.name}),!l&&e.jsx("span",{className:t.fileSize,children:$(s.size)})]}),l&&d&&s.children&&e.jsx("ul",{className:t.dirChildren,children:s.children.map(g=>e.jsx(ue,{file:g,selectedPath:n,onSelect:r,depth:o+1,forceExpand:f},g.path))})]})}function Jt({groupId:s,selectedPath:n,onSelectedPathChange:r}){const[o,f]=c.useState(null),[l,p]=c.useState([]),[i,h]=c.useState(null),[d,g]=c.useState(null),[k,C]=c.useState(!0),[x,K]=c.useState(!1),[w,y]=c.useState(null),[q,Y]=c.useState(!1),[j,V]=c.useState(""),[u,J]=c.useState(null),[N,Q]=c.useState(""),[pe,z]=c.useState(!1),[B,b]=c.useState("view"),[E,me]=c.useState(!1),[H,Z]=c.useState(()=>{try{const a=localStorage.getItem(G),m=a?Number(a):NaN;return Number.isFinite(m)&&m>=re&&m<=oe?m:R}catch{return R}}),[A,ee]=c.useState(!1),te=c.useRef(null);c.useEffect(()=>{if(!A)return;const a=ge=>{const X=te.current;if(!X)return;const ne=Math.max(re,Math.min(oe,X.w+(ge.clientX-X.x)));Z(ne);try{localStorage.setItem(G,String(ne))}catch{}},m=()=>ee(!1);return window.addEventListener("mousemove",a),window.addEventListener("mouseup",m),document.body.style.cursor="col-resize",document.body.style.userSelect="none",()=>{window.removeEventListener("mousemove",a),window.removeEventListener("mouseup",m),document.body.style.cursor="",document.body.style.userSelect=""}},[A]);const[L,D]=c.useState("preview"),{ready:fe}=we(),W=c.useCallback(async()=>{try{C(!0);const a=await T.list(s);f(a.root),p(a.files??[])}catch(a){console.error("Failed to load artifacts:",a)}finally{C(!1)}},[s]);c.useEffect(()=>{W(),h(null),g(null),y(null),b("view"),T.listRefs(s).then(J).catch(()=>J(null))},[s,W]);const F=c.useCallback(async a=>{h(a),K(!0),y(null),b("view"),D("preview"),r==null||r(a.path);try{const m=await T.getContent(s,a.path);g(m)}catch(m){console.error("Failed to load file content:",m),g(null)}finally{K(!1)}},[s,r]);c.useEffect(()=>{if(!n||l.length===0||(i==null?void 0:i.path)===n)return;const a=de(l,n);a&&F(a)},[n,l,i,F]);const M=c.useMemo(()=>zt(l,N),[l,N]),U=N.trim().length>0,I=c.useMemo(()=>{if(!i||!o)return"";if(i.absPath)return i.absPath;const a=o.endsWith("/")?"":"/";return`${o}${a}${i.path}`},[i,o]),he=c.useCallback(async()=>{if(!i)return;const a=I||i.path;try{await navigator.clipboard.writeText(a),z(!0),window.setTimeout(()=>z(!1),1500)}catch{z(!1)}},[i,I]),se=async()=>{if(i){Y(!0);try{const a=await T.getOriginal(s,i.path,j||"HEAD");y(a),b("diff")}catch(a){console.error("Failed to load original:",a),y({path:i.path,base:j,repoRoot:null,content:"",note:`获取原始内容失败: ${a instanceof Error?a.message:String(a)}`}),b("diff")}finally{Y(!1)}}},ae=c.useMemo(()=>i?Wt(i.path):"plaintext",[i]),ie=i?Ft(i.path):!1,P=i?le(i.path):!1,O=c.useMemo(()=>P&&d?Ut(i.path,d):null,[P,d,i]),_e=P&&!O,ve=(d==null?void 0:d.type)==="binary"&&!P;return e.jsxs("div",{className:t.artifactPanel,children:[e.jsxs("div",{className:t.artifactHeader,children:[e.jsxs("h3",{className:t.artifactTitle,children:["📦"," Artifacts & repos"]}),e.jsxs("div",{className:t.previewActions,children:[e.jsx(v,{variant:"ghost",size:"sm",onClick:()=>me(a=>!a),title:E?"展开文件树":"收起文件树(让位给预览)",children:E?"▶":"◀"}),e.jsx(v,{variant:"ghost",size:"sm",onClick:W,children:"刷新"})]})]}),o&&e.jsxs("div",{className:t.pathBar,children:[e.jsx("span",{className:t.pathBarPath,title:i?I:o,children:i?I:o}),i&&e.jsxs("div",{className:t.pathBarActions,children:[e.jsx(v,{variant:"ghost",size:"sm",onClick:he,title:"复制绝对路径",children:pe?"已复制":"复制"}),e.jsx(v,{variant:"ghost",size:"sm",onClick:()=>{h(null),g(null),y(null),b("view"),r==null||r(null)},title:"关闭预览",children:"关闭"})]})]}),e.jsxs("div",{className:t.splitLayout,children:[e.jsxs("div",{className:`${t.treePane} ${E?t.treePaneCollapsed:""}`,style:E?void 0:{width:`${H}px`,flex:`0 0 ${H}px`},children:[l.length>0&&e.jsxs("div",{className:t.searchRow,children:[e.jsx(ce,{className:t.searchInput,value:N,onChange:a=>Q(a.target.value),placeholder:"搜索文件名或路径…",type:"search",autoComplete:"off",spellCheck:!1}),U&&e.jsx("span",{className:t.searchMeta,children:M.length===0?"无匹配":`${M.length} 项`})]}),k?e.jsx("div",{className:t.loadingText,children:"加载中..."}):l.length===0?e.jsx("div",{className:t.artifactEmpty,children:e.jsxs("div",{children:[e.jsx("p",{children:"暂无产物文件"}),e.jsx("p",{style:{fontSize:12,marginTop:4},children:o||"~/.rotom/artifacts/"})]})}):U&&M.length===0?e.jsx("div",{className:t.artifactEmpty,children:e.jsxs("div",{children:[e.jsxs("p",{children:["未找到匹配「",N,"」的文件"]}),e.jsx(v,{variant:"ghost",size:"sm",onClick:()=>Q(""),style:{marginTop:8},children:"清空搜索"})]})}):e.jsx("ul",{className:t.fileTree,children:M.map(a=>e.jsx(ue,{file:a,selectedPath:(i==null?void 0:i.path)??null,onSelect:F,depth:0,forceExpand:U},a.path))})]}),!E&&e.jsx("div",{className:`${t.treeResizer} ${A?t.treeResizerActive:""}`,onMouseDown:a=>{a.preventDefault(),te.current={x:a.clientX,w:H},ee(!0)},onDoubleClick:()=>{Z(R);try{localStorage.setItem(G,String(R))}catch{}},title:"拖拽调整目录树宽度,双击恢复默认"}),e.jsxs("div",{className:t.previewPane,children:[i&&e.jsxs("div",{className:t.previewSection,children:[e.jsx("div",{className:t.previewHeader,children:e.jsx("div",{className:t.previewHeaderBottom,children:B==="diff"?e.jsx(v,{variant:"secondary",size:"sm",onClick:()=>b("view"),title:"回到查看模式",children:"查看"}):e.jsxs(e.Fragment,{children:[ie&&e.jsxs("div",{className:t.viewModeToggle,role:"group","aria-label":"预览模式",children:[e.jsx("button",{type:"button",className:`${t.viewModeBtn} ${L==="preview"?t.viewModeBtnActive:""}`,onClick:()=>D("preview"),children:"渲染"}),e.jsx("button",{type:"button",className:`${t.viewModeBtn} ${L==="source"?t.viewModeBtnActive:""}`,onClick:()=>D("source"),children:"源码"})]}),e.jsxs(Ee,{className:t.diffBaseSelect,size:"sm",value:u!=null&&u.heads.includes(j)||j===""||j==="HEAD"?j:"__custom__",onChange:a=>{const m=a.target.value;m!=="__custom__"&&V(m)},title:"选择常用 git ref(分支/tag/HEAD)",children:[e.jsx("option",{value:"",children:"HEAD(默认)"}),u==null?void 0:u.heads.map(a=>e.jsxs("option",{value:a,children:[a,a===u.head?" (当前)":""]},a)),u&&u.tags.length>0&&e.jsx("optgroup",{label:"tags",children:u.tags.map(a=>e.jsx("option",{value:`tags/${a}`,children:a},a))}),!(u!=null&&u.note)&&u&&u.refs.length===0&&e.jsx("option",{value:"",disabled:!0,children:"仓库无分支"}),(u==null?void 0:u.note)&&e.jsx("option",{value:"",disabled:!0,children:u.note}),e.jsx("option",{value:"__custom__",children:"自定义…"})]}),e.jsx(ce,{className:t.diffBaseInput,size:"sm",value:j,onChange:a=>V(a.target.value),onKeyDown:a=>{a.key==="Enter"&&se()},placeholder:"commit / 分支 / tag",title:"对比基准 (git ref / commit / 分支),回车发起对比"}),e.jsx(v,{variant:"secondary",size:"sm",onClick:se,disabled:q,children:q?"加载中...":"对比"})]})})}),x?e.jsx("div",{className:t.loadingText,children:"加载中..."}):_e?e.jsxs("div",{className:t.diffNote,children:["图片格式不支持预览 (",$(d.size),")。"]}):P&&O&&d?e.jsx(Ot,{name:i.name,src:O,size:d.size}):ve?e.jsxs("div",{className:t.diffNote,children:["二进制文件 (",$(d.size),"),无法直接预览。"]}):ie&&L==="preview"&&B==="view"&&d?e.jsx("div",{className:t.mdPreviewWrap,children:e.jsx(Pe,{content:d.content})}):fe?B==="view"&&d?e.jsx("div",{className:t.editorWrap,children:e.jsx(je,{height:"100%",language:ae,value:d.content,theme:"vs",options:{readOnly:!0,minimap:{enabled:!1},fontSize:12,scrollBeyondLastLine:!1,automaticLayout:!0,renderWhitespace:"selection",wordWrap:"on"}})}):B==="diff"&&d&&w?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:t.diffHeader,children:["Diff vs ",e.jsx("code",{children:w.base}),w.repoRoot?e.jsxs("span",{className:t.diffRepo,children:[" (repo: ",w.repoRoot,")"]}):null,w.note?e.jsxs("span",{className:t.diffRepo,children:[" · ",w.note]}):null]}),e.jsx("div",{className:t.editorWrap,children:e.jsx(be,{height:"100%",language:ae,original:w.content,modified:d.content,theme:"vs",options:{readOnly:!0,minimap:{enabled:!1},fontSize:12,renderSideBySide:!0,scrollBeyondLastLine:!1,automaticLayout:!0,wordWrap:"on"}})})]}):null:e.jsx("div",{className:t.loadingText,children:"编辑器加载中..."})]}),!i&&e.jsxs("div",{className:t.previewEmpty,children:[e.jsx("div",{className:t.previewEmptyIcon,children:"📄"}),e.jsx("p",{children:"从左侧选择文件预览"}),e.jsx("p",{className:t.previewEmptyHint,children:"支持 .md 渲染、图片直接预览、Monaco 代码高亮、diff 对比"})]})]})]}),e.jsx(He,{groupId:s})]})}export{Jt as ArtifactPanel};