@pasko70/pibo 1.16.5 → 2.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 (146) hide show
  1. package/dist/agent-runtime/auth.js +28 -0
  2. package/dist/agent-runtime/capabilities.js +221 -0
  3. package/dist/agent-runtime/context-build.js +313 -0
  4. package/dist/agent-runtime/contract.js +45 -0
  5. package/dist/agent-runtime/errors.js +57 -0
  6. package/dist/agent-runtime/events.js +1 -0
  7. package/dist/agent-runtime/history.js +1 -0
  8. package/dist/agent-runtime/profile-validation.js +109 -0
  9. package/dist/agent-runtime/registry.js +608 -0
  10. package/dist/agent-runtime/resource-files.js +87 -0
  11. package/dist/agent-runtime/resource-service.js +635 -0
  12. package/dist/agent-runtime/resources.js +1 -0
  13. package/dist/agent-runtime/routed-session.js +931 -0
  14. package/dist/agent-runtime/testing/contract.js +51 -0
  15. package/dist/agent-runtime/testing/fake-adapter.js +192 -0
  16. package/dist/agent-runtime/types.js +3 -0
  17. package/dist/agent-runtimes/codex-native/adapter.js +905 -0
  18. package/dist/agent-runtimes/codex-native/auth.js +476 -0
  19. package/dist/agent-runtimes/codex-native/client.js +734 -0
  20. package/dist/agent-runtimes/codex-native/config.js +152 -0
  21. package/dist/agent-runtimes/codex-native/history.js +283 -0
  22. package/dist/agent-runtimes/codex-native/mcp-stdio-launcher.js +89 -0
  23. package/dist/agent-runtimes/codex-native/models.js +581 -0
  24. package/dist/agent-runtimes/codex-native/process.js +459 -0
  25. package/dist/agent-runtimes/codex-native/protocol-types.js +1 -0
  26. package/dist/agent-runtimes/codex-native/protocol-version.js +7 -0
  27. package/dist/agent-runtimes/codex-native/redaction.js +31 -0
  28. package/dist/agent-runtimes/codex-native/requests.js +378 -0
  29. package/dist/agent-runtimes/codex-native/resource-delivery.js +445 -0
  30. package/dist/agent-runtimes/codex-native/thread.js +388 -0
  31. package/dist/agent-runtimes/codex-native/turn.js +849 -0
  32. package/dist/agent-runtimes/pi/adapter.js +653 -0
  33. package/dist/agent-runtimes/pi/auth.js +455 -0
  34. package/dist/agent-runtimes/pi/history.js +640 -0
  35. package/dist/agent-runtimes/pi/model-catalog.js +87 -0
  36. package/dist/agent-runtimes/pi/routed-session.js +1464 -0
  37. package/dist/agent-runtimes/pi/runtime.js +534 -0
  38. package/dist/agent-runtimes/pi/tool-compiler.js +69 -0
  39. package/dist/apps/chat/agent-profiles.js +40 -23
  40. package/dist/apps/chat/agent-store.js +111 -64
  41. package/dist/apps/chat/chat-api-routes.js +1 -1
  42. package/dist/apps/chat/chat-request-normalizers.js +47 -0
  43. package/dist/apps/chat/data/chat-data-mappers.js +25 -9
  44. package/dist/apps/chat/data/history-query-service.js +108 -0
  45. package/dist/apps/chat/data/session-query-service.js +24 -3
  46. package/dist/apps/chat/data/timeline-query-service.js +3 -3
  47. package/dist/apps/chat/model-catalog.js +1 -55
  48. package/dist/apps/chat/provider-auth-actions.js +171 -23
  49. package/dist/apps/chat/stream.js +12 -0
  50. package/dist/apps/chat/trace-v2.js +1 -0
  51. package/dist/apps/chat/trace.js +126 -415
  52. package/dist/apps/chat/web-app.js +582 -157
  53. package/dist/apps/chat-ui/assets/{dist-QXwINWYY.js → dist-9v2ZXT6V.js} +1 -1
  54. package/dist/apps/chat-ui/assets/{dist-CvZNkkOi.js → dist-B1LVAytl.js} +1 -1
  55. package/dist/apps/chat-ui/assets/{dist-_SP-MjLq.js → dist-C6LPDFXU.js} +1 -1
  56. package/dist/apps/chat-ui/assets/{dist-Bad4Yz4P.js → dist-DVtXxfHc.js} +1 -1
  57. package/dist/apps/chat-ui/assets/{dist-xy-zomwl.js → dist-DW4Qrfbd.js} +1 -1
  58. package/dist/apps/chat-ui/assets/{dist-DI4i80O2.js → dist-DmbsHdMC.js} +1 -1
  59. package/dist/apps/chat-ui/assets/{dist-D17yAUqp.js → dist-DyvwuSFO.js} +1 -1
  60. package/dist/apps/chat-ui/assets/{dist-Bc3Vbgjh.js → dist-I26VB0QO.js} +1 -1
  61. package/dist/apps/chat-ui/assets/{dist-BzqH7w58.js → dist-RtHbJRPV.js} +1 -1
  62. package/dist/apps/chat-ui/assets/{dist-CMDJ-WZm.js → dist-sZgJkWZb.js} +1 -1
  63. package/dist/apps/chat-ui/assets/index-BPbNvcZe.css +1 -0
  64. package/dist/apps/chat-ui/assets/index-CIOf2uL2.js +237 -0
  65. package/dist/apps/chat-ui/index.html +2 -2
  66. package/dist/apps/chat-vscode-web/assets/index-1ivRyjFs.js +41 -0
  67. package/dist/apps/chat-vscode-web/index.html +1 -1
  68. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  69. package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.0.0.vsix +0 -0
  70. package/dist/auth/login-actions.js +5 -251
  71. package/dist/cli-session/localSessionSource.js +8 -1
  72. package/dist/core/context-build.js +124 -20
  73. package/dist/core/default-profile.js +8 -0
  74. package/dist/core/profiles.js +30 -3
  75. package/dist/core/routed-session.js +5 -1446
  76. package/dist/core/runtime.js +5 -561
  77. package/dist/core/session-router.js +637 -83
  78. package/dist/data/cli.js +20 -3
  79. package/dist/data/schema.js +104 -1
  80. package/dist/data/session-store.js +24 -1
  81. package/dist/debug/events.js +25 -10
  82. package/dist/debug/failures.js +23 -3
  83. package/dist/debug/index.js +24 -6
  84. package/dist/debug/messages.js +87 -7
  85. package/dist/debug/persisted-payloads.js +31 -0
  86. package/dist/debug/pty.js +7 -6
  87. package/dist/debug/runtime-binding.js +29 -0
  88. package/dist/debug/session.js +137 -0
  89. package/dist/debug/summary.js +12 -0
  90. package/dist/debug/telemetry.js +28 -0
  91. package/dist/debug/tools.js +12 -2
  92. package/dist/debug/trace.js +154 -76
  93. package/dist/gateway/server.js +87 -2
  94. package/dist/gateway/tool.js +5 -5
  95. package/dist/index.js +21 -2
  96. package/dist/loops/tools.js +13 -12
  97. package/dist/mcp/agent-context.js +13 -4
  98. package/dist/mcp/client.js +10 -6
  99. package/dist/mcp/config.js +5 -2
  100. package/dist/mcp/runtime-session.js +166 -0
  101. package/dist/plugins/builtin.js +191 -53
  102. package/dist/plugins/codex-native.js +29 -0
  103. package/dist/plugins/registry.js +66 -2
  104. package/dist/runs/tools.js +26 -25
  105. package/dist/session-ui/terminalRows.js +2 -2
  106. package/dist/sessions/pibo-data-store.js +125 -16
  107. package/dist/sessions/runtime-binding.js +107 -0
  108. package/dist/sessions/sqlite-store.js +239 -18
  109. package/dist/sessions/store.js +86 -7
  110. package/dist/shared/trace-engine.js +18 -9
  111. package/dist/shared/trace-event-projection.js +29 -11
  112. package/dist/shared/{trace-transcript.js → trace-history.js} +193 -195
  113. package/dist/shared/trace-order.js +9 -4
  114. package/dist/shared/trace-page-merge.js +1 -1
  115. package/dist/signals/projector.js +20 -2
  116. package/dist/signals/registry.js +11 -0
  117. package/dist/subagents/tool.js +8 -6
  118. package/dist/tools/codex-browser.js +20 -19
  119. package/dist/tools/codex-compat.js +10 -9
  120. package/dist/tools/codex-image-generation.js +8 -6
  121. package/dist/tools/contract.js +45 -0
  122. package/dist/tools/credential-registry.js +182 -0
  123. package/dist/tools/mcp-bridge.js +511 -0
  124. package/dist/tools/payload-writer.js +17 -0
  125. package/dist/tools/runtime/tool.js +10 -9
  126. package/dist/tools/schema.js +9 -0
  127. package/dist/tools/session-service.js +193 -0
  128. package/dist/tools/session-tool-set.js +85 -0
  129. package/dist/web-annotations/tools.js +17 -16
  130. package/docs/README.md +11 -0
  131. package/package.json +3 -2
  132. package/skills/builtin/pibo-agent-runtime-adapter/SKILL.md +183 -0
  133. package/skills/builtin/pibo-agent-runtime-adapter/evals/README.md +10 -0
  134. package/skills/builtin/pibo-agent-runtime-adapter/evals/evals.json +45 -0
  135. package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/orion-full-harness.md +60 -0
  136. package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/relay-partial-harness.md +52 -0
  137. package/skills/builtin/pibo-agent-runtime-adapter/references/capabilities-and-designer.md +194 -0
  138. package/skills/builtin/pibo-agent-runtime-adapter/references/history-debug-and-security.md +213 -0
  139. package/skills/builtin/pibo-agent-runtime-adapter/references/interfaces-and-registration.md +214 -0
  140. package/skills/builtin/pibo-agent-runtime-adapter/references/lifecycle-bindings-and-events.md +193 -0
  141. package/skills/builtin/pibo-agent-runtime-adapter/references/portable-delivery-and-native-behavior.md +226 -0
  142. package/skills/builtin/pibo-agent-runtime-adapter/references/testing-migration-and-validation.md +256 -0
  143. package/dist/apps/chat-ui/assets/index-C45B0rw7.js +0 -237
  144. package/dist/apps/chat-ui/assets/index-E0RmuCkY.css +0 -1
  145. package/dist/apps/chat-vscode-web/assets/index-D-tSsXLG.js +0 -41
  146. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.16.5.vsix +0 -0
