@rexvane/piweb 0.3.15 → 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 (230) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +9 -6
  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/routes-manifest.json +20 -0
  7. package/.next/server/app/_global-error/page.js +2 -2
  8. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/server/app/_global-error.html +1 -1
  10. package/.next/server/app/_global-error.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/server/app/_not-found/page.js +1 -1
  15. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/_not-found.html +1 -1
  17. package/.next/server/app/_not-found.rsc +7 -4
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +7 -4
  19. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -2
  20. package/.next/server/app/_not-found.segments/_tree.segment.rsc +5 -2
  21. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  22. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  23. package/.next/server/app/api/agent/[id]/route.js +1 -1
  24. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  25. package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
  26. package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
  27. package/.next/server/app/api/agent/new/route.js +1 -1
  28. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  29. package/.next/server/app/api/config/route.js +1 -1
  30. package/.next/server/app/api/config/route.js.nft.json +1 -1
  31. package/.next/server/app/api/files/route.js +1 -1
  32. package/.next/server/app/api/files/route.js.nft.json +1 -1
  33. package/.next/server/app/api/git/route.js +2 -2
  34. package/.next/server/app/api/git/route.js.nft.json +1 -1
  35. package/.next/server/app/api/growth/route.js +1 -1
  36. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  37. package/.next/server/app/api/health/route.js +1 -1
  38. package/.next/server/app/api/models/route.js +1 -1
  39. package/.next/server/app/api/models/route.js.nft.json +1 -1
  40. package/.next/server/app/api/pi-settings/route.js +1 -1
  41. package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
  42. package/.next/server/app/api/plugins/route.js +1 -1
  43. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  44. package/.next/server/app/api/prompts/route.js +1 -1
  45. package/.next/server/app/api/prompts/route.js.nft.json +1 -1
  46. package/.next/server/app/api/security/route.js +1 -1
  47. package/.next/server/app/api/security/route.js.nft.json +1 -1
  48. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  49. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  50. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  51. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  52. package/.next/server/app/api/sessions/import/route.js +3 -3
  53. package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
  54. package/.next/server/app/api/sessions/route.js +1 -1
  55. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  56. package/.next/server/app/api/skills/route.js +2 -2
  57. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  58. package/.next/server/app/api/swarm/[id]/route.js +1 -0
  59. package/.next/server/app/api/swarm/[id]/route.js.nft.json +1 -0
  60. package/.next/server/app/api/swarm/[id]/route_client-reference-manifest.js +1 -0
  61. package/.next/server/app/api/swarm/route.js +1 -0
  62. package/.next/server/app/api/swarm/route.js.nft.json +1 -0
  63. package/.next/server/app/api/swarm/route_client-reference-manifest.js +1 -0
  64. package/.next/server/app/api/tests/route.js +2 -0
  65. package/.next/server/app/api/tests/route.js.nft.json +1 -0
  66. package/.next/server/app/api/tests/route_client-reference-manifest.js +1 -0
  67. package/.next/server/app/api/update/route.js +2 -2
  68. package/.next/server/app/api/update/route.js.nft.json +1 -1
  69. package/.next/server/app/api/version/route.js +1 -1
  70. package/.next/server/app/api/version/route.js.nft.json +1 -1
  71. package/.next/server/app/api/web-auth/route.js +1 -1
  72. package/.next/server/app/api/workspaces/route.js +1 -1
  73. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  74. package/.next/server/app/icon.svg/route.js +1 -1
  75. package/.next/server/app/index.html +1 -1
  76. package/.next/server/app/index.rsc +9 -6
  77. package/.next/server/app/index.segments/__PAGE__.segment.rsc +7 -4
  78. package/.next/server/app/index.segments/_full.segment.rsc +9 -6
  79. package/.next/server/app/index.segments/_tree.segment.rsc +5 -2
  80. package/.next/server/app/login/page.js +1 -1
  81. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  82. package/.next/server/app/login.html +1 -1
  83. package/.next/server/app/login.rsc +8 -5
  84. package/.next/server/app/login.segments/_full.segment.rsc +8 -5
  85. package/.next/server/app/login.segments/_tree.segment.rsc +5 -2
  86. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +6 -3
  87. package/.next/server/app/page.js +1 -1
  88. package/.next/server/app/page_client-reference-manifest.js +1 -1
  89. package/.next/server/app-paths-manifest.json +9 -6
  90. package/.next/server/chunks/496.js +1 -1
  91. package/.next/server/chunks/532.js +1 -1
  92. package/.next/server/chunks/686.js +6 -2
  93. package/.next/server/chunks/757.js +1 -0
  94. package/.next/server/chunks/775.js +3 -0
  95. package/.next/server/middleware-build-manifest.js +1 -1
  96. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  97. package/.next/server/pages/404.html +1 -1
  98. package/.next/server/pages/500.html +1 -1
  99. package/.next/server/server-reference-manifest.json +1 -1
  100. package/.next/static/0t1jBUAuVaTFTXxRyIo3q/_buildManifest.js +1 -0
  101. package/.next/static/chunks/2608.43c58236c9cce8e6.js +1 -0
  102. package/.next/static/chunks/{2716.a9e808190a4e2424.js → 2716.4ec42df1d51354c9.js} +1 -1
  103. package/.next/static/chunks/4632.62139b565b657aac.js +1 -0
  104. package/.next/static/chunks/{4673.d3d0a7f2599080fb.js → 4673.7796104395deb8d7.js} +1 -1
  105. package/.next/static/chunks/4686-17f21a006739a596.js +1 -0
  106. package/.next/static/chunks/7577.10af9bb661652da8.js +1 -0
  107. package/.next/static/chunks/8866.38ec4ac4ee5a1b74.js +1 -0
  108. package/.next/static/chunks/app/_global-error/page-5148173b3f55e415.js +1 -0
  109. package/.next/static/chunks/app/api/agent/[id]/events/route-5148173b3f55e415.js +1 -0
  110. package/.next/static/chunks/app/api/agent/[id]/route-5148173b3f55e415.js +1 -0
  111. package/.next/static/chunks/app/api/agent/[id]/tree/route-5148173b3f55e415.js +1 -0
  112. package/.next/static/chunks/app/api/agent/new/route-5148173b3f55e415.js +1 -0
  113. package/.next/static/chunks/app/api/config/route-5148173b3f55e415.js +1 -0
  114. package/.next/static/chunks/app/api/files/route-5148173b3f55e415.js +1 -0
  115. package/.next/static/chunks/app/api/git/route-5148173b3f55e415.js +1 -0
  116. package/.next/static/chunks/app/api/growth/route-5148173b3f55e415.js +1 -0
  117. package/.next/static/chunks/app/api/health/route-5148173b3f55e415.js +1 -0
  118. package/.next/static/chunks/app/api/models/route-5148173b3f55e415.js +1 -0
  119. package/.next/static/chunks/app/api/pi-settings/route-5148173b3f55e415.js +1 -0
  120. package/.next/static/chunks/app/api/plugins/route-5148173b3f55e415.js +1 -0
  121. package/.next/static/chunks/app/api/prompts/route-5148173b3f55e415.js +1 -0
  122. package/.next/static/chunks/app/api/security/route-5148173b3f55e415.js +1 -0
  123. package/.next/static/chunks/app/api/sessions/[id]/export/route-5148173b3f55e415.js +1 -0
  124. package/.next/static/chunks/app/api/sessions/[id]/route-5148173b3f55e415.js +1 -0
  125. package/.next/static/chunks/app/api/sessions/import/route-5148173b3f55e415.js +1 -0
  126. package/.next/static/chunks/app/api/sessions/route-5148173b3f55e415.js +1 -0
  127. package/.next/static/chunks/app/api/skills/route-5148173b3f55e415.js +1 -0
  128. package/.next/static/chunks/app/api/swarm/[id]/route-5148173b3f55e415.js +1 -0
  129. package/.next/static/chunks/app/api/swarm/route-5148173b3f55e415.js +1 -0
  130. package/.next/static/chunks/app/api/tests/route-5148173b3f55e415.js +1 -0
  131. package/.next/static/chunks/app/api/update/route-5148173b3f55e415.js +1 -0
  132. package/.next/static/chunks/app/api/version/route-5148173b3f55e415.js +1 -0
  133. package/.next/static/chunks/app/api/web-auth/route-5148173b3f55e415.js +1 -0
  134. package/.next/static/chunks/app/api/workspaces/route-5148173b3f55e415.js +1 -0
  135. package/.next/static/chunks/app/page-14379b8060d5cf07.js +1 -0
  136. package/.next/static/chunks/next/dist/client/components/builtin/app-error-5148173b3f55e415.js +1 -0
  137. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-5148173b3f55e415.js +1 -0
  138. package/.next/static/chunks/next/dist/client/components/builtin/not-found-5148173b3f55e415.js +1 -0
  139. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-5148173b3f55e415.js +1 -0
  140. package/.next/static/chunks/webpack-d1a69ad9edb38b11.js +1 -0
  141. package/.next/static/css/d0a0162cb481abe8.css +1 -0
  142. package/.next/types/app/api/swarm/[id]/route.ts +353 -0
  143. package/.next/types/app/api/swarm/route.ts +353 -0
  144. package/.next/types/app/api/tests/route.ts +353 -0
  145. package/.next/types/routes.d.ts +4 -1
  146. package/.next/types/validator.ts +27 -0
  147. package/README.md +10 -0
  148. package/package.json +4 -3
  149. package/scripts/pick-folder.ps1 +11 -2
  150. package/scripts/release.mjs +11 -31
  151. package/scripts/run-test-file.mjs +34 -0
  152. package/scripts/verify-release-config.mjs +24 -0
  153. package/src/app/api/config/route.ts +4 -1
  154. package/src/app/api/security/route.ts +8 -3
  155. package/src/app/api/swarm/[id]/route.ts +28 -0
  156. package/src/app/api/swarm/route.ts +24 -0
  157. package/src/app/api/tests/route.ts +27 -0
  158. package/src/app/api/update/route.ts +8 -2
  159. package/src/app/globals.css +777 -0
  160. package/src/app/layout.tsx +13 -6
  161. package/src/components/AppShell.tsx +505 -199
  162. package/src/components/ChatInput.tsx +153 -26
  163. package/src/components/ChatWindow.tsx +46 -40
  164. package/src/components/ContextMeter.tsx +11 -17
  165. package/src/components/FileViewer.tsx +1 -1
  166. package/src/components/GitPanel.tsx +13 -11
  167. package/src/components/ModelSelector.tsx +19 -6
  168. package/src/components/PiMark.tsx +5 -38
  169. package/src/components/ProjectPanel.tsx +1 -0
  170. package/src/components/PromptPanel.tsx +132 -51
  171. package/src/components/SessionImportSection.tsx +17 -17
  172. package/src/components/SessionSidebar.tsx +159 -57
  173. package/src/components/SettingsPanel.tsx +330 -193
  174. package/src/components/SwarmCoordinatorPod.tsx +1021 -0
  175. package/src/components/TestInspectorPod.tsx +549 -0
  176. package/src/components/icons.tsx +23 -0
  177. package/src/hooks/usePiWeb.ts +112 -8
  178. package/src/i18n.tsx +59 -3
  179. package/src/lib/agent-manager.ts +180 -12
  180. package/src/lib/command-validation.ts +9 -1
  181. package/src/lib/delegation.ts +93 -0
  182. package/src/lib/managed-niubash.ts +8 -4
  183. package/src/lib/pi.ts +3 -2
  184. package/src/lib/runtime-activity.ts +27 -0
  185. package/src/lib/session-import/index.ts +5 -0
  186. package/src/lib/session-import/lock.ts +25 -0
  187. package/src/lib/session-import/writer.ts +8 -3
  188. package/src/lib/skills-service.ts +16 -9
  189. package/src/lib/swarm-service.ts +371 -0
  190. package/src/lib/test-run-service.ts +122 -0
  191. package/src/lib/theme.ts +68 -1
  192. package/src/lib/trajectory.ts +1 -0
  193. package/src/lib/types.ts +14 -0
  194. package/src/lib/workflow.ts +55 -0
  195. package/.next/static/DdBwPYoHBKtaJ97QIr6Co/_buildManifest.js +0 -1
  196. package/.next/static/chunks/2608.ec8ff1f5767c9056.js +0 -1
  197. package/.next/static/chunks/4632.0ed92ae42905ce40.js +0 -1
  198. package/.next/static/chunks/4686-7d694def920ff093.js +0 -1
  199. package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +0 -1
  200. package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +0 -1
  201. package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +0 -1
  202. package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +0 -1
  203. package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +0 -1
  204. package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +0 -1
  205. package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +0 -1
  206. package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +0 -1
  207. package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +0 -1
  208. package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +0 -1
  209. package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +0 -1
  210. package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +0 -1
  211. package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +0 -1
  212. package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +0 -1
  213. package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +0 -1
  214. package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +0 -1
  215. package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +0 -1
  216. package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +0 -1
  217. package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +0 -1
  218. package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +0 -1
  219. package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +0 -1
  220. package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +0 -1
  221. package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +0 -1
  222. package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +0 -1
  223. package/.next/static/chunks/app/page-9a5440047c5a415c.js +0 -1
  224. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +0 -1
  225. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +0 -1
  226. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +0 -1
  227. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +0 -1
  228. package/.next/static/chunks/webpack-42c5aae79b0e5d8c.js +0 -1
  229. package/.next/static/css/b2d35abc828d4713.css +0 -1
  230. /package/.next/static/{DdBwPYoHBKtaJ97QIr6Co → 0t1jBUAuVaTFTXxRyIo3q}/_ssgManifest.js +0 -0
