@pasko70/pibo 1.9.13 → 1.10.1

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 (163) hide show
  1. package/README.md +183 -183
  2. package/context/codex-base-prompt.md +148 -148
  3. package/context/compute-worker.md +23 -23
  4. package/context/pibo-compaction-prompt.md +100 -100
  5. package/context/pibo-native-tooling.md +18 -18
  6. package/context/pibo-system-prompt.md +77 -77
  7. package/dist/apps/chat/agent-profiles.js +2 -2
  8. package/dist/apps/chat/agent-store.js +263 -250
  9. package/dist/apps/chat/chat-request-normalizers.js +17 -0
  10. package/dist/apps/chat/data/chat-data-mappers.js +2 -0
  11. package/dist/apps/chat/data/project-service.js +168 -168
  12. package/dist/apps/chat/data/read-state-service.js +18 -18
  13. package/dist/apps/chat/data/session-query-service.js +25 -25
  14. package/dist/apps/chat/data/timeline-query-service.js +19 -19
  15. package/dist/apps/chat/loop-api.js +181 -0
  16. package/dist/apps/chat/static-assets.js +854 -854
  17. package/dist/apps/chat/web-app.js +36 -16
  18. package/dist/apps/chat/workflow-manual-trigger-runtime.js +149 -46
  19. package/dist/apps/chat/workflow-persistence.js +255 -255
  20. package/dist/apps/chat-ui/assets/{dist-BZ2eTC4f.js → dist-3Sts0Afa.js} +1 -1
  21. package/dist/apps/chat-ui/assets/{dist-BHa-kcGl.js → dist-4NlLjLs7.js} +1 -1
  22. package/dist/apps/chat-ui/assets/{dist-yCYNNb5d.js → dist-BA6mhAec.js} +1 -1
  23. package/dist/apps/chat-ui/assets/{dist-D811wJeV.js → dist-BPl-fzRB.js} +1 -1
  24. package/dist/apps/chat-ui/assets/{dist-Dq4GxJi3.js → dist-BPnn9e2B.js} +1 -1
  25. package/dist/apps/chat-ui/assets/{dist-CzE6k3F3.js → dist-BpfBSMzK.js} +1 -1
  26. package/dist/apps/chat-ui/assets/{dist-CnVsqwSG.js → dist-Cvx5M97R.js} +1 -1
  27. package/dist/apps/chat-ui/assets/{dist-WyXdYl-w.js → dist-DnXWNQRm.js} +1 -1
  28. package/dist/apps/chat-ui/assets/{dist-BNMu92bb.js → dist-hU-2oAb6.js} +1 -1
  29. package/dist/apps/chat-ui/assets/{dist-BCB6zezO.js → dist-r31x5Fcc.js} +1 -1
  30. package/dist/apps/chat-ui/assets/{dist-HqTN67dc.js → dist-uF6UXzI7.js} +1 -1
  31. package/dist/apps/chat-ui/assets/index-CaTGYBOS.js +173 -0
  32. package/dist/apps/chat-ui/assets/{index-C0x9nEcf.css → index-al8DeEjA.css} +1 -1
  33. package/dist/apps/chat-ui/index.html +18 -18
  34. package/dist/apps/chat-ui/manifest.webmanifest +25 -25
  35. package/dist/apps/chat-ui/sw.js +47 -47
  36. package/dist/apps/chat-vscode-web/assets/index-CK4SMZuu.js +41 -0
  37. package/dist/apps/chat-vscode-web/index.html +13 -13
  38. package/dist/apps/cli-ui/cliSessionsCommand.js +23 -23
  39. package/dist/apps/context-files-ui/index.html +11 -11
  40. package/dist/cli-session/localSessionSource.js +34 -12
  41. package/dist/cli.js +61 -44
  42. package/dist/compute/cli.js +54 -54
  43. package/dist/compute/resource-health.js +35 -2
  44. package/dist/core/events.js +6 -1
  45. package/dist/core/routed-session.js +97 -5
  46. package/dist/core/runtime.js +6 -1
  47. package/dist/core/session-router.js +27 -8
  48. package/dist/cron/cli.js +15 -15
  49. package/dist/cron/store.js +49 -49
  50. package/dist/data/cli.js +23 -23
  51. package/dist/data/event-log.js +23 -23
  52. package/dist/data/ingest-service.js +3 -1
  53. package/dist/data/message-store.js +27 -27
  54. package/dist/data/navigation-store.js +9 -9
  55. package/dist/data/observation-store.js +4 -4
  56. package/dist/data/payload-store.js +17 -17
  57. package/dist/data/schema.js +433 -433
  58. package/dist/data/session-store.js +4 -4
  59. package/dist/data/telemetry-queries.js +54 -54
  60. package/dist/data/telemetry.js +197 -197
  61. package/dist/debug/events.js +12 -12
  62. package/dist/debug/failures.js +6 -6
  63. package/dist/debug/index.js +231 -227
  64. package/dist/debug/messages.js +6 -6
  65. package/dist/debug/pty.js +124 -124
  66. package/dist/debug/session.js +29 -29
  67. package/dist/debug/tools.js +5 -5
  68. package/dist/debug/trace.js +42 -7
  69. package/dist/debug/web-snapshot-browser-scripts.js +294 -294
  70. package/dist/debug/web-streaming-browser-library.js +925 -925
  71. package/dist/debug/web-streaming-browser-scripts.js +232 -232
  72. package/dist/debug/web-streaming-provider-telemetry.js +4 -4
  73. package/dist/debug/web.js +93 -93
  74. package/dist/gateway/cli.js +19 -19
  75. package/dist/gateway/server.js +4 -2
  76. package/dist/gateway/web.js +2 -2
  77. package/dist/index.js +1 -0
  78. package/dist/loops/accounting.js +19 -0
  79. package/dist/loops/channel.js +8 -0
  80. package/dist/loops/cli.js +224 -0
  81. package/dist/loops/plugin.js +16 -0
  82. package/dist/loops/prompts.js +86 -0
  83. package/dist/loops/service.js +446 -0
  84. package/dist/loops/stopping.js +170 -0
  85. package/dist/loops/store.js +642 -0
  86. package/dist/loops/templates.js +232 -0
  87. package/dist/loops/tools.js +184 -0
  88. package/dist/loops/types.js +1 -0
  89. package/dist/mcp/config-command.js +53 -53
  90. package/dist/mcp/index.js +21 -21
  91. package/dist/mcp/registry.js +11 -11
  92. package/dist/pi-packages/cli.js +11 -11
  93. package/dist/plugins/builtin.js +8 -0
  94. package/dist/plugins/context-files-store.js +110 -110
  95. package/dist/plugins/context-files.js +4 -4
  96. package/dist/plugins/registry.js +23 -12
  97. package/dist/ralph/cli.js +18 -18
  98. package/dist/ralph/templates.js +140 -140
  99. package/dist/reliability/store.js +256 -229
  100. package/dist/runs/lifecycle.js +59 -0
  101. package/dist/runs/registry.js +47 -1
  102. package/dist/runs/tools.js +31 -13
  103. package/dist/session-ui/terminalRows.js +66 -2
  104. package/dist/sessions/pibo-data-store.js +16 -16
  105. package/dist/sessions/sqlite-store.js +53 -53
  106. package/dist/setup/cli.js +58 -58
  107. package/dist/shared/trace-async-agent-runs.js +4 -4
  108. package/dist/shared/trace-event-projection.js +59 -19
  109. package/dist/shared/trace-nodes.js +5 -0
  110. package/dist/shared/trace-page-merge.js +15 -0
  111. package/dist/shared/trace-run-notifications.js +3 -1
  112. package/dist/signals/projector.js +6 -2
  113. package/dist/tools/agent-browser-wrapper.js +80 -80
  114. package/dist/tools/browser-pool.js +50 -0
  115. package/dist/tools/browser-use-cdp.js +12 -12
  116. package/dist/tools/browser-use-leases.js +12 -8
  117. package/dist/tools/browser-use-wrapper.js +762 -762
  118. package/dist/tools/guides.js +596 -538
  119. package/dist/tools/index.js +123 -99
  120. package/dist/tools/registry.js +21 -3
  121. package/dist/tools/runtime/node-worker-source.js +205 -205
  122. package/dist/tools/runtime/python-worker-source.js +177 -177
  123. package/dist/vscode/cli.js +9 -9
  124. package/dist/web-annotations/cdp.js +900 -900
  125. package/dist/web-annotations/store.js +96 -96
  126. package/docs/README.md +23 -23
  127. package/docs/ops/install-developer-host.md +112 -112
  128. package/docs/ops/install-user-host.md +96 -96
  129. package/docs/ops/upgrade-user-to-developer-host.md +69 -69
  130. package/docs/ops/vscode-extension-release.md +160 -160
  131. package/package.json +99 -95
  132. package/skills/builtin/graphify/SKILL.md +52 -52
  133. package/skills/builtin/loop/SKILL.md +78 -0
  134. package/skills/builtin/pi-agent-harness/SKILL.md +319 -319
  135. package/skills/builtin/pi-agent-harness/agents/openai.yaml +4 -4
  136. package/skills/builtin/pibo-docker-system/SKILL.md +170 -170
  137. package/skills/builtin/pibo-spec-writing/SKILL.md +330 -330
  138. package/skills/builtin/prd/SKILL.md +143 -143
  139. package/skills/builtin/ralph-loop/SKILL.md +361 -359
  140. package/skills/builtin/ralph-prd-json/SKILL.md +123 -123
  141. package/skills/builtin/skill-creator/LICENSE.txt +201 -201
  142. package/skills/builtin/skill-creator/SKILL.md +513 -513
  143. package/skills/builtin/skill-creator/agents/analyzer.md +274 -274
  144. package/skills/builtin/skill-creator/agents/comparator.md +202 -202
  145. package/skills/builtin/skill-creator/agents/grader.md +223 -223
  146. package/skills/builtin/skill-creator/assets/eval_review.html +146 -146
  147. package/skills/builtin/skill-creator/eval-viewer/generate_review.py +471 -471
  148. package/skills/builtin/skill-creator/eval-viewer/viewer.html +1325 -1325
  149. package/skills/builtin/skill-creator/references/schemas.md +430 -430
  150. package/skills/builtin/skill-creator/scripts/aggregate_benchmark.py +401 -401
  151. package/skills/builtin/skill-creator/scripts/generate_report.py +326 -326
  152. package/skills/builtin/skill-creator/scripts/improve_description.py +247 -247
  153. package/skills/builtin/skill-creator/scripts/package_skill.py +136 -136
  154. package/skills/builtin/skill-creator/scripts/quick_validate.py +102 -102
  155. package/skills/builtin/skill-creator/scripts/run_eval.py +310 -310
  156. package/skills/builtin/skill-creator/scripts/run_loop.py +328 -328
  157. package/skills/builtin/skill-creator/scripts/utils.py +47 -47
  158. package/skills/builtin/web-annotations/SKILL.md +93 -93
  159. package/src/mcp/LICENSE.mcp-cli +21 -21
  160. package/dist/apps/chat-ui/assets/index-DwHJfmiF.js +0 -173
  161. package/dist/apps/chat-vscode-web/assets/index-BAMxIaI_.js +0 -41
  162. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  163. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.9.13.vsix +0 -0
