@pasko70/pibo 3.0.1 → 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 (122) hide show
  1. package/README.md +20 -6
  2. package/compute-image/Dockerfile +47 -0
  3. package/compute-image/Dockerfile.dockerignore +5 -0
  4. package/dist/agent-runtime/context-build.js +26 -2
  5. package/dist/agent-runtime/contract.js +78 -28
  6. package/dist/agent-runtime/portable-history.js +19 -9
  7. package/dist/agent-runtime/registry.js +8 -2
  8. package/dist/agent-runtime/routed-session.js +120 -15
  9. package/dist/agent-runtimes/codex-native/adapter.js +12 -0
  10. package/dist/agent-runtimes/codex-native/resource-delivery.js +35 -7
  11. package/dist/agent-runtimes/omp/adapter.js +3 -7
  12. package/dist/agent-runtimes/pi/adapter.js +5 -3
  13. package/dist/agent-runtimes/pi/routed-session.js +59 -12
  14. package/dist/agent-runtimes/pi/runtime.js +6 -0
  15. package/dist/apps/chat/agent-profiles.js +31 -2
  16. package/dist/apps/chat/agent-store.js +98 -6
  17. package/dist/apps/chat/chat-request-normalizers.js +31 -0
  18. package/dist/apps/chat/data/chat-data-mappers.js +61 -11
  19. package/dist/apps/chat/data/project-service.js +119 -15
  20. package/dist/apps/chat/data/room-service.js +21 -0
  21. package/dist/apps/chat/data/session-query-service.js +33 -10
  22. package/dist/apps/chat/data/timeline-query-service.js +60 -35
  23. package/dist/apps/chat/loop-api.js +10 -2
  24. package/dist/apps/chat/output-compactor.js +144 -21
  25. package/dist/apps/chat/output-event-policy.js +179 -2
  26. package/dist/apps/chat/stream.js +16 -7
  27. package/dist/apps/chat/trace-v2.js +1 -0
  28. package/dist/apps/chat/web-app.js +317 -86
  29. package/dist/apps/chat-ui/assets/{dist-BDIATCQt.js → dist-CUQJqggN.js} +1 -1
  30. package/dist/apps/chat-ui/assets/{dist-mqCviI-c.js → dist-ClUlQWYN.js} +1 -1
  31. package/dist/apps/chat-ui/assets/{dist-JnW4v8UE.js → dist-Djul9BmZ.js} +1 -1
  32. package/dist/apps/chat-ui/assets/{dist-lRiLP9rr.js → dist-GD0JGdCi.js} +1 -1
  33. package/dist/apps/chat-ui/assets/{dist-BBDMeU5-.js → dist-W5HOqHym.js} +1 -1
  34. package/dist/apps/chat-ui/assets/index-BcjkX-iP.js +228 -0
  35. package/dist/apps/chat-ui/assets/index-CmqRSbBU.css +1 -0
  36. package/dist/apps/chat-ui/index.html +2 -2
  37. package/dist/apps/chat-vscode-web/assets/index-JvrPUGvI.js +43 -0
  38. package/dist/apps/chat-vscode-web/index.html +1 -1
  39. package/dist/apps/cli-ui/InkSessionApp.js +111 -12
  40. package/dist/apps/cli-ui/cliSessionsCommand.js +34 -8
  41. package/dist/cli-session/localSessionSource.js +153 -161
  42. package/dist/cli.js +7 -11
  43. package/dist/compute/cli.js +35 -16
  44. package/dist/compute/docker.js +341 -94
  45. package/dist/config/config.js +3 -0
  46. package/dist/core/context-build.js +7 -16
  47. package/dist/core/output-persistence-retry.js +484 -0
  48. package/dist/core/output-render-sequence.js +331 -0
  49. package/dist/core/profiles.js +24 -0
  50. package/dist/core/provider-recovery.js +7 -1
  51. package/dist/core/session-model.js +37 -1
  52. package/dist/core/session-router.js +78 -110
  53. package/dist/data/ingest-service.js +91 -9
  54. package/dist/data/navigation-store.js +5 -5
  55. package/dist/data/pibo-store.js +8 -1
  56. package/dist/data/schema.js +243 -3
  57. package/dist/data/session-store.js +3 -3
  58. package/dist/debug/agents.js +91 -89
  59. package/dist/debug/index.js +46 -2
  60. package/dist/debug/pty.js +127 -48
  61. package/dist/gateway/cli.js +4 -0
  62. package/dist/gateway/web.js +8 -8
  63. package/dist/local/client.js +3 -2
  64. package/dist/loops/channel.js +3 -1
  65. package/dist/loops/cli.js +2 -1
  66. package/dist/loops/service.js +90 -11
  67. package/dist/loops/store.js +100 -74
  68. package/dist/mcp/agent-context.js +13 -26
  69. package/dist/mcp/commands/info.js +3 -1
  70. package/dist/mcp/config-command.js +46 -2
  71. package/dist/mcp/config.js +18 -4
  72. package/dist/plugins/context-files-store.js +36 -6
  73. package/dist/plugins/context-files.js +52 -1
  74. package/dist/plugins/registry.js +2 -0
  75. package/dist/plugins/user-profile-resources.js +22 -0
  76. package/dist/previews/base-url.js +34 -0
  77. package/dist/previews/config.js +2 -32
  78. package/dist/ralph/store.js +5 -0
  79. package/dist/reliability/store.js +378 -106
  80. package/dist/session-ui/terminalRows.js +8 -4
  81. package/dist/sessions/pibo-data-store.js +145 -0
  82. package/dist/sessions/store.js +42 -0
  83. package/dist/setup/cli.js +422 -6
  84. package/dist/setup/installation-profiles.js +464 -0
  85. package/dist/shared/deterministic-digest.js +94 -0
  86. package/dist/shared/trace-engine.js +54 -0
  87. package/dist/shared/trace-event-projection.js +124 -67
  88. package/dist/shared/trace-history.js +47 -12
  89. package/dist/shared/trace-live-reducer.js +23 -4
  90. package/dist/shared/trace-nodes.js +17 -28
  91. package/dist/shared/trace-order.js +41 -5
  92. package/dist/shared/trace-page-merge.js +44 -14
  93. package/dist/shared/trace-patch-nodes.js +2 -0
  94. package/dist/shared/trace-subagent-links.js +4 -1
  95. package/dist/shared/trace-tool-identity.js +15 -8
  96. package/dist/signals/projector.js +28 -6
  97. package/dist/subagents/observation-query.js +121 -0
  98. package/dist/subagents/tool.js +7 -3
  99. package/dist/tools/agent-browser-leases.js +58 -23
  100. package/dist/tools/agent-browser-wrapper.js +1 -1
  101. package/dist/tools/browser-use-leases.js +129 -16
  102. package/dist/tools/browser-use-wrapper.js +1 -1
  103. package/dist/tools/index.js +27 -11
  104. package/dist/tools/python-runtime.js +10 -3
  105. package/dist/tools/registry.js +1 -1
  106. package/dist/tools/runtime/node-backend.js +16 -5
  107. package/dist/tools/runtime/node-worker-source.js +112 -33
  108. package/dist/tools/runtime/python-backend.js +16 -5
  109. package/dist/tools/runtime/python-worker-source.js +167 -16
  110. package/dist/tools/runtime/registry.js +23 -10
  111. package/dist/user-skills/store.js +0 -1
  112. package/docs/ops/vscode-extension-release.md +9 -1
  113. package/npm-shrinkwrap.json +9 -2
  114. package/package.json +9 -2
  115. package/scripts/docker-entrypoint.sh +46 -0
  116. package/scripts/prepare-agent-browser-wrapper.sh +70 -0
  117. package/scripts/prepare-browser-use-wrapper.sh +255 -0
  118. package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +0 -228
  119. package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +0 -1
  120. package/dist/apps/chat-vscode-web/assets/index-Dtw2Z7jz.js +0 -43
  121. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  122. package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.1.vsix +0 -0