@@ -0,0 +1,353 @@
1
+ // File: /home/runner/work/PiWeb/PiWeb/src/app/api/tests/route.ts
2
+ import * as entry from '../../../../../src/app/api/tests/route.js'
3
+ import type { NextRequest } from 'next/server.js'
4
+
5
+ import type { InstantConfigForTypeCheckInternal, Prefetch } from 'next/dist/build/segment-config/app/app-segment-config.js'
6
+
7
+ type TEntry = typeof import('../../../../../src/app/api/tests/route.js')
8
+
9
+ type SegmentParams<T extends Object = any> = T extends Record<string, any>
10
+ ? { [K in keyof T]: T[K] extends string ? string | string[] | undefined : never }
11
+ : T
12
+
13
+ // Check that the entry is a valid entry
14
+ checkFields<Diff<{
15
+ GET?: Function
16
+ HEAD?: Function
17
+ OPTIONS?: Function
18
+ POST?: Function
19
+ PUT?: Function
20
+ DELETE?: Function
21
+ PATCH?: Function
22
+ config?: {}
23
+ generateStaticParams?: Function
24
+ instant?: InstantConfigForTypeCheckInternal
25
+ prefetch?: Prefetch
26
+ unstable_dynamicStaleTime?: number
27
+ revalidate?: RevalidateRange<TEntry> | false
28
+ dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static'
29
+ dynamicParams?: boolean
30
+ fetchCache?: 'auto' | 'force-no-store' | 'only-no-store' | 'default-no-store' | 'default-cache' | 'only-cache' | 'force-cache'
31
+ /** @deprecated The `preferredRegion` config is deprecated. Remove this export. */
32
+ preferredRegion?: 'auto' | 'global' | 'home' | string | string[]
33
+ runtime?: 'nodejs' | 'experimental-edge' | 'edge'
34
+ maxDuration?: number
35
+
36
+ }, TEntry, ''>>()
37
+
38
+ type RouteContext = { params: Promise<SegmentParams> }
39
+ // Check the prop type of the entry function
40
+ if ('GET' in entry) {
41
+ checkFields<
42
+ Diff<
43
+ ParamCheck<Request | NextRequest>,
44
+ {
45
+ __tag__: 'GET'
46
+ __param_position__: 'first'
47
+ __param_type__: FirstArg<MaybeField<TEntry, 'GET'>>
48
+ },
49
+ 'GET'
50
+ >
51
+ >()
52
+ checkFields<
53
+ Diff<
54
+ ParamCheck<RouteContext>,
55
+ {
56
+ __tag__: 'GET'
57
+ __param_position__: 'second'
58
+ __param_type__: SecondArg<MaybeField<TEntry, 'GET'>>
59
+ },
60
+ 'GET'
61
+ >
62
+ >()
63
+
64
+ checkFields<
65
+ Diff<
66
+ {
67
+ __tag__: 'GET',
68
+ __return_type__: Response | void | never | Promise<Response | void | never>
69
+ },
70
+ {
71
+ __tag__: 'GET',
72
+ __return_type__: ReturnType<MaybeField<TEntry, 'GET'>>
73
+ },
74
+ 'GET'
75
+ >
76
+ >()
77
+ }
78
+ // Check the prop type of the entry function
79
+ if ('HEAD' in entry) {
80
+ checkFields<
81
+ Diff<
82
+ ParamCheck<Request | NextRequest>,
83
+ {
84
+ __tag__: 'HEAD'
85
+ __param_position__: 'first'
86
+ __param_type__: FirstArg<MaybeField<TEntry, 'HEAD'>>
87
+ },
88
+ 'HEAD'
89
+ >
90
+ >()
91
+ checkFields<
92
+ Diff<
93
+ ParamCheck<RouteContext>,
94
+ {
95
+ __tag__: 'HEAD'
96
+ __param_position__: 'second'
97
+ __param_type__: SecondArg<MaybeField<TEntry, 'HEAD'>>
98
+ },
99
+ 'HEAD'
100
+ >
101
+ >()
102
+
103
+ checkFields<
104
+ Diff<
105
+ {
106
+ __tag__: 'HEAD',
107
+ __return_type__: Response | void | never | Promise<Response | void | never>
108
+ },
109
+ {
110
+ __tag__: 'HEAD',
111
+ __return_type__: ReturnType<MaybeField<TEntry, 'HEAD'>>
112
+ },
113
+ 'HEAD'
114
+ >
115
+ >()
116
+ }
117
+ // Check the prop type of the entry function
118
+ if ('OPTIONS' in entry) {
119
+ checkFields<
120
+ Diff<
121
+ ParamCheck<Request | NextRequest>,
122
+ {
123
+ __tag__: 'OPTIONS'
124
+ __param_position__: 'first'
125
+ __param_type__: FirstArg<MaybeField<TEntry, 'OPTIONS'>>
126
+ },
127
+ 'OPTIONS'
128
+ >
129
+ >()
130
+ checkFields<
131
+ Diff<
132
+ ParamCheck<RouteContext>,
133
+ {
134
+ __tag__: 'OPTIONS'
135
+ __param_position__: 'second'
136
+ __param_type__: SecondArg<MaybeField<TEntry, 'OPTIONS'>>
137
+ },
138
+ 'OPTIONS'
139
+ >
140
+ >()
141
+
142
+ checkFields<
143
+ Diff<
144
+ {
145
+ __tag__: 'OPTIONS',
146
+ __return_type__: Response | void | never | Promise<Response | void | never>
147
+ },
148
+ {
149
+ __tag__: 'OPTIONS',
150
+ __return_type__: ReturnType<MaybeField<TEntry, 'OPTIONS'>>
151
+ },
152
+ 'OPTIONS'
153
+ >
154
+ >()
155
+ }
156
+ // Check the prop type of the entry function
157
+ if ('POST' in entry) {
158
+ checkFields<
159
+ Diff<
160
+ ParamCheck<Request | NextRequest>,
161
+ {
162
+ __tag__: 'POST'
163
+ __param_position__: 'first'
164
+ __param_type__: FirstArg<MaybeField<TEntry, 'POST'>>
165
+ },
166
+ 'POST'
167
+ >
168
+ >()
169
+ checkFields<
170
+ Diff<
171
+ ParamCheck<RouteContext>,
172
+ {
173
+ __tag__: 'POST'
174
+ __param_position__: 'second'
175
+ __param_type__: SecondArg<MaybeField<TEntry, 'POST'>>
176
+ },
177
+ 'POST'
178
+ >
179
+ >()
180
+
181
+ checkFields<
182
+ Diff<
183
+ {
184
+ __tag__: 'POST',
185
+ __return_type__: Response | void | never | Promise<Response | void | never>
186
+ },
187
+ {
188
+ __tag__: 'POST',
189
+ __return_type__: ReturnType<MaybeField<TEntry, 'POST'>>
190
+ },
191
+ 'POST'
192
+ >
193
+ >()
194
+ }
195
+ // Check the prop type of the entry function
196
+ if ('PUT' in entry) {
197
+ checkFields<
198
+ Diff<
199
+ ParamCheck<Request | NextRequest>,
200
+ {
201
+ __tag__: 'PUT'
202
+ __param_position__: 'first'
203
+ __param_type__: FirstArg<MaybeField<TEntry, 'PUT'>>
204
+ },
205
+ 'PUT'
206
+ >
207
+ >()
208
+ checkFields<
209
+ Diff<
210
+ ParamCheck<RouteContext>,
211
+ {
212
+ __tag__: 'PUT'
213
+ __param_position__: 'second'
214
+ __param_type__: SecondArg<MaybeField<TEntry, 'PUT'>>
215
+ },
216
+ 'PUT'
217
+ >
218
+ >()
219
+
220
+ checkFields<
221
+ Diff<
222
+ {
223
+ __tag__: 'PUT',
224
+ __return_type__: Response | void | never | Promise<Response | void | never>
225
+ },
226
+ {
227
+ __tag__: 'PUT',
228
+ __return_type__: ReturnType<MaybeField<TEntry, 'PUT'>>
229
+ },
230
+ 'PUT'
231
+ >
232
+ >()
233
+ }
234
+ // Check the prop type of the entry function
235
+ if ('DELETE' in entry) {
236
+ checkFields<
237
+ Diff<
238
+ ParamCheck<Request | NextRequest>,
239
+ {
240
+ __tag__: 'DELETE'
241
+ __param_position__: 'first'
242
+ __param_type__: FirstArg<MaybeField<TEntry, 'DELETE'>>
243
+ },
244
+ 'DELETE'
245
+ >
246
+ >()
247
+ checkFields<
248
+ Diff<
249
+ ParamCheck<RouteContext>,
250
+ {
251
+ __tag__: 'DELETE'
252
+ __param_position__: 'second'
253
+ __param_type__: SecondArg<MaybeField<TEntry, 'DELETE'>>
254
+ },
255
+ 'DELETE'
256
+ >
257
+ >()
258
+
259
+ checkFields<
260
+ Diff<
261
+ {
262
+ __tag__: 'DELETE',
263
+ __return_type__: Response | void | never | Promise<Response | void | never>
264
+ },
265
+ {
266
+ __tag__: 'DELETE',
267
+ __return_type__: ReturnType<MaybeField<TEntry, 'DELETE'>>
268
+ },
269
+ 'DELETE'
270
+ >
271
+ >()
272
+ }
273
+ // Check the prop type of the entry function
274
+ if ('PATCH' in entry) {
275
+ checkFields<
276
+ Diff<
277
+ ParamCheck<Request | NextRequest>,
278
+ {
279
+ __tag__: 'PATCH'
280
+ __param_position__: 'first'
281
+ __param_type__: FirstArg<MaybeField<TEntry, 'PATCH'>>
282
+ },
283
+ 'PATCH'
284
+ >
285
+ >()
286
+ checkFields<
287
+ Diff<
288
+ ParamCheck<RouteContext>,
289
+ {
290
+ __tag__: 'PATCH'
291
+ __param_position__: 'second'
292
+ __param_type__: SecondArg<MaybeField<TEntry, 'PATCH'>>
293
+ },
294
+ 'PATCH'
295
+ >
296
+ >()
297
+
298
+ checkFields<
299
+ Diff<
300
+ {
301
+ __tag__: 'PATCH',
302
+ __return_type__: Response | void | never | Promise<Response | void | never>
303
+ },
304
+ {
305
+ __tag__: 'PATCH',
306
+ __return_type__: ReturnType<MaybeField<TEntry, 'PATCH'>>
307
+ },
308
+ 'PATCH'
309
+ >
310
+ >()
311
+ }
312
+
313
+ // Check the arguments and return type of the generateStaticParams function
314
+ if ('generateStaticParams' in entry) {
315
+ checkFields<Diff<{ params: SegmentParams }, FirstArg<MaybeField<TEntry, 'generateStaticParams'>>, 'generateStaticParams'>>()
316
+ checkFields<Diff<{ __tag__: 'generateStaticParams', __return_type__: any[] | Promise<any[]> }, { __tag__: 'generateStaticParams', __return_type__: ReturnType<MaybeField<TEntry, 'generateStaticParams'>> }>>()
317
+ }
318
+
319
+ export interface PageProps {
320
+ params?: Promise<SegmentParams>
321
+ searchParams?: Promise<any>
322
+ }
323
+ export interface LayoutProps {
324
+ children?: React.ReactNode
325
+
326
+ params?: Promise<SegmentParams>
327
+ }
328
+
329
+ // =============
330
+ // Utility types
331
+ type RevalidateRange<T> = T extends { revalidate: any } ? NonNegative<T['revalidate']> : never
332
+
333
+ // If T is unknown or any, it will be an empty {} type. Otherwise, it will be the same as Omit<T, keyof Base>.
334
+ type OmitWithTag<T, K extends keyof any, _M> = Omit<T, K>
335
+ type Diff<Base, T extends Base, Message extends string = ''> = 0 extends (1 & T) ? {} : OmitWithTag<T, keyof Base, Message>
336
+
337
+ type FirstArg<T extends Function> = T extends (...args: [infer T, any]) => any ? unknown extends T ? any : T : never
338
+ type SecondArg<T extends Function> = T extends (...args: [any, infer T]) => any ? unknown extends T ? any : T : never
339
+ type MaybeField<T, K extends string> = T extends { [k in K]: infer G } ? G extends Function ? G : never : never
340
+
341
+ type ParamCheck<T> = {
342
+ __tag__: string
343
+ __param_position__: string
344
+ __param_type__: T
345
+ }
346
+
347
+ function checkFields<_ extends { [k in keyof any]: never }>() {}
348
+
349
+ // https://github.com/sindresorhus/type-fest
350
+ type Numeric = number | bigint
351
+ type Zero = 0 | 0n
352
+ type Negative<T extends Numeric> = T extends Zero ? never : `${T}` extends `-${string}` ? T : never
353
+ type NonNegative<T extends Numeric> = T extends Zero ? T : Negative<T> extends never ? T : '__invalid_negative_number__'
@@ -2,7 +2,7 @@
2
2
  // Do not edit this file manually
