@pasko70/pibo 3.4.0 → 3.4.2

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 (40) hide show
  1. package/dist/agent-runtime/routed-session.js +7 -1
  2. package/dist/agent-runtimes/pi/adapter.js +4 -1
  3. package/dist/agent-runtimes/pi/history.js +55 -1
  4. package/dist/apps/chat/data/chat-data-mappers.js +1 -1
  5. package/dist/apps/chat/stream.js +1 -1
  6. package/dist/apps/chat/trace-v2.js +1 -0
  7. package/dist/apps/chat/web-app.js +2 -1
  8. package/dist/apps/chat-ui/assets/{dist-jVaiGXFD.js → dist-B7Ju7u08.js} +1 -1
  9. package/dist/apps/chat-ui/assets/{dist-Dyp6sLQn.js → dist-BDnWOTcs.js} +1 -1
  10. package/dist/apps/chat-ui/assets/{dist-t25moyJS.js → dist-CAiO6h6z.js} +1 -1
  11. package/dist/apps/chat-ui/assets/{dist-AhtdAETR.js → dist-CCR1HsaR.js} +1 -1
  12. package/dist/apps/chat-ui/assets/{dist-Bk_L1qbJ.js → dist-CXWLhBio.js} +1 -1
  13. package/dist/apps/chat-ui/assets/index-0cCGS0o3.js +228 -0
  14. package/dist/apps/chat-ui/assets/index-BMJGoyM8.css +1 -0
  15. package/dist/apps/chat-ui/index.html +2 -2
  16. package/dist/apps/chat-vscode-web/assets/index-BD3Ogz9Z.js +43 -0
  17. package/dist/apps/chat-vscode-web/index.html +1 -1
  18. package/dist/auth/openai-codex-usage.js +34 -5
  19. package/dist/core/session-router.js +100 -11
  20. package/dist/data/ingest-service.js +1 -1
  21. package/dist/data/schema.js +10 -1
  22. package/dist/debug/agents.js +5 -3
  23. package/dist/gateway/server.js +1 -1
  24. package/dist/previews/web-app.js +85 -0
  25. package/dist/session-ui/terminalRows.js +3 -1
  26. package/dist/sessions/pibo-data-store.js +38 -1
  27. package/dist/sessions/store.js +38 -0
  28. package/dist/shared/tool-call-metrics.js +77 -0
  29. package/dist/shared/trace-event-projection.js +2 -0
  30. package/dist/shared/trace-live-reducer.js +1 -0
  31. package/dist/shared/trace-patch-nodes.js +4 -0
  32. package/dist/subagents/context.js +2 -2
  33. package/dist/subagents/observation-query.js +28 -1
  34. package/dist/subagents/observations.js +8 -0
  35. package/dist/subagents/tool.js +12 -8
  36. package/npm-shrinkwrap.json +2 -2
  37. package/package.json +1 -1
  38. package/dist/apps/chat-ui/assets/index-CZjNw-aK.js +0 -228
  39. package/dist/apps/chat-ui/assets/index-CcEjFITM.css +0 -1
  40. package/dist/apps/chat-vscode-web/assets/index-Spj6M0tn.js +0 -43