@@ -1,13 +1,20 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { DEFAULT_PIBO_PROFILE_NAME, createDefaultPiboPluginRegistry, resolvePiboProfileNameFromRegistryOrDefault, } from "../plugins/builtin.js";
2
+ import { DEFAULT_PIBO_PROFILE_NAME, resolvePiboProfileNameFromRegistryOrDefault, } from "../plugins/builtin.js";
3
+ import { createDefaultPiboUserProfileRegistry } from "../plugins/user-profile-resources.js";
3
4
  import { createDefaultPiboDataSessionStore } from "../sessions/pibo-data-store.js";
5
+ import { OutputRenderSequencer, outputRenderHighWaterStore, validRenderSequence } from "../core/output-render-sequence.js";
6
+ import { OutputPersistenceRetryQueue, } from "../core/output-persistence-retry.js";
4
7
  import { getDefaultPiboWorkspace } from "../core/workspace.js";
5
8
  import { buildTraceView } from "../apps/chat/trace.js";
6
9
  import { storedPiboEventFromV2Row, } from "../apps/chat/data/chat-data-mappers.js";
7
- import { ChatDataIngestService } from "../data/ingest-service.js";
10
+ import { ChatDataIngestService, outputIdempotencyKey, outputPersistenceDeliveryKey } from "../data/ingest-service.js";
11
+ import { OutputCompactor } from "../apps/chat/output-compactor.js";
12
+ import { isPiboOutputEvent } from "../apps/chat/output-event-policy.js";
8
13
  import { ChatRoomService } from "../apps/chat/data/room-service.js";
9
14
  import { ChatHistoryQueryService } from "../apps/chat/data/history-query-service.js";
15
+ import { createDefaultPiboReliabilityStore, } from "../reliability/store.js";
10
16
  import { buildSlashCommandCatalog, normalizeCommandErrorDescriptor, normalizeCommandResultDescriptor, } from "../session-ui/index.js";
17
+ import { patchTraceViewWithEvent } from "../shared/trace-engine.js";
11
18
  import { CliSourceError, } from "./sessionSource.js";