3
3
 
4
4
  type AppRoutes = "/" | "/login"
5
- type AppRouteHandlerRoutes = "/api/agent/[id]" | "/api/agent/[id]/events" | "/api/agent/[id]/tree" | "/api/agent/new" | "/api/config" | "/api/files" | "/api/git" | "/api/growth" | "/api/health" | "/api/models" | "/api/pi-settings" | "/api/plugins" | "/api/prompts" | "/api/security" | "/api/sessions" | "/api/sessions/[id]" | "/api/sessions/[id]/export" | "/api/sessions/import" | "/api/skills" | "/api/update" | "/api/version" | "/api/web-auth" | "/api/workspaces"
5
+ type AppRouteHandlerRoutes = "/api/agent/[id]" | "/api/agent/[id]/events" | "/api/agent/[id]/tree" | "/api/agent/new" | "/api/config" | "/api/files" | "/api/git" | "/api/growth" | "/api/health" | "/api/models" | "/api/pi-settings" | "/api/plugins" | "/api/prompts" | "/api/security" | "/api/sessions" | "/api/sessions/[id]" | "/api/sessions/[id]/export" | "/api/sessions/import" | "/api/skills" | "/api/swarm" | "/api/swarm/[id]" | "/api/tests" | "/api/update" | "/api/version" | "/api/web-auth" | "/api/workspaces"
6
6
  type PageRoutes = never
