@pasko70/pibo 3.6.1 → 3.6.3

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 (45) hide show
  1. package/dist/agent-runtime/auth-contract.js +215 -0
  2. package/dist/agent-runtime/registry.js +1 -214
  3. package/dist/agent-runtime/routed-session.js +7 -0
  4. package/dist/agent-runtimes/codex-native/turn.js +2 -2
  5. package/dist/agent-runtimes/pi/auth.js +1 -0
  6. package/dist/agent-runtimes/pi/model-catalog.js +2 -0
  7. package/dist/agent-runtimes/pi/runtime.js +2 -0
  8. package/dist/apps/chat/trace-v2.js +1 -0
  9. package/dist/apps/chat/web-app.js +1 -0
  10. package/dist/apps/chat-ui/assets/{dist-YQ6IdwhV.js → dist-BHiMQOG6.js} +1 -1
  11. package/dist/apps/chat-ui/assets/{dist-CCgwGNBo.js → dist-BfNGI8zD.js} +1 -1
  12. package/dist/apps/chat-ui/assets/{dist-DFtTPSEB.js → dist-Cf5vD7GX.js} +1 -1
  13. package/dist/apps/chat-ui/assets/{dist-XSlRIuRd.js → dist-Ds-8WrKd.js} +1 -1
  14. package/dist/apps/chat-ui/assets/{dist-mpK-CrNR.js → dist-iE0b01WP.js} +1 -1
  15. package/dist/apps/chat-ui/assets/{index-B0BS_H88.js → index-BWhdVZpM.js} +87 -87
  16. package/dist/apps/chat-ui/assets/{index-DRaSMxCz.css → index-VZBlQTo5.css} +1 -1
  17. package/dist/apps/chat-ui/index.html +2 -2
  18. package/dist/apps/chat-vscode-web/assets/{index-CxKxukk_.js → index-CtaeZkpG.js} +6 -6
  19. package/dist/apps/chat-vscode-web/index.html +1 -1
  20. package/dist/apps/cli-ui/inkColors.js +1 -0
  21. package/dist/core/session-router.js +112 -3
  22. package/dist/data/chat-read-projections.js +46 -33
  23. package/dist/mcp/config.js +13 -48
  24. package/dist/providers/meta-muse.js +40 -0
  25. package/dist/runs/resource-isolation.js +3 -125
  26. package/dist/runs/windows-process-tree.js +132 -0
  27. package/dist/session-ui/terminalRows.js +106 -12
  28. package/dist/shared/trace-engine.js +2 -1
  29. package/dist/shared/trace-event-projection.js +80 -0
  30. package/dist/shared/trace-patch-nodes.js +14 -0
  31. package/dist/subagents/context.js +9 -3
  32. package/dist/subagents/observations.js +1 -1
  33. package/dist/subagents/tool.js +14 -6
  34. package/npm-shrinkwrap.json +2 -2
  35. package/package.json +1 -1
  36. package/packages/workflows/dist/runtime/adapter-node.d.ts.map +1 -1
  37. package/packages/workflows/dist/runtime/adapter-node.js +1 -13
  38. package/packages/workflows/dist/runtime/adapter-node.js.map +1 -1
  39. package/packages/workflows/dist/runtime/dispatch-failures.d.ts +1 -0
  40. package/packages/workflows/dist/runtime/dispatch-failures.d.ts.map +1 -1
  41. package/packages/workflows/dist/runtime/dispatch-failures.js +12 -0
  42. package/packages/workflows/dist/runtime/dispatch-failures.js.map +1 -1
  43. package/packages/workflows/dist/runtime/edge-transfer.d.ts.map +1 -1
  44. package/packages/workflows/dist/runtime/edge-transfer.js +1 -12
  45. package/packages/workflows/dist/runtime/edge-transfer.js.map +1 -1