@@ -0,0 +1,77 @@
1
+ /** Bounded structural walk; never tokenize, copy or scan large strings. */
2
+ export function estimateToolPayloadTokens(payload) {
3
+ let budget = 10_000;
4
+ const seen = new WeakSet();
5
+ function size(value, depth) {
6
+ if (--budget < 0 || depth > 64)
7
+ return NaN;
8
+ if (typeof value === "string")
9
+ return value.length;
10
+ if (value === null)
11
+ return 4;
12
+ if (typeof value === "boolean")
13
+ return value ? 4 : 5;
14
+ if (typeof value === "number")
15
+ return String(value).length;
16
+ if (typeof value !== "object" || seen.has(value))
17
+ return NaN;
18
+ seen.add(value);
19
+ let chars = 2;
20
+ if (Array.isArray(value)) {
21
+ for (const item of value) {
22
+ chars += size(item, depth + 1) + 1;
23
+ if (!Number.isFinite(chars))
24
+ return NaN;
25
+ }
26
+ }
27
+ else {
28
+ const record = value;
29
+ // Binary/media payloads have no meaningful character-based token count.
30
+ if (["image", "audio", "document", "resource", "image_url"].includes(String(record.type)))
31
+ return NaN;
32
+ for (const key in record) {
33
+ if (--budget < 0)
34
+ return NaN;
35
+ if (!Object.hasOwn(record, key) || record[key] === undefined)
36
+ continue;
37
+ chars += key.length + 3 + size(record[key], depth + 1) + 1;
38
+ if (!Number.isFinite(chars))
39
+ return NaN;
40
+ }
41
+ }
42
+ seen.delete(value);
43
+ return chars;
44
+ }
45
+ try {
46
+ const chars = size(payload, 0);
47
+ return Number.isFinite(chars) ? Math.ceil(chars / 4) : undefined;
48
+ }
49
+ catch {
50
+ // Diagnostics must not turn an otherwise successful tool into a failure.
51
+ return undefined;
52
+ }
53
+ }
54
+ export class ToolCallMetricsCollector {
55
+ active = new Map();
56
+ start(id, args, now = performance.now()) {
57
+ if (this.active.has(id))
58
+ return;
59
+ this.active.set(id, { startedAt: now, inputTokens: estimateToolPayloadTokens(args) });
60
+ }
61
+ finish(id, result, now = performance.now()) {
62
+ const started = this.active.get(id);
63
+ this.active.delete(id);
64
+ // Harness result metadata is not model-visible tool output.
65
+ const output = result && typeof result === "object" && "content" in result
66
+ ? result.content : result;
67
+ return {
68
+ tokenBasis: "chars/4",
69
+ durationMs: started ? Math.max(0, now - started.startedAt) : undefined,
70
+ inputTokens: started?.inputTokens,
71
+ outputTokens: estimateToolPayloadTokens(output),
72
+ };
73
+ }
74
+ clear() {
75
+ this.active.clear();
76
+ }
77
+ }
@@ -626,6 +626,7 @@ function traceNodeFromEvent(piboSessionId, event, childByParent, linkedChildByTo
626
626
  toolCallId: event.toolCallId,
627
627
  toolInvocationOrdinal: event.toolInvocationOrdinal ?? 0,
628
628
  intent: event.intent,
629
+ toolMetrics: event.type === "tool_execution_finished" ? event.toolMetrics : undefined,
629
630
  type: subagentTool ? "agent.delegation" : "tool.call",
630
631
  title: event.toolName,
631
632
  status: event.type === "tool_execution_finished"
@@ -1102,6 +1103,7 @@ function thinkingEventNodeId(event) {
1102
1103
  function mergeToolEvent(target, update) {
1103
1104
  target.status = update.status;
1104
1105
  target.intent = update.intent ?? target.intent;
1106
+ target.toolMetrics = update.toolMetrics ?? target.toolMetrics;
1105
1107
  target.summary = update.summary ?? target.summary;
1106
1108
  target.input = mergeDelegationInput(target, update);
1107
1109
  target.output = update.output ?? target.output;
@@ -122,6 +122,7 @@ function storedEventFromStreamEvent(event, piboSessionId, nextSequence, now) {
122
122
  toolName: event.toolName,
123
123
  toolInvocationOrdinal: validOrdinal(event.toolInvocationOrdinal) ? event.toolInvocationOrdinal : undefined,
124
124
  result: event.result,
125
+ toolMetrics: event.toolMetrics,
125
126
  isError: Boolean(event.isError),
126
127
  ...(event.intent ? { intent: event.intent } : {}),
127
128
  };
@@ -60,6 +60,10 @@ function traceNodeShallowEqual(left, right) {
60
60
  left.startedAt === right.startedAt &&
61
61
  left.completedAt === right.completedAt &&
62
62
  left.durationMs === right.durationMs &&
63
+ left.toolMetrics?.durationMs === right.toolMetrics?.durationMs &&
64
+ left.toolMetrics?.inputTokens === right.toolMetrics?.inputTokens &&
65
+ left.toolMetrics?.outputTokens === right.toolMetrics?.outputTokens &&
66
+ left.toolMetrics?.tokenBasis === right.toolMetrics?.tokenBasis &&
63
67
  left.summary === right.summary &&
64
68
  left.input === right.input &&
65
69
  left.output === right.output &&
@@ -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], textContains?, textRegex?, afterSequence?, limit?, includeTools?, toolDetail?, ... })",
36
+ "pibo_agents_observe({ requestIds?: [runId], cursorMode?: \"auto\"|\"history\", 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. 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.",
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 only wakes the orchestrator. Observe uses `cursorMode: \"auto\"` by default: the first equivalent query returns the newest completed assistant messages, and later calls return only unread messages. Use `cursorMode: \"history\"` only to reread earlier observations. Streaming deltas, duplicate tool progress events, and tools are hidden by default. Inspect tools only when a child stalls, reports an error, or needs targeted diagnosis; prefer exact `toolCallIds`, then `includeTools: true`, and use `toolDetail: \"full\"` only when summaries are insufficient. Use `textContains` for case-insensitive substring matching or `textRegex` for rg/Rust-regex matching; both must match when supplied together. Text, regex, identity, and event filters create separate automatic query cursors; an explicit `afterSequence` overrides and advances the matching automatic cursor.",
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,6 +1,31 @@
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";
1
+ import { createHash } from "node:crypto";
2
+ import { PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES, PIBO_AGENT_OBSERVATION_DEFAULT_TOOL_EVENT_TYPES, normalizePiboAgentObservationCursor, normalizePiboAgentObservationCursorMode, normalizePiboAgentObservationLimit, normalizePiboAgentObservationOrder, normalizePiboAgentObservationToolDetail, parsePiboAgentObservationTimestamp, piboAgentObservationKind, piboAgentObservationToolSummary, } from "./observations.js";
2
3
  import { PIBO_AGENT_TEXT_REGEX_BATCH_MAX_ITEMS, PIBO_AGENT_TEXT_REGEX_BATCH_TARGET_BYTES, matchPiboAgentObservationTextRegex, preparePiboAgentObservationTextRegex, } from "./observation-text-regex.js";
4
+ function sortedUnique(values) {
5
+ return values ? [...new Set(values)].sort() : undefined;
6
+ }
7
+ export function piboAgentObservationCursorScopeKey(input) {
8
+ const scope = {
9
+ requestIds: sortedUnique(input.requestIds),
10
+ toolCallIds: sortedUnique(input.toolCallIds),
11
+ agentIds: sortedUnique(input.agentIds),
12
+ names: sortedUnique(input.names),
13
+ threadKeys: sortedUnique(input.threadKeys),
14
+ eventTypes: sortedUnique(input.eventTypes),
15
+ kinds: input.kinds ? [...new Set(input.kinds)].sort() : undefined,
16
+ roles: sortedUnique(input.roles),
17
+ since: input.since,
18
+ until: input.until,
19
+ textContains: input.textContains?.toLowerCase(),
20
+ textRegex: input.textRegex,
21
+ includeTools: input.includeTools === true,
22
+ toolDetail: input.toolDetail ?? "summary",
23
+ includeDetails: input.includeDetails === true,
24
+ };
25
+ return `v1:${createHash("sha256").update(JSON.stringify(scope)).digest("hex")}`;
26
+ }
3
27
  export function preparePiboAgentObservationQuery(input = {}) {
28
+ const cursorMode = normalizePiboAgentObservationCursorMode(input.cursorMode);
4
29
  const order = normalizePiboAgentObservationOrder(input.order);
5
30
  const limit = normalizePiboAgentObservationLimit(input.limit);
6
31
  const toolDetail = normalizePiboAgentObservationToolDetail(input.toolDetail);
@@ -40,6 +65,7 @@ export function preparePiboAgentObservationQuery(input = {}) {
40
65
  return {
41
66
  filters: {
42
67
  ...input,
68
+ cursorMode,
43
69
  ...(defaultMessageView ? { eventTypes: defaultEventTypes } : {}),
44
70
  ...(afterSequence !== undefined ? { afterSequence } : {}),
45
71
  order,
@@ -49,6 +75,7 @@ export function preparePiboAgentObservationQuery(input = {}) {
49
75
  includeDetails: input.includeDetails === true,
50
76
  },
51
77
  ...(afterSequence !== undefined ? { afterSequence } : {}),
78
+ cursorMode,
52
79
  order,
53
80
  scanOrder: afterSequence !== undefined ? "asc" : order,
54
81
  limit,
@@ -212,6 +212,14 @@ export function normalizePiboAgentObservationOrder(value) {
212
212
  throw new Error(`Agent observation order must be "asc" or "desc".`);
213
213
  return value;
214
214
  }
215
+ export function normalizePiboAgentObservationCursorMode(value) {
216
+ if (value === undefined)
217
+ return "auto";
218
+ if (value !== "auto" && value !== "history") {
219
+ throw new Error(`Agent observation cursorMode must be "auto" or "history".`);
220
+ }
221
+ return value;
222
+ }
215
223
  export function normalizePiboAgentObservationToolDetail(value) {
216
224
  if (value === undefined)
217
225
  return "summary";
@@ -60,12 +60,15 @@ function preparePiboDeprecatedSubagentToolInput(input) {
60
60
  export function formatAgentObservationsForModel(result) {
61
61
  const includeTools = result.filters.includeTools === true;
62
62
  const toolDetail = result.filters.toolDetail ?? "summary";
63
+ const cursorMode = result.filters.cursorMode ?? "auto";
63
64
  const lines = [
64
- `Agent observations (${result.observations.length}; tools=${includeTools ? toolDetail : "hidden"}; order=${result.filters.order ?? "desc"}; limit=${result.filters.limit ?? 20})`,
65
- `nextAfterSequence=${result.nextAfterSequence}; truncated=${result.truncated}`,
65
+ `Agent observations (${result.observations.length}; cursor=${cursorMode}; tools=${includeTools ? toolDetail : "hidden"}; order=${result.filters.order ?? "desc"}; limit=${result.filters.limit ?? 20})`,
66
+ `afterSequence=${result.filters.afterSequence ?? "initial"}; nextAfterSequence=${result.nextAfterSequence}${result.autoCursorSequence === undefined ? "" : `; autoCursorSequence=${result.autoCursorSequence}`}; truncated=${result.truncated}`,
66
67
  ];
67
68
  if (result.observations.length === 0) {
68
- lines.push("", "No delegated-agent observations matched the filters.");
69
+ lines.push("", cursorMode === "auto"
70
+ ? "No new delegated-agent messages matched since the automatic cursor. Use cursorMode=\"history\" only when you need to reread earlier observations."
71
+ : "No historical delegated-agent observations matched the filters.");
69
72
  return lines.join("\n");
70
73
  }
71
74
  for (const observation of result.observations) {
@@ -188,10 +191,10 @@ export function createAgentToolDefinitions(subagents, controller) {
188
191
  title: "Pibo Agents Observe",
189
192
  description: [
190
193
  "Read completed delegated-agent messages with bounded cursor, identity, event, time, substring, regex, order, and limit filters.",
191
- "Default: the newest 20 completed assistant messages, with streaming deltas and tools hidden.",
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.",
194
+ "Default cursorMode=auto: the first equivalent query returns the newest 20 completed assistant messages; later calls return only unread messages. Streaming deltas, duplicate tool progress events, and tools stay hidden.",
195
+ "Use cursorMode=history only to reread earlier observations. Inspect tools only when an agent appears stuck, reports a problem, or needs targeted diagnosis; prefer exact toolCallIds, then includeTools=true, and use toolDetail=full only when compact summaries are insufficient.",
193
196
  ].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. 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.",
197
+ promptSnippet: "Observe child progress through completed assistant messages. cursorMode=auto is the default and remembers each equivalent query, so repeated calls return only unread messages; use cursorMode=history to reread earlier observations. Streaming deltas, duplicate tool progress events, and tools are hidden by default. Inspect tools only for stalls, errors, or targeted diagnosis: prefer exact toolCallIds, use includeTools=true only when broader context is needed, and use toolDetail=full only when summaries are insufficient. Use textContains or textRegex for focused matching; different filters use separate automatic cursors. An explicit afterSequence overrides the stored cursor and advances that automatic query cursor.",
195
198
  executionMode: "parallel",
196
199
  annotations: { readOnly: true },
197
200
  inputSchema: Type.Object({
@@ -207,10 +210,11 @@ export function createAgentToolDefinitions(subagents, controller) {
207
210
  until: Type.Optional(Type.String({ description: "Inclusive ISO-8601 upper timestamp bound" })),
208
211
  textContains: Type.Optional(Type.String({ description: "Case-insensitive substring match against normalized observation text" })),
209
212
  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." })),
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 })),
213
+ cursorMode: Type.Optional(piboStringEnum(["auto", "history"], { default: "auto", description: "auto remembers this normalized query and returns only unread observations after its first newest-message snapshot. history ignores and does not change the saved cursor, allowing deliberate rereads." })),
214
+ afterSequence: Type.Optional(Type.Integer({ description: "Explicit exclusive cursor override. In auto mode it replaces and advances the saved cursor for this normalized query; cursor pages consume the oldest unseen matches and desc reverses only the returned page.", minimum: 0 })),
211
215
  order: Type.Optional(piboStringEnum(["asc", "desc"], { default: "desc", description: "Newest first by default when no cursor is supplied" })),
212
216
  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 })),
213
- includeTools: Type.Optional(Type.Boolean({ description: "Include tools. Default false. With the default message view, true adds tool_call and tool_execution_finished records.", default: false })),
217
+ includeTools: Type.Optional(Type.Boolean({ description: "Include compact tool calls and terminal results. Default false; enable only for stalls, errors, or targeted diagnosis. Prefer exact toolCallIds when known.", default: false })),
214
218
  toolDetail: Type.Optional(piboStringEnum(["summary", "full"], { default: "summary", description: "Tool text detail when tools are included. summary is compact; full remains bounded to the observation text limit." })),
215
219
  includeDetails: Type.Optional(Type.Boolean({ description: "Include the normalized source event in structured details. Default false; use only for diagnostics.", default: false })),
216
220
  }),
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@pasko70/pibo",
3
- "version": "3.4.0",
3
+ "version": "3.4.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@pasko70/pibo",
9
- "version": "3.4.0",
9
+ "version": "3.4.2",
10
10
  "workspaces": [
11
11
  "packages/workflows"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pasko70/pibo",
3
- "version": "3.4.0",
3
+ "version": "3.4.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",