@@ -0,0 +1,59 @@
1
+ export class PiboRunExecutionTimeoutError extends Error {
2
+ timeoutPhase;
3
+ constructor(message, timeoutPhase) {
4
+ super(message);
5
+ this.timeoutPhase = timeoutPhase;
6
+ this.name = "PiboRunExecutionTimeoutError";
7
+ }
8
+ }
9
+ export function resolveRunTimeoutMs(toolName, params) {
10
+ if (!params || typeof params !== "object" || Array.isArray(params))
11
+ return undefined;
12
+ const input = params;
13
+ if (typeof input.timeoutMs === "number")
14
+ return normalizePositiveTimeout(input.timeoutMs, "arguments.timeoutMs");
15
+ if (toolName === "bash" && typeof input.timeout === "number")
16
+ return normalizePositiveTimeout(input.timeout * 1000, "arguments.timeout");
17
+ return undefined;
18
+ }
19
+ export function foregroundServiceWarning(toolName, params, timeoutMs) {
20
+ if (toolName !== "bash" || timeoutMs === undefined || !params || typeof params !== "object" || Array.isArray(params))
21
+ return undefined;
22
+ const command = typeof params.command === "string" ? String(params.command) : "";
23
+ if (!isKnownForegroundServiceCommand(command))
24
+ return undefined;
25
+ return `Known long-lived service command is running in a finite yielded run (${timeoutMs}ms). It can start successfully and later end as timed_out. Use the managed gateway CLI, or launch a detached/background service with a separate bounded health check.`;
26
+ }
27
+ export function isConfiguredTimeoutError(error) {
28
+ const message = error instanceof Error ? error.message : String(error);
29
+ return /(?:timed?\s*out|timeout)/i.test(message);
30
+ }
31
+ export function hasMeaningfulTimeoutOutput(value) {
32
+ const text = extractText(value);
33
+ if (!text)
34
+ return false;
35
+ return text.split(/\r?\n/).some((line) => {
36
+ const trimmed = line.trim();
37
+ return !!trimmed && !/(?:timed?\s*out|timeout|command exited with code|process exited with code)/i.test(trimmed);
38
+ });
39
+ }
40
+ function isKnownForegroundServiceCommand(command) {
41
+ return /(?:^|[;&|]\s*|\b)(?:pibo\s+gateway(?::web|\s+web|\s+dev)?(?:\s|$)|node\s+[^\n]*pibo(?:\.js)?\s+gateway:web(?:\s|$)|(?:\.\/)?(?:scripts\/)?docker-entrypoint\.sh\s+gateway:web(?:\s|$)|npm\s+(?:run\s+)?gateway(?::web)?(?:\s|$)|(?:npm\s+run\s+)?(?:dev|start)\s+--[^\n]*\bgateway(?::web)?\b|vite(?:\s|$))/i.test(command);
42
+ }
43
+ function normalizePositiveTimeout(value, field) {
44
+ if (!Number.isFinite(value) || value <= 0)
45
+ throw new Error(`${field} must be a positive number`);
46
+ return Math.max(1, Math.round(value));
47
+ }
48
+ function extractText(value) {
49
+ if (typeof value === "string")
50
+ return value;
51
+ if (!value || typeof value !== "object")
52
+ return "";
53
+ const candidate = value;
54
+ if (typeof candidate.text === "string")
55
+ return candidate.text;
56
+ if (!Array.isArray(candidate.content))
57
+ return "";
58
+ return candidate.content.map((part) => part && typeof part === "object" && typeof part.text === "string" ? String(part.text) : "").filter(Boolean).join("\n");
59
+ }
@@ -4,6 +4,14 @@ const DEFAULT_DETACHED_TERMINAL_TTL_MS = 60 * 1000;
4
4
  function now() {
5
5
  return new Date().toISOString();
6
6
  }
