@pasko70/pibo 3.2.7 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/dist/agent-runtime/capabilities.js +5 -0
  2. package/dist/agent-runtime/contract.js +2 -0
  3. package/dist/agent-runtime/routed-session.js +68 -11
  4. package/dist/agent-runtimes/codex-native/adapter.js +4 -1
  5. package/dist/agent-runtimes/codex-native/process.js +1 -1
  6. package/dist/agent-runtimes/codex-native/protocol-version.js +6 -6
  7. package/dist/agent-runtimes/codex-native/thread.js +26 -4
  8. package/dist/agent-runtimes/omp/adapter.js +1 -0
  9. package/dist/agent-runtimes/omp/process.js +23 -3
  10. package/dist/agent-runtimes/omp/protocol-version.js +2 -0
  11. package/dist/agent-runtimes/pi/adapter.js +5 -1
  12. package/dist/agent-runtimes/pi/model-catalog.js +2 -2
  13. package/dist/agent-runtimes/pi/routed-session.js +53 -5
  14. package/dist/agent-runtimes/pi/runtime.js +6 -3
  15. package/dist/apps/chat/chat-api-routes.js +12 -38
  16. package/dist/apps/chat/chat-request-normalizers.js +15 -50
  17. package/dist/apps/chat/data/legacy-project-migration.js +492 -0
  18. package/dist/apps/chat/data/room-service.js +2 -2
  19. package/dist/apps/chat/data/workflow-session-model.js +1 -0
  20. package/dist/apps/chat/data/workflow-session-service.js +352 -0
  21. package/dist/apps/chat/rooms.js +1 -1
  22. package/dist/apps/chat/session-metadata.js +30 -0
  23. package/dist/apps/chat/trace.js +15 -2
  24. package/dist/apps/chat/types/rooms.js +30 -0
  25. package/dist/apps/chat/web-app.js +355 -651
  26. package/dist/apps/chat/workflow-catalog.js +18 -18
  27. package/dist/apps/chat/{project-workflow-human-actions.js → workflow-human-actions.js} +14 -14
  28. package/dist/apps/chat/workflow-manual-trigger-runtime.js +5 -1
  29. package/dist/apps/chat/workflow-persistence.js +20 -28
  30. package/dist/apps/chat/{project-workflow-sessions.js → workflow-sessions.js} +18 -17
  31. package/dist/apps/chat-ui/assets/{dist-ENXYFYda.js → dist-AhtdAETR.js} +1 -1
  32. package/dist/apps/chat-ui/assets/{dist-CGgUuVB6.js → dist-Bk_L1qbJ.js} +1 -1
  33. package/dist/apps/chat-ui/assets/{dist-DG6T1maU.js → dist-Dyp6sLQn.js} +1 -1
  34. package/dist/apps/chat-ui/assets/{dist-CFtZTGHa.js → dist-jVaiGXFD.js} +1 -1
  35. package/dist/apps/chat-ui/assets/{dist-BikaGikn.js → dist-t25moyJS.js} +1 -1
  36. package/dist/apps/chat-ui/assets/index-CZjNw-aK.js +228 -0
  37. package/dist/apps/chat-ui/assets/index-CcEjFITM.css +1 -0
  38. package/dist/apps/chat-ui/index.html +2 -2
  39. package/dist/apps/chat-vscode-web/assets/index-Spj6M0tn.js +43 -0
  40. package/dist/apps/chat-vscode-web/index.html +1 -1
  41. package/dist/compute/pool/cli.js +1 -1
  42. package/dist/compute/pool/seeds.js +2 -3
  43. package/dist/config/config.js +1 -1
  44. package/dist/core/session-router.js +19 -10
  45. package/dist/debug/agents.js +9 -3
  46. package/dist/plugins/builtin.js +2 -0
  47. package/dist/plugins/registry.js +1 -0
  48. package/dist/previews/cli.js +154 -79
  49. package/dist/previews/compute-worker.js +37 -0
  50. package/dist/previews/config.js +2 -1
  51. package/dist/previews/migrations/session-ownership.js +7 -0
  52. package/dist/previews/proxy.js +4 -2
  53. package/dist/previews/public-setup.js +108 -0
  54. package/dist/previews/store.js +20 -13
  55. package/dist/previews/web-app.js +12 -2
  56. package/dist/providers/openai-gpt56.js +41 -3
  57. package/dist/sessions/workflow-session-kind.js +0 -3
  58. package/dist/subagents/context.js +2 -2
  59. package/dist/subagents/observation-query.js +49 -6
  60. package/dist/subagents/observation-text-regex.js +124 -0
  61. package/dist/subagents/tool.js +3 -2
  62. package/dist/tools/hashline.js +64 -0
  63. package/npm-shrinkwrap.json +786 -284
  64. package/package.json +11 -6
  65. package/packages/workflows/README.md +21 -0
  66. package/packages/workflows/dist/api/index.d.ts +48 -0
  67. package/packages/workflows/dist/api/index.d.ts.map +1 -0
  68. package/packages/workflows/dist/api/index.js +96 -0
  69. package/packages/workflows/dist/api/index.js.map +1 -0
  70. package/packages/workflows/dist/definition-hash.d.ts +4 -0
  71. package/packages/workflows/dist/definition-hash.d.ts.map +1 -0
  72. package/packages/workflows/dist/definition-hash.js +27 -0
  73. package/packages/workflows/dist/definition-hash.js.map +1 -0
  74. package/packages/workflows/dist/fixtures/index.d.ts +39 -0
  75. package/packages/workflows/dist/fixtures/index.d.ts.map +1 -0
  76. package/packages/workflows/dist/fixtures/index.js +608 -0
  77. package/packages/workflows/dist/fixtures/index.js.map +1 -0
  78. package/packages/workflows/dist/index.d.ts +16 -0
  79. package/packages/workflows/dist/index.d.ts.map +1 -0
  80. package/packages/workflows/dist/index.js +10 -0
  81. package/packages/workflows/dist/index.js.map +1 -0
  82. package/packages/workflows/dist/inspection/index.d.ts +47 -0
  83. package/packages/workflows/dist/inspection/index.d.ts.map +1 -0
  84. package/packages/workflows/dist/inspection/index.js +131 -0
  85. package/packages/workflows/dist/inspection/index.js.map +1 -0
  86. package/packages/workflows/dist/registry/index.d.ts +30 -0
  87. package/packages/workflows/dist/registry/index.d.ts.map +1 -0
  88. package/packages/workflows/dist/registry/index.js +161 -0
  89. package/packages/workflows/dist/registry/index.js.map +1 -0
  90. package/packages/workflows/dist/runtime/adapter-node.d.ts +28 -0
  91. package/packages/workflows/dist/runtime/adapter-node.d.ts.map +1 -0
  92. package/packages/workflows/dist/runtime/adapter-node.js +172 -0
  93. package/packages/workflows/dist/runtime/adapter-node.js.map +1 -0
  94. package/packages/workflows/dist/runtime/agent-node.d.ts +32 -0
  95. package/packages/workflows/dist/runtime/agent-node.d.ts.map +1 -0
  96. package/packages/workflows/dist/runtime/agent-node.js +231 -0
  97. package/packages/workflows/dist/runtime/agent-node.js.map +1 -0
  98. package/packages/workflows/dist/runtime/agent-runtime.d.ts +25 -0
  99. package/packages/workflows/dist/runtime/agent-runtime.d.ts.map +1 -0
  100. package/packages/workflows/dist/runtime/agent-runtime.js +111 -0
  101. package/packages/workflows/dist/runtime/agent-runtime.js.map +1 -0
  102. package/packages/workflows/dist/runtime/code-node.d.ts +31 -0
  103. package/packages/workflows/dist/runtime/code-node.d.ts.map +1 -0
  104. package/packages/workflows/dist/runtime/code-node.js +223 -0
  105. package/packages/workflows/dist/runtime/code-node.js.map +1 -0
  106. package/packages/workflows/dist/runtime/dispatch-failures.d.ts +36 -0
  107. package/packages/workflows/dist/runtime/dispatch-failures.d.ts.map +1 -0
  108. package/packages/workflows/dist/runtime/dispatch-failures.js +80 -0
  109. package/packages/workflows/dist/runtime/dispatch-failures.js.map +1 -0
  110. package/packages/workflows/dist/runtime/edge-payloads.d.ts +3 -0
  111. package/packages/workflows/dist/runtime/edge-payloads.d.ts.map +1 -0
  112. package/packages/workflows/dist/runtime/edge-payloads.js +11 -0
  113. package/packages/workflows/dist/runtime/edge-payloads.js.map +1 -0
  114. package/packages/workflows/dist/runtime/edge-transfer.d.ts +39 -0
  115. package/packages/workflows/dist/runtime/edge-transfer.d.ts.map +1 -0
  116. package/packages/workflows/dist/runtime/edge-transfer.js +356 -0
  117. package/packages/workflows/dist/runtime/edge-transfer.js.map +1 -0
  118. package/packages/workflows/dist/runtime/human-action.d.ts +43 -0
  119. package/packages/workflows/dist/runtime/human-action.d.ts.map +1 -0
  120. package/packages/workflows/dist/runtime/human-action.js +231 -0
  121. package/packages/workflows/dist/runtime/human-action.js.map +1 -0
  122. package/packages/workflows/dist/runtime/human-node.d.ts +27 -0
  123. package/packages/workflows/dist/runtime/human-node.d.ts.map +1 -0
  124. package/packages/workflows/dist/runtime/human-node.js +126 -0
  125. package/packages/workflows/dist/runtime/human-node.js.map +1 -0
  126. package/packages/workflows/dist/runtime/ids.d.ts +2 -0
  127. package/packages/workflows/dist/runtime/ids.d.ts.map +1 -0
  128. package/packages/workflows/dist/runtime/ids.js +4 -0
  129. package/packages/workflows/dist/runtime/ids.js.map +1 -0
  130. package/packages/workflows/dist/runtime/index.d.ts +23 -0
  131. package/packages/workflows/dist/runtime/index.d.ts.map +1 -0
  132. package/packages/workflows/dist/runtime/index.js +12 -0
  133. package/packages/workflows/dist/runtime/index.js.map +1 -0
  134. package/packages/workflows/dist/runtime/manual-trigger.d.ts +51 -0
  135. package/packages/workflows/dist/runtime/manual-trigger.d.ts.map +1 -0
  136. package/packages/workflows/dist/runtime/manual-trigger.js +403 -0
  137. package/packages/workflows/dist/runtime/manual-trigger.js.map +1 -0
  138. package/packages/workflows/dist/runtime/nested-workflow-node.d.ts +48 -0
  139. package/packages/workflows/dist/runtime/nested-workflow-node.d.ts.map +1 -0
  140. package/packages/workflows/dist/runtime/nested-workflow-node.js +246 -0
  141. package/packages/workflows/dist/runtime/nested-workflow-node.js.map +1 -0
  142. package/packages/workflows/dist/runtime/one-node-agent.d.ts +34 -0
  143. package/packages/workflows/dist/runtime/one-node-agent.d.ts.map +1 -0
  144. package/packages/workflows/dist/runtime/one-node-agent.js +334 -0
  145. package/packages/workflows/dist/runtime/one-node-agent.js.map +1 -0
  146. package/packages/workflows/dist/runtime/persistence.d.ts +9 -0
  147. package/packages/workflows/dist/runtime/persistence.d.ts.map +1 -0
  148. package/packages/workflows/dist/runtime/persistence.js +67 -0
  149. package/packages/workflows/dist/runtime/persistence.js.map +1 -0
  150. package/packages/workflows/dist/runtime/pibo-routing.d.ts +126 -0
  151. package/packages/workflows/dist/runtime/pibo-routing.d.ts.map +1 -0
  152. package/packages/workflows/dist/runtime/pibo-routing.js +96 -0
  153. package/packages/workflows/dist/runtime/pibo-routing.js.map +1 -0
  154. package/packages/workflows/dist/runtime/prompts.d.ts +20 -0
  155. package/packages/workflows/dist/runtime/prompts.d.ts.map +1 -0
  156. package/packages/workflows/dist/runtime/prompts.js +235 -0
  157. package/packages/workflows/dist/runtime/prompts.js.map +1 -0
  158. package/packages/workflows/dist/runtime/retry.d.ts +35 -0
  159. package/packages/workflows/dist/runtime/retry.d.ts.map +1 -0
  160. package/packages/workflows/dist/runtime/retry.js +80 -0
  161. package/packages/workflows/dist/runtime/retry.js.map +1 -0
  162. package/packages/workflows/dist/runtime/state.d.ts +14 -0
  163. package/packages/workflows/dist/runtime/state.d.ts.map +1 -0
  164. package/packages/workflows/dist/runtime/state.js +139 -0
  165. package/packages/workflows/dist/runtime/state.js.map +1 -0
  166. package/packages/workflows/dist/runtime/time.d.ts +6 -0
  167. package/packages/workflows/dist/runtime/time.d.ts.map +1 -0
  168. package/packages/workflows/dist/runtime/time.js +42 -0
  169. package/packages/workflows/dist/runtime/time.js.map +1 -0
  170. package/packages/workflows/dist/store/catalog-records.d.ts +18 -0
  171. package/packages/workflows/dist/store/catalog-records.d.ts.map +1 -0
  172. package/packages/workflows/dist/store/catalog-records.js +59 -0
  173. package/packages/workflows/dist/store/catalog-records.js.map +1 -0
  174. package/packages/workflows/dist/store/contracts.d.ts +149 -0
  175. package/packages/workflows/dist/store/contracts.d.ts.map +1 -0
  176. package/packages/workflows/dist/store/contracts.js +2 -0
  177. package/packages/workflows/dist/store/contracts.js.map +1 -0
  178. package/packages/workflows/dist/store/draft-writes.d.ts +7 -0
  179. package/packages/workflows/dist/store/draft-writes.d.ts.map +1 -0
  180. package/packages/workflows/dist/store/draft-writes.js +18 -0
  181. package/packages/workflows/dist/store/draft-writes.js.map +1 -0
  182. package/packages/workflows/dist/store/index.d.ts +55 -0
  183. package/packages/workflows/dist/store/index.d.ts.map +1 -0
  184. package/packages/workflows/dist/store/index.js +650 -0
  185. package/packages/workflows/dist/store/index.js.map +1 -0
  186. package/packages/workflows/dist/store/list-query.d.ts +13 -0
  187. package/packages/workflows/dist/store/list-query.d.ts.map +1 -0
  188. package/packages/workflows/dist/store/list-query.js +20 -0
  189. package/packages/workflows/dist/store/list-query.js.map +1 -0
  190. package/packages/workflows/dist/store/row-mappers.d.ts +206 -0
  191. package/packages/workflows/dist/store/row-mappers.d.ts.map +1 -0
  192. package/packages/workflows/dist/store/row-mappers.js +262 -0
  193. package/packages/workflows/dist/store/row-mappers.js.map +1 -0
  194. package/packages/workflows/dist/store/schema.d.ts +13 -0
  195. package/packages/workflows/dist/store/schema.d.ts.map +1 -0
  196. package/packages/workflows/dist/store/schema.js +458 -0
  197. package/packages/workflows/dist/store/schema.js.map +1 -0
  198. package/packages/workflows/dist/store/write-values.d.ts +18 -0
  199. package/packages/workflows/dist/store/write-values.d.ts.map +1 -0
  200. package/packages/workflows/dist/store/write-values.js +227 -0
  201. package/packages/workflows/dist/store/write-values.js.map +1 -0
  202. package/packages/workflows/dist/types/index.d.ts +1202 -0
  203. package/packages/workflows/dist/types/index.d.ts.map +1 -0
  204. package/packages/workflows/dist/types/index.js +8 -0
  205. package/packages/workflows/dist/types/index.js.map +1 -0
  206. package/packages/workflows/dist/validation/diagnostics.d.ts +27 -0
  207. package/packages/workflows/dist/validation/diagnostics.d.ts.map +1 -0
  208. package/packages/workflows/dist/validation/diagnostics.js +104 -0
  209. package/packages/workflows/dist/validation/diagnostics.js.map +1 -0
  210. package/packages/workflows/dist/validation/graph-cycles.d.ts +4 -0
  211. package/packages/workflows/dist/validation/graph-cycles.d.ts.map +1 -0
  212. package/packages/workflows/dist/validation/graph-cycles.js +130 -0
  213. package/packages/workflows/dist/validation/graph-cycles.js.map +1 -0
  214. package/packages/workflows/dist/validation/graph-edges.d.ts +5 -0
  215. package/packages/workflows/dist/validation/graph-edges.d.ts.map +1 -0
  216. package/packages/workflows/dist/validation/graph-edges.js +48 -0
  217. package/packages/workflows/dist/validation/graph-edges.js.map +1 -0
  218. package/packages/workflows/dist/validation/graph-ports.d.ts +5 -0
  219. package/packages/workflows/dist/validation/graph-ports.d.ts.map +1 -0
  220. package/packages/workflows/dist/validation/graph-ports.js +55 -0
  221. package/packages/workflows/dist/validation/graph-ports.js.map +1 -0
  222. package/packages/workflows/dist/validation/index.d.ts +18 -0
  223. package/packages/workflows/dist/validation/index.d.ts.map +1 -0
  224. package/packages/workflows/dist/validation/index.js +175 -0
  225. package/packages/workflows/dist/validation/index.js.map +1 -0
  226. package/packages/workflows/dist/validation/json-schema.d.ts +12 -0
  227. package/packages/workflows/dist/validation/json-schema.d.ts.map +1 -0
  228. package/packages/workflows/dist/validation/json-schema.js +556 -0
  229. package/packages/workflows/dist/validation/json-schema.js.map +1 -0
  230. package/packages/workflows/dist/validation/registry-refs.d.ts +21 -0
  231. package/packages/workflows/dist/validation/registry-refs.d.ts.map +1 -0
  232. package/packages/workflows/dist/validation/registry-refs.js +283 -0
  233. package/packages/workflows/dist/validation/registry-refs.js.map +1 -0
  234. package/packages/workflows/dist/validation/retry-policy.d.ts +3 -0
  235. package/packages/workflows/dist/validation/retry-policy.d.ts.map +1 -0
  236. package/packages/workflows/dist/validation/retry-policy.js +114 -0
  237. package/packages/workflows/dist/validation/retry-policy.js.map +1 -0
  238. package/packages/workflows/dist/validation/schema-declarations.d.ts +5 -0
  239. package/packages/workflows/dist/validation/schema-declarations.d.ts.map +1 -0
  240. package/packages/workflows/dist/validation/schema-declarations.js +36 -0
  241. package/packages/workflows/dist/validation/schema-declarations.js.map +1 -0
  242. package/packages/workflows/dist/validation/state-access.d.ts +4 -0
  243. package/packages/workflows/dist/validation/state-access.d.ts.map +1 -0
  244. package/packages/workflows/dist/validation/state-access.js +121 -0
  245. package/packages/workflows/dist/validation/state-access.js.map +1 -0
  246. package/packages/workflows/dist/xstate/index.d.ts +73 -0
  247. package/packages/workflows/dist/xstate/index.d.ts.map +1 -0
  248. package/packages/workflows/dist/xstate/index.js +728 -0
  249. package/packages/workflows/dist/xstate/index.js.map +1 -0
  250. package/packages/workflows/package.json +23 -0
  251. package/dist/apps/chat/data/project-service.js +0 -1029
  252. package/dist/apps/chat-ui/assets/index-BwT5grLv.css +0 -1
  253. package/dist/apps/chat-ui/assets/index-PTJanD3E.js +0 -228
  254. package/dist/apps/chat-vscode-web/assets/index-D1bpAUkR.js +0 -43
