@rexvane/piweb 0.3.16 → 0.3.17

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 (288) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +8 -11
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +6 -6
  5. package/.next/react-loadable-manifest.json +4 -16
  6. package/.next/required-server-files.js +1 -3
  7. package/.next/required-server-files.json +1 -3
  8. package/.next/routes-manifest.json +0 -20
  9. package/.next/server/app/_global-error/page.js +2 -2
  10. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/server/app/_not-found/page.js +1 -1
  17. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +4 -7
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +4 -7
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -5
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -5
  23. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  24. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  25. package/.next/server/app/api/agent/[id]/route.js +1 -1
  26. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  27. package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
  28. package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
  29. package/.next/server/app/api/agent/new/route.js +1 -1
  30. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  31. package/.next/server/app/api/config/route.js +1 -1
  32. package/.next/server/app/api/config/route.js.nft.json +1 -1
  33. package/.next/server/app/api/files/route.js +1 -1
  34. package/.next/server/app/api/files/route.js.nft.json +1 -1
  35. package/.next/server/app/api/git/route.js +2 -2
  36. package/.next/server/app/api/git/route.js.nft.json +1 -1
  37. package/.next/server/app/api/growth/route.js +1 -1
  38. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  39. package/.next/server/app/api/health/route.js +1 -1
  40. package/.next/server/app/api/models/route.js +1 -1
  41. package/.next/server/app/api/models/route.js.nft.json +1 -1
  42. package/.next/server/app/api/pi-settings/route.js +1 -1
  43. package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
  44. package/.next/server/app/api/plugins/route.js +1 -1
  45. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  46. package/.next/server/app/api/prompts/route.js +1 -1
  47. package/.next/server/app/api/prompts/route.js.nft.json +1 -1
  48. package/.next/server/app/api/security/route.js +1 -1
  49. package/.next/server/app/api/security/route.js.nft.json +1 -1
  50. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  51. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  52. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  53. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  54. package/.next/server/app/api/sessions/import/route.js +3 -3
  55. package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
  56. package/.next/server/app/api/sessions/route.js +1 -1
  57. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  58. package/.next/server/app/api/skills/route.js +2 -2
  59. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  60. package/.next/server/app/api/update/route.js +2 -2
  61. package/.next/server/app/api/update/route.js.nft.json +1 -1
  62. package/.next/server/app/api/version/route.js +1 -1
  63. package/.next/server/app/api/version/route.js.nft.json +1 -1
  64. package/.next/server/app/api/web-auth/route.js +1 -1
  65. package/.next/server/app/api/workspaces/route.js +1 -1
  66. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  67. package/.next/server/app/icon.svg/route.js +1 -1
  68. package/.next/server/app/index.html +1 -1
  69. package/.next/server/app/index.rsc +6 -9
  70. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -7
  71. package/.next/server/app/index.segments/_full.segment.rsc +6 -9
  72. package/.next/server/app/index.segments/_tree.segment.rsc +2 -5
  73. package/.next/server/app/login/page.js +1 -1
  74. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/login.html +1 -1
  76. package/.next/server/app/login.rsc +5 -8
  77. package/.next/server/app/login.segments/_full.segment.rsc +5 -8
  78. package/.next/server/app/login.segments/_tree.segment.rsc +2 -5
  79. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -6
  80. package/.next/server/app/page.js +1 -1
  81. package/.next/server/app/page_client-reference-manifest.js +1 -1
  82. package/.next/server/app-paths-manifest.json +8 -11
  83. package/.next/server/chunks/496.js +1 -1
  84. package/.next/server/chunks/532.js +1 -1
  85. package/.next/server/chunks/686.js +4 -8
  86. package/.next/server/middleware-build-manifest.js +1 -1
  87. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  88. package/.next/server/pages/404.html +1 -1
  89. package/.next/server/pages/500.html +1 -1
  90. package/.next/server/server-reference-manifest.json +1 -1
  91. package/.next/static/chunks/2608.ec8ff1f5767c9056.js +1 -0
  92. package/.next/static/chunks/2716.93be8ad5c970c516.js +1 -0
  93. package/.next/static/chunks/4632.112d2e893d48d188.js +1 -0
  94. package/.next/static/chunks/4673.c1fb1580efc6ace7.js +1 -0
  95. package/.next/static/chunks/4686-f4a037d609cea3f2.js +1 -0
  96. package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +1 -0
  97. package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +1 -0
  98. package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +1 -0
  99. package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +1 -0
  100. package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +1 -0
  101. package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +1 -0
  102. package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +1 -0
  103. package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +1 -0
  104. package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +1 -0
  105. package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +1 -0
  106. package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +1 -0
  107. package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +1 -0
  108. package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +1 -0
  109. package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +1 -0
  110. package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +1 -0
  111. package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +1 -0
  112. package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +1 -0
  113. package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +1 -0
  114. package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +1 -0
  115. package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +1 -0
  116. package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +1 -0
  117. package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +1 -0
  118. package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +1 -0
  119. package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +1 -0
  120. package/.next/static/chunks/app/page-b51087831582ee37.js +1 -0
  121. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +1 -0
  122. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +1 -0
  123. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +1 -0
  124. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +1 -0
  125. package/.next/static/chunks/webpack-c31263fd83d75ce6.js +1 -0
  126. package/.next/static/css/850c2fd130ffa2fc.css +1 -0
  127. package/.next/static/zsynuO_9Kx6nrXgkTUJ04/_buildManifest.js +1 -0
  128. package/.next/types/routes.d.ts +1 -4
  129. package/.next/types/validator.ts +0 -27
  130. package/README.md +17 -29
  131. package/assets/archive.jpg +0 -0
  132. package/assets/growth-1.jpg +0 -0
  133. package/assets/growth-2.jpg +0 -0
  134. package/assets/import.jpg +0 -0
  135. package/assets/main.jpg +0 -0
  136. package/assets/outline.jpg +0 -0
  137. package/assets/prompts-1.jpg +0 -0
  138. package/assets/prompts-2.jpg +0 -0
  139. package/assets/providers-1.jpg +0 -0
  140. package/assets/providers-2.jpg +0 -0
  141. package/assets/providers-3.jpg +0 -0
  142. package/assets/trace-1.jpg +0 -0
  143. package/assets/trace-2.png +0 -0
  144. package/assets/trace-3.png +0 -0
  145. package/assets/trace-4.png +0 -0
  146. package/assets/viewer-1.jpg +0 -0
  147. package/assets/viewer-2.jpg +0 -0
  148. package/bin/pi.js +50 -0
  149. package/next.config.ts +32 -7
  150. package/package.json +4 -9
  151. package/scripts/launcher.mjs +1 -1
  152. package/scripts/pick-folder.ps1 +2 -11
  153. package/scripts/release.mjs +31 -11
  154. package/scripts/verify-package.mjs +0 -53
  155. package/src/app/api/config/route.ts +1 -4
  156. package/src/app/api/growth/route.ts +14 -14
  157. package/src/app/api/models/route.ts +2 -1
  158. package/src/app/api/security/route.ts +3 -8
  159. package/src/app/api/update/route.ts +2 -8
  160. package/src/app/globals.css +90 -777
  161. package/src/app/layout.tsx +6 -13
  162. package/src/components/AppShell.tsx +229 -521
  163. package/src/components/ChatInput.tsx +26 -153
  164. package/src/components/ChatWindow.tsx +231 -51
  165. package/src/components/ContextMeter.tsx +17 -11
  166. package/src/components/FileViewer.tsx +11 -10
  167. package/src/components/GitPanel.tsx +11 -13
  168. package/src/components/ModelSelector.tsx +6 -19
  169. package/src/components/PiMark.tsx +38 -5
  170. package/src/components/ProjectPanel.tsx +111 -95
  171. package/src/components/PromptPanel.tsx +51 -132
  172. package/src/components/SessionImportSection.tsx +37 -53
  173. package/src/components/SessionSidebar.tsx +57 -159
  174. package/src/components/SettingsPanel.tsx +193 -330
  175. package/src/components/icons.tsx +0 -23
  176. package/src/hooks/useGrowth.ts +123 -156
  177. package/src/hooks/usePiWeb.ts +15 -122
  178. package/src/i18n.tsx +42 -98
  179. package/src/lib/agent-manager.ts +21 -187
  180. package/src/lib/command-validation.ts +1 -9
  181. package/src/lib/growth-service.ts +390 -166
  182. package/src/lib/growth-tracker.ts +64 -264
  183. package/src/lib/growth-turns.ts +31 -1
  184. package/src/lib/models/model-scope.ts +15 -0
  185. package/src/lib/models-service.ts +16 -0
  186. package/src/lib/pi-settings.ts +7 -0
  187. package/src/lib/pi.ts +2 -3
  188. package/src/lib/session-import/claude.ts +2 -13
  189. package/src/lib/session-import/codex.ts +8 -44
  190. package/src/lib/session-import/files.ts +0 -14
  191. package/src/lib/session-import/grok.ts +24 -54
  192. package/src/lib/session-import/index.ts +0 -5
  193. package/src/lib/session-import/types.ts +0 -12
  194. package/src/lib/session-import/writer.ts +3 -8
  195. package/src/lib/skills-service.ts +9 -16
  196. package/src/lib/theme.ts +1 -68
  197. package/src/lib/trajectory.ts +0 -1
  198. package/src/lib/types.ts +27 -36
  199. package/tsconfig.json +2 -1
  200. package/.next/server/app/api/swarm/[id]/route.js +0 -1
  201. package/.next/server/app/api/swarm/[id]/route.js.nft.json +0 -1
  202. package/.next/server/app/api/swarm/[id]/route_client-reference-manifest.js +0 -1
  203. package/.next/server/app/api/swarm/route.js +0 -1
  204. package/.next/server/app/api/swarm/route.js.nft.json +0 -1
  205. package/.next/server/app/api/swarm/route_client-reference-manifest.js +0 -1
  206. package/.next/server/app/api/tests/route.js +0 -2
  207. package/.next/server/app/api/tests/route.js.nft.json +0 -1
  208. package/.next/server/app/api/tests/route_client-reference-manifest.js +0 -1
  209. package/.next/server/chunks/757.js +0 -1
  210. package/.next/server/chunks/775.js +0 -3
  211. package/.next/static/0t1jBUAuVaTFTXxRyIo3q/_buildManifest.js +0 -1
  212. package/.next/static/chunks/2608.43c58236c9cce8e6.js +0 -1
  213. package/.next/static/chunks/2716.4ec42df1d51354c9.js +0 -1
  214. package/.next/static/chunks/4632.62139b565b657aac.js +0 -1
  215. package/.next/static/chunks/4673.7796104395deb8d7.js +0 -1
  216. package/.next/static/chunks/4686-17f21a006739a596.js +0 -1
  217. package/.next/static/chunks/7577.10af9bb661652da8.js +0 -1
  218. package/.next/static/chunks/8866.38ec4ac4ee5a1b74.js +0 -1
  219. package/.next/static/chunks/app/_global-error/page-5148173b3f55e415.js +0 -1
  220. package/.next/static/chunks/app/api/agent/[id]/events/route-5148173b3f55e415.js +0 -1
  221. package/.next/static/chunks/app/api/agent/[id]/route-5148173b3f55e415.js +0 -1
  222. package/.next/static/chunks/app/api/agent/[id]/tree/route-5148173b3f55e415.js +0 -1
  223. package/.next/static/chunks/app/api/agent/new/route-5148173b3f55e415.js +0 -1
  224. package/.next/static/chunks/app/api/config/route-5148173b3f55e415.js +0 -1
  225. package/.next/static/chunks/app/api/files/route-5148173b3f55e415.js +0 -1
  226. package/.next/static/chunks/app/api/git/route-5148173b3f55e415.js +0 -1
  227. package/.next/static/chunks/app/api/growth/route-5148173b3f55e415.js +0 -1
  228. package/.next/static/chunks/app/api/health/route-5148173b3f55e415.js +0 -1
  229. package/.next/static/chunks/app/api/models/route-5148173b3f55e415.js +0 -1
  230. package/.next/static/chunks/app/api/pi-settings/route-5148173b3f55e415.js +0 -1
  231. package/.next/static/chunks/app/api/plugins/route-5148173b3f55e415.js +0 -1
  232. package/.next/static/chunks/app/api/prompts/route-5148173b3f55e415.js +0 -1
  233. package/.next/static/chunks/app/api/security/route-5148173b3f55e415.js +0 -1
  234. package/.next/static/chunks/app/api/sessions/[id]/export/route-5148173b3f55e415.js +0 -1
  235. package/.next/static/chunks/app/api/sessions/[id]/route-5148173b3f55e415.js +0 -1
  236. package/.next/static/chunks/app/api/sessions/import/route-5148173b3f55e415.js +0 -1
  237. package/.next/static/chunks/app/api/sessions/route-5148173b3f55e415.js +0 -1
  238. package/.next/static/chunks/app/api/skills/route-5148173b3f55e415.js +0 -1
  239. package/.next/static/chunks/app/api/swarm/[id]/route-5148173b3f55e415.js +0 -1
  240. package/.next/static/chunks/app/api/swarm/route-5148173b3f55e415.js +0 -1
  241. package/.next/static/chunks/app/api/tests/route-5148173b3f55e415.js +0 -1
  242. package/.next/static/chunks/app/api/update/route-5148173b3f55e415.js +0 -1
  243. package/.next/static/chunks/app/api/version/route-5148173b3f55e415.js +0 -1
  244. package/.next/static/chunks/app/api/web-auth/route-5148173b3f55e415.js +0 -1
  245. package/.next/static/chunks/app/api/workspaces/route-5148173b3f55e415.js +0 -1
  246. package/.next/static/chunks/app/page-14379b8060d5cf07.js +0 -1
  247. package/.next/static/chunks/next/dist/client/components/builtin/app-error-5148173b3f55e415.js +0 -1
  248. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-5148173b3f55e415.js +0 -1
  249. package/.next/static/chunks/next/dist/client/components/builtin/not-found-5148173b3f55e415.js +0 -1
  250. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-5148173b3f55e415.js +0 -1
  251. package/.next/static/chunks/webpack-d1a69ad9edb38b11.js +0 -1
  252. package/.next/static/css/d0a0162cb481abe8.css +0 -1
  253. package/.next/types/app/api/swarm/[id]/route.ts +0 -353
  254. package/.next/types/app/api/swarm/route.ts +0 -353
  255. package/.next/types/app/api/tests/route.ts +0 -353
  256. package/assets/archive.png +0 -0
  257. package/assets/growth-1.png +0 -0
  258. package/assets/growth-2.png +0 -0
  259. package/assets/import.png +0 -0
  260. package/assets/main.png +0 -0
  261. package/assets/outline.png +0 -0
  262. package/assets/prompts-1.png +0 -0
  263. package/assets/prompts-2.png +0 -0
  264. package/assets/providers-1.png +0 -0
  265. package/assets/providers-2.png +0 -0
  266. package/assets/providers-3.png +0 -0
  267. package/assets/trace-1.png +0 -0
  268. package/assets/viewer-1.png +0 -0
  269. package/assets/viewer-2.png +0 -0
  270. package/scripts/managed-niubash-assets.mjs +0 -146
  271. package/scripts/prepare-niubash-packages.mjs +0 -182
  272. package/scripts/run-test-file.mjs +0 -34
  273. package/scripts/smoke-managed-niubash.mjs +0 -62
  274. package/scripts/verify-release-config.mjs +0 -24
  275. package/src/app/api/swarm/[id]/route.ts +0 -28
  276. package/src/app/api/swarm/route.ts +0 -24
  277. package/src/app/api/tests/route.ts +0 -27
  278. package/src/components/SwarmCoordinatorPod.tsx +0 -1021
  279. package/src/components/TestInspectorPod.tsx +0 -549
  280. package/src/lib/delegation.ts +0 -93
  281. package/src/lib/growth-rounds.ts +0 -84
  282. package/src/lib/managed-niubash.ts +0 -231
  283. package/src/lib/runtime-activity.ts +0 -27
  284. package/src/lib/session-import/lock.ts +0 -25
  285. package/src/lib/swarm-service.ts +0 -371
  286. package/src/lib/test-run-service.ts +0 -122
  287. package/src/lib/workflow.ts +0 -55
  288. /package/.next/static/{0t1jBUAuVaTFTXxRyIo3q → zsynuO_9Kx6nrXgkTUJ04}/_ssgManifest.js +0 -0
