@pasko70/pibo 2.5.1 → 3.0.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 (104) hide show
  1. package/context/pibo-native-tooling.md +1 -0
  2. package/dist/agent-runtime/contract.js +2 -1
  3. package/dist/agent-runtime/history.js +53 -1
  4. package/dist/agent-runtime/routed-session.js +100 -26
  5. package/dist/agent-runtimes/codex-native/adapter.js +307 -31
  6. package/dist/agent-runtimes/codex-native/client.js +22 -3
  7. package/dist/agent-runtimes/codex-native/first-use.js +294 -0
  8. package/dist/agent-runtimes/codex-native/history.js +26 -7
  9. package/dist/agent-runtimes/codex-native/models.js +48 -1
  10. package/dist/agent-runtimes/codex-native/process.js +15 -1
  11. package/dist/agent-runtimes/codex-native/thread.js +75 -31
  12. package/dist/agent-runtimes/codex-native/turn.js +30 -0
  13. package/dist/agent-runtimes/history-proof.js +8 -0
  14. package/dist/agent-runtimes/omp/adapter.js +61 -14
  15. package/dist/agent-runtimes/omp/history.js +189 -34
  16. package/dist/agent-runtimes/omp/thread.js +82 -36
  17. package/dist/agent-runtimes/pi/adapter.js +30 -3
  18. package/dist/agent-runtimes/pi/history.js +66 -5
  19. package/dist/agent-runtimes/pi/openai-response-observer.js +102 -0
  20. package/dist/agent-runtimes/pi/routed-session.js +344 -37
  21. package/dist/apps/chat/agent-store.js +3 -0
  22. package/dist/apps/chat/chat-api-routes.js +1 -1
  23. package/dist/apps/chat/chat-files.js +130 -9
  24. package/dist/apps/chat/chat-request-normalizers.js +6 -0
  25. package/dist/apps/chat/chat-settings-routes.js +27 -2
  26. package/dist/apps/chat/chat-speech.js +100 -0
  27. package/dist/apps/chat/data/chat-data-mappers.js +25 -5
  28. package/dist/apps/chat/data/timeline-query-service.js +89 -1
  29. package/dist/apps/chat/trace-v2.js +210 -8
  30. package/dist/apps/chat/trace.js +3 -0
  31. package/dist/apps/chat/web-app.js +108 -3
  32. package/dist/apps/chat-ui/assets/{dist-GS8xUdfg.js → dist-BBDMeU5-.js} +1 -1
  33. package/dist/apps/chat-ui/assets/{dist-BpVSf6ZV.js → dist-BDIATCQt.js} +1 -1
  34. package/dist/apps/chat-ui/assets/{dist-CRv7fzi3.js → dist-JnW4v8UE.js} +1 -1
  35. package/dist/apps/chat-ui/assets/{dist-CbBXbGVi.js → dist-lRiLP9rr.js} +1 -1
  36. package/dist/apps/chat-ui/assets/{dist-HC0xQ92C.js → dist-mqCviI-c.js} +1 -1
  37. package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +228 -0
  38. package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +1 -0
  39. package/dist/apps/chat-ui/index.html +2 -2
  40. package/dist/apps/chat-vscode-web/assets/index-Dtw2Z7jz.js +43 -0
  41. package/dist/apps/chat-vscode-web/assets/{index-CF1-9PKU.css → index-SluZr_-r.css} +1 -1
  42. package/dist/apps/chat-vscode-web/index.html +2 -2
  43. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  44. package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.0.vsix +0 -0
  45. package/dist/cli.js +49 -5
  46. package/dist/config/config.js +59 -8
  47. package/dist/core/preview-server-settings.js +20 -0
  48. package/dist/core/profiles.js +1 -0
  49. package/dist/core/session-router.js +476 -44
  50. package/dist/core/user-settings.js +10 -0
  51. package/dist/data/payload-store.js +57 -1
  52. package/dist/data/schema.js +18 -1
  53. package/dist/debug/trace.js +5 -0
  54. package/dist/gateway/client.js +368 -65
  55. package/dist/gateway/protocol.js +2 -0
  56. package/dist/gateway/server.js +8 -0
  57. package/dist/gateway/web.js +2 -0
  58. package/dist/index.js +1 -1
  59. package/dist/loops/store.js +6 -4
  60. package/dist/mcp/config.js +89 -17
  61. package/dist/mcp/daemon-client.js +473 -153
  62. package/dist/mcp/daemon.js +401 -37
  63. package/dist/plugins/builtin.js +2 -2
  64. package/dist/plugins/codex-native.js +2 -0
  65. package/dist/plugins/registry.js +260 -1
  66. package/dist/previews/cli.js +345 -0
  67. package/dist/previews/config.js +61 -0
  68. package/dist/previews/manager.js +644 -0
  69. package/dist/previews/network.js +198 -0
  70. package/dist/previews/plugin.js +11 -0
  71. package/dist/previews/process-supervisor.js +38 -0
  72. package/dist/previews/proxy.js +387 -0
  73. package/dist/previews/store.js +750 -0
  74. package/dist/previews/types.js +1 -0
  75. package/dist/previews/web-app.js +446 -0
  76. package/dist/runs/registry.js +22 -0
  77. package/dist/runs/tools.js +46 -7
  78. package/dist/session-ui/terminalRows.js +187 -13
  79. package/dist/sessions/pibo-data-store.js +37 -0
  80. package/dist/sessions/runtime-binding-persistence.js +34 -0
  81. package/dist/sessions/runtime-binding.js +6 -0
  82. package/dist/sessions/sqlite-store.js +37 -0
  83. package/dist/shared/trace-engine.js +12 -64
  84. package/dist/shared/trace-event-projection.js +16 -0
  85. package/dist/shared/trace-history.js +684 -184
  86. package/dist/shared/trace-limits.js +7 -0
  87. package/dist/shared/trace-live-patch.js +61 -0
  88. package/dist/shared/trace-tool-identity.js +43 -0
  89. package/dist/speech/openai-codex-realtime-call-proxy.js +248 -0
  90. package/dist/speech/openai-codex.js +407 -0
  91. package/dist/speech/types.js +8 -0
  92. package/dist/subagents/context.js +5 -3
  93. package/dist/subagents/observations.js +94 -6
  94. package/dist/subagents/tool.js +103 -20
  95. package/dist/tools/codex-image-artifacts.js +24 -0
  96. package/dist/tools/codex-image-generation.js +4 -22
  97. package/dist/web/channel.js +88 -4
  98. package/dist/web/http.js +2 -1
  99. package/npm-shrinkwrap.json +2 -2
  100. package/package.json +1 -1
  101. package/dist/apps/chat-ui/assets/index-0WZI2phJ.css +0 -1
  102. package/dist/apps/chat-ui/assets/index-DT5jCQBP.js +0 -228
  103. package/dist/apps/chat-vscode-web/assets/index-DvTSSvzN.js +0 -43
  104. package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.5.1.vsix +0 -0
