@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
@@ -7,10 +7,10 @@
7
7
  */
8
8
  import path from "node:path";
9
9
  import fs from "node:fs/promises";
10
- import { eachJsonLine, listFiles, readHead, readTail, reachedLimit, statMtime } from "./files";
10
+ import { eachJsonLine, listFiles, readHead, reachedLimit, sortByRecency } from "./files";
11
11
  import { codexRoot } from "./paths";
12
12
  import type { ExternalSessionSummary, ImportedBlock, ImportedEntry, ImportedSession, ImportSourceModule, ScanOptions } from "./types";
13
- import { cleanTitle, compactBlocks, dataUrlImageBlock, outputBlocks, parseArguments, textBlock, toEpochMs } from "./types";
13
+ import { cleanTitle, compactBlocks, outputBlocks, parseArguments, textBlock, toEpochMs } from "./types";
14
14
 
15
15
  const ROOT = codexRoot;
16
16
 
@@ -80,7 +80,6 @@ function messageBlocks(payload: Record<string, unknown>): ImportedBlock[] {
80
80
  if (!Array.isArray(content)) return compactBlocks([textBlock(typeof content === "string" ? content : undefined)]);
81
81
  return compactBlocks(content.flatMap((raw): Array<ImportedBlock | null> => {
82
82
  const block = raw as Record<string, unknown>;
83
- if (block?.type === "input_image") return [dataUrlImageBlock(block.image_url, block.mime_type)];
84
83
  if (typeof block?.text === "string") return [textBlock(block.text)];
85
84
  return [];
86
85
  }));
@@ -96,17 +95,6 @@ function reasoningText(payload: Record<string, unknown>): ImportedBlock | null {
96
95
  return null;
97
96
  }
98
97
 
99
- /** 文件 mtime 可能因复制/恢复失真;Codex 每行自带时间,末尾记录才是权威的最近活动时间 */
100
- function latestTimestamp(records: Record<string, unknown>[]): number | undefined {
101
- let latest: number | undefined;
102
- for (const record of records) {
103
- const payload = record.payload as Record<string, unknown> | undefined;
104
- const at = toEpochMs(record.timestamp) ?? toEpochMs(payload?.timestamp);
105
- if (at !== undefined) latest = Math.max(latest ?? at, at);
106
- }
107
- return latest;
108
- }
109
-
110
98
  /** 标题:Codex 不写标题字段,用首条真人用户消息(rollout 头几十行里就有) */
111
99
  function titleFrom(records: Record<string, unknown>[]): string | undefined {
112
100
  for (const record of records) {
@@ -125,22 +113,10 @@ export const codexSource: ImportSourceModule = {
125
113
 
126
114
  async scan(options?: ScanOptions): Promise<ExternalSessionSummary[]> {
127
115
  const limit = options?.limit ?? 0;
128
- const files = await listFiles(ROOT(), { match: (name) => name.startsWith("rollout-") && name.endsWith(".jsonl") });
129
- // 只读每个文件末尾 64KB 就能按记录时间正确排序;不能先按 mtime 截断,否则复制过的旧文件会挤掉真正最近的会话。
130
- const candidates = await Promise.all(files.map(async (file) => {
131
- const mtime = await statMtime(file);
132
- try {
133
- const updatedAt = latestTimestamp([...eachJsonLine(await readTail(file))]);
134
- return { file, mtime, updatedAt: updatedAt ?? mtime };
135
- } catch {
136
- return { file, mtime, updatedAt: mtime };
137
- }
138
- }));
139
- candidates.sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0));
140
-
116
+ const candidates = await sortByRecency(await listFiles(ROOT(), { match: (name) => name.startsWith("rollout-") && name.endsWith(".jsonl") }));
141
117
  const out: ExternalSessionSummary[] = [];
142
- for (const { file, mtime, updatedAt } of candidates) {
143
- if (reachedLimit(out.length, limit)) break;
118
+ for (const { file, mtime } of candidates) {
119
+ if (reachedLimit(out.length, limit)) break; // 只读最近的若干条
144
120
  let head: string;
145
121
  try {
146
122
  head = await readHead(file);
@@ -160,7 +136,7 @@ export const codexSource: ImportSourceModule = {
160
136
  model: meta.model,
161
137
  provider: meta.provider,
162
138
  createdAt: meta.createdAt ?? mtime,
163
- updatedAt,
139
+ updatedAt: mtime,
164
140
  location: file,
165
141
  });
166
142
  }
@@ -178,18 +154,11 @@ export const codexSource: ImportSourceModule = {
178
154
  const skipped: string[] = [];
179
155
  let synthetic = 0;
180
156
  let reasoningSkipped = 0;
181
- let runtimeContext = 0;
182
157
  let meta: Meta = { cwd: summary.projectPath, createdAt: summary.createdAt, model: summary.model, provider: summary.provider };
183
158
 
184
159
  for (const record of eachJsonLine(text)) {
185
160
  if (record.type === "session_meta") {
186
161
  meta = { ...meta, ...metaFrom([record]) };
187
- const payload = record.payload as Record<string, unknown> | undefined;
188
- if (payload?.base_instructions) runtimeContext += 1;
189
- continue;
190
- }
191
- if (record.type === "world_state" || record.type === "turn_context") {
192
- runtimeContext += 1;
193
162
  continue;
194
163
  }
195
164
  if (record.type !== "response_item") continue;
@@ -200,15 +169,11 @@ export const codexSource: ImportSourceModule = {
200
169
  switch (payload.type) {
201
170
  case "message": {
202
171
  const role = payload.role;
203
- if (role === "developer") {
204
- runtimeContext += 1; // 系统/开发者上下文由 pi 按当前工作区重新生成
205
- continue;
206
- }
172
+ if (role === "developer") continue; // 系统提示,不进对话
207
173
  const blocks = messageBlocks(payload);
208
174
  const plain = blocks.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
209
175
  if (role === "user") {
210
- // 只有图片的真人输入也必须保留;纯文本的环境/AGENTS 注入仍不冒充用户消息
211
- if (!blocks.length || (plain && isSynthetic(plain))) {
176
+ if (!plain || isSynthetic(plain)) {
212
177
  synthetic += 1;
213
178
  continue;
214
179
  }
@@ -252,7 +217,6 @@ export const codexSource: ImportSourceModule = {
252
217
 
253
218
  if (synthetic) skipped.push(`跳过 ${synthetic} 条合成输入`);
254
219
  if (reasoningSkipped) skipped.push(`跳过 ${reasoningSkipped} 条仅含加密内容的推理记录`);
255
- if (runtimeContext) skipped.push(`跳过 ${runtimeContext} 条源工具运行时上下文(pi 会按当前工作区重新生成)`);
256
220
  if (!entries.some((entry) => entry.type === "message")) return null;
257
221
  return { summary: { ...summary, ...meta, projectPath: meta.cwd, model: meta.model, provider: meta.provider }, entries, skipped };
258
222
  },
@@ -13,20 +13,6 @@ export async function readHead(file: string, bytes = 64 * 1024): Promise<string>
13
13
  }
14
14
  }
15
15
 
16
- /** 读文件末尾若干字节;首行可能是被截断的半行,JSONL 解析时会自然跳过 */
17
- export async function readTail(file: string, bytes = 64 * 1024): Promise<string> {
18
- const handle = await fs.open(file, "r");
19
- try {
20
- const size = (await handle.stat()).size;
21
- const length = Math.min(size, bytes);
22
- const buffer = Buffer.alloc(length);
23
- const { bytesRead } = await handle.read(buffer, 0, length, size - length);
24
- return buffer.subarray(0, bytesRead).toString("utf8");
25
- } finally {
26
- await handle.close();
27
- }
28
- }
29
-
30
16
  /** 逐行解析 JSONL;单行坏掉只跳过该行,不放弃整个文件 */
31
17
  export function* eachJsonLine(text: string): Generator<Record<string, unknown>> {
32
18
  for (const line of text.split("\n")) {
@@ -6,10 +6,10 @@
6
6
  */
7
7
  import path from "node:path";
8
8
  import fs from "node:fs/promises";
9
- import { eachJsonLine, listFiles, readHead, reachedLimit, statMtime } from "./files";
9
+ import { eachJsonLine, listFiles, readHead, reachedLimit, sortByRecency } from "./files";
10
10
  import { grokRoot } from "./paths";
11
11
  import type { ExternalSessionSummary, ImportedBlock, ImportedEntry, ImportedSession, ImportSourceModule, ScanOptions } from "./types";
12
- import { cleanTitle, compactBlocks, dataUrlImageBlock, outputBlocks, parseArguments, textBlock, toEpochMs } from "./types";
12
+ import { cleanTitle, compactBlocks, outputBlocks, parseArguments, textBlock, toEpochMs } from "./types";
13
13
 
14
14
  const ROOT = grokRoot;
15
15
 
@@ -30,10 +30,10 @@ function decodeProjectDir(name: string): string | undefined {
30
30
  function importable(records: Record<string, unknown>[]): boolean {
31
31
  for (const record of records) {
32
32
  if (record.type === "assistant" || record.type === "tool_result") return true;
33
- if (record.type === "reasoning" && reasoningText(record.summary)) return true;
33
+ if (record.type === "reasoning" && typeof record.summary === "string" && record.summary.trim()) return true;
34
34
  if (record.type !== "user" || record.synthetic_reason) continue;
35
35
  const blocks = userBlocks(record.content);
36
- if (blocks.some((block) => block.type === "image" || (block.type === "text" && block.text.replace(/<[^>]+>/g, "").trim()))) return true;
36
+ if (blocks.some((block) => block.type === "text" && block.text.replace(/<[^>]+>/g, "").trim())) return true;
37
37
  }
38
38
  return false;
39
39
  }
@@ -46,69 +46,41 @@ async function hasRealMessage(history: string): Promise<boolean> {
46
46
  }
47
47
  }
48
48
 
49
- /** user 的 content 可能是字符串、文本块或 data URL 图片块 */
50
- function userBlocks(content: unknown): ImportedBlock[] {
49
+ /** user 的 content 可能是字符串或块数组 */function userBlocks(content: unknown): ImportedBlock[] {
51
50
  if (typeof content === "string") return compactBlocks([textBlock(content)]);
52
51
  if (!Array.isArray(content)) return [];
53
52
  return compactBlocks(content.flatMap((raw): Array<ImportedBlock | null> => {
54
53
  if (typeof raw === "string") return [textBlock(raw)];
55
54
  const block = raw as Record<string, unknown>;
56
55
  if (typeof block?.text === "string") return [textBlock(block.text)];
57
- if (block?.type === "image") return [dataUrlImageBlock(block.url, block.mime ?? block.mimeType)];
58
56
  return [];
59
57
  }));
60
58
  }
61
59
 
62
- /** Grok 新版把 reasoning.summary 从字符串改成了 summary_text 块数组;两代格式都要读 */
63
- function reasoningText(summary: unknown): string {
64
- if (typeof summary === "string") return summary.trim();
65
- if (!Array.isArray(summary)) return "";
66
- return summary
67
- .map((item) => {
68
- if (typeof item === "string") return item;
69
- const block = item as { text?: unknown; summary_text?: unknown };
70
- return typeof block?.text === "string" ? block.text : typeof block?.summary_text === "string" ? block.summary_text : "";
71
- })
72
- .filter(Boolean)
73
- .join("\n")
74
- .trim();
75
- }
76
-
77
60
  export const grokSource: ImportSourceModule = {
78
61
  id: "grok",
79
62
 
80
63
  async scan(options?: ScanOptions): Promise<ExternalSessionSummary[]> {
64
+ // 先按 chat_history 的 mtime 排序(只 stat,不读内容),再从最近往下读:
65
+ // summary.json 里有标题/模型/时间/消息数,读它即可,正文只在确认有真实对话时碰一下头部
81
66
  const limit = options?.limit ?? 0;
82
67
  const files = await listFiles(ROOT(), { match: (name) => name === "summary.json" });
83
- // summary.updated_at / last_active_at 是 Grok 的权威活动时间。必须先按它排序再 LIMIT,
84
- // 不能用复制、恢复备份时会变化的 chat_history mtime 决定“最近 15 条”。
85
- const candidates = (await Promise.all(files.map(async (summaryFile) => {
86
- const dir = path.dirname(summaryFile);
87
- const history = path.join(dir, "chat_history.jsonl");
68
+ const candidates = await sortByRecency(files.map((file) => path.join(path.dirname(file), "chat_history.jsonl")));
69
+ const out: ExternalSessionSummary[] = [];
70
+ for (const { file: history, mtime } of candidates) {
71
+ if (reachedLimit(out.length, limit)) break; // 只读最近的若干条
72
+ const dir = path.dirname(history);
73
+ let summary: Record<string, unknown>;
88
74
  try {
89
- const summary = JSON.parse(await fs.readFile(summaryFile, "utf8")) as Record<string, unknown>;
90
- const mtime = await statMtime(history);
91
- if (mtime === undefined) return null;
92
- return {
93
- dir,
94
- history,
95
- summary,
96
- mtime,
97
- updatedAt: toEpochMs(summary.updated_at ?? summary.last_active_at) ?? mtime,
98
- };
75
+ summary = JSON.parse(await fs.readFile(path.join(dir, "summary.json"), "utf8")) as Record<string, unknown>;
99
76
  } catch {
100
- return null;
77
+ continue;
101
78
  }
102
- }))).filter((candidate): candidate is NonNullable<typeof candidate> => candidate !== null)
103
- .sort((left, right) => right.updatedAt - left.updatedAt);
104
-
105
- const out: ExternalSessionSummary[] = [];
106
- for (const { dir, history, summary, mtime, updatedAt } of candidates) {
107
- if (reachedLimit(out.length, limit)) break;
79
+ if (mtime === undefined) continue; // 没有正文就没什么可导
108
80
  const info = summary.info as Record<string, unknown> | undefined;
109
- // num_messages 包含 trace/内部事件;num_chat_messages 才对应 chat_history 的会话记录数
110
- const numMessages = Number(summary.num_chat_messages ?? summary.num_messages);
81
+ const numMessages = Number(summary.num_messages ?? summary.num_chat_messages);
111
82
  // 子代理会话(grok 的 spawn_subagent:subagent / subagent_fork / subagent_resume,本机 24 条)
83
+ // 以及它们的派生:agent_name 是 general-purpose / explore 之类,不是你在用的那条对话
112
84
  if (typeof summary.session_kind === "string" && summary.session_kind.startsWith("subagent")) continue;
113
85
  if (numMessages === 0) continue; // 开了没用的空会话
114
86
  // 只有系统提示与合成提醒(system_reminder)的会话读不出对话,别列进列表
@@ -120,7 +92,7 @@ export const grokSource: ImportSourceModule = {
120
92
  projectPath: typeof info?.cwd === "string" ? info.cwd : decodeProjectDir(path.basename(path.dirname(dir))),
121
93
  model: typeof summary.current_model_id === "string" ? summary.current_model_id : undefined,
122
94
  createdAt: toEpochMs(summary.created_at) ?? mtime,
123
- updatedAt,
95
+ updatedAt: toEpochMs(summary.updated_at ?? summary.last_active_at) ?? mtime,
124
96
  ...(Number.isFinite(numMessages) ? { messageCount: numMessages } : {}),
125
97
  location: history,
126
98
  });
@@ -139,18 +111,17 @@ export const grokSource: ImportSourceModule = {
139
111
  const skipped: string[] = [];
140
112
  let encryptedReasoning = 0;
141
113
  let synthetic = 0;
142
- let runtimeContext = 0;
143
114
  let model = summary.model;
144
115
 
145
116
  for (const record of eachJsonLine(text)) {
146
117
  const timestamp = toEpochMs(record.timestamp ?? record.time ?? record.created_at) ?? summary.createdAt ?? Date.now();
147
118
  switch (record.type) {
148
119
  case "system":
149
- runtimeContext += 1;
150
- continue; // 系统提示由 pi 按目标工作区重新生成,不冒充用户消息
120
+ continue; // 系统提示不进对话
151
121
  case "user": {
152
122
  const blocks = userBlocks(record.content);
153
- if (record.synthetic_reason || !blocks.length) {
123
+ const plain = blocks.map((block) => (block.type === "text" ? block.text : "")).join("\n").trim();
124
+ if (record.synthetic_reason || !plain) {
154
125
  synthetic += 1;
155
126
  continue;
156
127
  }
@@ -174,8 +145,8 @@ export const grokSource: ImportSourceModule = {
174
145
  continue;
175
146
  }
176
147
  case "reasoning": {
177
- const text = reasoningText(record.summary);
178
- if (text) entries.push({ type: "message", message: { role: "assistant", content: [{ type: "thinking", thinking: text }], timestamp, ...(model ? { model } : {}) } });
148
+ const text = typeof record.summary === "string" ? record.summary : "";
149
+ if (text.trim()) entries.push({ type: "message", message: { role: "assistant", content: [{ type: "thinking", thinking: text }], timestamp, ...(model ? { model } : {}) } });
179
150
  else encryptedReasoning += 1; // 加密推理无正文可导:计数后汇总成一行,不要刷屏
180
151
  continue;
181
152
  }
@@ -192,7 +163,6 @@ export const grokSource: ImportSourceModule = {
192
163
 
193
164
  if (synthetic) skipped.push(`跳过 ${synthetic} 条合成输入`);
194
165
  if (encryptedReasoning) skipped.push(`跳过 ${encryptedReasoning} 条仅含加密内容的推理记录`);
195
- if (runtimeContext) skipped.push(`跳过 ${runtimeContext} 条源工具运行时上下文(pi 会按当前工作区重新生成)`);
196
166
  if (!entries.some((entry) => entry.type === "message")) return null;
197
167
  return { summary: { ...summary, model }, entries, skipped };
198
168
  },
@@ -18,7 +18,6 @@ import { dshSource } from "./dsh";
18
18
  import { zcodeSource } from "./zcode";
19
19
  import { opencodeSource } from "./opencode";
20
20
  import { importedKey, listImportedKeys, findImported, writeImportedSession } from "./writer";
21
- import { withImportLock } from "./lock";
22
21
 
23
22
  export const IMPORT_SOURCE_MODULES: Record<ImportSource, ImportSourceModule> = {
24
23
  claude: claudeSource,
@@ -147,10 +146,6 @@ async function oversize(location: string): Promise<boolean> {
147
146
  }
148
147
 
149
148
  export async function importSessions(items: ImportSelection[], options: ImportOptions): Promise<ImportReport> {
150
- return withImportLock(options.agentDir, () => importSessionsLocked(items, options));
151
- }
152
-
153
- async function importSessionsLocked(items: ImportSelection[], options: ImportOptions): Promise<ImportReport> {
154
149
  const results: ImportOutcome[] = [];
155
150
  const workspaces = new Set<string>();
156
151
  const fallback = (await isDirectory(options.fallbackCwd)) ? path.resolve(options.fallbackCwd as string) : undefined;
@@ -121,18 +121,6 @@ export function compactBlocks(blocks: Array<ImportedBlock | null | undefined>):
121
121
  return blocks.filter((block): block is ImportedBlock => Boolean(block));
122
122
  }
123
123
 
124
- /** 外部工具常把图片内联成 data URL;只接受 base64,避免把本地路径或远程 URL 塞进 pi 会话 */
125
- export function dataUrlImageBlock(value: unknown, explicitMime?: unknown): ImportedBlock | null {
126
- if (typeof value !== "string") return null;
127
- const match = /^data:([^;,]+);base64,(.+)$/s.exec(value);
128
- if (!match) return null;
129
- return {
130
- type: "image",
131
- data: match[2],
132
- mimeType: typeof explicitMime === "string" && explicitMime.trim() ? explicitMime : match[1],
133
- };
134
- }
135
-
136
124
  /** 工具返回值可能是字符串、对象或数组,统一成内容块 */
137
125
  export function outputBlocks(value: unknown): ImportedBlock[] {
138
126
  if (typeof value === "string") return compactBlocks([textBlock(value)]);
@@ -235,14 +235,9 @@ export async function writeImportedSession(session: ImportedSession, options: Wr
235
235
  const dir = path.join(options.agentDir, "sessions", encodeSessionDir(options.cwd));
236
236
  await fs.mkdir(dir, { recursive: true });
237
237
  const target = path.join(dir, importedFileName(summary.source, summary.externalId, createdAt));
238
- const temporary = `${target}.${randomUUID()}.tmp`;
239
- try {
240
- await fs.writeFile(temporary, `${lines.join("\n")}\n`, { encoding: "utf8", flag: "wx" });
241
- // Publish a complete file atomically without replacing an existing imported session.
242
- await fs.link(temporary, target);
243
- } finally {
244
- await fs.rm(temporary, { force: true });
245
- }
238
+ const temporary = `${target}.${process.pid}.tmp`;
239
+ await fs.writeFile(temporary, `${lines.join("\n")}\n`, "utf8");
240
+ await fs.rename(temporary, target);
246
241
  return { path: target, messageCount: messages.messages.length, skipped };
247
242
  }
248
243
 
@@ -33,8 +33,7 @@ async function toComparablePath(target: string): Promise<string> {
33
33
  } catch {
34
34
  resolved = path.resolve(target); // 路径还不存在(如待创建的技能目录):退化成普通规范化
35
35
  }
36
- const normalized = resolved.replace(/\\/g, "/").replace(/\/+$/, "");
37
- return process.platform === "win32" ? normalized.toLowerCase() : normalized;
36
+ return resolved.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
38
37
  }
39
38
 
40
39
  interface ScopeContext {
@@ -58,10 +57,10 @@ const within = (target: string, root: string) => target.startsWith(`${root}/`);
58
57
 
59
58
  async function classifyScope(filePath: string, context: ScopeContext): Promise<SkillView["scope"]> {
60
59
  const norm = await toComparablePath(filePath);
61
- if (norm.includes("/node_modules/")) return "package";
62
60
  if (within(norm, context.agentDir) || within(norm, `${context.home}/.agents`) || within(norm, `${context.home}/.pi`))
63
61
  return "global";
64
62
  if (context.cwd && within(norm, context.cwd)) return "project";
63
+ if (norm.includes("/node_modules/")) return "package";
65
64
  return "package";
66
65
  }
67
66
 
@@ -122,7 +121,7 @@ export async function setSkillDisabled(filePath: string, disabled: boolean, cwd?
122
121
  await fs.writeFile(authorized, next, "utf8");
123
122
  // 就地重载加载器并让活跃会话重建系统提示(技能列表在系统提示里);全局技能影响所有目录
124
123
  await reloadAllLoaders();
125
- const context = await scopeContext(cwd ?? process.cwd());
124
+ const context = await scopeContext(cwd);
126
125
  const scope = await classifyScope(authorized, context);
127
126
  await reloadSessionsForCwd(scope === "project" ? cwd : undefined);
128
127
  return { changed: true };
@@ -138,23 +137,17 @@ export async function readSkillFile(filePath: string, cwd?: string): Promise<str
138
137
  export async function deleteSkill(filePath: string, cwd?: string): Promise<{ removed: string }> {
139
138
  const skills = await listSkills(cwd);
140
139
  const authorized = await resolveDiscoveredPath(filePath, skills.map((skill) => skill.filePath));
141
- const context = await scopeContext(cwd ?? process.cwd());
140
+ const context = await scopeContext(cwd);
142
141
  if ((await classifyScope(authorized, context)) === "package") {
143
142
  throw new Error("package-managed skill: disable it instead (it is reinstalled with the package)");
144
143
  }
145
144
  const skillDir = path.dirname(authorized);
145
+ // 只删技能目录本身,不能误删父目录(全局技能目录 ~/.pi/agent/skills、项目 .agents/skills 等)
146
146
  const normalizedSkillDir = await toComparablePath(skillDir);
147
- const workspace = cwd ?? process.cwd();
148
- const roots = [path.parse(skillDir).root, getAgentDir(), os.homedir(), workspace];
149
- for (const base of [getAgentDir(), os.homedir(), workspace]) {
150
- for (const relative of ["skills", "web-skills", ".git", ".agents", ".agents/skills", ".pi", ".pi/skills", ".pi/agent", ".pi/agent/skills"]) {
151
- roots.push(path.join(base, relative));
152
- }
153
- }
154
- const protectedRoots = await Promise.all(roots.map(toComparablePath));
155
- // A discovered root-level SKILL.md never authorizes deleting its collection or workspace.
156
- if (protectedRoots.some((root) => root === normalizedSkillDir || within(root, normalizedSkillDir))) {
157
- throw new Error("refusing to delete protected skill collection or workspace root");
147
+ const knownRoots = [`${context.agentDir}/skills`, `${context.agentDir}/web-skills`];
148
+ if (!knownRoots.includes(normalizedSkillDir)) {
149
+ // 非已知根:项目内技能目录,至少确认 SKILL.md 直接位于其下且目录名非空
150
+ if (path.basename(skillDir) === "") throw new Error("refusing to delete workspace root");
158
151
  }
159
152
  await fs.rm(skillDir, { recursive: true, force: true });
160
153
  await reloadAllLoaders();
package/src/lib/theme.ts CHANGED
@@ -5,8 +5,6 @@
5
5
  */
6
6
 
7
7
  export type PebrelTheme =
8
- | 'piweb'
9
- | 'ocean-ink'
10
8
  | 'silver-steel'
11
9
  | 'limestone-coal'
12
10
  | 'linen-moss'
@@ -17,61 +15,7 @@ export type PebrelTheme =
17
15
  /** dsh 的 THEME_PREFERENCES:浅色/深色/跟随系统 */
18
16
  export type ThemeMode = 'light' | 'dark' | 'system';
19
17
 
20
- export interface ThemeMeta {
21
- id: PebrelTheme;
22
- name: string;
23
- description: string;
24
- colors: [string, string];
25
- }
26
-
27
- export const THEME_CATALOG: ThemeMeta[] = [
28
- {
29
- id: 'piweb',
30
- name: 'PiWeb · 工作台',
31
- description: '粘土薄荷原装配方',
32
- colors: ['#34D399', '#A7F3D0'],
33
- },
34
- {
35
- id: 'ocean-ink',
36
- name: '海与墨',
37
- description: '湛蓝深邃与清爽青靛',
38
- colors: ['#38BDF8', '#0284C7'],
39
- },
40
- {
41
- id: 'silver-steel',
42
- name: '银钢',
43
- description: '坚实质感与精密中性',
44
- colors: ['#E2E8F0', '#475569'],
45
- },
46
- {
47
- id: 'limestone-coal',
48
- name: '石灰与煤炭',
49
- description: '暖橙炭黑微拟物风格',
50
- colors: ['#FB923C', '#1E293B'],
51
- },
52
- {
53
- id: 'linen-moss',
54
- name: '亚麻与苔绿',
55
- description: '自然植物纤维温润感',
56
- colors: ['#FFDCC5', '#006C4B'],
57
- },
58
- {
59
- id: 'nord-paper',
60
- name: '北欧纸墨',
61
- description: '哑光书纸触觉明度',
62
- colors: ['#F6FAFE', '#00668A'],
63
- },
64
- {
65
- id: 'pebrel',
66
- name: 'Pebrel',
67
- description: '柔和薰衣草紫与鹅卵石质感',
68
- colors: ['#818CF8', '#EEF2FF'],
69
- },
70
- ];
71
-
72
18
  export const PEBREL_THEME_IDS: PebrelTheme[] = [
73
- 'piweb',
74
- 'ocean-ink',
75
19
  'dsh',
76
20
  'silver-steel',
77
21
  'limestone-coal',
@@ -82,12 +26,11 @@ export const PEBREL_THEME_IDS: PebrelTheme[] = [
82
26
 
83
27
  export const THEME_MODES: ThemeMode[] = ['light', 'dark', 'system'];
84
28
 
85
- export const DEFAULT_PEBREL_THEME: PebrelTheme = 'piweb';
29
+ export const DEFAULT_PEBREL_THEME: PebrelTheme = 'dsh';
86
30
  export const DEFAULT_THEME_MODE: ThemeMode = 'system';
87
31
 
88
32
  const THEME_KEY = 'piweb.pebrelTheme';
89
33
  const MODE_KEY = 'piweb.theme';
90
- const DESIGN_VERSION_KEY = 'piweb.designVersion';
91
34
 
92
35
  export function isPebrelTheme(value: string | null): value is PebrelTheme {
93
36
  return !!value && (PEBREL_THEME_IDS as string[]).includes(value);
@@ -101,11 +44,6 @@ export function loadPebrelTheme(): PebrelTheme {
101
44
  if (typeof window === 'undefined') return DEFAULT_PEBREL_THEME;
102
45
  try {
103
46
  const saved = localStorage.getItem(THEME_KEY);
104
- if (saved === 'dsh' && localStorage.getItem(DESIGN_VERSION_KEY) !== '1') {
105
- localStorage.setItem(THEME_KEY, 'piweb');
106
- localStorage.setItem(DESIGN_VERSION_KEY, '1');
107
- return 'piweb';
108
- }
109
47
  if (isPebrelTheme(saved)) return saved;
110
48
  } catch {
111
49
  /* ignore */
@@ -141,17 +79,13 @@ function applyLightDark(mode: ThemeMode): void {
141
79
  if (isDark) {
142
80
  root.setAttribute('data-ds-dark-theme', '');
143
81
  root.removeAttribute('data-ds-light-theme');
144
- root.classList.add('dark');
145
82
  body?.setAttribute('data-ds-dark-theme', '');
146
83
  body?.removeAttribute('data-ds-light-theme');
147
- body?.classList.add('dark');
148
84
  } else {
149
85
  root.removeAttribute('data-ds-dark-theme');
150
86
  root.setAttribute('data-ds-light-theme', '');
151
- root.classList.remove('dark');
152
87
  body?.removeAttribute('data-ds-dark-theme');
153
88
  body?.setAttribute('data-ds-light-theme', '');
154
- body?.classList.remove('dark');
155
89
  }
156
90
  root.style.colorScheme = isDark ? 'dark' : 'light';
157
91
  }
@@ -192,7 +126,6 @@ export function applyPebrelTheme(theme: PebrelTheme, mode?: ThemeMode): void {
192
126
  try {
193
127
  localStorage.setItem(THEME_KEY, theme);
194
128
  localStorage.setItem(MODE_KEY, effectiveMode);
195
- localStorage.setItem(DESIGN_VERSION_KEY, '1');
196
129
  } catch {
197
130
  /* ignore */
198
131
  }
@@ -14,7 +14,6 @@ export function toTrajTokens(u: any): TrajTokens | undefined {
14
14
  if (typeof u.output === "number") t.output = u.output;
15
15
  if (typeof u.cacheRead === "number") t.cacheRead = u.cacheRead;
16
16
  if (typeof u.cacheWrite === "number") t.cacheWrite = u.cacheWrite;
17
- if (typeof u.totalTokens === "number") t.totalTokens = u.totalTokens;
18
17
  return Object.keys(t).length ? t : undefined;
19
18
  }
20
19