@@ -0,0 +1,108 @@
1
+ import { lookup } from "node:dns/promises";
2
+ import { isIP } from "node:net";
3
+ import { PREVIEW_TLS_AUTHORIZATION_PATH, previewPublicURL, requirePreviewBaseURL } from "./config.js";
4
+ function shellQuote(value) {
5
+ return `'${value.replace(/'/g, `'\\''`)}'`;
6
+ }
7
+ function validateGatewayPort(port) {
8
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
9
+ throw new Error("Gateway port must be an integer between 1 and 65535");
10
+ return port;
11
+ }
12
+ function validatePublicIp(value) {
13
+ if (!value)
14
+ return { type: "A or AAAA", value: "<public-gateway-ip>" };
15
+ const family = isIP(value);
16
+ if (family === 0)
17
+ throw new Error("Public IP must be a valid IPv4 or IPv6 address");
18
+ return { type: family === 4 ? "A" : "AAAA", value };
19
+ }
20
+ export function createPreviewProductionSetupPlan(input) {
21
+ const baseURL = requirePreviewBaseURL(input.baseURL);
22
+ const gatewayPort = validateGatewayPort(input.gatewayPort ?? 4788);
23
+ const publicIp = validatePublicIp(input.publicIp);
24
+ const wildcardHostname = `*.${baseURL.hostname}`;
25
+ const caddySiteAddress = baseURL.port ? `${wildcardHostname}:${baseURL.port}` : wildcardHostname;
26
+ const tlsAuthorizationURL = `http://127.0.0.1:${gatewayPort}${PREVIEW_TLS_AUTHORIZATION_PATH}`;
27
+ return {
28
+ baseURL: baseURL.toString(),
29
+ wildcardHostname,
30
+ dnsRecord: {
31
+ type: publicIp.type,
32
+ name: wildcardHostname,
33
+ value: publicIp.value,
34
+ },
35
+ gateway: {
36
+ host: "127.0.0.1",
37
+ port: gatewayPort,
38
+ tlsAuthorizationURL,
39
+ },
40
+ caddy: {
41
+ globalOptions: `on_demand_tls {\n\task ${tlsAuthorizationURL}\n}`,
42
+ siteBlock: `${caddySiteAddress} {\n\ttls {\n\t\ton_demand\n\t}\n\treverse_proxy 127.0.0.1:${gatewayPort}\n}`,
43
+ },
44
+ commands: {
45
+ configure: `pibo config set preview.baseURL ${shellQuote(baseURL.origin)}`,
46
+ restartGateway: "pibo gateway web restart",
47
+ validateCaddy: "caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy",
48
+ verify: "pibo preview doctor <preview-id> --public",
49
+ },
50
+ warnings: [
51
+ "Merge the on_demand_tls stanza into an existing Caddy global options block; a Caddyfile may contain only one global options block.",
52
+ "Create the wildcard DNS record before Caddy requests the first Preview certificate.",
53
+ "Restart the Pibo gateway after setting preview.baseURL; the safe restart may wait for active sessions.",
54
+ ],
55
+ };
56
+ }
57
+ async function resolveHostname(hostname) {
58
+ return (await lookup(hostname, { all: true })).map((entry) => entry.address);
59
+ }
60
+ async function requestPreview(url) {
61
+ const response = await fetch(url, {
62
+ method: "GET",
63
+ redirect: "manual",
64
+ signal: AbortSignal.timeout(10_000),
65
+ });
66
+ return { status: response.status, location: response.headers.get("location") ?? undefined };
67
+ }
68
+ function errorDetail(error) {
69
+ return error instanceof Error ? error.message : String(error);
70
+ }
71
+ export async function inspectPreviewPublicRoute(baseURLValue, previewId, options = {}) {
72
+ const baseURL = requirePreviewBaseURL(baseURLValue instanceof URL ? baseURLValue.toString() : baseURLValue);
73
+ const publicURL = previewPublicURL(previewId, baseURL);
74
+ let addresses = [];
75
+ let dns;
76
+ try {
77
+ addresses = await (options.resolveHostname ?? resolveHostname)(publicURL.hostname);
78
+ dns = addresses.length > 0
79
+ ? { status: "ok", detail: `Resolved ${publicURL.hostname}`, addresses }
80
+ : { status: "fail", detail: `${publicURL.hostname} returned no addresses`, addresses };
81
+ }
82
+ catch (error) {
83
+ dns = { status: "fail", detail: `DNS lookup failed: ${errorDetail(error)}`, addresses };
84
+ }
85
+ let tlsAndRouting;
86
+ try {
87
+ const response = await (options.request ?? requestPreview)(publicURL);
88
+ if (response.status === 401) {
89
+ tlsAndRouting = {
90
+ status: "ok",
91
+ detail: "HTTPS reached the Preview gateway and anonymous access was rejected",
92
+ httpStatus: response.status,
93
+ };
94
+ }
95
+ else {
96
+ tlsAndRouting = {
97
+ status: "fail",
98
+ detail: `Expected HTTP 401 from the Preview gateway, received HTTP ${response.status}`,
99
+ httpStatus: response.status,
100
+ ...(response.location ? { location: response.location } : {}),
101
+ };
102
+ }
103
+ }
104
+ catch (error) {
105
+ tlsAndRouting = { status: "fail", detail: `HTTPS request failed: ${errorDetail(error)}` };
106
+ }
107
+ return { publicURL: publicURL.toString(), dns, tlsAndRouting };
108
+ }
@@ -5,7 +5,8 @@ import { DatabaseSync } from "node:sqlite";
5
5
  import { ensurePrivatePiboHomeForPath, piboHomePath } from "../core/pibo-home.js";