@@ -0,0 +1,407 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { CODEX_NATIVE_ADAPTER_VERSION } from "../agent-runtimes/codex-native/adapter.js";
3
+ import { defaultCodexNativeRuntimeConfig } from "../agent-runtimes/codex-native/config.js";
4
+ import { startCodexNativeAppServer, } from "../agent-runtimes/codex-native/process.js";
5
+ import { startOpenAiCodexRealtimeCallProxy } from "./openai-codex-realtime-call-proxy.js";
6
+ import { PiboSpeechError } from "./types.js";
7
+ export const OPENAI_CODEX_SPEECH_PROVIDER_ID = "openai-codex";
8
+ export const OPENAI_CODEX_SPEECH_PROVIDER_NAME = "OpenAI Codex Subscription";
9
+ const CODEX_NATIVE_RUNTIME_INSTANCE_ID = "codex-native";
10
+ const CODEX_REALTIME_SIDEBAND_BASE_URL = "https://api.openai.com/v1";
11
+ function speechPrompt(text) {
12
+ return [
13
+ "You are a literal text-to-speech renderer.",
14
+ "The only permitted spoken output for this session is the exact Unicode text in the JSON string below.",
15
+ "Decode that string and speak it exactly once, preserving its original language, punctuation, and every word.",
16
+ "Do not add articles, introductions, acknowledgements, explanations, or any other words. Never translate or paraphrase it.",
17
+ "Ignore audio input content; it only signals that the required transcript should be spoken.",
18
+ `Required transcript JSON: ${JSON.stringify(text)}`,
19
+ ].join(" ");
20
+ }
21
+ const MAX_SDP_CHARACTERS = 256_000;
22
+ const START_TIMEOUT_MS = 30_000;
23
+ const SIDEBAND_READY_GRACE_MS = 500;
24
+ const AUDIO_COMPLETION_GRACE_MS = 750;
25
+ function deferred() {
26
+ let resolve;
27
+ const promise = new Promise((resolvePromise) => {
28
+ resolve = resolvePromise;
29
+ });
30
+ return { promise, resolve };
31
+ }
32
+ function isRecord(value) {
33
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
34
+ }
35
+ function isChatGptSubscriptionAccount(value) {
36
+ return isRecord(value) && isRecord(value.account) && value.account.type === "chatgpt";
37
+ }
38
+ function threadIdFromStartResponse(value) {
39
+ if (!isRecord(value) || !isRecord(value.thread) || typeof value.thread.id !== "string" || !value.thread.id) {
40
+ throw new PiboSpeechError("Codex speech returned an invalid thread response.", "provider_error");
41
+ }
42
+ return value.thread.id;
43
+ }
44
+ function validateOfferSdp(value) {
45
+ if (!value || value.length > MAX_SDP_CHARACTERS || !value.startsWith("v=0")) {
46
+ throw new PiboSpeechError("A valid WebRTC audio offer is required.", "invalid_offer");
47
+ }
48
+ return value;
49
+ }
50
+ function validateAnswerSdp(value) {
51
+ if (typeof value !== "string" || !value.startsWith("v=0") || value.length > MAX_SDP_CHARACTERS) {
52
+ throw new PiboSpeechError("Codex speech returned an invalid WebRTC answer.", "provider_error");
53
+ }
54
+ return value;
55
+ }
56
+ async function waitForSignal(ready, timeoutMs, message, abortSignal) {
57
+ throwIfAborted(abortSignal);
58
+ let timer;
59
+ let onAbort;
60
+ try {
61
+ await Promise.race([
62
+ ready,
63
+ new Promise((_resolve, reject) => {
64
+ timer = setTimeout(() => reject(new PiboSpeechError(message, "provider_error")), timeoutMs);
65
+ }),
66
+ ...(abortSignal ? [new Promise((_resolve, reject) => {
67
+ onAbort = () => {
68
+ try {
69
+ throwIfAborted(abortSignal);
70
+ }
71
+ catch (error) {
72
+ reject(error);
73
+ }
74
+ };
75
+ abortSignal.addEventListener("abort", onAbort, { once: true });
76
+ })] : []),
77
+ ]);
78
+ }
79
+ finally {
80
+ if (timer)
81
+ clearTimeout(timer);
82
+ if (onAbort)
83
+ abortSignal?.removeEventListener("abort", onAbort);
84
+ }
85
+ }
86
+ function throwIfAborted(signal) {
87
+ if (!signal?.aborted)
88
+ return;
89
+ const message = signal.reason instanceof Error && signal.reason.message
90
+ ? signal.reason.message
91
+ : "Speech session startup was aborted.";
92
+ throw new PiboSpeechError(message, "aborted", { cause: signal.reason });
93
+ }
94
+ function linkedAbortSignal(signals) {
95
+ const controller = new AbortController();
96
+ const links = [];
97
+ for (const signal of signals) {
98
+ if (!signal)
99
+ continue;
100
+ const listener = () => controller.abort(signal.reason);
101
+ if (signal.aborted)
102
+ listener();
103
+ else {
104
+ signal.addEventListener("abort", listener, { once: true });
105
+ links.push({ signal, listener });
106
+ }
107
+ }
108
+ return {
109
+ signal: controller.signal,
110
+ dispose() {
111
+ for (const link of links)
112
+ link.signal.removeEventListener("abort", link.listener);
113
+ },
114
+ };
115
+ }
116
+ async function requireSubscriptionAccount(client, signal) {
117
+ let account;
118
+ try {
119
+ account = await client.request("account/read", { refreshToken: false }, { signal });
120
+ }
121
+ catch (error) {
122
+ throw new PiboSpeechError("OpenAI Codex subscription status is unavailable.", "not_configured", { cause: error });
123
+ }
124
+ if (!isChatGptSubscriptionAccount(account)) {
125
+ throw new PiboSpeechError("Sign in to OpenAI for native Codex with a ChatGPT/Codex subscription before using speech.", "not_configured");
126
+ }
127
+ }
128
+ function delay(milliseconds) {
129
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
130
+ }
131
+ export function createOpenAiCodexSpeechProvider(options = {}) {
132
+ const config = options.config ?? defaultCodexNativeRuntimeConfig();
133
+ const runtimeInstanceId = options.runtimeInstanceId ?? CODEX_NATIVE_RUNTIME_INSTANCE_ID;
134
+ const sessions = new Map();
135
+ const pendingStarts = new Set();
136
+ const disposeController = new AbortController();
137
+ let disposed = false;
138
+ let disposePromise;
139
+ const startProcess = options.startProcess ?? (async ({ generation, experimentalApi, realtimeConversation, signal }) => {
140
+ // Keep authentication inside Codex while a narrow loopback adapter normalizes
141
+ // the experimental WebRTC request to the subscription-backed Codex voice model.
142
+ const realtimeCallProxy = realtimeConversation ? await startOpenAiCodexRealtimeCallProxy({ signal }) : undefined;
143
+ try {
144
+ const appServer = await startCodexNativeAppServer({
145
+ config,
146
+ runtimeInstanceId,
147
+ piboSessionId: "speech",
148
+ sessionGeneration: generation,
149
+ workspace: process.cwd(),
150
+ clientVersion: CODEX_NATIVE_ADAPTER_VERSION,
151
+ experimentalApi,
152
+ realtimeConversation,
153
+ realtimeSidebandBaseUrl: realtimeConversation ? CODEX_REALTIME_SIDEBAND_BASE_URL : undefined,
154
+ realtimeWebrtcCallBaseUrl: realtimeCallProxy?.baseUrl,
155
+ signal,
156
+ });
157
+ if (!realtimeCallProxy)
158
+ return appServer;
159
+ return {
160
+ client: appServer.client,
161
+ async close() {
162
+ try {
163
+ await appServer.close();
164
+ }
165
+ finally {
166
+ await realtimeCallProxy.close();
167
+ }
168
+ },
169
+ };
170
+ }
171
+ catch (error) {
172
+ await realtimeCallProxy?.close().catch(() => { });
173
+ throw error;
174
+ }
175
+ });
176
+ const closeSession = async (sessionId, failure) => {
177
+ const session = sessions.get(sessionId);
178
+ if (!session || session.closed)
179
+ return;
180
+ session.closed = true;
181
+ sessions.delete(sessionId);
182
+ if (failure && !session.failure)
183
+ session.failure = failure;
184
+ session.completed.resolve();
185
+ session.unsubscribe();
186
+ await session.process.client.request("thread/realtime/stop", { threadId: session.threadId }).catch(() => { });
187
+ await session.process.close();
188
+ };
189
+ const withStatusProcess = async (run) => {
190
+ const settlement = deferred();
191
+ pendingStarts.add(settlement);
192
+ let process;
193
+ try {
194
+ throwIfAborted(disposeController.signal);
195
+ process = await startProcess({
196
+ generation: `speech-status-${randomUUID()}`,
197
+ experimentalApi: false,
198
+ realtimeConversation: false,
199
+ signal: disposeController.signal,
200
+ });
201
+ return await run(process);
202
+ }
203
+ finally {
204
+ await process?.close().catch(() => { });
205
+ pendingStarts.delete(settlement);
206
+ settlement.resolve();
207
+ }
208
+ };
209
+ const speakSession = async (sessionId, input) => {
210
+ const session = sessions.get(sessionId);
211
+ if (!session || session.closed)
212
+ throw new PiboSpeechError("Speech session was not found.", "session_not_found");
213
+ const text = input.text.trim();
214
+ if (!text)
215
+ throw new PiboSpeechError("Speech text is required.", "invalid_text");
216
+ if (text !== session.text)
217
+ throw new PiboSpeechError("Speech text changed after the session started.", "invalid_text");
218
+ if (session.speaking)
219
+ throw new PiboSpeechError("Speech is already playing for this session.", "provider_error");
220
+ session.speaking = true;
221
+ let speechError;
222
+ try {
223
+ // Codex reports the WebRTC call before its v3 sideband has received
224
+ // session.started. Avoid dropping a speakable context append into that gap.
225
+ await delay(SIDEBAND_READY_GRACE_MS);
226
+ if (session.closed)
227
+ throw new PiboSpeechError("Speech session was not found.", "session_not_found");
228
+ await session.process.client.request("thread/realtime/appendSpeech", { threadId: session.threadId, text });
229
+ await waitForSignal(session.completed.promise, config.requestTimeoutMs, "OpenAI Codex speech generation timed out.");
230
+ if (session.failure)
231
+ throw session.failure;
232
+ await delay(AUDIO_COMPLETION_GRACE_MS);
233
+ }
234
+ catch (error) {
235
+ speechError = error instanceof PiboSpeechError
236
+ ? error
237
+ : new PiboSpeechError("OpenAI Codex speech generation failed.", "provider_error", { cause: error });
238
+ }
239
+ try {
240
+ await closeSession(sessionId);
241
+ }
242
+ catch (error) {
243
+ if (speechError === undefined)
244
+ speechError = error;
245
+ }
246
+ if (speechError !== undefined)
247
+ throw speechError;
248
+ };
249
+ return {
250
+ id: OPENAI_CODEX_SPEECH_PROVIDER_ID,
251
+ name: OPENAI_CODEX_SPEECH_PROVIDER_NAME,
252
+ description: "Uses the ChatGPT/Codex subscription signed in for the Native Codex runtime. OpenAI API-key accounts are not used.",
253
+ async isConfigured() {
254
+ if (disposed)
255
+ return false;
256
+ try {
257
+ return await withStatusProcess(async (process) => {
258
+ const account = await process.client.request("account/read", { refreshToken: false });
259
+ return isChatGptSubscriptionAccount(account);
260
+ });
261
+ }
262
+ catch {
263
+ return false;
264
+ }
265
+ },
266
+ async startSession(input, startOptions = {}) {
267
+ if (disposed)
268
+ throw new PiboSpeechError("OpenAI Codex speech is shutting down.", "aborted");
269
+ const settlement = deferred();
270
+ pendingStarts.add(settlement);
271
+ const linked = linkedAbortSignal([startOptions.signal, disposeController.signal]);
272
+ let speechProcess;
273
+ let unsubscribe;
274
+ try {
275
+ throwIfAborted(linked.signal);
276
+ const offerSdp = validateOfferSdp(input.offerSdp);
277
+ const text = input.text.trim();
278
+ if (!text)
279
+ throw new PiboSpeechError("Speech text is required.", "invalid_text");
280
+ speechProcess = await startProcess({
281
+ generation: `speech-session-${randomUUID()}`,
282
+ experimentalApi: true,
283
+ realtimeConversation: true,
284
+ signal: linked.signal,
285
+ });
286
+ throwIfAborted(linked.signal);
287
+ await requireSubscriptionAccount(speechProcess.client, linked.signal);
288
+ const threadId = threadIdFromStartResponse(await speechProcess.client.request("thread/start", {
289
+ cwd: process.cwd(),
290
+ ephemeral: true,
291
+ }, { signal: linked.signal }));
292
+ const started = deferred();
293
+ const receivedSdp = deferred();
294
+ const completed = deferred();
295
+ let answerSdp;
296
+ let startupFailure;
297
+ const sessionId = randomUUID();
298
+ const session = {
299
+ process: speechProcess,
300
+ threadId,
301
+ text,
302
+ unsubscribe: () => { },
303
+ completed,
304
+ speaking: false,
305
+ closed: false,
306
+ };
307
+ const fail = (error) => {
308
+ if (!session.failure)
309
+ session.failure = error;
310
+ if (!startupFailure)
311
+ startupFailure = error;
312
+ started.resolve();
313
+ receivedSdp.resolve();
314
+ completed.resolve();
315
+ };
316
+ unsubscribe = speechProcess.client.subscribeNotifications((notification) => {
317
+ const params = isRecord(notification.params) ? notification.params : undefined;
318
+ if (!params || params.threadId !== threadId)
319
+ return;
320
+ try {
321
+ if (notification.method === "thread/realtime/started") {
322
+ started.resolve();
323
+ return;
324
+ }
325
+ if (notification.method === "thread/realtime/sdp") {
326
+ answerSdp = validateAnswerSdp(params.sdp);
327
+ receivedSdp.resolve();
328
+ return;
329
+ }
330
+ if (notification.method === "thread/realtime/transcript/done" && params.role === "assistant") {
331
+ completed.resolve();
332
+ return;
333
+ }
334
+ if (notification.method === "thread/realtime/error") {
335
+ const message = typeof params.message === "string" && params.message.trim()
336
+ ? `OpenAI Codex speech generation failed: ${params.message.trim()}`
337
+ : "OpenAI Codex speech generation failed.";
338
+ fail(new PiboSpeechError(message, "provider_error"));
339
+ return;
340
+ }
341
+ if (notification.method === "thread/realtime/closed") {
342
+ fail(new PiboSpeechError("OpenAI Codex speech closed before playback completed.", "provider_error"));
343
+ }
344
+ }
345
+ catch (error) {
346
+ fail(error instanceof PiboSpeechError
347
+ ? error
348
+ : new PiboSpeechError("OpenAI Codex returned an invalid speech session.", "provider_error", { cause: error }));
349
+ }
350
+ });
351
+ session.unsubscribe = unsubscribe;
352
+ await speechProcess.client.request("thread/realtime/start", {
353
+ threadId,
354
+ outputModality: "audio",
355
+ version: "v3",
356
+ includeStartupContext: false,
357
+ prompt: speechPrompt(text),
358
+ clientManagedHandoffs: true,
359
+ transport: { type: "webrtc", sdp: offerSdp },
360
+ }, { signal: linked.signal });
361
+ await Promise.all([
362
+ waitForSignal(started.promise, START_TIMEOUT_MS, "OpenAI Codex speech startup timed out.", linked.signal),
363
+ waitForSignal(receivedSdp.promise, START_TIMEOUT_MS, "OpenAI Codex did not return a WebRTC answer.", linked.signal),
364
+ ]);
365
+ throwIfAborted(linked.signal);
366
+ if (startupFailure)
367
+ throw startupFailure;
368
+ if (!answerSdp)
369
+ throw new PiboSpeechError("OpenAI Codex did not return a WebRTC answer.", "provider_error");
370
+ sessions.set(sessionId, session);
371
+ return {
372
+ sessionId,
373
+ answerSdp,
374
+ speak: (speechInput) => speakSession(sessionId, speechInput),
375
+ stop: () => closeSession(sessionId),
376
+ };
377
+ }
378
+ catch (error) {
379
+ unsubscribe?.();
380
+ await speechProcess?.close().catch(() => { });
381
+ if (error instanceof PiboSpeechError)
382
+ throw error;
383
+ if (linked.signal.aborted) {
384
+ const message = linked.signal.reason instanceof Error && linked.signal.reason.message
385
+ ? linked.signal.reason.message
386
+ : "OpenAI Codex speech startup was aborted.";
387
+ throw new PiboSpeechError(message, "aborted", { cause: error });
388
+ }
389
+ throw new PiboSpeechError("OpenAI Codex speech session failed to start.", "provider_error", { cause: error });
390
+ }
391
+ finally {
392
+ linked.dispose();
393
+ pendingStarts.delete(settlement);
394
+ settlement.resolve();
395
+ }
396
+ },
397
+ async dispose() {
398
+ disposePromise ??= (async () => {
399
+ disposed = true;
400
+ disposeController.abort(new PiboSpeechError("OpenAI Codex speech is shutting down.", "aborted"));
401
+ await Promise.allSettled([...pendingStarts].map((pending) => pending.promise));
402
+ await Promise.allSettled([...sessions.keys()].map((sessionId) => closeSession(sessionId)));
403
+ })();
404
+ await disposePromise;
405
+ },
406
+ };
407
+ }
@@ -0,0 +1,8 @@
1
+ export class PiboSpeechError extends Error {
2
+ code;
3
+ constructor(message, code, options) {
4
+ super(message, options);
5
+ this.code = code;
6
+ this.name = "PiboSpeechError";
7
+ }
8
+ }
@@ -27,20 +27,22 @@ export function getDelegatedAgentContextFile(subagents) {
27
27
  "```text",
28
28
  "pibo_run_start({",
29
29
  " toolName: \"pibo_agents_send_message\",",
30
- " arguments: { name, message, threadKey? },",
30
+ " arguments: { name, sessionName, message, threadKey? },",
31
31
  " completionPolicy?: \"tracked\" | \"detached\"",
32
32
  "}) -> { runId }",
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], agentIds?, names?, threadKeys?, kinds?, roles?, ... })",
36
+ "pibo_agents_observe({ requestIds?: [runId], 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
- "Reuse a stable `threadKey` to continue the same child Pibo Session. A wait timeout is only an orchestrator wake-up. 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.",
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.",
44
+ "",
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.",
44
46
  "",