7
+ function runTimeoutAt(createdAt, timeoutMs) {
8
+ return timeoutMs === undefined ? undefined : new Date(Date.parse(createdAt) + timeoutMs).toISOString();
9
+ }
10
+ function formatTimeout(timeoutMs) {
11
+ if (timeoutMs === undefined)
12
+ return "its configured timeout";
13
+ return timeoutMs % 1000 === 0 ? `${timeoutMs / 1000}s timeout` : `${timeoutMs}ms timeout`;
14
+ }
7
15
  function snapshot(record) {
8
16
  const output = {
9
17
  runId: record.runId,
@@ -18,12 +26,20 @@ function snapshot(record) {
18
26
  };
19
27
  if (record.summary)
20
28
  output.summary = record.summary;
29
+ if (record.timeoutMs !== undefined)
30
+ output.timeoutMs = record.timeoutMs;
31
+ if (record.timeoutAt)
32
+ output.timeoutAt = record.timeoutAt;
33
+ if (record.timeoutPhase)
34
+ output.timeoutPhase = record.timeoutPhase;
35
+ if (record.serviceWarning)
36
+ output.serviceWarning = record.serviceWarning;
21
37
  if (record.completedAt)
22
38
  output.completedAt = record.completedAt;
23
39
  return output;
24
40
  }
25
41
  function terminal(status) {
26
- return status === "completed" || status === "failed" || status === "cancelled";
42
+ return status === "completed" || status === "failed" || status === "timed_out" || status === "cancelled";
27
43
  }
28
44
  export class PiboRunRegistry {
29
45
  options;
@@ -53,6 +69,8 @@ export class PiboRunRegistry {
53
69
  params: input.params,
54
70
  retryable: input.retryable ?? false,
55
71
  maxAttempts: input.maxAttempts ?? 1,
72
+ timeoutMs: input.timeoutMs,
73
+ serviceWarning: input.serviceWarning,
56
74
  });
57
75
  const record = recordFromStored(stored);
58
76
  this.runs.set(record.runId, record);
@@ -75,6 +93,8 @@ export class PiboRunRegistry {
75
93
  summary: `${input.toolName} run is running.`,
76
94
  retryable: input.retryable ?? false,
77
95
  maxAttempts: Math.max(1, input.maxAttempts ?? 1),
96
+ ...(input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs, timeoutAt: runTimeoutAt(timestamp, input.timeoutMs) } : {}),
97
+ ...(input.serviceWarning ? { serviceWarning: input.serviceWarning } : {}),
78
98
  };
