@pasko70/pibo 3.0.0 → 3.0.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 (77) hide show
  1. package/README.md +20 -6
  2. package/dist/agent-runtime/context-build.js +26 -2
  3. package/dist/agent-runtime/routed-session.js +108 -9
  4. package/dist/agent-runtimes/codex-native/adapter.js +12 -0
  5. package/dist/agent-runtimes/codex-native/resource-delivery.js +35 -7
  6. package/dist/agent-runtimes/omp/adapter.js +3 -7
  7. package/dist/agent-runtimes/pi/adapter.js +5 -3
  8. package/dist/agent-runtimes/pi/routed-session.js +49 -8
  9. package/dist/agent-runtimes/pi/runtime.js +6 -0
  10. package/dist/apps/chat/agent-profiles.js +31 -2
  11. package/dist/apps/chat/agent-store.js +43 -3
  12. package/dist/apps/chat/chat-request-normalizers.js +31 -0
  13. package/dist/apps/chat/data/chat-data-mappers.js +61 -11
  14. package/dist/apps/chat/data/session-query-service.js +33 -10
  15. package/dist/apps/chat/data/timeline-query-service.js +60 -35
  16. package/dist/apps/chat/output-compactor.js +144 -21
  17. package/dist/apps/chat/output-event-policy.js +179 -2
  18. package/dist/apps/chat/stream.js +16 -7
  19. package/dist/apps/chat/trace-v2.js +1 -0
  20. package/dist/apps/chat/web-app.js +236 -64
  21. package/dist/apps/chat-ui/assets/{dist-BBDMeU5-.js → dist-B6GZgWSj.js} +1 -1
  22. package/dist/apps/chat-ui/assets/{dist-mqCviI-c.js → dist-BPotHecb.js} +1 -1
  23. package/dist/apps/chat-ui/assets/{dist-JnW4v8UE.js → dist-BzqQKeVO.js} +1 -1
  24. package/dist/apps/chat-ui/assets/{dist-BDIATCQt.js → dist-CvaPTBTN.js} +1 -1
  25. package/dist/apps/chat-ui/assets/{dist-lRiLP9rr.js → dist-paagejNj.js} +1 -1
  26. package/dist/apps/chat-ui/assets/index-6JJV-eic.js +228 -0
  27. package/dist/apps/chat-ui/assets/index-CeL9JPP5.css +1 -0
  28. package/dist/apps/chat-ui/index.html +2 -2
  29. package/dist/apps/chat-vscode-web/assets/{index-Dtw2Z7jz.js → index-U-MSErGa.js} +4 -4
  30. package/dist/apps/chat-vscode-web/index.html +1 -1
  31. package/dist/apps/cli-ui/cliSessionsCommand.js +34 -8
  32. package/dist/cli-session/localSessionSource.js +153 -161
  33. package/dist/cli.js +7 -11
  34. package/dist/core/context-build.js +7 -16
  35. package/dist/core/output-persistence-retry.js +484 -0
  36. package/dist/core/output-render-sequence.js +331 -0
  37. package/dist/core/profiles.js +24 -0
  38. package/dist/core/provider-recovery.js +7 -1
  39. package/dist/core/session-model.js +37 -1
  40. package/dist/core/session-router.js +67 -20
  41. package/dist/data/ingest-service.js +91 -9
  42. package/dist/data/navigation-store.js +5 -5
  43. package/dist/data/schema.js +122 -2
  44. package/dist/data/session-store.js +3 -3
  45. package/dist/debug/index.js +44 -2
  46. package/dist/gateway/web.js +8 -8
  47. package/dist/local/client.js +3 -2
  48. package/dist/plugins/context-files-store.js +30 -0
  49. package/dist/plugins/context-files.js +52 -1
  50. package/dist/plugins/registry.js +2 -0
  51. package/dist/plugins/user-profile-resources.js +22 -0
  52. package/dist/reliability/store.js +246 -37
  53. package/dist/session-ui/terminalRows.js +6 -3
  54. package/dist/sessions/pibo-data-store.js +145 -0
  55. package/dist/sessions/store.js +42 -0
  56. package/dist/setup/cli.js +422 -6
  57. package/dist/setup/installation-profiles.js +464 -0
  58. package/dist/shared/deterministic-digest.js +94 -0
  59. package/dist/shared/trace-engine.js +54 -0
  60. package/dist/shared/trace-event-projection.js +124 -67
  61. package/dist/shared/trace-history.js +47 -12
  62. package/dist/shared/trace-live-reducer.js +23 -4
  63. package/dist/shared/trace-nodes.js +17 -28
  64. package/dist/shared/trace-order.js +41 -5
  65. package/dist/shared/trace-page-merge.js +44 -14
  66. package/dist/shared/trace-patch-nodes.js +2 -0
  67. package/dist/shared/trace-subagent-links.js +4 -1
  68. package/dist/shared/trace-tool-identity.js +15 -8
  69. package/dist/signals/projector.js +28 -6
  70. package/dist/subagents/tool.js +7 -3
  71. package/docs/ops/vscode-extension-release.md +9 -1
  72. package/npm-shrinkwrap.json +2 -2
  73. package/package.json +2 -1
  74. package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +0 -228
  75. package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +0 -1
  76. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  77. package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.0.vsix +0 -0