7
7
  type LayoutRoutes = "/"
8
8
  type RedirectRoutes = never
@@ -31,6 +31,9 @@ interface ParamMap {
31
31
  "/api/sessions/[id]/export": { "id": string; }
32
32
  "/api/sessions/import": {}
33
33
  "/api/skills": {}
34
+ "/api/swarm": {}
35
+ "/api/swarm/[id]": { "id": string; }
36
+ "/api/tests": {}
34
37
  "/api/update": {}
35
38
  "/api/version": {}
36
39
  "/api/web-auth": {}
@@ -236,6 +236,33 @@ type RouteHandlerConfig<Route extends AppRouteHandlerRoutes = AppRouteHandlerRou
236
236
  type __Unused = __Check
237
237
  }
238
238
 
239
+ // Validate ../../src/app/api/swarm/[id]/route.ts
240
+ {
241
+ type __IsExpected<Specific extends RouteHandlerConfig<"/api/swarm/[id]">> = Specific
242
+ const handler = {} as typeof import("../../src/app/api/swarm/[id]/route.js")
243
+ type __Check = __IsExpected<typeof handler>
244
+ // @ts-ignore
245
+ type __Unused = __Check
246
+ }
247
+
248
+ // Validate ../../src/app/api/swarm/route.ts
249
+ {
250
+ type __IsExpected<Specific extends RouteHandlerConfig<"/api/swarm">> = Specific
251
+ const handler = {} as typeof import("../../src/app/api/swarm/route.js")
252
+ type __Check = __IsExpected<typeof handler>
253
+ // @ts-ignore
254
+ type __Unused = __Check
255
+ }
256
+
257
+ // Validate ../../src/app/api/tests/route.ts
258
+ {
259
+ type __IsExpected<Specific extends RouteHandlerConfig<"/api/tests">> = Specific
260
+ const handler = {} as typeof import("../../src/app/api/tests/route.js")
261
+ type __Check = __IsExpected<typeof handler>
262
+ // @ts-ignore
263
+ type __Unused = __Check
264
+ }
265
+
239
266
  // Validate ../../src/app/api/update/route.ts