79
99
  this.runs.set(runId, record);
80
100
  const output = snapshot(record);
@@ -113,6 +133,25 @@ export class PiboRunRegistry {
113
133
  this.notify({ type: "run_changed", run: output, previousStatus, reason: error });
114
134
  return output;
115
135
  }
136
+ timeOut(runId, error, timeoutPhase) {
137
+ const record = this.runs.get(runId);
138
+ if (!record || terminal(record.status))
139
+ return undefined;
140
+ const previousStatus = record.status;
141
+ record.status = "timed_out";
142
+ record.error = error;
143
+ record.timeoutPhase = timeoutPhase;
144
+ record.summary = timeoutPhase === "lifetime"
145
+ ? `${record.toolName} run started successfully, then reached ${formatTimeout(record.timeoutMs)}.`
146
+ : `${record.toolName} run reached ${formatTimeout(record.timeoutMs)} before startup was confirmed.`;
147
+ this.finish(record);
148
+ this.options.store?.updateRun(runId, record);
149
+ if (record.jobId)
150
+ this.options.store?.fail(record.jobId, `run-registry:${process.pid}`, error);
151
+ const output = snapshot(record);
152
+ this.notify({ type: "run_changed", run: output, previousStatus, reason: error });
153
+ return output;
154
+ }
116
155
  list(controllerPiboSessionId, options = {}) {
117
156
  this.prune();
118
157
  return [...this.runs.values()]
@@ -219,6 +258,7 @@ export class PiboRunRegistry {
219
258
  const notification = {
220
259
  completed: [],
221
260
  failed: [],
261
+ timedOut: [],
222
262
  cancelled: [],
223
263
  running: [],
224
264
  };
@@ -228,6 +268,8 @@ export class PiboRunRegistry {
228
268
  notification.completed.push(item);
229
269
  else if (record.status === "failed")
230
270
  notification.failed.push(item);
271
+ else if (record.status === "timed_out")
272
+ notification.timedOut.push(item);
231
273
  else if (record.status === "cancelled")
232
274
  notification.cancelled.push(item);
233
275
  else
@@ -356,5 +398,9 @@ function recordFromStored(record) {
356
398
  jobId: record.jobId,
357
399
  retryable: record.retryable,
358
400
  maxAttempts: record.maxAttempts,
401
+ timeoutMs: record.timeoutMs,
402
+ timeoutAt: record.timeoutAt,
403
+ timeoutPhase: record.timeoutPhase,
404
+ serviceWarning: record.serviceWarning,
359
405
  };
360
406
  }
@@ -1,5 +1,6 @@
1
1
  import { StringEnum, Type } from "@earendil-works/pi-ai";
2
2
  import { defineTool } from "@earendil-works/pi-coding-agent";
3
+ import { foregroundServiceWarning, hasMeaningfulTimeoutOutput, isConfiguredTimeoutError, PiboRunExecutionTimeoutError, resolveRunTimeoutMs } from "./lifecycle.js";
3
4
  function resultText(prefix, value) {
4
5
  return `${prefix}\n${JSON.stringify(value, null, 2)}`;
5
6
  }
@@ -30,7 +31,7 @@ export function createRunToolDefinitions(yieldableTools, controller) {
30
31
  defineTool({
31
32
  name: "pibo_run_start",
32
33
  label: "Pibo Run Start",
33
- description: "Start a yieldable tool as a yielded run. Use tracked when the result may matter later; use detached only for intentional fire-and-forget work.",
34
+ description: "Start a yieldable tool as a yielded run. The run records its configured timeout and classifies lifetime expiry separately from command failure. Use detached only for intentional fire-and-forget work.",
34
35
  promptSnippet: "Use pibo_run_start to run a yieldable tool in the background. It returns a runId. Use pibo_run_read for completed results and pibo_run_wait/status/list/cancel/ack to manage runs.",
35
36
  executionMode: "parallel",
36
37
  parameters: Type.Object({
@@ -43,25 +44,42 @@ export function createRunToolDefinitions(yieldableTools, controller) {
43
44
  }),
44
45
  async execute(toolCallId, params, signal, onUpdate, ctx) {
45
46
  const tool = requireTool(yieldableTools, params.toolName);
47
+ const timeoutMs = resolveRunTimeoutMs(tool.name, params.arguments);
48
+ const serviceWarning = foregroundServiceWarning(tool.name, params.arguments, timeoutMs);
49
+ let observedOutput = false;
46
50
  const run = controller.startToolRun({
47
51
  toolName: tool.name,
48
52
  params: params.arguments,
49
53
  completionPolicy: params.completionPolicy,
54
+ timeoutMs,
55
+ serviceWarning,
50
56
  async execute() {
51
- const result = await tool.execute(toolCallId, params.arguments, signal, onUpdate, ctx);
52
- const resultObject = result;
53
- const text = textFromToolResult(resultObject);
54
- if (resultObject.isError === true) {
55
- throw new Error(text ?? `${tool.name} returned an error result.`);
57
+ try {
58
+ const result = await tool.execute(toolCallId, params.arguments, signal, (update) => {
59
+ observedOutput ||= hasMeaningfulTimeoutOutput(update);
60
+ onUpdate?.(update);
61
+ }, ctx);
62
+ const resultObject = result;
63
+ const text = textFromToolResult(resultObject);
64
+ if (resultObject.isError === true) {
65
+ if (timeoutMs !== undefined && isConfiguredTimeoutError(text ?? ""))
66
+ throw new PiboRunExecutionTimeoutError(text ?? `${tool.name} timed out.`, observedOutput || hasMeaningfulTimeoutOutput(text) ? "lifetime" : "startup");
67
+ throw new Error(text ?? `${tool.name} returned an error result.`);
68
+ }
69
+ return { text, details: resultObject.details ?? result };
70
+ }
71
+ catch (error) {
72
+ if (error instanceof PiboRunExecutionTimeoutError)
73
+ throw error;
74
+ if (timeoutMs !== undefined && isConfiguredTimeoutError(error))
75
+ throw new PiboRunExecutionTimeoutError(error instanceof Error ? error.message : String(error), observedOutput ? "lifetime" : "startup");
76
+ throw error;
56
77
  }
57
- return {
58
- text,
59
- details: resultObject.details ?? result,
60
- };
61
78
  },
62
79
  });
80
+ const prefix = serviceWarning ? `Started yielded run ${run.runId}.\nWarning: ${serviceWarning}` : `Started yielded run ${run.runId}.`;
63
81
  return {
64
- content: [{ type: "text", text: resultText(`Started yielded run ${run.runId}.`, run) }],
82
+ content: [{ type: "text", text: resultText(prefix, run) }],
65
83
  details: run,
66
84
  };
67
85
  },
@@ -132,8 +150,8 @@ export function createRunToolDefinitions(yieldableTools, controller) {
132
150
  defineTool({
133
151
  name: "pibo_run_read",
134
152
  label: "Pibo Run Read",
135
- description: "Read the terminal result or error for a yielded run.",
136
- promptSnippet: "Use pibo_run_read to retrieve a completed or failed run result. Reading terminal tracked runs consumes reminders.",
153
+ description: "Read the terminal result, timeout reason, or error for a yielded run.",
154
+ promptSnippet: "Use pibo_run_read to retrieve a completed, failed, or timed_out run result. Reading terminal tracked runs consumes reminders.",
137
155
  executionMode: "parallel",
138
156
  parameters: Type.Object({
139
157
  runId: Type.String({ description: "Run id returned by pibo_run_start" }),
@@ -11,7 +11,7 @@ export function buildCompactTerminalRows(traceView, options) {
11
11
  .filter((item) => item.node.type !== "agent.turn" && (options.showThinking || item.node.type !== "model.reasoning"));
12
12
  const candidates = syncThinkingToolRows(flatNodes.map((item) => createRowCandidate(item.node, item.turnId)));
13
13
  applyCompletedTurnTiming(candidates, turnById);
14
- return groupRelatedToolCandidates(candidates).map((candidate) => candidate.row);
14
+ return groupRelatedToolCandidates(reconcileConceptualRowCandidates(candidates)).map((candidate) => candidate.row);
15
15
  }
16
16
  export function findActiveTurnStartedAt(traceView) {
17
17
  if (!traceView)
@@ -100,7 +100,71 @@ function createRowCandidate(node, turnId) {
100
100
  };
101
101
  break;
102
102
  }
103
- return { ...candidate, row: { ...candidate.row, ...debugFields(node) } };
103
+ return {
104
+ ...candidate,
105
+ row: {
106
+ ...candidate.row,
107
+ id: compactTerminalRowIdentity(node),
108
+ ...debugFields(node),
109
+ },
110
+ };
111
+ }
112
+ export function compactTerminalRowIdentity(node) {
113
+ if (node.type === "tool.call" || node.type === "tool.result" || node.type === "agent.delegation") {
114
+ const toolCallId = node.toolCallId ?? stableKeySuffix(node.stableKey, "tool:");
115
+ if (toolCallId)
116
+ return `terminal:tool:${toolCallId}`;
117
+ if (node.type === "agent.delegation" && node.linkedPiboSessionId) {
118
+ return `terminal:delegation:${node.linkedPiboSessionId}`;
119
+ }
120
+ }
121
+ if ((node.type === "agent.async" || node.type === "yielded.run") && node.runId) {
122
+ return `terminal:run:${node.runId}`;
123
+ }
124
+ if (node.type === "assistant.message") {
125
+ const assistantId = stableKeySuffix(node.stableKey, "assistant:");
126
+ if (assistantId)
127
+ return `terminal:assistant:${assistantId}`;
128
+ }
129
+ if (node.type === "model.reasoning") {
130
+ const reasoningId = stableKeySuffix(node.stableKey, "reasoning:");
131
+ if (reasoningId)
132
+ return `terminal:reasoning:${reasoningId}`;
133
+ }
134
+ return node.id;
135
+ }
136
+ function stableKeySuffix(stableKey, prefix) {
137
+ if (!stableKey?.startsWith(prefix))
138
+ return undefined;
139
+ const suffix = stableKey.slice(prefix.length);
140
+ return suffix && suffix !== "undefined" ? suffix : undefined;
141
+ }
142
+ function reconcileConceptualRowCandidates(candidates) {
143
+ const reconciled = [];
144
+ const indexById = new Map();
145
+ for (const candidate of candidates) {
146
+ const existingIndex = indexById.get(candidate.row.id);
147
+ if (existingIndex === undefined) {
148
+ indexById.set(candidate.row.id, reconciled.length);
149
+ reconciled.push(candidate);
150
+ continue;
151
+ }
152
+ const existing = reconciled[existingIndex];
153
+ reconciled[existingIndex] = {
154
+ ...candidate,
155
+ turnId: candidate.turnId ?? existing.turnId,
156
+ row: {
157
+ ...existing.row,
158
+ ...candidate.row,
159
+ sourceNodeIds: [...new Set([...existing.row.sourceNodeIds, ...candidate.row.sourceNodeIds])],
160
+ input: candidate.row.input ?? existing.row.input,
161
+ output: candidate.row.output ?? existing.row.output,
162
+ error: candidate.row.error ?? existing.row.error,
163
+ payloadRefs: { ...existing.row.payloadRefs, ...candidate.row.payloadRefs },
164
+ },
165
+ };
166
+ }
167
+ return reconciled;
104
168
  }
105
169
  function applyCompletedTurnTiming(candidates, turnById) {
106
170
  for (const turn of turnById.values()) {
@@ -53,20 +53,20 @@ export class PiboDataSessionStore {
53
53
  activeModel: input.activeModel === null ? undefined : input.activeModel ? { ...input.activeModel } : existing.activeModel,
54
54
  updatedAt: new Date().toISOString(),
55
55
  };
56
- this.db.prepare(`
57
- UPDATE sessions SET
58
- pi_session_id = ?,
59
- root_session_id = ?,
60
- parent_id = ?,
61
- origin_id = ?,
62
- profile = ?,
63
- active_model_json = ?,
64
- workspace = ?,
65
- title = ?,
66
- metadata_json = ?,
67
- updated_at = ?,
68
- last_activity_at = MAX(last_activity_at, ?)
69
- WHERE id = ? AND deleted_at IS NULL
56
+ this.db.prepare(`
57
+ UPDATE sessions SET
58
+ pi_session_id = ?,
59
+ root_session_id = ?,
60
+ parent_id = ?,
61
+ origin_id = ?,
62
+ profile = ?,
63
+ active_model_json = ?,
64
+ workspace = ?,
65
+ title = ?,
66
+ metadata_json = ?,
67
+ updated_at = ?,
68
+ last_activity_at = MAX(last_activity_at, ?)
69
+ WHERE id = ? AND deleted_at IS NULL
70
70
  `).run(updated.piSessionId, rootSessionId(updated), updated.parentId ?? null, updated.originId ?? null, updated.profile, updated.activeModel ? JSON.stringify(updated.activeModel) : null, updated.workspace ?? null, updated.title ?? "Untitled Session", JSON.stringify(updated.metadata ?? {}), updated.updatedAt, updated.updatedAt, id);
71
71
  return this.get(id);
72
72
  }
@@ -129,8 +129,8 @@ export class PiboDataSessionStore {
129
129
  "channel", "kind", "profile", "active_model_json", "workspace", "title", "first_message_preview",
130
130
  "status", "metadata_json", "created_at", "updated_at", "last_activity_at",
131
131
  ];
132
- this.db.prepare(`
133
- INSERT INTO sessions (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})
132
+ this.db.prepare(`
133
+ INSERT INTO sessions (${columns.join(", ")}) VALUES (${columns.map(() => "?").join(", ")})
134
134
  `).run(session.id, session.piSessionId, roomIdFromMetadata(session.metadata), rootSessionId(session), session.parentId ?? null, session.originId ?? null, session.channel, session.kind, session.profile, session.activeModel ? JSON.stringify(session.activeModel) : null, session.workspace ?? null, session.title ?? "Untitled Session", previewText(session.title ?? "") ?? null, "idle", JSON.stringify(session.metadata ?? {}), session.createdAt, session.updatedAt, session.updatedAt);
135
135
  }
136
136
  }
@@ -18,31 +18,31 @@ export class SqlitePiboSessionStore {
18
18
  this.ensureActiveModelColumn();
19
19
  }
20
20
  applySchema() {
21
- this.db.exec(`
22
- CREATE TABLE IF NOT EXISTS pibo_sessions (
23
- id TEXT PRIMARY KEY,
24
- pi_session_id TEXT NOT NULL UNIQUE,
25
- channel TEXT NOT NULL,
26
- kind TEXT NOT NULL,
27
- profile TEXT NOT NULL,
28
- parent_id TEXT,
29
- origin_id TEXT,
30
- workspace TEXT,
31
- title TEXT,
32
- metadata_json TEXT,
33
- active_model_json TEXT,
34
- created_at TEXT NOT NULL,
35
- updated_at TEXT NOT NULL,
36
- FOREIGN KEY(parent_id) REFERENCES pibo_sessions(id),
37
- FOREIGN KEY(origin_id) REFERENCES pibo_sessions(id)
38
- );
39
-
40
- CREATE INDEX IF NOT EXISTS idx_pibo_sessions_parent
41
- ON pibo_sessions(parent_id, updated_at);
42
- CREATE INDEX IF NOT EXISTS idx_pibo_sessions_origin
43
- ON pibo_sessions(origin_id, updated_at);
44
- CREATE INDEX IF NOT EXISTS idx_pibo_sessions_channel_kind
45
- ON pibo_sessions(channel, kind, updated_at);
21
+ this.db.exec(`
22
+ CREATE TABLE IF NOT EXISTS pibo_sessions (
23
+ id TEXT PRIMARY KEY,
24
+ pi_session_id TEXT NOT NULL UNIQUE,
25
+ channel TEXT NOT NULL,
26
+ kind TEXT NOT NULL,
27
+ profile TEXT NOT NULL,
28
+ parent_id TEXT,
29
+ origin_id TEXT,
30
+ workspace TEXT,
31
+ title TEXT,
32
+ metadata_json TEXT,
33
+ active_model_json TEXT,
34
+ created_at TEXT NOT NULL,
35
+ updated_at TEXT NOT NULL,
36
+ FOREIGN KEY(parent_id) REFERENCES pibo_sessions(id),
37
+ FOREIGN KEY(origin_id) REFERENCES pibo_sessions(id)
38
+ );
39
+
40
+ CREATE INDEX IF NOT EXISTS idx_pibo_sessions_parent
41
+ ON pibo_sessions(parent_id, updated_at);
42
+ CREATE INDEX IF NOT EXISTS idx_pibo_sessions_origin
43
+ ON pibo_sessions(origin_id, updated_at);
44
+ CREATE INDEX IF NOT EXISTS idx_pibo_sessions_channel_kind
45
+ ON pibo_sessions(channel, kind, updated_at);
46
46
  `);
47
47
  }
48
48
  ensureActiveModelColumn() {
@@ -60,22 +60,22 @@ export class SqlitePiboSessionStore {
60
60
  create(input) {
61
61
  const session = createPiboSession(input);
62
62
  this.db
63
- .prepare(`
64
- INSERT INTO pibo_sessions (
65
- id,
66
- pi_session_id,
67
- channel,
68
- kind,
69
- profile,
70
- parent_id,
71
- origin_id,
72
- workspace,
73
- title,
74
- metadata_json,
75
- active_model_json,
76
- created_at,
77
- updated_at
78
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
63
+ .prepare(`
64
+ INSERT INTO pibo_sessions (
65
+ id,
66
+ pi_session_id,
67
+ channel,
68
+ kind,
69
+ profile,
70
+ parent_id,
71
+ origin_id,
72
+ workspace,
73
+ title,
74
+ metadata_json,
75
+ active_model_json,
76
+ created_at,
77
+ updated_at
78
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
79
79
  `)
80
80
  .run(session.id, session.piSessionId, session.channel, session.kind, session.profile, session.parentId ?? null, session.originId ?? null, session.workspace ?? null, session.title ?? null, JSON.stringify(session.metadata ?? {}), session.activeModel ? JSON.stringify(session.activeModel) : null, session.createdAt, session.updatedAt);
81
81
  const created = this.get(session.id);
@@ -100,18 +100,18 @@ export class SqlitePiboSessionStore {
100
100
  updatedAt: new Date().toISOString(),
101
101
  };
102
102
  this.db
103
- .prepare(`
104
- UPDATE pibo_sessions SET
105
- pi_session_id = ?,
106
- profile = ?,
107
- parent_id = ?,
108
- origin_id = ?,
109
- workspace = ?,
110
- title = ?,
111
- metadata_json = ?,
112
- active_model_json = ?,
113
- updated_at = ?
114
- WHERE id = ?
103
+ .prepare(`
104
+ UPDATE pibo_sessions SET
105
+ pi_session_id = ?,
106
+ profile = ?,
107
+ parent_id = ?,
108
+ origin_id = ?,
109
+ workspace = ?,
110
+ title = ?,
111
+ metadata_json = ?,
112
+ active_model_json = ?,
113
+ updated_at = ?
114
+ WHERE id = ?
115
115
  `)
116
116
  .run(updated.piSessionId, updated.profile, updated.parentId ?? null, updated.originId ?? null, updated.workspace ?? null, updated.title ?? null, JSON.stringify(updated.metadata ?? {}), updated.activeModel ? JSON.stringify(updated.activeModel) : null, updated.updatedAt, id);
117
117
  return this.get(id);
package/dist/setup/cli.js CHANGED
@@ -23,89 +23,89 @@ function shellQuote(value) {
23
23
  }
24
24
  function serviceUnit(options) {
25
25
  const gatewayPortEnv = options.serviceKind === "dev" ? `Environment=PIBO_GATEWAY_DEV_PORT=${options.webPort}\n` : `Environment=PIBO_GATEWAY_WEB_PORT=${options.webPort}\n`;
26
- return `[Unit]
27
- Description=${options.description}
28
- After=network-online.target
29
- Wants=network-online.target
30
-
31
- [Service]
32
- Type=simple
33
- WorkingDirectory=${options.workingDirectory}
34
- Environment=HOME=/root
35
- Environment=PIBO_HOME=${options.piboHome}
36
- Environment=NODE_ENV=production
37
- ${gatewayPortEnv}ExecStart=${options.execStart}
38
- Restart=always
39
- RestartSec=5
40
- KillSignal=SIGTERM
41
- TimeoutStopSec=30
42
-
43
- [Install]
44
- WantedBy=multi-user.target
26
+ return `[Unit]
27
+ Description=${options.description}
28
+ After=network-online.target
29
+ Wants=network-online.target
30
+
31
+ [Service]
32
+ Type=simple
33
+ WorkingDirectory=${options.workingDirectory}
34
+ Environment=HOME=/root
35
+ Environment=PIBO_HOME=${options.piboHome}
36
+ Environment=NODE_ENV=production
37
+ ${gatewayPortEnv}ExecStart=${options.execStart}
38
+ Restart=always
39
+ RestartSec=5
40
+ KillSignal=SIGTERM
41
+ TimeoutStopSec=30
42
+
43
+ [Install]
44
+ WantedBy=multi-user.target
45
45
  `;
46
46
  }
47
47
  function devStartWrapper(options) {
48
- return `#!/usr/bin/env node
49
- import { runWebGatewayServer } from ${JSON.stringify(`${options.repoDir}/dist/gateway/web.js`)};
50
-
51
- await runWebGatewayServer({
52
- host: "127.0.0.1",
53
- port: ${options.gatewayPort},
54
- web: {
55
- host: "127.0.0.1",
56
- port: ${options.webPort},
57
- },
58
- });
48
+ return `#!/usr/bin/env node
49
+ import { runWebGatewayServer } from ${JSON.stringify(`${options.repoDir}/dist/gateway/web.js`)};
50
+
51
+ await runWebGatewayServer({
52
+ host: "127.0.0.1",
53
+ port: ${options.gatewayPort},
54
+ web: {
55
+ host: "127.0.0.1",
56
+ port: ${options.webPort},
57
+ },
58
+ });
59
59
  `;
60
60
  }
61
61
  function caddyfile(options) {
62
62
  const blocks = [];
63
63
  if (options.prodDomain) {
64
- blocks.push(`${options.prodDomain} {
65
- encode zstd gzip
66
- reverse_proxy 127.0.0.1:${options.prodPort}
64
+ blocks.push(`${options.prodDomain} {
65
+ encode zstd gzip
66
+ reverse_proxy 127.0.0.1:${options.prodPort}
67
67
  }`);
68
68
  }
69
69
  if (options.prodWwwDomain && options.prodDomain) {
70
- blocks.push(`${options.prodWwwDomain} {
71
- redir https://${options.prodDomain}{uri} permanent
70
+ blocks.push(`${options.prodWwwDomain} {
71
+ redir https://${options.prodDomain}{uri} permanent
72
72
  }`);
73
73
  }
74
74
  if (options.devDomain && options.devPort) {
75
- blocks.push(`${options.devDomain} {
76
- encode zstd gzip
77
- reverse_proxy 127.0.0.1:${options.devPort}
75
+ blocks.push(`${options.devDomain} {
76
+ encode zstd gzip
77
+ reverse_proxy 127.0.0.1:${options.devPort}
78
78
  }`);
79
79
  }
80
80
  if (options.devWwwDomain && options.devDomain) {
81
- blocks.push(`${options.devWwwDomain} {
82
- redir https://${options.devDomain}{uri} permanent
81
+ blocks.push(`${options.devWwwDomain} {
82
+ redir https://${options.devDomain}{uri} permanent
83
83
  }`);
84
84
  }
85
85
  return `${blocks.join("\n\n")}\n`;
86
86
  }
87
87
  function userEnvTemplate(options) {
88
- return `# Pibo user-host setup
89
- PIBO_HOME=${options.piboHome}
90
- PIBO_AUTH_BASE_URL=${options.domain ? `https://${options.domain}` : "https://pibo.example.com"}
91
- # Set through \`pibo config set\` or your secret manager:
92
- # PIBO_AUTH_SECRET=<at-least-32-characters>
93
- # PIBO_GOOGLE_CLIENT_ID=<google-client-id>
94
- # PIBO_GOOGLE_CLIENT_SECRET=<google-client-secret>
95
- # PIBO_ALLOWED_EMAILS=you@example.com
88
+ return `# Pibo user-host setup
89
+ PIBO_HOME=${options.piboHome}
90
+ PIBO_AUTH_BASE_URL=${options.domain ? `https://${options.domain}` : "https://pibo.example.com"}
91
+ # Set through \`pibo config set\` or your secret manager:
92
+ # PIBO_AUTH_SECRET=<at-least-32-characters>
93
+ # PIBO_GOOGLE_CLIENT_ID=<google-client-id>
94
+ # PIBO_GOOGLE_CLIENT_SECRET=<google-client-secret>
95
+ # PIBO_ALLOWED_EMAILS=you@example.com
96
96
  `;
97
97
  }
98
98
  function developerEnvTemplate(options) {
99
- return `# Pibo developer-host setup
100
- PIBO_ORIGIN=${options.origin ?? "git@github.com:<your-fork>/pibo.git"}
101
- PIBO_UPSTREAM=${options.upstream ?? "git@github.com:Pascapone/pibo.git"}
102
- PIBO_REPO_DIR=${options.repoDir}
103
- PIBO_PROD_HOME=${options.prodHome}
104
- PIBO_DEV_HOME=${options.devHome}
105
- PIBO_PROD_BASE_URL=${options.prodDomain ? `https://${options.prodDomain}` : "https://pibo.example.com"}
106
- PIBO_DEV_BASE_URL=${options.devDomain ? `https://${options.devDomain}` : "https://dev.pibo.example.com"}
107
- # Optional: override the dev deploy reachability probe. If omitted, deploy uses PIBO_DEV_BASE_URL/apps/chat.
108
- # PIBO_DEV_PUBLIC_URL=${options.devDomain ? `https://${options.devDomain}/apps/chat` : "https://dev.pibo.example.com/apps/chat"}
99
+ return `# Pibo developer-host setup
100
+ PIBO_ORIGIN=${options.origin ?? "git@github.com:<your-fork>/pibo.git"}
101
+ PIBO_UPSTREAM=${options.upstream ?? "git@github.com:Pascapone/pibo.git"}
102
+ PIBO_REPO_DIR=${options.repoDir}
103
+ PIBO_PROD_HOME=${options.prodHome}
104
+ PIBO_DEV_HOME=${options.devHome}
105
+ PIBO_PROD_BASE_URL=${options.prodDomain ? `https://${options.prodDomain}` : "https://pibo.example.com"}
106
+ PIBO_DEV_BASE_URL=${options.devDomain ? `https://${options.devDomain}` : "https://dev.pibo.example.com"}
107
+ # Optional: override the dev deploy reachability probe. If omitted, deploy uses PIBO_DEV_BASE_URL/apps/chat.
108
+ # PIBO_DEV_PUBLIC_URL=${options.devDomain ? `https://${options.devDomain}/apps/chat` : "https://dev.pibo.example.com/apps/chat"}
109
109
  `;
110
110
  }
111
111
  export function createUserHostSetupPlan(options = {}) {