@pasko70/pibo 3.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/compute-image/Dockerfile +47 -0
  2. package/compute-image/Dockerfile.dockerignore +5 -0
  3. package/dist/agent-runtime/contract.js +78 -28
  4. package/dist/agent-runtime/portable-history.js +19 -9
  5. package/dist/agent-runtime/registry.js +8 -2
  6. package/dist/agent-runtime/routed-session.js +12 -6
  7. package/dist/agent-runtimes/pi/routed-session.js +10 -4
  8. package/dist/apps/chat/agent-store.js +55 -3
  9. package/dist/apps/chat/data/project-service.js +119 -15
  10. package/dist/apps/chat/data/room-service.js +21 -0
  11. package/dist/apps/chat/loop-api.js +10 -2
  12. package/dist/apps/chat/web-app.js +84 -25
  13. package/dist/apps/chat-ui/assets/{dist-CvaPTBTN.js → dist-CUQJqggN.js} +1 -1
  14. package/dist/apps/chat-ui/assets/{dist-BPotHecb.js → dist-ClUlQWYN.js} +1 -1
  15. package/dist/apps/chat-ui/assets/{dist-BzqQKeVO.js → dist-Djul9BmZ.js} +1 -1
  16. package/dist/apps/chat-ui/assets/{dist-paagejNj.js → dist-GD0JGdCi.js} +1 -1
  17. package/dist/apps/chat-ui/assets/{dist-B6GZgWSj.js → dist-W5HOqHym.js} +1 -1
  18. package/dist/apps/chat-ui/assets/index-BcjkX-iP.js +228 -0
  19. package/dist/apps/chat-ui/assets/index-CmqRSbBU.css +1 -0
  20. package/dist/apps/chat-ui/index.html +2 -2
  21. package/dist/apps/chat-vscode-web/assets/index-JvrPUGvI.js +43 -0
  22. package/dist/apps/chat-vscode-web/index.html +1 -1
  23. package/dist/apps/cli-ui/InkSessionApp.js +111 -12
  24. package/dist/compute/cli.js +35 -16
  25. package/dist/compute/docker.js +341 -94
  26. package/dist/config/config.js +3 -0
  27. package/dist/core/session-router.js +33 -105
  28. package/dist/data/pibo-store.js +8 -1
  29. package/dist/data/schema.js +125 -5
  30. package/dist/debug/agents.js +91 -89
  31. package/dist/debug/index.js +2 -0
  32. package/dist/debug/pty.js +127 -48
  33. package/dist/gateway/cli.js +4 -0
  34. package/dist/loops/channel.js +3 -1
  35. package/dist/loops/cli.js +2 -1
  36. package/dist/loops/service.js +90 -11
  37. package/dist/loops/store.js +100 -74
  38. package/dist/mcp/agent-context.js +13 -26
  39. package/dist/mcp/commands/info.js +3 -1
  40. package/dist/mcp/config-command.js +46 -2
  41. package/dist/mcp/config.js +18 -4
  42. package/dist/plugins/context-files-store.js +6 -6
  43. package/dist/previews/base-url.js +34 -0
  44. package/dist/previews/config.js +2 -32
  45. package/dist/ralph/store.js +5 -0
  46. package/dist/reliability/store.js +174 -111
  47. package/dist/session-ui/terminalRows.js +2 -1
  48. package/dist/subagents/observation-query.js +121 -0
  49. package/dist/tools/agent-browser-leases.js +58 -23
  50. package/dist/tools/agent-browser-wrapper.js +1 -1
  51. package/dist/tools/browser-use-leases.js +129 -16
  52. package/dist/tools/browser-use-wrapper.js +1 -1
  53. package/dist/tools/index.js +27 -11
  54. package/dist/tools/python-runtime.js +10 -3
  55. package/dist/tools/registry.js +1 -1
  56. package/dist/tools/runtime/node-backend.js +16 -5
  57. package/dist/tools/runtime/node-worker-source.js +112 -33
  58. package/dist/tools/runtime/python-backend.js +16 -5
  59. package/dist/tools/runtime/python-worker-source.js +167 -16
  60. package/dist/tools/runtime/registry.js +23 -10
  61. package/dist/user-skills/store.js +0 -1
  62. package/npm-shrinkwrap.json +9 -2
  63. package/package.json +8 -2
  64. package/scripts/docker-entrypoint.sh +46 -0
  65. package/scripts/prepare-agent-browser-wrapper.sh +70 -0
  66. package/scripts/prepare-browser-use-wrapper.sh +255 -0
  67. package/dist/apps/chat-ui/assets/index-6JJV-eic.js +0 -228
  68. package/dist/apps/chat-ui/assets/index-CeL9JPP5.css +0 -1
  69. package/dist/apps/chat-vscode-web/assets/index-U-MSErGa.js +0 -43
@@ -2,11 +2,12 @@ import { randomUUID } from "node:crypto";
2
2
  import { isDeepStrictEqual } from "node:util";
3
3
  import { InitialSessionContext, } from "./profiles.js";
4
4
  import { createDefaultPiboPluginRegistry, createPiboProfileFromRegistryOrDefault, resolvePiboProfileNameFromRegistryOrDefault, selectDefaultPiboProfileName } from "../plugins/builtin.js";
