@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.
- package/README.md +20 -6
- package/compute-image/Dockerfile +47 -0
- package/compute-image/Dockerfile.dockerignore +5 -0
- package/dist/agent-runtime/context-build.js +26 -2
- package/dist/agent-runtime/contract.js +78 -28
- package/dist/agent-runtime/portable-history.js +19 -9
- package/dist/agent-runtime/registry.js +8 -2
- package/dist/agent-runtime/routed-session.js +120 -15
- package/dist/agent-runtimes/codex-native/adapter.js +12 -0
- package/dist/agent-runtimes/codex-native/resource-delivery.js +35 -7
- package/dist/agent-runtimes/omp/adapter.js +3 -7
- package/dist/agent-runtimes/pi/adapter.js +5 -3
- package/dist/agent-runtimes/pi/routed-session.js +59 -12
- package/dist/agent-runtimes/pi/runtime.js +6 -0
- package/dist/apps/chat/agent-profiles.js +31 -2
- package/dist/apps/chat/agent-store.js +98 -6
- package/dist/apps/chat/chat-request-normalizers.js +31 -0
- package/dist/apps/chat/data/chat-data-mappers.js +61 -11
- package/dist/apps/chat/data/project-service.js +119 -15
- package/dist/apps/chat/data/room-service.js +21 -0
- package/dist/apps/chat/data/session-query-service.js +33 -10
- package/dist/apps/chat/data/timeline-query-service.js +60 -35
- package/dist/apps/chat/loop-api.js +10 -2
- package/dist/apps/chat/output-compactor.js +144 -21
- package/dist/apps/chat/output-event-policy.js +179 -2
- package/dist/apps/chat/stream.js +16 -7
- package/dist/apps/chat/trace-v2.js +1 -0
- package/dist/apps/chat/web-app.js +317 -86
- package/dist/apps/chat-ui/assets/{dist-BDIATCQt.js → dist-CUQJqggN.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-mqCviI-c.js → dist-ClUlQWYN.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-JnW4v8UE.js → dist-Djul9BmZ.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-lRiLP9rr.js → dist-GD0JGdCi.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BBDMeU5-.js → dist-W5HOqHym.js} +1 -1
- package/dist/apps/chat-ui/assets/index-BcjkX-iP.js +228 -0
- package/dist/apps/chat-ui/assets/index-CmqRSbBU.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-JvrPUGvI.js +43 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/cli-ui/InkSessionApp.js +111 -12
- package/dist/apps/cli-ui/cliSessionsCommand.js +34 -8
- package/dist/cli-session/localSessionSource.js +153 -161
- package/dist/cli.js +7 -11
- package/dist/compute/cli.js +35 -16
- package/dist/compute/docker.js +341 -94
- package/dist/config/config.js +3 -0
- package/dist/core/context-build.js +7 -16
- package/dist/core/output-persistence-retry.js +484 -0
- package/dist/core/output-render-sequence.js +331 -0
- package/dist/core/profiles.js +24 -0
- package/dist/core/provider-recovery.js +7 -1
- package/dist/core/session-model.js +37 -1
- package/dist/core/session-router.js +78 -110
- package/dist/data/ingest-service.js +91 -9
- package/dist/data/navigation-store.js +5 -5
- package/dist/data/pibo-store.js +8 -1
- package/dist/data/schema.js +243 -3
- package/dist/data/session-store.js +3 -3
- package/dist/debug/agents.js +91 -89
- package/dist/debug/index.js +46 -2
- package/dist/debug/pty.js +127 -48
- package/dist/gateway/cli.js +4 -0
- package/dist/gateway/web.js +8 -8
- package/dist/local/client.js +3 -2
- package/dist/loops/channel.js +3 -1
- package/dist/loops/cli.js +2 -1
- package/dist/loops/service.js +90 -11
- package/dist/loops/store.js +100 -74
- package/dist/mcp/agent-context.js +13 -26
- package/dist/mcp/commands/info.js +3 -1
- package/dist/mcp/config-command.js +46 -2
- package/dist/mcp/config.js +18 -4
- package/dist/plugins/context-files-store.js +36 -6
- package/dist/plugins/context-files.js +52 -1
- package/dist/plugins/registry.js +2 -0
- package/dist/plugins/user-profile-resources.js +22 -0
- package/dist/previews/base-url.js +34 -0
- package/dist/previews/config.js +2 -32
- package/dist/ralph/store.js +5 -0
- package/dist/reliability/store.js +378 -106
- package/dist/session-ui/terminalRows.js +8 -4
- package/dist/sessions/pibo-data-store.js +145 -0
- package/dist/sessions/store.js +42 -0
- package/dist/setup/cli.js +422 -6
- package/dist/setup/installation-profiles.js +464 -0
- package/dist/shared/deterministic-digest.js +94 -0
- package/dist/shared/trace-engine.js +54 -0
- package/dist/shared/trace-event-projection.js +124 -67
- package/dist/shared/trace-history.js +47 -12
- package/dist/shared/trace-live-reducer.js +23 -4
- package/dist/shared/trace-nodes.js +17 -28
- package/dist/shared/trace-order.js +41 -5
- package/dist/shared/trace-page-merge.js +44 -14
- package/dist/shared/trace-patch-nodes.js +2 -0
- package/dist/shared/trace-subagent-links.js +4 -1
- package/dist/shared/trace-tool-identity.js +15 -8
- package/dist/signals/projector.js +28 -6
- package/dist/subagents/observation-query.js +121 -0
- package/dist/subagents/tool.js +7 -3
- package/dist/tools/agent-browser-leases.js +58 -23
- package/dist/tools/agent-browser-wrapper.js +1 -1
- package/dist/tools/browser-use-leases.js +129 -16
- package/dist/tools/browser-use-wrapper.js +1 -1
- package/dist/tools/index.js +27 -11
- package/dist/tools/python-runtime.js +10 -3
- package/dist/tools/registry.js +1 -1
- package/dist/tools/runtime/node-backend.js +16 -5
- package/dist/tools/runtime/node-worker-source.js +112 -33
- package/dist/tools/runtime/python-backend.js +16 -5
- package/dist/tools/runtime/python-worker-source.js +167 -16
- package/dist/tools/runtime/registry.js +23 -10
- package/dist/user-skills/store.js +0 -1
- package/docs/ops/vscode-extension-release.md +9 -1
- package/npm-shrinkwrap.json +9 -2
- package/package.json +9 -2
- package/scripts/docker-entrypoint.sh +46 -0
- package/scripts/prepare-agent-browser-wrapper.sh +70 -0
- package/scripts/prepare-browser-use-wrapper.sh +255 -0
- package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +0 -228
- package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +0 -1
- package/dist/apps/chat-vscode-web/assets/index-Dtw2Z7jz.js +0 -43
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.1.vsix +0 -0
|
@@ -2,6 +2,8 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
4
4
|
import { PiboSteeringUnavailableError } from "../../core/events.js";
|
|
5
|
+
import { OutputRenderSequencer } from "../../core/output-render-sequence.js";
|
|
6
|
+
import { OutputPersistenceRetryQueue, } from "../../core/output-persistence-retry.js";
|
|
5
7
|
import { PI_AGENT_RUNTIME_CAPABILITIES, PI_PROTOCOL_VERSION, } from "../../agent-runtimes/pi/adapter.js";
|
|
6
8
|
import { AgentRuntimeBindingMissingError } from "../../agent-runtime/errors.js";
|
|
7
9
|
import { buildPortableRuntimeContextSnapshot, profileWithRuntimeInstance, uniqueRuntimeDiagnostics, } from "../../agent-runtime/context-build.js";
|
|
@@ -9,7 +11,7 @@ import { PiboRuntimeResourceService } from "../../agent-runtime/resource-service
|
|
|
9
11
|
import { summarizeSessionSignalStatus } from "../../signals/status.js";
|
|
10
12
|
import { PiboWebHttpError, readJsonBody, responseJson } from "../../web/http.js";
|
|
11
13
|
import { OutputCompactor } from "./output-compactor.js";
|
|
12
|
-
import { isLiveOnlyOutputEvent, isPersistableOutputEvent } from "./output-event-policy.js";
|
|
14
|
+
import { isLiveOnlyOutputEvent, isPersistableOutputEvent, isPiboOutputEvent } from "./output-event-policy.js";
|
|
13
15
|
import { chatRoomIdFromMetadata, isDefaultPiboRoom, isPiboRoomArchived, roomWorkspaceFromMetadata, withChatRoomId, withPiboRoomWorkspace, } from "./types/rooms.js";
|
|
14
16
|
import { chatStreamFramesFromOutputEvent, createChatStreamState, nextTransientChatStreamFrameId } from "./stream.js";
|
|
15
17
|
import { buildSessionNodes, buildTraceView } from "./trace.js";
|
|
@@ -17,7 +19,7 @@ import { DEFAULT_TRACE_EVENTS_PAGE_SIZE, annotateTracePage, etagForVersion, esti
|
|
|
17
19
|
import { TRACE_V2_DEFAULT_TIMELINE_LIMIT, TRACE_V2_MAX_TIMELINE_LIMIT, TRACE_V2_PAYLOAD_DEFAULT_LIMIT_BYTES, TRACE_V2_PAYLOAD_MAX_LIMIT_BYTES, TRACE_V2_RAW_EVENTS_DEFAULT_LIMIT, TRACE_V2_RAW_EVENTS_MAX_LIMIT, TRACE_V2_TIMELINE_HARD_BYTES, TRACE_IMAGE_MAX_COUNT, parseTracePayloadRef, readTraceImagePayload, readTracePayloadChunk, traceRawEventsPageFromEvents, traceTimelinePageFromView, } from "./trace-v2.js";
|
|
18
20
|
import { isChatWebSessionArchived } from "./session-metadata.js";
|
|
19
21
|
import { withWorkflowSessionKind } from "../../sessions/workflow-session-kind.js";
|
|
20
|
-
import { CustomAgentStore, createDefaultCustomAgentStore, previewCustomAgentCreate, previewCustomAgentUpdate, } from "./agent-store.js";
|
|
22
|
+
import { CustomAgentTargetReferenceError, CustomAgentStore, createDefaultCustomAgentStore, previewCustomAgentCreate, previewCustomAgentUpdate, } from "./agent-store.js";
|
|
21
23
|
import { loadPiboModelDefaults, } from "../../core/model-defaults.js";
|
|
22
24
|
import { inspectPiboContextBuild } from "../../core/context-build.js";
|
|
23
25
|
import { isPiboThinkingLevel } from "../../core/thinking.js";
|
|
@@ -30,10 +32,10 @@ import { listMcpServerInfos } from "../../mcp/agent-context.js";
|
|
|
30
32
|
import { getDefaultPiboWorkspace } from "../../core/workspace.js";
|
|
31
33
|
import { findPiPackage, listPiPackages } from "../../pi-packages/store.js";
|
|
32
34
|
import { ScopedUserSkillManager } from "../../user-skills/manager.js";
|
|
33
|
-
import { ChatDataIngestService } from "../../data/ingest-service.js";
|
|
35
|
+
import { ChatDataIngestService, outputIdempotencyKey, outputPersistenceDeliveryKey } from "../../data/ingest-service.js";
|
|
34
36
|
import { ChatEventCommandService } from "./data/event-command-service.js";
|
|
35
37
|
import { ChatReadStateService } from "./data/read-state-service.js";
|
|
36
|
-
import { ChatRoomService } from "./data/room-service.js";
|
|
38
|
+
import { ChatRoomService, PiboRoomHierarchyCycleError } from "./data/room-service.js";
|
|
37
39
|
import { ChatSessionQueryService } from "./data/session-query-service.js";
|
|
38
40
|
import { ChatTimelineQueryService } from "./data/timeline-query-service.js";
|
|
39
41
|
import { ChatHistoryQueryService } from "./data/history-query-service.js";
|
|
@@ -55,7 +57,7 @@ import { chatSettingsRoute, chatSettingsRouteInvalidatesBootstrapCatalog, chatSe
|
|
|
55
57
|
import { chatCapabilityRoute, chatCapabilityRouteRequiresSameOrigin, handleChatCapabilityRoute, } from "./chat-capability-routes.js";
|
|
56
58
|
import { chatUserSkillRoute, chatUserSkillRouteRequiresSameOrigin, handleChatUserSkillRoute, syncChatUserSkills, } from "./chat-user-skill-routes.js";
|
|
57
59
|
import { CHAT_WEB_API_PREFIX, agentFolderResourceId, agentResourceId, projectResourcePath, projectSessionResourceId, projectWorkflowHumanActionsResource, projectWorkflowSessionStartResource, roomResourcePath, sessionActionResource, sessionResourceId, signalResource, workflowArchiveResourceId, workflowCatalogResourceId, workflowDraftActionResource, workflowDraftManualTriggerRunResource, workflowDraftResourceId, workflowDuplicateResourceId, workflowNextDraftResourceId, workflowPickerKind, workflowPromptAssetResourceId, workflowVersionResource, } from "./chat-api-routes.js";
|
|
58
|
-
import { assertProjectSessionPatchFields, buildStreamingFixtureSchedule, createAgentInput, createAgentUpdate, createRoomUpdate, createSessionUpdate, normalizeAgentArchived, normalizeAgentFolderName, normalizeClientTxnId, normalizeMessageDelivery, normalizeMessageText, normalizeParentRoomId, normalizeProjectArchived, normalizeProjectDescription, normalizeProjectPath, normalizeProjectSessionArchived, normalizeRoomDeleteConfirmation, normalizeRoomName, normalizeRoomTopic, normalizeRoomType, normalizeRoomWorkspace, normalizeSessionDeleteConfirmation, normalizeSessionTitle, normalizeStreamingFixtureCadenceMs, normalizeStreamingFixtureDeltas, normalizeStreamingFixtureMix, normalizeStreamingFixturePreludeMessages, normalizeStreamingFixturePreludeOnly, normalizeStreamingFixtureProfile, normalizeStreamingFixtureSuppressLiveDeltas, normalizeStreamingFixtureTraceSnapshots, resolveCreateSessionProfile, } from "./chat-request-normalizers.js";
|
|
60
|
+
import { assertProjectSessionPatchFields, buildStreamingFixtureSchedule, createAgentInput, createAgentUpdate, createRoomUpdate, createSessionUpdate, normalizeAgentArchived, normalizeAgentFolderName, normalizeClientTxnId, normalizeMessageDelivery, normalizeMessageText, normalizeParentRoomId, normalizeProjectArchived, normalizeProjectDescription, normalizeProjectPath, normalizeProjectSessionArchived, normalizeRoomDeleteConfirmation, normalizeRoomName, normalizeRoomTopic, normalizeRoomType, normalizeRoomWorkspace, normalizeSessionDeleteConfirmation, normalizeSessionTitle, normalizeStreamingFixtureCadenceMs, normalizeStreamingFixtureDeltas, normalizeStreamingFixtureMix, normalizeStreamingFixturePreludeMessages, normalizeStreamingFixturePreludeOnly, normalizeStreamingFixtureProfile, normalizeStreamingFixtureSuppressLiveDeltas, normalizeStreamingFixtureTraceSnapshots, normalizeStreamingFixtureToolInterleaving, resolveCreateSessionProfile, } from "./chat-request-normalizers.js";
|
|
59
61
|
import { ChatWorkflowArchiveStore, ChatWorkflowDraftStore, ChatWorkflowLifecycleEventStore, ChatWorkflowPromptAssetStore, ChatWorkflowPublishedVersionStore, ChatWorkflowTombstoneStore, hashWorkflowDefinitionJson, normalizeWorkflowPromptAssetLabel, sanitizeWorkflowDiagnostics, } from "./workflow-persistence.js";
|
|
60
62
|
import { normalizeProjectWorkflowHumanActionBody, projectWorkflowHumanActionDiagnosticResponse, projectWorkflowHumanActionLifecyclePayload, projectWorkflowHumanActionRuntimeDiagnostic, projectWorkflowHumanActionSubmittedLifecyclePayload, projectWorkflowPendingHumanActionFromToken, validateProjectWorkflowHumanActionRequest, } from "./project-workflow-human-actions.js";
|
|
61
63
|
import { createProjectWorkflowRunCurrent, createProjectWorkflowSessionSnapshot, normalizeProjectWorkflowSessionConfiguration, workflowVersionFromSnapshot, } from "./project-workflow-sessions.js";
|
|
@@ -396,80 +398,210 @@ function ensureEventIndexing(state, context) {
|
|
|
396
398
|
state.subscribedContext = context;
|
|
397
399
|
state.unsubscribe = context.channelContext.subscribe((event) => {
|
|
398
400
|
const startedAt = performance.now();
|
|
401
|
+
let prepared;
|
|
399
402
|
try {
|
|
400
|
-
|
|
401
|
-
|
|
403
|
+
if (!isPiboOutputEvent(event)) {
|
|
404
|
+
state.outputPersistenceRetries.quarantine("runtime_output_event_invalid");
|
|
405
|
+
recordPersistenceError(state.persistenceMetrics, new Error("runtime_output_event_invalid"));
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const positionedEvent = state.outputRenderSequencer.position(event);
|
|
409
|
+
state.activeTraceSessions.add(positionedEvent.piboSessionId);
|
|
410
|
+
const session = context.channelContext.getSession(positionedEvent.piboSessionId);
|
|
402
411
|
const room = session ? ensureSessionRoom(state, context, session) : undefined;
|
|
403
|
-
const result = state.outputCompactor.
|
|
412
|
+
const result = state.outputCompactor.prepare(positionedEvent);
|
|
413
|
+
prepared = result;
|
|
404
414
|
for (const liveEvent of result.liveEvents) {
|
|
405
415
|
if (isPersistableOutputEvent(liveEvent))
|
|
406
416
|
continue;
|
|
407
|
-
state.sessionQuery.recordEvent(liveEvent, session);
|
|
408
417
|
const transient = recordTransientReplayEvent(state, { roomId: room?.id, piboSessionId: liveEvent.piboSessionId, eventType: liveEvent.type, payload: liveEvent });
|
|
418
|
+
state.sessionQuery.recordEvent(liveEvent, session, undefined, new Date(transient.createdAtMs).toISOString());
|
|
409
419
|
if (isLiveOnlyOutputEvent(liveEvent) && !hasLiveObserver(state, liveEvent.piboSessionId))
|
|
410
420
|
continue;
|
|
411
421
|
for (const listener of state.liveListeners) {
|
|
412
422
|
listener(transient);
|
|
413
423
|
}
|
|
414
424
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
425
|
+
const persistableEvents = result.persistedEvents.filter(isPersistableOutputEvent);
|
|
426
|
+
if (persistableEvents.length === 0) {
|
|
427
|
+
result.ack();
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
const persistenceState = {
|
|
431
|
+
version: 1,
|
|
432
|
+
piboSessionId: positionedEvent.piboSessionId,
|
|
419
433
|
roomId: room?.id,
|
|
420
434
|
actorId: session?.id,
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
eventId: "eventId" in persistableEvent && typeof persistableEvent.eventId === "string" ? persistableEvent.eventId : `pibo.output:${persistableEvent.type}:${ingested.streamId}`,
|
|
437
|
-
eventType: persistableEvent.type,
|
|
438
|
-
actorType: "assistant",
|
|
439
|
-
actorId: session.id,
|
|
440
|
-
createdAt,
|
|
441
|
-
retentionClass: reliabilityRetentionClassForOutputEvent(persistableEvent),
|
|
442
|
-
payload: persistableEvent,
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
catch (error) {
|
|
446
|
-
console.warn("[chat-web] failed to write output event into V2", error);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
if (!stored)
|
|
450
|
-
continue;
|
|
451
|
-
if (persistableEvent.type === "assistant_message" || persistableEvent.type === "message_finished" || persistableEvent.type === "session_error") {
|
|
452
|
-
markActiveSessionRead(state, persistableEvent.piboSessionId, stored.streamId);
|
|
453
|
-
}
|
|
454
|
-
state.sessionQuery.recordEvent(persistableEvent, session, stored.streamId);
|
|
455
|
-
state.reliabilityStore.append({
|
|
456
|
-
topic: "pibo.output",
|
|
457
|
-
key: persistableEvent.piboSessionId,
|
|
458
|
-
eventId: `pibo.output:${persistableEvent.piboSessionId}:${persistableEvent.type}:${randomUUID()}`,
|
|
459
|
-
retentionClass: reliabilityRetentionClassForOutputEvent(persistableEvent),
|
|
460
|
-
payload: boundedReliabilityOutputPayload(state, persistableEvent),
|
|
461
|
-
});
|
|
462
|
-
for (const listener of state.liveListeners)
|
|
463
|
-
listener(stored);
|
|
435
|
+
deliveries: persistableEvents.map((persistableEvent) => ({
|
|
436
|
+
deliveryId: outputPersistenceDeliveryKey(persistableEvent),
|
|
437
|
+
event: persistableEvent,
|
|
438
|
+
})),
|
|
439
|
+
};
|
|
440
|
+
state.outputPersistenceRetries.enqueue(createWebOutputPersistenceJob({
|
|
441
|
+
state,
|
|
442
|
+
context,
|
|
443
|
+
persistenceState,
|
|
444
|
+
onSuccess: result.ack,
|
|
445
|
+
onDeadLetter: (error) => {
|
|
446
|
+
result.rollback();
|
|
447
|
+
recordPersistenceError(state.persistenceMetrics, error);
|
|
448
|
+
},
|
|
449
|
+
}));
|
|
464
450
|
}
|
|
465
451
|
recordPersistenceDuration(state.persistenceMetrics, performance.now() - startedAt);
|
|
466
452
|
}
|
|
467
453
|
catch (error) {
|
|
454
|
+
prepared?.rollback();
|
|
468
455
|
recordPersistenceError(state.persistenceMetrics, error);
|
|
469
456
|
console.error("[chat-web] failed to index router event", error);
|
|
470
|
-
throw error;
|
|
471
457
|
}
|
|
472
458
|
});
|
|
459
|
+
state.outputPersistenceRetries.recover((job) => {
|
|
460
|
+
const persistenceState = parseWebOutputPersistenceState(job.payload);
|
|
461
|
+
if (!persistenceState)
|
|
462
|
+
throw new Error(`Invalid durable output persistence job ${job.key}`);
|
|
463
|
+
return createWebOutputPersistenceJob({ state, context, persistenceState });
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
function createWebOutputPersistenceJob(input) {
|
|
467
|
+
const firstEvent = input.persistenceState.deliveries[0]?.event;
|
|
468
|
+
return {
|
|
469
|
+
key: JSON.stringify(input.persistenceState.deliveries.map((delivery) => outputPersistenceDeliveryKey(delivery.event))),
|
|
470
|
+
piboSessionId: input.persistenceState.piboSessionId,
|
|
471
|
+
eventId: firstEvent ? outputPersistenceDeliveryKey(firstEvent) : undefined,
|
|
472
|
+
payload: input.persistenceState,
|
|
473
|
+
run: (retryContext) => deliverWebOutputPersistenceState(input.state, input.context, retryContext),
|
|
474
|
+
onSuccess: input.onSuccess,
|
|
475
|
+
onDeadLetter: input.onDeadLetter,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function deliverWebOutputPersistenceState(state, context, retryContext) {
|
|
479
|
+
const persistenceState = parseWebOutputPersistenceState(retryContext.payload);
|
|
480
|
+
if (!persistenceState)
|
|
481
|
+
throw new Error("Invalid durable web output persistence state");
|
|
482
|
+
const session = context.channelContext.getSession(persistenceState.piboSessionId);
|
|
483
|
+
if (!session)
|
|
484
|
+
throw new Error(`No session available for ${persistenceState.piboSessionId}`);
|
|
485
|
+
for (const delivery of persistenceState.deliveries) {
|
|
486
|
+
if (!delivery.v2) {
|
|
487
|
+
const createdAt = new Date().toISOString();
|
|
488
|
+
const ingested = state.ingestService.ingestOutputEvent({
|
|
489
|
+
session,
|
|
490
|
+
roomId: persistenceState.roomId,
|
|
491
|
+
actorId: persistenceState.actorId ?? session.id,
|
|
492
|
+
event: delivery.event,
|
|
493
|
+
createdAt,
|
|
494
|
+
});
|
|
495
|
+
const storedEvent = state.dataStore.eventLog.findByIdempotencyKey(delivery.deliveryId);
|
|
496
|
+
if (!storedEvent || storedEvent.streamId !== ingested.streamId) {
|
|
497
|
+
throw new Error(`Missing V2 event ${ingested.streamId} for ${delivery.deliveryId}`);
|
|
498
|
+
}
|
|
499
|
+
delivery.v2 = {
|
|
500
|
+
streamId: ingested.streamId,
|
|
501
|
+
createdAt: storedEvent.createdAt,
|
|
502
|
+
eventId: eventIdentityForDelivery(delivery.event),
|
|
503
|
+
duplicate: ingested.duplicate,
|
|
504
|
+
};
|
|
505
|
+
retryContext.updatePayload(persistenceState);
|
|
506
|
+
}
|
|
507
|
+
if (!delivery.reliabilityDelivered) {
|
|
508
|
+
if (delivery.reliabilityPayload === undefined) {
|
|
509
|
+
delivery.reliabilityPayload = boundedReliabilityOutputPayload(state, delivery.event);
|
|
510
|
+
retryContext.updatePayload(persistenceState);
|
|
511
|
+
}
|
|
512
|
+
const deliveryKey = delivery.deliveryId;
|
|
513
|
+
state.reliabilityStore.appendOnce({
|
|
514
|
+
topic: "pibo.output",
|
|
515
|
+
key: delivery.event.piboSessionId,
|
|
516
|
+
eventId: deliveryKey,
|
|
517
|
+
idempotencyKey: deliveryKey,
|
|
518
|
+
retentionClass: reliabilityRetentionClassForOutputEvent(delivery.event),
|
|
519
|
+
payload: delivery.reliabilityPayload,
|
|
520
|
+
});
|
|
521
|
+
delivery.reliabilityDelivered = true;
|
|
522
|
+
retryContext.updatePayload(persistenceState);
|
|
523
|
+
}
|
|
524
|
+
if (!delivery.sideEffectsDelivered && state.reliabilityStore.hasDeliveryReceipt(delivery.deliveryId, "chat-web-observable-v1")) {
|
|
525
|
+
delivery.sideEffectsDelivered = true;
|
|
526
|
+
retryContext.updatePayload(persistenceState);
|
|
527
|
+
}
|
|
528
|
+
else if (!delivery.sideEffectsDelivered) {
|
|
529
|
+
const stored = storedChatEventForDelivery(persistenceState, delivery);
|
|
530
|
+
if (delivery.event.type === "assistant_message" || delivery.event.type === "message_finished" || delivery.event.type === "session_error") {
|
|
531
|
+
markActiveSessionRead(state, delivery.event.piboSessionId, stored.streamId);
|
|
532
|
+
}
|
|
533
|
+
state.sessionQuery.recordEvent(delivery.event, session, stored.streamId, stored.createdAt);
|
|
534
|
+
for (const listener of state.liveListeners) {
|
|
535
|
+
try {
|
|
536
|
+
listener(stored);
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
console.error("[chat-web] live listener failed", error);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// This receipt is deliberately recorded after projection and live sends.
|
|
543
|
+
// A crash before it replays the same deliveryId/streamId at least once;
|
|
544
|
+
// recording before sends would trade duplicates for silent loss.
|
|
545
|
+
state.reliabilityStore.recordDeliveryReceipt(delivery.deliveryId, "chat-web-observable-v1");
|
|
546
|
+
delivery.sideEffectsDelivered = true;
|
|
547
|
+
retryContext.updatePayload(persistenceState);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
function storedChatEventForDelivery(persistenceState, delivery) {
|
|
552
|
+
if (!delivery.v2)
|
|
553
|
+
throw new Error(`Missing V2 phase for ${outputPersistenceDeliveryKey(delivery.event)}`);
|
|
554
|
+
return {
|
|
555
|
+
streamId: delivery.v2.streamId,
|
|
556
|
+
...(persistenceState.roomId ? { roomId: persistenceState.roomId } : {}),
|
|
557
|
+
piboSessionId: persistenceState.piboSessionId,
|
|
558
|
+
eventId: delivery.v2.eventId,
|
|
559
|
+
eventType: delivery.event.type,
|
|
560
|
+
actorType: "assistant",
|
|
561
|
+
...(persistenceState.actorId ? { actorId: persistenceState.actorId } : {}),
|
|
562
|
+
createdAt: delivery.v2.createdAt,
|
|
563
|
+
retentionClass: reliabilityRetentionClassForOutputEvent(delivery.event),
|
|
564
|
+
payload: delivery.event,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
function eventIdentityForDelivery(event) {
|
|
568
|
+
return "eventId" in event && typeof event.eventId === "string"
|
|
569
|
+
? event.eventId
|
|
570
|
+
: outputPersistenceDeliveryKey(event);
|
|
571
|
+
}
|
|
572
|
+
function parseWebOutputPersistenceState(value) {
|
|
573
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
574
|
+
return undefined;
|
|
575
|
+
const candidate = value;
|
|
576
|
+
if (candidate.version !== 1 || typeof candidate.piboSessionId !== "string" || !Array.isArray(candidate.deliveries))
|
|
577
|
+
return undefined;
|
|
578
|
+
const deliveries = [];
|
|
579
|
+
for (const rawDelivery of candidate.deliveries) {
|
|
580
|
+
if (!rawDelivery || typeof rawDelivery !== "object" || Array.isArray(rawDelivery))
|
|
581
|
+
return undefined;
|
|
582
|
+
const delivery = rawDelivery;
|
|
583
|
+
if (!isPiboOutputEvent(delivery.event) || !outputIdempotencyKey(delivery.event))
|
|
584
|
+
return undefined;
|
|
585
|
+
const deliveryId = outputPersistenceDeliveryKey(delivery.event);
|
|
586
|
+
if (delivery.deliveryId !== undefined && delivery.deliveryId !== deliveryId)
|
|
587
|
+
return undefined;
|
|
588
|
+
const v2 = delivery.v2;
|
|
589
|
+
if (v2 !== undefined && (!v2 || typeof v2 !== "object" || Array.isArray(v2)
|
|
590
|
+
|| !Number.isSafeInteger(v2.streamId)
|
|
591
|
+
|| typeof v2.createdAt !== "string"
|
|
592
|
+
|| typeof v2.eventId !== "string"))
|
|
593
|
+
return undefined;
|
|
594
|
+
deliveries.push({ ...rawDelivery, deliveryId });
|
|
595
|
+
}
|
|
596
|
+
if (!deliveries.length)
|
|
597
|
+
return undefined;
|
|
598
|
+
return {
|
|
599
|
+
version: 1,
|
|
600
|
+
piboSessionId: candidate.piboSessionId,
|
|
601
|
+
...(typeof candidate.roomId === "string" ? { roomId: candidate.roomId } : {}),
|
|
602
|
+
...(typeof candidate.actorId === "string" ? { actorId: candidate.actorId } : {}),
|
|
603
|
+
deliveries,
|
|
604
|
+
};
|
|
473
605
|
}
|
|
474
606
|
function reliabilityRetentionClassForOutputEvent(event) {
|
|
475
607
|
if (event.type === "assistant_delta" || event.type === "thinking_delta" || event.type === "tool_execution_updated")
|
|
@@ -943,6 +1075,7 @@ function ensureCustomAgentProfiles(state, context) {
|
|
|
943
1075
|
function serializeCustomAgent(agent, context) {
|
|
944
1076
|
return {
|
|
945
1077
|
...agent,
|
|
1078
|
+
brokenNativeTools: listBrokenNativeTools(agent.nativeTools, context),
|
|
946
1079
|
brokenContextFiles: listBrokenContextFiles(agent.contextFiles, context),
|
|
947
1080
|
};
|
|
948
1081
|
}
|
|
@@ -976,6 +1109,7 @@ const RUNTIME_PROFILE_UPDATE_FIELDS = new Set([
|
|
|
976
1109
|
"mcpServers",
|
|
977
1110
|
"piPackages",
|
|
978
1111
|
"mainModel",
|
|
1112
|
+
"mainModelFallbacks",
|
|
979
1113
|
"subagentModel",
|
|
980
1114
|
"thinkingLevel",
|
|
981
1115
|
"mainThinkingLevel",
|
|
@@ -1040,6 +1174,13 @@ async function requireValidCustomAgentRuntime(agent, context) {
|
|
|
1040
1174
|
return;
|
|
1041
1175
|
throw new PiboWebHttpError(`Agent runtime selection is invalid: ${errors.map((diagnostic) => diagnostic.message).join(" ")}`, 400);
|
|
1042
1176
|
}
|
|
1177
|
+
function listBrokenNativeTools(names, context) {
|
|
1178
|
+
const catalog = context.channelContext.getCapabilityCatalog?.();
|
|
1179
|
+
if (!catalog)
|
|
1180
|
+
return [];
|
|
1181
|
+
const knownNames = new Set(catalog.nativeTools.map((tool) => tool.name));
|
|
1182
|
+
return names.filter((name) => !knownNames.has(name));
|
|
1183
|
+
}
|
|
1043
1184
|
function listBrokenContextFiles(keys, context) {
|
|
1044
1185
|
const catalog = context.channelContext.getCapabilityCatalog?.();
|
|
1045
1186
|
if (!catalog)
|
|
@@ -2211,6 +2352,17 @@ function normalizeAgentDeleteConfirmation(value) {
|
|
|
2211
2352
|
}
|
|
2212
2353
|
return value.trim();
|
|
2213
2354
|
}
|
|
2355
|
+
function requireCustomAgentNotTargeted(state, agent, options = {}) {
|
|
2356
|
+
const dependents = state.agentStore.listReferencingAgents(agent.id)
|
|
2357
|
+
.filter((dependent) => !options.activeOnly || !dependent.archivedAt);
|
|
2358
|
+
if (dependents.length === 0)
|
|
2359
|
+
return;
|
|
2360
|
+
const error = new CustomAgentTargetReferenceError(agent.profileName, dependents.map((dependent) => dependent.profileName));
|
|
2361
|
+
const message = options.activeOnly
|
|
2362
|
+
? error.message.replace("targeted by custom agents", "targeted by active custom agents")
|
|
2363
|
+
: error.message;
|
|
2364
|
+
throw new PiboWebHttpError(message, 409);
|
|
2365
|
+
}
|
|
2214
2366
|
async function deleteSessionsForAgentProfile(state, context, webSession, profileNames) {
|
|
2215
2367
|
const deleteSession = context.channelContext.deleteSession;
|
|
2216
2368
|
if (!deleteSession)
|
|
@@ -2230,10 +2382,13 @@ async function deleteSessionsForAgentProfile(state, context, webSession, profile
|
|
|
2230
2382
|
}
|
|
2231
2383
|
}
|
|
2232
2384
|
const orderedIds = [...ids].sort((left, right) => sessionDepth(sessionsById.get(right), sessionsById) - sessionDepth(sessionsById.get(left), sessionsById));
|
|
2385
|
+
for (const id of orderedIds) {
|
|
2386
|
+
await state.projectService.deleteProjectSessionWithCanonicalDelete(id, () => deleteSession(id));
|
|
2387
|
+
state.sessionQuery.deleteSessions([id]);
|
|
2388
|
+
state.eventCommands.deleteSessions([id]);
|
|
2389
|
+
}
|
|
2233
2390
|
for (const id of orderedIds)
|
|
2234
|
-
|
|
2235
|
-
state.sessionQuery.deleteSessions(orderedIds);
|
|
2236
|
-
state.eventCommands.deleteSessions(orderedIds);
|
|
2391
|
+
disposeSessionStreamState(state, id);
|
|
2237
2392
|
return orderedIds;
|
|
2238
2393
|
}
|
|
2239
2394
|
async function deleteSessionSubtree(state, context, webSession, rootSession) {
|
|
@@ -2254,10 +2409,13 @@ async function deleteSessionSubtree(state, context, webSession, rootSession) {
|
|
|
2254
2409
|
}
|
|
2255
2410
|
}
|
|
2256
2411
|
const orderedIds = [...ids].sort((left, right) => sessionDepth(sessionsById.get(right), sessionsById) - sessionDepth(sessionsById.get(left), sessionsById));
|
|
2412
|
+
for (const id of orderedIds) {
|
|
2413
|
+
await state.projectService.deleteProjectSessionWithCanonicalDelete(id, () => deleteSession(id));
|
|
2414
|
+
state.sessionQuery.deleteSessions([id]);
|
|
2415
|
+
state.eventCommands.deleteSessions([id]);
|
|
2416
|
+
}
|
|
2257
2417
|
for (const id of orderedIds)
|
|
2258
|
-
|
|
2259
|
-
state.sessionQuery.deleteSessions(orderedIds);
|
|
2260
|
-
state.eventCommands.deleteSessions(orderedIds);
|
|
2418
|
+
disposeSessionStreamState(state, id);
|
|
2261
2419
|
return orderedIds;
|
|
2262
2420
|
}
|
|
2263
2421
|
async function deleteRoomTree(state, context, webSession, room, confirmName) {
|
|
@@ -2292,15 +2450,23 @@ async function deleteRoomTree(state, context, webSession, room, confirmName) {
|
|
|
2292
2450
|
}
|
|
2293
2451
|
}
|
|
2294
2452
|
const orderedSessionIds = [...ids].sort((left, right) => sessionDepth(sessionsById.get(right), sessionsById) - sessionDepth(sessionsById.get(left), sessionsById));
|
|
2453
|
+
for (const id of orderedSessionIds) {
|
|
2454
|
+
await state.projectService.deleteProjectSessionWithCanonicalDelete(id, () => deleteSession(id));
|
|
2455
|
+
state.sessionQuery.deleteSessions([id]);
|
|
2456
|
+
state.eventCommands.deleteSessions([id]);
|
|
2457
|
+
}
|
|
2295
2458
|
for (const id of orderedSessionIds)
|
|
2296
|
-
|
|
2297
|
-
state.sessionQuery.deleteSessions(orderedSessionIds);
|
|
2298
|
-
state.eventCommands.deleteSessions(orderedSessionIds);
|
|
2459
|
+
disposeSessionStreamState(state, id);
|
|
2299
2460
|
state.eventCommands.deleteRooms(roomIds);
|
|
2300
2461
|
const orderedRoomIds = [...roomIds].reverse();
|
|
2301
2462
|
state.roomService.deleteRooms(orderedRoomIds);
|
|
2302
2463
|
return { deletedRoomIds: orderedRoomIds, deletedSessionIds: orderedSessionIds };
|
|
2303
2464
|
}
|
|
2465
|
+
function disposeSessionStreamState(state, piboSessionId) {
|
|
2466
|
+
state.outputCompactor.disposeSession(piboSessionId);
|
|
2467
|
+
state.outputRenderSequencer.disposeSession(piboSessionId);
|
|
2468
|
+
state.activeTraceSessions.delete(piboSessionId);
|
|
2469
|
+
}
|
|
2304
2470
|
function sessionDepth(session, sessionsById) {
|
|
2305
2471
|
let depth = 0;
|
|
2306
2472
|
let current = session;
|
|
@@ -2652,9 +2818,6 @@ function parseEventStreamMode(value, fallback) {
|
|
|
2652
2818
|
return value;
|
|
2653
2819
|
return fallback;
|
|
2654
2820
|
}
|
|
2655
|
-
function isPiboOutputEvent(value) {
|
|
2656
|
-
return !!value && typeof value === "object" && !Array.isArray(value) && typeof value.type === "string";
|
|
2657
|
-
}
|
|
2658
2821
|
function liveEventMatches(event, input) {
|
|
2659
2822
|
if (input.roomId && event.roomId !== input.roomId)
|
|
2660
2823
|
return false;
|
|
@@ -3236,21 +3399,23 @@ function startChatStreamingFixture(input) {
|
|
|
3236
3399
|
const deltas = normalizeStreamingFixtureDeltas(input.body.deltas, mix);
|
|
3237
3400
|
const traceSnapshots = normalizeStreamingFixtureTraceSnapshots(input.body.traceSnapshots);
|
|
3238
3401
|
const suppressLiveDeltas = normalizeStreamingFixtureSuppressLiveDeltas(input.body.suppressLiveDeltas);
|
|
3402
|
+
const toolInterleaving = normalizeStreamingFixtureToolInterleaving(input.body.toolInterleaving);
|
|
3239
3403
|
const scheduleMs = buildStreamingFixtureSchedule(deltas.length, cadenceMs, profile);
|
|
3240
3404
|
const reasoningDeltas = mix === "reasoning-text" ? [" think", " plan", " check", " answer"] : [];
|
|
3241
3405
|
const reasoningScheduleMs = reasoningDeltas.map((_, index) => Math.max(10, Math.round(((index + 1) * cadenceMs) / 2)));
|
|
3242
3406
|
const eventId = `streaming-fixture-${randomUUID()}`;
|
|
3243
3407
|
const emit = (event) => {
|
|
3244
|
-
|
|
3245
|
-
|
|
3408
|
+
const positionedEvent = input.state.outputRenderSequencer.position(event);
|
|
3409
|
+
if (traceSnapshots && (positionedEvent.type === "assistant_delta" || positionedEvent.type === "assistant_message" || positionedEvent.type === "message_finished")) {
|
|
3410
|
+
input.state.outputCompactor.compact(positionedEvent);
|
|
3246
3411
|
}
|
|
3247
|
-
if (suppressLiveDeltas &&
|
|
3412
|
+
if (suppressLiveDeltas && positionedEvent.type === "assistant_delta")
|
|
3248
3413
|
return;
|
|
3249
3414
|
const liveEvent = recordTransientReplayEvent(input.state, {
|
|
3250
3415
|
roomId: room.id,
|
|
3251
3416
|
piboSessionId: selectedSession.id,
|
|
3252
|
-
eventType:
|
|
3253
|
-
payload:
|
|
3417
|
+
eventType: positionedEvent.type,
|
|
3418
|
+
payload: positionedEvent,
|
|
3254
3419
|
});
|
|
3255
3420
|
for (const listener of input.state.liveListeners)
|
|
3256
3421
|
listener(liveEvent);
|
|
@@ -3277,6 +3442,17 @@ function startChatStreamingFixture(input) {
|
|
|
3277
3442
|
});
|
|
3278
3443
|
}
|
|
3279
3444
|
emit({ type: "message_started", piboSessionId: selectedSession.id, eventId, text: "Streaming benchmark fixture", source: "service" });
|
|
3445
|
+
const toolBase = {
|
|
3446
|
+
piboSessionId: selectedSession.id,
|
|
3447
|
+
eventId,
|
|
3448
|
+
toolCallId: "streaming-fixture-reused-tool",
|
|
3449
|
+
toolName: "read",
|
|
3450
|
+
};
|
|
3451
|
+
if (toolInterleaving) {
|
|
3452
|
+
emit({ ...toolBase, type: "tool_call", args: { path: "README.md" }, argsComplete: true });
|
|
3453
|
+
emitAt(Math.max(10, Math.round(cadenceMs / 3)), { ...toolBase, type: "tool_execution_started", args: { path: "README.md" } });
|
|
3454
|
+
emitAt(Math.max(20, Math.round(cadenceMs * 1.5)), { ...toolBase, type: "tool_execution_updated", args: { path: "README.md" }, partialResult: "Streaming fixture tool update" });
|
|
3455
|
+
}
|
|
3280
3456
|
if (reasoningDeltas.length) {
|
|
3281
3457
|
emit({ type: "thinking_started", piboSessionId: selectedSession.id, eventId, thinkingIndex: 0 });
|
|
3282
3458
|
reasoningDeltas.forEach((delta, index) => {
|
|
@@ -3290,6 +3466,9 @@ function startChatStreamingFixture(input) {
|
|
|
3290
3466
|
const finalText = deltas.join("");
|
|
3291
3467
|
const finalReasoning = reasoningDeltas.join("");
|
|
3292
3468
|
const finishDelayMs = Math.max(scheduleMs[scheduleMs.length - 1] ?? 0, reasoningScheduleMs[reasoningScheduleMs.length - 1] ?? 0) + cadenceMs;
|
|
3469
|
+
if (toolInterleaving) {
|
|
3470
|
+
emitAt(Math.max(30, finishDelayMs - Math.max(10, Math.round(cadenceMs / 2))), { ...toolBase, type: "tool_execution_finished", result: "Streaming fixture tool result", isError: false });
|
|
3471
|
+
}
|
|
3293
3472
|
emitAt(finishDelayMs, { type: "assistant_message", piboSessionId: selectedSession.id, eventId, assistantIndex: 0, text: finalText });
|
|
3294
3473
|
emitAt(finishDelayMs, { type: "message_finished", piboSessionId: selectedSession.id, eventId, source: "service" });
|
|
3295
3474
|
return responseJson({
|
|
@@ -3304,6 +3483,7 @@ function startChatStreamingFixture(input) {
|
|
|
3304
3483
|
preludeMessages,
|
|
3305
3484
|
traceSnapshots,
|
|
3306
3485
|
suppressLiveDeltas,
|
|
3486
|
+
toolInterleaving,
|
|
3307
3487
|
scheduleMs,
|
|
3308
3488
|
reasoningScheduleMs,
|
|
3309
3489
|
reasoningDeltaCount: reasoningDeltas.length,
|
|
@@ -3428,6 +3608,7 @@ export function createChatWebApp(options = {}) {
|
|
|
3428
3608
|
ensurePrivateChatUploadDirectory();
|
|
3429
3609
|
const defaultProfile = options.defaultProfile ?? "base";
|
|
3430
3610
|
const dataStore = createDataStore(options);
|
|
3611
|
+
const reliabilityStore = createReliabilityStore(options.reliabilityStorePath);
|
|
3431
3612
|
const eventLoopDelay = monitorEventLoopDelay({ resolution: 20 });
|
|
3432
3613
|
eventLoopDelay.enable();
|
|
3433
3614
|
const state = {
|
|
@@ -3439,7 +3620,7 @@ export function createChatWebApp(options = {}) {
|
|
|
3439
3620
|
roomService: new ChatRoomService(dataStore),
|
|
3440
3621
|
projectService: new ChatProjectService(options.projectStorePath),
|
|
3441
3622
|
agentStore: createAgentStore(options.agentStorePath),
|
|
3442
|
-
reliabilityStore
|
|
3623
|
+
reliabilityStore,
|
|
3443
3624
|
cronStore: createDefaultPiboCronStore({ path: options.cronStorePath }),
|
|
3444
3625
|
loopStore: createDefaultPiboLoopStore({ path: options.ralphStorePath }),
|
|
3445
3626
|
dataStore,
|
|
@@ -3447,6 +3628,8 @@ export function createChatWebApp(options = {}) {
|
|
|
3447
3628
|
traceCache: new Map(),
|
|
3448
3629
|
traceTimelinePageCache: new Map(),
|
|
3449
3630
|
outputCompactor: new OutputCompactor(),
|
|
3631
|
+
outputPersistenceRetries: new OutputPersistenceRetryQueue({ durableStore: reliabilityStore }),
|
|
3632
|
+
outputRenderSequencer: new OutputRenderSequencer(),
|
|
3450
3633
|
liveListeners: new Set(),
|
|
3451
3634
|
transientReplaySequence: 0,
|
|
3452
3635
|
transientReplayBuffer: [],
|
|
@@ -3486,11 +3669,14 @@ export function createChatWebApp(options = {}) {
|
|
|
3486
3669
|
state.unsubscribe = undefined;
|
|
3487
3670
|
state.subscribedContext = undefined;
|
|
3488
3671
|
state.eventLoopDelay.disable();
|
|
3672
|
+
state.outputPersistenceRetries.dispose();
|
|
3489
3673
|
state.projectService.close();
|
|
3490
3674
|
state.agentStore.close();
|
|
3491
3675
|
state.reliabilityStore.close();
|
|
3492
3676
|
state.cronStore.close();
|
|
3493
3677
|
state.loopStore.close();
|
|
3678
|
+
state.outputCompactor.disposeAll();
|
|
3679
|
+
state.outputRenderSequencer.disposeAll();
|
|
3494
3680
|
state.dataStore.close();
|
|
3495
3681
|
},
|
|
3496
3682
|
async handleRequest(request, context) {
|
|
@@ -3789,7 +3975,9 @@ export function createChatWebApp(options = {}) {
|
|
|
3789
3975
|
const webSession = await requireSession(request, context);
|
|
3790
3976
|
const body = await readJsonBody(request);
|
|
3791
3977
|
try {
|
|
3792
|
-
requireSharedProject(state, webSession, projectResource.projectId, { includeArchived: true });
|
|
3978
|
+
const existingProject = requireSharedProject(state, webSession, projectResource.projectId, { includeArchived: true });
|
|
3979
|
+
if (existingProject.metadata.default === true)
|
|
3980
|
+
throw new PiboWebHttpError("Project Manager cannot be changed", 400);
|
|
3793
3981
|
const project = state.projectService.updateProject(projectResource.projectId, {
|
|
3794
3982
|
...(body.name !== undefined ? { name: normalizeRoomName(body.name) } : {}),
|
|
3795
3983
|
...(body.description !== undefined ? { description: normalizeProjectDescription(body.description) ?? null } : {}),
|
|
@@ -3810,7 +3998,9 @@ export function createChatWebApp(options = {}) {
|
|
|
3810
3998
|
const webSession = await requireSession(request, context);
|
|
3811
3999
|
const body = await readJsonBody(request);
|
|
3812
4000
|
try {
|
|
3813
|
-
requireSharedProject(state, webSession, projectResource.projectId, { includeArchived: true });
|
|
4001
|
+
const project = requireSharedProject(state, webSession, projectResource.projectId, { includeArchived: true });
|
|
4002
|
+
if (project.metadata.default === true)
|
|
4003
|
+
throw new PiboWebHttpError("Project Manager cannot be deleted", 400);
|
|
3814
4004
|
return responseJson(state.projectService.deleteProject(projectResource.projectId, {
|
|
3815
4005
|
confirmName: normalizeRoomDeleteConfirmation(body.confirmName),
|
|
3816
4006
|
deleteFiles: body.deleteFiles === true,
|
|
@@ -4100,22 +4290,43 @@ export function createChatWebApp(options = {}) {
|
|
|
4100
4290
|
if (!rootPiboSessionId)
|
|
4101
4291
|
throw new PiboWebHttpError("rootPiboSessionId is required", 400);
|
|
4102
4292
|
requireSharedSession(context, rootPiboSessionId);
|
|
4103
|
-
|
|
4293
|
+
const includeStatuses = parseBooleanSearchParam(url, "includeStatuses");
|
|
4294
|
+
if (!context.channelContext.snapshotSignalTree
|
|
4295
|
+
|| !context.channelContext.subscribeSignalTree
|
|
4296
|
+
|| (includeStatuses && (!context.channelContext.snapshotSignalStatuses || !context.channelContext.subscribeSignalStatuses))) {
|
|
4104
4297
|
throw new PiboWebHttpError("Signal registry is not available", 503);
|
|
4105
4298
|
}
|
|
4106
|
-
let
|
|
4299
|
+
let unsubscribeTree;
|
|
4300
|
+
let unsubscribeStatuses;
|
|
4107
4301
|
let heartbeat;
|
|
4302
|
+
let closed = false;
|
|
4108
4303
|
const stream = new ReadableStream({
|
|
4109
4304
|
start: (controller) => {
|
|
4110
4305
|
writeJsonSse(controller, "signal_snapshot", context.channelContext.snapshotSignalTree(rootPiboSessionId));
|
|
4111
|
-
|
|
4112
|
-
|
|
4306
|
+
unsubscribeTree = context.channelContext.subscribeSignalTree(rootPiboSessionId, (patch) => {
|
|
4307
|
+
if (!closed)
|
|
4308
|
+
writeJsonSse(controller, "signal_patch", patch, String(patch.toVersion));
|
|
4113
4309
|
});
|
|
4114
|
-
|
|
4310
|
+
if (includeStatuses) {
|
|
4311
|
+
writeJsonSse(controller, "signal_status_snapshot", context.channelContext.snapshotSignalStatuses());
|
|
4312
|
+
unsubscribeStatuses = context.channelContext.subscribeSignalStatuses((patch) => {
|
|
4313
|
+
if (closed)
|
|
4314
|
+
return;
|
|
4315
|
+
const statusPatch = compactSignalStatusPatch(patch);
|
|
4316
|
+
writeJsonSse(controller, "signal_status_patch", statusPatch, `${patch.rootPiboSessionId}:${patch.toVersion}`);
|
|
4317
|
+
});
|
|
4318
|
+
}
|
|
4319
|
+
heartbeat = setInterval(() => {
|
|
4320
|
+
if (!closed)
|
|
4321
|
+
writeSseComment(controller, "heartbeat");
|
|
4322
|
+
}, 25_000);
|
|
4115
4323
|
},
|
|
4116
4324
|
cancel: () => {
|
|
4117
|
-
|
|
4118
|
-
|
|
4325
|
+
closed = true;
|
|
4326
|
+
unsubscribeTree?.();
|
|
4327
|
+
unsubscribeTree = undefined;
|
|
4328
|
+
unsubscribeStatuses?.();
|
|
4329
|
+
unsubscribeStatuses = undefined;
|
|
4119
4330
|
if (heartbeat)
|
|
4120
4331
|
clearInterval(heartbeat);
|
|
4121
4332
|
heartbeat = undefined;
|
|
@@ -4519,6 +4730,9 @@ export function createChatWebApp(options = {}) {
|
|
|
4519
4730
|
if (customAgentUpdateAffectsRuntime(update)) {
|
|
4520
4731
|
await requireValidCustomAgentRuntime(previewCustomAgentUpdate(existing, update), context);
|
|
4521
4732
|
}
|
|
4733
|
+
if (archived === true && !existing.archivedAt) {
|
|
4734
|
+
requireCustomAgentNotTargeted(state, existing, { activeOnly: true });
|
|
4735
|
+
}
|
|
4522
4736
|
const updated = Object.keys(update).length ? state.agentStore.update(patchAgentId, update) : existing;
|
|
4523
4737
|
const afterUpdate = requireSharedAgent(updated);
|
|
4524
4738
|
const agent = archived === undefined ? afterUpdate : state.agentStore.setArchived(patchAgentId, archived);
|
|
@@ -4545,8 +4759,16 @@ export function createChatWebApp(options = {}) {
|
|
|
4545
4759
|
if (confirmName !== agent.profileName) {
|
|
4546
4760
|
throw new PiboWebHttpError(`Type "${agent.profileName}" to permanently delete this agent and its sessions.`, 400);
|
|
4547
4761
|
}
|
|
4762
|
+
requireCustomAgentNotTargeted(state, agent);
|
|
4548
4763
|
const deletedSessionIds = await deleteSessionsForAgentProfile(state, context, webSession, [agent.profileName, ...agent.profileAliases]);
|
|
4549
|
-
|
|
4764
|
+
try {
|
|
4765
|
+
state.agentStore.delete(agent.id);
|
|
4766
|
+
}
|
|
4767
|
+
catch (error) {
|
|
4768
|
+
if (error instanceof CustomAgentTargetReferenceError)
|
|
4769
|
+
throw new PiboWebHttpError(error.message, 409);
|
|
4770
|
+
throw error;
|
|
4771
|
+
}
|
|
4550
4772
|
context.channelContext.removeProfile?.(agent.profileName);
|
|
4551
4773
|
invalidateBootstrapCatalogCache(state);
|
|
4552
4774
|
return responseJson({ deletedAgentId: agent.id, deletedSessionIds });
|
|
@@ -4657,7 +4879,16 @@ export function createChatWebApp(options = {}) {
|
|
|
4657
4879
|
const update = createRoomUpdate(existingRoom, body);
|
|
4658
4880
|
if (update.parentRoomId)
|
|
4659
4881
|
requireRoom(state, update.parentRoomId, webSession, "admin");
|
|
4660
|
-
|
|
4882
|
+
let room;
|
|
4883
|
+
try {
|
|
4884
|
+
room = state.roomService.updateRoom(roomResource.roomId, update);
|
|
4885
|
+
}
|
|
4886
|
+
catch (error) {
|
|
4887
|
+
if (error instanceof PiboRoomHierarchyCycleError) {
|
|
4888
|
+
throw new PiboWebHttpError(error.message, 400);
|
|
4889
|
+
}
|
|
4890
|
+
throw error;
|
|
4891
|
+
}
|
|
4661
4892
|
if (!room)
|
|
4662
4893
|
throw new PiboWebHttpError("Room not found", 404);
|
|
4663
4894
|
return responseJson({ room });
|