@pasko70/pibo 2.5.2 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/context/pibo-native-tooling.md +1 -0
- package/dist/agent-runtime/contract.js +2 -1
- package/dist/agent-runtime/history.js +53 -1
- package/dist/agent-runtime/routed-session.js +100 -26
- package/dist/agent-runtimes/codex-native/adapter.js +304 -30
- package/dist/agent-runtimes/codex-native/client.js +22 -3
- package/dist/agent-runtimes/codex-native/first-use.js +294 -0
- package/dist/agent-runtimes/codex-native/history.js +26 -7
- package/dist/agent-runtimes/codex-native/process.js +14 -0
- package/dist/agent-runtimes/codex-native/thread.js +59 -25
- package/dist/agent-runtimes/codex-native/turn.js +27 -0
- package/dist/agent-runtimes/history-proof.js +8 -0
- package/dist/agent-runtimes/omp/adapter.js +61 -14
- package/dist/agent-runtimes/omp/history.js +189 -34
- package/dist/agent-runtimes/omp/thread.js +82 -36
- package/dist/agent-runtimes/pi/adapter.js +30 -3
- package/dist/agent-runtimes/pi/history.js +66 -5
- package/dist/agent-runtimes/pi/openai-response-observer.js +102 -0
- package/dist/agent-runtimes/pi/routed-session.js +344 -37
- package/dist/apps/chat/chat-api-routes.js +1 -1
- package/dist/apps/chat/chat-files.js +130 -9
- package/dist/apps/chat/chat-settings-routes.js +27 -2
- package/dist/apps/chat/chat-speech.js +100 -0
- package/dist/apps/chat/data/chat-data-mappers.js +25 -5
- package/dist/apps/chat/data/timeline-query-service.js +89 -1
- package/dist/apps/chat/trace-v2.js +210 -8
- package/dist/apps/chat/trace.js +3 -0
- package/dist/apps/chat/web-app.js +108 -3
- package/dist/apps/chat-ui/assets/{dist-DTWJf37A.js → dist-BBDMeU5-.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CkYWKnvy.js → dist-BDIATCQt.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DZ8Wpj-H.js → dist-JnW4v8UE.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Dmblz15N.js → dist-lRiLP9rr.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DjcwoyNY.js → dist-mqCviI-c.js} +1 -1
- package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +228 -0
- package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-Dtw2Z7jz.js +43 -0
- package/dist/apps/chat-vscode-web/assets/{index-CF1-9PKU.css → index-SluZr_-r.css} +1 -1
- package/dist/apps/chat-vscode-web/index.html +2 -2
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.1.vsix +0 -0
- package/dist/cli.js +49 -5
- package/dist/config/config.js +59 -8
- package/dist/core/preview-server-settings.js +20 -0
- package/dist/core/session-router.js +464 -37
- package/dist/core/user-settings.js +10 -0
- package/dist/data/payload-store.js +57 -1
- package/dist/data/schema.js +18 -1
- package/dist/debug/trace.js +5 -0
- package/dist/gateway/client.js +368 -65
- package/dist/gateway/protocol.js +2 -0
- package/dist/gateway/server.js +8 -0
- package/dist/gateway/web.js +2 -0
- package/dist/index.js +1 -1
- package/dist/loops/store.js +6 -4
- package/dist/mcp/config.js +89 -17
- package/dist/mcp/daemon-client.js +473 -153
- package/dist/mcp/daemon.js +401 -37
- package/dist/plugins/builtin.js +2 -2
- package/dist/plugins/codex-native.js +2 -0
- package/dist/plugins/registry.js +259 -1
- package/dist/previews/cli.js +345 -0
- package/dist/previews/config.js +61 -0
- package/dist/previews/manager.js +644 -0
- package/dist/previews/network.js +198 -0
- package/dist/previews/plugin.js +11 -0
- package/dist/previews/process-supervisor.js +38 -0
- package/dist/previews/proxy.js +387 -0
- package/dist/previews/store.js +750 -0
- package/dist/previews/types.js +1 -0
- package/dist/previews/web-app.js +446 -0
- package/dist/runs/registry.js +22 -0
- package/dist/runs/tools.js +46 -7
- package/dist/session-ui/terminalRows.js +187 -13
- package/dist/sessions/pibo-data-store.js +37 -0
- package/dist/sessions/runtime-binding-persistence.js +34 -0
- package/dist/sessions/runtime-binding.js +6 -0
- package/dist/sessions/sqlite-store.js +37 -0
- package/dist/shared/trace-engine.js +12 -64
- package/dist/shared/trace-event-projection.js +16 -0
- package/dist/shared/trace-history.js +684 -184
- package/dist/shared/trace-limits.js +7 -0
- package/dist/shared/trace-live-patch.js +61 -0
- package/dist/shared/trace-tool-identity.js +43 -0
- package/dist/speech/openai-codex-realtime-call-proxy.js +248 -0
- package/dist/speech/openai-codex.js +407 -0
- package/dist/speech/types.js +8 -0
- package/dist/subagents/context.js +5 -3
- package/dist/subagents/observations.js +94 -6
- package/dist/subagents/tool.js +103 -20
- package/dist/tools/codex-image-artifacts.js +24 -0
- package/dist/tools/codex-image-generation.js +4 -22
- package/dist/web/channel.js +88 -4
- package/dist/web/http.js +2 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-0WZI2phJ.css +0 -1
- package/dist/apps/chat-ui/assets/index-DsgTm1Aw.js +0 -228
- package/dist/apps/chat-vscode-web/assets/index-DvTSSvzN.js +0 -43
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.5.2.vsix +0 -0
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
3
|
import { InitialSessionContext, } from "./profiles.js";
|
|
3
4
|
import { createDefaultPiboPluginRegistry, createPiboProfileFromRegistryOrDefault, resolvePiboProfileNameFromRegistryOrDefault, selectDefaultPiboProfileName } from "../plugins/builtin.js";
|
|
4
5
|
import { RuntimeRoutedSession as RoutedSession, } from "../agent-runtime/routed-session.js";
|
|
5
6
|
import { runtimeSessionErrorDetails } from "./session-errors.js";
|
|
6
|
-
import {
|
|
7
|
+
import { normalizePiboAgentSessionName, } from "../subagents/tool.js";
|
|
8
|
+
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";
|
|
7
9
|
import { PiboRunRegistry } from "../runs/registry.js";
|
|
8
10
|
import { PiboRunCancellationError, PiboRunCancelledError, PiboRunExecutionTimeoutError, waitForRunCancellationSettlement } from "../runs/lifecycle.js";
|
|
9
11
|
import { PiboRunResourceLimitError } from "../runs/resource-isolation.js";
|
|
10
12
|
import { createPiboSignalRegistry } from "../signals/registry.js";
|
|
11
13
|
import { createDefaultPiboReliabilityStore } from "../reliability/store.js";
|
|
12
|
-
import { InMemoryPiboSessionStore, } from "../sessions/store.js";
|
|
14
|
+
import { InMemoryPiboSessionStore, createPiboSessionId, } from "../sessions/store.js";
|
|
15
|
+
import { createAgentRuntimeBindingPersistence } from "../sessions/runtime-binding-persistence.js";
|
|
13
16
|
import { createLegacyPiRuntimeSessionBinding, RuntimeSessionBindingConflictError, } from "../sessions/runtime-binding.js";
|
|
14
17
|
import { AgentRuntimeBindingMissingError, AgentRuntimeUnavailableError } from "../agent-runtime/errors.js";
|
|
15
18
|
import { validateAgentRuntimeProfileCapabilities } from "../agent-runtime/profile-validation.js";
|
|
@@ -162,6 +165,23 @@ function formatRunReminderMessage(notification) {
|
|
|
162
165
|
function isRunReminderServiceMessage(event) {
|
|
163
166
|
return event.source === "service" && event.text.startsWith("<pibo_run_notification>");
|
|
164
167
|
}
|
|
168
|
+
function isRunReminderContextPressureError(event) {
|
|
169
|
+
return event.errorDetails?.category === "context_overflow"
|
|
170
|
+
|| event.errorDetails?.code === "context_length_exceeded"
|
|
171
|
+
|| event.errorDetails?.code === "run_reminder_limit_exceeded";
|
|
172
|
+
}
|
|
173
|
+
function runReminderRecoveryGroupKey(notification) {
|
|
174
|
+
if (notification.origin)
|
|
175
|
+
return `origin:${JSON.stringify(notification.origin)}`;
|
|
176
|
+
const runIds = [
|
|
177
|
+
...notification.completed,
|
|
178
|
+
...notification.failed,
|
|
179
|
+
...notification.timedOut,
|
|
180
|
+
...notification.cancelled,
|
|
181
|
+
...notification.running,
|
|
182
|
+
].map((run) => run.runId).sort();
|
|
183
|
+
return `runs:${JSON.stringify(runIds)}`;
|
|
184
|
+
}
|
|
165
185
|
function yieldedRunOrigin(event) {
|
|
166
186
|
if (!event?.id || event.provenance?.kind !== "loop-run")
|
|
167
187
|
return undefined;
|
|
@@ -190,6 +210,58 @@ function isTerminalRunStatus(status) {
|
|
|
190
210
|
function asJsonObject(value) {
|
|
191
211
|
return value ?? {};
|
|
192
212
|
}
|
|
213
|
+
const DERIVED_SESSION_RECONCILIATION_METADATA_KEY = "pibo.sessionIdentityReconciliation.v1";
|
|
214
|
+
function unresolvedDerivedSessionSource(session) {
|
|
215
|
+
const marker = session.metadata?.[DERIVED_SESSION_RECONCILIATION_METADATA_KEY];
|
|
216
|
+
if (!marker || typeof marker !== "object" || Array.isArray(marker))
|
|
217
|
+
return undefined;
|
|
218
|
+
return marker.state === "cleanup-required" && typeof marker.sourcePiboSessionId === "string"
|
|
219
|
+
? marker.sourcePiboSessionId
|
|
220
|
+
: undefined;
|
|
221
|
+
}
|
|
222
|
+
function derivedSessionMetadata(value) {
|
|
223
|
+
const metadata = { ...asJsonObject(value) };
|
|
224
|
+
for (const key of [
|
|
225
|
+
DERIVED_SESSION_RECONCILIATION_METADATA_KEY,
|
|
226
|
+
"workflowSessionKind",
|
|
227
|
+
"projectSessionKind",
|
|
228
|
+
"subagentName",
|
|
229
|
+
"subagentToolName",
|
|
230
|
+
"agentStatus",
|
|
231
|
+
"threadKey",
|
|
232
|
+
]) {
|
|
233
|
+
delete metadata[key];
|
|
234
|
+
}
|
|
235
|
+
return metadata;
|
|
236
|
+
}
|
|
237
|
+
function matchesDerivedSessionIntent(session, input) {
|
|
238
|
+
const binding = session.runtimeBinding;
|
|
239
|
+
const expectedBinding = input.runtimeBinding;
|
|
240
|
+
if (!binding || !expectedBinding)
|
|
241
|
+
return false;
|
|
242
|
+
const expectedPiSessionId = expectedBinding.adapterId === "pi" ? expectedBinding.nativeSessionId ?? "" : "";
|
|
243
|
+
return session.id === input.id
|
|
244
|
+
&& session.piSessionId === expectedPiSessionId
|
|
245
|
+
&& session.channel === input.channel
|
|
246
|
+
&& session.kind === input.kind
|
|
247
|
+
&& session.profile === input.profile
|
|
248
|
+
&& session.parentId === input.parentId
|
|
249
|
+
&& session.originId === input.originId
|
|
250
|
+
&& session.workspace === input.workspace
|
|
251
|
+
&& (session.title === input.title || (input.title === undefined && session.title === "Untitled Session"))
|
|
252
|
+
&& isDeepStrictEqual(session.metadata ?? {}, input.metadata ?? {})
|
|
253
|
+
&& isDeepStrictEqual(session.activeModel, input.activeModel)
|
|
254
|
+
&& binding.piboSessionId === input.id
|
|
255
|
+
&& binding.runtimeInstanceId === expectedBinding.runtimeInstanceId
|
|
256
|
+
&& binding.adapterId === expectedBinding.adapterId
|
|
257
|
+
&& binding.nativeSessionId === expectedBinding.nativeSessionId
|
|
258
|
+
&& binding.state === (expectedBinding.state ?? "unbound")
|
|
259
|
+
&& binding.protocol === expectedBinding.protocol
|
|
260
|
+
&& binding.protocolVersion === expectedBinding.protocolVersion
|
|
261
|
+
&& binding.adapterVersion === expectedBinding.adapterVersion
|
|
262
|
+
&& isDeepStrictEqual(binding.locator, expectedBinding.locator)
|
|
263
|
+
&& isDeepStrictEqual(binding.metadata ?? {}, expectedBinding.metadata ?? {});
|
|
264
|
+
}
|
|
193
265
|
function shouldResetSessionAfterAction(action, output) {
|
|
194
266
|
if (action === "login.apikey" || action === "logout")
|
|
195
267
|
return true;
|
|
@@ -273,10 +345,14 @@ export class PiboSessionRouter {
|
|
|
273
345
|
agentObservationEvictedThroughByParent = new Map();
|
|
274
346
|
nextAgentObservationSequence = 1;
|
|
275
347
|
scheduledRunReminders = new Map();
|
|
348
|
+
runReminderDeliveries = new Map();
|
|
349
|
+
deferredRunReminders = new Map();
|
|
350
|
+
runReminderRecoveries = new Map();
|
|
276
351
|
runReminderGenerations = new Map();
|
|
277
352
|
runCancellationHandlers = new Map();
|
|
278
353
|
activeRunExecutions = new Set();
|
|
279
354
|
quiescingSessions = new Set();
|
|
355
|
+
unresolvedDerivedSessionTransitions = new Map();
|
|
280
356
|
disposingSessions = new Map();
|
|
281
357
|
idleSessionTimers = new Map();
|
|
282
358
|
routedSessionIdleTimeoutMs;
|
|
@@ -356,6 +432,13 @@ export class PiboSessionRouter {
|
|
|
356
432
|
async emit(event) {
|
|
357
433
|
if (this.closing)
|
|
358
434
|
throw new Error("Pibo session router is disposed.");
|
|
435
|
+
let messageSignalAccepted = false;
|
|
436
|
+
const acceptMessageSignal = () => {
|
|
437
|
+
if (event.type !== "message" || !event.id || messageSignalAccepted)
|
|
438
|
+
return;
|
|
439
|
+
messageSignalAccepted = true;
|
|
440
|
+
this.signalRegistry.project({ type: "message_accepted", piboSessionId: event.piboSessionId, eventId: event.id, source: event.source });
|
|
441
|
+
};
|
|
359
442
|
const teardownAction = event.type === "execution" && (event.action === "dispose" || event.action === "kill" || event.action === "kill_all");
|
|
360
443
|
const teardownIds = teardownAction
|
|
361
444
|
? [event.piboSessionId, ...this.descendantSessionIds(event.piboSessionId)]
|
|
@@ -370,7 +453,13 @@ export class PiboSessionRouter {
|
|
|
370
453
|
const stored = this.sessionStore.get(event.piboSessionId);
|
|
371
454
|
if (stored)
|
|
372
455
|
this.signalRegistry.project({ type: "session_created", session: stored });
|
|
373
|
-
|
|
456
|
+
// Cold runtime creation and steering retain their established eager
|
|
457
|
+
// signal lifecycle. Cached queued messages are accepted inside the
|
|
458
|
+
// routed session's synchronous admission boundary so an identity
|
|
459
|
+
// reservation can reject without first publishing acceptance.
|
|
460
|
+
if (event.delivery === "steer" || (!this.sessions.has(event.piboSessionId) && !this.pendingSessions.has(event.piboSessionId))) {
|
|
461
|
+
acceptMessageSignal();
|
|
462
|
+
}
|
|
374
463
|
}
|
|
375
464
|
let session;
|
|
376
465
|
try {
|
|
@@ -399,9 +488,13 @@ export class PiboSessionRouter {
|
|
|
399
488
|
throw new Error(`Pibo session "${event.piboSessionId}" is quiescing.`);
|
|
400
489
|
}
|
|
401
490
|
if (event.type === "message") {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
491
|
+
if (event.delivery === "steer")
|
|
492
|
+
return await session.steerMessage(event);
|
|
493
|
+
const output = session.enqueueMessage(event, acceptMessageSignal);
|
|
494
|
+
// Test doubles and compatibility shims may ignore the optional
|
|
495
|
+
// callback; production routed sessions invoke it before queueing.
|
|
496
|
+
acceptMessageSignal();
|
|
497
|
+
return output;
|
|
405
498
|
}
|
|
406
499
|
if (event.action === "abort") {
|
|
407
500
|
this.signalRegistry.project({ type: "session_interrupted", piboSessionId: event.piboSessionId, reason: "abort action" });
|
|
@@ -840,6 +933,15 @@ export class PiboSessionRouter {
|
|
|
840
933
|
this.scheduleIdleSessionEvictionIfIdle(piboSessionId);
|
|
841
934
|
}
|
|
842
935
|
}
|
|
936
|
+
async getSessionForkCandidates(piboSessionId) {
|
|
937
|
+
const session = await this.getOrCreateSession(piboSessionId);
|
|
938
|
+
try {
|
|
939
|
+
return await session.getForkCandidates();
|
|
940
|
+
}
|
|
941
|
+
finally {
|
|
942
|
+
this.scheduleIdleSessionEvictionIfIdle(piboSessionId);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
843
945
|
async setLiveSessionActiveModel(piboSessionId, model) {
|
|
844
946
|
const session = this.sessions.get(piboSessionId);
|
|
845
947
|
if (!session)
|
|
@@ -1224,6 +1326,17 @@ export class PiboSessionRouter {
|
|
|
1224
1326
|
this.portableToolSessions.delete(piboSession.id);
|
|
1225
1327
|
throw error;
|
|
1226
1328
|
}
|
|
1329
|
+
const bindingSync = { expectedRevision: binding.revision };
|
|
1330
|
+
const runtimeBindingPersistence = createAgentRuntimeBindingPersistence(this.sessionStore, {
|
|
1331
|
+
piboSessionId: piboSession.id,
|
|
1332
|
+
onPersisted: (updated) => {
|
|
1333
|
+
binding = updated;
|
|
1334
|
+
bindingSync.expectedRevision = updated.revision;
|
|
1335
|
+
const updatedSession = this.sessionStore.get(piboSession.id);
|
|
1336
|
+
if (updatedSession)
|
|
1337
|
+
this.signalRegistry.project({ type: "session_created", session: updatedSession });
|
|
1338
|
+
},
|
|
1339
|
+
});
|
|
1227
1340
|
let runtimeSession;
|
|
1228
1341
|
try {
|
|
1229
1342
|
runtimeSession = await runtimeRegistry.openAgentRuntimeSession(binding.runtimeInstanceId, {
|
|
@@ -1249,6 +1362,7 @@ export class PiboSessionRouter {
|
|
|
1249
1362
|
codeRuntimeToolController,
|
|
1250
1363
|
portableTools,
|
|
1251
1364
|
resources,
|
|
1365
|
+
...(runtimeBindingPersistence ? { runtimeBindingPersistence } : {}),
|
|
1252
1366
|
compatibility: {
|
|
1253
1367
|
persistSession: this.options.persistSession,
|
|
1254
1368
|
thinkingLevel: initialThinkingLevel ?? this.options.thinkingLevel,
|
|
@@ -1309,6 +1423,7 @@ export class PiboSessionRouter {
|
|
|
1309
1423
|
binding = this.persistSessionRuntimeBinding(piboSession, openedBinding, {
|
|
1310
1424
|
expectedRevision: binding.revision,
|
|
1311
1425
|
});
|
|
1426
|
+
bindingSync.expectedRevision = binding.revision;
|
|
1312
1427
|
}
|
|
1313
1428
|
}
|
|
1314
1429
|
catch (error) {
|
|
@@ -1331,6 +1446,7 @@ export class PiboSessionRouter {
|
|
|
1331
1446
|
})
|
|
1332
1447
|
: undefined,
|
|
1333
1448
|
onSessionOperation: (result, event) => this.handleSessionOperation(result, event),
|
|
1449
|
+
onBeforeSessionIdentityOperation: (event) => this.retryUnresolvedDerivedSessionCompensation(event.piboSessionId),
|
|
1334
1450
|
onKillChildren: (id, opts) => this.killChildSessions(id, opts),
|
|
1335
1451
|
onStateChange: (state) => {
|
|
1336
1452
|
this.signalRegistry.project({
|
|
@@ -1339,18 +1455,22 @@ export class PiboSessionRouter {
|
|
|
1339
1455
|
processing: state.processing,
|
|
1340
1456
|
queuedMessages: state.queuedMessages,
|
|
1341
1457
|
});
|
|
1342
|
-
if (!state.processing && state.queuedMessages === 0 && !state.disposed) {
|
|
1343
|
-
this.syncLiveSessionRuntimeBinding(piboSession.id, runtimeSession);
|
|
1458
|
+
if (!state.processing && state.queuedMessages === 0 && !state.disposed && !state.sessionIdentityOperationInFlight) {
|
|
1459
|
+
this.syncLiveSessionRuntimeBinding(piboSession.id, runtimeSession, bindingSync);
|
|
1460
|
+
this.scheduleRunReminder(piboSession.id, false);
|
|
1344
1461
|
}
|
|
1345
|
-
if (state.disposed || state.processing || state.queuedMessages > 0)
|
|
1462
|
+
if (state.disposed || state.processing || state.queuedMessages > 0 || state.sessionIdentityOperationInFlight)
|
|
1346
1463
|
this.clearIdleSessionTimer(piboSession.id);
|
|
1347
1464
|
else
|
|
1348
1465
|
this.scheduleIdleSessionEvictionIfIdle(piboSession.id);
|
|
1349
1466
|
},
|
|
1350
|
-
onMessagesInterrupted: (messages, reason) =>
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1467
|
+
onMessagesInterrupted: (messages, reason) => {
|
|
1468
|
+
this.telemetryRecorder?.recordMessagesInterrupted(messages, {
|
|
1469
|
+
session: this.sessionStore.get(piboSession.id),
|
|
1470
|
+
status: this.sessions.get(piboSession.id)?.getStatus(),
|
|
1471
|
+
}, reason);
|
|
1472
|
+
this.handleInterruptedRunReminders(messages);
|
|
1473
|
+
},
|
|
1354
1474
|
messagePreflight: this.options.messagePreflight,
|
|
1355
1475
|
getRuntimeAuthStatus: () => runtimeRegistry.getAgentRuntimeAuthStatus(binding.runtimeInstanceId),
|
|
1356
1476
|
startRuntimeAuth: async (input) => {
|
|
@@ -1430,22 +1550,33 @@ export class PiboSessionRouter {
|
|
|
1430
1550
|
updatedAt: now,
|
|
1431
1551
|
};
|
|
1432
1552
|
}
|
|
1433
|
-
syncLiveSessionRuntimeBinding(piboSessionId, runtimeSession) {
|
|
1553
|
+
syncLiveSessionRuntimeBinding(piboSessionId, runtimeSession, state) {
|
|
1434
1554
|
const session = this.sessionStore.get(piboSessionId);
|
|
1435
1555
|
if (!session)
|
|
1436
1556
|
return;
|
|
1437
1557
|
try {
|
|
1438
1558
|
const persisted = this.resolveSessionRuntimeBinding(session);
|
|
1439
|
-
this.persistSessionRuntimeBinding(session, withPersistedPortableHistoryAuditMetadata(persisted, runtimeSession.getBinding()));
|
|
1559
|
+
const updated = this.persistSessionRuntimeBinding(session, withPersistedPortableHistoryAuditMetadata(persisted, runtimeSession.getBinding()), { expectedRevision: state.expectedRevision });
|
|
1560
|
+
state.expectedRevision = updated.revision;
|
|
1440
1561
|
}
|
|
1441
1562
|
catch (error) {
|
|
1442
1563
|
const message = error instanceof Error ? error.message : String(error);
|
|
1564
|
+
const reset = this.resetCachedSession(piboSessionId, "runtime binding synchronization failed");
|
|
1443
1565
|
this.emitOutput({
|
|
1444
1566
|
type: "session_error",
|
|
1445
1567
|
piboSessionId,
|
|
1446
1568
|
error: `Failed to persist the live runtime binding: ${message}`,
|
|
1447
1569
|
errorDetails: runtimeSessionErrorDetails(message),
|
|
1448
1570
|
});
|
|
1571
|
+
void reset.catch((resetError) => {
|
|
1572
|
+
const resetMessage = resetError instanceof Error ? resetError.message : String(resetError);
|
|
1573
|
+
this.emitOutput({
|
|
1574
|
+
type: "session_error",
|
|
1575
|
+
piboSessionId,
|
|
1576
|
+
error: `Failed to discard a runtime after binding synchronization failed: ${resetMessage}`,
|
|
1577
|
+
errorDetails: runtimeSessionErrorDetails(resetMessage),
|
|
1578
|
+
});
|
|
1579
|
+
});
|
|
1449
1580
|
}
|
|
1450
1581
|
}
|
|
1451
1582
|
assertOpenableRuntimeBinding(binding) {
|
|
@@ -1494,21 +1625,44 @@ export class PiboSessionRouter {
|
|
|
1494
1625
|
const source = this.resolvePiboSession(event.piboSessionId);
|
|
1495
1626
|
const previousBinding = this.resolveSessionRuntimeBinding(source);
|
|
1496
1627
|
const liveBinding = this.sessions.get(event.piboSessionId)?.getRuntimeBinding();
|
|
1628
|
+
const currentNativeSessionId = result.current.piSessionId || undefined;
|
|
1497
1629
|
const currentBinding = {
|
|
1498
1630
|
...previousBinding,
|
|
1499
1631
|
...liveBinding,
|
|
1500
1632
|
piboSessionId: source.id,
|
|
1501
|
-
nativeSessionId:
|
|
1502
|
-
state: "bound",
|
|
1503
|
-
locator: result.current.sessionFile
|
|
1633
|
+
nativeSessionId: currentNativeSessionId,
|
|
1634
|
+
state: currentNativeSessionId ? "bound" : "unbound",
|
|
1635
|
+
locator: currentNativeSessionId && result.current.sessionFile
|
|
1504
1636
|
? { kind: "local-file", value: result.current.sessionFile }
|
|
1505
|
-
:
|
|
1637
|
+
: currentNativeSessionId
|
|
1638
|
+
? liveBinding?.locator ?? previousBinding.locator
|
|
1639
|
+
: undefined,
|
|
1640
|
+
metadata: currentNativeSessionId ? liveBinding?.metadata ?? previousBinding.metadata : undefined,
|
|
1506
1641
|
};
|
|
1507
1642
|
if (event.action === "session.fork" || event.action === "session.clone") {
|
|
1508
1643
|
const action = event.action;
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1644
|
+
let transitionError;
|
|
1645
|
+
try {
|
|
1646
|
+
const created = this.createDerivedSession(result, action, currentBinding);
|
|
1647
|
+
result.piboSessionId = created.id;
|
|
1648
|
+
}
|
|
1649
|
+
catch (error) {
|
|
1650
|
+
transitionError = error;
|
|
1651
|
+
}
|
|
1652
|
+
// Always discard the source live handle after derivation. Bound native
|
|
1653
|
+
// branches move that handle to the derived session; first-message Codex
|
|
1654
|
+
// branches intentionally leave it on the source and persist unbound.
|
|
1655
|
+
try {
|
|
1656
|
+
await this.resetCachedSession(event.piboSessionId);
|
|
1657
|
+
}
|
|
1658
|
+
catch (resetError) {
|
|
1659
|
+
if (transitionError) {
|
|
1660
|
+
throw new AggregateError([transitionError, resetError], `Failed to persist and reset ${action}.`);
|
|
1661
|
+
}
|
|
1662
|
+
throw resetError;
|
|
1663
|
+
}
|
|
1664
|
+
if (transitionError)
|
|
1665
|
+
throw transitionError;
|
|
1512
1666
|
return;
|
|
1513
1667
|
}
|
|
1514
1668
|
this.persistSessionRuntimeBinding(source, currentBinding, {
|
|
@@ -1519,17 +1673,18 @@ export class PiboSessionRouter {
|
|
|
1519
1673
|
}
|
|
1520
1674
|
createDerivedSession(result, action, currentBinding) {
|
|
1521
1675
|
const source = this.resolvePiboSession(result.piboSessionId);
|
|
1522
|
-
|
|
1676
|
+
const input = {
|
|
1677
|
+
id: createPiboSessionId(),
|
|
1523
1678
|
channel: source.channel,
|
|
1524
1679
|
kind: "branch",
|
|
1525
1680
|
profile: source.profile,
|
|
1526
|
-
parentId: source.
|
|
1681
|
+
parentId: source.parentId,
|
|
1527
1682
|
originId: source.id,
|
|
1528
1683
|
runtimeBinding: {
|
|
1529
1684
|
runtimeInstanceId: currentBinding.runtimeInstanceId,
|
|
1530
1685
|
adapterId: currentBinding.adapterId,
|
|
1531
|
-
nativeSessionId: currentBinding.nativeSessionId
|
|
1532
|
-
state:
|
|
1686
|
+
nativeSessionId: currentBinding.nativeSessionId,
|
|
1687
|
+
state: currentBinding.state,
|
|
1533
1688
|
protocol: currentBinding.protocol,
|
|
1534
1689
|
protocolVersion: currentBinding.protocolVersion,
|
|
1535
1690
|
adapterVersion: currentBinding.adapterVersion,
|
|
@@ -1540,12 +1695,116 @@ export class PiboSessionRouter {
|
|
|
1540
1695
|
title: source.title,
|
|
1541
1696
|
activeModel: source.activeModel,
|
|
1542
1697
|
metadata: {
|
|
1543
|
-
...
|
|
1698
|
+
...derivedSessionMetadata(source.metadata),
|
|
1544
1699
|
originAction: action,
|
|
1545
1700
|
originRuntimeNativeSessionId: result.previous.piSessionId,
|
|
1546
1701
|
...(currentBinding.adapterId === "pi" ? { originPiSessionId: result.previous.piSessionId } : {}),
|
|
1547
1702
|
},
|
|
1703
|
+
};
|
|
1704
|
+
let created;
|
|
1705
|
+
try {
|
|
1706
|
+
created = this.sessionStore.create(input);
|
|
1707
|
+
}
|
|
1708
|
+
catch (error) {
|
|
1709
|
+
let persisted;
|
|
1710
|
+
try {
|
|
1711
|
+
persisted = this.sessionStore.get(input.id);
|
|
1712
|
+
}
|
|
1713
|
+
catch (lookupError) {
|
|
1714
|
+
const failure = new AggregateError([error, lookupError], `Branch persistence for "${input.id}" failed and its commit state could not be reconciled.`);
|
|
1715
|
+
this.unresolvedDerivedSessionTransitions.set(source.id, { piboSessionId: input.id, cause: failure });
|
|
1716
|
+
throw failure;
|
|
1717
|
+
}
|
|
1718
|
+
if (!persisted)
|
|
1719
|
+
throw error;
|
|
1720
|
+
if (matchesDerivedSessionIntent(persisted, input))
|
|
1721
|
+
return persisted;
|
|
1722
|
+
return this.rejectDerivedSessionAfterCompensation(source.id, input.id, error);
|
|
1723
|
+
}
|
|
1724
|
+
if (matchesDerivedSessionIntent(created, input))
|
|
1725
|
+
return created;
|
|
1726
|
+
return this.rejectDerivedSessionAfterCompensation(source.id, input.id, new Error(`Session store returned a branch that does not match the intended derivation "${input.id}".`));
|
|
1727
|
+
}
|
|
1728
|
+
retryUnresolvedDerivedSessionCompensation(sourcePiboSessionId) {
|
|
1729
|
+
const remembered = this.unresolvedDerivedSessionTransitions.get(sourcePiboSessionId);
|
|
1730
|
+
const persisted = this.sessionStore.find({ originId: sourcePiboSessionId })
|
|
1731
|
+
.filter((session) => unresolvedDerivedSessionSource(session) === sourcePiboSessionId)
|
|
1732
|
+
.map((session) => ({
|
|
1733
|
+
piboSessionId: session.id,
|
|
1734
|
+
cause: new Error(`Branch "${session.id}" has a persisted session identity reconciliation marker.`),
|
|
1735
|
+
}));
|
|
1736
|
+
const unresolved = [
|
|
1737
|
+
...(remembered ? [remembered] : []),
|
|
1738
|
+
...persisted.filter((candidate) => candidate.piboSessionId !== remembered?.piboSessionId),
|
|
1739
|
+
];
|
|
1740
|
+
for (const candidate of unresolved) {
|
|
1741
|
+
try {
|
|
1742
|
+
if (!this.sessionStore.get(candidate.piboSessionId)) {
|
|
1743
|
+
if (remembered?.piboSessionId === candidate.piboSessionId) {
|
|
1744
|
+
this.unresolvedDerivedSessionTransitions.delete(sourcePiboSessionId);
|
|
1745
|
+
}
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1748
|
+
if (!this.sessionStore.delete)
|
|
1749
|
+
throw new Error("Session store does not support compensating deletion.");
|
|
1750
|
+
this.sessionStore.delete(candidate.piboSessionId);
|
|
1751
|
+
if (this.sessionStore.get(candidate.piboSessionId)) {
|
|
1752
|
+
throw new Error(`Compensating deletion left branch "${candidate.piboSessionId}" persisted.`);
|
|
1753
|
+
}
|
|
1754
|
+
if (remembered?.piboSessionId === candidate.piboSessionId) {
|
|
1755
|
+
this.unresolvedDerivedSessionTransitions.delete(sourcePiboSessionId);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
catch (error) {
|
|
1759
|
+
this.unresolvedDerivedSessionTransitions.set(sourcePiboSessionId, candidate);
|
|
1760
|
+
throw new AggregateError([candidate.cause, error], `Branch "${candidate.piboSessionId}" still requires compensation; refusing another session identity operation for source "${sourcePiboSessionId}".`);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
persistDerivedSessionReconciliationMarker(sourcePiboSessionId, piboSessionId) {
|
|
1765
|
+
const residual = this.sessionStore.get(piboSessionId);
|
|
1766
|
+
if (!residual)
|
|
1767
|
+
return;
|
|
1768
|
+
const updated = this.sessionStore.update(piboSessionId, {
|
|
1769
|
+
metadata: {
|
|
1770
|
+
...(residual.metadata ?? {}),
|
|
1771
|
+
[DERIVED_SESSION_RECONCILIATION_METADATA_KEY]: {
|
|
1772
|
+
state: "cleanup-required",
|
|
1773
|
+
sourcePiboSessionId,
|
|
1774
|
+
},
|
|
1775
|
+
},
|
|
1548
1776
|
});
|
|
1777
|
+
if (!updated || unresolvedDerivedSessionSource(updated) !== sourcePiboSessionId) {
|
|
1778
|
+
throw new Error(`Failed to persist the reconciliation marker for branch "${piboSessionId}".`);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
rejectDerivedSessionAfterCompensation(sourcePiboSessionId, piboSessionId, cause) {
|
|
1782
|
+
let cleanupError;
|
|
1783
|
+
try {
|
|
1784
|
+
if (!this.sessionStore.delete) {
|
|
1785
|
+
throw new Error("Session store does not support compensating deletion.");
|
|
1786
|
+
}
|
|
1787
|
+
this.sessionStore.delete(piboSessionId);
|
|
1788
|
+
if (this.sessionStore.get(piboSessionId)) {
|
|
1789
|
+
throw new Error(`Compensating deletion left branch "${piboSessionId}" persisted.`);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
catch (error) {
|
|
1793
|
+
cleanupError = error;
|
|
1794
|
+
}
|
|
1795
|
+
if (cleanupError) {
|
|
1796
|
+
let markerError;
|
|
1797
|
+
try {
|
|
1798
|
+
this.persistDerivedSessionReconciliationMarker(sourcePiboSessionId, piboSessionId);
|
|
1799
|
+
}
|
|
1800
|
+
catch (error) {
|
|
1801
|
+
markerError = error;
|
|
1802
|
+
}
|
|
1803
|
+
const failure = new AggregateError([cause, cleanupError, ...(markerError ? [markerError] : [])], `Branch persistence for "${piboSessionId}" was inconsistent and compensation failed.`);
|
|
1804
|
+
this.unresolvedDerivedSessionTransitions.set(sourcePiboSessionId, { piboSessionId, cause: failure });
|
|
1805
|
+
throw failure;
|
|
1806
|
+
}
|
|
1807
|
+
throw cause;
|
|
1549
1808
|
}
|
|
1550
1809
|
runtimeAuthAffectedInstanceIds(runtimeInstanceId) {
|
|
1551
1810
|
const registry = this.resolveAgentRuntimeRegistry(runtimeInstanceId);
|
|
@@ -1661,13 +1920,14 @@ export class PiboSessionRouter {
|
|
|
1661
1920
|
}
|
|
1662
1921
|
createAgentsController(parentPiboSessionId) {
|
|
1663
1922
|
return {
|
|
1664
|
-
sendMessage: async ({ subagent, message, threadKey, toolCallId, requestId, parentProvenance, signal }) => {
|
|
1923
|
+
sendMessage: async ({ subagent, sessionName, message, threadKey, toolCallId, requestId, parentProvenance, signal }) => {
|
|
1665
1924
|
if (signal?.aborted)
|
|
1666
1925
|
throw subagentAbortError();
|
|
1667
1926
|
if (typeof requestId !== "string" || !requestId.trim())
|
|
1668
1927
|
throw new Error("Delegated agent requestId is required.");
|
|
1669
1928
|
this.assertSubagentDepth(parentPiboSessionId, subagent);
|
|
1670
|
-
const
|
|
1929
|
+
const normalizedSessionName = normalizePiboAgentSessionName(sessionName);
|
|
1930
|
+
const child = this.resolveSubagentSession(parentPiboSessionId, subagent, normalizedSessionName, threadKey);
|
|
1671
1931
|
const resolvedThreadKey = typeof child.metadata?.threadKey === "string" ? child.metadata.threadKey : "";
|
|
1672
1932
|
const loopJobId = parentProvenance?.kind === "loop-run"
|
|
1673
1933
|
? parentProvenance.jobId
|
|
@@ -1762,6 +2022,7 @@ export class PiboSessionRouter {
|
|
|
1762
2022
|
agentId: session.id,
|
|
1763
2023
|
name: typeof session.metadata?.subagentName === "string" ? session.metadata.subagentName : session.profile,
|
|
1764
2024
|
profile: session.profile,
|
|
2025
|
+
...(session.title ? { sessionName: session.title } : {}),
|
|
1765
2026
|
...(typeof session.metadata?.threadKey === "string" ? { threadKey: session.metadata.threadKey } : {}),
|
|
1766
2027
|
status: killed ? "killed" : running ? "running" : "idle",
|
|
1767
2028
|
createdAt: session.createdAt,
|
|
@@ -1783,6 +2044,7 @@ export class PiboSessionRouter {
|
|
|
1783
2044
|
observeManagedAgents(parentPiboSessionId, input) {
|
|
1784
2045
|
const order = normalizePiboAgentObservationOrder(input.order);
|
|
1785
2046
|
const limit = normalizePiboAgentObservationLimit(input.limit);
|
|
2047
|
+
const toolDetail = normalizePiboAgentObservationToolDetail(input.toolDetail);
|
|
1786
2048
|
const afterSequence = normalizePiboAgentObservationCursor(input.afterSequence);
|
|
1787
2049
|
const since = parsePiboAgentObservationTimestamp(input.since, "since");
|
|
1788
2050
|
const until = parsePiboAgentObservationTimestamp(input.until, "until");
|
|
@@ -1801,6 +2063,14 @@ export class PiboSessionRouter {
|
|
|
1801
2063
|
const kinds = input.kinds ? new Set(input.kinds) : undefined;
|
|
1802
2064
|
const roles = input.roles ? new Set(input.roles) : undefined;
|
|
1803
2065
|
const textContains = input.textContains?.toLowerCase();
|
|
2066
|
+
const defaultMessageView = eventTypes === undefined && kinds === undefined;
|
|
2067
|
+
const explicitlySelectsTools = input.eventTypes?.some((eventType) => piboAgentObservationKind(eventType) === "tool") === true
|
|
2068
|
+
|| input.kinds?.includes("tool") === true;
|
|
2069
|
+
const includeTools = input.includeTools === true || (input.includeTools === undefined && explicitlySelectsTools);
|
|
2070
|
+
const defaultEventTypes = includeTools
|
|
2071
|
+
? [...PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES, ...PIBO_AGENT_OBSERVATION_DEFAULT_TOOL_EVENT_TYPES]
|
|
2072
|
+
: [...PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES];
|
|
2073
|
+
const defaultEventTypeSet = new Set(defaultEventTypes);
|
|
1804
2074
|
const matches = this.agentObservations.filter((observation) => {
|
|
1805
2075
|
if (observation.managingParentId !== parentPiboSessionId)
|
|
1806
2076
|
return false;
|
|
@@ -1812,6 +2082,10 @@ export class PiboSessionRouter {
|
|
|
1812
2082
|
return false;
|
|
1813
2083
|
if (threadKeys && (!observation.threadKey || !threadKeys.has(observation.threadKey)))
|
|
1814
2084
|
return false;
|
|
2085
|
+
if (observation.kind === "tool" && !includeTools)
|
|
2086
|
+
return false;
|
|
2087
|
+
if (defaultMessageView && !defaultEventTypeSet.has(observation.eventType))
|
|
2088
|
+
return false;
|
|
1815
2089
|
if (eventTypes && !eventTypes.has(observation.eventType))
|
|
1816
2090
|
return false;
|
|
1817
2091
|
if (kinds && !kinds.has(observation.kind))
|
|
@@ -1837,7 +2111,10 @@ export class PiboSessionRouter {
|
|
|
1837
2111
|
ordered.reverse();
|
|
1838
2112
|
const selected = ordered.map((observation) => {
|
|
1839
2113
|
const { managingParentId: _managingParentId, details, ...visible } = observation;
|
|
1840
|
-
|
|
2114
|
+
const compact = visible.kind === "tool" && toolDetail === "summary"
|
|
2115
|
+
? { ...visible, text: piboAgentObservationToolSummary(visible.text, visible.isError, details) }
|
|
2116
|
+
: visible;
|
|
2117
|
+
return input.includeDetails === true && details !== undefined ? { ...compact, details } : compact;
|
|
1841
2118
|
});
|
|
1842
2119
|
const evictedThrough = this.agentObservationEvictedThroughByParent.get(parentPiboSessionId) ?? 0;
|
|
1843
2120
|
const retentionTruncated = afterSequence === undefined ? evictedThrough > 0 : afterSequence < evictedThrough;
|
|
@@ -1845,9 +2122,12 @@ export class PiboSessionRouter {
|
|
|
1845
2122
|
return {
|
|
1846
2123
|
filters: {
|
|
1847
2124
|
...input,
|
|
2125
|
+
...(defaultMessageView ? { eventTypes: defaultEventTypes } : {}),
|
|
1848
2126
|
...(afterSequence !== undefined ? { afterSequence } : {}),
|
|
1849
2127
|
order,
|
|
1850
2128
|
limit,
|
|
2129
|
+
includeTools,
|
|
2130
|
+
toolDetail,
|
|
1851
2131
|
includeDetails: input.includeDetails === true,
|
|
1852
2132
|
},
|
|
1853
2133
|
observations: selected,
|
|
@@ -2050,7 +2330,7 @@ export class PiboSessionRouter {
|
|
|
2050
2330
|
}
|
|
2051
2331
|
return depth;
|
|
2052
2332
|
}
|
|
2053
|
-
resolveSubagentSession(parentPiboSessionId, subagent, threadKey) {
|
|
2333
|
+
resolveSubagentSession(parentPiboSessionId, subagent, sessionName, threadKey) {
|
|
2054
2334
|
const targetProfile = resolvePiboProfileNameFromRegistryOrDefault(this.pluginRegistry, subagent.targetProfile);
|
|
2055
2335
|
const parent = this.resolvePiboSession(parentPiboSessionId);
|
|
2056
2336
|
const resolvedThreadKey = resolveSubagentThreadKey(threadKey);
|
|
@@ -2087,8 +2367,8 @@ export class PiboSessionRouter {
|
|
|
2087
2367
|
agentStatus: "active",
|
|
2088
2368
|
...(parentChatRoomId ? { chatRoomId: parentChatRoomId } : {}),
|
|
2089
2369
|
}, "subagent");
|
|
2090
|
-
if (JSON.stringify(updatedMetadata) !== JSON.stringify(existing.metadata ?? {})) {
|
|
2091
|
-
return this.sessionStore.update(existing.id, { metadata: updatedMetadata }) ?? existing;
|
|
2370
|
+
if (existing.title !== sessionName || JSON.stringify(updatedMetadata) !== JSON.stringify(existing.metadata ?? {})) {
|
|
2371
|
+
return this.sessionStore.update(existing.id, { title: sessionName, metadata: updatedMetadata }) ?? existing;
|
|
2092
2372
|
}
|
|
2093
2373
|
return existing;
|
|
2094
2374
|
}
|
|
@@ -2100,6 +2380,7 @@ export class PiboSessionRouter {
|
|
|
2100
2380
|
parentId: parent.id,
|
|
2101
2381
|
runtimeBinding: this.createRuntimeBindingInput(childProfile),
|
|
2102
2382
|
workspace: parent.workspace,
|
|
2383
|
+
title: sessionName,
|
|
2103
2384
|
metadata: newSessionMetadata,
|
|
2104
2385
|
activeModel: subagent.model,
|
|
2105
2386
|
});
|
|
@@ -2162,10 +2443,137 @@ export class PiboSessionRouter {
|
|
|
2162
2443
|
for (const listener of this.listeners) {
|
|
2163
2444
|
listener(event);
|
|
2164
2445
|
}
|
|
2446
|
+
this.handleRunReminderOutput(event);
|
|
2165
2447
|
if (event.type === "message_finished" && event.source !== "service") {
|
|
2166
2448
|
this.scheduleRunReminder(event.piboSessionId, true);
|
|
2167
2449
|
}
|
|
2168
2450
|
};
|
|
2451
|
+
handleRunReminderOutput(event) {
|
|
2452
|
+
if (event.type === "message_finished") {
|
|
2453
|
+
const delivery = event.eventId ? this.runReminderDeliveries.get(event.eventId) : undefined;
|
|
2454
|
+
if (event.eventId)
|
|
2455
|
+
this.runReminderDeliveries.delete(event.eventId);
|
|
2456
|
+
if (delivery) {
|
|
2457
|
+
this.clearRunReminderRecovery(delivery);
|
|
2458
|
+
this.scheduleRunReminder(delivery.piboSessionId, false, delivery.generation);
|
|
2459
|
+
}
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2462
|
+
if (event.type === "session_error") {
|
|
2463
|
+
const eventId = event.eventId;
|
|
2464
|
+
if (!eventId)
|
|
2465
|
+
return;
|
|
2466
|
+
const delivery = this.runReminderDeliveries.get(eventId);
|
|
2467
|
+
if (!delivery)
|
|
2468
|
+
return;
|
|
2469
|
+
this.runReminderDeliveries.delete(eventId);
|
|
2470
|
+
if (event.errorDetails?.code === "loop_continuation_invalidated") {
|
|
2471
|
+
this.suppressRunReminderDelivery(delivery);
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
2474
|
+
if (!isRunReminderContextPressureError(event)
|
|
2475
|
+
|| this.closing
|
|
2476
|
+
|| this.quiescingSessions.has(delivery.piboSessionId)
|
|
2477
|
+
|| delivery.generation !== this.runReminderGeneration(delivery.piboSessionId)) {
|
|
2478
|
+
this.runRegistry.releaseNotification(delivery.piboSessionId, delivery.notification);
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
if (this.hasRunReminderRecovery(delivery)) {
|
|
2482
|
+
this.suppressRunReminderDelivery(delivery);
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
this.runRegistry.releaseNotification(delivery.piboSessionId, delivery.notification);
|
|
2486
|
+
if (!this.runRegistry.hasPendingNotification(delivery.piboSessionId))
|
|
2487
|
+
return;
|
|
2488
|
+
this.recordRunReminderRecovery(delivery);
|
|
2489
|
+
const alreadyDeferred = this.deferredRunReminders.get(delivery.piboSessionId) === delivery.generation;
|
|
2490
|
+
this.deferredRunReminders.set(delivery.piboSessionId, delivery.generation);
|
|
2491
|
+
this.sessions.get(delivery.piboSessionId)?.removeQueuedMessages(isRunReminderServiceMessage);
|
|
2492
|
+
if (!alreadyDeferred)
|
|
2493
|
+
this.queueRunReminderRecoveryCompaction(delivery.piboSessionId, delivery.generation);
|
|
2494
|
+
return;
|
|
2495
|
+
}
|
|
2496
|
+
if (event.type !== "compaction_end" || event.aborted || event.errorMessage)
|
|
2497
|
+
return;
|
|
2498
|
+
const generation = this.deferredRunReminders.get(event.piboSessionId);
|
|
2499
|
+
if (generation === undefined)
|
|
2500
|
+
return;
|
|
2501
|
+
this.deferredRunReminders.delete(event.piboSessionId);
|
|
2502
|
+
if (this.closing || this.quiescingSessions.has(event.piboSessionId))
|
|
2503
|
+
return;
|
|
2504
|
+
if (generation !== this.runReminderGeneration(event.piboSessionId))
|
|
2505
|
+
return;
|
|
2506
|
+
this.scheduleRunReminder(event.piboSessionId, true, generation);
|
|
2507
|
+
}
|
|
2508
|
+
suppressRunReminderDelivery(delivery) {
|
|
2509
|
+
const released = this.runRegistry.releaseNotification(delivery.piboSessionId, delivery.notification);
|
|
2510
|
+
for (const run of released)
|
|
2511
|
+
this.runRegistry.suppressNotification(delivery.piboSessionId, run.runId);
|
|
2512
|
+
this.clearRunReminderRecovery(delivery);
|
|
2513
|
+
this.deferredRunReminders.delete(delivery.piboSessionId);
|
|
2514
|
+
this.sessions.get(delivery.piboSessionId)?.removeQueuedMessages(isRunReminderServiceMessage);
|
|
2515
|
+
this.scheduleRunReminder(delivery.piboSessionId, false, delivery.generation);
|
|
2516
|
+
}
|
|
2517
|
+
hasRunReminderRecovery(delivery) {
|
|
2518
|
+
const recovery = this.runReminderRecoveries.get(delivery.piboSessionId);
|
|
2519
|
+
return recovery?.generation === delivery.generation
|
|
2520
|
+
&& recovery.groups.has(runReminderRecoveryGroupKey(delivery.notification));
|
|
2521
|
+
}
|
|
2522
|
+
recordRunReminderRecovery(delivery) {
|
|
2523
|
+
let recovery = this.runReminderRecoveries.get(delivery.piboSessionId);
|
|
2524
|
+
if (!recovery || recovery.generation !== delivery.generation) {
|
|
2525
|
+
recovery = { generation: delivery.generation, groups: new Set() };
|
|
2526
|
+
this.runReminderRecoveries.set(delivery.piboSessionId, recovery);
|
|
2527
|
+
}
|
|
2528
|
+
recovery.groups.add(runReminderRecoveryGroupKey(delivery.notification));
|
|
2529
|
+
}
|
|
2530
|
+
clearRunReminderRecovery(delivery) {
|
|
2531
|
+
const recovery = this.runReminderRecoveries.get(delivery.piboSessionId);
|
|
2532
|
+
if (!recovery || recovery.generation !== delivery.generation)
|
|
2533
|
+
return;
|
|
2534
|
+
recovery.groups.delete(runReminderRecoveryGroupKey(delivery.notification));
|
|
2535
|
+
if (recovery.groups.size === 0)
|
|
2536
|
+
this.runReminderRecoveries.delete(delivery.piboSessionId);
|
|
2537
|
+
}
|
|
2538
|
+
queueRunReminderRecoveryCompaction(piboSessionId, generation) {
|
|
2539
|
+
const session = this.sessions.get(piboSessionId);
|
|
2540
|
+
if (!session)
|
|
2541
|
+
return;
|
|
2542
|
+
const eventId = randomUUID();
|
|
2543
|
+
void session.executeAction({
|
|
2544
|
+
type: "execution",
|
|
2545
|
+
piboSessionId,
|
|
2546
|
+
id: eventId,
|
|
2547
|
+
action: "compact",
|
|
2548
|
+
params: {
|
|
2549
|
+
customInstructions: "Preserve the current task and pending yielded-run lifecycle, and leave enough context for the deferred run notification.",
|
|
2550
|
+
},
|
|
2551
|
+
}).catch((error) => {
|
|
2552
|
+
if (this.closing || this.quiescingSessions.has(piboSessionId))
|
|
2553
|
+
return;
|
|
2554
|
+
if (generation !== this.runReminderGeneration(piboSessionId))
|
|
2555
|
+
return;
|
|
2556
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2557
|
+
this.emitOutput({
|
|
2558
|
+
type: "session_error",
|
|
2559
|
+
piboSessionId,
|
|
2560
|
+
eventId,
|
|
2561
|
+
error: message,
|
|
2562
|
+
errorDetails: runtimeSessionErrorDetails(message),
|
|
2563
|
+
});
|
|
2564
|
+
});
|
|
2565
|
+
}
|
|
2566
|
+
handleInterruptedRunReminders(messages) {
|
|
2567
|
+
for (const message of messages) {
|
|
2568
|
+
if (!isRunReminderServiceMessage(message) || !message.id)
|
|
2569
|
+
continue;
|
|
2570
|
+
const delivery = this.runReminderDeliveries.get(message.id);
|
|
2571
|
+
if (!delivery)
|
|
2572
|
+
continue;
|
|
2573
|
+
this.runReminderDeliveries.delete(message.id);
|
|
2574
|
+
this.runRegistry.releaseNotification(delivery.piboSessionId, delivery.notification);
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2169
2577
|
projectKnownSessionSignals() {
|
|
2170
2578
|
for (const session of this.sessionStore.list?.() ?? []) {
|
|
2171
2579
|
this.signalRegistry.project({ type: "session_created", session });
|
|
@@ -2185,6 +2593,12 @@ export class PiboSessionRouter {
|
|
|
2185
2593
|
for (const piboSessionId of piboSessionIds) {
|
|
2186
2594
|
this.runReminderGenerations.set(piboSessionId, this.runReminderGeneration(piboSessionId) + 1);
|
|
2187
2595
|
this.scheduledRunReminders.delete(piboSessionId);
|
|
2596
|
+
this.deferredRunReminders.delete(piboSessionId);
|
|
2597
|
+
this.runReminderRecoveries.delete(piboSessionId);
|
|
2598
|
+
for (const [eventId, delivery] of this.runReminderDeliveries) {
|
|
2599
|
+
if (delivery.piboSessionId === piboSessionId)
|
|
2600
|
+
this.runReminderDeliveries.delete(eventId);
|
|
2601
|
+
}
|
|
2188
2602
|
try {
|
|
2189
2603
|
this.sessions.get(piboSessionId)?.removeQueuedMessages(isRunReminderServiceMessage);
|
|
2190
2604
|
}
|
|
@@ -2213,6 +2627,11 @@ export class PiboSessionRouter {
|
|
|
2213
2627
|
return;
|
|
2214
2628
|
if (expectedGeneration !== this.runReminderGeneration(piboSessionId))
|
|
2215
2629
|
return;
|
|
2630
|
+
const deferredGeneration = this.deferredRunReminders.get(piboSessionId);
|
|
2631
|
+
if (deferredGeneration === expectedGeneration)
|
|
2632
|
+
return;
|
|
2633
|
+
if (deferredGeneration !== undefined)
|
|
2634
|
+
this.deferredRunReminders.delete(piboSessionId);
|
|
2216
2635
|
if (!this.runRegistry.hasPendingNotification(piboSessionId, { includeAlreadyNotified }))
|
|
2217
2636
|
return;
|
|
2218
2637
|
const previous = this.scheduledRunReminders.get(piboSessionId);
|
|
@@ -2240,23 +2659,31 @@ export class PiboSessionRouter {
|
|
|
2240
2659
|
this.scheduledRunReminders.delete(piboSessionId);
|
|
2241
2660
|
if (this.closing || this.quiescingSessions.has(piboSessionId) || expectedGeneration !== this.runReminderGeneration(piboSessionId))
|
|
2242
2661
|
return;
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
return;
|
|
2662
|
+
let notification;
|
|
2663
|
+
let eventId;
|
|
2246
2664
|
try {
|
|
2247
2665
|
const session = await this.getOrCreateSession(piboSessionId);
|
|
2248
2666
|
if (this.closing || this.quiescingSessions.has(piboSessionId) || expectedGeneration !== this.runReminderGeneration(piboSessionId))
|
|
2249
2667
|
return;
|
|
2668
|
+
notification = this.runRegistry.createNotification(piboSessionId, { includeAlreadyNotified: scheduled.includeAlreadyNotified });
|
|
2669
|
+
if (!notification)
|
|
2670
|
+
return;
|
|
2671
|
+
eventId = randomUUID();
|
|
2672
|
+
this.runReminderDeliveries.set(eventId, { piboSessionId, generation: expectedGeneration, notification });
|
|
2250
2673
|
session.enqueueMessage({
|
|
2251
2674
|
type: "message",
|
|
2252
2675
|
piboSessionId,
|
|
2253
2676
|
text: formatRunReminderMessage(notification),
|
|
2254
2677
|
source: "service",
|
|
2255
|
-
id:
|
|
2678
|
+
id: eventId,
|
|
2256
2679
|
provenance: runReminderProvenance(notification),
|
|
2257
2680
|
});
|
|
2258
2681
|
}
|
|
2259
2682
|
catch (error) {
|
|
2683
|
+
if (eventId)
|
|
2684
|
+
this.runReminderDeliveries.delete(eventId);
|
|
2685
|
+
if (notification)
|
|
2686
|
+
this.runRegistry.releaseNotification(piboSessionId, notification);
|
|
2260
2687
|
if (this.closing || this.quiescingSessions.has(piboSessionId) || expectedGeneration !== this.runReminderGeneration(piboSessionId))
|
|
2261
2688
|
return;
|
|
2262
2689
|
const message = error instanceof Error ? error.message : String(error);
|