@@ -1,5 +1,6 @@
1
1
  export const TRACE_SOURCE_RANK = {
2
2
  transcript: 0,
3
+ "product-history": 1,
3
4
  "event-log": 1,
4
5
  live: 2,
5
6
  };
@@ -17,15 +18,19 @@ export const TRACE_PHASE_RANK = {
17
18
  "assistant.message": 8,
18
19
  error: 9,
19
20
  };
20
- export function transcriptTraceOrder(transcriptIndex, contentPartIndex, type) {
21
+ export function historyTraceOrder(historyIndex, contentPartIndex, type, source) {
21
22
  return {
22
- sourceRank: TRACE_SOURCE_RANK.transcript,
23
- turnSeq: transcriptIndex,
24
- transcriptIndex,
23
+ sourceRank: TRACE_SOURCE_RANK[source],
24
+ turnSeq: historyIndex,
25
+ transcriptIndex: historyIndex,
25
26
  contentPartIndex,
26
27
  phaseRank: TRACE_PHASE_RANK[type],
27
28
  };
28
29
  }
30
+ /** @deprecated Use historyTraceOrder. */
31
+ export function transcriptTraceOrder(transcriptIndex, contentPartIndex, type) {
32
+ return historyTraceOrder(transcriptIndex, contentPartIndex, type, "transcript");
33
+ }
29
34
  export function eventTraceOrder(eventSequence, type) {
30
35
  return {
31
36
  sourceRank: TRACE_SOURCE_RANK["event-log"],
@@ -85,7 +85,7 @@ function clearRemovedParentIds(nodes, removedIds) {
85
85
  function transcriptEventIds(nodes) {
86
86
  const eventIds = new Set();
87
87
  for (const node of nodes) {
88
- if (node.source === "transcript" && node.eventId)
88
+ if ((node.source === "transcript" || node.source === "product-history") && node.eventId)
89
89
  eventIds.add(node.eventId);
90
90
  }
91
91
  return eventIds;
@@ -35,7 +35,17 @@ export const sessionLifecycleSignalProducer = {
35
35
  const session = data.session;
36
36
  const existing = context.getNode(`session:${session.id}`);
37
37
  const rootPiboSessionId = session.parentId ? context.getSessionRoot(session.parentId) : session.id;
38
- if (existing && existing.parentPiboSessionId === session.parentId && existing.rootPiboSessionId === rootPiboSessionId)
38
+ const binding = session.runtimeBinding;
39
+ if (existing
40
+ && existing.parentPiboSessionId === session.parentId
41
+ && existing.rootPiboSessionId === rootPiboSessionId
42
+ && existing.metadata?.kind === session.kind
43
+ && existing.metadata?.channel === session.channel
44
+ && existing.metadata?.profile === session.profile
45
+ && existing.metadata?.runtimeInstanceId === binding?.runtimeInstanceId
46
+ && existing.metadata?.runtimeAdapterId === binding?.adapterId
47
+ && existing.metadata?.runtimeBindingState === binding?.state
48
+ && existing.metadata?.nativeSessionId === binding?.nativeSessionId)
39
49
  return [];
40
50
  return [{ type: "upsert_node", node: node({
41
51
  id: `session:${session.id}`,
@@ -44,7 +54,15 @@ export const sessionLifecycleSignalProducer = {
44
54
  rootPiboSessionId,
45
55
  piboSessionId: session.id,
46
56
  parentPiboSessionId: session.parentId,
47
- metadata: { kind: session.kind, channel: session.channel, profile: session.profile },
57
+ metadata: {
58
+ kind: session.kind,
59
+ channel: session.channel,
60
+ profile: session.profile,
61
+ runtimeInstanceId: session.runtimeBinding?.runtimeInstanceId,
62
+ runtimeAdapterId: session.runtimeBinding?.adapterId,
63
+ runtimeBindingState: session.runtimeBinding?.state,
64
+ nativeSessionId: session.runtimeBinding?.nativeSessionId,
65
+ },
48
66
  }, context) }];
49
67
  }
50
68
  if (data.type === "message_accepted") {
@@ -180,6 +180,9 @@ function runSummaryEqual(a, b) {
180
180
  && a.startedAt === b.startedAt
181
181
  && a.updatedAt === b.updatedAt;
182
182
  }
183
+ function isRuntimeBindingState(value) {
184
+ return value === "unbound" || value === "bound" || value === "missing" || value === "error";
185
+ }
183
186
  function childSummaryEqual(a, b) {
184
187
  return a.nodeId === b.nodeId
185
188
  && a.piboSessionId === b.piboSessionId
@@ -193,6 +196,10 @@ function sessionSnapshotSemanticallyEqual(a, b) {
193
196
  return false;
194
197
  return a.piboSessionId === b.piboSessionId
195
198
  && a.piSessionId === b.piSessionId
199
+ && a.runtimeInstanceId === b.runtimeInstanceId
200
+ && a.runtimeAdapterId === b.runtimeAdapterId
201
+ && a.runtimeBindingState === b.runtimeBindingState
202
+ && a.nativeSessionId === b.nativeSessionId
196
203
  && a.parentPiboSessionId === b.parentPiboSessionId
197
204
  && a.rootPiboSessionId === b.rootPiboSessionId
198
205
  && a.localStatus === b.localStatus
@@ -535,6 +542,10 @@ export class InMemoryPiboSignalRegistry {
535
542
  const hasActiveDescendant = childSnapshots.some((snapshot) => snapshot.isTreeActive);
536
543
  return {
537
544
  piboSessionId,
545
+ runtimeInstanceId: typeof sessionNode?.metadata?.runtimeInstanceId === "string" ? sessionNode.metadata.runtimeInstanceId : undefined,
546
+ runtimeAdapterId: typeof sessionNode?.metadata?.runtimeAdapterId === "string" ? sessionNode.metadata.runtimeAdapterId : undefined,
547
+ runtimeBindingState: isRuntimeBindingState(sessionNode?.metadata?.runtimeBindingState) ? sessionNode.metadata.runtimeBindingState : undefined,
548
+ nativeSessionId: typeof sessionNode?.metadata?.nativeSessionId === "string" ? sessionNode.metadata.nativeSessionId : undefined,
538
549
  parentPiboSessionId: this.parentSessionIdByChildId.get(piboSessionId),
539
550
  rootPiboSessionId,
540
551
  version,
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
- import { Type } from "@earendil-works/pi-ai";
3
- import { defineTool } from "@earendil-works/pi-coding-agent";
2
+ import { Type } from "typebox";
3
+ import { definePiboTool } from "../tools/contract.js";
4
4
  function hashPart(value) {
5
5
  return createHash("sha256").update(value).digest("hex").slice(0, 12);
6
6
  }
@@ -28,26 +28,28 @@ export function createSubagentToolDefinitions(subagents, runner) {
28
28
  }
29
29
  function createSubagentToolDefinition(subagent, runner) {
30
30
  const name = createSubagentToolName(subagent.name);
31
- return defineTool({
31
+ return definePiboTool({
32
32
  name,
33
- label: `Pibo Subagent ${subagent.name}`,
33
+ title: `Pibo Subagent ${subagent.name}`,
34
34
  description: subagent.description ??
35
35
  `Send a message to the ${subagent.name} subagent. Use threadKey to continue the same subagent session.`,
36
36
  promptSnippet: subagent.description ??
37
37
  `Send a message to the ${subagent.name} subagent. Pass the same threadKey when you want to continue the same subagent session.`,
38
38
  executionMode: "parallel",
39
- parameters: Type.Object({
39
+ inputSchema: Type.Object({
40
40
  message: Type.String({ description: "Message to send to the subagent" }),
41
41
  threadKey: Type.Optional(Type.String({
42
42
  description: "Stable key for continuing a previous subagent conversation. Omit it to create a new subagent session.",
43
+ maxLength: 256,
43
44
  })),
44
45
  }),
45
- async execute(toolCallId, params) {
46
+ async execute(toolCallId, params, signal) {
46
47
  const result = await runner.runSubagent({
47
48
  subagent,
48
49
  message: params.message,
49
50
  threadKey: params.threadKey,
50
51
  toolCallId,
52
+ signal,
51
53
  });
52
54
  return {
53
55
  content: [
@@ -1,7 +1,8 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { join } from "node:path";
3
- import { StringEnum, Type } from "@earendil-works/pi-ai";
4
- import { defineTool } from "@earendil-works/pi-coding-agent";
3
+ import { Type } from "typebox";
4
+ import { piboStringEnum } from "./schema.js";
5
+ import { definePiboTool } from "./contract.js";
5
6
  import { connectCdpTarget, listCdpTargets } from "./cdp-client.js";
6
7
  import { browserPoolPaths, releaseBrowserPoolLease } from "./browser-pool.js";
7
8
  import { browserUseLeaseEnvironment, findActiveBrowserUseLeaseForHolder } from "./browser-use-leases.js";
@@ -203,24 +204,24 @@ export function createCodexBrowserToolProfiles() {
203
204
  export function createCodexBrowserToolDefinitions(controller, enabledNames = CODEX_BROWSER_TOOL_NAMES) {
204
205
  const enabled = new Set(enabledNames);
205
206
  const tools = [
206
- [BROWSER_USE_OPEN_TABS_TOOL_NAME, defineTool({
207
+ [BROWSER_USE_OPEN_TABS_TOOL_NAME, definePiboTool({
207
208
  name: BROWSER_USE_OPEN_TABS_TOOL_NAME,
208
- label: "browser_use.open_tabs",
209
+ title: "browser_use.open_tabs",
209
210
  description: "List open tabs in the persistent Browser Use session. Returns stable target IDs, short tab IDs, titles, URLs, and tab indices.",
210
211
  promptSnippet: "Use browser_use_open_tabs (browser_use.open_tabs) to discover tabs before targeting a screenshot or tab-specific action.",
211
212
  executionMode: "parallel",
212
- parameters: Type.Object({}),
213
+ inputSchema: Type.Object({}),
213
214
  async execute() {
214
215
  return toolResult(await controller.openTabs());
215
216
  },
216
217
  })],
217
- [BROWSER_USE_TAKE_SCREENSHOT_TOOL_NAME, defineTool({
218
+ [BROWSER_USE_TAKE_SCREENSHOT_TOOL_NAME, definePiboTool({
218
219
  name: BROWSER_USE_TAKE_SCREENSHOT_TOOL_NAME,
219
- label: "browser_use.take_screenshot",
220
+ title: "browser_use.take_screenshot",
220
221
  description: "Take a PNG screenshot from the persistent Browser Use session. Omit tabId to capture the first attachable page tab.",
221
222
  promptSnippet: "Use browser_use_take_screenshot (browser_use.take_screenshot) when visual page state matters.",
222
223
  executionMode: "parallel",
223
- parameters: Type.Object({
224
+ inputSchema: Type.Object({
224
225
  tabId: Type.Optional(Type.String({ description: "Full target ID or short tab ID returned by browser_use_open_tabs." })),
225
226
  fullPage: Type.Optional(Type.Boolean({ description: "Capture the full scrollable page instead of only the viewport." })),
226
227
  timeoutMs: Type.Optional(Type.Number({ description: "Screenshot timeout in milliseconds." })),
@@ -237,20 +238,20 @@ export function createCodexBrowserToolDefinitions(controller, enabledNames = COD
237
238
  };
238
239
  },
239
240
  })],
240
- [BROWSER_USE_TOOL_NAME, defineTool({
241
+ [BROWSER_USE_TOOL_NAME, definePiboTool({
241
242
  name: BROWSER_USE_TOOL_NAME,
242
- label: "browser_use.browser_use",
243
+ title: "browser_use.browser_use",
243
244
  description: "Control one persistent Browser Use session with a structured action. State and element indices persist through the same Pibo session and managed browser-pool lease.",
244
245
  promptSnippet: "Use browser_use_browser_use (browser_use.browser_use) for structured navigation and interaction; call state before index-based clicks or input.",
245
246
  executionMode: "sequential",
246
- parameters: Type.Object({
247
- action: StringEnum(["navigate", "state", "click", "input", "type", "scroll", "back", "switch_tab", "close_tab", "keys", "evaluate", "wait_selector", "wait_text"], { description: "Browser action." }),
247
+ inputSchema: Type.Object({
248
+ action: piboStringEnum(["navigate", "state", "click", "input", "type", "scroll", "back", "switch_tab", "close_tab", "keys", "evaluate", "wait_selector", "wait_text"], { description: "Browser action." }),
248
249
  url: Type.Optional(Type.String({ description: "URL for navigate." })),
249
250
  index: Type.Optional(Type.Number({ description: "Interactive element index for click or input." })),
250
251
  coordinateX: Type.Optional(Type.Number({ description: "Viewport X coordinate for click; use with coordinateY." })),
251
252
  coordinateY: Type.Optional(Type.Number({ description: "Viewport Y coordinate for click; use with coordinateX." })),
252
253
  text: Type.Optional(Type.String({ description: "Text for input, type, or wait_text." })),
253
- direction: Type.Optional(StringEnum(["up", "down"], { description: "Scroll direction." })),
254
+ direction: Type.Optional(piboStringEnum(["up", "down"], { description: "Scroll direction." })),
254
255
  amount: Type.Optional(Type.Number({ description: "Scroll amount in pixels." })),
255
256
  tab: Type.Optional(Type.Number({ description: "Browser Use tab index for switch_tab or close_tab." })),
256
257
  keys: Type.Optional(Type.String({ description: "Keys to send, such as Enter or Control+a." })),
@@ -262,13 +263,13 @@ export function createCodexBrowserToolDefinitions(controller, enabledNames = COD
262
263
  return toolResult(await controller.use(params, signal));
263
264
  },
264
265
  })],
265
- [NODE_REPL_JS_TOOL_NAME, defineTool({
266
+ [NODE_REPL_JS_TOOL_NAME, definePiboTool({
266
267
  name: NODE_REPL_JS_TOOL_NAME,
267
- label: "node_repl.js",
268
+ title: "node_repl.js",
268
269
  description: "Execute JavaScript in a persistent, session-scoped Node REPL with top-level await. Top-level bindings persist. The sandbox omits require and process, and exposes browser.openTabs() plus browser.use(action, params) for the same Browser Use session.",
269
270
  promptSnippet: "Use node_repl_js (node_repl.js) for persistent JavaScript state and browser-bound analysis. Top-level await and reusable top-level bindings are supported.",
270
271
  executionMode: "sequential",
271
- parameters: Type.Object({
272
+ inputSchema: Type.Object({
272
273
  code: Type.String({ description: "JavaScript to execute. Top-level declarations persist across calls." }),
273
274
  timeoutMs: Type.Optional(Type.Number({ description: "Execution timeout in milliseconds." })),
274
275
  }),
@@ -276,13 +277,13 @@ export function createCodexBrowserToolDefinitions(controller, enabledNames = COD
276
277
  return nodeReplToolResult(await controller.js(params.code, params.timeoutMs));
277
278
  },
278
279
  })],
279
- [NODE_REPL_JS_RESET_TOOL_NAME, defineTool({
280
+ [NODE_REPL_JS_RESET_TOOL_NAME, definePiboTool({
280
281
  name: NODE_REPL_JS_RESET_TOOL_NAME,
281
- label: "node_repl.js_reset",
282
+ title: "node_repl.js_reset",
282
283
  description: "Reset the persistent node_repl.js namespace for the current Pibo session.",
283
284
  promptSnippet: "Use node_repl_js_reset (node_repl.js_reset) only when persistent JavaScript state should be discarded.",
284
285
  executionMode: "sequential",
285
- parameters: Type.Object({}),
286
+ inputSchema: Type.Object({}),
286
287
  async execute() {
287
288
  return nodeReplToolResult(await controller.jsReset());
288
289
  },
@@ -1,8 +1,9 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { extname, isAbsolute, resolve } from "node:path";
4
- import { StringEnum, Type } from "@earendil-works/pi-ai";
5
- import { defineTool } from "@earendil-works/pi-coding-agent";
4
+ import { Type } from "typebox";
5
+ import { piboStringEnum } from "./schema.js";
6
+ import { definePiboTool } from "./contract.js";
6
7
  function resolveCwd(baseCwd, workdir) {
7
8
  if (!workdir || workdir.trim().length === 0)
8
9
  return baseCwd;
@@ -22,13 +23,13 @@ function mimeTypeForPath(path) {
22
23
  }
23
24
  }
24
25
  export function createCodexCompatToolDefinitions() {
25
- const applyPatch = defineTool({
26
+ const applyPatch = definePiboTool({
26
27
  name: "apply_patch",
27
- label: "Apply Patch",
28
+ title: "Apply Patch",
28
29
  description: "Applies a Codex-style patch to files in the workspace.",
29
30
  promptSnippet: "Use apply_patch for manual file edits by passing the complete patch text.",
30
31
  executionMode: "sequential",
31
- parameters: Type.Object({
32
+ inputSchema: Type.Object({
32
33
  patch: Type.String({ description: "Patch text starting with *** Begin Patch and ending with *** End Patch." }),
33
34
  workdir: Type.Optional(Type.String({ description: "Working directory. Defaults to the runtime cwd." })),
34
35
  }),
@@ -59,15 +60,15 @@ export function createCodexCompatToolDefinitions() {
59
60
  };
60
61
  },
61
62
  });
62
- const viewImage = defineTool({
63
+ const viewImage = definePiboTool({
63
64
  name: "view_image",
64
- label: "View Image",
65
+ title: "View Image",
65
66
  description: "Reads a local image file and returns it as an inline image result.",
66
67
  promptSnippet: "Use view_image to inspect a local image path when visual details matter.",
67
68
  executionMode: "parallel",
68
- parameters: Type.Object({
69
+ inputSchema: Type.Object({
69
70
  path: Type.String({ description: "Local filesystem path to an image file." }),
70
- detail: Type.Optional(StringEnum(["original"], { description: "Use original resolution when set." })),
71
+ detail: Type.Optional(piboStringEnum(["original"], { description: "Use original resolution when set." })),
71
72
  }),
72
73
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
73
74
  const path = resolveCwd(ctx.cwd, params.path);
@@ -2,8 +2,8 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { platform, release, arch } from "node:os";
3
3
  import { dirname, extname, isAbsolute, join, resolve } from "node:path";
4
4
  import { AuthStorage } from "@earendil-works/pi-coding-agent";
5
- import { Type } from "@earendil-works/pi-ai";
6
- import { defineTool } from "@earendil-works/pi-coding-agent";
5
+ import { Type } from "typebox";
6
+ import { definePiboTool } from "./contract.js";
7
7
  import { getPiboHome } from "../core/pibo-home.js";
8
8
  const OPENAI_CODEX_PROVIDER = "openai-codex";
9
9
  const DEFAULT_CODEX_BACKEND_BASE_URL = "https://chatgpt.com/backend-api";
@@ -215,9 +215,9 @@ async function collectEditImages(params, cwd, sessionEntries) {
215
215
  return [];
216
216
  }
217
217
  export function createCodexImageGenerationToolDefinition(context = {}, options = {}) {
218
- return defineTool({
218
+ return definePiboTool({
219
219
  name: "codex_image_generation",
220
- label: "Codex Image Generation",
220
+ title: "Codex Image Generation",
221
221
  description: "Generates or edits images through the ChatGPT/Codex backend API using openai-codex OAuth entitlement. Does not use the public OpenAI Images API.",
222
222
  promptSnippet: "Use codex_image_generation to create an image from a prompt, or to edit referenced/recent images with the Codex/ChatGPT image backend.",
223
223
  promptGuidelines: [
@@ -225,14 +225,16 @@ export function createCodexImageGenerationToolDefinition(context = {}, options =
225
225
  "For edits, pass either referenced_image_paths for local image files or num_last_images_to_include for recent conversation images, but not both.",
226
226
  ],
227
227
  executionMode: "sequential",
228
- parameters: Type.Object({
228
+ inputSchema: Type.Object({
229
229
  prompt: Type.String({ description: "Image generation/editing prompt. Be specific about the desired final image." }),
230
230
  referenced_image_paths: Type.Optional(Type.Array(Type.String({ description: "Local filesystem image path to edit." }), { description: `Optional local image paths to edit. At most ${MAX_EDIT_IMAGES}.` })),
231
231
  num_last_images_to_include: Type.Optional(Type.Number({ description: `Use the last N conversation images as edit references. Must be between 1 and ${MAX_EDIT_IMAGES}.` })),
232
232
  }),
233
233
  async execute(toolCallId, params, signal, _onUpdate, ctx) {
234
234
  validateImageArgs(params);
235
- const images = await collectEditImages(params, ctx.cwd, ctx.sessionManager.getBranch());
235
+ const legacySessionManager = ctx.sessionManager;
236
+ const sessionEntries = [...(ctx.getConversationEntries?.() ?? context.getConversationEntries?.() ?? legacySessionManager?.getBranch?.() ?? [])];
237
+ const images = await collectEditImages(params, ctx.cwd, sessionEntries);
236
238
  const operation = images.length > 0 ? "edit" : "generate";
237
239
  const endpoint = operation === "edit" ? "edits" : "generations";
238
240
  const auth = await getCodexImageAuth();
@@ -0,0 +1,45 @@
1
+ /** Identity helper that preserves schema-derived input types. */
2
+ export function definePiboTool(definition) {
3
+ definition.label ??= definition.title;
4
+ definition.parameters ??= definition.inputSchema;
5
+ return definition;
6
+ }
7
+ export function isPiboToolDefinition(value) {
8
+ return Boolean(value
9
+ && typeof value === "object"
10
+ && "inputSchema" in value
11
+ && "execute" in value
12
+ && typeof value.execute === "function");
13
+ }
14
+ export function normalizePiboToolResult(result) {
15
+ return {
16
+ content: result.content.map((content) => ({ ...content })),
17
+ ...(result.details !== undefined ? { details: result.details } : {}),
18
+ ...(result.isError !== undefined ? { isError: result.isError } : {}),
19
+ };
20
+ }
21
+ /** Convert a legacy Pi-shaped definition without leaking Pi types into generic code. */
22
+ export function normalizePiboToolDefinition(definition) {
23
+ if (isPiboToolDefinition(definition))
24
+ return definition;
25
+ return definePiboTool({
26
+ name: definition.name,
27
+ title: definition.label,
28
+ description: definition.description,
29
+ ...(definition.promptSnippet ? { promptSnippet: definition.promptSnippet } : {}),
30
+ ...(definition.promptGuidelines ? { promptGuidelines: [...definition.promptGuidelines] } : {}),
31
+ inputSchema: definition.parameters,
32
+ ...(definition.executionMode ? { executionMode: definition.executionMode } : {}),
33
+ portable: false,
34
+ ...(definition.prepareArguments ? { prepareInput: definition.prepareArguments } : {}),
35
+ async execute(toolCallId, input, signal, onUpdate, context) {
36
+ if (context.nativeContext === undefined) {
37
+ throw new Error(`Tool "${definition.name}" uses the deprecated Pi-native definition contract and cannot be delivered through portable MCP.`);
38
+ }
39
+ const result = await definition.execute(toolCallId, input, signal, onUpdate
40
+ ? (update) => onUpdate(normalizePiboToolResult(update))
41
+ : undefined, context.nativeContext);
42
+ return normalizePiboToolResult(result);
43
+ },
44
+ });
45
+ }
@@ -0,0 +1,182 @@
1
+ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
2
+ const TOKEN_PREFIX = "pibo_tool_";
3
+ const DEFAULT_TTL_MS = 5 * 60_000;
4
+ const DEFAULT_MAX_LIFETIME_MS = 30 * 60_000;
5
+ export class PiboToolCredentialError extends Error {
6
+ code;
7
+ constructor(code, message) {
8
+ super(message);
9
+ this.name = "PiboToolCredentialError";
10
+ this.code = code;
11
+ }
12
+ }
13
+ function positiveDuration(value, fallback, label) {
14
+ const resolved = value ?? fallback;
15
+ if (!Number.isFinite(resolved) || resolved <= 0)
16
+ throw new Error(`${label} must be a positive finite duration`);
17
+ return Math.floor(resolved);
18
+ }
19
+ function normalizeScope(scope) {
20
+ const piboSessionId = scope.piboSessionId.trim();
21
+ const runtimeInstanceId = scope.runtimeInstanceId.trim();
22
+ const adapterId = scope.adapterId.trim();
23
+ const sessionGeneration = scope.sessionGeneration.trim();
24
+ const cwd = scope.cwd.trim();
25
+ if (!piboSessionId || !runtimeInstanceId || !adapterId || !sessionGeneration || !cwd) {
26
+ throw new PiboToolCredentialError("credential_scope_invalid", "Tool credentials require a Pibo Session, runtime instance, adapter, session generation, and working directory.");
27
+ }
28
+ const allowedToolNames = [...new Set(scope.allowedToolNames.map((name) => name.trim()).filter(Boolean))].sort();
29
+ if (allowedToolNames.length === 0) {
30
+ throw new PiboToolCredentialError("credential_scope_invalid", "Tool credentials require at least one allowed tool.");
31
+ }
32
+ return {
33
+ piboSessionId,
34
+ ...(scope.piboRoomId?.trim() ? { piboRoomId: scope.piboRoomId.trim() } : {}),
35
+ ...(scope.profileName?.trim() ? { profileName: scope.profileName.trim() } : {}),
36
+ runtimeInstanceId,
37
+ adapterId,
38
+ sessionGeneration,
39
+ cwd,
40
+ allowedToolNames,
41
+ };
42
+ }
43
+ function tokenHash(secret) {
44
+ return createHash("sha256").update(secret, "utf8").digest();
45
+ }
46
+ function parseToken(token) {
47
+ if (!token.startsWith(TOKEN_PREFIX))
48
+ return undefined;
49
+ const separator = token.indexOf(".", TOKEN_PREFIX.length);
50
+ if (separator < 0)
51
+ return undefined;
52
+ const credentialId = token.slice(TOKEN_PREFIX.length, separator);
53
+ const secret = token.slice(separator + 1);
54
+ if (!credentialId || !secret)
55
+ return undefined;
56
+ return { credentialId, secret };
57
+ }
58
+ function iso(value) {
59
+ return value === undefined ? undefined : new Date(value).toISOString();
60
+ }
61
+ function infoFor(record) {
62
+ return {
63
+ credentialId: record.credentialId,
64
+ ...record.scope,
65
+ allowedToolNames: [...record.scope.allowedToolNames],
66
+ issuedAt: new Date(record.issuedAtMs).toISOString(),
67
+ expiresAt: new Date(record.expiresAtMs).toISOString(),
68
+ ...(iso(record.lastAliveAtMs) ? { lastAliveAt: iso(record.lastAliveAtMs) } : {}),
69
+ ...(iso(record.revokedAtMs) ? { revokedAt: iso(record.revokedAtMs) } : {}),
70
+ };
71
+ }
72
+ export class PiboToolCredentialRegistry {
73
+ records = new Map();
74
+ defaultTtlMs;
75
+ maxLifetimeMs;
76
+ now;
77
+ constructor(options = {}) {
78
+ this.defaultTtlMs = positiveDuration(options.defaultTtlMs, DEFAULT_TTL_MS, "defaultTtlMs");
79
+ this.maxLifetimeMs = positiveDuration(options.maxLifetimeMs, DEFAULT_MAX_LIFETIME_MS, "maxLifetimeMs");
80
+ if (this.defaultTtlMs > this.maxLifetimeMs) {
81
+ throw new Error("defaultTtlMs must not exceed maxLifetimeMs");
82
+ }
83
+ this.now = options.now ?? Date.now;
84
+ }
85
+ issue(scope, ttlMs = this.defaultTtlMs) {
86
+ const normalizedScope = normalizeScope(scope);
87
+ const lifetime = Math.min(positiveDuration(ttlMs, this.defaultTtlMs, "ttlMs"), this.maxLifetimeMs);
88
+ const now = this.now();
89
+ const credentialId = randomBytes(16).toString("base64url");
90
+ const secret = randomBytes(32).toString("base64url");
91
+ const record = {
92
+ credentialId,
93
+ tokenHash: tokenHash(secret),
94
+ scope: normalizedScope,
95
+ issuedAtMs: now,
96
+ expiresAtMs: now + lifetime,
97
+ };
98
+ this.records.set(credentialId, record);
99
+ return {
100
+ token: `${TOKEN_PREFIX}${credentialId}.${secret}`,
101
+ info: infoFor(record),
102
+ };
103
+ }
104
+ authenticate(token, options = {}) {
105
+ const parsed = parseToken(token);
106
+ const record = parsed ? this.records.get(parsed.credentialId) : undefined;
107
+ if (!parsed || !record) {
108
+ throw new PiboToolCredentialError("credential_invalid", "Invalid Pibo tool credential.");
109
+ }
110
+ const presentedHash = tokenHash(parsed.secret);
111
+ if (presentedHash.length !== record.tokenHash.length || !timingSafeEqual(presentedHash, record.tokenHash)) {
112
+ throw new PiboToolCredentialError("credential_invalid", "Invalid Pibo tool credential.");
113
+ }
114
+ if (record.revokedAtMs !== undefined) {
115
+ throw new PiboToolCredentialError("credential_revoked", "Pibo tool credential has been revoked.");
116
+ }
117
+ const now = this.now();
118
+ if (record.expiresAtMs <= now) {
119
+ throw new PiboToolCredentialError("credential_expired", "Pibo tool credential has expired.");
120
+ }
121
+ if (options.touch !== false)
122
+ record.lastAliveAtMs = now;
123
+ return infoFor(record);
124
+ }
125
+ renew(token, ttlMs = this.defaultTtlMs) {
126
+ const parsed = parseToken(token);
127
+ this.authenticate(token, { touch: false });
128
+ const record = parsed ? this.records.get(parsed.credentialId) : undefined;
129
+ if (!record)
130
+ throw new PiboToolCredentialError("credential_invalid", "Invalid Pibo tool credential.");
131
+ const now = this.now();
132
+ const requested = positiveDuration(ttlMs, this.defaultTtlMs, "ttlMs");
133
+ const maximumExpiry = record.issuedAtMs + this.maxLifetimeMs;
134
+ record.expiresAtMs = Math.min(now + requested, maximumExpiry);
135
+ if (record.expiresAtMs <= now) {
136
+ record.revokedAtMs = now;
137
+ throw new PiboToolCredentialError("credential_expired", "Pibo tool credential reached its maximum lifetime.");
138
+ }
139
+ record.lastAliveAtMs = now;
140
+ return infoFor(record);
141
+ }
142
+ revoke(token) {
143
+ const parsed = parseToken(token);
144
+ if (!parsed)
145
+ return false;
146
+ return this.revokeCredentialId(parsed.credentialId);
147
+ }
148
+ revokeCredentialId(credentialId) {
149
+ const record = this.records.get(credentialId);
150
+ if (!record || record.revokedAtMs !== undefined)
151
+ return false;
152
+ record.revokedAtMs = this.now();
153
+ return true;
154
+ }
155
+ revokeSessionGeneration(piboSessionId, sessionGeneration) {
156
+ let revoked = 0;
157
+ for (const record of this.records.values()) {
158
+ if (record.scope.piboSessionId === piboSessionId
159
+ && record.scope.sessionGeneration === sessionGeneration
160
+ && record.revokedAtMs === undefined) {
161
+ record.revokedAtMs = this.now();
162
+ revoked += 1;
163
+ }
164
+ }
165
+ return revoked;
166
+ }
167
+ getInfo(credentialId) {
168
+ const record = this.records.get(credentialId);
169
+ return record ? infoFor(record) : undefined;
170
+ }
171
+ cleanupExpired() {
172
+ const now = this.now();
173
+ let removed = 0;
174
+ for (const [credentialId, record] of this.records.entries()) {
175
+ if (record.expiresAtMs <= now || record.revokedAtMs !== undefined) {
176
+ this.records.delete(credentialId);
177
+ removed += 1;
178
+ }
179
+ }
180
+ return removed;
181
+ }
182
+ }