5
- import { RuntimeRoutedSession as RoutedSession, } from "../agent-runtime/routed-session.js";
5
+ import { RUN_REMINDER_MAX_DURATION_MS, RuntimeRoutedSession as RoutedSession, } from "../agent-runtime/routed-session.js";
6
6
  import { runtimeSessionErrorDetails } from "./session-errors.js";
7
7
  import { OutputRenderSequencer, outputRenderHighWaterStore } from "./output-render-sequence.js";
8
8
  import { normalizePiboAgentSessionName, } from "../subagents/tool.js";
9
- import { PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES, PIBO_AGENT_OBSERVATION_DEFAULT_TOOL_EVENT_TYPES, normalizePiboAgentObservationCursor, normalizePiboAgentObservationLimit, normalizePiboAgentObservationOrder, normalizePiboAgentObservationToolDetail, parsePiboAgentObservationTimestamp, piboAgentObservationDetails, piboAgentObservationKind, piboAgentObservationRole, piboAgentObservationSourceFromEvent, piboAgentObservationText, piboAgentObservationToolSummary, } from "../subagents/observations.js";
9
+ import { piboAgentObservationDetails, piboAgentObservationKind, piboAgentObservationRole, piboAgentObservationSourceFromEvent, piboAgentObservationText, } from "../subagents/observations.js";
10
+ import { preparePiboAgentObservationQuery, selectPiboAgentObservationPage, } from "../subagents/observation-query.js";
10
11
  import { PiboRunRegistry } from "../runs/registry.js";
11
12
  import { PiboRunCancellationError, PiboRunCancelledError, PiboRunExecutionTimeoutError, waitForRunCancellationSettlement } from "../runs/lifecycle.js";
12
13
  import { PiboRunResourceLimitError } from "../runs/resource-isolation.js";
@@ -159,7 +160,11 @@ function formatRunReminderMessage(notification) {
159
160
  toolName: run.toolName,
160
161
  summary: run.summary,
161
162
  })),
162
- instruction: "Use pibo_run_read for completed, failed, or timed_out runs. Use pibo_run_wait, pibo_run_status, pibo_run_cancel, or pibo_run_ack for runs you still need to manage.",
163
+ instruction: [
164
+ `This autonomous run-reminder turn stops after ${RUN_REMINDER_MAX_DURATION_MS / 60_000} minutes of wall-clock time.`,
165
+ "Handle the listed runs promptly, then finish the turn. Do not start new subagents, yielded runs, or other long-running work from this reminder; leave larger follow-up work for a separate user-initiated or Goal continuation turn.",
166
+ "Use pibo_run_read for completed, failed, or timed_out runs. Use pibo_run_wait, pibo_run_status, pibo_run_cancel, or pibo_run_ack for runs you still need to manage.",
167
+ ].join(" "),
163
168
  }),
164
169
  "</pibo_run_notification>",
165
170
  ].join("\n");
@@ -425,9 +430,15 @@ export class PiboSessionRouter {
425
430
  this.signalRegistry.project({ type: "session_created", session });
426
431
  this.signalRegistry.project({ type: "pibo_output", event: recovery.event });
427
432
  }
433
+ const recoveredRunReminderControllers = new Set();
428
434
  for (const run of this.runRegistry.listAll({ includeConsumed: true, includeDetached: true })) {
429
435
  this.signalRegistry.project({ type: "run_changed", run, reason: "recovered" });
436
+ if (options.recoverInterruptedRuntimeState && isTerminalRunStatus(run.status) && this.sessionStore.get(run.controllerPiboSessionId)) {
437
+ recoveredRunReminderControllers.add(run.controllerPiboSessionId);
438
+ }
430
439
  }
440
+ for (const piboSessionId of recoveredRunReminderControllers)
441
+ this.scheduleRunReminder(piboSessionId, false);
431
442
  }
432
443
  subscribe(listener) {
433
444
  this.listeners.add(listener);
@@ -923,8 +934,12 @@ export class PiboSessionRouter {
923
934
  expectedRevision: input.expectedRevision,
924
935
  mode,
925
936
  });
926
- if (switchingRuntime)
927
- this.sessionStore.update(piboSessionId, { activeModel: null });
937
+ if (switchingRuntime) {
938
+ this.sessionStore.update(piboSessionId, {
939
+ activeModel: null,
940
+ metadata: withPiboSessionModelFallbacksMetadata(session.metadata, []),
941
+ });
942
+ }
928
943
  return structuredClone(persisted);
929
944
  }