240
267
  {
241
268
  type __IsExpected<Specific extends RouteHandlerConfig<"/api/update">> = Specific
package/README.md CHANGED
@@ -20,6 +20,16 @@
20
20
 
21
21
  PiWeb and pi are two separate programs, and PiWeb is not another build of pi: it is a web shell that drives the pi already installed on your machine through the official SDK, leaving pi itself untouched. The two **share one set of state** — the same agent directory (`~/.pi/agent`) holds your settings, models and credentials, sessions, skills and packages — so a conversation started in the pi CLI shows up in the browser right away, and the other way round. Updating is **two separate things**: the pi engine and PiWeb each carry their own version and their own update entry point (the two "Check for updates" rows in Settings), and upgrading one leaves the other alone. What runs underneath is native pi; plugin compatibility in the browser, interactive plugins especially, is not finished yet — for now plugins can be managed rather than fully driven from the page.
22
22
 
23
+ ## Workflows and Delegation
24
+
25
+ Use the **+** button in the composer's lower-left corner to choose **Agent / Plan / Goal** per session. Agent uses the existing tool preset. Plan uses read-only tools and stops for explicit approval; "Execute plan" temporarily restores the current tool preset for that run, then returns to read-only planning. Goal retains the first prompt as the objective and asks the agent to verify progress on later turns; it does not start unlimited follow-up turns automatically. Workflow state is stored as a custom entry in the native pi session.
26
+
27
+ Plan's tool restriction is not a sandbox for locally installed, trusted pi extensions. Approval is tied to the latest proposed plan and is rejected if the conversation changes after that plan.
28
+
29
+ Click a sent user message to edit and resend it in place, even without changing its text. PiWeb reruns the conversation from that message and keeps the previous branch in the native session history rather than adding a new message at the bottom.
30
+
31
+ With the standard or full tool preset, `piweb_delegate` can run up to three independent research tasks in parallel. Each temporary subagent uses the current model and read-only file tools. It cannot run commands, edit files, or delegate again. Reports return to the parent; these are not independent Worker sessions. The explicit read-only preset does not automatically enable delegation.
32
+
23
33
  ## Features
24
34
 
25
35
  ### Real-Time Streaming Chat
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rexvane/piweb",
3
- "version": "0.3.15",
3
+ "version": "0.3.16",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Modern web UI for the pi coding agent, driven by the official pi SDK without modifying pi",
@@ -52,8 +52,8 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@automann/maple-mono-cn": "^7.9.2",
55
- "@earendil-works/pi-ai": "0.85.1",
56
- "@earendil-works/pi-coding-agent": "0.85.1",
55
+ "@earendil-works/pi-ai": "0.87.1",
56
+ "@earendil-works/pi-coding-agent": "0.87.1",
57
57
  "@lobehub/icons-static-svg": "^1.95.0",
58
58
  "@tailwindcss/postcss": "^4.2.2",
59
59
  "@types/node": "^26",
@@ -71,6 +71,7 @@
71
71
  "rehype-highlight": "^7.0.2",
72
72
  "remark-gfm": "^4.0.1",
73
73
  "tailwindcss": "^4.2.2",
74
+ "typebox": "1.3.27",
74
75
  "typescript": "^7"
75
76
  },
