@pasko70/pibo 1.16.6 → 2.0.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/dist/agent-runtime/auth.js +28 -0
- package/dist/agent-runtime/capabilities.js +221 -0
- package/dist/agent-runtime/context-build.js +313 -0
- package/dist/agent-runtime/contract.js +45 -0
- package/dist/agent-runtime/errors.js +57 -0
- package/dist/agent-runtime/events.js +1 -0
- package/dist/agent-runtime/history.js +1 -0
- package/dist/agent-runtime/profile-validation.js +109 -0
- package/dist/agent-runtime/registry.js +608 -0
- package/dist/agent-runtime/resource-files.js +87 -0
- package/dist/agent-runtime/resource-service.js +635 -0
- package/dist/agent-runtime/resources.js +1 -0
- package/dist/agent-runtime/routed-session.js +931 -0
- package/dist/agent-runtime/testing/contract.js +51 -0
- package/dist/agent-runtime/testing/fake-adapter.js +192 -0
- package/dist/agent-runtime/types.js +3 -0
- package/dist/agent-runtimes/codex-native/adapter.js +905 -0
- package/dist/agent-runtimes/codex-native/auth.js +476 -0
- package/dist/agent-runtimes/codex-native/client.js +734 -0
- package/dist/agent-runtimes/codex-native/config.js +152 -0
- package/dist/agent-runtimes/codex-native/history.js +283 -0
- package/dist/agent-runtimes/codex-native/mcp-stdio-launcher.js +89 -0
- package/dist/agent-runtimes/codex-native/models.js +581 -0
- package/dist/agent-runtimes/codex-native/process.js +459 -0
- package/dist/agent-runtimes/codex-native/protocol-types.js +1 -0
- package/dist/agent-runtimes/codex-native/protocol-version.js +7 -0
- package/dist/agent-runtimes/codex-native/redaction.js +31 -0
- package/dist/agent-runtimes/codex-native/requests.js +378 -0
- package/dist/agent-runtimes/codex-native/resource-delivery.js +445 -0
- package/dist/agent-runtimes/codex-native/thread.js +388 -0
- package/dist/agent-runtimes/codex-native/turn.js +849 -0
- package/dist/agent-runtimes/pi/adapter.js +653 -0
- package/dist/agent-runtimes/pi/auth.js +455 -0
- package/dist/agent-runtimes/pi/history.js +640 -0
- package/dist/agent-runtimes/pi/model-catalog.js +87 -0
- package/dist/agent-runtimes/pi/routed-session.js +1464 -0
- package/dist/agent-runtimes/pi/runtime.js +534 -0
- package/dist/agent-runtimes/pi/tool-compiler.js +69 -0
- package/dist/apps/chat/agent-profiles.js +40 -23
- package/dist/apps/chat/agent-store.js +111 -64
- package/dist/apps/chat/chat-api-routes.js +1 -1
- package/dist/apps/chat/chat-request-normalizers.js +47 -0
- package/dist/apps/chat/data/chat-data-mappers.js +25 -9
- package/dist/apps/chat/data/history-query-service.js +108 -0
- package/dist/apps/chat/data/session-query-service.js +24 -3
- package/dist/apps/chat/data/timeline-query-service.js +3 -3
- package/dist/apps/chat/model-catalog.js +1 -55
- package/dist/apps/chat/provider-auth-actions.js +171 -23
- package/dist/apps/chat/stream.js +12 -0
- package/dist/apps/chat/trace-v2.js +1 -0
- package/dist/apps/chat/trace.js +126 -415
- package/dist/apps/chat/web-app.js +582 -157
- package/dist/apps/chat-ui/assets/{dist-BTLtT_Vf.js → dist-9v2ZXT6V.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CMk1aPug.js → dist-B1LVAytl.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CcwA_IWN.js → dist-C6LPDFXU.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DKOthbxr.js → dist-DVtXxfHc.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-ls2I6BIw.js → dist-DW4Qrfbd.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D1rAtRWs.js → dist-DmbsHdMC.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-C1JQRskW.js → dist-DyvwuSFO.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DfpaylQ-.js → dist-I26VB0QO.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D10bTFD5.js → dist-RtHbJRPV.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-5KpmmBBZ.js → dist-sZgJkWZb.js} +1 -1
- package/dist/apps/chat-ui/assets/index-BPbNvcZe.css +1 -0
- package/dist/apps/chat-ui/assets/index-CIOf2uL2.js +237 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-1ivRyjFs.js +41 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.0.0.vsix +0 -0
- package/dist/auth/login-actions.js +5 -251
- package/dist/cli-session/localSessionSource.js +8 -1
- package/dist/core/context-build.js +124 -20
- package/dist/core/default-profile.js +8 -0
- package/dist/core/profiles.js +30 -3
- package/dist/core/routed-session.js +5 -1446
- package/dist/core/runtime.js +5 -561
- package/dist/core/session-router.js +637 -83
- package/dist/data/cli.js +20 -3
- package/dist/data/schema.js +104 -1
- package/dist/data/session-store.js +24 -1
- package/dist/debug/events.js +25 -10
- package/dist/debug/failures.js +23 -3
- package/dist/debug/index.js +24 -6
- package/dist/debug/messages.js +87 -7
- package/dist/debug/persisted-payloads.js +31 -0
- package/dist/debug/pty.js +7 -6
- package/dist/debug/runtime-binding.js +29 -0
- package/dist/debug/session.js +137 -0
- package/dist/debug/summary.js +12 -0
- package/dist/debug/telemetry.js +28 -0
- package/dist/debug/tools.js +12 -2
- package/dist/debug/trace.js +154 -76
- package/dist/gateway/server.js +87 -2
- package/dist/gateway/tool.js +5 -5
- package/dist/index.js +21 -2
- package/dist/loops/tools.js +13 -12
- package/dist/mcp/agent-context.js +13 -4
- package/dist/mcp/client.js +10 -6
- package/dist/mcp/config.js +5 -2
- package/dist/mcp/runtime-session.js +166 -0
- package/dist/plugins/builtin.js +191 -53
- package/dist/plugins/codex-native.js +29 -0
- package/dist/plugins/registry.js +66 -2
- package/dist/runs/tools.js +26 -25
- package/dist/session-ui/terminalRows.js +2 -2
- package/dist/sessions/pibo-data-store.js +125 -16
- package/dist/sessions/runtime-binding.js +107 -0
- package/dist/sessions/sqlite-store.js +239 -18
- package/dist/sessions/store.js +86 -7
- package/dist/shared/trace-engine.js +18 -9
- package/dist/shared/trace-event-projection.js +29 -11
- package/dist/shared/{trace-transcript.js → trace-history.js} +193 -195
- package/dist/shared/trace-order.js +9 -4
- package/dist/shared/trace-page-merge.js +1 -1
- package/dist/signals/projector.js +20 -2
- package/dist/signals/registry.js +11 -0
- package/dist/subagents/tool.js +8 -6
- package/dist/tools/codex-browser.js +20 -19
- package/dist/tools/codex-compat.js +10 -9
- package/dist/tools/codex-image-generation.js +8 -6
- package/dist/tools/contract.js +45 -0
- package/dist/tools/credential-registry.js +182 -0
- package/dist/tools/mcp-bridge.js +511 -0
- package/dist/tools/payload-writer.js +17 -0
- package/dist/tools/runtime/tool.js +10 -9
- package/dist/tools/schema.js +9 -0
- package/dist/tools/session-service.js +193 -0
- package/dist/tools/session-tool-set.js +85 -0
- package/dist/web-annotations/tools.js +17 -16
- package/docs/README.md +11 -0
- package/package.json +3 -2
- package/skills/builtin/pibo-agent-runtime-adapter/SKILL.md +183 -0
- package/skills/builtin/pibo-agent-runtime-adapter/evals/README.md +10 -0
- package/skills/builtin/pibo-agent-runtime-adapter/evals/evals.json +45 -0
- package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/orion-full-harness.md +60 -0
- package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/relay-partial-harness.md +52 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/capabilities-and-designer.md +194 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/history-debug-and-security.md +213 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/interfaces-and-registration.md +214 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/lifecycle-bindings-and-events.md +193 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/portable-delivery-and-native-behavior.md +226 -0
- package/skills/builtin/pibo-agent-runtime-adapter/references/testing-migration-and-validation.md +256 -0
- package/dist/apps/chat-ui/assets/index-C2YI0xfw.js +0 -237
- package/dist/apps/chat-ui/assets/index-E0RmuCkY.css +0 -1
- package/dist/apps/chat-vscode-web/assets/index-D-tSsXLG.js +0 -41
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { closeSync, existsSync, openSync, readFileSync, readSync, readdirSync, statSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { parseSessionEntries, SessionManager } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
export const PI_HISTORY_TAIL_MAX_BYTES = 2 * 1024 * 1024;
|
|
7
|
+
export const PI_HISTORY_PAGE_MAX_BYTES = 512 * 1024;
|
|
8
|
+
export const PI_HISTORY_SCAN_MAX_BYTES = 16 * 1024 * 1024;
|
|
9
|
+
const PI_SESSION_LIST_CACHE_TTL_MS = 5_000;
|
|
10
|
+
const PI_SESSION_DIRECT_CACHE_TTL_MS = 5_000;
|
|
11
|
+
const PI_SESSION_FAST_HEAD_BYTES = 64 * 1024;
|
|
12
|
+
const PI_HISTORY_CURSOR_PREFIX = "pi-history:";
|
|
13
|
+
const piSessionListCache = new Map();
|
|
14
|
+
const piSessionDirectCache = new Map();
|
|
15
|
+
export async function inspectPiAgentRuntimeHistory(runtimeInstanceId, input) {
|
|
16
|
+
const nativeSessionId = input.binding.nativeSessionId;
|
|
17
|
+
if (!nativeSessionId) {
|
|
18
|
+
return {
|
|
19
|
+
runtimeInstanceId,
|
|
20
|
+
adapterId: "pi",
|
|
21
|
+
bindingState: input.binding.state,
|
|
22
|
+
available: false,
|
|
23
|
+
diagnostics: [{
|
|
24
|
+
severity: "error",
|
|
25
|
+
code: "pi_history_native_session_id_missing",
|
|
26
|
+
message: "The Pi runtime binding has no native session id for history lookup.",
|
|
27
|
+
}],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const item = await findPiSessionForHistory(nativeSessionId, input.workspace, input.binding.locator?.kind === "local-file" ? input.binding.locator.value : undefined, { fast: true });
|
|
31
|
+
if (!item) {
|
|
32
|
+
return {
|
|
33
|
+
runtimeInstanceId,
|
|
34
|
+
adapterId: "pi",
|
|
35
|
+
bindingState: input.binding.state,
|
|
36
|
+
available: false,
|
|
37
|
+
diagnostics: [{
|
|
38
|
+
severity: input.binding.state === "missing" ? "error" : "warning",
|
|
39
|
+
code: "pi_history_not_found",
|
|
40
|
+
message: `Pi history for native session "${nativeSessionId}" was not found in workspace "${input.workspace}".`,
|
|
41
|
+
}],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const metadata = metadataFromPiSession(item);
|
|
45
|
+
return {
|
|
46
|
+
runtimeInstanceId,
|
|
47
|
+
adapterId: "pi",
|
|
48
|
+
bindingState: input.binding.state,
|
|
49
|
+
available: true,
|
|
50
|
+
locator: { kind: "local-file", value: item.path },
|
|
51
|
+
title: metadata.name,
|
|
52
|
+
firstMessage: metadata.firstMessage,
|
|
53
|
+
createdAt: metadata.created,
|
|
54
|
+
updatedAt: metadata.modified,
|
|
55
|
+
entryCount: metadata.messageCount,
|
|
56
|
+
sizeBytes: metadata.sessionSize,
|
|
57
|
+
version: historyVersion(metadata),
|
|
58
|
+
diagnostics: [],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export async function readPiAgentRuntimeHistory(runtimeInstanceId, input) {
|
|
62
|
+
const inspection = await inspectPiAgentRuntimeHistory(runtimeInstanceId, input);
|
|
63
|
+
if (!inspection.available || inspection.locator?.kind !== "local-file" || !inspection.locator.value) {
|
|
64
|
+
return {
|
|
65
|
+
runtimeInstanceId,
|
|
66
|
+
adapterId: "pi",
|
|
67
|
+
source: "native",
|
|
68
|
+
entries: [],
|
|
69
|
+
hasMore: false,
|
|
70
|
+
inspection,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const decoded = decodePiHistoryCursor(input.cursor);
|
|
74
|
+
const page = readPiTranscriptHistoryPage(inspection.locator.value, {
|
|
75
|
+
beforeByte: decoded.beforeByte,
|
|
76
|
+
beforeTimestamp: input.beforeTimestamp ?? decoded.beforeTimestamp,
|
|
77
|
+
limit: input.limit,
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
runtimeInstanceId,
|
|
81
|
+
adapterId: "pi",
|
|
82
|
+
source: "native",
|
|
83
|
+
entries: piSessionEntriesToAgentRuntimeHistoryEntries(page.entries),
|
|
84
|
+
orderOffset: page.startByte,
|
|
85
|
+
nextCursor: page.hasOlder && page.nextBeforeByte !== undefined
|
|
86
|
+
? encodePiHistoryCursor({ beforeByte: page.nextBeforeByte, beforeTimestamp: input.beforeTimestamp ?? decoded.beforeTimestamp })
|
|
87
|
+
: undefined,
|
|
88
|
+
hasMore: page.hasOlder,
|
|
89
|
+
inspection,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export function piSessionEntriesToAgentRuntimeHistoryEntries(entries) {
|
|
93
|
+
const normalized = [];
|
|
94
|
+
let nativeTurnId;
|
|
95
|
+
for (let sequence = 0; sequence < entries.length; sequence += 1) {
|
|
96
|
+
const entry = entries[sequence];
|
|
97
|
+
if (entry.type === "session_info" && entry.name) {
|
|
98
|
+
normalized.push({
|
|
99
|
+
id: `pi:${entry.id}`,
|
|
100
|
+
type: "session_info",
|
|
101
|
+
source: "native",
|
|
102
|
+
createdAt: entry.timestamp,
|
|
103
|
+
sequence,
|
|
104
|
+
nativeEntryId: entry.id,
|
|
105
|
+
name: entry.name,
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (entry.type !== "message")
|
|
110
|
+
continue;
|
|
111
|
+
const role = piMessageRole(entry);
|
|
112
|
+
if (role === "user") {
|
|
113
|
+
nativeTurnId = entry.id;
|
|
114
|
+
normalized.push({
|
|
115
|
+
id: `pi:${entry.id}`,
|
|
116
|
+
type: "message",
|
|
117
|
+
source: "native",
|
|
118
|
+
createdAt: entry.timestamp,
|
|
119
|
+
sequence,
|
|
120
|
+
nativeTurnId,
|
|
121
|
+
nativeEntryId: entry.id,
|
|
122
|
+
role: "user",
|
|
123
|
+
content: normalizePiTextContent(piMessageContent(entry)),
|
|
124
|
+
});
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (role === "assistant") {
|
|
128
|
+
const message = entry.message;
|
|
129
|
+
normalized.push({
|
|
130
|
+
id: `pi:${entry.id}`,
|
|
131
|
+
type: "message",
|
|
132
|
+
source: "native",
|
|
133
|
+
createdAt: entry.timestamp,
|
|
134
|
+
sequence,
|
|
135
|
+
nativeTurnId,
|
|
136
|
+
nativeEntryId: entry.id,
|
|
137
|
+
role: "assistant",
|
|
138
|
+
content: normalizePiAssistantContent(piMessageContent(entry)),
|
|
139
|
+
status: piAssistantStatus(message),
|
|
140
|
+
error: typeof message.errorMessage === "string" ? message.errorMessage : undefined,
|
|
141
|
+
metadata: compactJsonObject({
|
|
142
|
+
stopReason: jsonValue(message.stopReason),
|
|
143
|
+
nativeStatus: jsonValue(message.status),
|
|
144
|
+
}),
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (role === "toolResult") {
|
|
149
|
+
const message = entry.message;
|
|
150
|
+
normalized.push({
|
|
151
|
+
id: `pi:${entry.id}`,
|
|
152
|
+
type: "message",
|
|
153
|
+
source: "native",
|
|
154
|
+
createdAt: entry.timestamp,
|
|
155
|
+
sequence,
|
|
156
|
+
nativeTurnId,
|
|
157
|
+
nativeEntryId: entry.id,
|
|
158
|
+
role: "tool",
|
|
159
|
+
content: normalizePiTextContent(message.content),
|
|
160
|
+
toolCallId: typeof message.toolCallId === "string" ? message.toolCallId : undefined,
|
|
161
|
+
toolName: typeof message.toolName === "string" ? message.toolName : undefined,
|
|
162
|
+
result: jsonValue({ content: message.content, ...(message.details === undefined ? {} : { details: message.details }) }) ?? null,
|
|
163
|
+
isError: message.isError === true,
|
|
164
|
+
status: message.isError === true ? "error" : "complete",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return normalized;
|
|
169
|
+
}
|
|
170
|
+
export async function loadPiHistoryMetadata(nativeSessionId, cwd = process.cwd()) {
|
|
171
|
+
const item = await findPiSessionForHistory(nativeSessionId, cwd);
|
|
172
|
+
return metadataFromPiSession(item);
|
|
173
|
+
}
|
|
174
|
+
export function loadPiHistoryFastMetadata(nativeSessionId, cwd = process.cwd()) {
|
|
175
|
+
const item = findPiSessionDirectFast(nativeSessionId, cwd)
|
|
176
|
+
?? findPiSessionDirect(nativeSessionId, cwd, { fast: true });
|
|
177
|
+
return metadataFromPiSession(item);
|
|
178
|
+
}
|
|
179
|
+
export async function listPiHistorySessions(cwd = process.cwd()) {
|
|
180
|
+
const now = Date.now();
|
|
181
|
+
const cached = piSessionListCache.get(cwd);
|
|
182
|
+
if (cached && cached.expiresAt > now)
|
|
183
|
+
return cached.promise;
|
|
184
|
+
const promise = SessionManager.list(cwd).then((sessions) => sessions.map((session) => ({
|
|
185
|
+
path: session.path,
|
|
186
|
+
id: session.id,
|
|
187
|
+
cwd: session.cwd,
|
|
188
|
+
name: session.name,
|
|
189
|
+
parentSessionPath: session.parentSessionPath,
|
|
190
|
+
created: session.created.toISOString(),
|
|
191
|
+
modified: session.modified.toISOString(),
|
|
192
|
+
messageCount: session.messageCount,
|
|
193
|
+
firstMessage: session.firstMessage,
|
|
194
|
+
})));
|
|
195
|
+
piSessionListCache.set(cwd, { expiresAt: now + PI_SESSION_LIST_CACHE_TTL_MS, promise });
|
|
196
|
+
promise.catch(() => {
|
|
197
|
+
if (piSessionListCache.get(cwd)?.promise === promise)
|
|
198
|
+
piSessionListCache.delete(cwd);
|
|
199
|
+
});
|
|
200
|
+
return promise;
|
|
201
|
+
}
|
|
202
|
+
export function readPiTranscriptEntries(path) {
|
|
203
|
+
if (!existsSync(path))
|
|
204
|
+
return [];
|
|
205
|
+
return parseSessionEntries(readFileSync(path, "utf8")).filter((entry) => entry.type !== "session");
|
|
206
|
+
}
|
|
207
|
+
export function readPiTranscriptTailEntries(path, maxBytes = PI_HISTORY_TAIL_MAX_BYTES) {
|
|
208
|
+
if (!existsSync(path))
|
|
209
|
+
return [];
|
|
210
|
+
const stats = statSync(path);
|
|
211
|
+
if (stats.size <= maxBytes)
|
|
212
|
+
return readPiTranscriptEntries(path);
|
|
213
|
+
const length = Math.max(1, Math.min(maxBytes, stats.size));
|
|
214
|
+
const start = stats.size - length;
|
|
215
|
+
const buffer = Buffer.alloc(length);
|
|
216
|
+
const fd = openSync(path, "r");
|
|
217
|
+
try {
|
|
218
|
+
const bytesRead = readSync(fd, buffer, 0, length, start);
|
|
219
|
+
let content = buffer.subarray(0, bytesRead).toString("utf8");
|
|
220
|
+
const firstNewline = content.indexOf("\n");
|
|
221
|
+
if (start > 0 && firstNewline >= 0)
|
|
222
|
+
content = content.slice(firstNewline + 1);
|
|
223
|
+
return parseSessionEntries(content).filter((entry) => entry.type !== "session");
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
closeSync(fd);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
export function readPiTranscriptHistoryPage(path, input = {}) {
|
|
230
|
+
if (!existsSync(path))
|
|
231
|
+
return { entries: [], hasOlder: false, scannedBytes: 0, startByte: 0, endByte: 0 };
|
|
232
|
+
const stats = statSync(path);
|
|
233
|
+
const fileSize = Math.max(0, stats.size);
|
|
234
|
+
const initialEnd = input.beforeByte === undefined
|
|
235
|
+
? fileSize
|
|
236
|
+
: Math.max(0, Math.min(input.beforeByte, fileSize));
|
|
237
|
+
const limit = Math.max(1, Math.min(input.limit ?? 50, 500));
|
|
238
|
+
const pageBytes = Math.max(1024, Math.min(input.pageBytes ?? PI_HISTORY_PAGE_MAX_BYTES, PI_HISTORY_SCAN_MAX_BYTES));
|
|
239
|
+
const maxScanBytes = Math.max(pageBytes, Math.min(input.maxScanBytes ?? PI_HISTORY_SCAN_MAX_BYTES, 32 * 1024 * 1024));
|
|
240
|
+
const beforeTime = input.beforeTimestamp ? Date.parse(input.beforeTimestamp) : undefined;
|
|
241
|
+
const entries = [];
|
|
242
|
+
let cursorEnd = initialEnd;
|
|
243
|
+
let scannedBytes = 0;
|
|
244
|
+
const fd = openSync(path, "r");
|
|
245
|
+
try {
|
|
246
|
+
while (cursorEnd > 0 && entries.length < limit && scannedBytes < maxScanBytes) {
|
|
247
|
+
const record = readPreviousTranscriptLine(fd, cursorEnd, {
|
|
248
|
+
blockBytes: pageBytes,
|
|
249
|
+
maxBytes: maxScanBytes - scannedBytes,
|
|
250
|
+
});
|
|
251
|
+
if (!record) {
|
|
252
|
+
cursorEnd = 0;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
cursorEnd = record.startByte;
|
|
256
|
+
scannedBytes += record.scannedBytes;
|
|
257
|
+
if (record.truncated)
|
|
258
|
+
break;
|
|
259
|
+
const parsed = parseTranscriptLine(record.text);
|
|
260
|
+
for (let entryIndex = parsed.length - 1; entryIndex >= 0; entryIndex -= 1) {
|
|
261
|
+
const entry = parsed[entryIndex];
|
|
262
|
+
if (beforeTime !== undefined && entryTimestampMs(entry) >= beforeTime)
|
|
263
|
+
continue;
|
|
264
|
+
entries.push({ entry, startByte: record.startByte });
|
|
265
|
+
if (entries.length >= limit)
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
closeSync(fd);
|
|
272
|
+
}
|
|
273
|
+
const nextBeforeByte = cursorEnd > 0 ? cursorEnd : undefined;
|
|
274
|
+
return {
|
|
275
|
+
entries: entries.reverse().map((item) => item.entry),
|
|
276
|
+
nextBeforeByte,
|
|
277
|
+
hasOlder: nextBeforeByte !== undefined,
|
|
278
|
+
scannedBytes,
|
|
279
|
+
startByte: nextBeforeByte ?? 0,
|
|
280
|
+
endByte: initialEnd,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function normalizePiAssistantContent(content) {
|
|
284
|
+
if (typeof content === "string")
|
|
285
|
+
return [{ type: "text", text: content }];
|
|
286
|
+
if (!Array.isArray(content))
|
|
287
|
+
return [];
|
|
288
|
+
const parts = [];
|
|
289
|
+
for (const value of content) {
|
|
290
|
+
if (!value || typeof value !== "object")
|
|
291
|
+
continue;
|
|
292
|
+
const part = value;
|
|
293
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
294
|
+
parts.push({ type: "text", text: part.text });
|
|
295
|
+
}
|
|
296
|
+
else if (part.type === "thinking" && typeof part.thinking === "string") {
|
|
297
|
+
parts.push({ type: "reasoning", text: part.thinking });
|
|
298
|
+
}
|
|
299
|
+
else if (part.type === "toolCall" && typeof part.id === "string" && typeof part.name === "string") {
|
|
300
|
+
parts.push({
|
|
301
|
+
type: "tool_call",
|
|
302
|
+
toolCallId: part.id,
|
|
303
|
+
toolName: part.name,
|
|
304
|
+
input: jsonValue(part.arguments),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return parts;
|
|
309
|
+
}
|
|
310
|
+
function normalizePiTextContent(content) {
|
|
311
|
+
if (typeof content === "string")
|
|
312
|
+
return content;
|
|
313
|
+
if (!Array.isArray(content))
|
|
314
|
+
return stringifyUnknown(content);
|
|
315
|
+
const parts = [];
|
|
316
|
+
for (const value of content) {
|
|
317
|
+
if (!value || typeof value !== "object")
|
|
318
|
+
continue;
|
|
319
|
+
const part = value;
|
|
320
|
+
if (part.type === "text" && typeof part.text === "string")
|
|
321
|
+
parts.push({ type: "text", text: part.text });
|
|
322
|
+
}
|
|
323
|
+
return parts;
|
|
324
|
+
}
|
|
325
|
+
function piAssistantStatus(message) {
|
|
326
|
+
if (message.stopReason === "error" || typeof message.errorMessage === "string")
|
|
327
|
+
return "error";
|
|
328
|
+
if (message.status === "streaming" || message.status === "in_progress")
|
|
329
|
+
return "running";
|
|
330
|
+
return "complete";
|
|
331
|
+
}
|
|
332
|
+
function piMessageRole(entry) {
|
|
333
|
+
return entry.message.role;
|
|
334
|
+
}
|
|
335
|
+
function piMessageContent(entry) {
|
|
336
|
+
return entry.message.content;
|
|
337
|
+
}
|
|
338
|
+
function stringifyUnknown(value) {
|
|
339
|
+
if (value === undefined || value === null)
|
|
340
|
+
return "";
|
|
341
|
+
if (typeof value === "string")
|
|
342
|
+
return value;
|
|
343
|
+
try {
|
|
344
|
+
return JSON.stringify(value);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return String(value);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function compactJsonObject(value) {
|
|
351
|
+
const compact = Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
352
|
+
return Object.keys(compact).length ? compact : undefined;
|
|
353
|
+
}
|
|
354
|
+
function jsonValue(value) {
|
|
355
|
+
if (value === undefined)
|
|
356
|
+
return undefined;
|
|
357
|
+
try {
|
|
358
|
+
return JSON.parse(JSON.stringify(value));
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
return String(value);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async function findPiSessionForHistory(nativeSessionId, cwd, locatorPath, options = {}) {
|
|
365
|
+
if (locatorPath) {
|
|
366
|
+
const located = findPiSessionAtPath(locatorPath, nativeSessionId, cwd, options);
|
|
367
|
+
if (located)
|
|
368
|
+
return located;
|
|
369
|
+
}
|
|
370
|
+
return findPiSessionDirect(nativeSessionId, cwd, options)
|
|
371
|
+
?? (await listPiHistorySessions(cwd)).find((session) => session.id === nativeSessionId);
|
|
372
|
+
}
|
|
373
|
+
function metadataFromPiSession(item) {
|
|
374
|
+
if (!item)
|
|
375
|
+
return {};
|
|
376
|
+
let sessionSize;
|
|
377
|
+
let sessionMtimeMs;
|
|
378
|
+
try {
|
|
379
|
+
const stats = statSync(item.path);
|
|
380
|
+
sessionSize = stats.size;
|
|
381
|
+
sessionMtimeMs = stats.mtimeMs;
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
// The session can be deleted after discovery and before inspection.
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
sessionPath: item.path,
|
|
388
|
+
sessionSize,
|
|
389
|
+
sessionMtimeMs,
|
|
390
|
+
name: item.name,
|
|
391
|
+
firstMessage: item.firstMessage,
|
|
392
|
+
created: item.created,
|
|
393
|
+
modified: item.modified,
|
|
394
|
+
messageCount: item.messageCount,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function historyVersion(metadata) {
|
|
398
|
+
if (!metadata.sessionPath)
|
|
399
|
+
return undefined;
|
|
400
|
+
return createHash("sha1").update(JSON.stringify({
|
|
401
|
+
path: metadata.sessionPath,
|
|
402
|
+
size: metadata.sessionSize ?? null,
|
|
403
|
+
mtime: metadata.sessionMtimeMs ?? null,
|
|
404
|
+
})).digest("hex");
|
|
405
|
+
}
|
|
406
|
+
function findPiSessionDirectFast(nativeSessionId, cwd) {
|
|
407
|
+
const cacheKey = `${cwd}\0${nativeSessionId}`;
|
|
408
|
+
const cached = piSessionDirectCache.get(cacheKey);
|
|
409
|
+
if (!cached || cached.expiresAt <= Date.now())
|
|
410
|
+
return undefined;
|
|
411
|
+
try {
|
|
412
|
+
const stats = statSync(cached.path);
|
|
413
|
+
if (stats.size === cached.size && stats.mtimeMs === cached.mtimeMs)
|
|
414
|
+
return cached.item;
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
piSessionDirectCache.delete(cacheKey);
|
|
418
|
+
}
|
|
419
|
+
return undefined;
|
|
420
|
+
}
|
|
421
|
+
function setPiSessionDirectFastCache(nativeSessionId, cwd, item, stats) {
|
|
422
|
+
piSessionDirectCache.set(`${cwd}\0${nativeSessionId}`, {
|
|
423
|
+
expiresAt: Date.now() + PI_SESSION_DIRECT_CACHE_TTL_MS,
|
|
424
|
+
path: item.path,
|
|
425
|
+
size: stats.size,
|
|
426
|
+
mtimeMs: stats.mtimeMs,
|
|
427
|
+
item,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
function findPiSessionDirect(nativeSessionId, cwd, options = {}) {
|
|
431
|
+
const sessionDir = defaultPiSessionDir(cwd);
|
|
432
|
+
if (!existsSync(sessionDir))
|
|
433
|
+
return undefined;
|
|
434
|
+
try {
|
|
435
|
+
const file = readdirSync(sessionDir).find((candidate) => candidate.endsWith(`_${nativeSessionId}.jsonl`));
|
|
436
|
+
return file ? findPiSessionAtPath(join(sessionDir, file), nativeSessionId, cwd, options) : undefined;
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
return undefined;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function findPiSessionAtPath(sessionPath, nativeSessionId, cwd, options = {}) {
|
|
443
|
+
if (!existsSync(sessionPath))
|
|
444
|
+
return undefined;
|
|
445
|
+
try {
|
|
446
|
+
const stats = statSync(sessionPath);
|
|
447
|
+
const entries = options.fast
|
|
448
|
+
? readHeadEntries(sessionPath, stats.size, PI_SESSION_FAST_HEAD_BYTES)
|
|
449
|
+
: parseSessionEntries(readFileSync(sessionPath, "utf8"));
|
|
450
|
+
const header = entries.find((entry) => entry.type === "session");
|
|
451
|
+
if (header?.id !== nativeSessionId)
|
|
452
|
+
return undefined;
|
|
453
|
+
let name;
|
|
454
|
+
let firstMessage = "";
|
|
455
|
+
let messageCount = 0;
|
|
456
|
+
for (const entry of entries) {
|
|
457
|
+
if (entry.type === "session_info")
|
|
458
|
+
name = stringValue(entry.name)?.trim() || undefined;
|
|
459
|
+
if (entry.type !== "message")
|
|
460
|
+
continue;
|
|
461
|
+
messageCount += 1;
|
|
462
|
+
if (firstMessage || piMessageRole(entry) !== "user")
|
|
463
|
+
continue;
|
|
464
|
+
firstMessage = extractPiMessageText(piMessageContent(entry));
|
|
465
|
+
}
|
|
466
|
+
const item = {
|
|
467
|
+
path: sessionPath,
|
|
468
|
+
id: nativeSessionId,
|
|
469
|
+
cwd: stringValue(header.cwd) ?? cwd,
|
|
470
|
+
name,
|
|
471
|
+
parentSessionPath: stringValue(header.parentSession),
|
|
472
|
+
created: stringValue(header.timestamp) ?? stats.birthtime.toISOString(),
|
|
473
|
+
modified: options.fast
|
|
474
|
+
? stats.mtime.toISOString()
|
|
475
|
+
: sessionModifiedIso(entries.filter((entry) => entry.type !== "session"), stringValue(header.timestamp), stats.mtime),
|
|
476
|
+
messageCount,
|
|
477
|
+
firstMessage: firstMessage || "(no messages)",
|
|
478
|
+
};
|
|
479
|
+
if (options.fast)
|
|
480
|
+
setPiSessionDirectFastCache(nativeSessionId, cwd, item, stats);
|
|
481
|
+
return item;
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function defaultPiSessionDir(cwd) {
|
|
488
|
+
const agentDir = process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
|
|
489
|
+
const safePath = `--${cwd.replace(/^[\\/]/, "").replace(/[\\/:]/g, "-")}--`;
|
|
490
|
+
return join(agentDir, "sessions", safePath);
|
|
491
|
+
}
|
|
492
|
+
function sessionModifiedIso(entries, headerTimestamp, statsMtime) {
|
|
493
|
+
let lastActivityTime;
|
|
494
|
+
for (const entry of entries) {
|
|
495
|
+
if (entry.type !== "message")
|
|
496
|
+
continue;
|
|
497
|
+
const timestamp = entry.message.timestamp;
|
|
498
|
+
if (typeof timestamp === "number") {
|
|
499
|
+
lastActivityTime = Math.max(lastActivityTime ?? 0, timestamp);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
const entryTime = new Date(entry.timestamp).getTime();
|
|
503
|
+
if (!Number.isNaN(entryTime))
|
|
504
|
+
lastActivityTime = Math.max(lastActivityTime ?? 0, entryTime);
|
|
505
|
+
}
|
|
506
|
+
if (lastActivityTime)
|
|
507
|
+
return new Date(lastActivityTime).toISOString();
|
|
508
|
+
const headerTime = headerTimestamp ? new Date(headerTimestamp).getTime() : Number.NaN;
|
|
509
|
+
return !Number.isNaN(headerTime) ? new Date(headerTime).toISOString() : statsMtime.toISOString();
|
|
510
|
+
}
|
|
511
|
+
function readHeadEntries(path, fileSize, maxBytes) {
|
|
512
|
+
const length = Math.max(1, Math.min(maxBytes, fileSize));
|
|
513
|
+
const buffer = Buffer.alloc(length);
|
|
514
|
+
const fd = openSync(path, "r");
|
|
515
|
+
try {
|
|
516
|
+
const bytesRead = readSync(fd, buffer, 0, length, 0);
|
|
517
|
+
let content = buffer.subarray(0, bytesRead).toString("utf8");
|
|
518
|
+
if (bytesRead < fileSize && !content.endsWith("\n")) {
|
|
519
|
+
const lastNewline = content.lastIndexOf("\n");
|
|
520
|
+
if (lastNewline >= 0)
|
|
521
|
+
content = content.slice(0, lastNewline + 1);
|
|
522
|
+
}
|
|
523
|
+
if (!content.trim())
|
|
524
|
+
return [];
|
|
525
|
+
return parseSessionEntries(content.endsWith("\n") ? content : `${content}\n`);
|
|
526
|
+
}
|
|
527
|
+
finally {
|
|
528
|
+
closeSync(fd);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function readPreviousTranscriptLine(fd, cursorEnd, input) {
|
|
532
|
+
let lineEnd = cursorEnd;
|
|
533
|
+
const byte = Buffer.allocUnsafe(1);
|
|
534
|
+
while (lineEnd > 0) {
|
|
535
|
+
readSync(fd, byte, 0, 1, lineEnd - 1);
|
|
536
|
+
if (byte[0] !== 0x0a && byte[0] !== 0x0d)
|
|
537
|
+
break;
|
|
538
|
+
lineEnd -= 1;
|
|
539
|
+
}
|
|
540
|
+
if (lineEnd === 0)
|
|
541
|
+
return undefined;
|
|
542
|
+
const maxBytes = Math.max(1, input.maxBytes);
|
|
543
|
+
const floor = Math.max(0, lineEnd - maxBytes);
|
|
544
|
+
let scanEnd = lineEnd;
|
|
545
|
+
let lineStart = floor;
|
|
546
|
+
let foundBoundary = floor === 0;
|
|
547
|
+
while (scanEnd > floor) {
|
|
548
|
+
const scanStart = Math.max(floor, scanEnd - input.blockBytes);
|
|
549
|
+
const buffer = Buffer.allocUnsafe(scanEnd - scanStart);
|
|
550
|
+
const bytesRead = readSync(fd, buffer, 0, buffer.length, scanStart);
|
|
551
|
+
const newlineIndex = buffer.subarray(0, bytesRead).lastIndexOf(0x0a);
|
|
552
|
+
if (newlineIndex >= 0) {
|
|
553
|
+
lineStart = scanStart + newlineIndex + 1;
|
|
554
|
+
foundBoundary = true;
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
scanEnd = scanStart;
|
|
558
|
+
}
|
|
559
|
+
if (!foundBoundary) {
|
|
560
|
+
return {
|
|
561
|
+
text: "",
|
|
562
|
+
startByte: floor,
|
|
563
|
+
scannedBytes: cursorEnd - floor,
|
|
564
|
+
truncated: true,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const lineBuffer = Buffer.allocUnsafe(lineEnd - lineStart);
|
|
568
|
+
const bytesRead = readSync(fd, lineBuffer, 0, lineBuffer.length, lineStart);
|
|
569
|
+
return {
|
|
570
|
+
text: lineBuffer.subarray(0, bytesRead).toString("utf8"),
|
|
571
|
+
startByte: lineStart,
|
|
572
|
+
scannedBytes: cursorEnd - lineStart,
|
|
573
|
+
truncated: false,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function parseTranscriptLine(line) {
|
|
577
|
+
try {
|
|
578
|
+
return parseSessionEntries(`${line}\n`).filter((entry) => entry.type !== "session");
|
|
579
|
+
}
|
|
580
|
+
catch {
|
|
581
|
+
return [];
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
function entryTimestampMs(entry) {
|
|
585
|
+
const timestamp = entry.timestamp;
|
|
586
|
+
if (typeof timestamp !== "string")
|
|
587
|
+
return Number.POSITIVE_INFINITY;
|
|
588
|
+
const parsed = Date.parse(timestamp);
|
|
589
|
+
return Number.isFinite(parsed) ? parsed : Number.POSITIVE_INFINITY;
|
|
590
|
+
}
|
|
591
|
+
function encodePiHistoryCursor(cursor) {
|
|
592
|
+
return `${PI_HISTORY_CURSOR_PREFIX}${Buffer.from(JSON.stringify({ v: 1, ...cursor }), "utf8").toString("base64url")}`;
|
|
593
|
+
}
|
|
594
|
+
function decodePiHistoryCursor(cursor) {
|
|
595
|
+
if (!cursor)
|
|
596
|
+
return {};
|
|
597
|
+
if (cursor.startsWith("transcript:")) {
|
|
598
|
+
const [, byteValue, encodedTimestamp] = cursor.split(":");
|
|
599
|
+
const beforeByte = Number.parseInt(byteValue ?? "", 10);
|
|
600
|
+
if (!Number.isFinite(beforeByte) || beforeByte < 0)
|
|
601
|
+
throw new Error("Invalid legacy Pi transcript history cursor.");
|
|
602
|
+
return {
|
|
603
|
+
beforeByte,
|
|
604
|
+
beforeTimestamp: encodedTimestamp ? Buffer.from(encodedTimestamp, "base64url").toString("utf8") || undefined : undefined,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
if (!cursor.startsWith(PI_HISTORY_CURSOR_PREFIX))
|
|
608
|
+
throw new Error("Invalid Pi history cursor.");
|
|
609
|
+
try {
|
|
610
|
+
const value = JSON.parse(Buffer.from(cursor.slice(PI_HISTORY_CURSOR_PREFIX.length), "base64url").toString("utf8"));
|
|
611
|
+
if (value.v !== 1)
|
|
612
|
+
throw new Error("unsupported version");
|
|
613
|
+
if (value.beforeByte !== undefined && (typeof value.beforeByte !== "number" || !Number.isInteger(value.beforeByte) || value.beforeByte < 0))
|
|
614
|
+
throw new Error("invalid byte offset");
|
|
615
|
+
if (value.beforeTimestamp !== undefined && typeof value.beforeTimestamp !== "string")
|
|
616
|
+
throw new Error("invalid timestamp");
|
|
617
|
+
return {
|
|
618
|
+
beforeByte: value.beforeByte,
|
|
619
|
+
beforeTimestamp: value.beforeTimestamp,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
throw new Error(`Invalid Pi history cursor: ${error instanceof Error ? error.message : String(error)}`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
function extractPiMessageText(content) {
|
|
627
|
+
if (typeof content === "string")
|
|
628
|
+
return content;
|
|
629
|
+
if (!Array.isArray(content))
|
|
630
|
+
return "";
|
|
631
|
+
return content.map((part) => {
|
|
632
|
+
if (!part || typeof part !== "object")
|
|
633
|
+
return "";
|
|
634
|
+
const typed = part;
|
|
635
|
+
return typed.type === "text" && typeof typed.text === "string" ? typed.text : "";
|
|
636
|
+
}).join("");
|
|
637
|
+
}
|
|
638
|
+
function stringValue(value) {
|
|
639
|
+
return typeof value === "string" ? value : undefined;
|
|
640
|
+
}
|