12
19
  export const CLI_LOCAL_RECOVERY_SOURCE_ID = "local:root";
13
20
  export const CLI_LOCAL_RECOVERY_SOURCE_LABEL = "Local recovery";
@@ -20,9 +27,14 @@ export class LocalCliSessionSource {
20
27
  ownsRouter;
21
28
  unsubscribeRouter;
22
29
  now;
30
+ outputRenderSequencer;
31
+ outputCompactor = new OutputCompactor();
32
+ outputPersistenceRetries;
23
33
  statusMessage;
24
34
  dataStore;
25
35
  ownsDataStore;
36
+ reliabilityStore;
37
+ ownsReliabilityStore;
26
38
  ingestService;
27
39
  roomService;
28
40
  agentSummaries;
@@ -39,14 +51,28 @@ export class LocalCliSessionSource {
39
51
  options.ownsSessionStore ?? options.sessionStore === undefined;
40
52
  this.roomProvider = options.roomProvider;
41
53
  this.pluginRegistry =
42
- options.pluginRegistry ?? createDefaultPiboPluginRegistry();
54
+ options.pluginRegistry ?? createDefaultPiboUserProfileRegistry();
43
55
  this.router = options.router;
44
56
  this.ownsRouter = options.ownsRouter ?? false;
45
- this.unsubscribeRouter = this.router?.subscribe((event) => this.handleRouterEvent(event));
46
57
  this.now = options.now ?? (() => new Date().toISOString());
58
+ this.outputRenderSequencer = new OutputRenderSequencer({
59
+ now: () => {
60
+ const timestamp = Date.parse(this.now());
61
+ return Number.isFinite(timestamp) ? timestamp : Date.now();
62
+ },
63
+ highWaterStore: outputRenderHighWaterStore(this.sessionStore),
64
+ });
47
65
  this.statusMessage = options.statusMessage;
48
66
  this.dataStore = options.dataStore;
49
67
  this.ownsDataStore = options.ownsDataStore === true;
68
+ this.reliabilityStore = options.reliabilityStore
69
+ ?? (this.ownsDataStore ? createDefaultPiboReliabilityStore() : undefined);
70
+ this.ownsReliabilityStore = options.ownsReliabilityStore
71
+ ?? (this.ownsDataStore && options.reliabilityStore === undefined);
72
+ this.outputPersistenceRetries = new OutputPersistenceRetryQueue({
73
+ durableStore: this.reliabilityStore,
74
+ queueName: "output-persistence-cli",
75
+ });
50
76
  this.ingestService = options.dataStore
51
77
  ? new ChatDataIngestService(options.dataStore)
52
78
  : undefined;
@@ -56,6 +82,13 @@ export class LocalCliSessionSource {
56
82
  this.agentSummaries = options.agentSummaries
57
83
  ? [...options.agentSummaries]
58
84
  : undefined;
85
+ this.outputPersistenceRetries.recover((job) => {
86
+ const persistenceState = parseCliOutputPersistenceState(job.payload);
87
+ if (!persistenceState)
88
+ throw new Error(`Invalid durable CLI output persistence job ${job.key}`);
89
+ return this.createOutputPersistenceJob(persistenceState);
90
+ });
91
+ this.unsubscribeRouter = this.router?.subscribe((event) => this.handleRouterEvent(event));
59
92
  }
60
93
  async listRooms() {
61
94
  this.assertOpen();
@@ -179,6 +212,14 @@ export class LocalCliSessionSource {
179
212
  const session = this.resolveSession(sessionId);
180
213
  const eventId = randomUUID();
181
214
  this.ingestUserMessage(session, eventId, trimmed);
215
+ this.recordOutputEvent({
216
+ type: "message_queued",
217
+ piboSessionId: sessionId,
218
+ eventId,
219
+ text: trimmed,
220
+ source: "ui",
221
+ queuedMessages: 0,
222
+ });
182
223
  if (this.router) {
183
224
  try {
184
225
  await this.router.emit({
@@ -195,7 +236,6 @@ export class LocalCliSessionSource {
195
236
  }
196
237
  return;
197
238
  }
198
- this.recordLocalUserMessage(sessionId, eventId, trimmed, "done");
199
239
  this.updateSessionStatus(sessionId, "idle");
200
240
  }
201
241
  async listAgents() {
@@ -371,14 +411,20 @@ export class LocalCliSessionSource {
371
411
  }
372
412
  try {
373
413
  this.closed = true;
414
+ await this.outputPersistenceRetries.drain();
415
+ this.outputPersistenceRetries.dispose();
374
416
  for (const handle of [...this.openHandles])
375
417
  handle.close();
376
418
  this.listeners.clear();
419
+ this.outputRenderSequencer.disposeAll();
420
+ this.outputCompactor.disposeAll();
377
421
  this.unsubscribeRouter?.();
378
422
  if (this.ownsSessionStore)
379
423
  this.sessionStore.close?.();
380
424
  if (this.ownsDataStore)
381
425
  this.dataStore?.close();
426
+ if (this.ownsReliabilityStore)
427
+ this.reliabilityStore?.close();
382
428
  }
383
429
  finally {
384
430
  this.closing = false;
@@ -544,170 +590,95 @@ export class LocalCliSessionSource {
544
590
  handleRouterEvent(event) {
545
591
  if (this.closed)
546
592
  return;
593
+ if (!isPiboOutputEvent(event)) {
594
+ this.outputPersistenceRetries.quarantine("runtime_output_event_invalid");
595
+ return;
596
+ }
547
597
  const session = this.sessionStore.get(event.piboSessionId);
548
598
  if (!session)
549
599
  return;
550
- this.ingestOutputEvent(session, event);
551
- this.recordOutputEvent(event);
600
+ const positionedEvent = this.outputRenderSequencer.position(event);
601
+ const compacted = this.outputCompactor.prepare(positionedEvent);
602
+ if (compacted.persistedEvents.length === 0) {
603
+ compacted.ack();
604
+ }
605
+ else {
606
+ const persistenceState = {
607
+ version: 1,
608
+ piboSessionId: session.id,
609
+ deliveries: compacted.persistedEvents.map((persistedEvent) => ({ event: persistedEvent })),
610
+ };
611
+ this.outputPersistenceRetries.enqueue(this.createOutputPersistenceJob(persistenceState, () => compacted.ack(), () => compacted.rollback()));
612
+ }
613
+ for (const liveEvent of compacted.liveEvents)
614
+ this.recordOutputEvent(liveEvent);
615
+ }
616
+ createOutputPersistenceJob(persistenceState, onSuccess, onDeadLetter) {
617
+ const firstEvent = persistenceState.deliveries[0]?.event;
618
+ return {
619
+ key: JSON.stringify(persistenceState.deliveries.map((delivery) => outputPersistenceDeliveryKey(delivery.event))),
620
+ piboSessionId: persistenceState.piboSessionId,
621
+ eventId: firstEvent ? outputPersistenceDeliveryKey(firstEvent) : undefined,
622
+ payload: persistenceState,
623
+ run: (retryContext) => this.deliverOutputPersistenceState(retryContext),
624
+ onSuccess,
625
+ onDeadLetter,
626
+ };
627
+ }
628
+ deliverOutputPersistenceState(retryContext) {
629
+ const persistenceState = parseCliOutputPersistenceState(retryContext.payload);
630
+ if (!persistenceState)
631
+ throw new Error("Invalid durable CLI output persistence state");
632
+ const session = this.sessionStore.get(persistenceState.piboSessionId);
633
+ if (!session)
634
+ throw new Error(`No session available for ${persistenceState.piboSessionId}`);
635
+ for (const delivery of persistenceState.deliveries) {
636
+ if (delivery.persisted)
637
+ continue;
638
+ if (!this.ingestOutputEvent(session, delivery.event)) {
639
+ throw new Error(`Failed to persist ${outputPersistenceDeliveryKey(delivery.event)}`);
640
+ }
641
+ delivery.persisted = true;
642
+ retryContext.updatePayload(persistenceState);
643
+ }
552
644
  }
553
645
  recordOutputEvent(event) {
554
- const sessionId = event.piboSessionId;
555
- const eventId = "eventId" in event ? event.eventId : undefined;
556
- if (event.type === "message_queued" || event.type === "message_started") {
557
- this.recordLocalUserMessage(sessionId, eventId ?? randomUUID(), event.text, event.type === "message_started" ? "running" : "done");
646
+ const positionedEvent = validRenderSequence(event.renderSequence)
647
+ ? event
648
+ : this.outputRenderSequencer.position(event);
649
+ const sessionId = positionedEvent.piboSessionId;
650
+ const session = this.resolveSession(sessionId);
651
+ const current = this.traceViews.get(sessionId) ?? emptyTraceView(session);
652
+ const status = positionedEvent.type === "session_error"
653
+ ? "error"
654
+ : positionedEvent.type === "message_finished"
655
+ ? "idle"
656
+ : "running";
657
+ const next = patchTraceViewWithEvent(current, storedEvent(positionedEvent, this.now(), current.rawEvents.length + 1), status === "idle" ? "idle" : status);
658
+ next.eventCount = next.rawEvents.length;
659
+ next.version = `local-${next.rawEvents.length}-${session.updatedAt}`;
660
+ this.traceViews.set(sessionId, next);
661
+ this.emit(sessionId, {
662
+ type: "trace",
663
+ session: sessionToSummary(session, this.ensureDefaultRoom().id),
664
+ traceView: next,
665
+ });
666
+ if (positionedEvent.type === "message_queued" || positionedEvent.type === "message_started") {
558
667
  this.updateSessionStatus(sessionId, "running");
559
- return;
560
668
  }
561
- if (event.type === "message_finished") {
669
+ else if (positionedEvent.type === "message_finished") {
562
670
  this.updateSessionStatus(sessionId, "idle");
563
- return;
564
- }
565
- if (event.type === "assistant_delta") {
566
- this.upsertTraceNode(sessionId, {
567
- id: traceNodeId("assistant", eventId, event.assistantIndex ?? event.contentIndex),
568
- piboSessionId: sessionId,
569
- type: "assistant.message",
570
- title: "Agent Message",
571
- status: "running",
572
- startedAt: this.now(),
573
- output: event.text,
574
- children: [],
575
- }, { appendOutput: true, rawEvent: event });
576
- return;
577
- }
578
- if (event.type === "assistant_message") {
579
- this.upsertTraceNode(sessionId, {
580
- id: traceNodeId("assistant", eventId, event.assistantIndex ?? event.contentIndex),
581
- piboSessionId: sessionId,
582
- type: "assistant.message",
583
- title: "Agent Message",
584
- status: "done",
585
- startedAt: this.now(),
586
- completedAt: this.now(),
587
- output: event.text,
588
- children: [],
589
- }, { rawEvent: event });
590
- return;
591
- }
592
- if (event.type === "tool_call" ||
593
- event.type === "tool_execution_started" ||
594
- event.type === "tool_execution_updated" ||
595
- event.type === "tool_execution_finished") {
596
- const finished = event.type === "tool_execution_finished";
597
- this.upsertTraceNode(sessionId, {
598
- id: traceNodeId("tool", event.toolCallId),
599
- piboSessionId: sessionId,
600
- eventId,
601
- toolCallId: event.toolCallId,
602
- type: finished ? "tool.result" : "tool.call",
603
- title: event.toolName,
604
- status: finished ? (event.isError ? "error" : "done") : "running",
605
- startedAt: this.now(),
606
- completedAt: finished ? this.now() : undefined,
607
- input: "args" in event ? event.args : undefined,
608
- output: finished
609
- ? event.result
610
- : "partialResult" in event
611
- ? event.partialResult
612
- : undefined,
613
- children: [],
614
- }, { rawEvent: event });
615
- return;
616
671
  }
617
- if (event.type === "session_error") {
618
- this.recordLocalError(sessionId, eventId ?? randomUUID(), event.error);
619
- return;
672
+ else if (positionedEvent.type === "session_error") {
673
+ this.updateSessionStatus(sessionId, "error");
620
674
  }
621
- this.appendRawEvent(sessionId, event);
622
- }
623
- recordLocalUserMessage(sessionId, eventId, text, status) {
624
- this.upsertTraceNode(sessionId, {
625
- id: traceNodeId("user", eventId),
626
- piboSessionId: sessionId,
627
- eventId,
628
- type: "user.message",
629
- title: "User Message",
630
- status,
631
- startedAt: this.now(),
632
- completedAt: status === "done" ? this.now() : undefined,
633
- output: text,
634
- children: [],
635
- }, {
636
- rawEvent: {
637
- type: "message_queued",
638
- piboSessionId: sessionId,
639
- eventId,
640
- queuedMessages: 0,
641
- text,
642
- source: "ui",
643
- },
644
- });
645
675
  }
646
676
  recordLocalError(sessionId, eventId, error) {
647
- this.upsertTraceNode(sessionId, {
648
- id: traceNodeId("error", eventId),
677
+ this.recordOutputEvent({
678
+ type: "session_error",
649
679
  piboSessionId: sessionId,
650
680
  eventId,
651
- type: "error",
652
- title: "CLI source error",
653
- status: "error",
654
- startedAt: this.now(),
655
- completedAt: this.now(),
656
681
  error: error instanceof Error ? error.message : String(error),
657
- children: [],
658
- }, {
659
- rawEvent: {
660
- type: "session_error",
661
- piboSessionId: sessionId,
662
- eventId,
663
- error: error instanceof Error ? error.message : String(error),
664
- },
665
- });
666
- this.updateSessionStatus(sessionId, "error");
667
- }
668
- upsertTraceNode(sessionId, node, options = {}) {
669
- const session = this.resolveSession(sessionId);
670
- const traceView = cloneJson(this.traceViews.get(sessionId) ?? emptyTraceView(session));
671
- const index = traceView.nodes.findIndex((candidate) => candidate.id === node.id);
672
- if (index >= 0) {
673
- const previous = traceView.nodes[index];
674
- const output = options.appendOutput &&
675
- typeof previous.output === "string" &&
676
- typeof node.output === "string"
677
- ? previous.output + node.output
678
- : node.output;
679
- traceView.nodes[index] = {
680
- ...previous,
681
- ...node,
682
- output,
683
- children: node.children ?? previous.children,
684
- };
685
- }
686
- else {
687
- traceView.nodes.push(node);
688
- }
689
- if (options.rawEvent)
690
- traceView.rawEvents.push(storedEvent(options.rawEvent, this.now(), traceView.rawEvents.length + 1));
691
- traceView.eventCount = traceView.rawEvents.length;
692
- traceView.version = `local-${traceView.rawEvents.length}-${session.updatedAt}`;
693
- this.traceViews.set(sessionId, traceView);
694
- this.emit(sessionId, {
695
- type: "trace",
696
- session: sessionToSummary(session, this.ensureDefaultRoom().id),
697
- traceView: cloneJson(traceView),
698
- });
699
- }
700
- appendRawEvent(sessionId, event) {
701
- const session = this.resolveSession(sessionId);
702
- const traceView = cloneJson(this.traceViews.get(sessionId) ?? emptyTraceView(session));
703
- traceView.rawEvents.push(storedEvent(event, this.now(), traceView.rawEvents.length + 1));
704
- traceView.eventCount = traceView.rawEvents.length;
705
- traceView.version = `local-${traceView.rawEvents.length}-${session.updatedAt}`;
706
- this.traceViews.set(sessionId, traceView);
707
- this.emit(sessionId, {
708
- type: "trace",
709
- session: sessionToSummary(session, this.ensureDefaultRoom().id),
710
- traceView: cloneJson(traceView),
711
682
  });
712
683
  }
713
684
  updateSessionStatus(sessionId, status) {
@@ -770,7 +741,7 @@ export class LocalCliSessionSource {
770
741
  }
771
742
  ingestOutputEvent(session, event) {
772
743
  if (!this.ingestService)
773
- return;
744
+ return true;
774
745
  try {
775
746
  this.ingestService.ingestOutputEvent({
776
747
  session,
@@ -779,9 +750,11 @@ export class LocalCliSessionSource {
779
750
  event,
780
751
  createdAt: this.now(),
781
752
  });
753
+ return true;
782
754
  }
783
755
  catch {
784
- // Persistence is best-effort; live local rendering still proceeds through in-memory trace updates.
756
+ // The caller retains the prepared compaction and retries this exact event identity.
757
+ return false;
785
758
  }
786
759
  }
787
760
  buildStatus(session) {
@@ -860,6 +833,29 @@ export class LocalCliSessionSource {
860
833
  export function createLocalCliSessionSource(options = {}) {
861
834
  return new LocalCliSessionSource(options);
862
835
  }
836
+ function parseCliOutputPersistenceState(value) {
837
+ if (!value || typeof value !== "object" || Array.isArray(value))
838
+ return undefined;
839
+ const candidate = value;
840
+ if (candidate.version !== 1 || typeof candidate.piboSessionId !== "string" || !Array.isArray(candidate.deliveries))
841
+ return undefined;
842
+ const deliveries = [];
843
+ for (const rawDelivery of candidate.deliveries) {
844
+ if (!rawDelivery || typeof rawDelivery !== "object" || Array.isArray(rawDelivery))
845
+ return undefined;
846
+ const delivery = rawDelivery;
847
+ const event = delivery.event;
848
+ if (!isPiboOutputEvent(event) || !outputIdempotencyKey(event))
849
+ return undefined;
850
+ deliveries.push({
851
+ event: event,
852
+ ...(delivery.persisted === true ? { persisted: true } : {}),
853
+ });
854
+ }
855
+ if (!deliveries.length)
856
+ return undefined;
857
+ return { version: 1, piboSessionId: candidate.piboSessionId, deliveries };
858
+ }
863
859
  export function redactCliSecretText(text) {
864
860
  return text
865
861
  .replace(/\b([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD)[A-Z0-9_]*)\s*=\s*([^\s]+)/gi, "$1=[redacted]")
@@ -989,17 +985,13 @@ function storedEvent(event, createdAt, eventSequence) {
989
985
  id: eventId ?? `${event.piboSessionId}-${event.type}-${eventSequence}`,
990
986
  piboSessionId: event.piboSessionId,
991
987
  eventSequence,
988
+ renderSequence: event.renderSequence,
992
989
  eventId,
993
990
  type: event.type,
994
991
  createdAt,
995
992
  payload: cloneJson(event),
996
993
  };
997
994
  }
998
- function traceNodeId(kind, eventId, index) {
999
- return ["local", kind, eventId ?? "event", index]
1000
- .filter((part) => part !== undefined)
1001
- .join(":");
1002
- }
1003
995
  function toCliSourceError(code, message, cause) {
1004
996
  return cause instanceof CliSourceError
1005
997
  ? cause
package/dist/cli.js CHANGED
@@ -1,16 +1,12 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { readFileSync } from "node:fs";
2
2
  import { Command } from "commander";
3
3
  import { PIBO_CONFIG_KEYS, getDefaultPiboConfigPath, deletePiboConfigValue, getDisplayPiboConfigValue, loadPiboConfig, redactPiboConfig, savePiboConfig, setPiboConfigValue, } from "./config/config.js";
4
4
  import { parsePiboThinkingLevel } from "./core/thinking.js";
5
- import { ensurePrivatePiboHome, piboHomePath } from "./core/pibo-home.js";
5
+ import { ensurePrivatePiboHome } from "./core/pibo-home.js";
6
6
  async function resolveCliProfile(profileName) {
7
- const { createDefaultPiboPluginRegistry, createGatewayProducerPiboProfile, createPiboProfileFromRegistryOrDefault } = await import("./plugins/builtin.js");
8
- const registry = createDefaultPiboPluginRegistry();
9
- const chatAgentStorePath = piboHomePath("chat-agents.sqlite");
10
- if (existsSync(chatAgentStorePath)) {
11
- const { createPiboChatCustomAgentProfilesPlugin } = await import("./plugins/chat-custom-agents.js");
12
- registry.registerPlugin(createPiboChatCustomAgentProfilesPlugin({ agentStorePath: chatAgentStorePath }));
13
- }
7
+ const { createGatewayProducerPiboProfile, createPiboProfileFromRegistryOrDefault } = await import("./plugins/builtin.js");
8
+ const { createDefaultPiboUserProfileRegistry } = await import("./plugins/user-profile-resources.js");
9
+ const registry = createDefaultPiboUserProfileRegistry();
14
10
  const profile = profileName === "gateway-producer" || profileName === "pibo-gateway-producer"
15
11
  ? createGatewayProducerPiboProfile()
16
12
  : createPiboProfileFromRegistryOrDefault(registry, profileName);
@@ -281,7 +277,7 @@ export async function runPiboCli(argv = process.argv) {
281
277
  });
282
278
  program
283
279
  .command("setup")
284
- .description("Plan user-host installs and developer-host upgrades")
280
+ .description("Plan and manage supported host installation profiles")
285
281
  .helpOption(false)
286
282
  .allowUnknownOption(true)
287
283
  .allowExcessArguments(true)
@@ -533,7 +529,7 @@ Commands:
533
529
  compute Manage Pibo Docker compute workers
534
530
  resources Inspect and safely reap managed compute and browser resources
535
531
  preview Expose session-linked live development previews
536
- setup Plan user-host installs and developer-host upgrades
532
+ setup Plan and manage supported host installation profiles
537
533
  skills Manage Pibo user skills
538
534
  cron Manage scheduled Pibo jobs
539
535
  loop Manage continuous agent loops (goal mode by default)
@@ -1,5 +1,5 @@
1
- import { Command } from "commander";
2
- import { IMAGE_NAME, imageExists, shouldRebuild, shouldRebuildDeps, dockerBuild, saveHash, saveDepHash, spawnWorker, spawnDevWorker, listWorkers, releaseWorker, planReapWorkers, applyComputeWorkerReapPlan, getComputeDiskDiagnostics, } from "./docker.js";
1
+ import { Command, InvalidArgumentError } from "commander";
2
+ import { imageExists, shouldRebuild, shouldRebuildDeps, dockerBuild, resolveComputeImageBuildConfig, saveHash, saveDepHash, spawnWorker, spawnDevWorker, listWorkers, releaseWorker, planReapWorkers, applyComputeWorkerReapPlan, getComputeDiskDiagnostics, } from "./docker.js";
3
3
  import { getComputeResourceHealth } from "./resource-health.js";
4
4
  import { mkdir } from "node:fs/promises";
5
5
  import path from "node:path";
@@ -7,6 +7,9 @@ import os from "node:os";
7
7
  const WORKSPACE_DIR = process.env.PIBO_COMPUTE_WORKSPACE || process.cwd();
8
8
  const HASH_FILE = path.join(os.homedir(), ".pibo", "compute-image-hash");
9
9
  const DEP_HASH_FILE = path.join(os.homedir(), ".pibo", "compute-dep-hash");
10
+ function computeImageBuildConfig() {
11
+ return resolveComputeImageBuildConfig(WORKSPACE_DIR);
12
+ }
10
13
  function printJson(value) {
11
14
  console.log(JSON.stringify(value, null, 2));
12
15
  }
@@ -16,6 +19,12 @@ function parsePositiveIntegerOption(value) {
16
19
  const parsed = Number(value);
17
20
  return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
18
21
  }
22
+ function parseNonNegativeNumber(value) {
23
+ const parsed = Number(value);
24
+ if (!Number.isFinite(parsed) || parsed < 0)
25
+ throw new InvalidArgumentError("Value must be a non-negative number");
26
+ return parsed;
27
+ }
19
28
  function hostPort(value) {
20
29
  if (!value || value === "-")
21
30
  return undefined;
@@ -233,18 +242,24 @@ Creates a worker container and starts the gateway. Prints JSON with the containe
233
242
 
234
243
  Use this for quick isolated checks. For code changes, prefer:
235
244
  $ pibo compute dev spawn --worktree my-fix
245
+
246
+ Environment:
247
+ PIBO_COMPUTE_IMAGE Override the Docker image name.
248
+ PIBO_COMPUTE_WORKSPACE Use an explicit workspace or source checkout.
236
249
  `)
237
250
  .action(async (options) => {
238
251
  await mkdir(path.dirname(HASH_FILE), { recursive: true });
239
- const needsBuild = !(await imageExists(IMAGE_NAME)) || (await shouldRebuild(WORKSPACE_DIR, HASH_FILE));
252
+ const image = computeImageBuildConfig();
253
+ const needsBuild = !(await imageExists(image.imageName)) || (await shouldRebuild(image.buildContext, HASH_FILE, image.dockerfile));
240
254
  if (needsBuild) {
241
- console.error(`Building ${IMAGE_NAME} from ${WORKSPACE_DIR}...`);
242
- await dockerBuild(WORKSPACE_DIR);
243
- await saveHash(WORKSPACE_DIR, HASH_FILE);
255
+ console.error(`Building ${image.imageName} from ${image.buildContext}...`);
256
+ await dockerBuild(image);
257
+ await saveHash(image.buildContext, HASH_FILE, image.dockerfile);
244
258
  console.error("Build complete.");
245
259
  }
246
260
  const worker = await spawnWorker({
247
261
  workspaceDir: WORKSPACE_DIR,
262
+ imageName: image.imageName,
248
263
  name: options.name,
249
264
  holder: options.holder,
250
265
  ttlSeconds: parsePositiveIntegerOption(options.ttlSeconds),
@@ -282,12 +297,13 @@ Example:
282
297
  `)
283
298
  .action(async (options) => {
284
299
  await mkdir(path.dirname(DEP_HASH_FILE), { recursive: true });
300
+ const image = resolveComputeImageBuildConfig(options.repo, { packageRoot: options.repo });
285
301
  console.error("[pibo compute] Checking Docker image status...");
286
- const needsBuild = !(await imageExists(IMAGE_NAME)) || (await shouldRebuildDeps(options.repo, DEP_HASH_FILE));
302
+ const needsBuild = !(await imageExists(image.imageName)) || (await shouldRebuildDeps(options.repo, DEP_HASH_FILE));
287
303
  if (needsBuild) {
288
304
  console.error(`[pibo compute] Dependencies changed (package.json, package-lock.json, or Dockerfile).`);
289
- console.error(`[pibo compute] Rebuilding Docker image ${IMAGE_NAME} — this takes 1-2 minutes...`);
290
- await dockerBuild(options.repo);
305
+ console.error(`[pibo compute] Rebuilding Docker image ${image.imageName} — this takes 1-2 minutes...`);
306
+ await dockerBuild(image);
291
307
  await saveDepHash(options.repo, DEP_HASH_FILE);
292
308
  console.error("[pibo compute] Docker image build complete.");
293
309
  }
@@ -298,6 +314,7 @@ Example:
298
314
  const worker = await spawnDevWorker({
299
315
  repoDir: options.repo,
300
316
  worktreeName: options.worktree,
317
+ imageName: image.imageName,
301
318
  holder: options.holder,
302
319
  ttlSeconds: parsePositiveIntegerOption(options.ttlSeconds),
303
320
  idleSeconds: parsePositiveIntegerOption(options.idleSeconds),
@@ -317,10 +334,11 @@ Example:
317
334
  Rebuilds from the current workspace and refreshes compute image hashes.
318
335
  `)
319
336
  .action(async () => {
320
- console.error(`Rebuilding ${IMAGE_NAME} from ${WORKSPACE_DIR}...`);
321
- await dockerBuild(WORKSPACE_DIR);
322
- await saveHash(WORKSPACE_DIR, HASH_FILE);
323
- await saveDepHash(WORKSPACE_DIR, DEP_HASH_FILE);
337
+ const image = computeImageBuildConfig();
338
+ console.error(`Rebuilding ${image.imageName} from ${image.buildContext}...`);
339
+ await dockerBuild(image);
340
+ await saveHash(image.buildContext, HASH_FILE, image.dockerfile);
341
+ await saveDepHash(image.buildContext, DEP_HASH_FILE);
324
342
  console.error("Build complete.");
325
343
  });
326
344
  program
@@ -403,7 +421,7 @@ Use the name or id shown by:
403
421
  program
404
422
  .command("reap")
405
423
  .description("Preview or apply compute worker container cleanup")
406
- .option("--max-age-minutes <n>", "Select workers older than this many minutes", "60")
424
+ .option("--max-age-minutes <n>", "Select workers older than this many minutes", parseNonNegativeNumber, 60)
407
425
  .option("--include-dev", "Also select dev-worker containers; dev workers are skipped by default")
408
426
  .option("--no-stopped", "Do not select stopped, dead, created, or restarting containers")
409
427
  .option("--no-dirty", "Do not select dirty or OOM-killed containers")
@@ -416,12 +434,13 @@ Stopped, dirty/OOM, and old workers are selected unless disabled by selector fla
416
434
  Worktrees are preserved; container cleanup never deletes Git worktrees.
417
435
  `)
418
436
  .action(async (options) => {
419
- const maxAge = Number(options.maxAgeMinutes);
437
+ if (options.apply && options.dryRun)
438
+ throw new Error("Use either --apply or --dry-run, not both");
420
439
  const plan = await planReapWorkers({
421
440
  includeDev: options.includeDev === true,
422
441
  includeStopped: options.stopped !== false,
423
442
  includeDirty: options.dirty !== false,
424
- maxAgeMinutes: Number.isFinite(maxAge) ? maxAge : 60,
443
+ maxAgeMinutes: options.maxAgeMinutes,
425
444
  });
426
445
  const shouldApply = options.apply === true;
427
446
  const removed = shouldApply ? await applyComputeWorkerReapPlan(plan) : [];