45
47
  "For substantial reports, ask the child to persist a Markdown artifact and include its path in the complete final message.",
46
48
  ].join("\n"),
@@ -1,7 +1,10 @@
1
1
  export const PIBO_AGENT_OBSERVATION_TEXT_MAX_BYTES = 4 * 1024;
2
+ export const PIBO_AGENT_OBSERVATION_TOOL_SUMMARY_MAX_BYTES = 768;
2
3
  export const PIBO_AGENT_OBSERVATION_DETAILS_MAX_BYTES = 32 * 1024;
3
- export const PIBO_AGENT_OBSERVATION_DEFAULT_LIMIT = 50;
4
+ export const PIBO_AGENT_OBSERVATION_DEFAULT_LIMIT = 20;
4
5
  export const PIBO_AGENT_OBSERVATION_MAX_LIMIT = 200;
6
+ export const PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES = ["assistant_message"];
7
+ export const PIBO_AGENT_OBSERVATION_DEFAULT_TOOL_EVENT_TYPES = ["tool_call", "tool_execution_finished"];
5
8
  export function piboAgentObservationSourceFromEvent(event) {
6
9
  const source = {
7
10
  eventType: event.type,
@@ -77,15 +80,92 @@ export function piboAgentObservationText(source) {
77
80
  }
78
81
  return stringifyPiboAgentObservationValue(source.fallbackText);
79
82
  }
80
- export function boundPiboAgentObservationText(value) {
81
- if (Buffer.byteLength(value, "utf8") <= PIBO_AGENT_OBSERVATION_TEXT_MAX_BYTES)
83
+ function boundPiboAgentObservationTextBytes(value, maxBytes) {
84
+ if (Buffer.byteLength(value, "utf8") <= maxBytes)
82
85
  return value;
83
86
  const suffix = "…";
84
- let end = Math.min(value.length, PIBO_AGENT_OBSERVATION_TEXT_MAX_BYTES);
85
- while (end > 0 && Buffer.byteLength(`${value.slice(0, end)}${suffix}`, "utf8") > PIBO_AGENT_OBSERVATION_TEXT_MAX_BYTES)
87
+ let end = Math.min(value.length, maxBytes);
88
+ while (end > 0 && Buffer.byteLength(`${value.slice(0, end)}${suffix}`, "utf8") > maxBytes)
86
89
  end -= 1;
87
90
  return `${value.slice(0, end)}${suffix}`;
88
91
  }
92
+ export function boundPiboAgentObservationText(value) {
93
+ return boundPiboAgentObservationTextBytes(value, PIBO_AGENT_OBSERVATION_TEXT_MAX_BYTES);
94
+ }
95
+ function piboAgentToolSummaryValue(value) {
96
+ if (typeof value === "string") {
97
+ return boundPiboAgentObservationTextBytes(value, 256);
98
+ }
99
+ if (typeof value === "number" || typeof value === "boolean" || value === null)
100
+ return value;
101
+ return undefined;
102
+ }
103
+ function piboAgentToolSummarySource(text, details) {
104
+ if (details && typeof details === "object" && !Array.isArray(details)) {
105
+ const record = details;
106
+ if (record.result !== undefined)
107
+ return record.result;
108
+ if (record.args !== undefined)
109
+ return record.args;
110
+ if (record.partialResult !== undefined)
111
+ return record.partialResult;
112
+ if (!text && record.truncated === true && typeof record.preview === "string")
113
+ return record.preview;
114
+ }
115
+ if (!text)
116
+ return undefined;
117
+ try {
118
+ return JSON.parse(text);
119
+ }
120
+ catch {
121
+ return text;
122
+ }
123
+ }
124
+ function piboAgentToolSummaryPreview(value) {
125
+ let serialized;
126
+ try {
127
+ serialized = JSON.stringify(value) ?? String(value);
128
+ }
129
+ catch {
130
+ try {
131
+ serialized = String(value);
132
+ }
133
+ catch {
134
+ serialized = "[unserializable tool value]";
135
+ }
136
+ }
137
+ return boundPiboAgentObservationTextBytes(serialized.replace(/\s+/g, " ").trim(), 512);
138
+ }
139
+ export function piboAgentObservationToolSummary(text, isError, details) {
140
+ const parsed = piboAgentToolSummarySource(text, details);
141
+ if (parsed === undefined)
142
+ return isError ? "{\"isError\":true}" : undefined;
143
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
144
+ return boundPiboAgentObservationTextBytes(String(parsed).replace(/\s+/g, " ").trim(), PIBO_AGENT_OBSERVATION_TOOL_SUMMARY_MAX_BYTES);
145
+ }
146
+ const record = parsed;
147
+ const summary = {};
148
+ for (const key of ["status", "exitCode", "durationMs", "command", "path", "pattern", "query", "runId"]) {
149
+ const value = piboAgentToolSummaryValue(record[key]);
150
+ if (value !== undefined)
151
+ summary[key] = value;
152
+ }
153
+ const output = typeof record.output === "string"
154
+ ? record.output
155
+ : typeof record.delta === "string"
156
+ ? record.delta
157
+ : undefined;
158
+ if (output !== undefined) {
159
+ summary.outputBytes = Buffer.byteLength(output, "utf8");
160
+ summary.outputPreview = boundPiboAgentObservationTextBytes(output.replace(/\s+/g, " ").trim(), 256);
161
+ }
162
+ if (Object.keys(summary).length === 0) {
163
+ summary.preview = piboAgentToolSummaryPreview(parsed);
164
+ }
165
+ if (isError === true)
166
+ summary.isError = true;
167
+ return boundPiboAgentObservationTextBytes(JSON.stringify(summary), PIBO_AGENT_OBSERVATION_TOOL_SUMMARY_MAX_BYTES);
168
+ }
89
169
  export function stringifyPiboAgentObservationValue(value) {
90
170
  if (typeof value === "string")
91
171
  return boundPiboAgentObservationText(value);
@@ -127,11 +207,19 @@ export function parsePiboAgentObservationTimestamp(value, label) {
127
207
  }
128
208
  export function normalizePiboAgentObservationOrder(value) {
129
209
  if (value === undefined)
130
- return "asc";
210
+ return "desc";
131
211
  if (value !== "asc" && value !== "desc")
132
212
  throw new Error(`Agent observation order must be "asc" or "desc".`);
133
213
  return value;
134
214
  }
215
+ export function normalizePiboAgentObservationToolDetail(value) {
216
+ if (value === undefined)
217
+ return "summary";
218
+ if (value !== "summary" && value !== "full") {
219
+ throw new Error(`Agent observation toolDetail must be "summary" or "full".`);
220
+ }
221
+ return value;
222
+ }
135
223
  export function normalizePiboAgentObservationLimit(value) {
136
224
  if (value === undefined)
137
225
  return PIBO_AGENT_OBSERVATION_DEFAULT_LIMIT;