@pasko70/pibo 3.0.0 → 3.0.2
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/dist/agent-runtime/context-build.js +26 -2
- package/dist/agent-runtime/routed-session.js +108 -9
- 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 +49 -8
- 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 +43 -3
- 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/session-query-service.js +33 -10
- package/dist/apps/chat/data/timeline-query-service.js +60 -35
- 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 +236 -64
- package/dist/apps/chat-ui/assets/{dist-BBDMeU5-.js → dist-B6GZgWSj.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-mqCviI-c.js → dist-BPotHecb.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-JnW4v8UE.js → dist-BzqQKeVO.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BDIATCQt.js → dist-CvaPTBTN.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-lRiLP9rr.js → dist-paagejNj.js} +1 -1
- package/dist/apps/chat-ui/assets/index-6JJV-eic.js +228 -0
- package/dist/apps/chat-ui/assets/index-CeL9JPP5.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/{index-Dtw2Z7jz.js → index-U-MSErGa.js} +4 -4
- package/dist/apps/chat-vscode-web/index.html +1 -1
- 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/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 +67 -20
- package/dist/data/ingest-service.js +91 -9
- package/dist/data/navigation-store.js +5 -5
- package/dist/data/schema.js +122 -2
- package/dist/data/session-store.js +3 -3
- package/dist/debug/index.js +44 -2
- package/dist/gateway/web.js +8 -8
- package/dist/local/client.js +3 -2
- package/dist/plugins/context-files-store.js +30 -0
- 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/reliability/store.js +246 -37
- package/dist/session-ui/terminalRows.js +6 -3
- 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/tool.js +7 -3
- package/docs/ops/vscode-extension-release.md +9 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- 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/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.0.vsix +0 -0
|
@@ -4,6 +4,7 @@ import { InitialSessionContext, } from "./profiles.js";
|
|
|
4
4
|
import { createDefaultPiboPluginRegistry, createPiboProfileFromRegistryOrDefault, resolvePiboProfileNameFromRegistryOrDefault, selectDefaultPiboProfileName } from "../plugins/builtin.js";
|
|
5
5
|
import { RuntimeRoutedSession as RoutedSession, } from "../agent-runtime/routed-session.js";
|
|
6
6
|
import { runtimeSessionErrorDetails } from "./session-errors.js";
|
|
7
|
+
import { OutputRenderSequencer, outputRenderHighWaterStore } from "./output-render-sequence.js";
|
|
7
8
|
import { normalizePiboAgentSessionName, } from "../subagents/tool.js";
|
|
8
9
|
import { PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES, PIBO_AGENT_OBSERVATION_DEFAULT_TOOL_EVENT_TYPES, normalizePiboAgentObservationCursor, normalizePiboAgentObservationLimit, normalizePiboAgentObservationOrder, normalizePiboAgentObservationToolDetail, parsePiboAgentObservationTimestamp, piboAgentObservationDetails, piboAgentObservationKind, piboAgentObservationRole, piboAgentObservationSourceFromEvent, piboAgentObservationText, piboAgentObservationToolSummary, } from "../subagents/observations.js";
|
|
9
10
|
import { PiboRunRegistry } from "../runs/registry.js";
|
|
@@ -20,7 +21,7 @@ import { getDefaultPiboWorkspace } from "./workspace.js";
|
|
|
20
21
|
import { loadPiboModelDefaults, selectRequestedFastMode } from "./model-defaults.js";
|
|
21
22
|
import { loadPiboGatewaySettings } from "./gateway-settings.js";
|
|
22
23
|
import { loadPiboUserSettings } from "./user-settings.js";
|
|
23
|
-
import { resolvePiboSessionActiveModel } from "./session-model.js";
|
|
24
|
+
import { PIBO_INITIAL_MODEL_FALLBACKS_METADATA_KEY, resolvePiboSessionActiveModel, resolvePiboSessionModelFallbacks, withPiboSessionModelFallbacksMetadata, } from "./session-model.js";
|
|
24
25
|
import { isPiboThinkingLevel } from "./thinking.js";
|
|
25
26
|
import { RuntimeSessionRegistry } from "../tools/runtime/registry.js";
|
|
26
27
|
import { GatewayWorkAdmissionController } from "./gateway-resource-guard.js";
|
|
@@ -93,6 +94,7 @@ function profileForSession(baseProfile, runtimeInstanceId, nativeSessionId, pare
|
|
|
93
94
|
parentSessionId: parentNativeSessionId,
|
|
94
95
|
model: usesProfileRuntime ? baseProfile.model : undefined,
|
|
95
96
|
mainModel: usesProfileRuntime ? baseProfile.mainModel : undefined,
|
|
97
|
+
mainModelFallbacks: usesProfileRuntime ? baseProfile.mainModelFallbacks : undefined,
|
|
96
98
|
subagentModel: usesProfileRuntime ? baseProfile.subagentModel : undefined,
|
|
97
99
|
thinkingLevel: baseProfile.thinkingLevel,
|
|
98
100
|
mainThinkingLevel: baseProfile.mainThinkingLevel,
|
|
@@ -329,6 +331,7 @@ export class PiboSessionRouter {
|
|
|
329
331
|
sessions = new Map();
|
|
330
332
|
pendingSessions = new Map();
|
|
331
333
|
listeners = new Set();
|
|
334
|
+
outputRenderSequencer;
|
|
332
335
|
runRegistry;
|
|
333
336
|
gatewayWorkAdmission = new GatewayWorkAdmissionController();
|
|
334
337
|
signalRegistry;
|
|
@@ -374,6 +377,9 @@ export class PiboSessionRouter {
|
|
|
374
377
|
// Preserve that composition contract during the adapter migration without branching on adapter ids.
|
|
375
378
|
this.compatibilityRuntimeRegistry = options.pluginRegistry ? createDefaultPiboPluginRegistry() : undefined;
|
|
376
379
|
this.sessionStore = options.sessionStore ?? new InMemoryPiboSessionStore();
|
|
380
|
+
this.outputRenderSequencer = new OutputRenderSequencer({
|
|
381
|
+
highWaterStore: outputRenderHighWaterStore(this.sessionStore),
|
|
382
|
+
});
|
|
377
383
|
this.telemetryStore = options.telemetryStore ?? telemetryStoreFromSessionStore(this.sessionStore);
|
|
378
384
|
this.telemetryWriter = this.telemetryStore ? new AsyncTelemetryWriter(this.telemetryStore) : undefined;
|
|
379
385
|
this.telemetryRecorder = this.telemetryStore
|
|
@@ -700,6 +706,7 @@ export class PiboSessionRouter {
|
|
|
700
706
|
if (this.disposingSessions.get(id) === operation)
|
|
701
707
|
this.disposingSessions.delete(id);
|
|
702
708
|
this.quiescingSessions.delete(id);
|
|
709
|
+
this.outputRenderSequencer.disposeSession(id);
|
|
703
710
|
this.signalRegistry.project({ type: "session_disposed", piboSessionId: id, reason });
|
|
704
711
|
}
|
|
705
712
|
await this.telemetryWriter?.flush();
|
|
@@ -1123,6 +1130,7 @@ export class PiboSessionRouter {
|
|
|
1123
1130
|
this.runtimeResourceSessions.clear();
|
|
1124
1131
|
this.activeSubagentRequests.clear();
|
|
1125
1132
|
this.subagentRequestIdsByEvent.clear();
|
|
1133
|
+
this.outputRenderSequencer.disposeAll();
|
|
1126
1134
|
this.agentObservations.length = 0;
|
|
1127
1135
|
this.agentObservationEvictedThroughByParent.clear();
|
|
1128
1136
|
await this.telemetryWriter?.dispose();
|
|
@@ -1229,6 +1237,9 @@ export class PiboSessionRouter {
|
|
|
1229
1237
|
const activeModel = changesRuntimeModelNamespace
|
|
1230
1238
|
? undefined
|
|
1231
1239
|
: this.ensureSessionActiveModel(piboSession, sessionProfile, parentModelScopeId, modelDefaults);
|
|
1240
|
+
const modelFallbacks = changesRuntimeModelNamespace
|
|
1241
|
+
? []
|
|
1242
|
+
: this.ensureSessionModelFallbacks(piboSession, sessionProfile, parentModelScopeId, activeModel);
|
|
1232
1243
|
const userSettings = loadPiboUserSettings();
|
|
1233
1244
|
const telemetryExtension = this.telemetryStore
|
|
1234
1245
|
? createPiboProviderTelemetryExtension({ store: this.telemetryStore, writer: this.telemetryWriter, session: piboSession, model: activeModel })
|
|
@@ -1373,6 +1384,7 @@ export class PiboSessionRouter {
|
|
|
1373
1384
|
],
|
|
1374
1385
|
modelDefaults,
|
|
1375
1386
|
initialFastMode,
|
|
1387
|
+
providerFallbacksEnabled: modelFallbacks.length > 0,
|
|
1376
1388
|
},
|
|
1377
1389
|
},
|
|
1378
1390
|
});
|
|
@@ -1472,6 +1484,7 @@ export class PiboSessionRouter {
|
|
|
1472
1484
|
this.handleInterruptedRunReminders(messages);
|
|
1473
1485
|
},
|
|
1474
1486
|
messagePreflight: this.options.messagePreflight,
|
|
1487
|
+
modelFallbacks,
|
|
1475
1488
|
getRuntimeAuthStatus: () => runtimeRegistry.getAgentRuntimeAuthStatus(binding.runtimeInstanceId),
|
|
1476
1489
|
startRuntimeAuth: async (input) => {
|
|
1477
1490
|
const { runtimeInstanceId: _runtimeInstanceId, ...result } = await runtimeRegistry.startAgentRuntimeAuth(binding.runtimeInstanceId, input);
|
|
@@ -1612,6 +1625,20 @@ export class PiboSessionRouter {
|
|
|
1612
1625
|
}
|
|
1613
1626
|
return activeModel;
|
|
1614
1627
|
}
|
|
1628
|
+
ensureSessionModelFallbacks(piboSession, profile, parentPiSessionId, activeModel) {
|
|
1629
|
+
const modelFallbacks = resolvePiboSessionModelFallbacks({
|
|
1630
|
+
profile,
|
|
1631
|
+
piboSession,
|
|
1632
|
+
parentPiSessionId,
|
|
1633
|
+
activeModel,
|
|
1634
|
+
});
|
|
1635
|
+
if (piboSession.metadata?.[PIBO_INITIAL_MODEL_FALLBACKS_METADATA_KEY] === undefined) {
|
|
1636
|
+
this.sessionStore.update(piboSession.id, {
|
|
1637
|
+
metadata: withPiboSessionModelFallbacksMetadata(piboSession.metadata, modelFallbacks),
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
return modelFallbacks;
|
|
1641
|
+
}
|
|
1615
1642
|
resolveModelDefaults() {
|
|
1616
1643
|
if (typeof this.options.modelDefaults === "function")
|
|
1617
1644
|
return this.options.modelDefaults();
|
|
@@ -2052,6 +2079,10 @@ export class PiboSessionRouter {
|
|
|
2052
2079
|
throw new Error("Agent observation since must not be after until.");
|
|
2053
2080
|
}
|
|
2054
2081
|
const requestIds = input.requestIds ? new Set(input.requestIds) : undefined;
|
|
2082
|
+
if (input.toolCallIds && input.toolCallIds.length > 50) {
|
|
2083
|
+
throw new Error("Agent observation toolCallIds must contain at most 50 entries.");
|
|
2084
|
+
}
|
|
2085
|
+
const toolCallIds = input.toolCallIds ? new Set(input.toolCallIds) : undefined;
|
|
2055
2086
|
const agentIds = input.agentIds ? new Set(input.agentIds) : undefined;
|
|
2056
2087
|
if (agentIds) {
|
|
2057
2088
|
for (const agentId of agentIds)
|
|
@@ -2065,7 +2096,8 @@ export class PiboSessionRouter {
|
|
|
2065
2096
|
const textContains = input.textContains?.toLowerCase();
|
|
2066
2097
|
const defaultMessageView = eventTypes === undefined && kinds === undefined;
|
|
2067
2098
|
const explicitlySelectsTools = input.eventTypes?.some((eventType) => piboAgentObservationKind(eventType) === "tool") === true
|
|
2068
|
-
|| input.kinds?.includes("tool") === true
|
|
2099
|
+
|| input.kinds?.includes("tool") === true
|
|
2100
|
+
|| toolCallIds !== undefined;
|
|
2069
2101
|
const includeTools = input.includeTools === true || (input.includeTools === undefined && explicitlySelectsTools);
|
|
2070
2102
|
const defaultEventTypes = includeTools
|
|
2071
2103
|
? [...PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES, ...PIBO_AGENT_OBSERVATION_DEFAULT_TOOL_EVENT_TYPES]
|
|
@@ -2076,6 +2108,8 @@ export class PiboSessionRouter {
|
|
|
2076
2108
|
return false;
|
|
2077
2109
|
if (requestIds && (!observation.requestId || !requestIds.has(observation.requestId)))
|
|
2078
2110
|
return false;
|
|
2111
|
+
if (toolCallIds && (!observation.toolCallId || !toolCallIds.has(observation.toolCallId)))
|
|
2112
|
+
return false;
|
|
2079
2113
|
if (agentIds && !agentIds.has(observation.agentId))
|
|
2080
2114
|
return false;
|
|
2081
2115
|
if (names && !names.has(observation.name))
|
|
@@ -2346,13 +2380,13 @@ export class PiboSessionRouter {
|
|
|
2346
2380
|
const parentChatRoomId = typeof parent.metadata?.chatRoomId === "string" ? parent.metadata.chatRoomId : undefined;
|
|
2347
2381
|
if (parentChatRoomId)
|
|
2348
2382
|
metadata.chatRoomId = parentChatRoomId;
|
|
2349
|
-
const newSessionMetadata = {
|
|
2383
|
+
const newSessionMetadata = withPiboSessionModelFallbacksMetadata({
|
|
2350
2384
|
...metadata,
|
|
2351
2385
|
...(subagent.thinkingLevel ? { initialThinkingLevel: subagent.thinkingLevel } : {}),
|
|
2352
2386
|
...(subagent.runtimeOptions && Object.keys(subagent.runtimeOptions).length > 0
|
|
2353
2387
|
? { initialRuntimeOptions: structuredClone(subagent.runtimeOptions) }
|
|
2354
2388
|
: {}),
|
|
2355
|
-
};
|
|
2389
|
+
}, subagent.modelFallbacks ?? []);
|
|
2356
2390
|
const existing = this.sessionStore.find({
|
|
2357
2391
|
channel: "pibo.subagents",
|
|
2358
2392
|
kind: "subagent",
|
|
@@ -2435,17 +2469,23 @@ export class PiboSessionRouter {
|
|
|
2435
2469
|
}
|
|
2436
2470
|
}
|
|
2437
2471
|
emitOutput = (event) => {
|
|
2438
|
-
const
|
|
2439
|
-
this.
|
|
2440
|
-
this.
|
|
2441
|
-
this.
|
|
2442
|
-
this.
|
|
2472
|
+
const positionedEvent = this.outputRenderSequencer.position(event);
|
|
2473
|
+
const session = this.sessionStore.get(positionedEvent.piboSessionId);
|
|
2474
|
+
this.recordAgentObservation(positionedEvent, session);
|
|
2475
|
+
this.telemetryRecorder?.recordOutput(positionedEvent, { session, status: this.sessions.get(positionedEvent.piboSessionId)?.getStatus() });
|
|
2476
|
+
this.signalRegistry.project({ type: "pibo_output", event: positionedEvent, session });
|
|
2477
|
+
this.pluginRegistry.notifyEvent(positionedEvent);
|
|
2443
2478
|
for (const listener of this.listeners) {
|
|
2444
|
-
|
|
2479
|
+
try {
|
|
2480
|
+
listener(positionedEvent);
|
|
2481
|
+
}
|
|
2482
|
+
catch (error) {
|
|
2483
|
+
console.error("[pibo] output listener failed", error);
|
|
2484
|
+
}
|
|
2445
2485
|
}
|
|
2446
|
-
this.handleRunReminderOutput(
|
|
2447
|
-
if (
|
|
2448
|
-
this.scheduleRunReminder(
|
|
2486
|
+
this.handleRunReminderOutput(positionedEvent);
|
|
2487
|
+
if (positionedEvent.type === "message_finished" && positionedEvent.source !== "service") {
|
|
2488
|
+
this.scheduleRunReminder(positionedEvent.piboSessionId, true);
|
|
2449
2489
|
}
|
|
2450
2490
|
};
|
|
2451
2491
|
handleRunReminderOutput(event) {
|
|
@@ -2467,6 +2507,10 @@ export class PiboSessionRouter {
|
|
|
2467
2507
|
if (!delivery)
|
|
2468
2508
|
return;
|
|
2469
2509
|
this.runReminderDeliveries.delete(eventId);
|
|
2510
|
+
if (event.errorDetails?.code === "loop_continuation_invalidated") {
|
|
2511
|
+
this.suppressRunReminderDelivery(delivery);
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2470
2514
|
if (!isRunReminderContextPressureError(event)
|
|
2471
2515
|
|| this.closing
|
|
2472
2516
|
|| this.quiescingSessions.has(delivery.piboSessionId)
|
|
@@ -2475,13 +2519,7 @@ export class PiboSessionRouter {
|
|
|
2475
2519
|
return;
|
|
2476
2520
|
}
|
|
2477
2521
|
if (this.hasRunReminderRecovery(delivery)) {
|
|
2478
|
-
|
|
2479
|
-
for (const run of released)
|
|
2480
|
-
this.runRegistry.suppressNotification(delivery.piboSessionId, run.runId);
|
|
2481
|
-
this.clearRunReminderRecovery(delivery);
|
|
2482
|
-
this.deferredRunReminders.delete(delivery.piboSessionId);
|
|
2483
|
-
this.sessions.get(delivery.piboSessionId)?.removeQueuedMessages(isRunReminderServiceMessage);
|
|
2484
|
-
this.scheduleRunReminder(delivery.piboSessionId, false, delivery.generation);
|
|
2522
|
+
this.suppressRunReminderDelivery(delivery);
|
|
2485
2523
|
return;
|
|
2486
2524
|
}
|
|
2487
2525
|
this.runRegistry.releaseNotification(delivery.piboSessionId, delivery.notification);
|
|
@@ -2507,6 +2545,15 @@ export class PiboSessionRouter {
|
|
|
2507
2545
|
return;
|
|
2508
2546
|
this.scheduleRunReminder(event.piboSessionId, true, generation);
|
|
2509
2547
|
}
|
|
2548
|
+
suppressRunReminderDelivery(delivery) {
|
|
2549
|
+
const released = this.runRegistry.releaseNotification(delivery.piboSessionId, delivery.notification);
|
|
2550
|
+
for (const run of released)
|
|
2551
|
+
this.runRegistry.suppressNotification(delivery.piboSessionId, run.runId);
|
|
2552
|
+
this.clearRunReminderRecovery(delivery);
|
|
2553
|
+
this.deferredRunReminders.delete(delivery.piboSessionId);
|
|
2554
|
+
this.sessions.get(delivery.piboSessionId)?.removeQueuedMessages(isRunReminderServiceMessage);
|
|
2555
|
+
this.scheduleRunReminder(delivery.piboSessionId, false, delivery.generation);
|
|
2556
|
+
}
|
|
2510
2557
|
hasRunReminderRecovery(delivery) {
|
|
2511
2558
|
const recovery = this.runReminderRecoveries.get(delivery.piboSessionId);
|
|
2512
2559
|
return recovery?.generation === delivery.generation
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { rootSessionId } from "./session-store.js";
|
|
3
|
+
export class PiboOutputIdentityCollisionError extends Error {
|
|
4
|
+
idempotencyKey;
|
|
5
|
+
existingFingerprint;
|
|
6
|
+
incomingFingerprint;
|
|
7
|
+
code = "pibo_output_identity_collision";
|
|
8
|
+
constructor(idempotencyKey, existingFingerprint, incomingFingerprint) {
|
|
9
|
+
super(`Pibo output identity collision for "${idempotencyKey}"`);
|
|
10
|
+
this.idempotencyKey = idempotencyKey;
|
|
11
|
+
this.existingFingerprint = existingFingerprint;
|
|
12
|
+
this.incomingFingerprint = incomingFingerprint;
|
|
13
|
+
this.name = "PiboOutputIdentityCollisionError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
3
16
|
const INLINE_MESSAGE_PAYLOAD_THRESHOLD_BYTES = 16 * 1024;
|
|
4
17
|
const INLINE_JSON_PAYLOAD_THRESHOLD_BYTES = 16 * 1024;
|
|
5
18
|
export class ChatDataIngestService {
|
|
@@ -76,9 +89,39 @@ export class ChatDataIngestService {
|
|
|
76
89
|
ingestOutputEvent(input) {
|
|
77
90
|
const event = input.event;
|
|
78
91
|
const idempotencyKey = outputIdempotencyKey(event);
|
|
92
|
+
const identityFingerprint = outputEventFingerprint(event);
|
|
79
93
|
if (idempotencyKey) {
|
|
80
94
|
const existing = this.store.eventLog.findByIdempotencyKey(idempotencyKey);
|
|
81
95
|
if (existing) {
|
|
96
|
+
const existingFingerprint = typeof existing.attributes.identityFingerprint === "string"
|
|
97
|
+
? existing.attributes.identityFingerprint
|
|
98
|
+
: undefined;
|
|
99
|
+
if (existingFingerprint && existingFingerprint !== identityFingerprint) {
|
|
100
|
+
const now = input.createdAt ?? new Date().toISOString();
|
|
101
|
+
this.store.eventLog.appendEvent({
|
|
102
|
+
sessionId: input.session.id,
|
|
103
|
+
sessionSequence: this.nextEventSequence(input.session.id),
|
|
104
|
+
roomId: input.roomId,
|
|
105
|
+
topic: "pibo.diagnostic",
|
|
106
|
+
type: "pibo.output.identity_collision",
|
|
107
|
+
source: "pibo-ingest",
|
|
108
|
+
actorType: "system",
|
|
109
|
+
actorId: input.actorId,
|
|
110
|
+
eventId: eventIdForOutputEvent(event),
|
|
111
|
+
idempotencyKey: `${idempotencyKey}:collision:${identityFingerprint}`,
|
|
112
|
+
retentionClass: "audit_event",
|
|
113
|
+
previewText: `Output identity collision for ${event.type}`,
|
|
114
|
+
attributes: {
|
|
115
|
+
outputIdempotencyKey: idempotencyKey,
|
|
116
|
+
existingFingerprint,
|
|
117
|
+
incomingFingerprint: identityFingerprint,
|
|
118
|
+
incomingType: event.type,
|
|
119
|
+
},
|
|
120
|
+
createdAt: now,
|
|
121
|
+
indexedAt: now,
|
|
122
|
+
});
|
|
123
|
+
throw new PiboOutputIdentityCollisionError(idempotencyKey, existingFingerprint, identityFingerprint);
|
|
124
|
+
}
|
|
82
125
|
return {
|
|
83
126
|
streamId: existing.streamId,
|
|
84
127
|
duplicate: true,
|
|
@@ -112,6 +155,9 @@ export class ChatDataIngestService {
|
|
|
112
155
|
payloadRef,
|
|
113
156
|
previewText: previewTextForOutputEvent(event),
|
|
114
157
|
attributes: compactObject({
|
|
158
|
+
identityFingerprint,
|
|
159
|
+
eventIdentityScoped: eventIdForOutputEvent(event) !== undefined,
|
|
160
|
+
semanticEventId: eventIdForOutputEvent(event),
|
|
115
161
|
legacyStreamId: input.legacyStreamId,
|
|
116
162
|
inlinePayload: payloadRef ? undefined : toPiboJsonValue(payload?.value),
|
|
117
163
|
...attributesForOutputEvent(event),
|
|
@@ -222,7 +268,24 @@ function deterministicId(prefix, value) {
|
|
|
222
268
|
return `${prefix}_${createHash("sha256").update(value).digest("hex").slice(0, 32)}`;
|
|
223
269
|
}
|
|
224
270
|
function hashJson(value) {
|
|
225
|
-
return createHash("sha256").update(
|
|
271
|
+
return createHash("sha256").update(canonicalJson(value)).digest("hex").slice(0, 16);
|
|
272
|
+
}
|
|
273
|
+
function outputEventFingerprint(event) {
|
|
274
|
+
const identity = { ...event };
|
|
275
|
+
delete identity.renderSequence;
|
|
276
|
+
return createHash("sha256").update(canonicalJson(identity)).digest("hex");
|
|
277
|
+
}
|
|
278
|
+
function canonicalJson(value) {
|
|
279
|
+
if (value === null || typeof value !== "object")
|
|
280
|
+
return JSON.stringify(value) ?? "null";
|
|
281
|
+
if (Array.isArray(value))
|
|
282
|
+
return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
|
|
283
|
+
const record = value;
|
|
284
|
+
const entries = Object.keys(record)
|
|
285
|
+
.sort()
|
|
286
|
+
.filter((key) => record[key] !== undefined)
|
|
287
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`);
|
|
288
|
+
return `{${entries.join(",")}}`;
|
|
226
289
|
}
|
|
227
290
|
function previewText(text) {
|
|
228
291
|
const normalized = text.replace(/\s+/g, " ").trim();
|
|
@@ -231,23 +294,34 @@ function previewText(text) {
|
|
|
231
294
|
function compactObject(value) {
|
|
232
295
|
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
233
296
|
}
|
|
234
|
-
function outputIdempotencyKey(event) {
|
|
297
|
+
export function outputIdempotencyKey(event) {
|
|
235
298
|
const base = eventIdForOutputEvent(event) ?? ("toolCallId" in event ? event.toolCallId : undefined);
|
|
236
|
-
if (
|
|
237
|
-
return
|
|
238
|
-
|
|
299
|
+
if (base)
|
|
300
|
+
return `pibo.output:${event.piboSessionId}:${event.type}:${base}:${outputPartKey(event)}`;
|
|
301
|
+
if (Number.isSafeInteger(event.renderSequence) && (event.renderSequence ?? 0) > 0) {
|
|
302
|
+
return `pibo.output:${event.piboSessionId}:${event.type}:render:${event.renderSequence}`;
|
|
303
|
+
}
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
export function outputPersistenceDeliveryKey(event) {
|
|
307
|
+
return outputIdempotencyKey(event)
|
|
308
|
+
?? `pibo.output:${event.piboSessionId}:${event.type}:render:${event.renderSequence ?? "unpositioned"}`;
|
|
239
309
|
}
|
|
240
310
|
function outputPartKey(event) {
|
|
241
311
|
if (event.type === "tool_call")
|
|
242
|
-
return `${event.toolCallId}:${event.argsComplete ? "complete" : "partial"}:${hashJson(event.args)}`;
|
|
312
|
+
return `${event.toolCallId}:${event.toolInvocationOrdinal ?? 0}:${event.argsComplete ? "complete" : "partial"}:${hashJson(event.args)}`;
|
|
243
313
|
if ("toolCallId" in event)
|
|
244
|
-
return event.toolCallId ?? "main"
|
|
314
|
+
return `${event.toolCallId ?? "main"}:${event.toolInvocationOrdinal ?? 0}`;
|
|
245
315
|
if (event.type === "assistant_message")
|
|
246
316
|
return String(event.assistantIndex ?? event.contentIndex ?? 0);
|
|
247
317
|
if (event.type === "assistant_delta")
|
|
248
318
|
return String(event.assistantIndex ?? event.contentIndex ?? 0);
|
|
319
|
+
if (event.type === "assistant_usage")
|
|
320
|
+
return String(event.usageIndex ?? hashJson({ inputTokens: event.inputTokens, outputTokens: event.outputTokens, cacheReadTokens: event.cacheReadTokens, cacheWriteTokens: event.cacheWriteTokens, reasoningTokens: event.reasoningTokens, totalTokens: event.totalTokens, costUsd: event.costUsd }));
|
|
249
321
|
if (event.type === "thinking_started" || event.type === "thinking_delta" || event.type === "thinking_finished")
|
|
250
322
|
return String(event.thinkingIndex ?? event.contentIndex ?? 0);
|
|
323
|
+
if (event.type === "compaction_start" || event.type === "compaction_end")
|
|
324
|
+
return String(event.compactionIndex ?? 0);
|
|
251
325
|
if (event.type === "execution_result")
|
|
252
326
|
return event.action;
|
|
253
327
|
return "main";
|
|
@@ -318,6 +392,9 @@ function previewTextForOutputEvent(event) {
|
|
|
318
392
|
return event.type;
|
|
319
393
|
}
|
|
320
394
|
function attributesForOutputEvent(event) {
|
|
395
|
+
return { renderSequence: event.renderSequence, toolInvocationOrdinal: event.toolInvocationOrdinal, ...specificAttributesForOutputEvent(event) };
|
|
396
|
+
}
|
|
397
|
+
function specificAttributesForOutputEvent(event) {
|
|
321
398
|
if (event.type === "message_queued")
|
|
322
399
|
return { inlineText: event.text, source: event.source, queuedMessages: event.queuedMessages };
|
|
323
400
|
if (event.type === "message_steered")
|
|
@@ -330,11 +407,14 @@ function attributesForOutputEvent(event) {
|
|
|
330
407
|
return { assistantIndex: event.assistantIndex, contentIndex: event.contentIndex };
|
|
331
408
|
if (event.type === "assistant_usage")
|
|
332
409
|
return {
|
|
410
|
+
usageIndex: event.usageIndex,
|
|
333
411
|
inputTokens: event.inputTokens,
|
|
334
412
|
outputTokens: event.outputTokens,
|
|
335
413
|
cacheReadTokens: event.cacheReadTokens,
|
|
336
414
|
cacheWriteTokens: event.cacheWriteTokens,
|
|
415
|
+
reasoningTokens: event.reasoningTokens,
|
|
337
416
|
totalTokens: event.totalTokens,
|
|
417
|
+
costUsd: event.costUsd,
|
|
338
418
|
};
|
|
339
419
|
if (event.type === "thinking_started" || event.type === "thinking_delta" || event.type === "thinking_finished")
|
|
340
420
|
return { thinkingIndex: event.thinkingIndex, contentIndex: event.contentIndex };
|
|
@@ -349,7 +429,7 @@ function attributesForOutputEvent(event) {
|
|
|
349
429
|
if (event.type === "session_error")
|
|
350
430
|
return { error: event.error, ...(event.errorDetails ? { errorDetails: event.errorDetails } : {}) };
|
|
351
431
|
if (event.type === "compaction_start" || event.type === "compaction_end")
|
|
352
|
-
return { reason: event.reason, aborted: "aborted" in event ? event.aborted : undefined, errorMessage: "errorMessage" in event ? event.errorMessage : undefined };
|
|
432
|
+
return { compactionIndex: event.compactionIndex, reason: event.reason, aborted: "aborted" in event ? event.aborted : undefined, errorMessage: "errorMessage" in event ? event.errorMessage : undefined };
|
|
353
433
|
return {};
|
|
354
434
|
}
|
|
355
435
|
function observationKindForOutputEvent(event) {
|
|
@@ -398,7 +478,9 @@ function outputSessionStatus(event) {
|
|
|
398
478
|
return "error";
|
|
399
479
|
if (event.type === "message_finished")
|
|
400
480
|
return "idle";
|
|
401
|
-
|
|
481
|
+
if (event.type === "message_queued" || event.type === "message_steered" || event.type === "message_started")
|
|
482
|
+
return "running";
|
|
483
|
+
return undefined;
|
|
402
484
|
}
|
|
403
485
|
function toPiboJsonValue(value) {
|
|
404
486
|
if (value === undefined)
|
|
@@ -14,19 +14,19 @@ export class NavigationStore {
|
|
|
14
14
|
"origin_id = excluded.origin_id",
|
|
15
15
|
"title = excluded.title",
|
|
16
16
|
"profile = excluded.profile",
|
|
17
|
-
"status = excluded.status",
|
|
17
|
+
...(input.status === undefined ? [] : ["status = excluded.status"]),
|
|
18
18
|
"archived_at = excluded.archived_at",
|
|
19
|
-
"last_activity_at = excluded.last_activity_at",
|
|
19
|
+
"last_activity_at = MAX(session_navigation.last_activity_at, excluded.last_activity_at)",
|
|
20
20
|
"last_message_preview = COALESCE(excluded.last_message_preview, session_navigation.last_message_preview)",
|
|
21
21
|
"child_count = excluded.child_count",
|
|
22
|
-
"sort_key = excluded.sort_key",
|
|
23
|
-
"updated_at = excluded.updated_at",
|
|
22
|
+
"sort_key = MAX(session_navigation.sort_key, excluded.sort_key)",
|
|
23
|
+
"updated_at = MAX(session_navigation.updated_at, excluded.updated_at)",
|
|
24
24
|
];
|
|
25
25
|
this.db.prepare(`
|
|
26
26
|
INSERT INTO session_navigation (${insertColumns.join(", ")})
|
|
27
27
|
VALUES (${insertColumns.map(() => "?").join(", ")})
|
|
28
28
|
ON CONFLICT(session_id) DO UPDATE SET ${assignments.join(", ")}
|
|
29
|
-
`).run(input.roomId ?? null, input.sessionId, input.rootSessionId ?? null, input.parentId ?? null, input.originId ?? null, input.title, input.profile, input.status, input.archivedAt ?? null, input.lastActivityAt, input.lastMessagePreview ?? null, input.childCount ?? 0, input.sortKey, input.updatedAt);
|
|
29
|
+
`).run(input.roomId ?? null, input.sessionId, input.rootSessionId ?? null, input.parentId ?? null, input.originId ?? null, input.title, input.profile, input.status ?? "running", input.archivedAt ?? null, input.lastActivityAt, input.lastMessagePreview ?? null, input.childCount ?? 0, input.sortKey, input.updatedAt);
|
|
30
30
|
const stored = this.getSession(input.sessionId);
|
|
31
31
|
if (!stored)
|
|
32
32
|
throw new Error(`Failed to upsert session navigation \"${input.sessionId}\"`);
|
package/dist/data/schema.js
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
|
-
export const PIBO_DATA_SCHEMA_VERSION =
|
|
2
|
-
export
|
|
1
|
+
export const PIBO_DATA_SCHEMA_VERSION = 7;
|
|
2
|
+
export const PIBO_DATA_SCHEMA_MIGRATION_STEPS = [
|
|
3
|
+
"schema",
|
|
4
|
+
"runtime-bindings",
|
|
5
|
+
"render-high-water",
|
|
6
|
+
"sequence-repair-selection",
|
|
7
|
+
"sequence-repair-temporary",
|
|
8
|
+
"sequence-repair-backfill",
|
|
9
|
+
"sequence-repair-cleanup",
|
|
10
|
+
"runtime-binding-metadata",
|
|
11
|
+
"user-version",
|
|
12
|
+
];
|
|
13
|
+
export function applyPiboDataSchema(db, hooks = {}) {
|
|
14
|
+
const ownsTransaction = !db.isTransaction;
|
|
15
|
+
if (ownsTransaction)
|
|
16
|
+
db.exec("BEGIN IMMEDIATE");
|
|
17
|
+
try {
|
|
18
|
+
applyPiboDataSchemaInTransaction(db, hooks);
|
|
19
|
+
if (ownsTransaction)
|
|
20
|
+
db.exec("COMMIT");
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (ownsTransaction && db.isTransaction)
|
|
24
|
+
db.exec("ROLLBACK");
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function applyPiboDataSchemaInTransaction(db, hooks) {
|
|
3
29
|
const previousVersion = Number(db.prepare("PRAGMA user_version").get()?.user_version ?? 0);
|
|
4
30
|
const existingSessionCount = db.prepare("SELECT COUNT(*) AS count FROM sqlite_schema WHERE type = 'table' AND name = 'sessions'").get();
|
|
5
31
|
const hadSessionsBeforeMigration = existingSessionCount.count > 0
|
|
@@ -46,6 +72,42 @@ export function applyPiboDataSchema(db) {
|
|
|
46
72
|
FOREIGN KEY (pibo_session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
47
73
|
);
|
|
48
74
|
|
|
75
|
+
CREATE TABLE IF NOT EXISTS session_output_render_high_water (
|
|
76
|
+
pibo_session_id TEXT PRIMARY KEY,
|
|
77
|
+
high_water INTEGER NOT NULL CHECK(high_water >= 0),
|
|
78
|
+
updated_at TEXT NOT NULL,
|
|
79
|
+
FOREIGN KEY (pibo_session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
CREATE TABLE IF NOT EXISTS session_tool_invocation_counters (
|
|
83
|
+
pibo_session_id TEXT NOT NULL,
|
|
84
|
+
event_id TEXT NOT NULL,
|
|
85
|
+
tool_call_id TEXT NOT NULL,
|
|
86
|
+
next_ordinal INTEGER NOT NULL CHECK(next_ordinal >= 0),
|
|
87
|
+
updated_at TEXT NOT NULL,
|
|
88
|
+
PRIMARY KEY (pibo_session_id, event_id, tool_call_id),
|
|
89
|
+
FOREIGN KEY (pibo_session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
CREATE TABLE IF NOT EXISTS session_tool_invocations (
|
|
93
|
+
pibo_session_id TEXT NOT NULL,
|
|
94
|
+
event_id TEXT NOT NULL,
|
|
95
|
+
tool_call_id TEXT NOT NULL,
|
|
96
|
+
invocation_ordinal INTEGER NOT NULL CHECK(invocation_ordinal >= 0),
|
|
97
|
+
call_fingerprint TEXT,
|
|
98
|
+
status TEXT NOT NULL CHECK(status IN ('open', 'closed')),
|
|
99
|
+
seen_call INTEGER NOT NULL DEFAULT 0 CHECK(seen_call IN (0, 1)),
|
|
100
|
+
seen_started INTEGER NOT NULL DEFAULT 0 CHECK(seen_started IN (0, 1)),
|
|
101
|
+
seen_updated INTEGER NOT NULL DEFAULT 0 CHECK(seen_updated IN (0, 1)),
|
|
102
|
+
seen_finished INTEGER NOT NULL DEFAULT 0 CHECK(seen_finished IN (0, 1)),
|
|
103
|
+
created_at TEXT NOT NULL,
|
|
104
|
+
updated_at TEXT NOT NULL,
|
|
105
|
+
PRIMARY KEY (pibo_session_id, event_id, tool_call_id, invocation_ordinal),
|
|
106
|
+
FOREIGN KEY (pibo_session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
107
|
+
);
|
|
108
|
+
CREATE INDEX IF NOT EXISTS idx_session_tool_invocations_open
|
|
109
|
+
ON session_tool_invocations(pibo_session_id, event_id, tool_call_id, status, invocation_ordinal DESC);
|
|
110
|
+
|
|
49
111
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_session_runtime_bindings_native
|
|
50
112
|
ON session_runtime_bindings(runtime_adapter_id, native_session_id)
|
|
51
113
|
WHERE native_session_id IS NOT NULL;
|
|
@@ -516,6 +578,7 @@ export function applyPiboDataSchema(db) {
|
|
|
516
578
|
CREATE INDEX IF NOT EXISTS idx_telemetry_tool_calls_retention_updated
|
|
517
579
|
ON telemetry_tool_calls(retention_class, updated_at);
|
|
518
580
|
`);
|
|
581
|
+
hooks.afterStep?.("schema");
|
|
519
582
|
db.exec(`
|
|
520
583
|
INSERT OR IGNORE INTO session_runtime_bindings (
|
|
521
584
|
pibo_session_id, runtime_instance_id, runtime_adapter_id, native_session_id,
|
|
@@ -536,6 +599,61 @@ export function applyPiboDataSchema(db) {
|
|
|
536
599
|
WHERE existing.pibo_session_id = sessions.id
|
|
537
600
|
);
|
|
538
601
|
`);
|
|
602
|
+
hooks.afterStep?.("runtime-bindings");
|
|
603
|
+
db.exec(`
|
|
604
|
+
INSERT INTO session_output_render_high_water (pibo_session_id, high_water, updated_at)
|
|
605
|
+
SELECT
|
|
606
|
+
id,
|
|
607
|
+
CAST(json_extract(metadata_json, '$.outputRenderSequenceHighWater') AS INTEGER),
|
|
608
|
+
updated_at
|
|
609
|
+
FROM sessions
|
|
610
|
+
WHERE json_type(metadata_json, '$.outputRenderSequenceHighWater') IN ('integer', 'real')
|
|
611
|
+
AND CAST(json_extract(metadata_json, '$.outputRenderSequenceHighWater') AS INTEGER) >= 0
|
|
612
|
+
ON CONFLICT(pibo_session_id) DO UPDATE SET
|
|
613
|
+
high_water = MAX(session_output_render_high_water.high_water, excluded.high_water),
|
|
614
|
+
updated_at = CASE
|
|
615
|
+
WHEN excluded.high_water > session_output_render_high_water.high_water THEN excluded.updated_at
|
|
616
|
+
ELSE session_output_render_high_water.updated_at
|
|
617
|
+
END;
|
|
618
|
+
`);
|
|
619
|
+
hooks.afterStep?.("render-high-water");
|
|
620
|
+
// Always inspect for interrupted pre-atomic v7 repairs. A previous process
|
|
621
|
+
// may have written negative temporary values before setting user_version.
|
|
622
|
+
db.exec(`
|
|
623
|
+
DROP TABLE IF EXISTS temp.pibo_v7_sequence_repair_sessions;
|
|
624
|
+
CREATE TEMP TABLE pibo_v7_sequence_repair_sessions (
|
|
625
|
+
session_id TEXT PRIMARY KEY
|
|
626
|
+
);
|
|
627
|
+
INSERT INTO pibo_v7_sequence_repair_sessions (session_id)
|
|
628
|
+
SELECT DISTINCT session_id
|
|
629
|
+
FROM event_log
|
|
630
|
+
WHERE session_id IS NOT NULL
|
|
631
|
+
AND (session_sequence IS NULL OR session_sequence <= 0);
|
|
632
|
+
`);
|
|
633
|
+
hooks.afterStep?.("sequence-repair-selection");
|
|
634
|
+
db.exec(`
|
|
635
|
+
UPDATE event_log
|
|
636
|
+
SET session_sequence = -stream_id
|
|
637
|
+
WHERE session_id IN (SELECT session_id FROM pibo_v7_sequence_repair_sessions);
|
|
638
|
+
`);
|
|
639
|
+
hooks.afterStep?.("sequence-repair-temporary");
|
|
640
|
+
db.exec(`
|
|
641
|
+
WITH ranked AS (
|
|
642
|
+
SELECT
|
|
643
|
+
stream_id,
|
|
644
|
+
ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY stream_id ASC) AS repaired_sequence
|
|
645
|
+
FROM event_log
|
|
646
|
+
WHERE session_id IN (SELECT session_id FROM pibo_v7_sequence_repair_sessions)
|
|
647
|
+
)
|
|
648
|
+
UPDATE event_log
|
|
649
|
+
SET session_sequence = (
|
|
650
|
+
SELECT repaired_sequence FROM ranked WHERE ranked.stream_id = event_log.stream_id
|
|
651
|
+
)
|
|
652
|
+
WHERE stream_id IN (SELECT stream_id FROM ranked);
|
|
653
|
+
`);
|
|
654
|
+
hooks.afterStep?.("sequence-repair-backfill");
|
|
655
|
+
db.exec("DROP TABLE pibo_v7_sequence_repair_sessions");
|
|
656
|
+
hooks.afterStep?.("sequence-repair-cleanup");
|
|
539
657
|
if (previousVersion < PIBO_DATA_SCHEMA_VERSION && hadSessionsBeforeMigration) {
|
|
540
658
|
const rows = db.prepare("SELECT pibo_session_id, metadata_json FROM session_runtime_bindings").all();
|
|
541
659
|
const update = db.prepare("UPDATE session_runtime_bindings SET metadata_json = ? WHERE pibo_session_id = ?");
|
|
@@ -558,5 +676,7 @@ export function applyPiboDataSchema(db) {
|
|
|
558
676
|
}), row.pibo_session_id);
|
|
559
677
|
}
|
|
560
678
|
}
|
|
679
|
+
hooks.afterStep?.("runtime-binding-metadata");
|
|
561
680
|
db.exec(`PRAGMA user_version = ${PIBO_DATA_SCHEMA_VERSION}`);
|
|
681
|
+
hooks.afterStep?.("user-version");
|
|
562
682
|
}
|
|
@@ -43,10 +43,10 @@ export class SessionStore {
|
|
|
43
43
|
"workspace = excluded.workspace",
|
|
44
44
|
"title = excluded.title",
|
|
45
45
|
"first_message_preview = COALESCE(sessions.first_message_preview, excluded.first_message_preview)",
|
|
46
|
-
"status = excluded.status",
|
|
46
|
+
...(input.status === undefined ? [] : ["status = excluded.status"]),
|
|
47
47
|
"metadata_json = excluded.metadata_json",
|
|
48
|
-
"updated_at = excluded.updated_at",
|
|
49
|
-
"last_activity_at = excluded.last_activity_at",
|
|
48
|
+
"updated_at = MAX(sessions.updated_at, excluded.updated_at)",
|
|
49
|
+
"last_activity_at = MAX(sessions.last_activity_at, excluded.last_activity_at)",
|
|
50
50
|
];
|
|
51
51
|
this.db.prepare(`
|
|
52
52
|
INSERT INTO sessions (${baseColumns.join(", ")})
|