@@ -1,1021 +0,0 @@
1
- "use client";
2
-
3
- import React, { useState, useEffect, useCallback, useRef } from "react";
4
- import { DiffView, diffStats, parseUnifiedDiff } from "@/components/DiffView";
5
- import {
6
- IconWorkflowAgent16,
7
- IconBranchOutline16,
8
- IconRefreshOutline14,
9
- IconTrashOutline16,
10
- IconPlusOutline16,
11
- IconCheckOutline14,
12
- IconWarningOutline16,
13
- IconClockOutline16,
14
- IconFileOutline16,
15
- IconShieldOutline16,
16
- IconStopFill16,
17
- } from "@/components/icons";
18
-
19
- export interface SwarmTaskInput {
20
- title: string;
21
- instruction: string;
22
- }
23
-
24
- export interface SwarmTask extends SwarmTaskInput {
25
- status: "pending" | "running" | "completed" | "failed" | "cancelled";
26
- report?: string;
27
- patch?: string;
28
- error?: string;
29
- accepted?: boolean;
30
- }
31
-
32
- export interface SwarmJob {
33
- id: string;
34
- cwd: string;
35
- root: string;
36
- base: string;
37
- status: "running" | "completed" | "failed" | "cancelled" | "interrupted";
38
- createdAt: number;
39
- finishedAt?: number;
40
- tasks: SwarmTask[];
41
- }
42
-
43
- export interface GitInfoData {
44
- available: boolean;
45
- isRepo: boolean;
46
- root: string | null;
47
- branch: string | null;
48
- files: Array<{ path: string; kind: string }>;
49
- statusError?: string;
50
- }
51
-
52
- export interface SwarmCoordinatorPodProps {
53
- cwd: string;
54
- onClose?: () => void;
55
- onSwitchToWorkbench?: () => void;
56
- projectTrust?: { required: boolean; trusted: boolean; source?: string } | null;
57
- onSetProjectTrust?: (trusted: boolean) => Promise<void>;
58
- onWorkspaceChanged?: () => void;
59
- }
60
-
61
- const sameWorkspace = (a: string, b: string) =>
62
- a.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase() === b.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
63
-
64
- export function SwarmCoordinatorPod({
65
- cwd,
66
- onClose,
67
- onSwitchToWorkbench,
68
- projectTrust,
69
- onSetProjectTrust,
70
- onWorkspaceChanged,
71
- }: SwarmCoordinatorPodProps) {
72
- // 1. Task form inputs (1 - 3 tasks)
73
- const [taskInputs, setTaskInputs] = useState<SwarmTaskInput[]>([{ title: "", instruction: "" }]);
74
-
75
- // 2. Git & Readiness State
76
- const [gitSnapshot, setGitSnapshot] = useState<{ cwd: string; data: GitInfoData } | null>(null);
77
- const [gitLoading, setGitLoading] = useState(false);
78
- const [gitError, setGitError] = useState<string | null>(null);
79
-
80
- // 3. Swarm Jobs & History
81
- const [historyJobs, setHistoryJobs] = useState<SwarmJob[]>([]);
82
- const [historyLoading, setHistoryLoading] = useState(false);
83
- const [activeJobId, setActiveJobId] = useState<string | null>(null);
84
- const [activeTab, setActiveTab] = useState<"active" | "history">("active");
85
-
86
- // 4. Execution & Operation States
87
- const [isLaunching, setIsLaunching] = useState(false);
88
- const [cancelling, setCancelling] = useState(false);
89
- const [acceptingIndex, setAcceptingIndex] = useState<number | null>(null);
90
- const [operationError, setOperationError] = useState<string | null>(null);
91
- const [expandedDiffs, setExpandedDiffs] = useState<Record<number, boolean>>({});
92
- const [fetchedTrust, setFetchedTrust] = useState<{ cwd: string; required: boolean; trusted: boolean; source?: string } | null>(null);
93
- const [trustError, setTrustError] = useState<string | null>(null);
94
- const [trustBusy, setTrustBusy] = useState(false);
95
- const activeCwdRef = useRef(cwd);
96
- activeCwdRef.current = cwd;
97
- const gitRequestRef = useRef(0);
98
- const historyRequestRef = useRef(0);
99
-
100
- const gitInfo = gitSnapshot?.cwd === cwd ? gitSnapshot.data : null;
101
- const trustCwd = gitInfo?.isRepo && gitInfo.root ? gitInfo.root : cwd;
102
- const trustState = fetchedTrust && sameWorkspace(fetchedTrust.cwd, trustCwd)
103
- ? fetchedTrust
104
- : sameWorkspace(trustCwd, cwd) ? projectTrust : null;
105
- const activeTrustCwdRef = useRef(trustCwd);
106
- activeTrustCwdRef.current = trustCwd;
107
- const visibleJobs = historyJobs.filter((job) => sameWorkspace(job.cwd, cwd));
108
-
109
- const fetchTrustStatus = useCallback(async () => {
110
- if (!trustCwd) return;
111
- const requestedCwd = cwd;
112
- const requestedTrustCwd = trustCwd;
113
- try {
114
- const res = await fetch(`/api/security?cwd=${encodeURIComponent(requestedTrustCwd)}`);
115
- const json = await res.json();
116
- if (!res.ok || !json.success) throw new Error(json.error || "读取项目信任状态失败");
117
- if (activeCwdRef.current === requestedCwd && sameWorkspace(activeTrustCwdRef.current, requestedTrustCwd)) {
118
- setFetchedTrust({ cwd: requestedTrustCwd, ...json.data });
119
- setTrustError(null);
120
- }
121
- } catch (error) {
122
- if (activeCwdRef.current === requestedCwd && sameWorkspace(activeTrustCwdRef.current, requestedTrustCwd)) setTrustError(error instanceof Error ? error.message : "读取项目信任状态失败");
123
- }
124
- }, [cwd, trustCwd]);
125
-
126
- useEffect(() => {
127
- setTrustError(null);
128
- if (!projectTrust || !sameWorkspace(trustCwd, cwd)) void fetchTrustStatus();
129
- }, [cwd, trustCwd, projectTrust, fetchTrustStatus]);
130
-
131
- const handleTrustProject = async () => {
132
- if (!cwd || trustBusy) return;
133
- setTrustBusy(true);
134
- setTrustError(null);
135
- try {
136
- if (onSetProjectTrust && sameWorkspace(trustCwd, cwd)) await onSetProjectTrust(true);
137
- else {
138
- const res = await fetch("/api/security", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ projectTrust: { cwd: trustCwd, decision: true } }) });
139
- const json = await res.json();
140
- if (!res.ok || !json.success) throw new Error(json.error || "信任项目失败");
141
- }
142
- await fetchTrustStatus();
143
- } catch (error) {
144
- setTrustError(error instanceof Error ? error.message : "信任项目失败");
145
- } finally {
146
- setTrustBusy(false);
147
- }
148
- };
149
-
150
- useEffect(() => {
151
- gitRequestRef.current += 1;
152
- historyRequestRef.current += 1;
153
- setGitSnapshot(null);
154
- setGitError(null);
155
- setHistoryJobs([]);
156
- setActiveJobId(null);
157
- setOperationError(null);
158
- setFetchedTrust(null);
159
- setTrustError(null);
160
- setTaskInputs([{ title: "", instruction: "" }]);
161
- }, [cwd]);
162
-
163
- // Fetch Git status for current workspace
164
- const fetchGitStatus = useCallback(async () => {
165
- if (!cwd) return;
166
- const requestedCwd = cwd;
167
- const request = ++gitRequestRef.current;
168
- setGitLoading(true);
169
- setGitError(null);
170
- try {
171
- const res = await fetch(`/api/git?cwd=${encodeURIComponent(cwd)}`);
172
- const json = await res.json();
173
- if (activeCwdRef.current !== requestedCwd || gitRequestRef.current !== request) return;
174
- if (res.ok && json.success && json.data) {
175
- setGitSnapshot({ cwd: requestedCwd, data: json.data });
176
- } else {
177
- setGitError(json.error || "读取 Git 状态失败");
178
- }
179
- } catch (err: any) {
180
- if (activeCwdRef.current === requestedCwd && gitRequestRef.current === request) setGitError(err?.message || "网络请求失败");
181
- } finally {
182
- if (activeCwdRef.current === requestedCwd && gitRequestRef.current === request) setGitLoading(false);
183
- }
184
- }, [cwd]);
185
-
186
- // Fetch Swarm list for current workspace
187
- const fetchSwarmHistory = useCallback(async () => {
188
- if (!cwd) return;
189
- const requestedCwd = cwd;
190
- const request = ++historyRequestRef.current;
191
- setHistoryLoading(true);
192
- try {
193
- const res = await fetch(`/api/swarm?cwd=${encodeURIComponent(cwd)}`);
194
- const json = await res.json();
195
- if (activeCwdRef.current !== requestedCwd || historyRequestRef.current !== request) return;
196
- if (res.ok && json.success && Array.isArray(json.data)) {
197
- setHistoryJobs(json.data);
198
- // If no active job selected yet, pick running job or the most recent job
199
- setActiveJobId((prev) => {
200
- if (prev && json.data.some((j: SwarmJob) => j.id === prev)) return prev;
201
- const running = json.data.find((j: SwarmJob) => j.status === "running");
202
- if (running) return running.id;
203
- return json.data.length > 0 ? json.data[0].id : null;
204
- });
205
- } else {
206
- setOperationError(json.error || "加载协同任务历史失败");
207
- }
208
- } catch (err: any) {
209
- if (activeCwdRef.current === requestedCwd && historyRequestRef.current === request) setOperationError(err?.message || "加载协同任务历史失败");
210
- } finally {
211
- if (activeCwdRef.current === requestedCwd && historyRequestRef.current === request) setHistoryLoading(false);
212
- }
213
- }, [cwd]);
214
-
215
- // Fetch single swarm job details
216
- const fetchJobDetails = useCallback(async (id: string): Promise<SwarmJob | null> => {
217
- const requestedCwd = cwd;
218
- try {
219
- const res = await fetch(`/api/swarm/${id}`);
220
- const json = await res.json();
221
- if (res.ok && json.success && json.data && activeCwdRef.current === requestedCwd && sameWorkspace(json.data.cwd, requestedCwd)) {
222
- const updated: SwarmJob = json.data;
223
- setHistoryJobs((prev) => {
224
- const exists = prev.some((j) => j.id === updated.id);
225
- if (exists) {
226
- return prev.map((j) => (j.id === updated.id ? updated : j));
227
- }
228
- return [updated, ...prev];
229
- });
230
- return updated;
231
- }
232
- } catch (err) {
233
- if (activeCwdRef.current === requestedCwd) setOperationError(err instanceof Error ? err.message : "获取任务详情失败");
234
- }
235
- return null;
236
- }, [cwd]);
237
-
238
- // Initial load
239
- useEffect(() => {
240
- fetchGitStatus();
241
- fetchSwarmHistory();
242
- }, [fetchGitStatus, fetchSwarmHistory]);
243
-
244
- // Current active job object
245
- const currentJob = visibleJobs.find((j) => j.id === activeJobId) || null;
246
- useEffect(() => {
247
- if (currentJob?.status !== "running") setCancelling(false);
248
- }, [currentJob?.status]);
249
-
250
- const runningJobIds = visibleJobs.filter((job) => job.status === "running").map((job) => job.id).join(",");
251
- // Keep every running job current even when the user inspects an older result.
252
- useEffect(() => {
253
- if (!runningJobIds) return;
254
- let stopped = false;
255
- let timer: ReturnType<typeof setTimeout>;
256
- const poll = async () => {
257
- const updates = await Promise.all(runningJobIds.split(",").map((id) => fetchJobDetails(id)));
258
- if (stopped) return;
259
- if (updates.some((job) => job && job.status !== "running")) void fetchGitStatus();
260
- timer = setTimeout(() => void poll(), 1500);
261
- };
262
- timer = setTimeout(() => void poll(), 1500);
263
- return () => { stopped = true; clearTimeout(timer); };
264
- }, [runningJobIds, fetchJobDetails, fetchGitStatus]);
265
-
266
- // Form operations
267
- const handleAddTask = () => {
268
- if (taskInputs.length >= 3) return;
269
- setTaskInputs((prev) => [...prev, { title: "", instruction: "" }]);
270
- };
271
-
272
- const handleRemoveTask = (index: number) => {
273
- if (taskInputs.length <= 1) return;
274
- setTaskInputs((prev) => prev.filter((_, i) => i !== index));
275
- };
276
-
277
- const handleUpdateTask = (index: number, field: keyof SwarmTaskInput, value: string) => {
278
- setTaskInputs((prev) => {
279
- const next = [...prev];
280
- next[index] = { ...next[index], [field]: value };
281
- return next;
282
- });
283
- };
284
-
285
- // Prerequisites calculation
286
- const isTrusted = Boolean(trustState && (!trustState.required || trustState.trusted));
287
- const isRepo = Boolean(gitInfo?.isRepo);
288
- const dirtyCount = gitInfo?.files?.length ?? 0;
289
- const isClean = isRepo && dirtyCount === 0 && !gitInfo?.statusError;
290
- const hasRunningSwarm = visibleJobs.some((j) => j.status === "running");
291
-
292
- const tasksValid = taskInputs.every(
293
- (t) => t.title.trim().length > 0 && t.title.trim().length <= 80 && t.instruction.trim().length > 0 && t.instruction.trim().length <= 4000,
294
- );
295
-
296
- let launchDisabledReason = "";
297
- if (!cwd) {
298
- launchDisabledReason = "未选择有效工作区";
299
- } else if (trustError) {
300
- launchDisabledReason = `禁止启动 (项目信任状态未知: ${trustError})`;
301
- } else if (!trustState) {
302
- launchDisabledReason = "正在读取项目信任状态";
303
- } else if (!isTrusted) {
304
- launchDisabledReason = "禁止启动 (项目未信任,请先允许)";
305
- } else if (gitError) {
306
- launchDisabledReason = `禁止启动 (Git 状态未知: ${gitError})`;
307
- } else if (gitInfo?.statusError) {
308
- launchDisabledReason = `禁止启动 (Git 状态未知: ${gitInfo.statusError})`;
309
- } else if (gitLoading || historyLoading) {
310
- launchDisabledReason = "正在检查工作区状态";
311
- } else if (!isRepo) {
312
- launchDisabledReason = "禁止启动 (非 Git 仓库,无法创建工作树)";
313
- } else if (!isClean) {
314
- launchDisabledReason = `禁止启动 (工作区存在未提交改动: ${dirtyCount} 项)`;
315
- } else if (hasRunningSwarm) {
316
- launchDisabledReason = "禁止启动 (已有协同任务正在运行)";
317
- } else if (!tasksValid) {
318
- launchDisabledReason = "请填写所有任务的目标与指令";
319
- } else if (isLaunching) {
320
- launchDisabledReason = "正在启动智能体群组...";
321
- }
322
-
323
- const canLaunch = !launchDisabledReason;
324
-
325
- // Launch Swarm API
326
- const handleLaunchSwarm = async () => {
327
- if (!canLaunch) return;
328
- const requestedCwd = cwd;
329
- setIsLaunching(true);
330
- setOperationError(null);
331
- try {
332
- const payload = {
333
- cwd,
334
- tasks: taskInputs.map((t) => ({
335
- title: t.title.trim(),
336
- instruction: t.instruction.trim(),
337
- })),
338
- };
339
- const res = await fetch("/api/swarm", {
340
- method: "POST",
341
- headers: { "Content-Type": "application/json" },
342
- body: JSON.stringify(payload),
343
- });
344
- const json = await res.json();
345
- if (!res.ok || !json.success) {
346
- throw new Error(json.error || "启动 Swarm 任务失败");
347
- }
348
- const newJob: SwarmJob = json.data;
349
- if (activeCwdRef.current !== requestedCwd) return;
350
- setHistoryJobs((prev) => [newJob, ...prev]);
351
- setActiveJobId(newJob.id);
352
- setActiveTab("active");
353
- } catch (err: any) {
354
- setOperationError(err?.message || "启动失败");
355
- } finally {
356
- setIsLaunching(false);
357
- }
358
- };
359
-
360
- // Cancel Swarm API
361
- const handleCancelSwarm = async () => {
362
- if (!currentJob || currentJob.status !== "running") return;
363
- setCancelling(true);
364
- setOperationError(null);
365
- let requested = false;
366
- try {
367
- const res = await fetch(`/api/swarm/${currentJob.id}`, {
368
- method: "POST",
369
- headers: { "Content-Type": "application/json" },
370
- body: JSON.stringify({ cwd, action: "cancel" }),
371
- });
372
- const json = await res.json();
373
- if (!res.ok || !json.success) {
374
- throw new Error(json.error || "取消任务失败");
375
- }
376
- const updated: SwarmJob = json.data;
377
- requested = true;
378
- if (activeCwdRef.current === cwd) setHistoryJobs((prev) => prev.map((j) => (j.id === updated.id ? updated : j)));
379
- } catch (err: any) {
380
- setOperationError(err?.message || "取消操作失败");
381
- } finally {
382
- if (!requested) setCancelling(false);
383
- }
384
- };
385
-
386
- // Accept Patch API
387
- const handleAcceptPatch = async (index: number) => {
388
- if (!currentJob || currentJob.status === "running" || currentJob.tasks[index]?.status !== "completed" || !currentJob.tasks[index]?.patch) return;
389
- const requestedCwd = cwd;
390
- setAcceptingIndex(index);
391
- setOperationError(null);
392
- try {
393
- const res = await fetch(`/api/swarm/${currentJob.id}`, {
394
- method: "POST",
395
- headers: { "Content-Type": "application/json" },
396
- body: JSON.stringify({ cwd, action: "accept", index }),
397
- });
398
- const json = await res.json();
399
- if (!res.ok || !json.success) {
400
- throw new Error(json.error || "采纳改动失败");
401
- }
402
- const updated: SwarmJob = json.data;
403
- if (activeCwdRef.current !== requestedCwd) return;
404
- setHistoryJobs((prev) => prev.map((j) => (j.id === updated.id ? updated : j)));
405
- // Refresh git status because working tree files changed!
406
- fetchGitStatus();
407
- onWorkspaceChanged?.();
408
- } catch (err: any) {
409
- setOperationError(err?.message || "采纳补丁失败");
410
- } finally {
411
- setAcceptingIndex(null);
412
- }
413
- };
414
-
415
- const toggleDiff = (index: number) => {
416
- setExpandedDiffs((prev) => ({
417
- ...prev,
418
- [index]: prev[index] === undefined ? false : !prev[index],
419
- }));
420
- };
421
-
422
- return (
423
- <div className="flex flex-col w-full h-full overflow-hidden text-slate-800 dark:text-slate-100 select-none">
424
- {/* Top Inspection & Git Readiness Strip (Clay Inset Pod) */}
425
- <div className="shrink-0 mb-3 px-4 py-3 clay-card flex flex-wrap items-center justify-between gap-3 bg-white dark:bg-slate-900">
426
- {/* Left: Workspace Selection & Git Branch */}
427
- <div className="flex items-center gap-3">
428
- <div className="w-10 h-10 clay-inset rounded-2xl flex items-center justify-center text-emerald-500">
429
- <IconWorkflowAgent16 size={20} />
430
- </div>
431
- <div>
432
- <div className="flex items-center gap-2">
433
- <span className="font-bold text-base text-slate-800 dark:text-slate-100 tracking-tight">PiWeb Core Swarm</span>
434
- <span className="px-2 py-0.5 clay-code-pill text-xs text-slate-500 dark:text-slate-400 font-mono truncate max-w-xs" title={cwd}>
435
- {cwd || "未选择工作区"}
436
- </span>
437
- {gitInfo?.branch && (
438
- <span className="px-2.5 py-0.5 rounded-full bg-sky-50 dark:bg-sky-950/40 text-sky-600 dark:text-sky-300 text-xs font-semibold flex items-center gap-1 shadow-sm">
439
- <IconBranchOutline16 size={12} />
440
- {gitInfo.branch}
441
- </span>
442
- )}
443
- </div>
444
- <div className="flex items-center gap-2 mt-0.5 text-xs text-slate-500 dark:text-slate-400">
445
- <span>独立 Git 工作树 · 仅受限文件工具 · 无 Shell</span>
446
- <span className="w-1 h-1 rounded-full bg-slate-300 dark:bg-slate-700" />
447
- <span className="text-emerald-600 dark:text-emerald-400 font-semibold">最大并行度: 3 节点</span>
448
- </div>
449
- </div>
450
- </div>
451
-
452
- {/* Center/Right: Git & Trust Readiness Banner */}
453
- <div className="flex items-center gap-3">
454
- {trustError ? (
455
- <div role="alert" className="clay-inset rounded-2xl px-3.5 py-2 text-xs text-rose-700">项目信任状态未知:{trustError}</div>
456
- ) : !trustState ? (
457
- <div className="clay-inset rounded-2xl px-3.5 py-2 text-xs text-slate-500">正在检查项目信任状态...</div>
458
- ) : !isTrusted ? (
459
- <div className="flex items-center gap-2.5 px-3.5 py-2 clay-inset bg-rose-50/80 dark:bg-rose-950/30 text-rose-900 dark:text-rose-200 rounded-2xl">
460
- <IconShieldOutline16 size={18} className="text-rose-500 flex-none" />
461
- <div className="text-left leading-tight">
462
- <p className="text-xs font-bold text-rose-950 dark:text-rose-100">工作区未信任 (Untrusted)</p>
463
- <p className="text-[11px] text-rose-800/80 dark:text-rose-300">需信任当前工作区后方可签发智能体协同任务。</p>
464
- </div>
465
- {cwd && (
466
- <button
467
- type="button"
468
- className="clay-btn px-2.5 py-1 bg-rose-500 hover:bg-rose-600 text-white text-xs font-semibold rounded-full ml-1"
469
- disabled={trustBusy}
470
- onClick={() => void handleTrustProject()}
471
- >
472
- 信任项目
473
- </button>
474
- )}
475
- </div>
476
- ) : gitError || gitInfo?.statusError ? (
477
- <div role="alert" className="clay-inset rounded-2xl px-3.5 py-2 text-xs text-rose-700">Git 状态读取失败:{gitError || gitInfo?.statusError}</div>
478
- ) : !gitInfo || historyLoading ? (
479
- <div className="clay-inset rounded-2xl px-3.5 py-2 text-xs text-slate-500">正在检查 Git 与协同任务状态...</div>
480
- ) : !isRepo ? (
481
- <div className="flex items-center gap-2.5 px-3.5 py-2 clay-inset bg-amber-50/80 dark:bg-amber-950/30 text-amber-900 dark:text-amber-200 rounded-2xl">
482
- <IconWarningOutline16 size={18} className="text-amber-500 flex-none" />
483
- <div className="text-left leading-tight">
484
- <p className="text-xs font-bold text-amber-950 dark:text-amber-100">非 Git 仓库 (Not Git Repository)</p>
485
- <p className="text-[11px] text-amber-800/80 dark:text-amber-300">当前目录未初始化 Git 仓库,无法派生隔离工作树。</p>
486
- </div>
487
- </div>
488
- ) : !isClean ? (
489
- <div className="flex items-center gap-2.5 px-3.5 py-2 clay-inset bg-amber-50/70 dark:bg-amber-950/30 text-amber-900 dark:text-amber-200 rounded-2xl">
490
- <IconWarningOutline16 size={18} className="text-amber-500 animate-bounce flex-none" />
491
- <div className="text-left leading-tight">
492
- <p className="text-xs font-bold text-amber-950 dark:text-amber-100">工作区就绪检测:未提交改动 (Git Dirty)</p>
493
- <p className="text-[11px] text-amber-800/80 dark:text-amber-300">
494
- 检测到 {dirtyCount} 项未提交变更。请提交或移走改动后再启动。
495
- </p>
496
- </div>
497
- </div>
498
- ) : (
499
- <div className="flex items-center gap-2.5 px-3.5 py-2 clay-inset bg-emerald-50/80 dark:bg-emerald-950/30 text-emerald-900 dark:text-emerald-200 rounded-2xl">
500
- <IconCheckOutline14 size={18} className="text-emerald-500 flex-none" />
501
- <div className="text-left leading-tight">
502
- <p className="text-xs font-bold text-emerald-950 dark:text-emerald-100">工作区就绪:干净的 Git 状态 (Ready)</p>
503
- <p className="text-[11px] text-emerald-800/80 dark:text-emerald-300">无未提交改动,可创建独立工作树并行执行。</p>
504
- </div>
505
- </div>
506
- )}
507
-
508
- {/* Recheck Trigger Button */}
509
- <button
510
- type="button"
511
- className="clay-btn px-3 py-2 bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200 hover:text-slate-900 text-xs font-semibold flex items-center gap-1.5"
512
- onClick={() => {
513
- fetchGitStatus();
514
- fetchSwarmHistory();
515
- }}
516
- title="重新检查 Git 与工作区状态"
517
- >
518
- <IconRefreshOutline14 size={14} className={`text-emerald-500 ${gitLoading ? "animate-spin" : ""}`} />
519
- 重新检查
520
- </button>
521
- </div>
522
- </div>
523
-
524
- {/* Main Split Canvas: Left Task Creator & Right Execution Stream */}
525
- <div className="flex-1 flex flex-col gap-3 overflow-y-auto min-h-0 lg:flex-row lg:overflow-hidden">
526
- {/* LEFT PANEL: Task Group Creator (Fixed 410px, scrollable) */}
527
- <section className="w-full min-h-[420px] shrink-0 clay-card bg-white dark:bg-slate-900 flex flex-col p-4 overflow-hidden lg:w-[410px] lg:min-h-0">
528
- {/* Section Header */}
529
- <div className="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800 shrink-0">
530
- <div>
531
- <div className="flex items-center gap-2">
532
- <span className="w-2.5 h-2.5 rounded-full bg-orange-400" />
533
- <h2 className="text-sm font-bold text-slate-800 dark:text-slate-100">发起智能体协同任务组</h2>
534
- </div>
535
- <span className="text-[11px] font-mono text-slate-400">POST /api/swarm (cwd, tasks:[])</span>
536
- </div>
537
- <span className="clay-badge px-2.5 py-1 text-xs bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 font-semibold">
538
- {taskInputs.length} / 3 任务
539
- </span>
540
- </div>
541
-
542
- {/* Scrollable Tasks Builder Area */}
543
- <div className="flex-1 overflow-y-auto py-3 space-y-3 pr-1">
544
- {taskInputs.map((task, idx) => (
545
- <div key={idx} className="clay-surface p-3.5 relative group rounded-2xl">
546
- <div className="flex items-center justify-between mb-2">
547
- <span
548
- className={`clay-badge px-2 py-0.5 text-xs font-bold ${
549
- idx === 0
550
- ? "bg-emerald-50 dark:bg-emerald-950/40 text-emerald-600 dark:text-emerald-400"
551
- : idx === 1
552
- ? "bg-sky-50 dark:bg-sky-950/40 text-sky-600 dark:text-sky-400"
553
- : "bg-purple-50 dark:bg-purple-950/40 text-purple-600 dark:text-purple-400"
554
- }`}
555
- >
556
- Task #{idx + 1}
557
- </span>
558
- <div className="flex items-center gap-2">
559
- <span className="text-[11px] text-slate-400">并行隔离节点 P{idx + 1}</span>
560
- {taskInputs.length > 1 && (
561
- <button
562
- type="button"
563
- className="text-slate-400 hover:text-rose-500 text-xs flex items-center gap-0.5 transition-colors"
564
- onClick={() => handleRemoveTask(idx)}
565
- title="移除此子任务"
566
- >
567
- <IconTrashOutline16 size={13} />
568
- 移除
569
- </button>
570
- )}
571
- </div>
572
- </div>
573
- <div className="space-y-2">
574
- <div>
575
- <label className="block text-[11px] font-medium text-slate-500 dark:text-slate-400 mb-1">
576
- 子任务目标 / Title (最多 80 字)
577
- </label>
578
- <input
579
- type="text"
580
- className="w-full clay-inset px-3 py-1.5 text-xs text-slate-800 dark:text-slate-100 bg-transparent border-0 focus:ring-1 focus:ring-emerald-400 outline-none rounded-xl"
581
- value={task.title}
582
- maxLength={80}
583
- placeholder={`输入第 ${idx + 1} 个子任务简述...`}
584
- onChange={(e) => handleUpdateTask(idx, "title", e.target.value)}
585
- />
586
- </div>
587
- <div>
588
- <label className="block text-[11px] font-medium text-slate-500 dark:text-slate-400 mb-1">
589
- 详细指示 / Instruction (最多 4000 字)
590
- </label>
591
- <textarea
592
- rows={3}
593
- className="w-full clay-inset p-2.5 text-xs text-slate-700 dark:text-slate-200 bg-transparent border-0 focus:ring-1 focus:ring-emerald-400 outline-none resize-none rounded-xl"
594
- value={task.instruction}
595
- maxLength={4000}
596
- placeholder="指定目标文件与函数改写逻辑规范..."
597
- onChange={(e) => handleUpdateTask(idx, "instruction", e.target.value)}
598
- />
599
- </div>
600
- </div>
601
- </div>
602
- ))}
603
- </div>
604
-
605
- {/* Add Task Slot Button */}
606
- {taskInputs.length < 3 && (
607
- <div className="pt-2 shrink-0">
608
- <button
609
- type="button"
610
- className="w-full py-2.5 clay-inset hover:bg-slate-200/50 dark:hover:bg-slate-800/50 text-slate-700 dark:text-slate-300 text-xs font-semibold rounded-xl flex items-center justify-center gap-2 transition-all"
611
- onClick={handleAddTask}
612
- >
613
- <IconPlusOutline16 size={14} className="text-emerald-500" />
614
- + 添加子任务 (最多 3 个)
615
- </button>
616
- </div>
617
- )}
618
-
619
- {/* Sandbox Security Policy Card */}
620
- <div className="mt-3 p-3 clay-inset bg-emerald-50/50 dark:bg-emerald-950/20 rounded-2xl shrink-0">
621
- <div className="flex items-center gap-1.5 text-emerald-700 dark:text-emerald-300 text-xs font-bold mb-1">
622
- <IconShieldOutline16 size={14} />
623
- 沙箱安全约定 (Sandbox Guarantee)
624
- </div>
625
- <p className="text-[11px] text-slate-500 dark:text-slate-400 leading-relaxed">
626
- 智能体在独立 Git 工作树中使用受限文件读写工具,无 Shell 权限;模型请求仍会发往已配置的供应商。文件改动需人工审查 Diff 后采纳。
627
- </p>
628
- </div>
629
-
630
- {/* Creator Bottom Action Bar */}
631
- <div className="mt-3 pt-3 border-t border-slate-100 dark:border-slate-800 flex flex-col gap-2 shrink-0">
632
- {operationError && (
633
- <div className="p-2 rounded-xl bg-rose-50 dark:bg-rose-950/40 text-rose-700 dark:text-rose-300 text-xs leading-tight">
634
- {operationError}
635
- </div>
636
- )}
637
- <button
638
- type="button"
639
- className={`w-full py-3 text-xs font-bold rounded-full flex items-center justify-center gap-2 transition-all ${
640
- canLaunch
641
- ? "clay-btn clay-btn-mint text-white cursor-pointer shadow-md hover:scale-[1.01]"
642
- : "clay-btn bg-slate-200 dark:bg-slate-800 text-slate-400 dark:text-slate-500 cursor-not-allowed shadow-none"
643
- }`}
644
- disabled={!canLaunch}
645
- onClick={handleLaunchSwarm}
646
- >
647
- <IconWorkflowAgent16 size={16} />
648
- <span>{canLaunch ? `启动群组任务 (${taskInputs.length} 个任务)` : launchDisabledReason}</span>
649
- </button>
650
- <div className="flex items-center justify-between px-1 text-[11px] text-slate-400">
651
- <span>按规约需保持 Git 无暂存/变更</span>
652
- {onSwitchToWorkbench && (
653
- <button
654
- type="button"
655
- className="text-sky-500 hover:underline"
656
- onClick={onSwitchToWorkbench}
657
- >
658
- 返回工作台
659
- </button>
660
- )}
661
- </div>
662
- </div>
663
- </section>
664
-
665
- {/* RIGHT PANEL: Active & Historical Swarm Executions + Diff Inspector */}
666
- <section className="flex-1 min-h-[480px] flex flex-col clay-card bg-white dark:bg-slate-900 p-4 overflow-hidden min-w-0 lg:min-h-0">
667
- {/* Execution Top Header & Realtime Poll Pill */}
668
- <div className="flex items-center justify-between pb-3 border-b border-slate-100 dark:border-slate-800 shrink-0">
669
- <div className="flex items-center gap-3">
670
- <div className="w-8 h-8 clay-inset rounded-full flex items-center justify-center text-sky-500">
671
- <IconWorkflowAgent16 size={18} />
672
- </div>
673
- <div>
674
- <div className="flex items-center gap-2">
675
- <h2 className="text-sm font-bold text-slate-800 dark:text-slate-100">活跃协同任务监视器</h2>
676
- {currentJob?.status === "running" && (
677
- <span className="clay-badge px-2.5 py-0.5 text-xs bg-emerald-50 dark:bg-emerald-950/40 text-emerald-600 dark:text-emerald-400 font-bold flex items-center gap-1.5">
678
- <span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-ping" />
679
- Polling (GET /api/swarm/{currentJob.id.slice(0, 8)})
680
- </span>
681
- )}
682
- </div>
683
- <p className="text-[11px] text-slate-400">
684
- 会话实例实时事件驱动 | 状态轮询间隔: 1.5s
685
- </p>
686
- </div>
687
- </div>
688
-
689
- {/* History Selector Tabs */}
690
- <div className="flex items-center gap-1 clay-inset p-1 rounded-full text-xs">
691
- <button
692
- type="button"
693
- className={`px-3 py-1 rounded-full font-bold transition-all ${
694
- activeTab === "active"
695
- ? "bg-white dark:bg-slate-800 text-slate-800 dark:text-slate-100 clay-badge"
696
- : "text-slate-500 hover:text-slate-800 dark:hover:text-slate-200"
697
- }`}
698
- onClick={() => setActiveTab("active")}
699
- >
700
- 当前协同 {currentJob ? `(1)` : `(0)`}
701
- </button>
702
- <button
703
- type="button"
704
- className={`px-3 py-1 rounded-full font-bold transition-all ${
705
- activeTab === "history"
706
- ? "bg-white dark:bg-slate-800 text-slate-800 dark:text-slate-100 clay-badge"
707
- : "text-slate-500 hover:text-slate-800 dark:hover:text-slate-200"
708
- }`}
709
- onClick={() => setActiveTab("history")}
710
- >
711
- 历史归档 ({visibleJobs.length})
712
- </button>
713
- </div>
714
- </div>
715
-
716
- {/* Execution Scroll Stream Body */}
717
- <div className="flex-1 overflow-y-auto pt-3 space-y-4 pr-1">
718
- {activeTab === "history" ? (
719
- /* History list view */
720
- <div className="space-y-3">
721
- {visibleJobs.length === 0 ? (
722
- <div className="text-center py-16 text-xs text-slate-400">
723
- 暂无历史协同任务,请在左侧发起新任务组。
724
- </div>
725
- ) : (
726
- visibleJobs.map((job) => {
727
- const isSelected = job.id === activeJobId;
728
- return (
729
- <div
730
- key={job.id}
731
- className={`clay-surface p-3.5 rounded-2xl transition-all cursor-pointer hover:scale-[1.005] ${
732
- isSelected ? "ring-2 ring-emerald-400" : ""
733
- }`}
734
- onClick={() => {
735
- setActiveJobId(job.id);
736
- setActiveTab("active");
737
- }}
738
- >
739
- <div className="flex items-center justify-between">
740
- <div className="flex items-center gap-2.5">
741
- <span
742
- className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold ${
743
- job.status === "completed"
744
- ? "bg-emerald-50 text-emerald-600"
745
- : job.status === "running"
746
- ? "bg-sky-50 text-sky-600 animate-spin"
747
- : "bg-slate-200 text-slate-600"
748
- }`}
749
- >
750
- {job.status === "completed" ? (
751
- <IconCheckOutline14 size={14} />
752
- ) : job.status === "running" ? (
753
- <IconRefreshOutline14 size={14} />
754
- ) : (
755
- <IconClockOutline16 size={14} />
756
- )}
757
- </span>
758
- <div>
759
- <div className="flex items-center gap-2">
760
- <span className="font-mono text-xs font-bold text-slate-700 dark:text-slate-200">
761
- {job.id}
762
- </span>
763
- <span
764
- className={`px-2 py-0.5 rounded-full text-[10px] font-bold ${
765
- job.status === "completed"
766
- ? "bg-emerald-100 text-emerald-700"
767
- : job.status === "running"
768
- ? "bg-sky-100 text-sky-700"
769
- : "bg-slate-200 text-slate-600"
770
- }`}
771
- >
772
- {job.status.toUpperCase()}
773
- </span>
774
- </div>
775
- <p className="text-[11px] text-slate-400 mt-0.5">
776
- {new Date(job.createdAt).toLocaleString()} · {job.tasks.length} 个子任务 (
777
- {job.tasks.filter((t) => t.status === "completed").length} 完成,{" "}
778
- {job.tasks.filter((t) => t.accepted).length} 采纳)
779
- </p>
780
- </div>
781
- </div>
782
- <button
783
- type="button"
784
- className="clay-btn px-3 py-1 bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-200 text-xs font-semibold"
785
- >
786
- 查看详情
787
- </button>
788
- </div>
789
- </div>
790
- );
791
- })
792
- )}
793
- </div>
794
- ) : !currentJob ? (
795
- /* Empty state when no active job */
796
- <div className="flex flex-col items-center justify-center h-full text-center py-16 px-4">
797
- <div className="w-16 h-16 rounded-3xl clay-inset flex items-center justify-center text-slate-400 mb-4">
798
- <IconWorkflowAgent16 size={32} />
799
- </div>
800
- <h3 className="text-sm font-bold text-slate-700 dark:text-slate-200 mb-1">
801
- 尚未启动智能体协同任务
802
- </h3>
803
- <p className="text-xs text-slate-400 max-w-sm mb-4">
804
- 在左侧配置 1 至 3 个并发隔离子任务,启动后系统将自动派生独立的 Git 工作树执行改动。
805
- </p>
806
- </div>
807
- ) : (
808
- /* Active Instance View */
809
- <div className="clay-surface p-4 rounded-3xl relative">
810
- {/* Swarm Header Meta */}
811
- <div className="flex items-center justify-between pb-3 border-b border-slate-200/60 dark:border-slate-800 mb-3">
812
- <div className="flex items-center gap-3">
813
- <span
814
- className={`px-2.5 py-1 clay-badge text-xs font-bold flex items-center gap-1.5 ${
815
- currentJob.status === "running"
816
- ? "bg-emerald-50 text-emerald-600"
817
- : currentJob.status === "completed"
818
- ? "bg-emerald-100 text-emerald-700"
819
- : currentJob.status === "failed"
820
- ? "bg-rose-100 text-rose-700"
821
- : "bg-slate-200 text-slate-600"
822
- }`}
823
- >
824
- {currentJob.status === "running" && (
825
- <span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
826
- )}
827
- {currentJob.status.toUpperCase()}
828
- </span>
829
- <span className="font-mono text-xs font-bold text-slate-700 dark:text-slate-200" title={currentJob.id}>
830
- ID: {currentJob.id}
831
- </span>
832
- <span className="text-xs text-slate-400 flex items-center gap-1">
833
- <IconClockOutline16 size={13} />
834
- {new Date(currentJob.createdAt).toLocaleTimeString()}
835
- </span>
836
- </div>
837
-
838
- {/* Cancel Execution Trigger */}
839
- {currentJob.status === "running" && (
840
- <button
841
- type="button"
842
- className="clay-btn px-3 py-1.5 bg-rose-50 hover:bg-rose-100 text-rose-600 text-xs font-semibold flex items-center gap-1"
843
- disabled={cancelling}
844
- onClick={handleCancelSwarm}
845
- >
846
- <IconStopFill16 size={13} />
847
- {cancelling ? "正在中断..." : "中断全部智能体 (action: cancel)"}
848
- </button>
849
- )}
850
- </div>
851
-
852
- {/* Subtasks Pipeline Grid */}
853
- <div className="space-y-3">
854
- {currentJob.tasks.map((task, idx) => {
855
- const parsedDiff = task.patch ? parseUnifiedDiff(task.patch) : null;
856
- const stats = parsedDiff ? diffStats(parsedDiff) : { add: 0, del: 0 };
857
- const isDiffExpanded = expandedDiffs[idx] !== false; // default expanded if patch exists
858
-
859
- return (
860
- <div key={idx} className="clay-card p-3.5 bg-white dark:bg-slate-900 rounded-2xl">
861
- <div className="flex items-center justify-between">
862
- <div className="flex items-center gap-2.5">
863
- <span
864
- className={`w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold ${
865
- task.status === "completed"
866
- ? "bg-emerald-50 text-emerald-600"
867
- : task.status === "running"
868
- ? "bg-sky-50 text-sky-600"
869
- : task.status === "failed"
870
- ? "bg-rose-50 text-rose-600"
871
- : "bg-slate-100 text-slate-400"
872
- }`}
873
- >
874
- {task.status === "completed" ? (
875
- <IconCheckOutline14 size={14} />
876
- ) : task.status === "running" ? (
877
- <IconRefreshOutline14 size={14} className="animate-spin" />
878
- ) : task.status === "failed" ? (
879
- <IconWarningOutline16 size={14} />
880
- ) : (
881
- <IconClockOutline16 size={14} />
882
- )}
883
- </span>
884
- <div>
885
- <h3 className="text-xs font-bold text-slate-800 dark:text-slate-100">
886
- Task {idx + 1}: {task.title}
887
- </h3>
888
- <p className="text-[11px] text-slate-400">
889
- 状态: {task.status.toUpperCase()}
890
- {task.patch ? ` · 包含补丁文件` : ` · 无文件改动`}
891
- </p>
892
- </div>
893
- </div>
894
-
895
- <div className="flex items-center gap-2">
896
- {task.patch && (
897
- <span className="px-2 py-0.5 clay-code-pill text-[11px] text-slate-600 dark:text-slate-300 font-mono">
898
- +{stats.add} -{stats.del} lines
899
- </span>
900
- )}
901
-
902
- {/* Accept Patch Action Button */}
903
- {task.patch && task.status === "completed" && (
904
- task.accepted ? (
905
- <span className="clay-badge px-3 py-1.5 bg-emerald-50 dark:bg-emerald-950/40 text-emerald-600 dark:text-emerald-400 text-xs font-bold flex items-center gap-1">
906
- <IconCheckOutline14 size={13} />
907
- 已采纳
908
- </span>
909
- ) : (
910
- <button
911
- type="button"
912
- className="clay-btn px-3 py-1.5 bg-emerald-500 hover:bg-emerald-600 text-white text-xs font-bold flex items-center gap-1.5 shadow-sm"
913
- disabled={currentJob.status === "running" || acceptingIndex === idx}
914
- onClick={() => handleAcceptPatch(idx)}
915
- >
916
- <IconCheckOutline14 size={13} />
917
- {acceptingIndex === idx ? "正在写入工作区..." : currentJob.status === "running" ? "等待全部任务结束" : "采纳改动 (Accept Patch)"}
918
- </button>
919
- )
920
- )}
921
-
922
- {task.patch && (
923
- <button
924
- type="button"
925
- className="clay-btn px-2.5 py-1.5 bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-200 hover:text-slate-900 text-xs font-semibold flex items-center gap-1"
926
- onClick={() => toggleDiff(idx)}
927
- >
928
- <IconFileOutline16 size={13} />
929
- {isDiffExpanded ? "折叠 Diff" : "查看 Diff"}
930
- </button>
931
- )}
932
- </div>
933
- </div>
934
-
935
- {/* Task Instruction Snapshot */}
936
- <div className="mt-2 text-[11px] text-slate-500 dark:text-slate-400 bg-slate-50 dark:bg-slate-950/30 px-2.5 py-1.5 rounded-xl">
937
- <span className="font-semibold text-slate-600 dark:text-slate-300">目标指令: </span>
938
- {task.instruction}
939
- </div>
940
-
941
- {/* Task Error Message */}
942
- {task.error && (
943
- <div className="mt-2 p-2.5 rounded-xl bg-rose-50 dark:bg-rose-950/40 text-rose-800 dark:text-rose-200 text-xs font-mono border border-rose-200 dark:border-rose-900">
944
- <span className="font-bold">执行异常: </span>
945
- {task.error}
946
- </div>
947
- )}
948
-
949
- {/* Task Execution Report */}
950
- {task.report && (
951
- <div className="mt-2 p-2.5 rounded-xl clay-inset bg-slate-50 dark:bg-slate-950/20 text-slate-700 dark:text-slate-300 text-xs leading-relaxed">
952
- <div className="font-bold text-[11px] text-slate-500 dark:text-slate-400 mb-1">
953
- 执行报告摘要:
954
- </div>
955
- <div className="whitespace-pre-wrap">{task.report}</div>
956
- </div>
957
- )}
958
-
959
- {/* Inline Unified Diff Viewer */}
960
- {task.patch && parsedDiff && isDiffExpanded && (
961
- <div className="mt-3 clay-inset p-3 bg-slate-900 rounded-xl overflow-hidden text-xs text-slate-200">
962
- <div className="flex items-center justify-between pb-2 mb-2 border-b border-slate-700/60 text-slate-400">
963
- <div className="flex items-center gap-2">
964
- <IconFileOutline16 size={14} className="text-sky-400" />
965
- <span className="font-mono text-xs">Unified Diff</span>
966
- </div>
967
- <span className="text-[11px] text-slate-400">
968
- {parsedDiff.filter((l) => l.kind === "file").length} 个文件变动
969
- </span>
970
- </div>
971
- <div className="overflow-x-auto max-h-80 select-text">
972
- <DiffView lines={parsedDiff} />
973
- </div>
974
- <div className="mt-2 pt-2 border-t border-slate-800 text-[11px] text-slate-400 flex items-center justify-between">
975
- <span>采纳说明:改动将直接应用至工作区,不暂存、不提交 Git。</span>
976
- <span className="font-mono">基于 {currentJob.base.slice(0, 8)} 生成</span>
977
- </div>
978
- </div>
979
- )}
980
-
981
- {/* Accepted Status Banner */}
982
- {task.accepted && (
983
- <div className="mt-2 p-2 rounded-xl bg-emerald-50 dark:bg-emerald-950/40 text-emerald-800 dark:text-emerald-200 text-xs flex items-center justify-between">
984
- <div className="flex items-center gap-1.5">
985
- <IconCheckOutline14 size={14} className="text-emerald-500" />
986
- <span>文件修改已成功写入工作区 (工作区已更新,暂未 commit)</span>
987
- </div>
988
- <span className="clay-badge px-2 py-0.5 bg-white dark:bg-slate-800 text-emerald-600 font-bold text-[10px]">
989
- 已采纳
990
- </span>
991
- </div>
992
- )}
993
- </div>
994
- );
995
- })}
996
- </div>
997
- </div>
998
- )}
999
- </div>
1000
-
1001
- {/* Execution Studio Footer Summary Status */}
1002
- <div className="mt-3 pt-3 border-t border-slate-100 dark:border-slate-800 flex items-center justify-between shrink-0 text-[11px] text-slate-400">
1003
- <div className="flex items-center gap-3">
1004
- <span className="flex items-center gap-1">
1005
- <IconShieldOutline16 size={13} className="text-emerald-500" />
1006
- 受限文件读写工具
1007
- </span>
1008
- <span className="flex items-center gap-1">
1009
- <IconBranchOutline16 size={13} className="text-sky-500" />
1010
- {currentJob ? `任务状态: ${currentJob.status}` : "尚无运行任务"}
1011
- </span>
1012
- </div>
1013
- <div>
1014
- <span>PiWeb Agent Swarm Engine (No-shell Isolated Worktree)</span>
1015
- </div>
1016
- </div>
1017
- </section>
1018
- </div>
1019
- </div>
1020
- );
1021
- }