@@ -0,0 +1,132 @@
1
+ import { execFile } from "node:child_process";
2
+ import { existsSync, readFileSync, unlinkSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { promisify } from "node:util";
5
+ const execFileAsync = promisify(execFile);
6
+ export function windowsProcessTreeCommand(command, pidPath) {
7
+ const portablePidPath = pidPath.replaceAll("\\", "/");
8
+ return [
9
+ "__pibo_win_pid=\"$(ps -W | awk -v p=$$ 'NR > 1 && $1 == p { print $4; exit }')\"",
10
+ "if [ -z \"$__pibo_win_pid\" ]; then printf '%s\\n' 'Pibo could not identify the Windows Bash process.' >&2; exit 125; fi",
11
+ `printf '%s %s' "$$" "$__pibo_win_pid" > ${bashSingleQuote(portablePidPath)}`,
12
+ "unset __pibo_win_pid",
13
+ command,
14
+ ].join("\n");
15
+ }
16
+ export async function terminateWindowsProcessTree(pidPath) {
17
+ const identity = await waitForWindowsProcessIdentity(pidPath);
18
+ if (!identity)
19
+ return;
20
+ try {
21
+ const rows = await listWindowsProcessTree(identity.msysPid);
22
+ for (const row of rows) {
23
+ try {
24
+ process.kill(row.windowsPid, "SIGKILL");
25
+ }
26
+ catch {
27
+ // The process may have exited between the snapshot and termination.
28
+ }
29
+ }
30
+ for (let attempt = 0; attempt < 100 && processIsAlive(identity.windowsPid); attempt += 1) {
31
+ await new Promise((resolveWait) => setTimeout(resolveWait, 25));
32
+ }
33
+ if (processIsAlive(identity.windowsPid)) {
34
+ throw new Error(`Windows yielded-run process tree rooted at PID ${identity.windowsPid} is still active after termination.`);
35
+ }
36
+ }
37
+ finally {
38
+ removeWindowsProcessIdentity(pidPath);
39
+ }
40
+ }
41
+ async function waitForWindowsProcessIdentity(pidPath) {
42
+ for (let attempt = 0; attempt < 80; attempt += 1) {
43
+ try {
44
+ const match = readFileSync(pidPath, "utf8").trim().match(/^([1-9]\d*)\s+([1-9]\d*)$/);
45
+ if (match)
46
+ return { msysPid: Number(match[1]), windowsPid: Number(match[2]) };
47
+ }
48
+ catch {
49
+ // The Bash wrapper may still be starting.
50
+ }
51
+ await new Promise((resolveWait) => setTimeout(resolveWait, 25));
52
+ }
53
+ return undefined;
54
+ }
55
+ async function listWindowsProcessTree(rootMsysPid) {
56
+ const { stdout } = await execFileAsync(windowsBashExecutable(), ["-lc", "ps -W"], {
57
+ encoding: "utf8",
58
+ timeout: 5_000,
59
+ windowsHide: true,
60
+ });
61
+ return parseWindowsProcessTreeSnapshot(stdout, rootMsysPid);
62
+ }
63
+ export function parseWindowsProcessTreeSnapshot(stdout, rootMsysPid) {
64
+ const rows = stdout.split(/\r?\n/).flatMap((line) => {
65
+ const fields = line.trim().split(/\s+/);
66
+ if (fields.length < 4 || !fields.slice(0, 4).every((field) => /^\d+$/.test(field)))
67
+ return [];
68
+ return [{
69
+ msysPid: Number(fields[0]),
70
+ parentMsysPid: Number(fields[1]),
71
+ processGroupId: Number(fields[2]),
72
+ windowsPid: Number(fields[3]),
73
+ }];
74
+ });
75
+ const selected = new Set([rootMsysPid]);
76
+ for (let changed = true; changed;) {
77
+ changed = false;
78
+ for (const row of rows) {
79
+ if (selected.has(row.msysPid))
80
+ continue;
81
+ if (row.processGroupId === rootMsysPid || selected.has(row.parentMsysPid)) {
82
+ selected.add(row.msysPid);
83
+ changed = true;
84
+ }
85
+ }
86
+ }
87
+ const byPid = new Map(rows.map((row) => [row.msysPid, row]));
88
+ return rows
89
+ .filter((row) => selected.has(row.msysPid) && row.windowsPid > 0)
90
+ .sort((left, right) => processTreeDepth(right, byPid) - processTreeDepth(left, byPid));
91
+ }
92
+ function processTreeDepth(row, byPid) {
93
+ let depth = 0;
94
+ let current = row;
95
+ const seen = new Set();
96
+ while (!seen.has(current.msysPid)) {
97
+ seen.add(current.msysPid);
98
+ const parent = byPid.get(current.parentMsysPid);
99
+ if (!parent)
100
+ break;
101
+ depth += 1;
102
+ current = parent;
103
+ }
104
+ return depth;
105
+ }
106
+ function windowsBashExecutable() {
107
+ const candidates = [
108
+ process.env.ProgramFiles ? join(process.env.ProgramFiles, "Git", "bin", "bash.exe") : undefined,
109
+ process.env["ProgramFiles(x86)"] ? join(process.env["ProgramFiles(x86)"], "Git", "bin", "bash.exe") : undefined,
110
+ ].filter((candidate) => Boolean(candidate));
111
+ return candidates.find((candidate) => existsSync(candidate)) ?? "bash.exe";
112
+ }
113
+ function processIsAlive(pid) {
114
+ try {
115
+ process.kill(pid, 0);
116
+ return true;
117
+ }
118
+ catch {
119
+ return false;
120
+ }
121
+ }
122
+ export function removeWindowsProcessIdentity(path) {
123
+ try {
124
+ unlinkSync(path);
125
+ }
126
+ catch {
127
+ // Best-effort transient process metadata cleanup.
128
+ }
129
+ }
130
+ function bashSingleQuote(value) {
131
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
132
+ }
@@ -23,14 +23,18 @@ export function buildCompactTerminalRows(traceView, options) {
23
23
  const candidates = syncThinkingToolRows(flatNodes.map((item) => createRowCandidate(item.node, item.turnId)));
24
24
  applyCompletedTurnTiming(candidates, turnById);
25
25
  const reconciled = reconcileConceptualRowCandidates(candidates);
26
- const toolDisplayMode = options.toolDisplayMode ?? "default";
27
- const rows = toolDisplayMode === "default" || toolDisplayMode === "slim"
28
- ? groupRelatedToolCandidates(reconciled, showToolDebugMetrics || toolDisplayMode === "slim").map((candidate) => candidate.row)
29
- : reconciled.map((candidate) => candidate.row);
26
+ const toolDisplayMode = options.toolDisplayMode ?? "full";
27
+ const rows = toolDisplayMode === "full"
28
+ ? groupRelatedToolCandidates(reconciled, showToolDebugMetrics).map((candidate) => candidate.row)
29
+ : toolDisplayMode === "default"
30
+ ? groupConsecutiveToolCandidates(reconciled).map((candidate) => candidate.row)
31
+ : toolDisplayMode === "slim"
32
+ ? groupRelatedToolCandidates(reconciled, true).map((candidate) => candidate.row)
33
+ : reconciled.map((candidate) => candidate.row);
30
34
  return applyToolDisplayMode(rows, toolDisplayMode);
31
35
  }
32
36
  function applyToolDisplayMode(rows, mode) {
33
- if (mode === "default")
37
+ if (mode === "default" || mode === "full")
34
38
  return rows;
35
39
  if (mode === "hide")
36
40
  return rows.filter((row) => !isToolDisplayRow(row));
@@ -40,11 +44,7 @@ function applyToolDisplayMode(rows, mode) {
40
44
  const intent = row.intent?.trim();
41
45
  if (mode === "intent" && !intent)
42
46
  return [];
43
- const slimRow = {
44
- ...row,
45
- lines: row.lines.slice(0, 1),
46
- singleLine: true,
47
- };
47
+ const slimRow = compactToolDisplayRow(row);
48
48
  if (mode !== "intent")
49
49
  return [slimRow];
50
50
  return [{
@@ -65,11 +65,19 @@ function applyToolDisplayMode(rows, mode) {
65
65
  }];
66
66
  });
67
67
  }
68
+ function compactToolDisplayRow(row) {
69
+ return {
70
+ ...row,
71
+ lines: row.lines.slice(0, 1),
72
+ singleLine: true,
73
+ };
74
+ }
68
75
  function isToolDisplayRow(row) {
69
76
  return row.sourceNodeIds.some((nodeId) => parseTraceToolNodeIdentity(nodeId) !== undefined)
70
77
  || row.id.startsWith("terminal:tool:")
71
78
  || row.kind === "tool.call"
72
79
  || row.kind === "tool.image"
80
+ || row.kind === "tool.group.calls"
73
81
  || row.kind === "tool.group.exploring"
74
82
  || row.kind === "tool.group.images"
75
83
  || row.kind === "agent.delegation";
@@ -387,6 +395,16 @@ function debugFields(node) {
387
395
  }
388
396
  function createUserMessageRow(node) {
389
397
  const text = stringValue(node.output) || stringValue(node.summary) || node.title;
398
+ const imagePreviews = node.fileAttachments?.flatMap((attachment, index) => {
399
+ if (!isImageFileAttachment(attachment.path, attachment.contentType))
400
+ return [];
401
+ return [{
402
+ id: `${node.id}:attachment:${index}`,
403
+ label: attachment.name,
404
+ path: attachment.path,
405
+ mimeType: attachment.contentType,
406
+ }];
407
+ });
390
408
  return {
391
409
  id: node.id,
392
410
  kind: "message.user",
@@ -399,8 +417,13 @@ function createUserMessageRow(node) {
399
417
  startedAt: node.startedAt,
400
418
  output: text,
401
419
  payloadRefs: node.payloadRefs,
420
+ imagePreviews: imagePreviews?.length ? imagePreviews : undefined,
402
421
  };
403
422
  }
423
+ function isImageFileAttachment(path, contentType) {
424
+ return contentType?.toLowerCase().startsWith("image/") === true
425
+ || /\.(?:avif|bmp|gif|jpe?g|png|svg|webp)$/i.test(path);
426
+ }
404
427
  function createAssistantMessageRow(node) {
405
428
  return {
406
429
  id: node.id,
@@ -548,7 +571,7 @@ function createImageToolRow(node, image) {
548
571
  lines: [
549
572
  {
550
573
  prefix: "bullet",
551
- tokens: [token(image.verb, toneForStatus(node.status), "semibold")],
574
+ tokens: [token(image.verb, node.status === "done" ? "purple" : toneForStatus(node.status), "semibold")],
552
575
  },
553
576
  {
554
577
  prefix: "detail",
@@ -943,6 +966,77 @@ function isThinkingOutput(value) {
943
966
  function isThinkingLevelSetOutput(value) {
944
967
  return isRecord(value) && value.action === "set_thinking_level";
945
968
  }
969
+ function groupConsecutiveToolCandidates(candidates) {
970
+ const grouped = [];
971
+ for (let index = 0; index < candidates.length; index += 1) {
972
+ const candidate = candidates[index];
973
+ const groupKind = defaultToolGroupKind(candidate);
974
+ if (!groupKind) {
975
+ grouped.push(candidate);
976
+ continue;
977
+ }
978
+ const run = [candidate];
979
+ let cursor = index + 1;
980
+ while (cursor < candidates.length && defaultToolGroupKind(candidates[cursor]) === groupKind && candidates[cursor].turnId === candidate.turnId) {
981
+ run.push(candidates[cursor]);
982
+ cursor += 1;
983
+ }
984
+ if (groupKind === "images") {
985
+ grouped.push(run.length === 1 ? candidate : { row: createImageGroup(run), turnId: candidate.turnId });
986
+ }
987
+ else if (run.length === 1) {
988
+ grouped.push({ ...candidate, row: compactToolDisplayRow(candidate.row) });
989
+ }
990
+ else {
991
+ grouped.push({ row: createToolCallGroup(run), turnId: candidate.turnId });
992
+ }
993
+ index = cursor - 1;
994
+ }
995
+ return grouped;
996
+ }
997
+ function defaultToolGroupKind(candidate) {
998
+ if (candidate.image)
999
+ return "images";
1000
+ if (candidate.row.kind === "tool.image" || candidate.row.kind === "tool.group.images")
1001
+ return undefined;
1002
+ return isBundledToolDisplayRow(candidate.row) ? "calls" : undefined;
1003
+ }
1004
+ function isBundledToolDisplayRow(row) {
1005
+ return isToolDisplayRow(row) && row.kind !== "agent.delegation";
1006
+ }
1007
+ function createToolCallGroup(candidates) {
1008
+ const groupRows = candidates.map((candidate) => compactToolDisplayRow(candidate.row));
1009
+ const firstRow = groupRows[0];
1010
+ const latestRow = groupRows[groupRows.length - 1];
1011
+ const hasError = groupRows.some((row) => row.status === "error");
1012
+ const status = groupRows.some((row) => row.status === "running")
1013
+ ? "running"
1014
+ : hasError
1015
+ ? "error"
1016
+ : "done";
1017
+ return {
1018
+ id: `group:tools:${firstRow.id}`,
1019
+ kind: "tool.group.calls",
1020
+ status,
1021
+ errorKind: hasError ? "tool" : undefined,
1022
+ lines: latestRow.lines.slice(0, 1),
1023
+ sourceNodeIds: groupRows.flatMap((row) => row.sourceNodeIds),
1024
+ isToolCall: true,
1025
+ toolMetrics: latestRow.toolMetrics,
1026
+ modelInferences: latestRow.modelInferences,
1027
+ title: latestRow.title,
1028
+ eventId: latestRow.eventId,
1029
+ runId: latestRow.runId,
1030
+ orderSource: firstRow.orderSource,
1031
+ orderStreamId: firstRow.orderStreamId,
1032
+ orderStreamFrameIndex: firstRow.orderStreamFrameIndex,
1033
+ startedAt: firstRow.startedAt,
1034
+ completedAt: latestRow.completedAt,
1035
+ expandable: true,
1036
+ singleLine: true,
1037
+ groupRows,
1038
+ };
1039
+ }
946
1040
  function groupRelatedToolCandidates(candidates, imagesOnly = false) {
947
1041
  const grouped = [];
948
1042
  for (let index = 0; index < candidates.length; index += 1) {
@@ -1042,7 +1136,7 @@ function createImageGroup(candidates) {
1042
1136
  lines: [
1043
1137
  {
1044
1138
  prefix: "bullet",
1045
- tokens: [token(status === "running" ? `Viewing ${detailItems.length} images` : status === "error" ? `${detailItems.length} image reads · error` : `${detailItems.length} Viewed ${detailItems.length === 1 ? "Image" : "Images"}`, toneForStatus(status), "semibold")],
1139
+ tokens: [token(status === "running" ? `Viewing ${detailItems.length} images` : status === "error" ? `${detailItems.length} image reads · error` : `${detailItems.length} Viewed ${detailItems.length === 1 ? "Image" : "Images"}`, status === "done" ? "purple" : toneForStatus(status), "semibold")],
1046
1140
  },
1047
1141
  ...visibleDetailItems.map((item, index) => ({
1048
1142
  prefix: index === 0 ? "detail" : "continuation",
@@ -1,5 +1,5 @@
1
1
  import { reconcileAsyncAgentRunStatuses } from "./trace-async-agent-runs.js";
2
- import { applySingleEventToNodes, dedupeTraceEvents, findOpenTranscriptEventIds, latestTraceStreamId, markIncompletePersistedTurns, mergeMessageTurnTimings, messageTurnTimingsFromEvents, reconcileTranscriptUserMessages, } from "./trace-event-projection.js";
2
+ import { applySingleEventToNodes, dedupeTraceEvents, findOpenTranscriptEventIds, latestTraceStreamId, markIncompletePersistedTurns, mergeMessageTurnTimings, messageTurnTimingsFromEvents, reconcileAcceptedUserMessageMetadata, reconcileTranscriptUserMessages, } from "./trace-event-projection.js";
3
3
  import { flattenTraceNodes, mapTraceNodesById, nestTraceNodes } from "./trace-nodes.js";
4
4
  import { mapTraceChildSessionsByParent, mapTraceSubagentSessionLinks, } from "./trace-subagent-links.js";
5
5
  import { projectHistoryEntries, traceNodesFromHistoryEntries } from "./trace-history.js";
@@ -43,6 +43,7 @@ export function buildTraceViewFromEvents(input) {
43
43
  for (const storedEvent of events) {
44
44
  applySingleEventToNodes(nodes, byId, input.session.id, storedEvent, childByParent, linkedChildByToolCallId, historyCoverage, openHistoryEventIds, sessionStatus);
45
45
  }
46
+ reconcileAcceptedUserMessageMetadata(nodes, events);
46
47
  const hasIncompleteTurns = markIncompletePersistedTurns(nodes, byId, input.session.id, events, turnTimings, sessionStatus);
47
48
  const nestedNodes = nestTraceNodes(nodes);
48
49
  reconcileAsyncAgentRunStatuses(nestedNodes);
@@ -7,6 +7,8 @@ import { qualifiedToolNodeId } from "./trace-tool-identity.js";
7
7
  import { observeCacheUsage } from "./cache-observability.js";
8
8
  import { compareInferenceCompletion } from "./model-inference-metrics.js";
9
9
  export function applySingleEventToNodes(nodes, byId, piboSessionId, storedEvent, childByParent, linkedChildByToolCallId, historyCoverage, openTranscriptEventIds, sessionStatus) {
10
+ if (applyAcceptedUserMessageMetadata(nodes, storedEvent))
11
+ return;
10
12
  const payload = storedEvent.payload;
11
13
  if (payload.type === "assistant_usage") {
12
14
  attachModelInferenceToLatestOutput(nodes, byId, payload, storedEvent);
@@ -127,6 +129,7 @@ export function applySingleEventToNodes(nodes, byId, piboSessionId, storedEvent,
127
129
  if (node.type === "user.message") {
128
130
  existing.status = node.status;
129
131
  existing.parentId = node.parentId ?? existing.parentId;
132
+ existing.fileAttachments = node.fileAttachments ?? existing.fileAttachments;
130
133
  existing.summary = node.summary ?? existing.summary;
131
134
  existing.output = node.output ?? existing.output;
132
135
  }
@@ -498,6 +501,82 @@ function normalizedUserMessageText(value) {
498
501
  const normalized = value?.replace(/\s+/g, " ").trim();
499
502
  return normalized || undefined;
500
503
  }
504
+ export function reconcileAcceptedUserMessageMetadata(nodes, events) {
505
+ for (const event of events)
506
+ applyAcceptedUserMessageMetadata(nodes, event);
507
+ }
508
+ function applyAcceptedUserMessageMetadata(nodes, storedEvent) {
509
+ const payload = storedEvent.payload;
510
+ if (!isRecord(payload) || payload.type !== "user.message.accepted")
511
+ return false;
512
+ const clientTxnId = typeof payload.clientTxnId === "string" ? payload.clientTxnId : undefined;
513
+ if (!clientTxnId)
514
+ return true;
515
+ const attachments = normalizeTraceFileAttachments(payload.fileAttachments, payload.fileAttachmentPaths);
516
+ if (!attachments.length)
517
+ return true;
518
+ const delivery = payload.delivery === "steer" ? "message_steered" : "message_queued";
519
+ const canonicalId = `event:${delivery}:${clientTxnId}`;
520
+ const target = flattenTraceNodes([...nodes]).find((node) => node.type === "user.message"
521
+ && (node.id === canonicalId || node.stableKey === canonicalId || node.eventId === clientTxnId));
522
+ if (target) {
523
+ target.fileAttachments = attachments;
524
+ const displayText = acceptedUserMessageDisplayText(payload);
525
+ if (displayText) {
526
+ target.summary = displayText;
527
+ target.output = displayText;
528
+ }
529
+ }
530
+ return true;
531
+ }
532
+ function acceptedUserMessageDisplayText(payload) {
533
+ if (typeof payload.userText === "string" && payload.userText.trim())
534
+ return payload.userText;
535
+ if (typeof payload.text !== "string")
536
+ return undefined;
537
+ let text = payload.text;
538
+ for (const key of ["fileAttachmentContext", "webAnnotationContext"]) {
539
+ const context = typeof payload[key] === "string" ? payload[key] : undefined;
540
+ if (!context || !text.trimEnd().endsWith(context))
541
+ continue;
542
+ text = text.trimEnd().slice(0, -context.length).trimEnd();
543
+ }
544
+ return text.trim() || undefined;
545
+ }
546
+ function traceFileAttachmentsFromMessageEvent(event) {
547
+ const record = event;
548
+ const attachments = normalizeTraceFileAttachments(record.fileAttachments, record.fileAttachmentPaths);
549
+ return attachments.length ? attachments : undefined;
550
+ }
551
+ function normalizeTraceFileAttachments(attachmentsValue, pathsValue) {
552
+ const attachments = Array.isArray(attachmentsValue) ? attachmentsValue.flatMap((value) => {
553
+ if (!isRecord(value) || typeof value.path !== "string" || !value.path.trim())
554
+ return [];
555
+ const path = value.path.trim();
556
+ return [{
557
+ path,
558
+ name: typeof value.name === "string" && value.name.trim() ? value.name.trim() : traceAttachmentName(path),
559
+ ...(typeof value.bytes === "number" && Number.isFinite(value.bytes) && value.bytes >= 0 ? { bytes: value.bytes } : {}),
560
+ ...(typeof value.contentType === "string" && value.contentType.trim() ? { contentType: value.contentType.trim() } : {}),
561
+ }];
562
+ }) : [];
563
+ if (attachments.length)
564
+ return attachments.slice(0, 10);
565
+ if (!Array.isArray(pathsValue))
566
+ return [];
567
+ return pathsValue.flatMap((value) => {
568
+ if (typeof value !== "string" || !value.trim())
569
+ return [];
570
+ const path = value.trim();
571
+ return [{ path, name: traceAttachmentName(path) }];
572
+ }).slice(0, 10);
573
+ }
574
+ function traceAttachmentName(path) {
575
+ return path.split(/[\\/]/).filter(Boolean).at(-1) ?? path;
576
+ }
577
+ function isRecord(value) {
578
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
579
+ }
501
580
  function traceNodeText(node) {
502
581
  if (typeof node.output === "string")
503
582
  return node.output;
@@ -654,6 +733,7 @@ function traceNodeFromEvent(piboSessionId, event, childByParent, linkedChildByTo
654
733
  title: "User Message",
655
734
  status: isOptimisticUserMessageEvent(event) ? "running" : "done",
656
735
  messageDeliveryState: isOptimisticUserMessageEvent(event) ? "sending" : undefined,
736
+ fileAttachments: traceFileAttachmentsFromMessageEvent(event),
657
737
  summary: event.text,
658
738
  output: event.text,
659
739
  };
@@ -60,6 +60,7 @@ function traceNodeShallowEqual(left, right) {
60
60
  left.startedAt === right.startedAt &&
61
61
  left.completedAt === right.completedAt &&
62
62
  left.durationMs === right.durationMs &&
63
+ traceFileAttachmentsEqual(left.fileAttachments, right.fileAttachments) &&
63
64
  left.toolMetrics?.durationMs === right.toolMetrics?.durationMs &&
64
65
  left.toolMetrics?.inputTokens === right.toolMetrics?.inputTokens &&
65
66
  left.toolMetrics?.outputTokens === right.toolMetrics?.outputTokens &&
@@ -74,6 +75,19 @@ function traceNodeShallowEqual(left, right) {
74
75
  left.stableKey === right.stableKey &&
75
76
  traceOrderKeyEqual(left.orderKey, right.orderKey));
76
77
  }
78
+ function traceFileAttachmentsEqual(left, right) {
79
+ if (left === right)
80
+ return true;
81
+ if (!left || !right || left.length !== right.length)
82
+ return false;
83
+ return left.every((attachment, index) => {
84
+ const other = right[index];
85
+ return attachment.name === other?.name
86
+ && attachment.path === other.path
87
+ && attachment.bytes === other.bytes
88
+ && attachment.contentType === other.contentType;
89
+ });
90
+ }
77
91
  function modelInferenceRecordsEqual(left, right) {
78
92
  if (left === right)
79
93
  return true;
@@ -27,7 +27,7 @@ export function getDelegatedAgentContextFile(subagents) {
27
27
  "```text",
28
28
  "pibo_run_start({",
29
29
  " toolName: \"pibo_agents_send_message\",",
30
- " arguments: { name, sessionName, message, threadKey? },",
30
+ " arguments: { name, sessionName, message, threadKey?, queue? },",
31
31
  " completionPolicy?: \"tracked\" | \"detached\"",
32
32
  "}) -> { runId }",
33
33
  "",
@@ -40,9 +40,15 @@ export function getDelegatedAgentContextFile(subagents) {
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 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.",
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.",
44
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.",
45
+ "## Steering and queue delivery",
46
+ "",
47
+ "When the reused child has an active turn that accepts steering, omit `queue` or set `queue: false`: Pibo sends the message into that active turn at the next steering boundary. Use this default for corrections, changed priorities, additional constraints, or information the child should apply before finishing its current work. When the child is idle, the same call automatically queues a normal new turn; steering cannot be forced while idle. Set `queue: true` only when you deliberately want a separate follow-up turn after the active turn finishes. The result reports the actual `delivery` as `steer` or `queue`. Cancelling a run after its steering message was accepted stops waiting for the shared active-turn result but cannot retract the steering message or cancel that active turn.",
48
+ "",
49
+ "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.",
50
+ "",
51
+ "Observe progress and decide whether to continue waiting, send default steering to an active child, set `queue: true` for a separate follow-up turn, cancel the request, or kill the child session.",
46
52
  "",
47
53
  "For substantial reports, ask the child to persist a Markdown artifact and include its path in the complete final message.",
48
54
  ].join("\n"),
@@ -3,7 +3,7 @@ export const PIBO_AGENT_OBSERVATION_TOOL_SUMMARY_MAX_BYTES = 768;
3
3
  export const PIBO_AGENT_OBSERVATION_DETAILS_MAX_BYTES = 32 * 1024;
4
4
  export const PIBO_AGENT_OBSERVATION_DEFAULT_LIMIT = 20;
5
5
  export const PIBO_AGENT_OBSERVATION_MAX_LIMIT = 200;
6
- export const PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES = ["assistant_message"];
6
+ export const PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES = ["assistant_message", "session_error"];
7
7
  export const PIBO_AGENT_OBSERVATION_DEFAULT_TOOL_EVENT_TYPES = ["tool_call", "tool_execution_finished"];
8
8
  export function piboAgentObservationSourceFromEvent(event) {
9
9
  const source = {
@@ -93,6 +93,7 @@ function normalizeAgentSendMessageResult(result, fallbackRequestId) {
93
93
  return {
94
94
  ...result,
95
95
  requestId: result.requestId?.trim() || fallbackRequestId,
96
+ delivery: result.delivery ?? "queue",
96
97
  finalMessage: typeof result.finalMessage === "string" ? result.finalMessage : result.reply.text,
97
98
  };
98
99
  }
@@ -114,11 +115,11 @@ export function createAgentToolDefinitions(subagents, controller) {
114
115
  name: "pibo_agents_send_message",
115
116
  title: "Pibo Agents Send Message",
116
117
  description: [
117
- "Yielded-only delegated send with a required sessionName. It must be a nonblank string of at most 40 Unicode code points and is trimmed before use. name selects the configured agent, sessionName is the human-readable child-session title, and threadKey controls conversation reuse. Invalid arguments fail before a run or child session is created. Start this tool through pibo_run_start; bounded waits do not limit the child lifetime.",
118
+ "Yielded-only delegated send with a required sessionName. It must be a nonblank string of at most 40 Unicode code points and is trimmed before use. name selects the configured agent, sessionName is the human-readable child-session title, and threadKey controls conversation reuse. By default, a reused child with an active steerable turn receives the message as steering; an idle child receives a queued turn. Set queue=true only when the message must run as a separate next turn even while the child is active. Steering is never attempted for an idle child. Invalid arguments fail before a run or child session is created. Start this tool through pibo_run_start; bounded waits do not limit the child lifetime.",
118
119
  "Available agents:",
119
120
  catalog,
120
121
  ].join("\n"),
121
- promptSnippet: "Start pibo_agents_send_message through pibo_run_start. Provide a nonblank sessionName of at most 40 Unicode code points on every call; Pibo trims it and rejects invalid input before creating a run. Follow-up calls update the reused child title without changing identity. Reuse threadKey to continue its child session, and use run wait/status/read/cancel plus agent observe for lifecycle control.",
122
+ promptSnippet: "Start pibo_agents_send_message through pibo_run_start. Reuse threadKey to address the same child session. Omit queue to steer its active turn automatically; if the child is idle, Pibo queues a normal turn instead. Set queue=true only when you deliberately want a separate follow-up turn after the active turn. You cannot force steering on an idle child. Provide a nonblank sessionName of at most 40 Unicode code points on every call; follow-up calls update the reused child title without changing identity. Use run wait/status/read/cancel plus agent observe for lifecycle control.",
122
123
  executionMode: "parallel",
123
124
  inputSchema: Type.Object({
124
125
  name: piboStringEnum(names, { description: "Configured delegated-agent selector; not the child title or reuse key" }),
@@ -133,6 +134,10 @@ export function createAgentToolDefinitions(subagents, controller) {
133
134
  description: "Stable reuse key for one delegated-agent conversation; independent of sessionName. Omit it to create a new child session.",
134
135
  maxLength: 256,
135
136
  })),
137
+ queue: Type.Optional(Type.Boolean({
138
+ description: "Force this message into the child's next-turn queue. Omit or set false to steer an active steerable child automatically; idle children are always queued.",
139
+ default: false,
140
+ })),
136
141
  }),
137
142
  prepareInput: preparePiboAgentToolInput,
138
143
  async execute(toolCallId, params, signal, _onUpdate, context) {
@@ -148,6 +153,7 @@ export function createAgentToolDefinitions(subagents, controller) {
148
153
  sessionName: preparedParams.sessionName,
149
154
  message: preparedParams.message,
150
155
  threadKey: preparedParams.threadKey,
156
+ queue: preparedParams.queue,
151
157
  toolCallId,
152
158
  requestId: context.yieldedRunId,
153
159
  parentProvenance: context.getActiveMessage?.()?.provenance,
@@ -156,7 +162,7 @@ export function createAgentToolDefinitions(subagents, controller) {
156
162
  return {
157
163
  content: [{
158
164
  type: "text",
159
- text: `Agent request ${result.requestId} completed (${result.name}, ${result.agentId}, thread ${result.threadKey}).\n\n${result.finalMessage}`,
165
+ text: `Agent request ${result.requestId} completed via ${result.delivery} (${result.name}, ${result.agentId}, thread ${result.threadKey}).\n\n${result.finalMessage}`,
160
166
  }],
161
167
  structuredContent: {
162
168
  status: "completed",
@@ -164,6 +170,8 @@ export function createAgentToolDefinitions(subagents, controller) {
164
170
  agentId: result.agentId,
165
171
  threadKey: result.threadKey,
166
172
  eventId: result.eventId,
173
+ delivery: result.delivery,
174
+ ...(result.activeEventId ? { activeEventId: result.activeEventId } : {}),
167
175
  finalMessage: result.finalMessage,
168
176
  },
169
177
  details: result,
@@ -190,11 +198,11 @@ export function createAgentToolDefinitions(subagents, controller) {
190
198
  name: "pibo_agents_observe",
191
199
  title: "Pibo Agents Observe",
192
200
  description: [
193
- "Read completed delegated-agent messages with bounded cursor, identity, event, time, substring, regex, order, and limit filters.",
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.",
201
+ "Read completed delegated-agent messages and session errors with bounded cursor, identity, event, time, substring, regex, order, and limit filters.",
202
+ "Default cursorMode=auto: the first equivalent query returns the newest 20 completed assistant messages and session errors; later calls return only unread observations. Streaming deltas, duplicate tool progress events, and tools stay hidden.",
195
203
  "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.",
196
204
  ].join("\n"),
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.",
205
+ promptSnippet: "Observe child progress through completed assistant messages and session errors. cursorMode=auto is the default and remembers each equivalent query, so repeated calls return only unread observations; 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.",
198
206
  executionMode: "parallel",
199
207
  annotations: { readOnly: true },
200
208
  inputSchema: Type.Object({
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@pasko70/pibo",
3
- "version": "3.6.1",
3
+ "version": "3.6.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@pasko70/pibo",
9
- "version": "3.6.1",
9
+ "version": "3.6.3",
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.6.1",
3
+ "version": "3.6.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1 +1 @@
1
- {"version":3,"file":"adapter-node.d.ts","sourceRoot":"","sources":["../../src/runtime/adapter-node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,aAAa,EACb,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,WAAW,EACX,oBAAoB,EACpB,aAAa,EACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAgB1D,MAAM,MAAM,kCAAkC,GAAG;IAC/C,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;IAC7C,GAAG,CAAC,EAAE,MAAM,IAAI,GAAG,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,aAAa,CAAC;IAC1C,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,SAAS,CAAC,EAAE,oBAAoB,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,EAAE,EAAE,IAAI,CAAC;IACT,GAAG,EAAE,WAAW,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,oBAAoB,EAAE,CAAC;IAC/B,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,EAAE,EAAE,KAAK,CAAC;IACV,GAAG,EAAE,WAAW,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,MAAM,EAAE,oBAAoB,EAAE,CAAC;IAC/B,WAAW,EAAE,kBAAkB,EAAE,CAAC;IAClC,KAAK,EAAE,oBAAoB,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,iCAAiC,GACzC,kCAAkC,GAClC,kCAAkC,CAAC;AAEvC,wBAAsB,2BAA2B,CAC/C,UAAU,EAAE,kBAAkB,EAC9B,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,kCAAkC,GAC1C,OAAO,CAAC,iCAAiC,CAAC,CAsK5C"}
1
+ {"version":3,"file":"adapter-node.d.ts","sourceRoot":"","sources":["../../src/runtime/adapter-node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,aAAa,EACb,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,WAAW,EACX,oBAAoB,EACpB,aAAa,EACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAiB1D,MAAM,MAAM,kCAAkC,GAAG;IAC/C,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;IAC7C,GAAG,CAAC,EAAE,MAAM,IAAI,GAAG,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,aAAa,CAAC;IAC1C,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,SAAS,CAAC,EAAE,oBAAoB,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,EAAE,EAAE,IAAI,CAAC;IACT,GAAG,EAAE,WAAW,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,oBAAoB,EAAE,CAAC;IAC/B,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,EAAE,EAAE,KAAK,CAAC;IACV,GAAG,EAAE,WAAW,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,MAAM,EAAE,oBAAoB,EAAE,CAAC;IAC/B,WAAW,EAAE,kBAAkB,EAAE,CAAC;IAClC,KAAK,EAAE,oBAAoB,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,iCAAiC,GACzC,kCAAkC,GAClC,kCAAkC,CAAC;AAEvC,wBAAsB,2BAA2B,CAC/C,UAAU,EAAE,kBAAkB,EAC9B,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,kCAAkC,GAC1C,OAAO,CAAC,iCAAiC,CAAC,CAsK5C"}
@@ -1,6 +1,6 @@
1
1
  import { resolveWorkflowAdapter } from "../registry/index.js";
2
2
  import { validateNodeOutput, validateWorkflowPortValue } from "../validation/index.js";
3
- import { adapterNodeDispatchFailure, failAdapterNodeDispatch, } from "./dispatch-failures.js";
3
+ import { adapterErrorSummaryFromCaught, adapterNodeDispatchFailure, failAdapterNodeDispatch, } from "./dispatch-failures.js";
4
4
  import { createWorkflowRuntimeId as createId } from "./ids.js";
5
5
  import { emitWorkflowRuntimeEvent, persistWorkflowNodeAttempt, persistWorkflowRun, } from "./persistence.js";
6
6
  import { createNodeScopedWorkflowRun, localStateSnapshotForNode } from "./state.js";
@@ -157,16 +157,4 @@ export async function dispatchWorkflowAdapterNode(definition, run, nodeId, input
157
157
  });
158
158
  }
159
159
  }
160
- function adapterErrorSummaryFromCaught(caught) {
161
- if (caught instanceof Error) {
162
- return {
163
- code: "WorkflowRuntimeError.adapterFailed",
164
- message: caught.message,
165
- };
166
- }
167
- return {
168
- code: "WorkflowRuntimeError.adapterFailed",
169
- message: "Workflow adapter failed with a non-Error value.",
170
- };
171
- }
172
160
  //# sourceMappingURL=adapter-node.js.map