930
945
  getSessionRuntimeStatus(piboSessionId) {
@@ -2069,105 +2084,15 @@ export class PiboSessionRouter {
2069
2084
  return session;
2070
2085
  }
2071
2086
  observeManagedAgents(parentPiboSessionId, input) {
2072
- const order = normalizePiboAgentObservationOrder(input.order);
2073
- const limit = normalizePiboAgentObservationLimit(input.limit);
2074
- const toolDetail = normalizePiboAgentObservationToolDetail(input.toolDetail);
2075
- const afterSequence = normalizePiboAgentObservationCursor(input.afterSequence);
2076
- const since = parsePiboAgentObservationTimestamp(input.since, "since");
2077
- const until = parsePiboAgentObservationTimestamp(input.until, "until");
2078
- if (since !== undefined && until !== undefined && since > until) {
2079
- throw new Error("Agent observation since must not be after until.");
2080
- }
2081
- const requestIds = input.requestIds ? new Set(input.requestIds) : undefined;
2082
- if (input.toolCallIds && input.toolCallIds.length > 50) {
2083
- throw new Error("Agent observation toolCallIds must contain at most 50 entries.");
2084
- }
2085
- const toolCallIds = input.toolCallIds ? new Set(input.toolCallIds) : undefined;
2086
- const agentIds = input.agentIds ? new Set(input.agentIds) : undefined;
2087
- if (agentIds) {
2088
- for (const agentId of agentIds)
2089
- this.requireManagedAgent(parentPiboSessionId, agentId);
2090
- }
2091
- const names = input.names ? new Set(input.names) : undefined;
2092
- const threadKeys = input.threadKeys ? new Set(input.threadKeys) : undefined;
2093
- const eventTypes = input.eventTypes ? new Set(input.eventTypes) : undefined;
2094
- const kinds = input.kinds ? new Set(input.kinds) : undefined;
2095
- const roles = input.roles ? new Set(input.roles) : undefined;
2096
- const textContains = input.textContains?.toLowerCase();
2097
- const defaultMessageView = eventTypes === undefined && kinds === undefined;
2098
- const explicitlySelectsTools = input.eventTypes?.some((eventType) => piboAgentObservationKind(eventType) === "tool") === true
2099
- || input.kinds?.includes("tool") === true
2100
- || toolCallIds !== undefined;
2101
- const includeTools = input.includeTools === true || (input.includeTools === undefined && explicitlySelectsTools);
2102
- const defaultEventTypes = includeTools
2103
- ? [...PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES, ...PIBO_AGENT_OBSERVATION_DEFAULT_TOOL_EVENT_TYPES]
2104
- : [...PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES];
2105
- const defaultEventTypeSet = new Set(defaultEventTypes);
2106
- const matches = this.agentObservations.filter((observation) => {
2107
- if (observation.managingParentId !== parentPiboSessionId)
2108
- return false;
2109
- if (requestIds && (!observation.requestId || !requestIds.has(observation.requestId)))
2110
- return false;
2111
- if (toolCallIds && (!observation.toolCallId || !toolCallIds.has(observation.toolCallId)))
2112
- return false;
2113
- if (agentIds && !agentIds.has(observation.agentId))
2114
- return false;
2115
- if (names && !names.has(observation.name))
2116
- return false;
2117
- if (threadKeys && (!observation.threadKey || !threadKeys.has(observation.threadKey)))
2118
- return false;
2119
- if (observation.kind === "tool" && !includeTools)
2120
- return false;
2121
- if (defaultMessageView && !defaultEventTypeSet.has(observation.eventType))
2122
- return false;
2123
- if (eventTypes && !eventTypes.has(observation.eventType))
2124
- return false;
2125
- if (kinds && !kinds.has(observation.kind))
2126
- return false;
2127
- if (roles && (!observation.role || !roles.has(observation.role)))
2128
- return false;
2129
- if (afterSequence !== undefined && observation.sequence <= afterSequence)
2130
- return false;
2131
- const createdAt = Date.parse(observation.createdAt);
2132
- if (since !== undefined && createdAt < since)
2133
- return false;
2134
- if (until !== undefined && createdAt > until)
2135
- return false;
2136
- if (textContains && !(observation.text ?? "").toLowerCase().includes(textContains))
2137
- return false;
2138
- return true;
2139
- });
2140
- const cursorPolling = afterSequence !== undefined;
2141
- const ordered = cursorPolling
2142
- ? matches.slice(0, limit)
2143
- : [...matches].sort((left, right) => order === "asc" ? left.sequence - right.sequence : right.sequence - left.sequence).slice(0, limit);
2144
- if (cursorPolling && order === "desc")
2145
- ordered.reverse();
2146
- const selected = ordered.map((observation) => {
2147
- const { managingParentId: _managingParentId, details, ...visible } = observation;
2148
- const compact = visible.kind === "tool" && toolDetail === "summary"
2149
- ? { ...visible, text: piboAgentObservationToolSummary(visible.text, visible.isError, details) }
2150
- : visible;
2151
- return input.includeDetails === true && details !== undefined ? { ...compact, details } : compact;
2152
- });
2153
- const evictedThrough = this.agentObservationEvictedThroughByParent.get(parentPiboSessionId) ?? 0;
2154
- const retentionTruncated = afterSequence === undefined ? evictedThrough > 0 : afterSequence < evictedThrough;
2155
- const nextAfterSequence = selected.reduce((maximum, observation) => Math.max(maximum, observation.sequence), afterSequence === undefined ? 0 : Math.max(afterSequence, evictedThrough));
2156
- return {
2157
- filters: {
2158
- ...input,
2159
- ...(defaultMessageView ? { eventTypes: defaultEventTypes } : {}),
2160
- ...(afterSequence !== undefined ? { afterSequence } : {}),
2161
- order,
2162
- limit,
2163
- includeTools,
2164
- toolDetail,
2165
- includeDetails: input.includeDetails === true,
2166
- },
2167
- observations: selected,
2168
- nextAfterSequence,
2169
- truncated: retentionTruncated || matches.length > selected.length,
2170
- };
2087
+ for (const agentId of input.agentIds ?? [])
2088
+ this.requireManagedAgent(parentPiboSessionId, agentId);
2089
+ const query = preparePiboAgentObservationQuery(input);
2090
+ const ordered = query.scanOrder === "asc"
2091
+ ? this.agentObservations
2092
+ : [...this.agentObservations].reverse();
2093
+ return selectPiboAgentObservationPage(ordered
2094
+ .filter((observation) => observation.managingParentId === parentPiboSessionId)
2095
+ .map(({ managingParentId: _managingParentId, ...observation }) => observation), query, { evictedThrough: this.agentObservationEvictedThroughByParent.get(parentPiboSessionId) ?? 0 });
2171
2096
  }
2172
2097
  async killManagedAgent(parentPiboSessionId, agentId) {
2173
2098
  const child = this.requireManagedAgent(parentPiboSessionId, agentId);
@@ -2615,7 +2540,10 @@ export class PiboSessionRouter {
2615
2540
  }
2616
2541
  }
2617
2542
  projectKnownSessionSignals() {
2618
- for (const session of this.sessionStore.list?.() ?? []) {
2543
+ const sessions = this.sessionStore.list?.() ?? [];
2544
+ const depthBySessionId = new Map(sessions.map((session) => [session.id, this.getSubagentDepth(session.id)]));
2545
+ sessions.sort((left, right) => (depthBySessionId.get(left.id) ?? 0) - (depthBySessionId.get(right.id) ?? 0));
2546
+ for (const session of sessions) {
2619
2547
  this.signalRegistry.project({ type: "session_created", session });
2620
2548
  }
2621
2549
  }
@@ -8,7 +8,7 @@ import { MessageStore } from "./message-store.js";
8
8
  import { NavigationStore } from "./navigation-store.js";
9
9
  import { ObservationStore } from "./observation-store.js";
10
10
  import { PayloadStore } from "./payload-store.js";
11
- import { applyPiboDataSchema } from "./schema.js";
11
+ import { applyPiboDataSchema, assertSupportedPiboDataSchemaVersion } from "./schema.js";
12
12
  import { TelemetryStore } from "./telemetry.js";
13
13
  import { SessionStore } from "./session-store.js";
14
14
  export class PiboDataStore {
@@ -27,6 +27,13 @@ export class PiboDataStore {
27
27
  if (this.path !== ":memory:")
28
28
  mkdirSync(dirname(this.path), { recursive: true });
29
29
  this.db = new DatabaseSync(this.path);
30
+ try {
31
+ assertSupportedPiboDataSchemaVersion(this.db);
32
+ }
33
+ catch (error) {
34
+ this.db.close();
35
+ throw error;
36
+ }
30
37
  if (insidePiboHome)
31
38
  protectPrivateFileSync(this.path);
32
39
  this.db.exec("PRAGMA busy_timeout = 5000");
@@ -1,4 +1,104 @@
1
1
  export const PIBO_DATA_SCHEMA_VERSION = 7;
2
+ const retiredScopeColumn = ["owner", "scope"].join("_");
3
+ const retiredScopeTables = [
4
+ {
5
+ name: "sessions",
6
+ columns: [
7
+ "id", "pi_session_id", "room_id", "root_session_id", "parent_id", "origin_id",
8
+ "channel", "kind", "profile", "active_model_json", "workspace", "title",
9
+ "first_message_preview", "status", "archived_at", "deleted_at", "metadata_json",
10
+ "created_at", "updated_at", "last_activity_at",
11
+ ],
12
+ definition: `
13
+ id TEXT PRIMARY KEY,
14
+ pi_session_id TEXT UNIQUE,
15
+ room_id TEXT,
16
+ root_session_id TEXT,
17
+ parent_id TEXT,
18
+ origin_id TEXT,
19
+ channel TEXT NOT NULL,
20
+ kind TEXT NOT NULL,
21
+ profile TEXT NOT NULL,
22
+ active_model_json TEXT,
23
+ workspace TEXT,
24
+ title TEXT NOT NULL DEFAULT 'Untitled Session',
25
+ first_message_preview TEXT,
26
+ status TEXT NOT NULL DEFAULT 'idle',
27
+ archived_at TEXT,
28
+ deleted_at TEXT,
29
+ metadata_json TEXT NOT NULL DEFAULT '{}',
30
+ created_at TEXT NOT NULL,
31
+ updated_at TEXT NOT NULL,
32
+ last_activity_at TEXT NOT NULL
33
+ `,
34
+ },
35
+ {
36
+ name: "rooms",
37
+ columns: [
38
+ "id", "name", "topic", "type", "parent_room_id", "workspace", "archived_at",
39
+ "retention_policy_id", "metadata_json", "created_at", "updated_at",
40
+ ],
41
+ definition: `
42
+ id TEXT PRIMARY KEY,
43
+ name TEXT NOT NULL,
44
+ topic TEXT,
45
+ type TEXT NOT NULL,
46
+ parent_room_id TEXT,
47
+ workspace TEXT,
48
+ archived_at TEXT,
49
+ retention_policy_id TEXT,
50
+ metadata_json TEXT NOT NULL DEFAULT '{}',
51
+ created_at TEXT NOT NULL,
52
+ updated_at TEXT NOT NULL
53
+ `,
54
+ },
55
+ {
56
+ name: "session_navigation",
57
+ columns: [
58
+ "room_id", "session_id", "root_session_id", "parent_id", "origin_id", "title",
59
+ "profile", "status", "archived_at", "last_activity_at", "last_message_preview",
60
+ "child_count", "sort_key", "updated_at",
61
+ ],
62
+ definition: `
63
+ room_id TEXT,
64
+ session_id TEXT PRIMARY KEY,
65
+ root_session_id TEXT,
66
+ parent_id TEXT,
67
+ origin_id TEXT,
68
+ title TEXT NOT NULL,
69
+ profile TEXT NOT NULL,
70
+ status TEXT NOT NULL,
71
+ archived_at TEXT,
72
+ last_activity_at TEXT NOT NULL,
73
+ last_message_preview TEXT,
74
+ child_count INTEGER NOT NULL DEFAULT 0,
75
+ sort_key TEXT NOT NULL,
76
+ updated_at TEXT NOT NULL
77
+ `,
78
+ },
79
+ ];
80
+ function retiredScopeTablesToRebuild(db) {
81
+ return retiredScopeTables.filter((table) => ((db.prepare(`SELECT 1 FROM pragma_table_info('${table.name}') WHERE name = ?`).get(retiredScopeColumn)) !== undefined));
82
+ }
83
+ function rebuildRetiredScopeTables(db, tablesToRebuild) {
84
+ for (const table of tablesToRebuild) {
85
+ const replacement = `__pibo_schema_v7_${table.name}`;
86
+ const columnList = table.columns.join(", ");
87
+ db.exec(`
88
+ CREATE TABLE ${replacement} (${table.definition});
89
+ INSERT INTO ${replacement} (${columnList}) SELECT ${columnList} FROM ${table.name};
90
+ DROP TABLE ${table.name};
91
+ ALTER TABLE ${replacement} RENAME TO ${table.name};
92
+ `);
93
+ }
94
+ }
95
+ export function assertSupportedPiboDataSchemaVersion(db) {
96
+ const version = Number(db.prepare("PRAGMA user_version").get()?.user_version ?? 0);
97
+ if (version > PIBO_DATA_SCHEMA_VERSION) {
98
+ throw new Error(`Pibo database schema version ${version} is newer than supported version ${PIBO_DATA_SCHEMA_VERSION}`);
99
+ }
100
+ return version;
101
+ }
2
102
  export const PIBO_DATA_SCHEMA_MIGRATION_STEPS = [
3
103
  "schema",
4
104
  "runtime-bindings",
@@ -11,11 +111,27 @@ export const PIBO_DATA_SCHEMA_MIGRATION_STEPS = [
11
111
  "user-version",
12
112
  ];
13
113
  export function applyPiboDataSchema(db, hooks = {}) {
114
+ const previousVersion = assertSupportedPiboDataSchemaVersion(db);
115
+ const tablesToRebuild = retiredScopeTablesToRebuild(db);
14
116
  const ownsTransaction = !db.isTransaction;
15
- if (ownsTransaction)
16
- db.exec("BEGIN IMMEDIATE");
117
+ if (!ownsTransaction && tablesToRebuild.length > 0) {
118
+ throw new Error("Pibo data schema migration requires an independent transaction");
119
+ }
120
+ const foreignKeysEnabled = ownsTransaction
121
+ && tablesToRebuild.length > 0
122
+ && Number(db.prepare("PRAGMA foreign_keys").get().foreign_keys ?? 0) === 1;
123
+ if (foreignKeysEnabled)
124
+ db.exec("PRAGMA foreign_keys = OFF");
17
125
  try {
18
- applyPiboDataSchemaInTransaction(db, hooks);
126
+ if (ownsTransaction)
127
+ db.exec("BEGIN IMMEDIATE");
128
+ applyPiboDataSchemaInTransaction(db, hooks, previousVersion, tablesToRebuild);
129
+ if (tablesToRebuild.length > 0) {
130
+ const violations = db.prepare("PRAGMA foreign_key_check").all();
131
+ if (violations.length > 0) {
132
+ throw new Error(`Pibo data schema migration would retain ${violations.length} foreign-key violation(s)`);
133
+ }
134
+ }
19
135
  if (ownsTransaction)
20
136
  db.exec("COMMIT");
21
137
  }
@@ -24,12 +140,16 @@ export function applyPiboDataSchema(db, hooks = {}) {
24
140
  db.exec("ROLLBACK");
25
141
  throw error;
26
142
  }
143
+ finally {
144
+ if (foreignKeysEnabled)
145
+ db.exec("PRAGMA foreign_keys = ON");
146
+ }
27
147
  }
28
- function applyPiboDataSchemaInTransaction(db, hooks) {
29
- const previousVersion = Number(db.prepare("PRAGMA user_version").get()?.user_version ?? 0);
148
+ function applyPiboDataSchemaInTransaction(db, hooks, previousVersion, tablesToRebuild) {
30
149
  const existingSessionCount = db.prepare("SELECT COUNT(*) AS count FROM sqlite_schema WHERE type = 'table' AND name = 'sessions'").get();
31
150
  const hadSessionsBeforeMigration = existingSessionCount.count > 0
32
151
  && Number(db.prepare("SELECT COUNT(*) AS count FROM sessions").get().count) > 0;
152
+ rebuildRetiredScopeTables(db, tablesToRebuild);
33
153
  db.exec(`
34
154
  CREATE TABLE IF NOT EXISTS sessions (
35
155
  id TEXT PRIMARY KEY,
@@ -1,4 +1,5 @@
1
- import { normalizePiboAgentObservationCursor, normalizePiboAgentObservationLimit, normalizePiboAgentObservationOrder, parsePiboAgentObservationTimestamp, piboAgentObservationDetails, piboAgentObservationKind, piboAgentObservationRole, piboAgentObservationText, } from "../subagents/observations.js";
1
+ import { normalizePiboAgentObservationLimit, piboAgentObservationDetails, piboAgentObservationKind, piboAgentObservationRole, piboAgentObservationText, } from "../subagents/observations.js";
2
+ import { preparePiboAgentObservationQuery, selectPiboAgentObservationPage, } from "../subagents/observation-query.js";
2
3
  import { createDebugPayloadStore, hydrateDebugEventRow } from "./persisted-payloads.js";
3
4
  import { eventAttributes, eventPayload } from "./payloads.js";
4
5
  import { openReadOnlyDebugDatabase, withStorePath } from "./sql.js";
@@ -36,17 +37,21 @@ export async function runDebugAgentsCli(args) {
36
37
  return;
37
38
  }
38
39
  const result = inspectDebugAgentObservations(parentPiboSessionId, store, {
40
+ toolCallIds: parsed.toolCallIds.length ? parsed.toolCallIds : undefined,
39
41
  agentIds: parsed.agentIds.length ? parsed.agentIds : undefined,
40
42
  names: parsed.names.length ? parsed.names : undefined,
41
43
  threadKeys: parsed.threadKeys.length ? parsed.threadKeys : undefined,
42
44
  eventTypes: parsed.eventTypes.length ? parsed.eventTypes : undefined,
43
45
  kinds: parsed.kinds.length ? parsed.kinds : undefined,
46
+ roles: parsed.roles.length ? parsed.roles : undefined,
44
47
  since: parsed.since,
45
48
  until: parsed.until,
46
49
  textContains: parsed.textContains,
47
50
  afterSequence: parsed.afterSequence,
48
51
  order: parsed.order,
49
52
  limit: parsed.limit,
53
+ includeTools: parsed.includeTools || undefined,
54
+ toolDetail: parsed.toolDetail,
50
55
  includeDetails: parsed.details,
51
56
  });
52
57
  if (parsed.json)
@@ -75,104 +80,70 @@ export function inspectDebugAgentObservations(parentPiboSessionId, store, input
75
80
  throw new Error(`Debug store "pibo-data" not found at ${store.path}`);
76
81
  const db = openReadOnlyDebugDatabase(store);
77
82
  try {
78
- const order = normalizePiboAgentObservationOrder(input.order);
79
- const limit = normalizePiboAgentObservationLimit(input.limit);
80
- const afterSequence = normalizePiboAgentObservationCursor(input.afterSequence);
81
- const since = parsePiboAgentObservationTimestamp(input.since, "since");
82
- const until = parsePiboAgentObservationTimestamp(input.until, "until");
83
- if (since !== undefined && until !== undefined && since > until)
84
- throw new Error("Agent observation since must not be after until.");
83
+ const query = preparePiboAgentObservationQuery(input);
85
84
  const owned = readOwnedAgents(db, parentPiboSessionId);
86
85
  const ownedById = new Map(owned.map((agent) => [agent.agentId, agent]));
87
86
  for (const agentId of input.agentIds ?? []) {
88
87
  if (!ownedById.has(agentId))
89
88
  throw new Error(`Agent "${agentId}" is not owned by Pibo session "${parentPiboSessionId}".`);
90
89
  }
91
- const agentIds = input.agentIds ? new Set(input.agentIds) : undefined;
92
- const names = input.names ? new Set(input.names) : undefined;
93
- const threadKeys = input.threadKeys ? new Set(input.threadKeys) : undefined;
94
- const eventTypes = input.eventTypes ? new Set(input.eventTypes) : undefined;
95
- const kinds = input.kinds ? new Set(input.kinds) : undefined;
96
90
  const payloadStore = createDebugPayloadStore(db, store);
97
91
  const clauses = ["s.parent_id = ?", "s.channel = 'pibo.subagents'", "s.kind = 'subagent'", "s.deleted_at IS NULL"];
98
92
  const values = [parentPiboSessionId];
99
- if (afterSequence !== undefined) {
93
+ if (query.afterSequence !== undefined) {
100
94
  clauses.push("e.stream_id > ?");
101
- values.push(afterSequence);
95
+ values.push(query.afterSequence);
96
+ }
97
+ if (query.scanEventTypes && query.scanEventTypes.length > 0) {
98
+ clauses.push(`e.type IN (${query.scanEventTypes.map(() => "?").join(", ")})`);
99
+ values.push(...query.scanEventTypes);
102
100
  }
103
- const scanOrder = afterSequence !== undefined ? "ASC" : order.toUpperCase();
104
101
  const statement = db.prepare(`
105
102
  SELECT e.stream_id, e.session_id, e.session_sequence, e.event_id, e.type, e.created_at,
106
103
  e.payload_ref, e.preview_text, e.attributes_json, s.profile, s.metadata_json
107
104
  FROM event_log e
108
105
  JOIN sessions s ON s.id = e.session_id
109
106
  WHERE ${clauses.join(" AND ")}
110
- ORDER BY e.stream_id ${scanOrder}
107
+ ORDER BY e.stream_id ${query.scanOrder.toUpperCase()}
111
108
  `);
112
- const textContains = input.textContains?.toLowerCase();
113
- const matches = [];
114
- for (const rawRow of statement.iterate(...values)) {
115
- const agent = ownedById.get(rawRow.session_id ?? "");
116
- if (!agent)
117
- continue;
118
- if (agentIds && !agentIds.has(agent.agentId))
119
- continue;
120
- if (names && !names.has(agent.name))
121
- continue;
122
- if (threadKeys && (!agent.threadKey || !threadKeys.has(agent.threadKey)))
123
- continue;
124
- if (eventTypes && !eventTypes.has(rawRow.type))
125
- continue;
126
- const kind = piboAgentObservationKind(rawRow.type);
127
- if (kinds && !kinds.has(kind))
128
- continue;
129
- const createdAt = Date.parse(rawRow.created_at);
130
- if (since !== undefined && createdAt < since)
131
- continue;
132
- if (until !== undefined && createdAt > until)
133
- continue;
134
- const row = hydrateDebugEventRow(rawRow, payloadStore);
135
- const attributes = eventAttributes(row);
136
- const payload = { ...attributes, ...eventPayload(row) };
137
- const source = debugAgentObservationSource(row, attributes);
138
- const text = piboAgentObservationText(source);
139
- if (textContains && !(text ?? "").toLowerCase().includes(textContains))
140
- continue;
141
- const role = piboAgentObservationRole(source);
142
- matches.push({
143
- streamId: row.stream_id,
144
- createdAt: row.created_at,
145
- agentId: agent.agentId,
146
- name: agent.name,
147
- ...(agent.threadKey ? { threadKey: agent.threadKey } : {}),
148
- eventType: row.type,
149
- kind,
150
- ...(role ? { role } : {}),
151
- ...(text ? { text } : {}),
152
- ...(typeof payload.toolName === "string" ? { toolName: payload.toolName } : {}),
153
- ...(typeof payload.toolCallId === "string" ? { toolCallId: payload.toolCallId } : {}),
154
- ...(row.type === "tool_execution_finished" ? { isError: payload.isError === true } : row.type === "session_error" ? { isError: true } : {}),
155
- ...(input.includeDetails === true ? { details: piboAgentObservationDetails(debugAgentObservationDetails(row, payload)) } : {}),
156
- });
157
- if (matches.length > limit)
158
- break;
109
+ function* normalizedObservations() {
110
+ for (const rawRow of statement.iterate(...values)) {
111
+ const agent = ownedById.get(rawRow.session_id ?? "");
112
+ if (!agent)
113
+ continue;
114
+ const row = hydrateDebugEventRow(rawRow, payloadStore);
115
+ const attributes = eventAttributes(row);
116
+ const payload = { ...attributes, ...eventPayload(row) };
117
+ const source = debugAgentObservationSource(row, attributes);
118
+ const text = piboAgentObservationText(source);
119
+ const role = piboAgentObservationRole(source);
120
+ yield {
121
+ sequence: row.stream_id,
122
+ createdAt: row.created_at,
123
+ agentId: agent.agentId,
124
+ name: agent.name,
125
+ ...(agent.threadKey ? { threadKey: agent.threadKey } : {}),
126
+ eventType: row.type,
127
+ kind: piboAgentObservationKind(row.type),
128
+ ...(role ? { role } : {}),
129
+ ...(text ? { text } : {}),
130
+ ...(typeof payload.toolName === "string" ? { toolName: payload.toolName } : {}),
131
+ ...(typeof payload.toolCallId === "string" ? { toolCallId: payload.toolCallId } : {}),
132
+ ...(row.type === "tool_execution_finished" ? { isError: payload.isError === true } : row.type === "session_error" ? { isError: true } : {}),
133
+ details: piboAgentObservationDetails(debugAgentObservationDetails(row, payload, source)),
134
+ };
135
+ }
159
136
  }
160
- const truncated = matches.length > limit;
161
- const observations = matches.slice(0, limit);
162
- if (afterSequence !== undefined && order === "desc")
163
- observations.reverse();
137
+ const page = selectPiboAgentObservationPage(normalizedObservations(), query);
164
138
  return {
165
139
  parentPiboSessionId,
166
- filters: {
167
- ...input,
168
- ...(afterSequence !== undefined ? { afterSequence } : {}),
169
- order,
170
- limit,
171
- includeDetails: input.includeDetails === true,
172
- },
173
- observations,
174
- nextAfterSequence: observations.reduce((maximum, observation) => Math.max(maximum, observation.streamId), afterSequence ?? 0),
175
- truncated,
140
+ filters: page.filters,
141
+ observations: page.observations.map(({ sequence, ...observation }) => ({
142
+ streamId: sequence,
143
+ ...observation,
144
+ })),
145
+ nextAfterSequence: page.nextAfterSequence,
146
+ truncated: page.truncated,
176
147
  };
177
148
  }
178
149
  catch (error) {
@@ -237,12 +208,15 @@ function debugAgentObservationSource(row, attributes) {
237
208
  source.subagentName = attributes.subagentName;
238
209
  return source;
239
210
  }
240
- function debugAgentObservationDetails(row, payload) {
211
+ function debugAgentObservationDetails(row, payload, source) {
241
212
  return {
242
213
  type: row.type,
243
214
  ...(row.session_id ? { piboSessionId: row.session_id } : {}),
244
215
  ...(row.event_id ? { eventId: row.event_id } : {}),
245
216
  ...payload,
217
+ ...(source.args !== undefined ? { args: source.args } : {}),
218
+ ...(source.partialResult !== undefined ? { partialResult: source.partialResult } : {}),
219
+ ...(source.result !== undefined ? { result: source.result } : {}),
246
220
  };
247
221
  }
248
222
  function parseObject(value) {
@@ -306,17 +280,31 @@ Filters use exact values. The command inspects only direct pibo.subagents childr
306
280
  console.log(`pibo debug agents ${parentPiboSessionId} observe
307
281
 
308
282
  Usage:
309
- pibo debug agents ${parentPiboSessionId} observe [--agent-id ps_...] [--name name] [--thread-key key]
310
- [--event-type type] [--kind message|thinking|tool|error|lifecycle|event]
311
- [--since iso] [--until iso] [--contains text] [--after-sequence n]
312
- [--order asc|desc] [--limit 1..200] [--details] [--json]
283
+ pibo debug agents ${parentPiboSessionId} observe [--tool-call-id id] [--agent-id ps_...] [--name name]
284
+ [--thread-key key] [--event-type type] [--kind message|thinking|tool|error|lifecycle|event]
285
+ [--role role] [--since iso] [--until iso] [--contains text] [--after-sequence n]
286
+ [--order asc|desc] [--limit 1..200] [--include-tools]
287
+ [--tool-detail summary|full] [--details] [--json]
313
288
 
314
- Repeat --agent-id, --name, --thread-key, --event-type, or --kind for OR within that field.
289
+ Default: the newest 20 completed assistant messages, with streaming deltas and tools hidden.
290
+ Use --include-tools for compact tool calls and terminal results. Explicit --event-type or --kind
291
+ filters retain access to progress events. Repeat plural filters for OR within that field.
315
292
  Different fields combine with AND. With --after-sequence, pages always consume the oldest unseen rows;
316
293
  --order desc reverses only the returned page, so nextAfterSequence remains safe for polling.`);
317
294
  }
318
295
  function parseAgentDebugOptions(args) {
319
- const parsed = { json: false, details: false, agentIds: [], names: [], threadKeys: [], eventTypes: [], kinds: [] };
296
+ const parsed = {
297
+ json: false,
298
+ details: false,
299
+ includeTools: false,
300
+ toolCallIds: [],
301
+ agentIds: [],
302
+ names: [],
303
+ threadKeys: [],
304
+ eventTypes: [],
305
+ kinds: [],
306
+ roles: [],
307
+ };
320
308
  for (let index = 0; index < args.length; index += 1) {
321
309
  const arg = args[index];
322
310
  if (arg === "--json") {
@@ -327,10 +315,16 @@ function parseAgentDebugOptions(args) {
327
315
  parsed.details = true;
328
316
  continue;
329
317
  }
318
+ if (arg === "--include-tools") {
319
+ parsed.includeTools = true;
320
+ continue;
321
+ }
330
322
  const value = args[index + 1];
331
323
  if (!value)
332
324
  throw new Error(`${arg} requires a value`);
333
- if (arg === "--agent-id")
325
+ if (arg === "--tool-call-id")
326
+ parsed.toolCallIds.push(value);
327
+ else if (arg === "--agent-id")
334
328
  parsed.agentIds.push(value);
335
329
  else if (arg === "--name")
336
330
  parsed.names.push(value);
@@ -343,6 +337,8 @@ function parseAgentDebugOptions(args) {
343
337
  throw new Error(`Invalid --kind "${value}"`);
344
338
  parsed.kinds.push(value);
345
339
  }
340
+ else if (arg === "--role")
341
+ parsed.roles.push(value);
346
342
  else if (arg === "--status") {
347
343
  if (!["running", "idle", "killed"].includes(value))
348
344
  throw new Error(`Invalid --status "${value}"`);
@@ -363,6 +359,11 @@ function parseAgentDebugOptions(args) {
363
359
  }
364
360
  else if (arg === "--limit")
365
361
  parsed.limit = normalizePiboAgentObservationLimit(parseNonNegativeInteger(value, arg));
362
+ else if (arg === "--tool-detail") {
363
+ if (value !== "summary" && value !== "full")
364
+ throw new Error(`Invalid --tool-detail "${value}"`);
365
+ parsed.toolDetail = value;
366
+ }
366
367
  else
367
368
  throw new Error(`Unknown pibo debug agents option "${arg}"`);
368
369
  index += 1;
@@ -373,10 +374,11 @@ function validateAgentDebugOptions(command, parsed) {
373
374
  if (command === "list") {
374
375
  if (parsed.names.length > 1)
375
376
  throw new Error("pibo debug agents list accepts at most one --name value");
376
- if (parsed.details || parsed.agentIds.length > 0 || parsed.threadKeys.length > 0 || parsed.eventTypes.length > 0
377
- || parsed.kinds.length > 0 || parsed.since !== undefined || parsed.until !== undefined
377
+ if (parsed.details || parsed.includeTools || parsed.toolCallIds.length > 0 || parsed.agentIds.length > 0
378
+ || parsed.threadKeys.length > 0 || parsed.eventTypes.length > 0 || parsed.kinds.length > 0
379
+ || parsed.roles.length > 0 || parsed.since !== undefined || parsed.until !== undefined
378
380
  || parsed.textContains !== undefined || parsed.afterSequence !== undefined || parsed.order !== undefined
379
- || parsed.limit !== undefined)
381
+ || parsed.limit !== undefined || parsed.toolDetail !== undefined)
380
382
  throw new Error("Unsupported option for pibo debug agents list. Run the list command with --help.");
381
383
  return;
382
384
  }