@@ -79,6 +79,7 @@ function storedEventFromStreamEvent(event, piboSessionId, nextSequence, now) {
79
79
  eventId: typeof event.runId === "string" ? event.runId : undefined,
80
80
  toolCallId: event.toolCallId,
81
81
  toolName: event.toolName,
82
+ toolInvocationOrdinal: validOrdinal(event.toolInvocationOrdinal) ? event.toolInvocationOrdinal : undefined,
82
83
  args: event.args,
83
84
  ...(event.intent ? { intent: event.intent } : {}),
84
85
  };
@@ -94,6 +95,7 @@ function storedEventFromStreamEvent(event, piboSessionId, nextSequence, now) {
94
95
  eventId,
95
96
  toolCallId: event.toolCallId,
96
97
  toolName: event.toolName,
98
+ toolInvocationOrdinal: validOrdinal(event.toolInvocationOrdinal) ? event.toolInvocationOrdinal : undefined,
97
99
  args: event.args,
98
100
  partialResult: event.partialResult,
99
101
  ...(event.intent ? { intent: event.intent } : {}),
@@ -104,6 +106,7 @@ function storedEventFromStreamEvent(event, piboSessionId, nextSequence, now) {
104
106
  eventId,
105
107
  toolCallId: event.toolCallId,
106
108
  toolName: event.toolName,
109
+ toolInvocationOrdinal: validOrdinal(event.toolInvocationOrdinal) ? event.toolInvocationOrdinal : undefined,
107
110
  args: event.args,
108
111
  argsComplete: Boolean(event.argsComplete),
109
112
  ...(event.intent ? { intent: event.intent } : {}),
@@ -117,6 +120,7 @@ function storedEventFromStreamEvent(event, piboSessionId, nextSequence, now) {
117
120
  eventId: typeof event.runId === "string" ? event.runId : undefined,
118
121
  toolCallId: event.toolCallId,
119
122
  toolName: event.toolName,
123
+ toolInvocationOrdinal: validOrdinal(event.toolInvocationOrdinal) ? event.toolInvocationOrdinal : undefined,
120
124
  result: event.result,
121
125
  isError: Boolean(event.isError),
122
126
  ...(event.intent ? { intent: event.intent } : {}),
@@ -140,6 +144,12 @@ function makeStored(streamEvent, piboSessionId, type, payload, nextSequence, now
140
144
  const streamFrameIndex = typeof streamEvent.streamFrameIndex === "number" ? streamEvent.streamFrameIndex : undefined;
141
145
  const liveReplayId = typeof streamEvent.liveReplayId === "number" && Number.isFinite(streamEvent.liveReplayId) ? streamEvent.liveReplayId : undefined;
142
146
  const sequence = nextSequence();
147
+ const renderSequence = validSequence(streamEvent.renderSequence)
148
+ ? streamEvent.renderSequence
149
+ : validSequence(payload.renderSequence) ? payload.renderSequence : undefined;
150
+ const positionedPayload = renderSequence !== undefined && payload.renderSequence === undefined
151
+ ? { ...payload, renderSequence }
152
+ : payload;
143
153
  return {
144
154
  id: typeof streamEvent.streamId === "number"
145
155
  ? `stream:${streamEvent.streamId}:${streamFrameIndex ?? "raw"}:${type}`
@@ -150,15 +160,22 @@ function makeStored(streamEvent, piboSessionId, type, payload, nextSequence, now
150
160
  : `live:${sequence}:${type}`,
151
161
  piboSessionId,
152
162
  eventSequence: sequence,
163
+ renderSequence,
153
164
  streamId: typeof streamEvent.streamId === "number" ? streamEvent.streamId : undefined,
154
165
  streamFrameIndex,
155
166
  traceSource: "live",
156
- eventId: typeof payload.eventId === "string" ? payload.eventId : undefined,
167
+ eventId: typeof positionedPayload.eventId === "string" ? positionedPayload.eventId : undefined,
157
168
  type,
158
169
  createdAt: streamEventCreatedAt(streamEvent) ?? now(),
159
- payload,
170
+ payload: positionedPayload,
160
171
  };
161
172
  }
173
+ function validSequence(value) {
174
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
175
+ }
176
+ function validOrdinal(value) {
177
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
178
+ }
162
179
  function streamEventCreatedAt(event) {
163
180
  if (typeof event.createdAt !== "string" || !Number.isFinite(Date.parse(event.createdAt)))
164
181
  return undefined;
@@ -180,8 +197,10 @@ function eventGroupKey(event) {
180
197
  const payload = isRecord(event.payload) ? event.payload : {};
181
198
  const piboSessionId = typeof payload.piboSessionId === "string" ? payload.piboSessionId : event.piboSessionId ?? "";
182
199
  const eventId = typeof payload.eventId === "string" ? payload.eventId : event.eventId ?? "";
183
- if (typeof payload.toolCallId === "string")
184
- return `${piboSessionId}:${eventId}:tool:${payload.toolCallId}`;
200
+ if (typeof payload.toolCallId === "string") {
201
+ const ordinal = typeof payload.toolInvocationOrdinal === "number" ? payload.toolInvocationOrdinal : 0;
202
+ return `${piboSessionId}:${eventId}:tool:${payload.toolCallId}:${ordinal}`;
203
+ }
185
204
  const assistantIndex = typeof payload.assistantIndex === "number" ? payload.assistantIndex : undefined;
186
205
  const thinkingIndex = typeof payload.thinkingIndex === "number" ? payload.thinkingIndex : undefined;
187
206
  const contentIndex = typeof payload.contentIndex === "number" ? payload.contentIndex : 0;
@@ -19,41 +19,30 @@ function areTraceNodesSorted(nodes) {
19
19
  return true;
20
20
  }
21
21
  export function compareTraceNodes(left, right) {
22
- if (left.orderKey && right.orderKey && left.orderKey.sourceRank === right.orderKey.sourceRank) {
23
- const bySameSourceOrder = compareTraceOrder(left.orderKey, right.orderKey);
24
- if (bySameSourceOrder !== 0)
25
- return bySameSourceOrder;
22
+ if (!left.orderKey && !right.orderKey) {
23
+ return compareLegacyNodeFallback(left, right);
26
24
  }
27
- else {
28
- const bySameTurnPhase = compareSameTurnPhase(left, right);
29
- if (bySameTurnPhase !== 0)
30
- return bySameTurnPhase;
31
- }
32
- const byStartTime = compareOptionalIsoTime(left.startedAt, right.startedAt);
33
- if (byStartTime !== 0)
34
- return byStartTime;
35
25
  const byOrder = compareTraceOrder(left.orderKey, right.orderKey);
36
26
  if (byOrder !== 0)
37
27
  return byOrder;
38
28
  return left.id.localeCompare(right.id);
39
29
  }
40
- function compareSameTurnPhase(left, right) {
41
- if (!left.eventId || left.eventId !== right.eventId)
42
- return 0;
43
- const leftPhase = left.orderKey?.phaseRank;
44
- const rightPhase = right.orderKey?.phaseRank;
45
- if (leftPhase === undefined || rightPhase === undefined)
46
- return 0;
47
- return leftPhase - rightPhase;
30
+ function compareLegacyNodeFallback(left, right) {
31
+ const leftTime = validIsoTime(left.startedAt);
32
+ const rightTime = validIsoTime(right.startedAt);
33
+ if (leftTime !== rightTime) {
34
+ if (leftTime === undefined)
35
+ return 1;
36
+ if (rightTime === undefined)
37
+ return -1;
38
+ const byTime = leftTime.localeCompare(rightTime);
39
+ if (byTime !== 0)
40
+ return byTime;
41
+ }
42
+ return left.id.localeCompare(right.id);
48
43
  }
49
- function compareOptionalIsoTime(left, right) {
50
- if (!left && !right)
51
- return 0;
52
- if (!left)
53
- return 1;
54
- if (!right)
55
- return -1;
56
- return left.localeCompare(right);
44
+ function validIsoTime(value) {
45
+ return value !== undefined && Number.isFinite(Date.parse(value)) ? value : undefined;
57
46
  }
58
47
  export function flattenTraceNodes(nodes) {
59
48
  const flattened = [];
@@ -18,12 +18,14 @@ export const TRACE_PHASE_RANK = {
18
18
  "assistant.message": 8,
19
19
  error: 9,
20
20
  };
21
- export function historyTraceOrder(historyIndex, contentPartIndex, type, source) {
21
+ export function historyTraceOrder(historyIndex, contentPartIndex, type, source, createdAt) {
22
22
  return {
23
23
  sourceRank: TRACE_SOURCE_RANK[source],
24
24
  turnSeq: historyIndex,
25
25
  transcriptIndex: historyIndex,
26
+ eventSequence: source === "product-history" ? historyIndex : undefined,
26
27
  contentPartIndex,
28
+ chronologyMs: parseChronologyMs(createdAt),
27
29
  phaseRank: TRACE_PHASE_RANK[type],
28
30
  };
29
31
  }
@@ -78,12 +80,46 @@ export function compareTraceOrder(left, right) {
78
80
  return 1;
79
81
  if (!right)
80
82
  return -1;
81
- return (left.sourceRank - right.sourceRank ||
82
- left.turnSeq - right.turnSeq ||
83
+ const leftPosition = canonicalTracePosition(left);
84
+ const rightPosition = canonicalTracePosition(right);
85
+ return (compareOptionalChronology(left.chronologyMs, right.chronologyMs) ||
86
+ leftPosition - rightPosition ||
87
+ left.phaseRank - right.phaseRank ||
88
+ (left.contentPartIndex ?? 0) - (right.contentPartIndex ?? 0) ||
89
+ left.sourceRank - right.sourceRank ||
83
90
  (left.transcriptIndex ?? Number.MAX_SAFE_INTEGER) - (right.transcriptIndex ?? Number.MAX_SAFE_INTEGER) ||
84
91
  (left.eventSequence ?? Number.MAX_SAFE_INTEGER) - (right.eventSequence ?? Number.MAX_SAFE_INTEGER) ||
92
+ (left.renderSequence ?? Number.MAX_SAFE_INTEGER) - (right.renderSequence ?? Number.MAX_SAFE_INTEGER) ||
85
93
  (left.streamId ?? Number.MAX_SAFE_INTEGER) - (right.streamId ?? Number.MAX_SAFE_INTEGER) ||
86
94
  (left.streamFrameIndex ?? Number.MAX_SAFE_INTEGER) - (right.streamFrameIndex ?? Number.MAX_SAFE_INTEGER) ||
87
- (left.contentPartIndex ?? 0) - (right.contentPartIndex ?? 0) ||
88
- left.phaseRank - right.phaseRank);
95
+ left.turnSeq - right.turnSeq);
96
+ }
97
+ function parseChronologyMs(value) {
98
+ if (!value)
99
+ return undefined;
100
+ const parsed = Date.parse(value);
101
+ return Number.isFinite(parsed) ? parsed : undefined;
102
+ }
103
+ function compareOptionalChronology(left, right) {
104
+ if (left === right)
105
+ return 0;
106
+ if (left === undefined)
107
+ return 1;
108
+ if (right === undefined)
109
+ return -1;
110
+ return left - right;
111
+ }
112
+ /**
113
+ * chronologyMs is the common cross-source domain; renderSequence supplies its
114
+ * immutable first-visible time for modern stream segments. This canonical
115
+ * position is the deterministic tie-break domain for equal/missing chronology.
116
+ * The comparator never selects a field based on the other operand, keeping the
117
+ * relation total and transitive.
118
+ */
119
+ function canonicalTracePosition(order) {
120
+ return order.renderSequence
121
+ ?? order.eventSequence
122
+ ?? order.transcriptIndex
123
+ ?? order.streamId
124
+ ?? order.turnSeq;
89
125
  }
@@ -3,14 +3,7 @@ import { compareTraceOrder } from "./trace-order.js";
3
3
  export function mergeOlderTracePage(current, older) {
4
4
  if (current.piboSessionId !== older.piboSessionId)
5
5
  return current;
6
- const seenRawEvents = new Set();
7
- const rawEvents = [...older.rawEvents, ...current.rawEvents].filter((event) => {
8
- const key = event.id || `${event.eventSequence ?? ""}:${event.type}:${event.createdAt}`;
9
- if (seenRawEvents.has(key))
10
- return false;
11
- seenRawEvents.add(key);
12
- return true;
13
- });
6
+ const rawEvents = mergeTraceRawEvents(older.rawEvents, current.rawEvents, false);
14
7
  return {
15
8
  ...current,
16
9
  version: current.version,
@@ -30,7 +23,7 @@ export function mergeRefreshedTracePage(current, refreshed) {
30
23
  return {
31
24
  ...refreshed,
32
25
  nodes: mergeRefreshedTraceNodes(current.nodes, refreshed),
33
- rawEvents: mergeTraceRawEvents(current.rawEvents, refreshed.rawEvents),
26
+ rawEvents: mergeTraceRawEvents(current.rawEvents, refreshed.rawEvents, true),
34
27
  beforeCursor: current.beforeCursor,
35
28
  firstEventSequence: current.firstEventSequence ?? refreshed.firstEventSequence,
36
29
  nextBeforeSequence: current.nextBeforeSequence,
@@ -121,14 +114,14 @@ function parseTimestamp(value) {
121
114
  const parsed = Date.parse(value);
122
115
  return Number.isFinite(parsed) ? parsed : undefined;
123
116
  }
124
- function mergeTraceRawEvents(currentEvents, refreshedEvents) {
117
+ function mergeTraceRawEvents(currentEvents, refreshedEvents, replaceCoveredTail) {
125
118
  if (!refreshedEvents.length)
126
- return currentEvents;
127
- const firstRefreshedSequence = refreshedEvents.reduce((first, event) => {
119
+ return [...currentEvents].sort(compareTraceRawEvents);
120
+ const firstRefreshedSequence = replaceCoveredTail ? refreshedEvents.reduce((first, event) => {
128
121
  if (event.eventSequence === undefined)
129
122
  return first;
130
123
  return first === undefined ? event.eventSequence : Math.min(first, event.eventSequence);
131
- }, undefined);
124
+ }, undefined) : undefined;
132
125
  const byKey = new Map();
133
126
  for (const event of currentEvents) {
134
127
  if (firstRefreshedSequence !== undefined && event.eventSequence !== undefined && event.eventSequence >= firstRefreshedSequence)
@@ -137,7 +130,44 @@ function mergeTraceRawEvents(currentEvents, refreshedEvents) {
137
130
  }
138
131
  for (const event of refreshedEvents)
139
132
  byKey.set(traceRawEventKey(event), event);
140
- return [...byKey.values()];
133
+ return [...byKey.values()].sort(compareTraceRawEvents);
134
+ }
135
+ function compareTraceRawEvents(left, right) {
136
+ const leftSequenceDomain = left.eventSequence === undefined ? 0 : 1;
137
+ const rightSequenceDomain = right.eventSequence === undefined ? 0 : 1;
138
+ return leftSequenceDomain - rightSequenceDomain
139
+ || (left.eventSequence !== undefined && right.eventSequence !== undefined
140
+ ? left.eventSequence - right.eventSequence
141
+ : 0)
142
+ || compareOptionalString(validTimestamp(left.createdAt), validTimestamp(right.createdAt))
143
+ || compareOptionalNumber(left.streamId, right.streamId)
144
+ || compareOptionalNumber(left.streamFrameIndex, right.streamFrameIndex)
145
+ || (left.eventSequence === undefined && right.eventSequence === undefined
146
+ ? compareOptionalNumber(left.renderSequence, right.renderSequence)
147
+ : 0)
148
+ || left.id.localeCompare(right.id)
149
+ || left.type.localeCompare(right.type);
150
+ }
151
+ function compareOptionalNumber(left, right) {
152
+ if (left === right)
153
+ return 0;
154
+ if (left === undefined)
155
+ return 1;
156
+ if (right === undefined)
157
+ return -1;
158
+ return left - right;
159
+ }
160
+ function compareOptionalString(left, right) {
161
+ if (left === right)
162
+ return 0;
163
+ if (left === undefined)
164
+ return 1;
165
+ if (right === undefined)
166
+ return -1;
167
+ return left.localeCompare(right);
168
+ }
169
+ function validTimestamp(value) {
170
+ return Number.isFinite(Date.parse(value)) ? value : undefined;
141
171
  }
142
172
  function traceRawEventKey(event) {
143
173
  return event.id || `${event.eventSequence ?? ""}:${event.type}:${event.createdAt}`;
@@ -51,6 +51,7 @@ function traceNodeShallowEqual(left, right) {
51
51
  left.piboSessionId === right.piboSessionId &&
52
52
  left.eventId === right.eventId &&
53
53
  left.toolCallId === right.toolCallId &&
54
+ left.toolInvocationOrdinal === right.toolInvocationOrdinal &&
54
55
  left.runId === right.runId &&
55
56
  left.intent === right.intent &&
56
57
  left.type === right.type &&
@@ -75,6 +76,7 @@ function traceOrderKeyEqual(left, right) {
75
76
  return false;
76
77
  return (left.sourceRank === right.sourceRank &&
77
78
  left.turnSeq === right.turnSeq &&
79
+ left.renderSequence === right.renderSequence &&
78
80
  left.transcriptIndex === right.transcriptIndex &&
79
81
  left.contentPartIndex === right.contentPartIndex &&
80
82
  left.eventSequence === right.eventSequence &&
@@ -1,3 +1,4 @@
1
+ import { qualifiedToolNodeId } from "./trace-tool-identity.js";
1
2
  export function mapTraceChildSessionsByParent(sessions) {
2
3
  const result = new Map();
3
4
  for (const session of sessions) {
@@ -15,7 +16,9 @@ export function mapTraceSubagentSessionLinks(events) {
15
16
  const event = storedEvent.payload;
16
17
  if (event.type !== "subagent_session" || !event.toolCallId)
17
18
  continue;
18
- result.set(event.toolCallId, event.childPiboSessionId);
19
+ const eventId = event.eventId
20
+ ?? (event.renderSequence !== undefined ? `render:${event.renderSequence}` : "unscoped");
21
+ result.set(qualifiedToolNodeId(event.toolCallId, eventId, event.toolInvocationOrdinal ?? 0), event.childPiboSessionId);
19
22
  }
20
23
  return result;
21
24
  }
@@ -1,20 +1,25 @@
1
- import { TRACE_RECONCILIATION_ENTRY_CAP } from "./trace-limits.js";
2
- const QUALIFIED_TOOL_PREFIX = "history-tool:";
1
+ const QUALIFIED_TOOL_PREFIX = "tool-invocation:";
2
+ const LEGACY_QUALIFIED_TOOL_PREFIX = "history-tool:";
3
3
  const TOOL_PREFIX = "tool:";
4
4
  const TOOL_INVOCATION_TAG = "event-tool";
5
- export function qualifiedHistoryToolNodeId(toolCallId, eventId, invocationOrdinal) {
5
+ export function qualifiedToolNodeId(toolCallId, eventId, invocationOrdinal) {
6
6
  const projectionIdentity = JSON.stringify([TOOL_INVOCATION_TAG, eventId, invocationOrdinal]);
7
7
  return `${QUALIFIED_TOOL_PREFIX}${encodeURIComponent(JSON.stringify([toolCallId, projectionIdentity]))}`;
8
8
  }
9
+ /** @deprecated Use qualifiedToolNodeId for every projection source. */
10
+ export const qualifiedHistoryToolNodeId = qualifiedToolNodeId;
9
11
  export function parseTraceToolNodeIdentity(nodeId) {
10
12
  if (nodeId.startsWith(TOOL_PREFIX)) {
11
13
  const toolCallId = nodeId.slice(TOOL_PREFIX.length);
12
14
  return toolCallId ? { toolCallId } : undefined;
13
15
  }
14
- if (!nodeId.startsWith(QUALIFIED_TOOL_PREFIX))
16
+ const qualifiedPrefix = nodeId.startsWith(QUALIFIED_TOOL_PREFIX)
17
+ ? QUALIFIED_TOOL_PREFIX
18
+ : nodeId.startsWith(LEGACY_QUALIFIED_TOOL_PREFIX) ? LEGACY_QUALIFIED_TOOL_PREFIX : undefined;
19
+ if (!qualifiedPrefix)
15
20
  return undefined;
16
21
  try {
17
- const decoded = JSON.parse(decodeURIComponent(nodeId.slice(QUALIFIED_TOOL_PREFIX.length)));
22
+ const decoded = JSON.parse(decodeURIComponent(nodeId.slice(qualifiedPrefix.length)));
18
23
  if (!Array.isArray(decoded)
19
24
  || decoded.length !== 2
20
25
  || typeof decoded[0] !== "string"
@@ -28,14 +33,16 @@ export function parseTraceToolNodeIdentity(nodeId) {
28
33
  || typeof qualifier[1] !== "string"
29
34
  || qualifier[1].length === 0
30
35
  || !Number.isSafeInteger(qualifier[2])
31
- || Number(qualifier[2]) < 0
32
- || Number(qualifier[2]) >= TRACE_RECONCILIATION_ENTRY_CAP)
36
+ || Number(qualifier[2]) < 0)
33
37
  return undefined;
34
38
  const identity = {
35
39
  toolCallId: decoded[0],
36
40
  qualifier: { eventId: qualifier[1], invocationOrdinal: Number(qualifier[2]) },
37
41
  };
38
- return qualifiedHistoryToolNodeId(identity.toolCallId, identity.qualifier.eventId, identity.qualifier.invocationOrdinal) === nodeId ? identity : undefined;
42
+ const canonicalId = qualifiedToolNodeId(identity.toolCallId, identity.qualifier.eventId, identity.qualifier.invocationOrdinal);
43
+ return canonicalId === nodeId || canonicalId.replace(QUALIFIED_TOOL_PREFIX, LEGACY_QUALIFIED_TOOL_PREFIX) === nodeId
44
+ ? identity
45
+ : undefined;
39
46
  }
40
47
  catch {
41
48
  return undefined;
@@ -16,9 +16,22 @@ function runStatus(status) {
16
16
  return "error";
17
17
  return status;
18
18
  }
19
- function settleActiveSessionNodes(piboSessionId, context, status, terminalSource) {
19
+ function nodeBelongsToTurn(node, turnNodeId, context) {
20
+ let current = node;
21
+ const visited = new Set();
22
+ while (current && !visited.has(current.id)) {
23
+ if (current.id === turnNodeId || current.parentNodeId === turnNodeId)
24
+ return true;
25
+ visited.add(current.id);
26
+ current = current.parentNodeId ? context.getNode(current.parentNodeId) : undefined;
27
+ }
28
+ return false;
29
+ }
30
+ function settleActiveSessionNodes(piboSessionId, context, status, terminalSource, eventId) {
31
+ const turnNodeId = eventId ? `turn:${piboSessionId}:${eventId}` : undefined;
20
32
  return context.getSessionNodes(piboSessionId)
21
33
  .filter((node) => node.kind !== "session" && node.kind !== "yielded_run" && isActiveSignalStatus(node.status))
34
+ .filter((node) => !turnNodeId || nodeBelongsToTurn(node, turnNodeId, context))
22
35
  .filter((node) => terminalSource !== "processing_stopped" || node.kind !== "turn" || node.metadata?.accepted !== true)
23
36
  .map((node) => ({
24
37
  type: "patch_node",
@@ -210,12 +223,21 @@ export const outputSignalProducer = {
210
223
  mutations.push(...settleActiveSessionNodes(piboSessionId, context, "done", "message_finished"));
211
224
  }
212
225
  if (event.type === "session_error") {
213
- mutations.push({ type: "set_session_queue", piboSessionId, queuedMessages: 0 });
214
- mutations.push(...settleActiveSessionNodes(piboSessionId, context, "error", "session_error"));
215
- mutations.push({ type: "patch_node", nodeId: `session:${piboSessionId}`, patch: { status: "error", completedAt: context.now(), error: { message: event.error, source: "pi" } } });
226
+ const turnNodeId = event.eventId ? `turn:${piboSessionId}:${event.eventId}` : undefined;
227
+ const hasUnrelatedActiveTurn = Boolean(turnNodeId && context.getSessionNodes(piboSessionId)
228
+ .some((candidate) => candidate.kind === "turn" && candidate.id !== turnNodeId && isActiveSignalStatus(candidate.status)));
229
+ const error = { message: event.error, source: "pi" };
230
+ if (!hasUnrelatedActiveTurn)
231
+ mutations.push({ type: "set_session_queue", piboSessionId, queuedMessages: 0 });
232
+ mutations.push(...settleActiveSessionNodes(piboSessionId, context, "error", "session_error", hasUnrelatedActiveTurn ? event.eventId : undefined));
233
+ mutations.push({
234
+ type: "patch_node",
235
+ nodeId: `session:${piboSessionId}`,
236
+ patch: hasUnrelatedActiveTurn ? { error } : { status: "error", completedAt: context.now(), error },
237
+ });
216
238
  if (event.eventId) {
217
- mutations.push({ type: "patch_node", nodeId: `message:${piboSessionId}:${event.eventId}`, patch: { status: "error", completedAt: context.now(), error: { message: event.error, source: "pi" } } });
218
- mutations.push({ type: "patch_node", nodeId: `turn:${piboSessionId}:${event.eventId}`, patch: { status: "error", completedAt: context.now(), error: { message: event.error, source: "pi" }, metadata: { ...eventTurn?.metadata, terminalSource: "session_error" } } });
239
+ mutations.push({ type: "patch_node", nodeId: `message:${piboSessionId}:${event.eventId}`, patch: { status: "error", completedAt: context.now(), error } });
240
+ mutations.push({ type: "patch_node", nodeId: `turn:${piboSessionId}:${event.eventId}`, patch: { status: "error", completedAt: context.now(), error, metadata: { ...eventTurn?.metadata, terminalSource: "session_error" } } });
219
241
  }
220
242
  }
221
243
  return mutations;
@@ -77,7 +77,10 @@ export function formatAgentObservationsForModel(result) {
77
77
  const event = observation.kind === "tool"
78
78
  ? `${observation.eventType}${observation.toolName ? ` ${observation.toolName}` : ""}`
79
79
  : observation.eventType;
80
- lines.push("", `#${observation.sequence} ${scope} — ${event}${observation.isError ? " [error]" : ""}`);
80
+ const toolCall = observation.kind === "tool" && observation.toolCallId
81
+ ? `; toolCallId=${observation.toolCallId}`
82
+ : "";
83
+ lines.push("", `#${observation.sequence} ${scope} — ${event}${toolCall}${observation.isError ? " [error]" : ""}`);
81
84
  if (observation.text)
82
85
  lines.push(observation.text);
83
86
  }
@@ -186,13 +189,14 @@ export function createAgentToolDefinitions(subagents, controller) {
186
189
  description: [
187
190
  "Read completed delegated-agent messages with bounded cursor, identity, event, time, text, order, and limit filters.",
188
191
  "Default: the newest 20 completed assistant messages, with streaming deltas and tools hidden.",
189
- "Set includeTools=true to add compact tool calls and terminal results; set toolDetail=full only for bounded diagnostic inspection.",
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.",
190
193
  ].join("\n"),
191
- 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, toolDetail=full for bounded raw tool text, or eventTypes/kinds for explicit progress diagnostics. 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. 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.",
192
195
  executionMode: "parallel",
193
196
  annotations: { readOnly: true },
194
197
  inputSchema: Type.Object({
195
198
  requestIds: Type.Optional(Type.Array(Type.String({ description: "Exact yielded run/request ID" }), { maxItems: 50 })),
199
+ toolCallIds: Type.Optional(Type.Array(Type.String({ description: "Exact existing toolCallId. Multiple values use OR semantics and return only matching tool observations." }), { maxItems: 50 })),
196
200
  agentIds: Type.Optional(Type.Array(Type.String({ description: "Owned child agentId" }), { maxItems: 50 })),
197
201
  names: Type.Optional(Type.Array(piboStringEnum(names), { maxItems: 50 })),
198
202
  threadKeys: Type.Optional(Type.Array(Type.String(), { maxItems: 50 })),
@@ -151,7 +151,15 @@ The `files` whitelist in `package.json` controls what is published:
151
151
  - `docs/ops/**` — operator runbooks.
152
152
  - `README.md` and `src/mcp/LICENSE.mcp-cli`.
153
153
 
154
- The `.vsix` is **not** in the npm package. It lives on the GitHub Release and (after the maintainer uploads it) on the VS Code Marketplace.
154
+ The `.vsix` is **not** in the npm package. The `package.json#files` whitelist explicitly excludes `dist/apps/vscode-artifacts/**` even when the release workflow creates the versioned VSIX and `latest.vsix` before `npm publish`. Those files remain in the workspace for GitHub Release and Marketplace upload.
155
+
156
+ Verify the package boundary before publishing:
157
+
158
+ ```bash
159
+ npm pack --dry-run --json --ignore-scripts | node -e 'let input=""; process.stdin.on("data", chunk => input += chunk).on("end", () => { const files = JSON.parse(input)[0].files.map(file => file.path); if (files.some(file => file.startsWith("dist/apps/vscode-artifacts/"))) process.exit(1); })'
160
+ ```
161
+
162
+ The VSIX lives on the GitHub Release and (after the maintainer uploads it) on the VS Code Marketplace.
155
163
 
156
164
  ## Why the WebView bundle is in the npm package
157
165
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@pasko70/pibo",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@pasko70/pibo",
9
- "version": "3.0.0",
9
+ "version": "3.0.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.0.0",
3
+ "version": "3.0.2",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  "packages/workflows"
@@ -8,6 +8,7 @@
8
8
  "description": "Minimal TypeScript wrapper around Pi Coding Agent.",
9
9
  "files": [
10
10
  "dist",
11
+ "!dist/apps/vscode-artifacts/**",
11
12
  "context",
12
13
  "skills/builtin/**",
13
14
  "docs/ops/**",