@rexvane/piweb 0.3.14 → 0.3.16

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 (262) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +12 -9
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +6 -6
  5. package/.next/react-loadable-manifest.json +16 -4
  6. package/.next/required-server-files.js +3 -1
  7. package/.next/required-server-files.json +3 -1
  8. package/.next/routes-manifest.json +20 -0
  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 +7 -4
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +7 -4
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -2
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +5 -2
  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/swarm/[id]/route.js +1 -0
  61. package/.next/server/app/api/swarm/[id]/route.js.nft.json +1 -0
  62. package/.next/server/app/api/swarm/[id]/route_client-reference-manifest.js +1 -0
  63. package/.next/server/app/api/swarm/route.js +1 -0
  64. package/.next/server/app/api/swarm/route.js.nft.json +1 -0
  65. package/.next/server/app/api/swarm/route_client-reference-manifest.js +1 -0
  66. package/.next/server/app/api/tests/route.js +2 -0
  67. package/.next/server/app/api/tests/route.js.nft.json +1 -0
  68. package/.next/server/app/api/tests/route_client-reference-manifest.js +1 -0
  69. package/.next/server/app/api/update/route.js +2 -2
  70. package/.next/server/app/api/update/route.js.nft.json +1 -1
  71. package/.next/server/app/api/version/route.js +1 -1
  72. package/.next/server/app/api/version/route.js.nft.json +1 -1
  73. package/.next/server/app/api/web-auth/route.js +1 -1
  74. package/.next/server/app/api/workspaces/route.js +1 -1
  75. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  76. package/.next/server/app/icon.svg/route.js +1 -1
  77. package/.next/server/app/index.html +1 -1
  78. package/.next/server/app/index.rsc +9 -6
  79. package/.next/server/app/index.segments/__PAGE__.segment.rsc +7 -4
  80. package/.next/server/app/index.segments/_full.segment.rsc +9 -6
  81. package/.next/server/app/index.segments/_tree.segment.rsc +5 -2
  82. package/.next/server/app/login/page.js +1 -1
  83. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  84. package/.next/server/app/login.html +1 -1
  85. package/.next/server/app/login.rsc +8 -5
  86. package/.next/server/app/login.segments/_full.segment.rsc +8 -5
  87. package/.next/server/app/login.segments/_tree.segment.rsc +5 -2
  88. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +6 -3
  89. package/.next/server/app/page.js +1 -1
  90. package/.next/server/app/page_client-reference-manifest.js +1 -1
  91. package/.next/server/app-paths-manifest.json +12 -9
  92. package/.next/server/chunks/496.js +1 -1
  93. package/.next/server/chunks/532.js +1 -1
  94. package/.next/server/chunks/686.js +6 -2
  95. package/.next/server/chunks/757.js +1 -0
  96. package/.next/server/chunks/775.js +3 -0
  97. package/.next/server/middleware-build-manifest.js +1 -1
  98. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  99. package/.next/server/pages/404.html +1 -1
  100. package/.next/server/pages/500.html +1 -1
  101. package/.next/server/server-reference-manifest.json +1 -1
  102. package/.next/static/0t1jBUAuVaTFTXxRyIo3q/_buildManifest.js +1 -0
  103. package/.next/static/chunks/2608.43c58236c9cce8e6.js +1 -0
  104. package/.next/static/chunks/{2716.a9e808190a4e2424.js → 2716.4ec42df1d51354c9.js} +1 -1
  105. package/.next/static/chunks/4632.62139b565b657aac.js +1 -0
  106. package/.next/static/chunks/{4673.d3d0a7f2599080fb.js → 4673.7796104395deb8d7.js} +1 -1
  107. package/.next/static/chunks/4686-17f21a006739a596.js +1 -0
  108. package/.next/static/chunks/7577.10af9bb661652da8.js +1 -0
  109. package/.next/static/chunks/8866.38ec4ac4ee5a1b74.js +1 -0
  110. package/.next/static/chunks/app/_global-error/page-5148173b3f55e415.js +1 -0
  111. package/.next/static/chunks/app/api/agent/[id]/events/route-5148173b3f55e415.js +1 -0
  112. package/.next/static/chunks/app/api/agent/[id]/route-5148173b3f55e415.js +1 -0
  113. package/.next/static/chunks/app/api/agent/[id]/tree/route-5148173b3f55e415.js +1 -0
  114. package/.next/static/chunks/app/api/agent/new/route-5148173b3f55e415.js +1 -0
  115. package/.next/static/chunks/app/api/config/route-5148173b3f55e415.js +1 -0
  116. package/.next/static/chunks/app/api/files/route-5148173b3f55e415.js +1 -0
  117. package/.next/static/chunks/app/api/git/route-5148173b3f55e415.js +1 -0
  118. package/.next/static/chunks/app/api/growth/route-5148173b3f55e415.js +1 -0
  119. package/.next/static/chunks/app/api/health/route-5148173b3f55e415.js +1 -0
  120. package/.next/static/chunks/app/api/models/route-5148173b3f55e415.js +1 -0
  121. package/.next/static/chunks/app/api/pi-settings/route-5148173b3f55e415.js +1 -0
  122. package/.next/static/chunks/app/api/plugins/route-5148173b3f55e415.js +1 -0
  123. package/.next/static/chunks/app/api/prompts/route-5148173b3f55e415.js +1 -0
  124. package/.next/static/chunks/app/api/security/route-5148173b3f55e415.js +1 -0
  125. package/.next/static/chunks/app/api/sessions/[id]/export/route-5148173b3f55e415.js +1 -0
  126. package/.next/static/chunks/app/api/sessions/[id]/route-5148173b3f55e415.js +1 -0
  127. package/.next/static/chunks/app/api/sessions/import/route-5148173b3f55e415.js +1 -0
  128. package/.next/static/chunks/app/api/sessions/route-5148173b3f55e415.js +1 -0
  129. package/.next/static/chunks/app/api/skills/route-5148173b3f55e415.js +1 -0
  130. package/.next/static/chunks/app/api/swarm/[id]/route-5148173b3f55e415.js +1 -0
  131. package/.next/static/chunks/app/api/swarm/route-5148173b3f55e415.js +1 -0
  132. package/.next/static/chunks/app/api/tests/route-5148173b3f55e415.js +1 -0
  133. package/.next/static/chunks/app/api/update/route-5148173b3f55e415.js +1 -0
  134. package/.next/static/chunks/app/api/version/route-5148173b3f55e415.js +1 -0
  135. package/.next/static/chunks/app/api/web-auth/route-5148173b3f55e415.js +1 -0
  136. package/.next/static/chunks/app/api/workspaces/route-5148173b3f55e415.js +1 -0
  137. package/.next/static/chunks/app/page-14379b8060d5cf07.js +1 -0
  138. package/.next/static/chunks/next/dist/client/components/builtin/app-error-5148173b3f55e415.js +1 -0
  139. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-5148173b3f55e415.js +1 -0
  140. package/.next/static/chunks/next/dist/client/components/builtin/not-found-5148173b3f55e415.js +1 -0
  141. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-5148173b3f55e415.js +1 -0
  142. package/.next/static/chunks/webpack-d1a69ad9edb38b11.js +1 -0
  143. package/.next/static/css/d0a0162cb481abe8.css +1 -0
  144. package/.next/types/app/api/swarm/[id]/route.ts +353 -0
  145. package/.next/types/app/api/swarm/route.ts +353 -0
  146. package/.next/types/app/api/tests/route.ts +353 -0
  147. package/.next/types/routes.d.ts +4 -1
  148. package/.next/types/validator.ts +27 -0
  149. package/README.md +61 -7
  150. package/assets/archive.png +0 -0
  151. package/assets/growth-1.png +0 -0
  152. package/assets/growth-2.png +0 -0
  153. package/assets/import.png +0 -0
  154. package/assets/main.png +0 -0
  155. package/assets/outline.png +0 -0
  156. package/assets/prompts-1.png +0 -0
  157. package/assets/prompts-2.png +0 -0
  158. package/assets/providers-1.png +0 -0
  159. package/assets/providers-2.png +0 -0
  160. package/assets/providers-3.png +0 -0
  161. package/assets/trace-1.png +0 -0
  162. package/assets/trace-2.png +0 -0
  163. package/assets/trace-3.png +0 -0
  164. package/assets/trace-4.png +0 -0
  165. package/assets/viewer-1.png +0 -0
  166. package/assets/viewer-2.png +0 -0
  167. package/next.config.ts +6 -2
  168. package/package.json +10 -5
  169. package/scripts/launcher.mjs +1 -1
  170. package/scripts/managed-niubash-assets.mjs +146 -0
  171. package/scripts/pick-folder.ps1 +11 -2
  172. package/scripts/prepare-niubash-packages.mjs +182 -0
  173. package/scripts/release.mjs +11 -31
  174. package/scripts/run-test-file.mjs +34 -0
  175. package/scripts/smoke-managed-niubash.mjs +62 -0
  176. package/scripts/verify-package.mjs +53 -0
  177. package/scripts/verify-release-config.mjs +24 -0
  178. package/src/app/api/config/route.ts +4 -1
  179. package/src/app/api/security/route.ts +8 -3
  180. package/src/app/api/swarm/[id]/route.ts +28 -0
  181. package/src/app/api/swarm/route.ts +24 -0
  182. package/src/app/api/tests/route.ts +27 -0
  183. package/src/app/api/update/route.ts +8 -2
  184. package/src/app/globals.css +777 -0
  185. package/src/app/layout.tsx +13 -6
  186. package/src/components/AppShell.tsx +505 -199
  187. package/src/components/ChatInput.tsx +153 -26
  188. package/src/components/ChatWindow.tsx +46 -40
  189. package/src/components/ContextMeter.tsx +11 -17
  190. package/src/components/FileViewer.tsx +1 -1
  191. package/src/components/GitPanel.tsx +13 -11
  192. package/src/components/ModelSelector.tsx +19 -6
  193. package/src/components/PiMark.tsx +5 -38
  194. package/src/components/ProjectPanel.tsx +1 -0
  195. package/src/components/PromptPanel.tsx +132 -51
  196. package/src/components/SessionImportSection.tsx +53 -37
  197. package/src/components/SessionSidebar.tsx +159 -57
  198. package/src/components/SettingsPanel.tsx +330 -193
  199. package/src/components/SwarmCoordinatorPod.tsx +1021 -0
  200. package/src/components/TestInspectorPod.tsx +549 -0
  201. package/src/components/icons.tsx +23 -0
  202. package/src/hooks/usePiWeb.ts +112 -8
  203. package/src/i18n.tsx +59 -3
  204. package/src/lib/agent-manager.ts +182 -11
  205. package/src/lib/command-validation.ts +9 -1
  206. package/src/lib/delegation.ts +93 -0
  207. package/src/lib/managed-niubash.ts +231 -0
  208. package/src/lib/pi.ts +3 -2
  209. package/src/lib/runtime-activity.ts +27 -0
  210. package/src/lib/session-import/claude.ts +13 -2
  211. package/src/lib/session-import/codex.ts +44 -8
  212. package/src/lib/session-import/files.ts +14 -0
  213. package/src/lib/session-import/grok.ts +54 -24
  214. package/src/lib/session-import/index.ts +5 -0
  215. package/src/lib/session-import/lock.ts +25 -0
  216. package/src/lib/session-import/types.ts +12 -0
  217. package/src/lib/session-import/writer.ts +8 -3
  218. package/src/lib/skills-service.ts +16 -9
  219. package/src/lib/swarm-service.ts +371 -0
  220. package/src/lib/test-run-service.ts +122 -0
  221. package/src/lib/theme.ts +68 -1
  222. package/src/lib/trajectory.ts +1 -0
  223. package/src/lib/types.ts +14 -0
  224. package/src/lib/workflow.ts +55 -0
  225. package/.next/static/LOFl9bqqMV2goBV8erESO/_buildManifest.js +0 -1
  226. package/.next/static/chunks/2608.ec8ff1f5767c9056.js +0 -1
  227. package/.next/static/chunks/4632.112d2e893d48d188.js +0 -1
  228. package/.next/static/chunks/4686-7d694def920ff093.js +0 -1
  229. package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +0 -1
  230. package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +0 -1
  231. package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +0 -1
  232. package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +0 -1
  233. package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +0 -1
  234. package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +0 -1
  235. package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +0 -1
  236. package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +0 -1
  237. package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +0 -1
  238. package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +0 -1
  239. package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +0 -1
  240. package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +0 -1
  241. package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +0 -1
  242. package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +0 -1
  243. package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +0 -1
  244. package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +0 -1
  245. package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +0 -1
  246. package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +0 -1
  247. package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +0 -1
  248. package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +0 -1
  249. package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +0 -1
  250. package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +0 -1
  251. package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +0 -1
  252. package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +0 -1
  253. package/.next/static/chunks/app/page-9a5440047c5a415c.js +0 -1
  254. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +0 -1
  255. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +0 -1
  256. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +0 -1
  257. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +0 -1
  258. package/.next/static/chunks/webpack-d28e132e1b8c100c.js +0 -1
  259. package/.next/static/css/b2d35abc828d4713.css +0 -1
  260. package/assets/showcase.png +0 -0
  261. package/bin/pi.js +0 -50
  262. /package/.next/static/{LOFl9bqqMV2goBV8erESO → 0t1jBUAuVaTFTXxRyIo3q}/_ssgManifest.js +0 -0