6
6
  import { protectPrivateFileSync } from "../core/private-path.js";
7
7
  import { loadPreviewConfig } from "./config.js";
8
- export const PREVIEW_SCHEMA_VERSION = 4;
8
+ import { migratePreviewSessionOwnership } from "./migrations/session-ownership.js";
9
+ export const PREVIEW_SCHEMA_VERSION = 6;
9
10
  export const MAX_ACTIVE_PREVIEW_EXPOSURES = 256;
10
11
  export const MAX_ACTIVE_PREVIEW_EXPOSURES_PER_SESSION = 16;
11
12
  export const MAX_OUTSTANDING_PREVIEW_TICKETS = 32;
@@ -52,8 +53,6 @@ function validateExposureInput(input) {
52
53
  throw new Error("Pibo Session id is invalid");
53
54
  if (!input.label.trim() || input.label.length > 120)
54
55
  throw new Error("Preview label must contain 1 to 120 characters");
55
- if (input.projectId && input.projectId.length > 256)
56
- throw new Error("Preview Project id is too long");
57
56
  if (!input.workspace || input.workspace.length > 4_096)
58
57
  throw new Error("Preview workspace is invalid");
59
58
  if (!Number.isInteger(input.targetPort) || input.targetPort < 1024 || input.targetPort > 65535) {
@@ -66,13 +65,15 @@ function validateExposureInput(input) {
66
65
  if ((input.managementMode ?? "external") === "managed" && !input.startCommand) {
67
66
  throw new Error("Managed Preview definitions require a start command");
68
67
  }
68
+ if (input.proxyMode !== undefined && input.proxyMode !== "standard" && input.proxyMode !== "pibo-compute-dev-auth") {
69
+ throw new Error("Preview proxy mode is invalid");
70
+ }
69
71
  }
70
72
  function exposureFromRow(row) {
71
73
  const managementMode = row.management_mode === "managed" ? "managed" : "external";
72
74
  return {
73
75
  id: row.id,
74
76
  piboSessionId: row.pibo_session_id,
75
- projectId: row.project_id ?? undefined,
76
77
  label: row.label,
77
78
  targetHost: row.target_host,
78
79
  targetPort: row.target_port,
@@ -80,6 +81,7 @@ function exposureFromRow(row) {
80
81
  targetProcessStartTicks: row.target_process_start_ticks ?? undefined,
81
82
  workspace: row.workspace,
82
83
  managementMode,
84
+ proxyMode: row.proxy_mode === "pibo-compute-dev-auth" ? "pibo-compute-dev-auth" : "standard",
83
85
  startCommand: row.start_command ?? undefined,
84
86
  serverState: managementMode === "managed" && row.server_state !== "external" ? row.server_state : undefined,
85
87
  serverGeneration: row.server_generation ?? undefined,
@@ -107,6 +109,7 @@ function sameAuthorizationExposure(first, second) {
107
109
  return first.id === second.id &&
108
110
  first.createdAt === second.createdAt &&
109
111
  first.managementMode === second.managementMode &&
112
+ first.proxyMode === second.proxyMode &&
110
113
  first.serverGeneration === second.serverGeneration &&
111
114
  first.targetHost === second.targetHost &&
112
115
  first.targetPort === second.targetPort &&
@@ -165,7 +168,6 @@ export class PreviewStore {
165
168
  CREATE TABLE IF NOT EXISTS preview_exposures (
166
169
  id TEXT PRIMARY KEY,
167
170
  pibo_session_id TEXT NOT NULL,
168
- project_id TEXT,
169
171
  label TEXT NOT NULL,
170
172
  target_host TEXT NOT NULL CHECK (target_host IN ('127.0.0.1', '::1')),
171
173
  target_port INTEGER NOT NULL CHECK (target_port BETWEEN 1024 AND 65535),
@@ -173,6 +175,7 @@ export class PreviewStore {
173
175
  target_process_start_ticks TEXT,
174
176
  workspace TEXT NOT NULL,
175
177
  management_mode TEXT NOT NULL DEFAULT 'external' CHECK (management_mode IN ('external', 'managed')),
178
+ proxy_mode TEXT NOT NULL DEFAULT 'standard' CHECK (proxy_mode IN ('standard', 'pibo-compute-dev-auth')),
176
179
  start_command TEXT,
177
180
  server_state TEXT NOT NULL DEFAULT 'external' CHECK (server_state IN ('external', 'stopped', 'starting', 'running', 'stopping', 'error')),
178
181
  server_generation TEXT,
@@ -217,6 +220,7 @@ export class PreviewStore {
217
220
  ["target_process_id", "INTEGER"],
218
221
  ["target_process_start_ticks", "TEXT"],
219
222
  ["management_mode", "TEXT NOT NULL DEFAULT 'external'"],
223
+ ["proxy_mode", "TEXT NOT NULL DEFAULT 'standard' CHECK (proxy_mode IN ('standard', 'pibo-compute-dev-auth'))"],
220
224
  ["start_command", "TEXT"],
221
225
  ["server_state", "TEXT NOT NULL DEFAULT 'external'"],
222
226
  ["server_generation", "TEXT"],
@@ -244,7 +248,6 @@ export class PreviewStore {
244
248
  CREATE TABLE preview_exposures (
245
249
  id TEXT PRIMARY KEY,
246
250
  pibo_session_id TEXT NOT NULL,
247
- project_id TEXT,
248
251
  label TEXT NOT NULL,
249
252
  target_host TEXT NOT NULL CHECK (target_host IN ('127.0.0.1', '::1')),
250
253
  target_port INTEGER NOT NULL CHECK (target_port BETWEEN 1024 AND 65535),
@@ -252,6 +255,7 @@ export class PreviewStore {
252
255
  target_process_start_ticks TEXT,
253
256
  workspace TEXT NOT NULL,
254
257
  management_mode TEXT NOT NULL DEFAULT 'external' CHECK (management_mode IN ('external', 'managed')),
258
+ proxy_mode TEXT NOT NULL DEFAULT 'standard' CHECK (proxy_mode IN ('standard', 'pibo-compute-dev-auth')),
255
259
  start_command TEXT,
256
260
  server_state TEXT NOT NULL DEFAULT 'external' CHECK (server_state IN ('external', 'stopped', 'starting', 'running', 'stopping', 'error')),
257
261
  server_generation TEXT,
@@ -268,17 +272,17 @@ export class PreviewStore {
268
272
  closed_at TEXT
269
273
  );
270
274
  INSERT INTO preview_exposures (
271
- id, pibo_session_id, project_id, label, target_host, target_port,
275
+ id, pibo_session_id, label, target_host, target_port,
272
276
  target_process_id, target_process_start_ticks, workspace,
273
- management_mode, start_command, server_state, server_generation,
277
+ management_mode, proxy_mode, start_command, server_state, server_generation,
274
278
  server_started_at, server_stop_at, server_stopped_at, server_error,
275
279
  manager_kind, manager_id, manager_pid, manager_process_start_ticks,
276
280
  created_at, expires_at, closed_at
277
281
  )
278
282
  SELECT
279
- id, pibo_session_id, project_id, label, target_host, target_port,
283
+ id, pibo_session_id, label, target_host, target_port,
280
284
  target_process_id, target_process_start_ticks, workspace,
281
- management_mode, start_command, server_state, server_generation,
285
+ management_mode, proxy_mode, start_command, server_state, server_generation,
282
286
  server_started_at, server_stop_at, server_stopped_at, server_error,
283
287
  manager_kind, manager_id, manager_pid, manager_process_start_ticks,
284
288
  created_at, expires_at, closed_at
@@ -292,6 +296,8 @@ export class PreviewStore {
292
296
  CREATE INDEX IF NOT EXISTS preview_exposures_managed_state_idx
293
297
  ON preview_exposures (management_mode, server_state, expires_at)
294
298
  `);
299
+ if (currentVersion < 5)
300
+ migratePreviewSessionOwnership(this.db);
295
301
  this.db.exec(`PRAGMA user_version = ${PREVIEW_SCHEMA_VERSION}`);
296
302
  this.db.exec("COMMIT");
297
303
  }
@@ -314,6 +320,7 @@ export class PreviewStore {
314
320
  createExposure(input) {
315
321
  validateExposureInput(input);
316
322
  const managementMode = input.managementMode ?? "external";
323
+ const proxyMode = input.proxyMode ?? "standard";
317
324
  const serverState = managementMode === "managed" ? input.serverState ?? "stopped" : "external";
318
325
  this.db.exec("BEGIN IMMEDIATE");
319
326
  try {
@@ -327,12 +334,12 @@ export class PreviewStore {
327
334
  }
328
335
  this.db.prepare(`
329
336
  INSERT INTO preview_exposures (
330
- id, pibo_session_id, project_id, label, target_host, target_port,
337
+ id, pibo_session_id, label, target_host, target_port,
331
338
  target_process_id, target_process_start_ticks, workspace,
332
- management_mode, start_command, server_state,
339
+ management_mode, proxy_mode, start_command, server_state,
333
340
  created_at, expires_at, closed_at
334
341
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)
335
- `).run(input.id, input.piboSessionId, input.projectId ?? null, input.label, input.targetHost, input.targetPort, input.targetProcessId ?? null, input.targetProcessStartTicks ?? null, input.workspace, managementMode, input.startCommand ?? null, serverState, input.createdAt, input.expiresAt);
342
+ `).run(input.id, input.piboSessionId, input.label, input.targetHost, input.targetPort, input.targetProcessId ?? null, input.targetProcessStartTicks ?? null, input.workspace, managementMode, proxyMode, input.startCommand ?? null, serverState, input.createdAt, input.expiresAt);
336
343
  const exposure = this.requireExposure(input.id);
337
344
  this.db.exec("COMMIT");
338
345
  return exposure;
@@ -1,6 +1,6 @@
1
1
  import { loadPiboConfig } from "../config/config.js";
2
2
  import { PiboWebHttpError, responseHtml, responseJson } from "../web/http.js";
3
- import { DEFAULT_PREVIEW_SESSION_TTL_MINUTES, DEFAULT_PREVIEW_TICKET_TTL_SECONDS, previewIdFromHostname, previewPublicURL, requirePreviewBaseURL, } from "./config.js";
3
+ import { DEFAULT_PREVIEW_SESSION_TTL_MINUTES, DEFAULT_PREVIEW_TICKET_TTL_SECONDS, PREVIEW_TLS_AUTHORIZATION_PATH, previewIdFromHostname, previewPublicURL, requirePreviewBaseURL, } from "./config.js";
4
4
  import { createDefaultPreviewProcessController, reconcileManagedPreviews, startManagedPreview, stopManagedPreview, } from "./manager.js";
5
5
  import { isPreviewTargetProcessCurrent, probePreviewTarget } from "./network.js";
6
6
  import { cookieValue, DEFAULT_MAX_PREVIEW_PROXY_CONNECTIONS, DEFAULT_MAX_PREVIEW_PROXY_CONNECTIONS_PER_PREVIEW, PreviewProxyLimiter, previewSessionCookieName, proxyPreviewHttp, proxyPreviewWebSocket, } from "./proxy.js";
@@ -59,7 +59,7 @@ async function exposureHealth(exposure) {
59
59
  return online ? "online" : "offline";
60
60
  }
61
61
  async function publicExposure(exposure, baseURL) {
62
- const { workspace: _workspace, startCommand: _startCommand, targetHost: _targetHost, targetPort: _targetPort, managementMode: _managementMode, targetProcessId: _targetProcessId, targetProcessStartTicks: _targetProcessStartTicks, serverError: _serverError, serverGeneration: _serverGeneration, managerKind: _managerKind, managerId: _managerId, managerPid: _managerPid, managerProcessStartTicks: _managerProcessStartTicks, ...publicFields } = exposure;
62
+ const { workspace: _workspace, startCommand: _startCommand, targetHost: _targetHost, targetPort: _targetPort, managementMode: _managementMode, proxyMode: _proxyMode, targetProcessId: _targetProcessId, targetProcessStartTicks: _targetProcessStartTicks, serverError: _serverError, serverGeneration: _serverGeneration, managerKind: _managerKind, managerId: _managerId, managerPid: _managerPid, managerProcessStartTicks: _managerProcessStartTicks, ...publicFields } = exposure;
63
63
  return {
64
64
  ...publicFields,
65
65
  managed: exposure.managementMode === "managed",
@@ -250,6 +250,16 @@ export function createPreviewWebApp(options = {}) {
250
250
  const url = new URL(request.url);
251
251
  if (!url.pathname.startsWith(PREVIEW_WEB_API_PREFIX))
252
252
  return undefined;
253
+ if (url.pathname === PREVIEW_TLS_AUTHORIZATION_PATH) {
254
+ if (request.method !== "GET")
255
+ return new Response(null, { status: 405, headers: { allow: "GET", "cache-control": "no-store" } });
256
+ const previewId = baseURL ? previewIdFromHostname(url.searchParams.get("domain") ?? "", baseURL) : undefined;
257
+ const allowed = previewId ? withStore(databasePath, (store) => {
258
+ const exposure = store.getExposure(previewId);
259
+ return Boolean(exposure && previewExposureState(exposure) === "active");
260
+ }) : false;
261
+ return new Response(null, { status: allowed ? 200 : 403, headers: { "cache-control": "no-store" } });
262
+ }
253
263
  await context.requireSession({ request });
254
264
  if (url.pathname === PREVIEW_WEB_API_PREFIX && request.method === "GET") {
255
265
  const piboSessionId = url.searchParams.get("piboSessionId")?.trim();
@@ -9,6 +9,8 @@ export const OPENAI_CODEX_BASE_URL = "https://chatgpt.com/backend-api";
9
9
  const OPENAI_GPT_56_CONTEXT_WINDOW = 1_050_000;
10
10
  const OPENAI_CODEX_GPT_56_CONTEXT_WINDOW = 272_000;
11
11
  const GPT_56_MAX_TOKENS = 128_000;
12
+ const OPENAI_CODEX_GPT_6_ASTRA_CONTEXT_WINDOW = 272_000;
13
+ const GPT_6_ASTRA_MAX_TOKENS = 128_000;
12
14
  export const OPENAI_GPT_56_MODELS = [
13
15
  {
14
16
  id: "gpt-5.6",
@@ -31,6 +33,11 @@ export const OPENAI_GPT_56_MODELS = [
31
33
  cost: { input: 1, output: 6, cacheRead: 0.1, cacheWrite: 1.25 },
32
34
  },
33
35
  ];
36
+ export const OPENAI_CODEX_GPT_6_ASTRA_MODEL = {
37
+ id: "gpt-6-astra",
38
+ name: "GPT-6-Astra",
39
+ cost: { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
40
+ };
34
41
  const OPENAI_GPT_56_MODEL_IDS = new Set(OPENAI_GPT_56_MODELS.map((model) => model.id));
35
42
  export function getBuiltInOpenAiModels() {
36
43
  return getBuiltInProviderModels(OPENAI_PROVIDER_ID);
@@ -60,7 +67,13 @@ export function buildOpenAiCodexGpt56Models(baseModels = getBuiltInOpenAiCodexMo
60
67
  modelCost: (model) => ({ ...model.cost, cacheWrite: 0 }),
61
68
  });
62
69
  }
63
- export function registerOpenAiGpt56Models(modelRegistry, options = {}) {
70
+ export function buildOpenAiCodexSupplementalModels(baseModels = getBuiltInOpenAiCodexModels()) {
71
+ const models = buildOpenAiCodexGpt56Models(baseModels);
72
+ if (models.some((model) => model.id === OPENAI_CODEX_GPT_6_ASTRA_MODEL.id))
73
+ return models;
74
+ return [...models, openAiCodexAstraModelToRegistryModel()];
75
+ }
76
+ export function registerOpenAiSupplementalModels(modelRegistry, options = {}) {
64
77
  const baseOpenAiModels = options.baseOpenAiModels ?? getBuiltInOpenAiModels();
65
78
  const openAiModels = buildOpenAiGpt56Models(baseOpenAiModels);
66
79
  const openAiAdded = countMissingGpt56Models(baseOpenAiModels, OPENAI_PROVIDER_ID);
@@ -71,8 +84,10 @@ export function registerOpenAiGpt56Models(modelRegistry, options = {}) {
71
84
  models: openAiModels,
72
85
  });
73
86
  const baseOpenAiCodexModels = options.baseOpenAiCodexModels ?? getBuiltInOpenAiCodexModels();
74
- const openAiCodexModels = buildOpenAiCodexGpt56Models(baseOpenAiCodexModels);
75
- const openAiCodexAdded = countMissingGpt56Models(baseOpenAiCodexModels, OPENAI_CODEX_PROVIDER_ID);
87
+ const openAiCodexModels = buildOpenAiCodexSupplementalModels(baseOpenAiCodexModels);
88
+ const hasBuiltInAstra = baseOpenAiCodexModels.some((model) => model.provider === OPENAI_CODEX_PROVIDER_ID && model.id === OPENAI_CODEX_GPT_6_ASTRA_MODEL.id);
89
+ const openAiCodexAdded = countMissingGpt56Models(baseOpenAiCodexModels, OPENAI_CODEX_PROVIDER_ID)
90
+ + (hasBuiltInAstra ? 0 : 1);
76
91
  modelRegistry.registerProvider(OPENAI_CODEX_PROVIDER_ID, {
77
92
  baseUrl: OPENAI_CODEX_BASE_URL,
78
93
  api: OPENAI_CODEX_RESPONSES_API,
@@ -85,6 +100,7 @@ export function registerOpenAiGpt56Models(modelRegistry, options = {}) {
85
100
  added: openAiAdded + openAiCodexAdded,
86
101
  };
87
102
  }
103
+ export const registerOpenAiGpt56Models = registerOpenAiSupplementalModels;
88
104
  export function findOpenAiGpt56Model(modelRegistry, model) {
89
105
  if (!model?.provider || !model.id)
90
106
  return undefined;
@@ -140,3 +156,25 @@ function openAiGpt56ModelToRegistryModel(model, options) {
140
156
  maxTokens: GPT_56_MAX_TOKENS,
141
157
  };
142
158
  }
159
+ function openAiCodexAstraModelToRegistryModel() {
160
+ return {
161
+ id: OPENAI_CODEX_GPT_6_ASTRA_MODEL.id,
162
+ name: OPENAI_CODEX_GPT_6_ASTRA_MODEL.name,
163
+ api: OPENAI_CODEX_RESPONSES_API,
164
+ provider: OPENAI_CODEX_PROVIDER_ID,
165
+ baseUrl: OPENAI_CODEX_BASE_URL,
166
+ reasoning: true,
167
+ // Codex also advertises "ultra", but Pibo's portable thinking-level contract ends at "max".
168
+ thinkingLevelMap: { off: null, minimal: null, xhigh: "xhigh", max: "max" },
169
+ input: ["text", "image"],
170
+ compat: {
171
+ supportsOpenAIGrammarTools: true,
172
+ supportsAdditionalTools: true,
173
+ supportsToolSearch: true,
174
+ },
175
+ cost: { ...OPENAI_CODEX_GPT_6_ASTRA_MODEL.cost, cacheWrite: 0 },
176
+ // Codex uses 272k by default and advertises 872k only as an optional maximum override.
177
+ contextWindow: OPENAI_CODEX_GPT_6_ASTRA_CONTEXT_WINDOW,
178
+ maxTokens: GPT_6_ASTRA_MAX_TOKENS,
179
+ };
180
+ }
@@ -7,9 +7,6 @@ export function workflowSessionKindFromMetadata(metadata) {
7
7
  const value = metadata?.[PIBO_WORKFLOW_SESSION_KIND_METADATA_KEY];
8
8
  if (isPiboWorkflowSessionKind(value))
9
9
  return value;
10
- // Backfill the initial Project workflow metadata shape used before the stable enum.
11
- if (metadata?.projectSessionKind === "main")
12
- return "main_workflow";
13
10
  return undefined;
14
11
  }
15
12
  export function withWorkflowSessionKind(metadata, workflowSessionKind) {
@@ -33,14 +33,14 @@ export function getDelegatedAgentContextFile(subagents) {
33
33
  "",
34
34
  "pibo_run_wait({ runId, timeoutMs? }) # bounded wait only; expiry does not stop the child",
35
35
  "pibo_run_status({ runId }) # compact lifecycle state",
36
- "pibo_agents_observe({ requestIds?: [runId], afterSequence?, limit?, includeTools?, toolDetail?, ... })",
36
+ "pibo_agents_observe({ requestIds?: [runId], textContains?, textRegex?, afterSequence?, limit?, includeTools?, toolDetail?, ... })",
37
37
  "pibo_run_read({ runId }) # terminal result, including the complete final agent message",
38
38
  "pibo_run_cancel({ runId }) # explicit request cancellation",
39
39
  "pibo_agents_list_agents({}) # available definitions and persistent child instances",
40
40
  "pibo_agents_kill({ agentId }) # terminate one persistent child session subtree",
41
41
  "```",
42
42
  "",
43
- "Set `sessionName` on every send to a nonblank human-readable child title of at most 40 Unicode code points. Pibo trims surrounding whitespace and rejects missing, blank, non-string, or oversized names before creating a yielded run or child session. Reuse a stable `threadKey` to continue the same child Pibo Session; a new `sessionName` updates its title without changing identity. A wait timeout is only an orchestrator wake-up. Observe defaults to the newest 20 completed assistant messages with streaming deltas, duplicate tool progress events, and tools hidden. Set `includeTools: true` for compact tool call/result summaries, `toolDetail: \"full\"` only for bounded diagnostics, and `limit: 50` when a larger page is necessary. Use `afterSequence` from the prior result for polling.",
43
+ "Set `sessionName` on every send to a nonblank human-readable child title of at most 40 Unicode code points. Pibo trims surrounding whitespace and rejects missing, blank, non-string, or oversized names before creating a yielded run or child session. Reuse a stable `threadKey` to continue the same child Pibo Session; a new `sessionName` updates its title without changing identity. A wait timeout is only an orchestrator wake-up. Observe defaults to the newest 20 completed assistant messages with streaming deltas, duplicate tool progress events, and tools hidden. Use `textContains` for case-insensitive substring matching or `textRegex` for case-sensitive rg/Rust-regex matching; both must match when supplied together. Set `includeTools: true` for compact tool call/result summaries, `toolDetail: \"full\"` only for bounded diagnostics, and `limit: 50` when a larger page is necessary. Use `afterSequence` from the prior result for polling.",
44
44
  "",
45
45
  "Observe progress and decide whether to continue waiting, steer through a new message after the current turn, cancel the request, or kill the child session.",
46
46
  "",
@@ -1,4 +1,5 @@
1
1
  import { PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES, PIBO_AGENT_OBSERVATION_DEFAULT_TOOL_EVENT_TYPES, normalizePiboAgentObservationCursor, normalizePiboAgentObservationLimit, normalizePiboAgentObservationOrder, normalizePiboAgentObservationToolDetail, parsePiboAgentObservationTimestamp, piboAgentObservationKind, piboAgentObservationToolSummary, } from "./observations.js";
2
+ import { PIBO_AGENT_TEXT_REGEX_BATCH_MAX_ITEMS, PIBO_AGENT_TEXT_REGEX_BATCH_TARGET_BYTES, matchPiboAgentObservationTextRegex, preparePiboAgentObservationTextRegex, } from "./observation-text-regex.js";
2
3
  export function preparePiboAgentObservationQuery(input = {}) {
3
4
  const order = normalizePiboAgentObservationOrder(input.order);
4
5
  const limit = normalizePiboAgentObservationLimit(input.limit);
@@ -21,6 +22,7 @@ export function preparePiboAgentObservationQuery(input = {}) {
21
22
  const kinds = input.kinds ? new Set(input.kinds) : undefined;
22
23
  const roles = input.roles ? new Set(input.roles) : undefined;
23
24
  const textContains = input.textContains?.toLowerCase();
25
+ const textRegex = preparePiboAgentObservationTextRegex(input.textRegex);
24
26
  const defaultMessageView = eventTypes === undefined && kinds === undefined;
25
27
  const explicitlySelectsTools = input.eventTypes?.some((eventType) => piboAgentObservationKind(eventType) === "tool") === true
26
28
  || input.kinds?.includes("tool") === true
@@ -53,6 +55,7 @@ export function preparePiboAgentObservationQuery(input = {}) {
53
55
  includeTools,
54
56
  toolDetail,
55
57
  ...(scanEventTypes ? { scanEventTypes } : {}),
58
+ ...(textRegex ? { textRegex } : {}),
56
59
  matches(observation) {
57
60
  if (requestIds && (!observation.requestId || !requestIds.has(observation.requestId)))
58
61
  return false;
@@ -89,12 +92,52 @@ export function preparePiboAgentObservationQuery(input = {}) {
89
92
  }
90
93
  export function selectPiboAgentObservationPage(observations, query, options = {}) {
91
94
  const matches = [];
92
- for (const observation of observations) {
93
- if (!query.matches(observation))
94
- continue;
95
- matches.push(observation);
96
- if (matches.length > query.limit)
97
- break;
95
+ if (query.textRegex) {
96
+ let candidates = [];
97
+ let candidateBytes = 0;
98
+ const matchCandidates = () => {
99
+ const regexMatches = matchPiboAgentObservationTextRegex(query.textRegex, candidates.map((observation) => observation.text ?? ""));
100
+ for (let index = 0; index < candidates.length; index += 1) {
101
+ if (!regexMatches[index])
102
+ continue;
103
+ matches.push(candidates[index]);
104
+ if (matches.length > query.limit) {
105
+ candidates = [];
106
+ candidateBytes = 0;
107
+ return true;
108
+ }
109
+ }
110
+ candidates = [];
111
+ candidateBytes = 0;
112
+ return false;
113
+ };
114
+ for (const observation of observations) {
115
+ if (!query.matches(observation))
116
+ continue;
117
+ const textBytes = Buffer.byteLength(observation.text ?? "", "utf8");
118
+ if (candidates.length > 0
119
+ && candidateBytes + textBytes > PIBO_AGENT_TEXT_REGEX_BATCH_TARGET_BYTES
120
+ && matchCandidates())
121
+ break;
122
+ candidates.push(observation);
123
+ candidateBytes += textBytes;
124
+ if (candidates.length >= PIBO_AGENT_TEXT_REGEX_BATCH_MAX_ITEMS
125
+ || candidateBytes >= PIBO_AGENT_TEXT_REGEX_BATCH_TARGET_BYTES) {
126
+ if (matchCandidates())
127
+ break;
128
+ }
129
+ }
130
+ if (matches.length <= query.limit && candidates.length > 0)
131
+ matchCandidates();
132
+ }
133
+ else {
134
+ for (const observation of observations) {
135
+ if (!query.matches(observation))
136
+ continue;
137
+ matches.push(observation);
138
+ if (matches.length > query.limit)
139
+ break;
140
+ }
98
141
  }
99
142
  const pageLimited = matches.length > query.limit;
100
143
  const selected = matches.slice(0, query.limit);
@@ -0,0 +1,124 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { createRequire } from "node:module";
3
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ export const PIBO_AGENT_TEXT_REGEX_BATCH_MAX_ITEMS = 128;
7
+ export const PIBO_AGENT_TEXT_REGEX_BATCH_TARGET_BYTES = 64 * 1024;
8
+ const PIBO_AGENT_TEXT_REGEX_MAX_OUTPUT_BYTES = 64 * 1024;
9
+ const requireFromHere = createRequire(import.meta.url);
10
+ function escapedNulIndex(pattern) {
11
+ for (let index = 0; index < pattern.length; index += 1) {
12
+ if (pattern[index] !== "\\")
13
+ continue;
14
+ let slashEnd = index;
15
+ while (pattern[slashEnd + 1] === "\\")
16
+ slashEnd += 1;
17
+ const slashCount = slashEnd - index + 1;
18
+ if (slashCount % 2 === 1) {
19
+ const remainder = pattern.slice(slashEnd + 1);
20
+ if (/^(?:x00|x\{0+\}|u\{0+\})/i.test(remainder))
21
+ return index;
22
+ }
23
+ index = slashEnd;
24
+ }
25
+ return undefined;
26
+ }
27
+ function assertSupportedNulPattern(pattern) {
28
+ if (pattern.includes("\0") || escapedNulIndex(pattern) !== undefined) {
29
+ throw new Error("Agent observation textRegex is invalid: matching NUL bytes is not supported.");
30
+ }
31
+ }
32
+ function resolvePiboAgentObservationRipgrepPath() {
33
+ const arch = process.env.npm_config_arch || process.arch;
34
+ const binaryName = process.platform === "win32" ? "rg.exe" : "rg";
35
+ const platformPackage = `@vscode/ripgrep-${process.platform}-${arch}`;
36
+ try {
37
+ const wrapperPath = requireFromHere.resolve("@vscode/ripgrep");
38
+ return createRequire(wrapperPath).resolve(`${platformPackage}/bin/${binaryName}`);
39
+ }
40
+ catch {
41
+ throw new Error(`Agent observation textRegex is unavailable: ${platformPackage} is not installed for this platform.`);
42
+ }
43
+ }
44
+ function ripgrepErrorReason(stderr) {
45
+ if (stderr.includes("pattern contains \"\\0\""))
46
+ return "matching NUL bytes is not supported";
47
+ const reasons = [...stderr.matchAll(/^error:\s*(.+)$/gm)];
48
+ return reasons.at(-1)?.[1]?.trim().replace(/\.$/, "");
49
+ }
50
+ function runRipgrepTextRegex(prepared, args, options = {}) {
51
+ return spawnSync(prepared.rgPath, [
52
+ "--no-config",
53
+ "--color=never",
54
+ "--null-data",
55
+ ...args,
56
+ ], {
57
+ ...options,
58
+ encoding: "utf8",
59
+ maxBuffer: PIBO_AGENT_TEXT_REGEX_MAX_OUTPUT_BYTES,
60
+ });
61
+ }
62
+ function throwRipgrepExecutionError(action, error) {
63
+ const errorCode = error && "code" in error ? error.code : undefined;
64
+ if (errorCode === "ENOENT" || errorCode === "EACCES") {
65
+ throw new Error("Agent observation textRegex is unavailable: bundled rg could not be executed.");
66
+ }
67
+ const detail = errorCode === "ENOBUFS"
68
+ ? "bundled rg output exceeded the bounded observation batch limit"
69
+ : "bundled rg could not run";
70
+ throw new Error(`Agent observation textRegex ${action} failed: ${detail}.`);
71
+ }
72
+ export function preparePiboAgentObservationTextRegex(pattern) {
73
+ if (pattern === undefined)
74
+ return undefined;
75
+ assertSupportedNulPattern(pattern);
76
+ const prepared = { pattern, rgPath: resolvePiboAgentObservationRipgrepPath() };
77
+ const result = runRipgrepTextRegex(prepared, ["--quiet", "--", pattern, "-"], { input: "" });
78
+ if (result.error)
79
+ throwRipgrepExecutionError("validation", result.error);
80
+ if (result.status === 0 || result.status === 1)
81
+ return prepared;
82
+ const reason = ripgrepErrorReason(result.stderr);
83
+ if (reason)
84
+ throw new Error(`Agent observation textRegex is invalid: ${reason}.`);
85
+ throwRipgrepExecutionError("validation");
86
+ }
87
+ export function matchPiboAgentObservationTextRegex(prepared, texts) {
88
+ for (const text of texts) {
89
+ if (text.includes("\0")) {
90
+ throw new Error("Agent observation textRegex cannot match observation text containing NUL bytes.");
91
+ }
92
+ }
93
+ if (texts.length === 0)
94
+ return [];
95
+ // One private file per observation preserves record boundaries. --files-with-matches
96
+ // emits each fixed filename at most once, so output cannot grow with submatch count.
97
+ const directory = mkdtempSync(join(tmpdir(), "pibo-agent-observe-regex-"));
98
+ const filenames = texts.map((_, index) => index.toString().padStart(6, "0"));
99
+ try {
100
+ for (let index = 0; index < texts.length; index += 1) {
101
+ writeFileSync(join(directory, filenames[index]), `${texts[index]}\0`, {
102
+ encoding: "utf8",
103
+ flag: "wx",
104
+ mode: 0o600,
105
+ });
106
+ }
107
+ const result = runRipgrepTextRegex(prepared, ["--files-with-matches", "--null", "--", prepared.pattern, ...filenames], { cwd: directory });
108
+ if (result.error)
109
+ throwRipgrepExecutionError("matching", result.error);
110
+ if (result.status === 1)
111
+ return texts.map(() => false);
112
+ if (result.status !== 0) {
113
+ const reason = ripgrepErrorReason(result.stderr);
114
+ if (reason)
115
+ throw new Error(`Agent observation textRegex is invalid: ${reason}.`);
116
+ throwRipgrepExecutionError("matching");
117
+ }
118
+ const matched = new Set(result.stdout.split("\0").filter(Boolean));
119
+ return filenames.map((filename) => matched.has(filename));
120
+ }
121
+ finally {
122
+ rmSync(directory, { recursive: true, force: true });
123
+ }
124
+ }
@@ -187,11 +187,11 @@ export function createAgentToolDefinitions(subagents, controller) {
187
187
  name: "pibo_agents_observe",
188
188
  title: "Pibo Agents Observe",
189
189
  description: [
190
- "Read completed delegated-agent messages with bounded cursor, identity, event, time, text, order, and limit filters.",
190
+ "Read completed delegated-agent messages with bounded cursor, identity, event, time, substring, regex, order, and limit filters.",
191
191
  "Default: the newest 20 completed assistant messages, with streaming deltas and tools hidden.",
192
192
  "Set includeTools=true to add compact tool calls and terminal results, or pass toolCallIds to retrieve only those exact tool observations. Set toolDetail=full only for bounded diagnostic inspection.",
193
193
  ].join("\n"),
194
- promptSnippet: "Observe child-agent progress through completed assistant messages. Defaults: newest 20 messages, no streaming deltas, no duplicate tool progress events, no tools. Set includeTools=true for compact tool call/result summaries, pass up to 50 exact toolCallIds to retrieve only those tool observations, use toolDetail=full for bounded raw tool text, or eventTypes/kinds for explicit progress diagnostics. Compact tool entries expose their toolCallId for follow-up queries. Pass afterSequence from the previous result for cursor polling; cursor pages consume the oldest unseen matches even when order is desc.",
194
+ promptSnippet: "Observe child-agent progress through completed assistant messages. Defaults: newest 20 messages, no streaming deltas, no duplicate tool progress events, no tools. Use textContains for case-insensitive substring matching or textRegex for case-sensitive rg/Rust-regex matching; when both are present, both must match. Set includeTools=true for compact tool call/result summaries, pass up to 50 exact toolCallIds to retrieve only those tool observations, use toolDetail=full for bounded raw tool text, or eventTypes/kinds for explicit progress diagnostics. Compact tool entries expose their toolCallId for follow-up queries. Pass afterSequence from the previous result for cursor polling; cursor pages consume the oldest unseen matches even when order is desc.",
195
195
  executionMode: "parallel",
196
196
  annotations: { readOnly: true },
197
197
  inputSchema: Type.Object({
@@ -206,6 +206,7 @@ export function createAgentToolDefinitions(subagents, controller) {
206
206
  since: Type.Optional(Type.String({ description: "Inclusive ISO-8601 lower timestamp bound" })),
207
207
  until: Type.Optional(Type.String({ description: "Inclusive ISO-8601 upper timestamp bound" })),
208
208
  textContains: Type.Optional(Type.String({ description: "Case-insensitive substring match against normalized observation text" })),
209
+ textRegex: Type.Optional(Type.String({ description: "Case-sensitive rg/Rust-regex match against normalized observation text. Use inline flags such as (?i) to change case behavior. Combines with textContains using AND semantics. NUL text and literal or escaped NUL patterns are rejected; regex use requires the optional rg platform binary." })),
209
210
  afterSequence: Type.Optional(Type.Integer({ description: "Exclusive live observation cursor. Cursor pages consume the oldest unseen matches; desc reverses only the returned page.", minimum: 0 })),
210
211
  order: Type.Optional(piboStringEnum(["asc", "desc"], { default: "desc", description: "Newest first by default when no cursor is supplied" })),
211
212
  limit: Type.Optional(Type.Integer({ description: "Maximum completed messages or activity records to return. Use 50 explicitly when needed.", minimum: 1, maximum: 200, default: 20 })),