@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
|
@@ -6,10 +6,10 @@ export class ChatSessionQueryService {
|
|
|
6
6
|
constructor(store) {
|
|
7
7
|
this.store = store;
|
|
8
8
|
}
|
|
9
|
-
upsertSession(session, status = "idle") {
|
|
9
|
+
upsertSession(session, status = "idle", lastActivityAt = session.updatedAt) {
|
|
10
10
|
const roomId = chatRoomIdFromMetadata(session.metadata) ?? "room_default";
|
|
11
|
-
this.store.sessions.upsertSession({ session, roomId, status, lastActivityAt
|
|
12
|
-
this.upsertNavigation(session, roomId, status,
|
|
11
|
+
this.store.sessions.upsertSession({ session, roomId, status, lastActivityAt });
|
|
12
|
+
this.upsertNavigation(session, roomId, status, lastActivityAt);
|
|
13
13
|
}
|
|
14
14
|
upsertSessionsIfChanged(sessions) {
|
|
15
15
|
let written = 0;
|
|
@@ -24,11 +24,32 @@ export class ChatSessionQueryService {
|
|
|
24
24
|
}
|
|
25
25
|
return { checked: sessions.length, written, skipped };
|
|
26
26
|
}
|
|
27
|
-
recordEvent(event, session) {
|
|
28
|
-
if (session)
|
|
29
|
-
|
|
27
|
+
recordEvent(event, session, _streamId, createdAt = new Date().toISOString()) {
|
|
28
|
+
if (session) {
|
|
29
|
+
const status = statusFromOutputEvent(event);
|
|
30
|
+
if (status)
|
|
31
|
+
this.upsertSession(session, status, createdAt);
|
|
32
|
+
else
|
|
33
|
+
this.touchSession(session, createdAt);
|
|
34
|
+
}
|
|
30
35
|
return undefined;
|
|
31
36
|
}
|
|
37
|
+
touchSession(session, lastActivityAt) {
|
|
38
|
+
const roomId = chatRoomIdFromMetadata(session.metadata) ?? "room_default";
|
|
39
|
+
this.store.sessions.upsertSession({ session, roomId, lastActivityAt });
|
|
40
|
+
this.store.navigation.upsertSession({
|
|
41
|
+
roomId,
|
|
42
|
+
sessionId: session.id,
|
|
43
|
+
rootSessionId: rootSessionId(session),
|
|
44
|
+
parentId: session.parentId,
|
|
45
|
+
originId: session.originId,
|
|
46
|
+
title: session.title || "Untitled Session",
|
|
47
|
+
profile: session.profile,
|
|
48
|
+
lastActivityAt,
|
|
49
|
+
sortKey: lastActivityAt,
|
|
50
|
+
updatedAt: lastActivityAt,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
32
53
|
listSessions() {
|
|
33
54
|
const rows = this.store.db.prepare(`
|
|
34
55
|
SELECT s.*, b.runtime_instance_id, b.runtime_adapter_id, b.native_session_id, b.binding_state
|
|
@@ -121,12 +142,14 @@ export class ChatSessionQueryService {
|
|
|
121
142
|
&& row.status === status
|
|
122
143
|
&& row.metadata_json === metadataJson
|
|
123
144
|
&& row.created_at === session.createdAt
|
|
124
|
-
&& row.updated_at
|
|
125
|
-
&& row.last_activity_at
|
|
145
|
+
&& row.updated_at >= session.updatedAt
|
|
146
|
+
&& row.last_activity_at >= session.updatedAt
|
|
126
147
|
&& row.navigation_status === status
|
|
127
148
|
&& row.navigation_title === title
|
|
128
|
-
&& row.navigation_sort_key
|
|
129
|
-
&& row.
|
|
149
|
+
&& row.navigation_sort_key !== null
|
|
150
|
+
&& row.navigation_sort_key >= session.updatedAt
|
|
151
|
+
&& row.navigation_updated_at !== null
|
|
152
|
+
&& row.navigation_updated_at >= session.updatedAt;
|
|
130
153
|
}
|
|
131
154
|
upsertNavigation(session, roomId, status, now) {
|
|
132
155
|
this.store.navigation.upsertSession({
|
|
@@ -99,43 +99,40 @@ export class ChatTimelineQueryService {
|
|
|
99
99
|
`).all(...values, TRACE_RECONCILIATION_ENTRY_CAP + 1);
|
|
100
100
|
if (rows.length > TRACE_RECONCILIATION_ENTRY_CAP)
|
|
101
101
|
return false;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
102
|
+
if (nodeIdentity.qualifier) {
|
|
103
|
+
let hasPersistedOrdinal = false;
|
|
104
|
+
const explicitlyAttached = rows.some((row) => {
|
|
105
|
+
let attributes;
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(row.attributes_json);
|
|
108
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
109
|
+
return false;
|
|
110
|
+
attributes = parsed;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
109
113
|
return false;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
&&
|
|
129
|
-
lifecycle.active.payloadMatches = true;
|
|
130
|
-
if (row.type === "tool_execution_finished")
|
|
131
|
-
lifecycle.active = undefined;
|
|
132
|
-
}
|
|
133
|
-
if (!nodeIdentity.qualifier) {
|
|
134
|
-
return invocations.length === 1 && invocations[0].payloadMatches;
|
|
114
|
+
}
|
|
115
|
+
const ordinal = attributes.toolInvocationOrdinal;
|
|
116
|
+
if (Number.isSafeInteger(ordinal))
|
|
117
|
+
hasPersistedOrdinal = true;
|
|
118
|
+
return Number.isSafeInteger(ordinal)
|
|
119
|
+
&& Number(ordinal) === nodeIdentity.qualifier.invocationOrdinal
|
|
120
|
+
&& row.event_id === nodeIdentity.qualifier.eventId
|
|
121
|
+
&& row.type !== "tool_execution_started"
|
|
122
|
+
&& payloadMatchesEventRow(this.store, payload.sha256, input.payloadId, row);
|
|
123
|
+
});
|
|
124
|
+
if (explicitlyAttached || hasPersistedOrdinal)
|
|
125
|
+
return explicitlyAttached;
|
|
126
|
+
if (nodeIdentity.qualifier.invocationOrdinal !== 0)
|
|
127
|
+
return false;
|
|
128
|
+
const inferred = inferLegacyToolInvocations(this.store, rows, payload.sha256, input.payloadId);
|
|
129
|
+
return !inferred.ambiguous
|
|
130
|
+
&& inferred.invocations.length === 1
|
|
131
|
+
&& inferred.invocations[0].eventId === nodeIdentity.qualifier.eventId
|
|
132
|
+
&& inferred.invocations[0].payloadMatches;
|
|
135
133
|
}
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
return exact.length === 1 && exact[0].payloadMatches;
|
|
134
|
+
const inferred = inferLegacyToolInvocations(this.store, rows, payload.sha256, input.payloadId);
|
|
135
|
+
return !inferred.ambiguous && inferred.invocations.length === 1 && inferred.invocations[0].payloadMatches;
|
|
139
136
|
}
|
|
140
137
|
countEventsByType(input = {}) {
|
|
141
138
|
const clauses = [];
|
|
@@ -199,3 +196,31 @@ function payloadMatchesEventRow(store, payloadSha256, payloadId, row) {
|
|
|
199
196
|
return false;
|
|
200
197
|
return store.payloads.findBySha256(inlineSha256)?.id === payloadId;
|
|
201
198
|
}
|
|
199
|
+
function inferLegacyToolInvocations(store, rows, payloadSha256, payloadId) {
|
|
200
|
+
const activeByEvent = new Map();
|
|
201
|
+
const invocations = [];
|
|
202
|
+
let ambiguous = false;
|
|
203
|
+
for (const row of rows) {
|
|
204
|
+
let active = activeByEvent.get(row.event_id);
|
|
205
|
+
if (row.type === "tool_execution_started") {
|
|
206
|
+
if (active) {
|
|
207
|
+
ambiguous = true;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
active = { eventId: row.event_id, payloadMatches: false };
|
|
211
|
+
activeByEvent.set(row.event_id, active);
|
|
212
|
+
invocations.push(active);
|
|
213
|
+
}
|
|
214
|
+
else if (!active) {
|
|
215
|
+
active = { eventId: row.event_id, payloadMatches: false };
|
|
216
|
+
activeByEvent.set(row.event_id, active);
|
|
217
|
+
invocations.push(active);
|
|
218
|
+
}
|
|
219
|
+
if (row.type !== "tool_execution_started" && payloadMatchesEventRow(store, payloadSha256, payloadId, row)) {
|
|
220
|
+
active.payloadMatches = true;
|
|
221
|
+
}
|
|
222
|
+
if (row.type === "tool_execution_finished")
|
|
223
|
+
activeByEvent.delete(row.event_id);
|
|
224
|
+
}
|
|
225
|
+
return { ambiguous, invocations };
|
|
226
|
+
}
|
|
@@ -2,7 +2,7 @@ import { PiboWebHttpError, readJsonBody, responseJson } from '../../web/http.js'
|
|
|
2
2
|
import { getPiboLoopService } from '../../loops/channel.js';
|
|
3
3
|
import { listLoopJobTemplates } from '../../loops/templates.js';
|
|
4
4
|
import { isPiboThinkingLevel } from '../../core/thinking.js';
|
|
5
|
-
import { normalizeLoopStopPolicy } from '../../loops/store.js';
|
|
5
|
+
import { normalizeLoopStopPolicy, PiboLoopActiveRunModeChangeError } from '../../loops/store.js';
|
|
6
6
|
import { isPiboRoomArchived } from './types/rooms.js';
|
|
7
7
|
const CHAT_WEB_API_PREFIX = '/api/chat';
|
|
8
8
|
function requireSameOriginJsonRequest(request) { const contentType = request.headers.get('content-type')?.split(';')[0]?.trim().toLowerCase(); if (contentType !== 'application/json')
|
|
@@ -184,7 +184,15 @@ export async function handleChatLoopApiRequest(options) {
|
|
|
184
184
|
throw new PiboWebHttpError('Loop job not found', 404);
|
|
185
185
|
if ((patch.mode ?? existing.mode) === 'ralph' && ((patch.tokenBudget !== undefined && patch.tokenBudget !== null) || (patch.tokenReserve !== undefined && patch.tokenReserve !== null)))
|
|
186
186
|
throw new PiboWebHttpError('tokenBudget and tokenReserve are only available for goal mode', 400);
|
|
187
|
-
|
|
187
|
+
let job;
|
|
188
|
+
try {
|
|
189
|
+
job = loopStore.updateJob(resource.id, patch);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (error instanceof PiboLoopActiveRunModeChangeError)
|
|
193
|
+
throw new PiboWebHttpError(error.message, 409);
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
188
196
|
if (!job)
|
|
189
197
|
throw new PiboWebHttpError('Loop job not found', 404);
|
|
190
198
|
return responseJson({ job: serializeJob(job) });
|
|
@@ -1,62 +1,90 @@
|
|
|
1
1
|
import { assistantOutputKey, isLiveOnlyOutputEvent, thinkingOutputKey, toolOutputKey } from "./output-event-policy.js";
|
|
2
|
+
const MAX_TRACKED_COMPACTOR_SESSIONS = 1_024;
|
|
2
3
|
export class OutputCompactor {
|
|
3
4
|
assistantBuffers = new Map();
|
|
4
5
|
thinkingBuffers = new Map();
|
|
5
6
|
toolSnapshots = new Map();
|
|
7
|
+
sessionRecency = new Map();
|
|
8
|
+
sessionBufferCounts = new Map();
|
|
6
9
|
compact(event) {
|
|
10
|
+
const prepared = this.prepare(event);
|
|
11
|
+
prepared.ack();
|
|
12
|
+
return prepared;
|
|
13
|
+
}
|
|
14
|
+
prepare(event) {
|
|
7
15
|
const persistedEvents = [];
|
|
8
16
|
const snapshots = [];
|
|
17
|
+
let liveEvents = [event];
|
|
18
|
+
const mutations = [];
|
|
9
19
|
switch (event.type) {
|
|
10
20
|
case "assistant_delta": {
|
|
11
21
|
const key = assistantOutputKey(event);
|
|
12
22
|
const previous = this.assistantBuffers.get(key);
|
|
13
|
-
const
|
|
14
|
-
this.assistantBuffers
|
|
15
|
-
snapshots.push({ ...event, text });
|
|
23
|
+
const next = { event, text: `${previous?.text ?? ""}${event.text}` };
|
|
24
|
+
mutations.push(() => this.setBuffer(this.assistantBuffers, key, next, event.piboSessionId));
|
|
25
|
+
snapshots.push({ ...event, text: next.text });
|
|
16
26
|
break;
|
|
17
27
|
}
|
|
18
28
|
case "assistant_message": {
|
|
19
29
|
const key = assistantOutputKey(event);
|
|
20
30
|
const buffered = this.assistantBuffers.get(key);
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
const canonical = { ...event, text: event.text || buffered?.text || "" };
|
|
32
|
+
mutations.push(() => {
|
|
33
|
+
this.deleteBuffer(this.assistantBuffers, key, buffered, event.piboSessionId);
|
|
34
|
+
});
|
|
35
|
+
persistedEvents.push(canonical);
|
|
36
|
+
liveEvents = [canonical];
|
|
23
37
|
break;
|
|
24
38
|
}
|
|
25
39
|
case "thinking_started": {
|
|
26
40
|
const key = thinkingOutputKey(event);
|
|
27
|
-
|
|
41
|
+
const next = { base: event, text: "" };
|
|
42
|
+
mutations.push(() => {
|
|
43
|
+
if (!this.thinkingBuffers.has(key))
|
|
44
|
+
this.setBuffer(this.thinkingBuffers, key, next, event.piboSessionId);
|
|
45
|
+
});
|
|
28
46
|
persistedEvents.push(event);
|
|
29
47
|
break;
|
|
30
48
|
}
|
|
31
49
|
case "thinking_delta": {
|
|
32
50
|
const key = thinkingOutputKey(event);
|
|
33
51
|
const previous = this.thinkingBuffers.get(key);
|
|
34
|
-
const
|
|
35
|
-
this.thinkingBuffers
|
|
36
|
-
snapshots.push({ ...event, text });
|
|
52
|
+
const next = { base: previous?.base ?? event, text: `${previous?.text ?? ""}${event.text}` };
|
|
53
|
+
mutations.push(() => this.setBuffer(this.thinkingBuffers, key, next, event.piboSessionId));
|
|
54
|
+
snapshots.push({ ...event, text: next.text });
|
|
37
55
|
break;
|
|
38
56
|
}
|
|
39
57
|
case "thinking_finished": {
|
|
40
58
|
const key = thinkingOutputKey(event);
|
|
41
59
|
const buffered = this.thinkingBuffers.get(key);
|
|
42
|
-
|
|
43
|
-
|
|
60
|
+
const canonical = { ...event, text: event.text || buffered?.text || "" };
|
|
61
|
+
mutations.push(() => {
|
|
62
|
+
this.deleteBuffer(this.thinkingBuffers, key, buffered, event.piboSessionId);
|
|
63
|
+
});
|
|
64
|
+
persistedEvents.push(canonical);
|
|
65
|
+
liveEvents = [canonical];
|
|
44
66
|
break;
|
|
45
67
|
}
|
|
46
68
|
case "tool_execution_updated": {
|
|
47
|
-
|
|
69
|
+
const key = toolOutputKey(event);
|
|
70
|
+
mutations.push(() => this.setBuffer(this.toolSnapshots, key, event, event.piboSessionId));
|
|
48
71
|
snapshots.push(event);
|
|
49
72
|
break;
|
|
50
73
|
}
|
|
51
74
|
case "tool_execution_finished": {
|
|
52
|
-
|
|
75
|
+
const key = toolOutputKey(event);
|
|
76
|
+
const previous = this.toolSnapshots.get(key);
|
|
77
|
+
mutations.push(() => {
|
|
78
|
+
this.deleteBuffer(this.toolSnapshots, key, previous, event.piboSessionId);
|
|
79
|
+
});
|
|
53
80
|
persistedEvents.push(event);
|
|
54
81
|
break;
|
|
55
82
|
}
|
|
56
83
|
case "message_finished":
|
|
57
84
|
case "session_error": {
|
|
58
|
-
|
|
59
|
-
persistedEvents.push(event);
|
|
85
|
+
const flushed = this.prepareBoundaryFlush(event, mutations);
|
|
86
|
+
persistedEvents.push(...flushed, event);
|
|
87
|
+
liveEvents = [...flushed, event];
|
|
60
88
|
break;
|
|
61
89
|
}
|
|
62
90
|
default:
|
|
@@ -64,7 +92,23 @@ export class OutputCompactor {
|
|
|
64
92
|
persistedEvents.push(event);
|
|
65
93
|
break;
|
|
66
94
|
}
|
|
67
|
-
|
|
95
|
+
let settled = false;
|
|
96
|
+
return {
|
|
97
|
+
liveEvents,
|
|
98
|
+
persistedEvents,
|
|
99
|
+
snapshots,
|
|
100
|
+
ack: () => {
|
|
101
|
+
if (settled)
|
|
102
|
+
return;
|
|
103
|
+
settled = true;
|
|
104
|
+
for (const mutate of mutations)
|
|
105
|
+
mutate();
|
|
106
|
+
this.touchSession(event.piboSessionId);
|
|
107
|
+
},
|
|
108
|
+
rollback: () => {
|
|
109
|
+
settled = true;
|
|
110
|
+
},
|
|
111
|
+
};
|
|
68
112
|
}
|
|
69
113
|
snapshotsForSession(piboSessionId) {
|
|
70
114
|
const snapshots = [];
|
|
@@ -86,29 +130,68 @@ export class OutputCompactor {
|
|
|
86
130
|
}
|
|
87
131
|
return snapshots;
|
|
88
132
|
}
|
|
89
|
-
|
|
133
|
+
disposeSession(piboSessionId) {
|
|
134
|
+
for (const [key, buffer] of this.assistantBuffers) {
|
|
135
|
+
if (buffer.event.piboSessionId === piboSessionId)
|
|
136
|
+
this.deleteBuffer(this.assistantBuffers, key, buffer, piboSessionId);
|
|
137
|
+
}
|
|
138
|
+
for (const [key, buffer] of this.thinkingBuffers) {
|
|
139
|
+
if (buffer.base.piboSessionId === piboSessionId)
|
|
140
|
+
this.deleteBuffer(this.thinkingBuffers, key, buffer, piboSessionId);
|
|
141
|
+
}
|
|
142
|
+
for (const [key, event] of this.toolSnapshots) {
|
|
143
|
+
if (event.piboSessionId === piboSessionId)
|
|
144
|
+
this.deleteBuffer(this.toolSnapshots, key, event, piboSessionId);
|
|
145
|
+
}
|
|
146
|
+
this.sessionBufferCounts.delete(piboSessionId);
|
|
147
|
+
this.sessionRecency.delete(piboSessionId);
|
|
148
|
+
}
|
|
149
|
+
disposeAll() {
|
|
150
|
+
this.assistantBuffers.clear();
|
|
151
|
+
this.thinkingBuffers.clear();
|
|
152
|
+
this.toolSnapshots.clear();
|
|
153
|
+
this.sessionRecency.clear();
|
|
154
|
+
this.sessionBufferCounts.clear();
|
|
155
|
+
}
|
|
156
|
+
debugState() {
|
|
157
|
+
return {
|
|
158
|
+
sessionCount: this.sessionRecency.size,
|
|
159
|
+
completedSessionCount: [...this.sessionRecency.keys()].filter((sessionId) => !this.sessionHasBuffers(sessionId)).length,
|
|
160
|
+
sessions: [...this.sessionRecency.keys()],
|
|
161
|
+
assistantBufferCount: this.assistantBuffers.size,
|
|
162
|
+
thinkingBufferCount: this.thinkingBuffers.size,
|
|
163
|
+
toolSnapshotCount: this.toolSnapshots.size,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
prepareBoundaryFlush(event, mutations) {
|
|
90
167
|
const flushed = [];
|
|
91
|
-
for (const [key, buffer] of
|
|
168
|
+
for (const [key, buffer] of this.assistantBuffers) {
|
|
92
169
|
if (!matchesBoundary(buffer.event, event))
|
|
93
170
|
continue;
|
|
94
|
-
|
|
171
|
+
mutations.push(() => {
|
|
172
|
+
this.deleteBuffer(this.assistantBuffers, key, buffer, buffer.event.piboSessionId);
|
|
173
|
+
});
|
|
95
174
|
flushed.push({
|
|
96
175
|
type: "assistant_message",
|
|
97
176
|
piboSessionId: buffer.event.piboSessionId,
|
|
98
177
|
eventId: buffer.event.eventId,
|
|
178
|
+
renderSequence: buffer.event.renderSequence,
|
|
99
179
|
assistantIndex: buffer.event.assistantIndex,
|
|
100
180
|
contentIndex: buffer.event.contentIndex,
|
|
101
181
|
text: buffer.text,
|
|
102
182
|
});
|
|
103
183
|
}
|
|
104
|
-
for (const [key, buffer] of
|
|
184
|
+
for (const [key, buffer] of this.thinkingBuffers) {
|
|
105
185
|
if (!matchesBoundary(buffer.base, event))
|
|
106
186
|
continue;
|
|
107
|
-
|
|
187
|
+
mutations.push(() => {
|
|
188
|
+
this.deleteBuffer(this.thinkingBuffers, key, buffer, buffer.base.piboSessionId);
|
|
189
|
+
});
|
|
108
190
|
flushed.push({
|
|
109
191
|
type: "thinking_finished",
|
|
110
192
|
piboSessionId: buffer.base.piboSessionId,
|
|
111
193
|
eventId: buffer.base.eventId,
|
|
194
|
+
renderSequence: buffer.base.renderSequence,
|
|
112
195
|
thinkingIndex: "thinkingIndex" in buffer.base ? buffer.base.thinkingIndex : undefined,
|
|
113
196
|
contentIndex: buffer.base.contentIndex,
|
|
114
197
|
text: buffer.text,
|
|
@@ -116,6 +199,46 @@ export class OutputCompactor {
|
|
|
116
199
|
}
|
|
117
200
|
return flushed;
|
|
118
201
|
}
|
|
202
|
+
touchSession(piboSessionId) {
|
|
203
|
+
this.sessionRecency.delete(piboSessionId);
|
|
204
|
+
this.sessionRecency.set(piboSessionId, true);
|
|
205
|
+
let completedCount = [...this.sessionRecency.keys()].filter((sessionId) => !this.sessionHasBuffers(sessionId)).length;
|
|
206
|
+
while (completedCount > MAX_TRACKED_COMPACTOR_SESSIONS) {
|
|
207
|
+
let removed = false;
|
|
208
|
+
for (const sessionId of this.sessionRecency.keys()) {
|
|
209
|
+
if (this.sessionHasBuffers(sessionId))
|
|
210
|
+
continue;
|
|
211
|
+
this.sessionRecency.delete(sessionId);
|
|
212
|
+
completedCount -= 1;
|
|
213
|
+
removed = true;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
if (!removed)
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
sessionHasBuffers(piboSessionId) {
|
|
221
|
+
return (this.sessionBufferCounts.get(piboSessionId) ?? 0) > 0;
|
|
222
|
+
}
|
|
223
|
+
setBuffer(map, key, value, piboSessionId) {
|
|
224
|
+
if (!map.has(key))
|
|
225
|
+
this.sessionBufferCounts.set(piboSessionId, (this.sessionBufferCounts.get(piboSessionId) ?? 0) + 1);
|
|
226
|
+
setLatest(map, key, value);
|
|
227
|
+
}
|
|
228
|
+
deleteBuffer(map, key, expected, piboSessionId) {
|
|
229
|
+
if (expected === undefined || map.get(key) !== expected)
|
|
230
|
+
return;
|
|
231
|
+
map.delete(key);
|
|
232
|
+
const next = (this.sessionBufferCounts.get(piboSessionId) ?? 1) - 1;
|
|
233
|
+
if (next > 0)
|
|
234
|
+
this.sessionBufferCounts.set(piboSessionId, next);
|
|
235
|
+
else
|
|
236
|
+
this.sessionBufferCounts.delete(piboSessionId);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function setLatest(map, key, value) {
|
|
240
|
+
map.delete(key);
|
|
241
|
+
map.set(key, value);
|
|
119
242
|
}
|
|
120
243
|
function matchesBoundary(bufferEvent, boundary) {
|
|
121
244
|
if (bufferEvent.piboSessionId !== boundary.piboSessionId)
|
|
@@ -2,7 +2,184 @@ export function isLiveOnlyOutputEvent(event) {
|
|
|
2
2
|
return event.type === "assistant_delta" || event.type === "thinking_delta" || event.type === "tool_execution_updated";
|
|
3
3
|
}
|
|
4
4
|
export function isPersistableOutputEvent(event) {
|
|
5
|
-
return !isLiveOnlyOutputEvent(event);
|
|
5
|
+
return isPiboOutputEvent(event) && !isLiveOnlyOutputEvent(event);
|
|
6
|
+
}
|
|
7
|
+
export function isPiboOutputEvent(value) {
|
|
8
|
+
if (!isRecord(value) || typeof value.type !== "string" || typeof value.piboSessionId !== "string")
|
|
9
|
+
return false;
|
|
10
|
+
if (value.eventId !== undefined && typeof value.eventId !== "string")
|
|
11
|
+
return false;
|
|
12
|
+
if (value.renderSequence !== undefined && !isPositiveSafeInteger(value.renderSequence))
|
|
13
|
+
return false;
|
|
14
|
+
if (value.toolInvocationOrdinal !== undefined && !isNonNegativeSafeInteger(value.toolInvocationOrdinal))
|
|
15
|
+
return false;
|
|
16
|
+
if (value.provenance !== undefined && !isMessageProvenance(value.provenance))
|
|
17
|
+
return false;
|
|
18
|
+
switch (value.type) {
|
|
19
|
+
case "message_queued":
|
|
20
|
+
return typeof value.text === "string"
|
|
21
|
+
&& isNonNegativeSafeInteger(value.queuedMessages)
|
|
22
|
+
&& isOptionalEventSource(value.source);
|
|
23
|
+
case "message_steered":
|
|
24
|
+
return typeof value.text === "string"
|
|
25
|
+
&& isOptionalEventSource(value.source)
|
|
26
|
+
&& isOptionalString(value.activeEventId);
|
|
27
|
+
case "message_started":
|
|
28
|
+
return typeof value.text === "string" && isOptionalEventSource(value.source);
|
|
29
|
+
case "assistant_delta":
|
|
30
|
+
case "assistant_message":
|
|
31
|
+
return typeof value.text === "string"
|
|
32
|
+
&& isOptionalNonNegativeSafeInteger(value.assistantIndex)
|
|
33
|
+
&& isOptionalNonNegativeSafeInteger(value.contentIndex);
|
|
34
|
+
case "thinking_delta":
|
|
35
|
+
return typeof value.text === "string"
|
|
36
|
+
&& isOptionalNonNegativeSafeInteger(value.thinkingIndex)
|
|
37
|
+
&& isOptionalNonNegativeSafeInteger(value.contentIndex);
|
|
38
|
+
case "message_finished":
|
|
39
|
+
return isOptionalEventSource(value.source);
|
|
40
|
+
case "thinking_started":
|
|
41
|
+
return isOptionalNonNegativeSafeInteger(value.thinkingIndex) && isOptionalNonNegativeSafeInteger(value.contentIndex);
|
|
42
|
+
case "thinking_finished":
|
|
43
|
+
return isOptionalString(value.text)
|
|
44
|
+
&& isOptionalNonNegativeSafeInteger(value.thinkingIndex)
|
|
45
|
+
&& isOptionalNonNegativeSafeInteger(value.contentIndex);
|
|
46
|
+
case "tool_call":
|
|
47
|
+
return hasOwn(value, "args")
|
|
48
|
+
&& typeof value.toolCallId === "string"
|
|
49
|
+
&& typeof value.toolName === "string"
|
|
50
|
+
&& typeof value.argsComplete === "boolean"
|
|
51
|
+
&& isOptionalString(value.intent);
|
|
52
|
+
case "tool_execution_started":
|
|
53
|
+
return hasOwn(value, "args")
|
|
54
|
+
&& typeof value.toolCallId === "string"
|
|
55
|
+
&& typeof value.toolName === "string"
|
|
56
|
+
&& isOptionalString(value.intent);
|
|
57
|
+
case "tool_execution_updated":
|
|
58
|
+
return hasOwn(value, "args")
|
|
59
|
+
&& hasOwn(value, "partialResult")
|
|
60
|
+
&& typeof value.toolCallId === "string"
|
|
61
|
+
&& typeof value.toolName === "string"
|
|
62
|
+
&& isOptionalString(value.intent);
|
|
63
|
+
case "tool_execution_finished":
|
|
64
|
+
return hasOwn(value, "result")
|
|
65
|
+
&& typeof value.toolCallId === "string"
|
|
66
|
+
&& typeof value.toolName === "string"
|
|
67
|
+
&& typeof value.isError === "boolean"
|
|
68
|
+
&& isOptionalString(value.intent);
|
|
69
|
+
case "subagent_session":
|
|
70
|
+
return typeof value.toolName === "string"
|
|
71
|
+
&& typeof value.subagentName === "string"
|
|
72
|
+
&& typeof value.childPiboSessionId === "string"
|
|
73
|
+
&& isOptionalString(value.requestId)
|
|
74
|
+
&& isOptionalString(value.toolCallId)
|
|
75
|
+
&& isOptionalString(value.threadKey);
|
|
76
|
+
case "assistant_usage":
|
|
77
|
+
return isFiniteNumber(value.totalTokens)
|
|
78
|
+
&& isOptionalNonNegativeSafeInteger(value.usageIndex)
|
|
79
|
+
&& ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens", "reasoningTokens"].every((key) => isOptionalFiniteNumber(value[key]))
|
|
80
|
+
&& isOptionalFiniteNumber(value.costUsd);
|
|
81
|
+
case "compaction_start":
|
|
82
|
+
return typeof value.reason === "string" && isOptionalNonNegativeSafeInteger(value.compactionIndex);
|
|
83
|
+
case "compaction_end":
|
|
84
|
+
return typeof value.reason === "string"
|
|
85
|
+
&& typeof value.aborted === "boolean"
|
|
86
|
+
&& isOptionalNonNegativeSafeInteger(value.compactionIndex)
|
|
87
|
+
&& isOptionalString(value.errorMessage);
|
|
88
|
+
case "approval_requested":
|
|
89
|
+
return isApprovalRequest(value.request);
|
|
90
|
+
case "approval_resolved":
|
|
91
|
+
case "user_input_resolved":
|
|
92
|
+
return typeof value.requestId === "string" && isRuntimeRequestResolution(value.resolution);
|
|
93
|
+
case "user_input_requested":
|
|
94
|
+
return isUserInputRequest(value.request);
|
|
95
|
+
case "execution_result":
|
|
96
|
+
return typeof value.action === "string" && hasOwn(value, "result");
|
|
97
|
+
case "session_error":
|
|
98
|
+
return typeof value.error === "string" && (value.errorDetails === undefined || isRecord(value.errorDetails));
|
|
99
|
+
case "pi_event":
|
|
100
|
+
return hasOwn(value, "event");
|
|
101
|
+
default:
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function isRecord(value) {
|
|
106
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
107
|
+
}
|
|
108
|
+
function hasOwn(value, key) {
|
|
109
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
110
|
+
}
|
|
111
|
+
function isFiniteNumber(value) {
|
|
112
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
113
|
+
}
|
|
114
|
+
function isOptionalFiniteNumber(value) {
|
|
115
|
+
return value === undefined || isFiniteNumber(value);
|
|
116
|
+
}
|
|
117
|
+
function isOptionalString(value) {
|
|
118
|
+
return value === undefined || typeof value === "string";
|
|
119
|
+
}
|
|
120
|
+
function isOptionalNonNegativeSafeInteger(value) {
|
|
121
|
+
return value === undefined || isNonNegativeSafeInteger(value);
|
|
122
|
+
}
|
|
123
|
+
function isPositiveSafeInteger(value) {
|
|
124
|
+
return Number.isSafeInteger(value) && value > 0;
|
|
125
|
+
}
|
|
126
|
+
function isNonNegativeSafeInteger(value) {
|
|
127
|
+
return Number.isSafeInteger(value) && value >= 0;
|
|
128
|
+
}
|
|
129
|
+
function isRuntimeRequestResolution(value) {
|
|
130
|
+
return value === "responded" || value === "cleared" || value === "aborted" || value === "expired";
|
|
131
|
+
}
|
|
132
|
+
function isOptionalEventSource(value) {
|
|
133
|
+
return value === undefined || value === "user" || value === "ui" || value === "service" || value === "actor";
|
|
134
|
+
}
|
|
135
|
+
function isMessageProvenance(value) {
|
|
136
|
+
if (!isRecord(value) || typeof value.kind !== "string")
|
|
137
|
+
return false;
|
|
138
|
+
if (value.kind === "loop-run") {
|
|
139
|
+
return typeof value.jobId === "string"
|
|
140
|
+
&& typeof value.runId === "string"
|
|
141
|
+
&& (value.cause === undefined || value.cause === "run-reminder")
|
|
142
|
+
&& isOptionalString(value.rootEventId);
|
|
143
|
+
}
|
|
144
|
+
if (value.kind === "subagent-request") {
|
|
145
|
+
return typeof value.requestId === "string"
|
|
146
|
+
&& typeof value.controllerPiboSessionId === "string"
|
|
147
|
+
&& isOptionalString(value.loopJobId)
|
|
148
|
+
&& isOptionalString(value.loopRunId);
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
function isApprovalRequest(value) {
|
|
153
|
+
return isRecord(value)
|
|
154
|
+
&& typeof value.requestId === "string"
|
|
155
|
+
&& typeof value.requestType === "string"
|
|
156
|
+
&& isOptionalString(value.title)
|
|
157
|
+
&& isOptionalString(value.detail)
|
|
158
|
+
&& (value.decisions === undefined || (Array.isArray(value.decisions)
|
|
159
|
+
&& value.decisions.every((decision) => isRecord(decision)
|
|
160
|
+
&& typeof decision.id === "string"
|
|
161
|
+
&& typeof decision.label === "string"
|
|
162
|
+
&& isOptionalString(decision.description))));
|
|
163
|
+
}
|
|
164
|
+
function isUserInputRequest(value) {
|
|
165
|
+
return isRecord(value)
|
|
166
|
+
&& typeof value.requestId === "string"
|
|
167
|
+
&& Array.isArray(value.questions)
|
|
168
|
+
&& isOptionalBoolean(value.blocking)
|
|
169
|
+
&& value.questions.every((question) => isRecord(question)
|
|
170
|
+
&& typeof question.id === "string"
|
|
171
|
+
&& typeof question.question === "string"
|
|
172
|
+
&& isOptionalString(question.header)
|
|
173
|
+
&& isOptionalBoolean(question.multiSelect)
|
|
174
|
+
&& isOptionalBoolean(question.allowFreeform)
|
|
175
|
+
&& isOptionalBoolean(question.secret)
|
|
176
|
+
&& (question.options === undefined || (Array.isArray(question.options)
|
|
177
|
+
&& question.options.every((option) => isRecord(option)
|
|
178
|
+
&& typeof option.label === "string"
|
|
179
|
+
&& isOptionalString(option.description)))));
|
|
180
|
+
}
|
|
181
|
+
function isOptionalBoolean(value) {
|
|
182
|
+
return value === undefined || typeof value === "boolean";
|
|
6
183
|
}
|
|
7
184
|
export function assistantOutputKey(event) {
|
|
8
185
|
const partIndex = event.assistantIndex ?? event.contentIndex ?? 0;
|
|
@@ -13,5 +190,5 @@ export function thinkingOutputKey(event) {
|
|
|
13
190
|
return [event.piboSessionId, event.eventId ?? "", partIndex].join(":");
|
|
14
191
|
}
|
|
15
192
|
export function toolOutputKey(event) {
|
|
16
|
-
return [event.piboSessionId, event.eventId ?? "", event.toolCallId].join(":");
|
|
193
|
+
return [event.piboSessionId, event.eventId ?? "", event.toolCallId, event.toolInvocationOrdinal ?? 0].join(":");
|
|
17
194
|
}
|