@pasko70/pibo 3.4.0 → 3.4.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/dist/agent-runtime/routed-session.js +7 -1
- package/dist/agent-runtimes/pi/adapter.js +4 -1
- package/dist/agent-runtimes/pi/history.js +55 -1
- package/dist/apps/chat/data/chat-data-mappers.js +1 -1
- package/dist/apps/chat/stream.js +1 -1
- package/dist/apps/chat/trace-v2.js +1 -0
- package/dist/apps/chat/web-app.js +2 -1
- package/dist/apps/chat-ui/assets/{dist-jVaiGXFD.js → dist-B7Ju7u08.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Dyp6sLQn.js → dist-BDnWOTcs.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-t25moyJS.js → dist-CAiO6h6z.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-AhtdAETR.js → dist-CCR1HsaR.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Bk_L1qbJ.js → dist-CXWLhBio.js} +1 -1
- package/dist/apps/chat-ui/assets/index-0cCGS0o3.js +228 -0
- package/dist/apps/chat-ui/assets/index-BMJGoyM8.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-BD3Ogz9Z.js +43 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/auth/openai-codex-usage.js +34 -5
- package/dist/core/session-router.js +100 -11
- package/dist/data/ingest-service.js +1 -1
- package/dist/data/schema.js +10 -1
- package/dist/debug/agents.js +5 -3
- package/dist/gateway/server.js +1 -1
- package/dist/previews/web-app.js +85 -0
- package/dist/session-ui/terminalRows.js +3 -1
- package/dist/sessions/pibo-data-store.js +38 -1
- package/dist/sessions/store.js +38 -0
- package/dist/shared/tool-call-metrics.js +77 -0
- package/dist/shared/trace-event-projection.js +2 -0
- package/dist/shared/trace-live-reducer.js +1 -0
- package/dist/shared/trace-patch-nodes.js +4 -0
- package/dist/subagents/context.js +2 -2
- package/dist/subagents/observation-query.js +28 -1
- package/dist/subagents/observations.js +8 -0
- package/dist/subagents/tool.js +12 -8
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-CZjNw-aK.js +0 -228
- package/dist/apps/chat-ui/assets/index-CcEjFITM.css +0 -1
- package/dist/apps/chat-vscode-web/assets/index-Spj6M0tn.js +0 -43
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#101d22" />
|
|
7
7
|
<title>Pibo</title>
|
|
8
|
-
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-BD3Ogz9Z.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-b18ZkEo0.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { readPiCredential, resolvePiProviderAuth, } from "../agent-runtimes/pi/credentials.js";
|
|
2
3
|
const OPENAI_CODEX_PROVIDER = "openai-codex";
|
|
3
4
|
const OPENAI_CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
4
5
|
const OPENAI_JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
6
|
+
const USAGE_REFRESH_MS = 30_000;
|
|
7
|
+
const USAGE_MAX_STALE_MS = 5 * 60_000;
|
|
8
|
+
const USAGE_REQUEST_TIMEOUT_MS = 2_000;
|
|
9
|
+
// One bounded, credential-scoped snapshot shared by sessions. Never retain raw credentials.
|
|
10
|
+
let usageCache;
|
|
5
11
|
function isRecord(value) {
|
|
6
12
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7
13
|
}
|
|
@@ -125,8 +131,31 @@ export async function getOpenAiCodexProviderUsageForActiveModel(activeModel) {
|
|
|
125
131
|
if (activeModel?.provider !== OPENAI_CODEX_PROVIDER)
|
|
126
132
|
return undefined;
|
|
127
133
|
const credential = await readPiCredential(OPENAI_CODEX_PROVIDER);
|
|
128
|
-
if (credential?.type !== "oauth")
|
|
134
|
+
if (credential?.type !== "oauth") {
|
|
135
|
+
usageCache = undefined;
|
|
129
136
|
return undefined;
|
|
137
|
+
}
|
|
138
|
+
const key = createHash("sha256").update(JSON.stringify(credential)).digest("hex");
|
|
139
|
+
if (usageCache?.key !== key)
|
|
140
|
+
usageCache = { key, refreshAfter: 0 };
|
|
141
|
+
const cache = usageCache;
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
const usable = () => cache.value && Date.now() - Date.parse(cache.value.fetchedAt) < USAGE_MAX_STALE_MS
|
|
144
|
+
? cache.value
|
|
145
|
+
: undefined;
|
|
146
|
+
if (now < cache.refreshAfter)
|
|
147
|
+
return usable();
|
|
148
|
+
cache.pending ??= fetchOpenAiCodexProviderUsage(credential.accountId)
|
|
149
|
+
.then((value) => { cache.value = value; return value; })
|
|
150
|
+
.catch(() => usable())
|
|
151
|
+
.finally(() => {
|
|
152
|
+
cache.refreshAfter = Date.now() + USAGE_REFRESH_MS;
|
|
153
|
+
cache.pending = undefined;
|
|
154
|
+
});
|
|
155
|
+
// Quota is advisory: stale-while-revalidate keeps repeat status commands local.
|
|
156
|
+
return usable() ?? await cache.pending;
|
|
157
|
+
}
|
|
158
|
+
async function fetchOpenAiCodexProviderUsage(storedAccountId) {
|
|
130
159
|
const resolvedAuth = await resolvePiProviderAuth(OPENAI_CODEX_PROVIDER);
|
|
131
160
|
const accessToken = resolvedAuth?.auth.apiKey;
|
|
132
161
|
if (!accessToken)
|
|
@@ -135,13 +164,13 @@ export async function getOpenAiCodexProviderUsageForActiveModel(activeModel) {
|
|
|
135
164
|
Authorization: `Bearer ${accessToken}`,
|
|
136
165
|
"User-Agent": "codex-cli",
|
|
137
166
|
};
|
|
138
|
-
const accountId = getOpenAiAccountId(accessToken,
|
|
167
|
+
const accountId = getOpenAiAccountId(accessToken, storedAccountId);
|
|
139
168
|
if (accountId)
|
|
140
169
|
headers["ChatGPT-Account-Id"] = accountId;
|
|
141
|
-
const response = await fetch(OPENAI_CODEX_USAGE_URL, { headers });
|
|
170
|
+
const response = await fetch(OPENAI_CODEX_USAGE_URL, { headers, signal: AbortSignal.timeout(USAGE_REQUEST_TIMEOUT_MS) });
|
|
142
171
|
if (!response.ok) {
|
|
143
|
-
|
|
144
|
-
throw new Error(`OpenAI Codex usage request failed: ${response.status}
|
|
172
|
+
await response.body?.cancel();
|
|
173
|
+
throw new Error(`OpenAI Codex usage request failed: ${response.status}`);
|
|
145
174
|
}
|
|
146
175
|
return normalizeUsagePayload(await response.json());
|
|
147
176
|
}
|
|
@@ -7,13 +7,13 @@ import { runtimeSessionErrorDetails } from "./session-errors.js";
|
|
|
7
7
|
import { OutputRenderSequencer, outputRenderHighWaterStore } from "./output-render-sequence.js";
|
|
8
8
|
import { normalizePiboAgentSessionName, } from "../subagents/tool.js";
|
|
9
9
|
import { piboAgentObservationDetails, piboAgentObservationKind, piboAgentObservationRole, piboAgentObservationSourceFromEvent, piboAgentObservationText, } from "../subagents/observations.js";
|
|
10
|
-
import { preparePiboAgentObservationQuery, selectPiboAgentObservationPage, } from "../subagents/observation-query.js";
|
|
10
|
+
import { piboAgentObservationCursorScopeKey, preparePiboAgentObservationQuery, selectPiboAgentObservationPage, } from "../subagents/observation-query.js";
|
|
11
11
|
import { PiboRunRegistry } from "../runs/registry.js";
|
|
12
12
|
import { PiboRunCancellationError, PiboRunCancelledError, PiboRunExecutionTimeoutError, waitForRunCancellationSettlement } from "../runs/lifecycle.js";
|
|
13
13
|
import { PiboRunResourceLimitError } from "../runs/resource-isolation.js";
|
|
14
14
|
import { createPiboSignalRegistry } from "../signals/registry.js";
|
|
15
15
|
import { createDefaultPiboReliabilityStore } from "../reliability/store.js";
|
|
16
|
-
import { InMemoryPiboSessionStore, createPiboSessionId, } from "../sessions/store.js";
|
|
16
|
+
import { PIBO_AGENT_OBSERVATION_AUTO_CURSOR_MAX_SCOPES, InMemoryPiboSessionStore, createPiboSessionId, } from "../sessions/store.js";
|
|
17
17
|
import { createAgentRuntimeBindingPersistence } from "../sessions/runtime-binding-persistence.js";
|
|
18
18
|
import { createLegacyPiRuntimeSessionBinding, RuntimeSessionBindingConflictError, } from "../sessions/runtime-binding.js";
|
|
19
19
|
import { AgentRuntimeBindingMissingError, AgentRuntimeUnavailableError } from "../agent-runtime/errors.js";
|
|
@@ -349,7 +349,9 @@ export class PiboSessionRouter {
|
|
|
349
349
|
activeSubagentRequests = new Map();
|
|
350
350
|
subagentRequestIdsByEvent = new Map();
|
|
351
351
|
agentObservations = [];
|
|
352
|
+
agentObservationHighWaterByParent = new Map();
|
|
352
353
|
agentObservationEvictedThroughByParent = new Map();
|
|
354
|
+
agentObservationAutoCursorFallback = new Map();
|
|
353
355
|
nextAgentObservationSequence = 1;
|
|
354
356
|
scheduledRunReminders = new Map();
|
|
355
357
|
runReminderDeliveries = new Map();
|
|
@@ -945,16 +947,41 @@ export class PiboSessionRouter {
|
|
|
945
947
|
const status = this.sessions.get(piboSessionId)?.getStatus();
|
|
946
948
|
return status ? this.withPersistedRuntimeBinding(status) : undefined;
|
|
947
949
|
}
|
|
948
|
-
async getSessionStatusSnapshot(piboSessionId) {
|
|
949
|
-
const session = await this.getOrCreateSession(piboSessionId);
|
|
950
|
+
async getSessionStatusSnapshot(piboSessionId, options) {
|
|
951
|
+
const session = options?.activate === false ? this.sessions.get(piboSessionId) : await this.getOrCreateSession(piboSessionId);
|
|
952
|
+
if (!session) {
|
|
953
|
+
this.resolvePiboSession(piboSessionId);
|
|
954
|
+
return undefined;
|
|
955
|
+
}
|
|
950
956
|
try {
|
|
951
957
|
return this.withPersistedRuntimeBinding(await session.getStatusSnapshot());
|
|
952
958
|
}
|
|
953
959
|
finally {
|
|
954
|
-
|
|
960
|
+
// Passive header polling must neither create nor indefinitely retain a runtime.
|
|
961
|
+
if (options?.activate !== false)
|
|
962
|
+
this.scheduleIdleSessionEvictionIfIdle(piboSessionId);
|
|
955
963
|
}
|
|
956
964
|
}
|
|
957
965
|
async getSessionForkCandidates(piboSessionId) {
|
|
966
|
+
const canReadPersisted = () => !this.closing
|
|
967
|
+
&& !this.quiescingSessions.has(piboSessionId)
|
|
968
|
+
&& !this.disposingSessions.has(piboSessionId)
|
|
969
|
+
&& !this.sessions.has(piboSessionId)
|
|
970
|
+
&& !this.pendingSessions.has(piboSessionId);
|
|
971
|
+
if (canReadPersisted()) {
|
|
972
|
+
const record = this.resolvePiboSession(piboSessionId);
|
|
973
|
+
const binding = this.resolveSessionRuntimeBinding(record);
|
|
974
|
+
const adapter = this.resolveAgentRuntimeRegistry(binding.runtimeInstanceId).requireAgentRuntimeAdapter(binding.runtimeInstanceId);
|
|
975
|
+
if (binding.state === "bound" && adapter.descriptor.id === binding.adapterId
|
|
976
|
+
&& adapter.descriptor.capabilities.lifecycle.fork && adapter.readForkCandidates) {
|
|
977
|
+
const workspace = record.workspace ?? this.options.cwd ?? getDefaultPiboWorkspace();
|
|
978
|
+
const candidates = await adapter.readForkCandidates({ binding, workspace });
|
|
979
|
+
const current = this.resolvePiboSession(piboSessionId);
|
|
980
|
+
if (candidates !== undefined && canReadPersisted() && current.workspace === record.workspace
|
|
981
|
+
&& runtimeBindingsEqual(binding, this.resolveSessionRuntimeBinding(current)))
|
|
982
|
+
return candidates;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
958
985
|
const session = await this.getOrCreateSession(piboSessionId);
|
|
959
986
|
try {
|
|
960
987
|
return await session.getForkCandidates();
|
|
@@ -1146,7 +1173,9 @@ export class PiboSessionRouter {
|
|
|
1146
1173
|
this.subagentRequestIdsByEvent.clear();
|
|
1147
1174
|
this.outputRenderSequencer.disposeAll();
|
|
1148
1175
|
this.agentObservations.length = 0;
|
|
1176
|
+
this.agentObservationHighWaterByParent.clear();
|
|
1149
1177
|
this.agentObservationEvictedThroughByParent.clear();
|
|
1178
|
+
this.agentObservationAutoCursorFallback.clear();
|
|
1150
1179
|
await this.telemetryWriter?.dispose();
|
|
1151
1180
|
const lifecycleFailures = [...authDisposals, ...webAppDisposals]
|
|
1152
1181
|
.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
@@ -1463,6 +1492,11 @@ export class PiboSessionRouter {
|
|
|
1463
1492
|
this.runtimeResourceSessions.delete(piboSession.id);
|
|
1464
1493
|
throw error;
|
|
1465
1494
|
}
|
|
1495
|
+
const resourceInspection = resources.getInspection();
|
|
1496
|
+
const statusResources = {
|
|
1497
|
+
enabledSkills: [...new Set(resourceInspection.skills.map((skill) => skill.name))],
|
|
1498
|
+
contextFiles: [...new Set(resourceInspection.context.map((contribution) => (contribution.sourcePath ?? contribution.path ?? contribution.label)).filter((value) => Boolean(value)))],
|
|
1499
|
+
};
|
|
1466
1500
|
session = new RoutedSession(piboSession.id, runtimeSession, this.emitOutput, this.pluginRegistry, {
|
|
1467
1501
|
forwardLegacyPiEvents: this.options.forwardPiEvents ?? false,
|
|
1468
1502
|
onNativeEventTelemetry: this.telemetryRecorder
|
|
@@ -1517,6 +1551,7 @@ export class PiboSessionRouter {
|
|
|
1517
1551
|
const { runtimeInstanceId: _runtimeInstanceId, ...result } = await runtimeRegistry.logoutAgentRuntimeAuth(binding.runtimeInstanceId, input);
|
|
1518
1552
|
return result;
|
|
1519
1553
|
},
|
|
1554
|
+
statusResources,
|
|
1520
1555
|
});
|
|
1521
1556
|
this.sessions.set(piboSession.id, session);
|
|
1522
1557
|
return session;
|
|
@@ -2092,8 +2127,18 @@ export class PiboSessionRouter {
|
|
|
2092
2127
|
observeManagedAgents(parentPiboSessionId, input) {
|
|
2093
2128
|
for (const agentId of input.agentIds ?? [])
|
|
2094
2129
|
this.requireManagedAgent(parentPiboSessionId, agentId);
|
|
2095
|
-
const
|
|
2130
|
+
const baseQuery = preparePiboAgentObservationQuery(input);
|
|
2131
|
+
const cursorScope = piboAgentObservationCursorScopeKey(baseQuery.filters);
|
|
2132
|
+
const explicitAfterSequence = input.afterSequence !== undefined;
|
|
2133
|
+
const savedAfterSequence = baseQuery.cursorMode === "auto" && !explicitAfterSequence
|
|
2134
|
+
? this.getAgentObservationAutoCursor(parentPiboSessionId, cursorScope)
|
|
2135
|
+
: undefined;
|
|
2136
|
+
const query = savedAfterSequence === undefined
|
|
2137
|
+
? baseQuery
|
|
2138
|
+
: preparePiboAgentObservationQuery({ ...input, afterSequence: savedAfterSequence });
|
|
2096
2139
|
const observations = this.agentObservations;
|
|
2140
|
+
const evictedThrough = this.agentObservationEvictedThroughByParent.get(parentPiboSessionId) ?? 0;
|
|
2141
|
+
const sourceHighWater = Math.max(evictedThrough, this.agentObservationHighWaterByParent.get(parentPiboSessionId) ?? 0);
|
|
2097
2142
|
function* ordered() {
|
|
2098
2143
|
const start = query.scanOrder === "asc" ? 0 : observations.length - 1;
|
|
2099
2144
|
const end = query.scanOrder === "asc" ? observations.length : -1;
|
|
@@ -2104,7 +2149,48 @@ export class PiboSessionRouter {
|
|
|
2104
2149
|
yield observation;
|
|
2105
2150
|
}
|
|
2106
2151
|
}
|
|
2107
|
-
|
|
2152
|
+
const page = selectPiboAgentObservationPage(ordered(), query, { evictedThrough });
|
|
2153
|
+
if (query.cursorMode === "history")
|
|
2154
|
+
return page;
|
|
2155
|
+
const initialSnapshot = !explicitAfterSequence && savedAfterSequence === undefined;
|
|
2156
|
+
const nextAfterSequence = initialSnapshot || !page.truncated
|
|
2157
|
+
? Math.max(page.nextAfterSequence, sourceHighWater)
|
|
2158
|
+
: page.nextAfterSequence;
|
|
2159
|
+
const advancedAfterSequence = this.advanceAgentObservationAutoCursor(parentPiboSessionId, cursorScope, nextAfterSequence);
|
|
2160
|
+
return {
|
|
2161
|
+
...page,
|
|
2162
|
+
autoCursorSequence: advancedAfterSequence,
|
|
2163
|
+
};
|
|
2164
|
+
}
|
|
2165
|
+
getAgentObservationAutoCursor(parentPiboSessionId, cursorScope) {
|
|
2166
|
+
if (this.sessionStore.getAgentObservationAutoCursor) {
|
|
2167
|
+
return this.sessionStore.getAgentObservationAutoCursor(parentPiboSessionId, cursorScope);
|
|
2168
|
+
}
|
|
2169
|
+
return this.agentObservationAutoCursorFallback.get(JSON.stringify([parentPiboSessionId, cursorScope]));
|
|
2170
|
+
}
|
|
2171
|
+
advanceAgentObservationAutoCursor(parentPiboSessionId, cursorScope, sequence) {
|
|
2172
|
+
if (this.sessionStore.advanceAgentObservationAutoCursor) {
|
|
2173
|
+
return this.sessionStore.advanceAgentObservationAutoCursor(parentPiboSessionId, cursorScope, sequence);
|
|
2174
|
+
}
|
|
2175
|
+
const key = JSON.stringify([parentPiboSessionId, cursorScope]);
|
|
2176
|
+
const advanced = Math.max(this.agentObservationAutoCursorFallback.get(key) ?? 0, sequence);
|
|
2177
|
+
this.agentObservationAutoCursorFallback.delete(key);
|
|
2178
|
+
this.agentObservationAutoCursorFallback.set(key, advanced);
|
|
2179
|
+
const prefix = `${JSON.stringify([parentPiboSessionId]).slice(0, -1)},`;
|
|
2180
|
+
let scopeCount = 0;
|
|
2181
|
+
for (const existingKey of this.agentObservationAutoCursorFallback.keys()) {
|
|
2182
|
+
if (existingKey.startsWith(prefix))
|
|
2183
|
+
scopeCount += 1;
|
|
2184
|
+
}
|
|
2185
|
+
for (const existingKey of this.agentObservationAutoCursorFallback.keys()) {
|
|
2186
|
+
if (scopeCount <= PIBO_AGENT_OBSERVATION_AUTO_CURSOR_MAX_SCOPES)
|
|
2187
|
+
break;
|
|
2188
|
+
if (!existingKey.startsWith(prefix))
|
|
2189
|
+
continue;
|
|
2190
|
+
this.agentObservationAutoCursorFallback.delete(existingKey);
|
|
2191
|
+
scopeCount -= 1;
|
|
2192
|
+
}
|
|
2193
|
+
return advanced;
|
|
2108
2194
|
}
|
|
2109
2195
|
async killManagedAgent(parentPiboSessionId, agentId) {
|
|
2110
2196
|
const child = this.requireManagedAgent(parentPiboSessionId, agentId);
|
|
@@ -2294,16 +2380,16 @@ export class PiboSessionRouter {
|
|
|
2294
2380
|
throw new Error(`Subagent "${subagent.name}" exceeded max depth ${maxDepth} from Pibo session "${parentPiboSessionId}"`);
|
|
2295
2381
|
}
|
|
2296
2382
|
}
|
|
2297
|
-
getSubagentDepth(piboSessionId) {
|
|
2383
|
+
getSubagentDepth(piboSessionId, sessionsById) {
|
|
2298
2384
|
let depth = 0;
|
|
2299
|
-
let current = this.sessionStore.get(piboSessionId);
|
|
2385
|
+
let current = sessionsById ? sessionsById.get(piboSessionId) : this.sessionStore.get(piboSessionId);
|
|
2300
2386
|
const seen = new Set();
|
|
2301
2387
|
while (current?.parentId) {
|
|
2302
2388
|
if (seen.has(current.parentId))
|
|
2303
2389
|
break;
|
|
2304
2390
|
seen.add(current.parentId);
|
|
2305
2391
|
depth += 1;
|
|
2306
|
-
current = this.sessionStore.get(current.parentId);
|
|
2392
|
+
current = sessionsById ? sessionsById.get(current.parentId) : this.sessionStore.get(current.parentId);
|
|
2307
2393
|
}
|
|
2308
2394
|
return depth;
|
|
2309
2395
|
}
|
|
@@ -2406,6 +2492,7 @@ export class PiboSessionRouter {
|
|
|
2406
2492
|
details: piboAgentObservationDetails(event),
|
|
2407
2493
|
};
|
|
2408
2494
|
this.agentObservations.push(observation);
|
|
2495
|
+
this.agentObservationHighWaterByParent.set(session.parentId, Math.max(this.agentObservationHighWaterByParent.get(session.parentId) ?? 0, sequence));
|
|
2409
2496
|
if (this.agentObservations.length > MAX_AGENT_OBSERVATIONS) {
|
|
2410
2497
|
const evicted = this.agentObservations.splice(0, this.agentObservations.length - MAX_AGENT_OBSERVATIONS);
|
|
2411
2498
|
for (const item of evicted) {
|
|
@@ -2561,7 +2648,9 @@ export class PiboSessionRouter {
|
|
|
2561
2648
|
}
|
|
2562
2649
|
projectKnownSessionSignals() {
|
|
2563
2650
|
const sessions = this.sessionStore.list?.() ?? [];
|
|
2564
|
-
|
|
2651
|
+
// The complete list is already loaded; avoid an additional store query for every ancestor.
|
|
2652
|
+
const sessionsById = new Map(sessions.map((session) => [session.id, session]));
|
|
2653
|
+
const depthBySessionId = new Map(sessions.map((session) => [session.id, this.getSubagentDepth(session.id, sessionsById)]));
|
|
2565
2654
|
sessions.sort((left, right) => (depthBySessionId.get(left.id) ?? 0) - (depthBySessionId.get(right.id) ?? 0));
|
|
2566
2655
|
for (const session of sessions) {
|
|
2567
2656
|
this.signalRegistry.project({ type: "session_created", session });
|
|
@@ -427,7 +427,7 @@ function specificAttributesForOutputEvent(event) {
|
|
|
427
427
|
if (event.type === "tool_call")
|
|
428
428
|
return { toolCallId: event.toolCallId, toolName: event.toolName, argsComplete: event.argsComplete, intent: event.intent };
|
|
429
429
|
if (event.type === "tool_execution_started" || event.type === "tool_execution_updated" || event.type === "tool_execution_finished")
|
|
430
|
-
return { toolCallId: event.toolCallId, toolName: event.toolName, isError: "isError" in event ? event.isError : undefined, intent: event.intent };
|
|
430
|
+
return { toolCallId: event.toolCallId, toolName: event.toolName, isError: "isError" in event ? event.isError : undefined, intent: event.intent, toolMetrics: event.type === "tool_execution_finished" ? event.toolMetrics : undefined };
|
|
431
431
|
if (event.type === "subagent_session")
|
|
432
432
|
return { toolCallId: event.toolCallId, toolName: event.toolName, subagentName: event.subagentName, childPiboSessionId: event.childPiboSessionId, threadKey: event.threadKey };
|
|
433
433
|
if (event.type === "execution_result")
|
package/dist/data/schema.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const PIBO_DATA_SCHEMA_VERSION =
|
|
1
|
+
export const PIBO_DATA_SCHEMA_VERSION = 9;
|
|
2
2
|
const NATIVE_HISTORY_FALLBACK_SCHEMA_VERSION = 5;
|
|
3
3
|
const retiredScopeColumn = ["owner", "scope"].join("_");
|
|
4
4
|
const payloadTableDefinition = `
|
|
@@ -254,6 +254,15 @@ function applyPiboDataSchemaInTransaction(db, hooks, previousVersion, tablesToRe
|
|
|
254
254
|
FOREIGN KEY (parent_pibo_session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
255
255
|
);
|
|
256
256
|
|
|
257
|
+
CREATE TABLE IF NOT EXISTS session_agent_observation_auto_cursors (
|
|
258
|
+
parent_pibo_session_id TEXT NOT NULL,
|
|
259
|
+
cursor_scope TEXT NOT NULL,
|
|
260
|
+
sequence INTEGER NOT NULL CHECK(sequence >= 0),
|
|
261
|
+
updated_at TEXT NOT NULL,
|
|
262
|
+
PRIMARY KEY (parent_pibo_session_id, cursor_scope),
|
|
263
|
+
FOREIGN KEY (parent_pibo_session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
|
264
|
+
);
|
|
265
|
+
|
|
257
266
|
CREATE TABLE IF NOT EXISTS session_output_part_counters (
|
|
258
267
|
pibo_session_id TEXT NOT NULL,
|
|
259
268
|
event_id TEXT NOT NULL,
|
package/dist/debug/agents.js
CHANGED
|
@@ -81,7 +81,7 @@ export function inspectDebugAgentObservations(parentPiboSessionId, store, input
|
|
|
81
81
|
throw new Error(`Debug store "pibo-data" not found at ${store.path}`);
|
|
82
82
|
const db = openReadOnlyDebugDatabase(store);
|
|
83
83
|
try {
|
|
84
|
-
const query = preparePiboAgentObservationQuery(input);
|
|
84
|
+
const query = preparePiboAgentObservationQuery({ ...input, cursorMode: "history" });
|
|
85
85
|
const owned = readOwnedAgents(db, parentPiboSessionId);
|
|
86
86
|
const ownedById = new Map(owned.map((agent) => [agent.agentId, agent]));
|
|
87
87
|
for (const agentId of input.agentIds ?? []) {
|
|
@@ -287,8 +287,10 @@ Usage:
|
|
|
287
287
|
[--order asc|desc] [--limit 1..200] [--include-tools]
|
|
288
288
|
[--tool-detail summary|full] [--details] [--json]
|
|
289
289
|
|
|
290
|
-
Default: the newest 20 completed assistant messages
|
|
291
|
-
|
|
290
|
+
Default: the newest 20 completed assistant messages; streaming deltas and tools are hidden.
|
|
291
|
+
The CLI is stateless history inspection. Use --after-sequence for explicit incremental polling.
|
|
292
|
+
Use --include-tools only for stalls, errors, or targeted diagnosis; prefer --tool-call-id when known.
|
|
293
|
+
Explicit --event-type or --kind
|
|
292
294
|
filters retain access to progress events. Repeat plural filters for OR within that field.
|
|
293
295
|
Different fields combine with AND. With --after-sequence, pages always consume the oldest unseen rows;
|
|
294
296
|
--order desc reverses only the returned page, so nextAfterSequence remains safe for polling.
|
package/dist/gateway/server.js
CHANGED
|
@@ -421,7 +421,7 @@ export class PiboGatewayServer {
|
|
|
421
421
|
},
|
|
422
422
|
rebindSessionRuntime: (piboSessionId, input) => this.requireRouter().rebindSessionRuntime(piboSessionId, input),
|
|
423
423
|
getSessionRuntimeStatus: (piboSessionId) => this.requireRouter().getSessionRuntimeStatus(piboSessionId),
|
|
424
|
-
getSessionStatusSnapshot: (piboSessionId) => this.requireRouter().getSessionStatusSnapshot(piboSessionId),
|
|
424
|
+
getSessionStatusSnapshot: (piboSessionId, options) => this.requireRouter().getSessionStatusSnapshot(piboSessionId, options),
|
|
425
425
|
getSessionForkCandidates: (piboSessionId) => this.requireRouter().getSessionForkCandidates(piboSessionId),
|
|
426
426
|
listSessionRuntimeStatuses: () => this.requireRouter().listSessionRuntimeStatuses(),
|
|
427
427
|
listRuns: (options) => this.requireRouter().listRuns(options),
|
package/dist/previews/web-app.js
CHANGED
|
@@ -8,7 +8,10 @@ import { PreviewCapacityError, PreviewStore, createDefaultPreviewStore, previewE
|
|
|
8
8
|
export const PREVIEW_WEB_APP_NAME = "pibo.session-live-previews";
|
|
9
9
|
export const PREVIEW_WEB_MOUNT_PATH = "/apps/previews";
|
|
10
10
|
export const PREVIEW_WEB_API_PREFIX = "/api/previews";
|
|
11
|
+
export const PREVIEW_EVENTS_PATH = `${PREVIEW_WEB_API_PREFIX}/events`;
|
|
11
12
|
export const PREVIEW_SESSION_EXCHANGE_PATH = "/__pibo/session";
|
|
13
|
+
const DEFAULT_PREVIEW_EVENT_POLL_INTERVAL_MS = 1_000;
|
|
14
|
+
const PREVIEW_EVENT_HEARTBEAT_INTERVAL_MS = 25_000;
|
|
12
15
|
function escapeHtml(value) {
|
|
13
16
|
return value
|
|
14
17
|
.replaceAll("&", "&")
|
|
@@ -69,6 +72,79 @@ async function publicExposure(exposure, baseURL) {
|
|
|
69
72
|
openUrl: `${PREVIEW_WEB_API_PREFIX}/${encodeURIComponent(exposure.id)}/open`,
|
|
70
73
|
};
|
|
71
74
|
}
|
|
75
|
+
function writePreviewEvent(controller, preview) {
|
|
76
|
+
controller.enqueue(new TextEncoder().encode([
|
|
77
|
+
"event: preview-created",
|
|
78
|
+
`data: ${JSON.stringify({ type: "preview-created", preview })}`,
|
|
79
|
+
"",
|
|
80
|
+
"",
|
|
81
|
+
].join("\n")));
|
|
82
|
+
}
|
|
83
|
+
function createPreviewEventStream(input) {
|
|
84
|
+
const store = input.databasePath ? new PreviewStore(input.databasePath) : createDefaultPreviewStore();
|
|
85
|
+
const knownPreviewIds = new Set(store.listExposures({ piboSessionId: input.piboSessionId }).map((preview) => preview.id));
|
|
86
|
+
let closed = false;
|
|
87
|
+
let polling = false;
|
|
88
|
+
let pollTimer;
|
|
89
|
+
let heartbeatTimer;
|
|
90
|
+
const close = () => {
|
|
91
|
+
if (closed)
|
|
92
|
+
return;
|
|
93
|
+
closed = true;
|
|
94
|
+
if (pollTimer)
|
|
95
|
+
clearInterval(pollTimer);
|
|
96
|
+
if (heartbeatTimer)
|
|
97
|
+
clearInterval(heartbeatTimer);
|
|
98
|
+
store.close();
|
|
99
|
+
};
|
|
100
|
+
const stream = new ReadableStream({
|
|
101
|
+
start(controller) {
|
|
102
|
+
controller.enqueue(new TextEncoder().encode(": ready\n\n"));
|
|
103
|
+
const poll = async () => {
|
|
104
|
+
if (closed || polling)
|
|
105
|
+
return;
|
|
106
|
+
polling = true;
|
|
107
|
+
try {
|
|
108
|
+
const created = store.listExposures({ piboSessionId: input.piboSessionId })
|
|
109
|
+
.filter((preview) => !knownPreviewIds.has(preview.id))
|
|
110
|
+
.reverse();
|
|
111
|
+
for (const exposure of created) {
|
|
112
|
+
knownPreviewIds.add(exposure.id);
|
|
113
|
+
const preview = await publicExposure(exposure, input.baseURL);
|
|
114
|
+
if (closed)
|
|
115
|
+
return;
|
|
116
|
+
writePreviewEvent(controller, preview);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (!closed)
|
|
121
|
+
controller.error(error);
|
|
122
|
+
close();
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
polling = false;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
pollTimer = setInterval(() => void poll(), input.pollIntervalMs);
|
|
129
|
+
heartbeatTimer = setInterval(() => {
|
|
130
|
+
if (!closed)
|
|
131
|
+
controller.enqueue(new TextEncoder().encode(": heartbeat\n\n"));
|
|
132
|
+
}, PREVIEW_EVENT_HEARTBEAT_INTERVAL_MS);
|
|
133
|
+
},
|
|
134
|
+
cancel() {
|
|
135
|
+
close();
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
return new Response(stream, {
|
|
139
|
+
headers: {
|
|
140
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
141
|
+
"cache-control": "no-cache, no-transform",
|
|
142
|
+
"content-encoding": "identity",
|
|
143
|
+
"x-accel-buffering": "no",
|
|
144
|
+
connection: "keep-alive",
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
72
148
|
function readBody(request, maxBytes) {
|
|
73
149
|
return new Promise((resolve, reject) => {
|
|
74
150
|
const contentLength = Number(request.headers["content-length"]);
|
|
@@ -206,6 +282,7 @@ export function createPreviewWebApp(options = {}) {
|
|
|
206
282
|
if (!Number.isInteger(browserSessionTtlMinutes) || browserSessionTtlMinutes < 1 || browserSessionTtlMinutes > 24 * 60) {
|
|
207
283
|
throw new Error("Preview browser session lifetime must be between 1 minute and 24 hours");
|
|
208
284
|
}
|
|
285
|
+
const eventPollIntervalMs = Math.max(50, options.eventPollIntervalMs ?? DEFAULT_PREVIEW_EVENT_POLL_INTERVAL_MS);
|
|
209
286
|
const maxProxyConnections = options.maxProxyConnections ??
|
|
210
287
|
configured.preview?.maxProxyConnections ??
|
|
211
288
|
DEFAULT_MAX_PREVIEW_PROXY_CONNECTIONS;
|
|
@@ -261,6 +338,14 @@ export function createPreviewWebApp(options = {}) {
|
|
|
261
338
|
return new Response(null, { status: allowed ? 200 : 403, headers: { "cache-control": "no-store" } });
|
|
262
339
|
}
|
|
263
340
|
await context.requireSession({ request });
|
|
341
|
+
if (url.pathname === PREVIEW_EVENTS_PATH && request.method === "GET") {
|
|
342
|
+
const piboSessionId = url.searchParams.get("piboSessionId")?.trim();
|
|
343
|
+
if (!piboSessionId)
|
|
344
|
+
return responseJson({ error: "piboSessionId is required" }, { status: 400 });
|
|
345
|
+
if (!baseURL)
|
|
346
|
+
return responseJson({ error: "Live previews are not configured. Set preview.baseURL." }, { status: 503 });
|
|
347
|
+
return createPreviewEventStream({ baseURL, databasePath, piboSessionId, pollIntervalMs: eventPollIntervalMs });
|
|
348
|
+
}
|
|
264
349
|
if (url.pathname === PREVIEW_WEB_API_PREFIX && request.method === "GET") {
|
|
265
350
|
const piboSessionId = url.searchParams.get("piboSessionId")?.trim();
|
|
266
351
|
if (!piboSessionId)
|
|
@@ -22,7 +22,7 @@ export function buildCompactTerminalRows(traceView, options) {
|
|
|
22
22
|
const candidates = syncThinkingToolRows(flatNodes.map((item) => createRowCandidate(item.node, item.turnId)));
|
|
23
23
|
applyCompletedTurnTiming(candidates, turnById);
|
|
24
24
|
const reconciled = reconcileConceptualRowCandidates(candidates);
|
|
25
|
-
const rows = (options.toolDisplayMode ?? "default") === "default"
|
|
25
|
+
const rows = !options.debugMode && (options.toolDisplayMode ?? "default") === "default"
|
|
26
26
|
? groupRelatedToolCandidates(reconciled).map((candidate) => candidate.row)
|
|
27
27
|
: reconciled.map((candidate) => candidate.row);
|
|
28
28
|
return applyToolDisplayMode(rows, options.toolDisplayMode ?? "default");
|
|
@@ -165,6 +165,8 @@ function createRowCandidate(node, turnId) {
|
|
|
165
165
|
...candidate.row,
|
|
166
166
|
id: compactTerminalRowIdentity(node),
|
|
167
167
|
intent: node.intent,
|
|
168
|
+
isToolCall: node.type === "tool.call" || node.type === "tool.result" || (node.type === "agent.delegation" && Boolean(node.toolCallId)),
|
|
169
|
+
toolMetrics: node.toolMetrics,
|
|
168
170
|
...debugFields(node),
|
|
169
171
|
},
|
|
170
172
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChatDataIngestService } from "../data/ingest-service.js";
|
|
2
2
|
import { PiboDataStore } from "../data/pibo-store.js";
|
|
3
|
-
import { createPiboSession, matchesFindInput, } from "./store.js";
|
|
3
|
+
import { PIBO_AGENT_OBSERVATION_AUTO_CURSOR_MAX_SCOPES, createPiboSession, matchesFindInput, } from "./store.js";
|
|
4
4
|
import { createLegacyPiRuntimeSessionBinding, nextRuntimeSessionBinding, RuntimeSessionBindingConflictError, } from "./runtime-binding.js";
|
|
5
5
|
const SESSION_SELECT = `
|
|
6
6
|
SELECT
|
|
@@ -165,6 +165,43 @@ export class PiboDataSessionStore {
|
|
|
165
165
|
return sequence;
|
|
166
166
|
});
|
|
167
167
|
}
|
|
168
|
+
getAgentObservationAutoCursor(parentPiboSessionId, cursorScope) {
|
|
169
|
+
const row = this.db.prepare(`
|
|
170
|
+
SELECT sequence
|
|
171
|
+
FROM session_agent_observation_auto_cursors
|
|
172
|
+
WHERE parent_pibo_session_id = ? AND cursor_scope = ?
|
|
173
|
+
`).get(parentPiboSessionId, cursorScope);
|
|
174
|
+
return row?.sequence;
|
|
175
|
+
}
|
|
176
|
+
advanceAgentObservationAutoCursor(parentPiboSessionId, cursorScope, sequence) {
|
|
177
|
+
return this.dataStore.transaction(() => {
|
|
178
|
+
this.db.prepare(`
|
|
179
|
+
INSERT INTO session_agent_observation_auto_cursors (
|
|
180
|
+
parent_pibo_session_id, cursor_scope, sequence, updated_at
|
|
181
|
+
)
|
|
182
|
+
SELECT id, ?, ?, ? FROM sessions WHERE id = ? AND deleted_at IS NULL
|
|
183
|
+
ON CONFLICT(parent_pibo_session_id, cursor_scope) DO UPDATE SET
|
|
184
|
+
sequence = MAX(session_agent_observation_auto_cursors.sequence, excluded.sequence),
|
|
185
|
+
updated_at = CASE
|
|
186
|
+
WHEN excluded.sequence > session_agent_observation_auto_cursors.sequence THEN excluded.updated_at
|
|
187
|
+
ELSE session_agent_observation_auto_cursors.updated_at
|
|
188
|
+
END
|
|
189
|
+
`).run(cursorScope, sequence, new Date().toISOString(), parentPiboSessionId);
|
|
190
|
+
this.db.prepare(`
|
|
191
|
+
DELETE FROM session_agent_observation_auto_cursors
|
|
192
|
+
WHERE parent_pibo_session_id = ?
|
|
193
|
+
AND cursor_scope <> ?
|
|
194
|
+
AND cursor_scope NOT IN (
|
|
195
|
+
SELECT cursor_scope
|
|
196
|
+
FROM session_agent_observation_auto_cursors
|
|
197
|
+
WHERE parent_pibo_session_id = ? AND cursor_scope <> ?
|
|
198
|
+
ORDER BY updated_at DESC, cursor_scope DESC
|
|
199
|
+
LIMIT ?
|
|
200
|
+
)
|
|
201
|
+
`).run(parentPiboSessionId, cursorScope, parentPiboSessionId, cursorScope, PIBO_AGENT_OBSERVATION_AUTO_CURSOR_MAX_SCOPES - 1);
|
|
202
|
+
return this.getAgentObservationAutoCursor(parentPiboSessionId, cursorScope) ?? sequence;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
168
205
|
claimOrAttachOutputPart(input) {
|
|
169
206
|
return this.dataStore.transaction(() => {
|
|
170
207
|
const indexAttribute = outputPartIndexAttribute(input.kind);
|
package/dist/sessions/store.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { createInitialRuntimeSessionBinding, createLegacyPiRuntimeSessionBinding, nextRuntimeSessionBinding, } from "./runtime-binding.js";
|
|
3
|
+
export const PIBO_AGENT_OBSERVATION_AUTO_CURSOR_MAX_SCOPES = 128;
|
|
3
4
|
export function createPiboSessionId() {
|
|
4
5
|
return `ps_${randomUUID()}`;
|
|
5
6
|
}
|
|
@@ -50,6 +51,7 @@ export class InMemoryPiboSessionStore {
|
|
|
50
51
|
byNativeSession = new Map();
|
|
51
52
|
outputRenderHighWater = new Map();
|
|
52
53
|
agentObservationNextSequence = new Map();
|
|
54
|
+
agentObservationAutoCursors = new Map();
|
|
53
55
|
outputToolInvocationNextOrdinal = new Map();
|
|
54
56
|
get(id) {
|
|
55
57
|
return this.byId.get(id);
|
|
@@ -128,6 +130,34 @@ export class InMemoryPiboSessionStore {
|
|
|
128
130
|
this.agentObservationNextSequence.set(parentPiboSessionId, sequence + 1);
|
|
129
131
|
return sequence;
|
|
130
132
|
}
|
|
133
|
+
getAgentObservationAutoCursor(parentPiboSessionId, cursorScope) {
|
|
134
|
+
if (!this.byId.has(parentPiboSessionId))
|
|
135
|
+
return undefined;
|
|
136
|
+
return this.agentObservationAutoCursors.get(agentObservationAutoCursorKey(parentPiboSessionId, cursorScope));
|
|
137
|
+
}
|
|
138
|
+
advanceAgentObservationAutoCursor(parentPiboSessionId, cursorScope, sequence) {
|
|
139
|
+
if (!this.byId.has(parentPiboSessionId))
|
|
140
|
+
return sequence;
|
|
141
|
+
const key = agentObservationAutoCursorKey(parentPiboSessionId, cursorScope);
|
|
142
|
+
const advanced = Math.max(this.agentObservationAutoCursors.get(key) ?? 0, sequence);
|
|
143
|
+
this.agentObservationAutoCursors.delete(key);
|
|
144
|
+
this.agentObservationAutoCursors.set(key, advanced);
|
|
145
|
+
const prefix = `${JSON.stringify([parentPiboSessionId]).slice(0, -1)},`;
|
|
146
|
+
let scopeCount = 0;
|
|
147
|
+
for (const existingKey of this.agentObservationAutoCursors.keys()) {
|
|
148
|
+
if (existingKey.startsWith(prefix))
|
|
149
|
+
scopeCount += 1;
|
|
150
|
+
}
|
|
151
|
+
for (const existingKey of this.agentObservationAutoCursors.keys()) {
|
|
152
|
+
if (scopeCount <= PIBO_AGENT_OBSERVATION_AUTO_CURSOR_MAX_SCOPES)
|
|
153
|
+
break;
|
|
154
|
+
if (!existingKey.startsWith(prefix))
|
|
155
|
+
continue;
|
|
156
|
+
this.agentObservationAutoCursors.delete(existingKey);
|
|
157
|
+
scopeCount -= 1;
|
|
158
|
+
}
|
|
159
|
+
return advanced;
|
|
160
|
+
}
|
|
131
161
|
claimOutputToolInvocationOrdinal(piboSessionId, eventId, toolCallId) {
|
|
132
162
|
const key = outputToolInvocationCounterKey(piboSessionId, eventId, toolCallId);
|
|
133
163
|
const ordinal = this.outputToolInvocationNextOrdinal.get(key) ?? 0;
|
|
@@ -172,6 +202,11 @@ export class InMemoryPiboSessionStore {
|
|
|
172
202
|
this.byNativeSession.delete(nativeKey);
|
|
173
203
|
this.outputRenderHighWater.delete(id);
|
|
174
204
|
this.agentObservationNextSequence.delete(id);
|
|
205
|
+
const observationCursorPrefix = `${JSON.stringify([id]).slice(0, -1)},`;
|
|
206
|
+
for (const key of this.agentObservationAutoCursors.keys()) {
|
|
207
|
+
if (key.startsWith(observationCursorPrefix))
|
|
208
|
+
this.agentObservationAutoCursors.delete(key);
|
|
209
|
+
}
|
|
175
210
|
const counterPrefix = `${JSON.stringify([id]).slice(0, -1)},`;
|
|
176
211
|
for (const key of this.outputToolInvocationNextOrdinal.keys()) {
|
|
177
212
|
if (key.startsWith(counterPrefix))
|
|
@@ -213,6 +248,9 @@ function outputRenderSequenceHighWater(metadata) {
|
|
|
213
248
|
const value = metadata?.outputRenderSequenceHighWater;
|
|
214
249
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
215
250
|
}
|
|
251
|
+
function agentObservationAutoCursorKey(parentPiboSessionId, cursorScope) {
|
|
252
|
+
return JSON.stringify([parentPiboSessionId, cursorScope]);
|
|
253
|
+
}
|
|
216
254
|
function outputToolInvocationCounterKey(piboSessionId, eventId, toolCallId) {
|
|
217
255
|
return JSON.stringify([piboSessionId, eventId, toolCallId]);
|
|
218
256
|
}
|