@@ -13,6 +13,7 @@ import type {
13
13
  WebMessage,
14
14
  WebSnapshot,
15
15
  ToolPreset,
16
+ WorkflowMode,
16
17
  } from "@/lib/types";
17
18
 
18
19
  export interface ToolCardState {
@@ -64,6 +65,22 @@ export interface PiWebState {
64
65
  growth: { steps: GrowthStep[]; pending: string[]; error: string | null };
65
66
  }
66
67
 
68
+ export function previewEditedBranch(messages: WebMessage[], entryId: string, text: string): WebMessage[] | null {
69
+ const index = messages.findIndex((message) => message.role === "user" && message.id === entryId);
70
+ if (index < 0) return null;
71
+ const original = messages[index];
72
+ return [
73
+ ...messages.slice(0, index),
74
+ {
75
+ ...original,
76
+ content: [
77
+ { type: "text" as const, text },
78
+ ...original.content.filter((part) => part.type === "image"),
79
+ ],
80
+ },
81
+ ];
82
+ }
83
+
67
84
  export interface SessionListItem extends SessionSummary {
68
85
  streaming: boolean;
69
86
  /** 正在运行的会话最后一个工具步骤(侧栏副标题) */
@@ -191,6 +208,9 @@ export const foldPiWebEvent = (state: PiWebState, evt: WebEvent): PiWebState =>
191
208
  tools: { active: evt.active, all: evt.all.map((n) => ({ name: n })) },
192
209
  };
193
210
  return s;
211
+ case "workflow":
212
+ if (s.snapshot) s.snapshot = { ...s.snapshot, workflow: evt.workflow };
213
+ return s;
194
214
  case "resources":
195
215
  if (s.snapshot) {
196
216
  s.snapshot = {
@@ -335,14 +355,20 @@ export function usePiWeb() {
335
355
  const [currentId, setCurrentId] = useState<string | null>(null);
336
356
  const [currentPath, setCurrentPath] = useState<string | null>(null);
337
357
  const [state, setState] = useState<PiWebState>(emptyState);
358
+ const stateRef = useRef(state);
359
+ stateRef.current = state;
338
360
  /** 当前 SSE 连接的增量批量器:按帧合并事件,避免每个 token 一次整树渲染 */
339
361
  const batcherRef = useRef<EventBatcher<WebEvent> | null>(null);
340
362
  const [models, setModels] = useState<{ providers: any[]; models: any[] } | null>(null);
363
+ const [modelLoading, setModelLoading] = useState(true);
364
+ const [modelLoadError, setModelLoadError] = useState<string | null>(null);
365
+ const modelRequestRef = useRef(0);
341
366
  const [addedWorkspaces, setAddedWorkspaces] = useState<string[]>([]);
342
367
  const [removedWorkspaces, setRemovedWorkspaces] = useState<string[]>([]);
343
368
  const [groupBy, setGroupByState] = useState<"workspace" | "flat">("workspace");
344
369
  const [orderBy, setOrderByState] = useState<"updated" | "manual">("updated");
345
370
  const [resyncNonce, setResyncNonce] = useState(0);
371
+ const resync = useCallback(() => setResyncNonce((nonce) => nonce + 1), []);
346
372
  const [snapshotEpoch, setSnapshotEpoch] = useState(0);
347
373
  const esRef = useRef<EventSource | null>(null);
348
374
  const promptRequestsRef = useRef(new Set<string>());
@@ -350,6 +376,7 @@ export function usePiWeb() {
350
376
  const presetRestorePromiseRef = useRef<Promise<void> | null>(null);
351
377
  const activeSessionRef = useRef(currentId);
352
378
  activeSessionRef.current = currentId;
379
+ const editSyncRef = useRef<{ sessionId: string; phase: "submitting" | "resyncing"; error?: string } | null>(null);
353
380
  const subscribedSessionRef = useRef<string | null>(null);
354
381
  const retrySessionList = useCallback(() => setSessionListRetryNonce((value) => value + 1), []);
355
382
  const clearCompaction = useCallback(() => setState((current) => ({ ...current, compaction: null })), []);
@@ -505,6 +532,28 @@ export function usePiWeb() {
505
532
  }
506
533
  }, [refreshWorkspaces]);
507
534
 
535
+ /** 直接添加指定路径到工作区列表;成功返回规范化路径 */
536
+ const addWorkspaceByPath = useCallback(async (dir: string): Promise<string | null> => {
537
+ try {
538
+ const r = await fetch("/api/workspaces", {
539
+ method: "POST",
540
+ headers: { "Content-Type": "application/json" },
541
+ body: JSON.stringify({ action: "add", path: dir }),
542
+ });
543
+ const j = await r.json();
544
+ if (!r.ok || !j.success) throw new Error(j.error || `request failed (${r.status})`);
545
+ if (j.data?.workspaces) {
546
+ setAddedWorkspaces(j.data.workspaces);
547
+ }
548
+ await refreshWorkspaces();
549
+ return dir;
550
+ } catch (error) {
551
+ const message = error instanceof Error ? error.message : "failed to add workspace";
552
+ setState((current) => ({ ...current, error: message }));
553
+ throw error;
554
+ }
555
+ }, [refreshWorkspaces]);
556
+
508
557
 
509
558
  const removeWorkspace = useCallback(
510
559
  async (dir: string) => {
@@ -593,12 +642,20 @@ export function usePiWeb() {
593
642
 
594
643
  // 模型目录
595
644
  const refreshModels = useCallback(async () => {
645
+ const request = ++modelRequestRef.current;
646
+ setModelLoading(true);
647
+ setModelLoadError(null);
596
648
  try {
597
649
  const r = await fetch("/api/models");
598
650
  const j = await r.json();
599
- if (j.success) setModels({ providers: j.data.providers, models: j.data.models });
600
- } catch {
601
- /* ignore */
651
+ if (!r.ok || !j.success || !Array.isArray(j.data?.providers) || !Array.isArray(j.data?.models)) {
652
+ throw new Error(j.error || `request failed (${r.status})`);
653
+ }
654
+ if (request === modelRequestRef.current) setModels({ providers: j.data.providers, models: j.data.models });
655
+ } catch (error) {
656
+ if (request === modelRequestRef.current) setModelLoadError(error instanceof Error ? error.message : "model catalog unavailable");
657
+ } finally {
658
+ if (request === modelRequestRef.current) setModelLoading(false);
602
659
  }
603
660
  }, []);
604
661
  useEffect(() => {
@@ -610,14 +667,17 @@ export function usePiWeb() {
610
667
  esRef.current?.close();
611
668
  esRef.current = null;
612
669
  if (!currentId) {
670
+ editSyncRef.current = null;
613
671
  subscribedSessionRef.current = null;
614
672
  setState(emptyState(savedToolPreset()));
615
673
  return;
616
674
  }
617
675
  const reconnectingCurrentSession = subscribedSessionRef.current === currentId;
676
+ if (!reconnectingCurrentSession) editSyncRef.current = null;
618
677
  subscribedSessionRef.current = currentId;
678
+ const editResync = editSyncRef.current?.sessionId === currentId && editSyncRef.current.phase === "resyncing";
619
679
  setState((current) => reconnectingCurrentSession
620
- ? { ...current, connected: false }
680
+ ? { ...current, connected: editResync ? current.connected : false }
621
681
  : { ...emptyState(savedToolPreset()), connected: false });
622
682
  let retryDelayMs = 2000;
623
683
  const connect = () => {
@@ -642,7 +702,10 @@ export function usePiWeb() {
642
702
  };
643
703
  // 流式事件按帧合并:token 速率再高也只每 16ms 渲染一次(见 event-batcher)
644
704
  const batcher = createEventBatcher<WebEvent>({
645
- flush: (events) => setState((s) => events.reduce((acc, evt) => foldPiWebEvent(acc, evt), s)),
705
+ flush: (events) => {
706
+ if (editSyncRef.current?.sessionId === currentId) return;
707
+ setState((s) => events.reduce((acc, evt) => foldPiWebEvent(acc, evt), s));
708
+ },
646
709
  });
647
710
  batcherRef.current = batcher;
648
711
  es.onmessage = (e) => {
@@ -654,6 +717,9 @@ export function usePiWeb() {
654
717
  return;
655
718
  }
656
719
  if (parsed.type === "snapshot" && parsed.snapshot) {
720
+ const editSync = editSyncRef.current;
721
+ if (editSync?.sessionId === currentId && editSync.phase === "submitting") return;
722
+ if (editSync?.sessionId === currentId) editSyncRef.current = null;
657
723
  // 快照是权威状态:之前缓冲的增量已经过时,直接丢弃
658
724
  batcher.cancel();
659
725
  retryDelayMs = 2000;
@@ -665,7 +731,7 @@ export function usePiWeb() {
665
731
  tools: toolsFromMessages(snap.messages, snap.isStreaming),
666
732
  toolPreset: snap.toolPreset,
667
733
  connected: true,
668
- error: null,
734
+ error: editSync?.sessionId === currentId ? editSync.error ?? null : null,
669
735
  compaction: null,
670
736
  retryNotice: null,
671
737
  // 服务器当前 pending 集合是权威来源,刷新可恢复、已答请求不能复活。
@@ -677,6 +743,7 @@ export function usePiWeb() {
677
743
  growth: { ...prev.growth, error: snap.growthError ?? null },
678
744
  }));
679
745
  } else {
746
+ if (editSyncRef.current?.sessionId === currentId) return;
680
747
  batcher.push(parsed as WebEvent);
681
748
  }
682
749
  };
@@ -719,9 +786,42 @@ export function usePiWeb() {
719
786
  }
720
787
  }, [currentId]);
721
788
 
789
+ const editAndResend = useCallback(async (entryId: string, text: string) => {
790
+ if (editSyncRef.current?.sessionId === currentId) return { success: false, error: "edit already in progress" };
791
+ if (!currentId || !previewEditedBranch(stateRef.current.messages, entryId, text)) return { success: false, error: "message is not editable" };
792
+ editSyncRef.current = { sessionId: currentId, phase: "submitting" };
793
+ batcherRef.current?.cancel();
794
+ setState((previous) => {
795
+ const messages = previewEditedBranch(previous.messages, entryId, text);
796
+ if (!messages) return previous;
797
+ return {
798
+ ...previous,
799
+ messages,
800
+ tools: toolsFromMessages(messages),
801
+ snapshot: previous.snapshot ? { ...previous.snapshot, messages, isStreaming: true } : null,
802
+ error: null,
803
+ retryNotice: null,
804
+ workingMessage: null,
805
+ };
806
+ });
807
+ const result = await sendCommand({ cmd: "editAndResend", entryId, text });
808
+ if (editSyncRef.current?.sessionId === currentId) {
809
+ editSyncRef.current = {
810
+ sessionId: currentId,
811
+ phase: "resyncing",
812
+ error: result?.success ? undefined : result?.error ?? "Failed to edit message",
813
+ };
814
+ batcherRef.current?.cancel();
815
+ esRef.current?.close();
816
+ esRef.current = null;
817
+ resync();
818
+ }
819
+ return result;
820
+ }, [currentId, resync, sendCommand]);
821
+
722
822
  const newSession = useCallback(async (
723
823
  cwd: string,
724
- overrides: { provider?: string; modelId?: string; thinking?: string } = {},
824
+ overrides: { provider?: string; modelId?: string; thinking?: string; mode?: WorkflowMode } = {},
725
825
  ): Promise<string | null> => {
726
826
  try {
727
827
  const r = await fetch("/api/agent/new", {
@@ -735,6 +835,7 @@ export function usePiWeb() {
735
835
  const id = idOf(p);
736
836
  // 工具预设取通用设置的持久化选择;模型/思考级别由 Hero 页显式下发
737
837
  const setup: Record<string, unknown>[] = [{ cmd: "setToolPreset", preset: savedToolPreset() }];
838
+ if (overrides.mode && overrides.mode !== "agent") setup.push({ cmd: "setWorkflowMode", mode: overrides.mode });
738
839
  if (overrides.provider && overrides.modelId) setup.push({ cmd: "setModel", provider: overrides.provider, modelId: overrides.modelId });
739
840
  if (overrides.thinking) setup.push({ cmd: "setThinkingLevel", level: overrides.thinking });
740
841
  for (const command of setup) {
@@ -828,7 +929,6 @@ export function usePiWeb() {
828
929
  // ---------- 会话预设已移除:工具档位见通用设置/工具启用情况,模型与思考级别由 Hero 页显式下发 ----------
829
930
 
830
931
  /** 强制重连 SSE 拿新快照(navigate 等场景) */
831
- const resync = useCallback(() => setResyncNonce((n) => n + 1), []);
832
932
  /** 应答扩展对话框(select/confirm/input);cancelled=true 表示用户关掉了 */
833
933
  const answerExtensionDialog = useCallback(
834
934
  async (id: string, response: { value?: string; confirmed?: boolean; cancelled?: boolean }) => {
@@ -887,6 +987,8 @@ export function usePiWeb() {
887
987
  currentPath,
888
988
  state,
889
989
  models,
990
+ modelLoading,
991
+ modelLoadError,
890
992
  addedWorkspaces,
891
993
  removedWorkspaces,
892
994
  workspaceAliases,
@@ -901,9 +1003,11 @@ export function usePiWeb() {
901
1003
  setGroupBy,
902
1004
  setOrderBy,
903
1005
  addWorkspaceByPicker,
1006
+ addWorkspaceByPath,
904
1007
  removeWorkspace,
905
1008
  refreshModels,
906
1009
  sendCommand,
1010
+ editAndResend,
907
1011
  newSession,
908
1012
  openSession,
909
1013
  closeSession,
package/src/i18n.tsx CHANGED
@@ -13,6 +13,8 @@ const zh = {
13
13
  rename: "重命名",
14
14
  renameSession: "重命名会话",
15
15
  renameWorkspace: "重命名工作区",
16
+ sessionActions: "会话操作",
17
+ workspaceActions: "工作区操作",
16
18
  search: "搜索",
17
19
  save: "保存",
18
20
  close: "关闭",
@@ -71,6 +73,23 @@ const zh = {
71
73
  backToBottom: "回到底部",
72
74
  modeSteer: "插话",
73
75
  modeQueue: "排队",
76
+ workflowAgent: "Agent",
77
+ workflowPlan: "Plan",
78
+ workflowGoal: "Goal",
79
+ workflowMenu: "选择工作模式",
80
+ workflowClear: "退出工作模式",
81
+ workflowPlanPlaceholder: "规划改动…",
82
+ workflowGoalPlaceholder: "描述要达成的目标…",
83
+ workflowAgentHint: "直接执行任务",
84
+ workflowPlanHint: "先只读规划,确认后执行",
85
+ workflowGoalHint: "围绕目标持续推进并验证",
86
+ planReady: "方案已就绪,等待你确认",
87
+ planReadyHint: "确认后开始修改代码并验证结果",
88
+ planRefine: "调整方案",
89
+ planApproving: "正在启动…",
90
+ planApprovalFailed: "启动失败,请重试",
91
+ planApprove: "执行方案",
92
+ goalActive: "当前目标",
74
93
  sendSteer: "插话:当前工具跑完就送达",
75
94
  sendQueue: "排队:本轮结束后发送",
76
95
  queueEdit: "↑ 取回编辑",
@@ -197,6 +216,7 @@ const zh = {
197
216
  webAuthOn: "已启用(PI_WEB_PASSWORD)",
198
217
  webAuthOff: "未启用(仅本机可访问)",
199
218
  logout: "退出登录",
219
+ logoutFailed: "退出登录失败,请检查网络后重试。",
200
220
  authMethodApiKey: "API 密钥",
201
221
  oauthDevicePrompt: "请在浏览器中完成授权",
202
222
  oauthOpenAuth: "打开授权页面",
@@ -235,6 +255,8 @@ const zh = {
235
255
  noSessions: "暂无会话",
236
256
  // Hero
237
257
  startWith: "新建工作区",
258
+ heroTitle: "让想法落到代码。",
259
+ heroDescription: "规划、执行、验证。一个专注于交付的轻量工作台。",
238
260
  // 输入卡
239
261
  inputPlaceholder: "给 pi 发消息…",
240
262
  thinking: "思考",
@@ -244,6 +266,8 @@ const zh = {
244
266
  triggerLoading: "正在加载模型…",
245
267
  blockedComposer: "当前模型不可用,请先选择模型",
246
268
  emptyModels: "没有可用的模型。",
269
+ modelsLoading: "正在加载模型…",
270
+ modelsLoadFailed: "模型列表加载失败。",
247
271
  emptyEfforts: "当前模型未提供推理等级。",
248
272
  thinkingUnsupported: "此模型不支持",
249
273
  thinkingUnsupportedHint: "模型没有声明推理能力。自定义模型请在 ~/.pi/agent/models.json 里给该模型加 \"reasoning\": true(可选 thinkingLevelMap 指定档位)",
@@ -317,8 +341,9 @@ const zh = {
317
341
  contextAgentToolCall: "Agent Tool Call",
318
342
  contextToolOutput: "Tool Output",
319
343
  contextCompacted: "Compacted Data",
320
- contextAutoCompactBuffer: "Auto-Compact Buffer",
321
- contextFreeSpace: "Free Space",
344
+ contextAutoCompactBuffer: "自动压缩预留(非占用)",
345
+ contextFreeSpace: "剩余空间",
346
+ contextEstimateNote: "总量由 Pi 估算;下方分类按字符估算,不可相加。",
322
347
  // 统计条
323
348
  turns: "轮次",
324
349
  toolCalls: "工具调用",
@@ -414,6 +439,7 @@ const zh = {
414
439
  themeNordPaper: "北欧纸墨",
415
440
  themePebrel: "Pebrel",
416
441
  themeDsh: "海与墨",
442
+ themePiWeb: "PiWeb · 工作台",
417
443
  fontSize: "正文字号",
418
444
  fontUiSize: "界面字体",
419
445
  fontUiSizeDesc: "侧栏、按钮、标签等界面文字",
@@ -506,6 +532,8 @@ const zh = {
506
532
  updateFailed: "更新失败",
507
533
  updateManualHint: "先停止 piweb,再执行上面的命令(Windows 无法替换运行中的安装目录)",
508
534
  skillsDesc: "关闭后技能不会出现在系统提示中,仅可通过 /skill:名称 手动调用",
535
+ skillsLoading: "正在加载技能…",
536
+ skillsEmpty: "当前工作区没有可用技能",
509
537
  confirmDeleteSkill: "确定删除技能“{name}”吗?将删除其所在技能目录,不可恢复。",
510
538
  deleteSkillHint: "删除技能目录",
511
539
  skillDeletePackageHint: "包技能请用禁用管理",
@@ -619,6 +647,8 @@ const en: Dict = {
619
647
  rename: "Rename",
620
648
  renameSession: "Rename session",
621
649
  renameWorkspace: "Rename workspace",
650
+ sessionActions: "Session actions",
651
+ workspaceActions: "Workspace actions",
622
652
  search: "Search",
623
653
  save: "Save",
624
654
  close: "Close",
@@ -676,6 +706,23 @@ const en: Dict = {
676
706
  backToBottom: "Back to bottom",
677
707
  modeSteer: "steer",
678
708
  modeQueue: "queue",
709
+ workflowAgent: "Agent",
710
+ workflowPlan: "Plan",
711
+ workflowGoal: "Goal",
712
+ workflowMenu: "Choose workflow mode",
713
+ workflowClear: "Leave workflow mode",
714
+ workflowPlanPlaceholder: "Plan changes…",
715
+ workflowGoalPlaceholder: "Describe the goal…",
716
+ workflowAgentHint: "Execute the task directly",
717
+ workflowPlanHint: "Plan read-only, then request approval",
718
+ workflowGoalHint: "Pursue and verify an objective",
719
+ planReady: "Plan ready for your approval",
720
+ planReadyHint: "Confirm to make changes and verify the result",
721
+ planRefine: "Refine plan",
722
+ planApproving: "Starting…",
723
+ planApprovalFailed: "Could not start. Try again.",
724
+ planApprove: "Execute plan",
725
+ goalActive: "Current goal",
679
726
  sendSteer: "Steer: delivered after the current tool call",
680
727
  sendQueue: "Queue: sent after this turn ends",
681
728
  queueEdit: "↑ Edit queued",
@@ -802,6 +849,7 @@ const en: Dict = {
802
849
  webAuthOn: "Enabled (PI_WEB_PASSWORD)",
803
850
  webAuthOff: "Disabled (local access only)",
804
851
  logout: "Sign out",
852
+ logoutFailed: "Could not sign out. Check your connection and retry.",
805
853
  authMethodApiKey: "API key",
806
854
  oauthDevicePrompt: "Finish authorization in your browser",
807
855
  oauthOpenAuth: "Open authorization page",
@@ -839,6 +887,8 @@ const en: Dict = {
839
887
  oauthCancel: "Cancel login",
840
888
  noSessions: "No sessions yet",
841
889
  startWith: "New workspace",
890
+ heroTitle: "Move ideas into working code.",
891
+ heroDescription: "Plan, execute, verify. A focused workbench for shipping.",
842
892
  inputPlaceholder: "Message pi…",
843
893
  thinking: "Thinking",
844
894
  thinkingOff: "Off",
@@ -847,6 +897,8 @@ const en: Dict = {
847
897
  triggerLoading: "Loading models…",
848
898
  blockedComposer: "This model is unavailable — select one to continue",
849
899
  emptyModels: "No models available.",
900
+ modelsLoading: "Loading models…",
901
+ modelsLoadFailed: "Could not load models.",
850
902
  emptyEfforts: "This model provides no reasoning effort levels.",
851
903
  thinkingUnsupported: "Not supported by this model",
852
904
  thinkingUnsupportedHint: "The model does not declare reasoning support. For custom models, add \"reasoning\": true (optionally a thinkingLevelMap) to the model in ~/.pi/agent/models.json",
@@ -920,8 +972,9 @@ const en: Dict = {
920
972
  contextAgentToolCall: "Agent Tool Call",
921
973
  contextToolOutput: "Tool Output",
922
974
  contextCompacted: "Compacted Data",
923
- contextAutoCompactBuffer: "Auto-Compact Buffer",
975
+ contextAutoCompactBuffer: "Auto-compact reserve (not used)",
924
976
  contextFreeSpace: "Free Space",
977
+ contextEstimateNote: "Pi estimates total usage; content categories are rough estimates and do not add up.",
925
978
  turns: "turns",
926
979
  toolCalls: "tool calls",
927
980
  cost: "cost",
@@ -1013,6 +1066,7 @@ const en: Dict = {
1013
1066
  themeNordPaper: "Nord & Paper",
1014
1067
  themePebrel: "Pebrel",
1015
1068
  themeDsh: "Sea & Ink",
1069
+ themePiWeb: "PiWeb · Workbench",
1016
1070
  fontSize: "Font size",
1017
1071
  fontUiSize: "UI font size",
1018
1072
  fontUiSizeDesc: "Sidebar, buttons, labels and other UI text",
@@ -1105,6 +1159,8 @@ const en: Dict = {
1105
1159
  updateFailed: "Update failed",
1106
1160
  updateManualHint: "Stop piweb, then run the command above (Windows cannot replace a running installation)",
1107
1161
  skillsDesc: "Disabled skills are hidden from the system prompt and only callable via /skill:name",
1162
+ skillsLoading: "Loading skills…",
1163
+ skillsEmpty: "No skills available in this workspace",
1108
1164
  confirmDeleteSkill: "Delete skill \"{name}\"? Its skill directory will be removed and cannot be recovered.",
1109
1165
  deleteSkillHint: "Delete the skill directory",
1110
1166
  skillDeletePackageHint: "package skill: use disable",