76
77
  "optionalDependencies": {
@@ -82,6 +82,10 @@ public class PiFolderPicker {
82
82
  }
83
83
  }
84
84
 
85
+ public static bool ShouldRetryWithoutOwner(int hr, IntPtr hwnd) {
86
+ return hr == unchecked((int)0x80070578) && hwnd != IntPtr.Zero;
87
+ }
88
+
85
89
  public static string Pick(string title) {
86
90
  try {
87
91
  EnableDpiAwareness();
@@ -90,8 +94,13 @@ public class PiFolderPicker {
90
94
  dlg.GetOptions(out options);
91
95
  dlg.SetOptions(options | FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM);
92
96
  if (!string.IsNullOrEmpty(title)) dlg.SetTitle(title);
93
- dlg.SetOkButtonLabel(OkLabel);
94
- int hr = dlg.Show(GetForegroundWindow());
97
+ IntPtr hwnd = GetForegroundWindow();
98
+ int hr = dlg.Show(hwnd);
99
+ if (hr == unchecked((int)0x800704C7)) return "";
100
+ // Retry only an invalid owner handle, never a user cancellation or arbitrary failure.
101
+ if (ShouldRetryWithoutOwner(hr, hwnd)) {
102
+ hr = dlg.Show(IntPtr.Zero);
103
+ }
95
104
  if (hr != 0) return "";
96
105
  IShellItem item;
97
106
  dlg.GetResult(out item);
@@ -2,8 +2,7 @@ import fs from "node:fs/promises";
2
2
  import { existsSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { randomUUID } from "node:crypto";
5
- import { createRequire } from "node:module";
6
- import { pathToFileURL, fileURLToPath } from "node:url";
5
+ import { fileURLToPath } from "node:url";
7
6
  import { assertInstallation, assertLocalPath, readProductionBuild, resolveBuildOutput, RELEASES_DIR, RELEASE_MANIFEST } from "./build-output.mjs";
8
7
  import { runCommand } from "./process-runner.mjs";
9
8
  import { isMainModule } from "./entrypoint.mjs";
@@ -77,21 +76,6 @@ async function writeReleaseTypeConfig(root, buildDir) {
77
76
  });
78
77
  }
79
78
 
80
- /** Run in a fresh process, after install/pull, so cached config/Next modules cannot lie. */
81
- export async function verifyReleaseConfig(root = process.cwd(), env = process.env) {
82
- const installation = assertInstallation(root);
83
- const buildDir = resolveBuildOutput(installation, env.PIWEB_BUILD_DIR);
84
- if (!buildDir.startsWith(`${RELEASES_DIR}/`)) throw new Error("release requires an isolated build output");
85
- const require = createRequire(path.join(installation, "package.json"));
86
- const module = await import(pathToFileURL(require.resolve("next/dist/server/config.js")).href);
87
- const loadConfig = typeof module.default === "function" ? module.default : module.default.default;
88
- const { PHASE_PRODUCTION_BUILD } = require("next/constants");
89
- const config = await loadConfig(PHASE_PRODUCTION_BUILD, installation, { silent: true });
90
- if (config.distDir !== buildDir || config.typescript?.tsconfigPath !== CONFIG_PATH || config.typescript?.ignoreBuildErrors) {
91
- throw new Error("Next config does not honor the isolated release directory/typecheck; refusing to build over the serving output");
92
- }
93
- }
94
-
95
79
  export const PI_PACKAGE = "@earendil-works/pi-coding-agent";
96
80
  const PI_AI_PACKAGE = "@earendil-works/pi-ai";
97
81
 
@@ -150,7 +134,7 @@ export async function prepareRelease({ root = ROOT, env = process.env, run = run
150
134
  signal?.throwIfAborted();
151
135
  assertInstallation(installation);
152
136
  await writeReleaseTypeConfig(installation, buildDir);
153
- await run(process.execPath, [path.join(installation, "scripts", "release.mjs"), "--verify-config"], {
137
+ await run(process.execPath, [path.join(installation, "scripts", "verify-release-config.mjs")], {
154
138
  cwd: installation, env: childEnv, timeoutMs: 60_000, signal,
155
139
  });
156
140
  await run("npm", ["run", "check"], { cwd: installation, env: childEnv, timeoutMs: 600_000, signal });
@@ -175,19 +159,15 @@ if (isMainModule(import.meta.url)) {
175
159
  process.once("SIGINT", cancel);
176
160
  process.once("SIGTERM", cancel);
177
161
  try {
178
- if (process.argv.length === 3 && process.argv[2] === "--verify-config") {
179
- await verifyReleaseConfig();
180
- } else {
181
- const updatePi = process.argv.length === 3 && process.argv[2] === "--update-pi";
182
- if (process.argv.length > 2 && !updatePi) throw new Error("Usage: node scripts/release.mjs [--update-pi]");
183
- console.log("[piweb] Checking and building an isolated release; the existing server is not restarted.");
184
- if (updatePi) console.log("[piweb] Pi dependency update: use a maintenance window with no active sessions; node_modules is shared.");
185
- const release = await prepareRelease({
186
- signal: controller.signal,
187
- prepare: updatePi ? async (context) => { await installPiUpdate(context); } : undefined,
188
- });
189
- console.log(`[piweb] Release ready: ${release.buildDir} (${release.buildId}). Restart PiWeb manually when idle to use it.`);
190
- }
162
+ const updatePi = process.argv.length === 3 && process.argv[2] === "--update-pi";
163
+ if (process.argv.length > 2 && !updatePi) throw new Error("Usage: node scripts/release.mjs [--update-pi]");
164
+ console.log("[piweb] Checking and building an isolated release; the existing server is not restarted.");
165
+ if (updatePi) console.log("[piweb] Pi dependency update: use a maintenance window with no active sessions; node_modules is shared.");
166
+ const release = await prepareRelease({
167
+ signal: controller.signal,
168
+ prepare: updatePi ? async (context) => { await installPiUpdate(context); } : undefined,
169
+ });
170
+ console.log(`[piweb] Release ready: ${release.buildDir} (${release.buildId}). Restart PiWeb manually when idle to use it.`);
191
171
  } catch (error) {
192
172
  console.error(`[piweb] Release failed: ${error instanceof Error ? error.message : error}`);
193
173
  process.exitCode = 1;
@@ -0,0 +1,34 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+
5
+ const [cwd, selected, reportFile] = process.argv.slice(2);
6
+ let runner;
7
+ try {
8
+ if (!cwd || !selected || !reportFile) throw new Error("expected workspace, test file and report path");
9
+ const api = await import(pathToFileURL(path.join(cwd, "node_modules/vitest/dist/node.js")).href);
10
+ const options = { root: cwd, run: true, watch: false, reporters: ["json"], outputFile: reportFile };
11
+ runner = Number(api.version.split(".")[0]) >= 5
12
+ ? await api.createVitest(options)
13
+ : await api.createVitest("test", options);
14
+ if (typeof runner.globTestSpecifications !== "function" || typeof runner.runTestSpecifications !== "function") {
15
+ throw new Error("this Vitest version does not support exact test specifications");
16
+ }
17
+ const canonical = (file) => process.platform === "win32" ? file.toLowerCase() : file;
18
+ const target = canonical(await fs.realpath(selected));
19
+ const specs = await runner.globTestSpecifications();
20
+ const exact = [];
21
+ for (const spec of specs) {
22
+ if (canonical(await fs.realpath(spec.moduleId)) === target) exact.push(spec);
23
+ }
24
+ if (!exact.length) throw new Error("selected file is excluded by the workspace Vitest configuration");
25
+ // Discovery is read-only; only exact specifications are handed to the executor.
26
+ if (typeof runner.standalone === "function") await runner.standalone();
27
+ else await runner.init();
28
+ await runner.runTestSpecifications(exact);
29
+ } catch (error) {
30
+ console.error(error instanceof Error ? error.message : String(error));
31
+ process.exitCode = 1;
32
+ } finally {
33
+ await runner?.close();
34
+ }
@@ -0,0 +1,24 @@
1
+ import path from "node:path";
2
+ import { createRequire } from "node:module";
3
+ import { pathToFileURL } from "node:url";
4
+ import { assertInstallation, resolveBuildOutput, RELEASES_DIR } from "./build-output.mjs";
5
+ import { isMainModule } from "./entrypoint.mjs";
6
+
7
+ /** Run in a fresh process after install/pull so cached Next config cannot be reused. */
8
+ export async function verifyReleaseConfig(root = process.cwd(), env = process.env) {
9
+ const installation = assertInstallation(root);
10
+ const buildDir = resolveBuildOutput(installation, env.PIWEB_BUILD_DIR);
11
+ if (!buildDir.startsWith(`${RELEASES_DIR}/`)) throw new Error("release requires an isolated build output");
12
+ const requireFromInstall = createRequire(path.join(installation, "package.json"));
13
+ const module = await import(pathToFileURL(requireFromInstall.resolve("next/dist/server/config.js")).href);
14
+ const loadConfig = typeof module.default === "function" ? module.default : module.default.default;
15
+ const { PHASE_PRODUCTION_BUILD } = requireFromInstall("next/constants");
16
+ const config = await loadConfig(PHASE_PRODUCTION_BUILD, installation, { silent: true });
17
+ if (config.distDir !== buildDir || config.typescript?.tsconfigPath !== `${RELEASES_DIR}/tsconfig.json` || config.typescript?.ignoreBuildErrors) {
18
+ throw new Error("Next config does not honor the isolated release directory/typecheck; refusing to build over the serving output");
19
+ }
20
+ }
21
+
22
+ if (isMainModule(import.meta.url)) {
23
+ await verifyReleaseConfig();
24
+ }
@@ -9,7 +9,10 @@ export const dynamic = "force-dynamic";
9
9
  export async function GET() {
10
10
  try {
11
11
  const file = path.join(getAgentDir(), "settings.json");
12
- const content = await fs.readFile(file, "utf8");
12
+ const content = await fs.readFile(file, "utf8").catch((error: NodeJS.ErrnoException) => {
13
+ if (error.code === "ENOENT") return "{}\n";
14
+ throw error;
15
+ });
13
16
  return new Response(content, {
14
17
  headers: {
15
18
  "Content-Type": "application/json; charset=utf-8",
@@ -1,17 +1,22 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import type { ProjectTrust } from "@/lib/security-service";
3
3
  import { getSecurity, setProjectTrust } from "@/lib/security-service";
4
- import { reloadLoader, setProjectTrust as setProjectTrustDecision } from "@/lib/pi";
4
+ import { reloadLoader, resolveProjectTrust, setProjectTrust as setProjectTrustDecision } from "@/lib/pi";
5
5
  import { reloadSessionsForCwd } from "@/lib/agent-manager";
6
6
  import { BoundaryError, resolveWorkspacePath } from "@/lib/path-security";
7
7
 
8
8
  export const dynamic = "force-dynamic";
9
9
 
10
- export async function GET() {
10
+ export async function GET(req: Request) {
11
11
  try {
12
+ const params = new URL(req.url).searchParams;
13
+ if (params.has("cwd")) {
14
+ const cwd = await resolveWorkspacePath(params.get("cwd"));
15
+ return NextResponse.json({ success: true, data: resolveProjectTrust(cwd) });
16
+ }
12
17
  return NextResponse.json({ success: true, data: await getSecurity() });
13
18
  } catch (err: any) {
14
- return NextResponse.json({ success: false, error: String(err?.message ?? err) }, { status: 500 });
19
+ return NextResponse.json({ success: false, error: String(err?.message ?? err) }, { status: err instanceof BoundaryError ? 400 : 500 });
15
20
  }
16
21
  }
17
22