@pasko70/pibo 1.10.1 → 1.11.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/apps/chat-ui/assets/{dist-4NlLjLs7.js → dist-0E9FVJ6k.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BpfBSMzK.js → dist-BvqC0hRM.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-hU-2oAb6.js → dist-C2BRHNXr.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DnXWNQRm.js → dist-C57jNmjf.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BPl-fzRB.js → dist-CBL8UXjd.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-uF6UXzI7.js → dist-CI-LPHjw.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-3Sts0Afa.js → dist-CMRFkfl5.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Cvx5M97R.js → dist-CpZPhD2y.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BA6mhAec.js → dist-CuCZ-3_K.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BPnn9e2B.js → dist-CzC5kPWJ.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-r31x5Fcc.js → dist-D9hLnn1T.js} +1 -1
- package/dist/apps/chat-ui/assets/{index-CaTGYBOS.js → index-BEvjPUor.js} +21 -21
- package/dist/apps/chat-ui/assets/{index-al8DeEjA.css → index-DNeE4HrG.css} +1 -1
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/{index-CK4SMZuu.js → index-BLZRi4Ka.js} +3 -3
- package/dist/apps/chat-vscode-web/assets/index-Bf2JvJ9z.css +2 -0
- package/dist/apps/chat-vscode-web/index.html +2 -2
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.11.0.vsix +0 -0
- package/dist/auth/better-auth.js +49 -1
- package/dist/auth/cli.js +220 -0
- package/dist/auth/machine-keys.js +258 -0
- package/dist/auth/machine-session.js +123 -0
- package/dist/bin/pibo.js +0 -0
- package/dist/bin/rg.js +0 -0
- package/dist/cli.js +6 -0
- package/dist/compute/cli.js +7 -0
- package/dist/compute/resource-health.js +27 -3
- package/dist/config/config.js +5 -0
- package/dist/core/session-router.js +17 -0
- package/dist/data/schema.js +4 -0
- package/dist/debug/trace-status.js +25 -0
- package/dist/debug/trace.js +9 -10
- package/dist/resources/cli.js +15 -0
- package/dist/resources/lifecycle.js +28 -4
- package/dist/resources/reaper.js +1 -0
- package/dist/signals/projector.js +10 -1
- package/package.json +1 -1
- package/dist/apps/chat-vscode-web/assets/index-B5QK07zO.css +0 -2
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
+
export const PIBO_MACHINE_SESSION_COOKIE = "pibo_machine_session";
|
|
3
|
+
export const DEFAULT_MACHINE_SESSION_TTL_SECONDS = 8 * 60 * 60;
|
|
4
|
+
const MACHINE_SESSION_VERSION = 1;
|
|
5
|
+
const MAX_COOKIE_VALUE_LENGTH = 1024;
|
|
6
|
+
function signPayload(secret, encodedPayload) {
|
|
7
|
+
return createHmac("sha256", secret).update(encodedPayload, "utf8").digest();
|
|
8
|
+
}
|
|
9
|
+
function encodePayload(secret, payload) {
|
|
10
|
+
const encodedPayload = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
11
|
+
const signature = signPayload(secret, encodedPayload).toString("base64url");
|
|
12
|
+
return `${encodedPayload}.${signature}`;
|
|
13
|
+
}
|
|
14
|
+
function decodePayload(secret, value) {
|
|
15
|
+
if (value.length === 0 || value.length > MAX_COOKIE_VALUE_LENGTH)
|
|
16
|
+
return undefined;
|
|
17
|
+
const parts = value.split(".");
|
|
18
|
+
if (parts.length !== 2 || !parts[0] || !parts[1])
|
|
19
|
+
return undefined;
|
|
20
|
+
let providedSignature;
|
|
21
|
+
try {
|
|
22
|
+
providedSignature = Buffer.from(parts[1], "base64url");
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const expectedSignature = signPayload(secret, parts[0]);
|
|
28
|
+
if (providedSignature.length !== expectedSignature.length ||
|
|
29
|
+
!timingSafeEqual(providedSignature, expectedSignature)) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8"));
|
|
34
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
35
|
+
return undefined;
|
|
36
|
+
const payload = parsed;
|
|
37
|
+
if (payload.v !== MACHINE_SESSION_VERSION ||
|
|
38
|
+
typeof payload.keyId !== "string" ||
|
|
39
|
+
!/^[a-f0-9]{16}$/.test(payload.keyId) ||
|
|
40
|
+
typeof payload.expiresAt !== "number" ||
|
|
41
|
+
!Number.isSafeInteger(payload.expiresAt)) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
return { v: MACHINE_SESSION_VERSION, keyId: payload.keyId, expiresAt: payload.expiresAt };
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function cookieValue(headers) {
|
|
51
|
+
const cookieHeader = headers.get("cookie");
|
|
52
|
+
if (!cookieHeader)
|
|
53
|
+
return undefined;
|
|
54
|
+
for (const part of cookieHeader.split(";")) {
|
|
55
|
+
const separator = part.indexOf("=");
|
|
56
|
+
if (separator < 0)
|
|
57
|
+
continue;
|
|
58
|
+
const name = part.slice(0, separator).trim();
|
|
59
|
+
if (name !== PIBO_MACHINE_SESSION_COOKIE)
|
|
60
|
+
continue;
|
|
61
|
+
return part.slice(separator + 1).trim();
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
function sessionCookieHeader(value, expiresAt, maxAgeSeconds) {
|
|
66
|
+
return [
|
|
67
|
+
`${PIBO_MACHINE_SESSION_COOKIE}=${value}`,
|
|
68
|
+
"Path=/",
|
|
69
|
+
"HttpOnly",
|
|
70
|
+
"Secure",
|
|
71
|
+
"SameSite=Strict",
|
|
72
|
+
`Max-Age=${maxAgeSeconds}`,
|
|
73
|
+
`Expires=${expiresAt.toUTCString()}`,
|
|
74
|
+
].join("; ");
|
|
75
|
+
}
|
|
76
|
+
export function createMachineSessionManager(options) {
|
|
77
|
+
const ttlSeconds = options.ttlSeconds ?? DEFAULT_MACHINE_SESSION_TTL_SECONDS;
|
|
78
|
+
if (!Number.isInteger(ttlSeconds) || ttlSeconds < 1) {
|
|
79
|
+
throw new Error("Machine session TTL must be a positive integer");
|
|
80
|
+
}
|
|
81
|
+
const now = options.now ?? (() => new Date());
|
|
82
|
+
return {
|
|
83
|
+
create(authentication) {
|
|
84
|
+
const createdAt = now();
|
|
85
|
+
const ttlExpiry = createdAt.getTime() + ttlSeconds * 1000;
|
|
86
|
+
const keyExpiry = authentication.session.expiresAt?.getTime();
|
|
87
|
+
const expiryMs = keyExpiry === undefined ? ttlExpiry : Math.min(ttlExpiry, keyExpiry);
|
|
88
|
+
if (expiryMs <= createdAt.getTime())
|
|
89
|
+
throw new Error("Machine key is expired");
|
|
90
|
+
const expiresAt = new Date(expiryMs);
|
|
91
|
+
const value = encodePayload(options.secret, {
|
|
92
|
+
v: MACHINE_SESSION_VERSION,
|
|
93
|
+
keyId: authentication.id,
|
|
94
|
+
expiresAt: expiryMs,
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
header: sessionCookieHeader(value, expiresAt, Math.max(1, Math.floor((expiryMs - createdAt.getTime()) / 1000))),
|
|
98
|
+
expiresAt,
|
|
99
|
+
session: { ...authentication.session, expiresAt },
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
getSession(headers) {
|
|
103
|
+
const value = cookieValue(headers);
|
|
104
|
+
if (!value)
|
|
105
|
+
return undefined;
|
|
106
|
+
const payload = decodePayload(options.secret, value);
|
|
107
|
+
const currentTime = now().getTime();
|
|
108
|
+
if (!payload || payload.expiresAt <= currentTime)
|
|
109
|
+
return undefined;
|
|
110
|
+
const keySession = options.machineKeys.getSessionById(payload.keyId);
|
|
111
|
+
if (!keySession)
|
|
112
|
+
return undefined;
|
|
113
|
+
const keyExpiry = keySession.expiresAt?.getTime();
|
|
114
|
+
const effectiveExpiry = keyExpiry === undefined ? payload.expiresAt : Math.min(payload.expiresAt, keyExpiry);
|
|
115
|
+
if (effectiveExpiry <= currentTime)
|
|
116
|
+
return undefined;
|
|
117
|
+
return { ...keySession, expiresAt: new Date(effectiveExpiry) };
|
|
118
|
+
},
|
|
119
|
+
clearHeader() {
|
|
120
|
+
return sessionCookieHeader("", new Date(0), 0);
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
package/dist/bin/pibo.js
CHANGED
|
File without changes
|
package/dist/bin/rg.js
CHANGED
|
File without changes
|
package/dist/cli.js
CHANGED
|
@@ -80,6 +80,11 @@ export async function runPiboCli(argv = process.argv) {
|
|
|
80
80
|
printPiboVersion();
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
|
+
if (argv[2] === "auth") {
|
|
84
|
+
const { runAuthCli } = await import("./auth/cli.js");
|
|
85
|
+
await runAuthCli([argv[0] ?? "node", "pibo auth", ...argv.slice(3)]);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
83
88
|
if (argv[2] === "mcp") {
|
|
84
89
|
const { runMcpCli } = await import("./mcp/index.js");
|
|
85
90
|
await runMcpCli([argv[0] ?? "node", "pibo mcp", ...argv.slice(3)]);
|
|
@@ -465,6 +470,7 @@ function printRootDiscoveryText() {
|
|
|
465
470
|
|
|
466
471
|
Commands:
|
|
467
472
|
config Manage local pibo config
|
|
473
|
+
auth Manage Web authentication and machine identities
|
|
468
474
|
mcp Discover and call configured MCP servers
|
|
469
475
|
tools Install and inspect curated external CLI tools
|
|
470
476
|
pi-packages Register Pi Coding Agent packages
|
package/dist/compute/cli.js
CHANGED
|
@@ -123,6 +123,13 @@ export function renderComputeResourceHealthText(health) {
|
|
|
123
123
|
const lines = [`Compute resource health: ${health.severity} (read-only)`];
|
|
124
124
|
lines.push(`Generated at: ${health.generatedAt}`);
|
|
125
125
|
lines.push(`Browser processes: ${health.browserProcesses.totalChromiumMainProcesses} main / ${health.browserProcesses.totalChromiumProcesses} total Chromium processes`);
|
|
126
|
+
if (health.browserProcesses.exemptMainProcessDetails.length > 0) {
|
|
127
|
+
lines.push(`Explicitly exempt browser main processes: ${health.browserProcesses.exemptMainProcessDetails.length}`);
|
|
128
|
+
lines.push("PID\tUSER_DATA_DIR\tCOMMAND");
|
|
129
|
+
for (const process of health.browserProcesses.exemptMainProcessDetails.slice(0, 5)) {
|
|
130
|
+
lines.push(`${process.pid}\t${process.userDataDir ?? "-"}\t${process.commandName}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
126
133
|
if (health.browserProcesses.unassignedMainProcessDetails.length > 0) {
|
|
127
134
|
lines.push(`Unmanaged browser main processes: ${health.browserProcesses.unassignedMainProcessDetails.length}`);
|
|
128
135
|
lines.push("PID\tWORKER\tUSER_DATA_DIR\tCOMMAND");
|
|
@@ -2,7 +2,7 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { readdir, readFile } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
import { basename, join } from "node:path";
|
|
5
|
+
import { basename, isAbsolute, join, resolve } from "node:path";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { readResourceReaperTimerStatus } from "../resources/reaper-state.js";
|
|
8
8
|
import { createEmptyBrowserPoolState, normalizeBrowserPoolState } from "../tools/browser-pool.js";
|
|
@@ -43,6 +43,7 @@ export function buildComputeResourceHealth(options = {}) {
|
|
|
43
43
|
const disk = options.disk;
|
|
44
44
|
const checks = [];
|
|
45
45
|
const mainProcesses = processes.filter((process) => process.isChromium && process.isMainProcess);
|
|
46
|
+
const exemptBrowserUserDataDirs = new Set(normalizeBrowserUserDataDirs(options.exemptBrowserUserDataDirs ?? readExemptBrowserUserDataDirs()));
|
|
46
47
|
const perWorker = browserPools.map(({ state, statePath }) => {
|
|
47
48
|
const activeLeaseCount = state.activeLeaseId ? Math.max(1, state.activeLeaseCount ?? 1) : state.activeLeaseCount ?? 0;
|
|
48
49
|
const matched = mainProcesses.filter((process) => browserProcessMatchesPool(process, state));
|
|
@@ -76,8 +77,13 @@ export function buildComputeResourceHealth(options = {}) {
|
|
|
76
77
|
const activeWorkerOwnedMainPids = new Set(mainProcesses
|
|
77
78
|
.filter((process) => Boolean(findOwningActiveWorker(process, processes, workers)))
|
|
78
79
|
.map((process) => process.pid));
|
|
79
|
-
const
|
|
80
|
-
.filter((process) => !assignedMainPids.has(process.pid) && !activeWorkerOwnedMainPids.has(process.pid))
|
|
80
|
+
const unmatchedMainProcesses = mainProcesses
|
|
81
|
+
.filter((process) => !assignedMainPids.has(process.pid) && !activeWorkerOwnedMainPids.has(process.pid));
|
|
82
|
+
const exemptMainProcessDetails = unmatchedMainProcesses
|
|
83
|
+
.filter((process) => browserProcessMatchesExemptUserDataDir(process, exemptBrowserUserDataDirs))
|
|
84
|
+
.map((process) => describeUnassignedBrowserProcess(process, workers));
|
|
85
|
+
const unassignedMainProcessDetails = unmatchedMainProcesses
|
|
86
|
+
.filter((process) => !browserProcessMatchesExemptUserDataDir(process, exemptBrowserUserDataDirs))
|
|
81
87
|
.map((process) => describeUnassignedBrowserProcess(process, workers));
|
|
82
88
|
const unassignedChromiumMainProcesses = unassignedMainProcessDetails.length;
|
|
83
89
|
const activePoolIds = perWorker.filter((pool) => pool.activeLeaseCount > 0).map((pool) => `${pool.workerId}/${pool.poolId}`);
|
|
@@ -121,6 +127,8 @@ export function buildComputeResourceHealth(options = {}) {
|
|
|
121
127
|
totalChromiumMainProcesses: mainProcesses.length,
|
|
122
128
|
unassignedChromiumMainProcesses,
|
|
123
129
|
unassignedMainProcessDetails,
|
|
130
|
+
exemptChromiumMainProcesses: exemptMainProcessDetails.length,
|
|
131
|
+
exemptMainProcessDetails,
|
|
124
132
|
perWorker,
|
|
125
133
|
},
|
|
126
134
|
browserLeases: { active: activePoolIds.length, activePoolIds, staleCdpFiles },
|
|
@@ -198,6 +206,21 @@ function readChromeArg(args, name) {
|
|
|
198
206
|
const pattern = new RegExp(`(?:^|\\s)--${name}=([^\\s]+)`);
|
|
199
207
|
return args.match(pattern)?.[1];
|
|
200
208
|
}
|
|
209
|
+
function readExemptBrowserUserDataDirs() {
|
|
210
|
+
return (process.env.PIBO_RESOURCE_REAPER_EXEMPT_BROWSER_USER_DATA_DIRS ?? "").split(",");
|
|
211
|
+
}
|
|
212
|
+
function normalizeBrowserUserDataDirs(values) {
|
|
213
|
+
return [...new Set([...values]
|
|
214
|
+
.map((value) => value.trim())
|
|
215
|
+
.filter((value) => value.length > 0 && isAbsolute(value))
|
|
216
|
+
.map((value) => resolve(value)))];
|
|
217
|
+
}
|
|
218
|
+
function browserProcessMatchesExemptUserDataDir(process, exemptions) {
|
|
219
|
+
const userDataDir = readChromeArg(process.args, "user-data-dir");
|
|
220
|
+
if (!userDataDir || !isAbsolute(userDataDir))
|
|
221
|
+
return false;
|
|
222
|
+
return exemptions.has(resolve(userDataDir));
|
|
223
|
+
}
|
|
201
224
|
function sanitizeArgsPreview(args) {
|
|
202
225
|
const redacted = args
|
|
203
226
|
.replace(/(token|access_token|refresh_token|password|passwd|cookie|secret)=([^\s]+)/gi, "$1=<redacted>")
|
|
@@ -224,6 +247,7 @@ export async function getComputeResourceHealth(options = {}) {
|
|
|
224
247
|
browserPools,
|
|
225
248
|
staleCdpFiles,
|
|
226
249
|
reaperTimers: detectReaperTimerStatus(),
|
|
250
|
+
exemptBrowserUserDataDirs: options.exemptBrowserUserDataDirs,
|
|
227
251
|
});
|
|
228
252
|
}
|
|
229
253
|
export function defaultBrowserUseHome() {
|
package/dist/config/config.js
CHANGED
|
@@ -43,6 +43,11 @@ export const PIBO_CONFIG_KEYS = [
|
|
|
43
43
|
type: "string",
|
|
44
44
|
description: "SQLite path for Better Auth data.",
|
|
45
45
|
},
|
|
46
|
+
{
|
|
47
|
+
key: "auth.machineKeyStorePath",
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Optional path for revocable machine-key records. Default $PIBO_HOME/machine-keys.json.",
|
|
50
|
+
},
|
|
46
51
|
{
|
|
47
52
|
key: "auth.mode",
|
|
48
53
|
type: "string",
|
|
@@ -244,6 +244,23 @@ export class PiboSessionRouter {
|
|
|
244
244
|
}
|
|
245
245
|
return output;
|
|
246
246
|
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
if (event.type === "message" && event.id) {
|
|
249
|
+
this.signalRegistry.project({
|
|
250
|
+
type: "message_rejected",
|
|
251
|
+
piboSessionId: event.piboSessionId,
|
|
252
|
+
eventId: event.id,
|
|
253
|
+
});
|
|
254
|
+
const status = session.getStatus();
|
|
255
|
+
this.signalRegistry.project({
|
|
256
|
+
type: "session_processing_changed",
|
|
257
|
+
piboSessionId: event.piboSessionId,
|
|
258
|
+
processing: status.processing,
|
|
259
|
+
queuedMessages: status.queuedMessages,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
247
264
|
finally {
|
|
248
265
|
this.scheduleIdleSessionEvictionIfIdle(event.piboSessionId);
|
|
249
266
|
}
|
package/dist/data/schema.js
CHANGED
|
@@ -354,6 +354,10 @@ export function applyPiboDataSchema(db) {
|
|
|
354
354
|
ON sessions(channel, kind, updated_at DESC);
|
|
355
355
|
CREATE INDEX IF NOT EXISTS idx_event_log_session_stream
|
|
356
356
|
ON event_log(session_id, stream_id);
|
|
357
|
+
CREATE INDEX IF NOT EXISTS idx_event_log_unread_session_stream
|
|
358
|
+
ON event_log(session_id, stream_id)
|
|
359
|
+
WHERE (retention_class = 'chat_message' AND type IN ('user.message.accepted', 'assistant_message'))
|
|
360
|
+
OR type = 'session_error';
|
|
357
361
|
CREATE INDEX IF NOT EXISTS idx_event_log_session_sequence_stream
|
|
358
362
|
ON event_log(session_id, session_sequence DESC, stream_id DESC);
|
|
359
363
|
CREATE INDEX IF NOT EXISTS idx_event_log_session_type_sequence_stream
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function resolveDebugTraceSessionStatus(sessionStatus, eventTypes) {
|
|
2
|
+
for (let index = eventTypes.length - 1; index >= 0; index -= 1) {
|
|
3
|
+
switch (eventTypes[index]) {
|
|
4
|
+
case "session_error":
|
|
5
|
+
return { status: "error", source: "event-log" };
|
|
6
|
+
case "message_started":
|
|
7
|
+
return { status: "running", source: "event-log" };
|
|
8
|
+
case "message_finished":
|
|
9
|
+
return { status: "idle", source: "event-log" };
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
status: sessionStatus === "running" || sessionStatus === "error" ? sessionStatus : "idle",
|
|
14
|
+
source: "session-store",
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function summarizeDebugTraceStatus(sessionStatus, nodeStatuses) {
|
|
18
|
+
const errorNodeCount = nodeStatuses.filter((status) => status === "error").length;
|
|
19
|
+
if (sessionStatus === "error")
|
|
20
|
+
return { status: "error", errorNodeCount };
|
|
21
|
+
if (sessionStatus === "running" || nodeStatuses.some((status) => status === "running")) {
|
|
22
|
+
return { status: "running", errorNodeCount };
|
|
23
|
+
}
|
|
24
|
+
return { status: "done", errorNodeCount };
|
|
25
|
+
}
|
package/dist/debug/trace.js
CHANGED
|
@@ -3,6 +3,7 @@ import { normalizeSessionErrorDetails } from "../core/session-errors.js";
|
|
|
3
3
|
import { compareTraceNodes } from "../shared/trace-nodes.js";
|
|
4
4
|
import { openReadOnlyDebugDatabase, withStorePath } from "./sql.js";
|
|
5
5
|
import { formatNextCommands } from "./next-commands.js";
|
|
6
|
+
import { resolveDebugTraceSessionStatus, summarizeDebugTraceStatus } from "./trace-status.js";
|
|
6
7
|
export async function inspectDebugTrace(piboSessionId, stores, options = {}) {
|
|
7
8
|
if (!stores.sessions.exists)
|
|
8
9
|
throw new Error(`Debug store "sessions" not found at ${stores.sessions.path}`);
|
|
@@ -22,19 +23,23 @@ export async function inspectDebugTrace(piboSessionId, stores, options = {}) {
|
|
|
22
23
|
.prepare("SELECT stream_id, session_id, session_sequence, event_id, type, created_at, preview_text, attributes_json FROM event_log WHERE session_id = ? ORDER BY stream_id ASC")
|
|
23
24
|
.all(piboSessionId).map((row) => eventFromRow(row, adapterIssues)).filter((event) => event !== undefined)
|
|
24
25
|
: [];
|
|
26
|
+
const sessionStatus = resolveDebugTraceSessionStatus(sessionRow.status, events.map((event) => event.type));
|
|
25
27
|
const view = await buildTraceView({
|
|
26
28
|
session,
|
|
27
29
|
sessions,
|
|
28
30
|
events,
|
|
29
|
-
status:
|
|
31
|
+
status: sessionStatus.status,
|
|
30
32
|
});
|
|
31
33
|
const rows = flattenTraceNodes(view.nodes);
|
|
34
|
+
const statusSummary = summarizeDebugTraceStatus(sessionStatus.status, rows.map((node) => node.status));
|
|
32
35
|
const filtered = options.runningOnly ? rows.filter((node) => node.status === "running") : rows;
|
|
33
36
|
return {
|
|
34
37
|
piboSessionId: view.piboSessionId,
|
|
35
38
|
piSessionId: view.piSessionId,
|
|
36
39
|
title: view.title,
|
|
37
|
-
status:
|
|
40
|
+
status: statusSummary.status,
|
|
41
|
+
statusSource: sessionStatus.source,
|
|
42
|
+
errorNodeCount: statusSummary.errorNodeCount,
|
|
38
43
|
nodes: filtered,
|
|
39
44
|
rawNodeCount: rows.length,
|
|
40
45
|
...(options.check ? { checks: checkTraceView(view, adapterIssues) } : {}),
|
|
@@ -69,6 +74,8 @@ export function formatDebugTrace(result, options = {}) {
|
|
|
69
74
|
`piSessionId: ${result.piSessionId}`,
|
|
70
75
|
`title: ${result.title}`,
|
|
71
76
|
`status: ${result.status}`,
|
|
77
|
+
`statusSource: ${result.statusSource}`,
|
|
78
|
+
`nodeErrors: ${result.errorNodeCount}`,
|
|
72
79
|
"",
|
|
73
80
|
];
|
|
74
81
|
if (result.nodes.length === 0) {
|
|
@@ -161,14 +168,6 @@ function flattenTraceNodes(nodes, depth = 0) {
|
|
|
161
168
|
...flattenTraceNodes(node.children, depth + 1),
|
|
162
169
|
]);
|
|
163
170
|
}
|
|
164
|
-
function traceStatus(view) {
|
|
165
|
-
const rows = flattenTraceNodes(view.nodes);
|
|
166
|
-
if (rows.some((node) => node.status === "error"))
|
|
167
|
-
return "error";
|
|
168
|
-
if (rows.some((node) => node.status === "running"))
|
|
169
|
-
return "running";
|
|
170
|
-
return "done";
|
|
171
|
-
}
|
|
172
171
|
export function checkTraceView(view, adapterIssues = []) {
|
|
173
172
|
const issues = [...adapterIssues];
|
|
174
173
|
const all = flattenPiboTraceNodes(view.nodes);
|
package/dist/resources/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isAbsolute, resolve } from "node:path";
|
|
1
2
|
import { Command } from "commander";
|
|
2
3
|
import { getComputeResourceHealth } from "../compute/resource-health.js";
|
|
3
4
|
import { renderComputeResourceHealthText } from "../compute/cli.js";
|
|
@@ -17,6 +18,15 @@ function parsePidList(value) {
|
|
|
17
18
|
throw new Error("PIDs must be positive integers separated by commas");
|
|
18
19
|
return [...new Set(pids)];
|
|
19
20
|
}
|
|
21
|
+
function parseAbsolutePathList(value) {
|
|
22
|
+
const paths = value.split(",").map((item) => item.trim());
|
|
23
|
+
if (paths.some((path) => !path || !isAbsolute(path)))
|
|
24
|
+
throw new Error("Browser user-data directories must be absolute paths separated by commas");
|
|
25
|
+
return [...new Set(paths.map((path) => resolve(path)))];
|
|
26
|
+
}
|
|
27
|
+
function shellQuote(value) {
|
|
28
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
29
|
+
}
|
|
20
30
|
export function renderResourceLeasesText(leases) {
|
|
21
31
|
if (leases.length === 0)
|
|
22
32
|
return "No active managed browser-pool leases.\nNext: pibo resources status";
|
|
@@ -53,6 +63,10 @@ export function renderResourceReapText(value) {
|
|
|
53
63
|
];
|
|
54
64
|
if (plan.options.includeDev)
|
|
55
65
|
args.push("--include-dev");
|
|
66
|
+
if (plan.options.exemptBrowserPids.length > 0)
|
|
67
|
+
args.push(`--exempt-browser-pids ${plan.options.exemptBrowserPids.join(",")}`);
|
|
68
|
+
if (plan.options.exemptBrowserUserDataDirs.length > 0)
|
|
69
|
+
args.push(`--exempt-browser-user-data-dirs ${shellQuote(plan.options.exemptBrowserUserDataDirs.join(","))}`);
|
|
56
70
|
if (plan.options.browserPoolRoot)
|
|
57
71
|
args.push(`--browser-pool-root ${plan.options.browserPoolRoot}`);
|
|
58
72
|
if (plan.options.browserUseHome)
|
|
@@ -118,6 +132,7 @@ export async function runResourcesCli(argv) {
|
|
|
118
132
|
.option("--idle-timeout-minutes <n>", "Select browser pools idle for this many minutes", parseNonNegativeNumber, 10)
|
|
119
133
|
.option("--unmanaged-browser-grace-minutes <n>", "Select unmanaged Chromium older than this many minutes", parseNonNegativeNumber, 10)
|
|
120
134
|
.option("--exempt-browser-pids <list>", "Comma-separated browser PIDs or process groups to preserve", parsePidList)
|
|
135
|
+
.option("--exempt-browser-user-data-dirs <list>", "Comma-separated absolute browser profile directories to preserve", parseAbsolutePathList)
|
|
121
136
|
.option("--browser-pool-root <path>", "Browser pool root directory to scan")
|
|
122
137
|
.option("--browser-use-home <path>", "Browser-use home directory to scan for stale CDP files")
|
|
123
138
|
.option("--json", "Print machine-readable cleanup plan or result")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { readdir, readFile, rm } from "node:fs/promises";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
4
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { applyComputeWorkerReapPlan, buildComputeWorkerReapPlan, planReapWorkers, } from "../compute/docker.js";
|
|
7
7
|
import { defaultBrowserPoolRoot, defaultBrowserUseHome, getComputeResourceHealth, parseProcessList, } from "../compute/resource-health.js";
|
|
@@ -60,9 +60,14 @@ export async function planResourceReap(options = {}) {
|
|
|
60
60
|
collectManagedBrowserPools(resolved.browserPoolRoot),
|
|
61
61
|
planStaleCdpFiles(resolved.browserUseHome),
|
|
62
62
|
planComputeReapSafely({ includeDev: resolved.includeDev, maxAgeMinutes: resolved.maxAgeMinutes, now }),
|
|
63
|
-
getComputeResourceHealth({
|
|
63
|
+
getComputeResourceHealth({
|
|
64
|
+
now,
|
|
65
|
+
browserPoolRoot: resolved.browserPoolRoot,
|
|
66
|
+
browserUseHome: resolved.browserUseHome,
|
|
67
|
+
exemptBrowserUserDataDirs: [],
|
|
68
|
+
}),
|
|
64
69
|
]);
|
|
65
|
-
const unmanagedBrowsers = buildUnmanagedBrowserPlanItems(health.browserProcesses.unassignedMainProcessDetails, resolved.unmanagedBrowserGraceMinutes, new Set(resolved.exemptBrowserPids));
|
|
70
|
+
const unmanagedBrowsers = buildUnmanagedBrowserPlanItems(health.browserProcesses.unassignedMainProcessDetails, resolved.unmanagedBrowserGraceMinutes, new Set(resolved.exemptBrowserPids), new Set(resolved.exemptBrowserUserDataDirs));
|
|
66
71
|
return buildResourceReapPlan({ now, options: resolved, records, staleFiles, unmanagedBrowsers, compute });
|
|
67
72
|
}
|
|
68
73
|
export function buildResourceReapPlan(input) {
|
|
@@ -126,9 +131,10 @@ function resolveReapOptions(options) {
|
|
|
126
131
|
browserPoolRoot: options.browserPoolRoot ?? defaultBrowserPoolRoot(),
|
|
127
132
|
browserUseHome: options.browserUseHome ?? defaultBrowserUseHome(),
|
|
128
133
|
exemptBrowserPids: options.exemptBrowserPids ?? readExemptBrowserPids(),
|
|
134
|
+
exemptBrowserUserDataDirs: normalizeBrowserUserDataDirs(options.exemptBrowserUserDataDirs ?? readExemptBrowserUserDataDirs()),
|
|
129
135
|
};
|
|
130
136
|
}
|
|
131
|
-
export function buildUnmanagedBrowserPlanItems(processes, graceMinutes, exemptPids = new Set()) {
|
|
137
|
+
export function buildUnmanagedBrowserPlanItems(processes, graceMinutes, exemptPids = new Set(), exemptUserDataDirs = new Set()) {
|
|
132
138
|
const graceSeconds = graceMinutes * 60;
|
|
133
139
|
return processes.map((process) => {
|
|
134
140
|
let action = "terminate";
|
|
@@ -141,6 +147,10 @@ export function buildUnmanagedBrowserPlanItems(processes, graceMinutes, exemptPi
|
|
|
141
147
|
action = "skip";
|
|
142
148
|
reason = "explicitly exempted pid or process group";
|
|
143
149
|
}
|
|
150
|
+
else if (browserUserDataDirIsExempt(process.userDataDir, exemptUserDataDirs)) {
|
|
151
|
+
action = "skip";
|
|
152
|
+
reason = "explicitly exempted browser user-data-dir";
|
|
153
|
+
}
|
|
144
154
|
else if (process.elapsedSeconds !== undefined && process.elapsedSeconds < graceSeconds) {
|
|
145
155
|
action = "skip";
|
|
146
156
|
reason = `process age ${process.elapsedSeconds}s is within ${graceSeconds}s grace period`;
|
|
@@ -211,6 +221,20 @@ function readExemptBrowserPids() {
|
|
|
211
221
|
.map((value) => Number.parseInt(value.trim(), 10))
|
|
212
222
|
.filter((value) => Number.isInteger(value) && value > 0);
|
|
213
223
|
}
|
|
224
|
+
function readExemptBrowserUserDataDirs() {
|
|
225
|
+
return (process.env.PIBO_RESOURCE_REAPER_EXEMPT_BROWSER_USER_DATA_DIRS ?? "").split(",");
|
|
226
|
+
}
|
|
227
|
+
function normalizeBrowserUserDataDirs(values) {
|
|
228
|
+
return [...new Set([...values]
|
|
229
|
+
.map((value) => value.trim())
|
|
230
|
+
.filter((value) => value.length > 0 && isAbsolute(value))
|
|
231
|
+
.map((value) => resolve(value)))];
|
|
232
|
+
}
|
|
233
|
+
function browserUserDataDirIsExempt(userDataDir, exemptions) {
|
|
234
|
+
if (!userDataDir || !isAbsolute(userDataDir))
|
|
235
|
+
return false;
|
|
236
|
+
return exemptions.has(resolve(userDataDir));
|
|
237
|
+
}
|
|
214
238
|
function buildBrowserReapPlanItem(record, now, idleTimeoutMinutes) {
|
|
215
239
|
const { state } = record;
|
|
216
240
|
let action = "skip";
|
package/dist/resources/reaper.js
CHANGED
|
@@ -76,6 +76,7 @@ export class ResourceReaperService {
|
|
|
76
76
|
browserPoolRoot: this.options.browserPoolRoot,
|
|
77
77
|
browserUseHome: this.options.browserUseHome,
|
|
78
78
|
exemptBrowserPids: this.options.exemptBrowserPids,
|
|
79
|
+
exemptBrowserUserDataDirs: this.options.exemptBrowserUserDataDirs,
|
|
79
80
|
now: runAt,
|
|
80
81
|
});
|
|
81
82
|
result = await this.apply(plan);
|
|
@@ -28,7 +28,7 @@ function settleActiveSessionNodes(piboSessionId, context, status, terminalSource
|
|
|
28
28
|
}
|
|
29
29
|
export const sessionLifecycleSignalProducer = {
|
|
30
30
|
name: "session-lifecycle",
|
|
31
|
-
accepts: (input) => ["session_created", "session_disposed", "session_processing_changed", "message_accepted", "queue_changed", "recovery", "session_interrupted", "signal_node_pruned"].includes(input.type),
|
|
31
|
+
accepts: (input) => ["session_created", "session_disposed", "session_processing_changed", "message_accepted", "message_rejected", "queue_changed", "recovery", "session_interrupted", "signal_node_pruned"].includes(input.type),
|
|
32
32
|
project(input, context) {
|
|
33
33
|
const data = input;
|
|
34
34
|
if (data.type === "session_created") {
|
|
@@ -78,6 +78,15 @@ export const sessionLifecycleSignalProducer = {
|
|
|
78
78
|
}
|
|
79
79
|
return mutations;
|
|
80
80
|
}
|
|
81
|
+
if (data.type === "message_rejected") {
|
|
82
|
+
const acceptedTurn = context.getNode(`turn:${data.piboSessionId}:${data.eventId}`);
|
|
83
|
+
return [
|
|
84
|
+
{ type: "remove_node", nodeId: `message:${data.piboSessionId}:${data.eventId}` },
|
|
85
|
+
...(acceptedTurn?.metadata?.accepted === true
|
|
86
|
+
? [{ type: "remove_node", nodeId: acceptedTurn.id }]
|
|
87
|
+
: []),
|
|
88
|
+
];
|
|
89
|
+
}
|
|
81
90
|
if (data.type === "session_disposed") {
|
|
82
91
|
return [
|
|
83
92
|
...settleActiveSessionNodes(data.piboSessionId, context, "cancelled", "session_disposed"),
|
package/package.json
CHANGED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
/*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */
|
|
2
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-emerald-500:oklch(69.6% .17 162.48);--color-cyan-400:oklch(78.9% .154 211.53);--color-rose-200:oklch(89.2% .058 10.001);--color-rose-300:oklch(81% .117 11.638);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-700:oklch(51.4% .222 16.935);--color-rose-900:oklch(41% .159 10.272);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-semibold:600;--tracking-wider:.05em;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.start{inset-inline-start:var(--spacing)}.z-10{z-index:10}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-12{margin-top:calc(var(--spacing) * 12)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.-ml-20{margin-left:calc(var(--spacing) * -20)}.ml-3{margin-left:calc(var(--spacing) * 3)}.block{display:block}.flex{display:flex}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-7{height:calc(var(--spacing) * 7)}.h-full{height:100%}.min-h-0{min-height:calc(var(--spacing) * 0)}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-7{width:calc(var(--spacing) * 7)}.w-full{width:100%}.max-w-\[180px\]{max-width:180px}.min-w-0{min-width:calc(var(--spacing) * 0)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing) * 1)}.gap-2{gap:calc(var(--spacing) * 2)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-\[\#11a4d4\]{border-color:#11a4d4}.border-rose-700{border-color:var(--color-rose-700)}.border-slate-700{border-color:var(--color-slate-700)}.border-slate-800{border-color:var(--color-slate-800)}.bg-\[\#0a1418\]{background-color:#0a1418}.bg-\[\#0c1820\]{background-color:#0c1820}.bg-\[\#102834\]{background-color:#102834}.bg-cyan-400{background-color:var(--color-cyan-400)}.bg-emerald-500{background-color:var(--color-emerald-500)}.bg-rose-500{background-color:var(--color-rose-500)}.bg-rose-900\/40{background-color:#8b083666}@supports (color:color-mix(in lab, red, red)){.bg-rose-900\/40{background-color:color-mix(in oklab, var(--color-rose-900) 40%, transparent)}}.bg-slate-500{background-color:var(--color-slate-500)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.text-rose-200{color:var(--color-rose-200)}.text-rose-300{color:var(--color-rose-300)}.text-slate-200{color:var(--color-slate-200)}.text-slate-300{color:var(--color-slate-300)}.text-slate-400{color:var(--color-slate-400)}.text-slate-500{color:var(--color-slate-500)}.uppercase{text-transform:uppercase}.underline{text-decoration-line:underline}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}@media (hover:hover){.hover\:border-\[\#11a4d4\]:hover{border-color:#11a4d4}.hover\:border-slate-500:hover{border-color:var(--color-slate-500)}.hover\:bg-\[\#102834\]:hover{background-color:#102834}.hover\:bg-rose-900\/30:hover{background-color:#8b08364d}@supports (color:color-mix(in lab, red, red)){.hover\:bg-rose-900\/30:hover{background-color:color-mix(in oklab, var(--color-rose-900) 30%, transparent)}}.hover\:text-\[\#11a4d4\]:hover{color:#11a4d4}.hover\:text-slate-200:hover{color:var(--color-slate-200)}}}:root{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;scrollbar-color:#334155 #101d22}html,body,#root{height:100%}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background:#101d22;margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Helvetica Neue,sans-serif}code,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.session-signal{display:inline-block;box-shadow:0 0 #64748b00}.session-signal-idle{background:#475569}.session-signal-running{background:#0bda57;animation:1.9s ease-in-out infinite session-signal-slow-pulse}.session-signal-unread{background:#38bdf8;animation:.85s ease-in-out infinite session-signal-fast-pulse}.session-signal-error{background:#ef4444;animation:.55s ease-in-out infinite session-signal-error-blink}@keyframes session-signal-slow-pulse{0%,to{transform:scale(.95);box-shadow:0 0 #0bda5738}50%{transform:scale(1.15);box-shadow:0 0 0 5px #0bda5700}}@keyframes session-signal-fast-pulse{0%,to{transform:scale(.9);box-shadow:0 0 #38bdf861}50%{transform:scale(1.25);box-shadow:0 0 0 6px #38bdf800}}@keyframes session-signal-error-blink{0%,to{opacity:1;transform:scale(1.15);box-shadow:0 0 #ef444473}50%{opacity:.45;transform:scale(.9);box-shadow:0 0 0 6px #ef444400}}.compact-terminal-working-scramble{color:#737373;letter-spacing:-.02em;vertical-align:baseline;width:10ch;font-size:.92em;display:inline-flex}.compact-terminal-working-scramble>span{width:1ch;display:inline-block}.compact-terminal-working-scramble-active{color:#a3a3a3;text-shadow:0 0 4px #a3a3a324}@media (prefers-reduced-motion:reduce){.compact-terminal-working-scramble-active{color:#737373;text-shadow:none}}.model-response-markdown{color:#dbe7e8;overflow-wrap:anywhere}.model-response-markdown>:first-child{margin-top:0}.model-response-markdown>:last-child{margin-bottom:0}.model-response-markdown p{margin:0 0 .85rem}.model-response-markdown h1,.model-response-markdown h2,.model-response-markdown h3,.model-response-markdown h4{color:#f8fafc;letter-spacing:0;margin:1.1rem 0 .45rem;font-weight:700;line-height:1.25}.model-response-markdown h1{font-size:1.15rem}.model-response-markdown h2{font-size:1.05rem}.model-response-markdown h3,.model-response-markdown h4{font-size:.95rem}.model-response-markdown a{color:#42c7ec;text-underline-offset:3px;-webkit-text-decoration:underline #42c7ec73;text-decoration:underline #42c7ec73}.model-response-markdown a:hover{color:#7dd3fc;text-decoration-color:currentColor}.model-response-markdown strong{color:#f8fafc;font-weight:700}.model-response-markdown del{color:#94a3b8}.model-response-markdown ul,.model-response-markdown ol{margin:0 0 .95rem;padding-left:1.25rem;list-style-position:outside}.model-response-markdown ul{list-style-type:disc}.model-response-markdown ol{list-style-type:decimal}.model-response-markdown ul ul,.model-response-markdown ol ul{margin:.25rem 0;list-style-type:circle}.model-response-markdown ul ol,.model-response-markdown ol ol{margin:.25rem 0;list-style-type:lower-alpha}.model-response-markdown li{margin:.25rem 0;padding-left:.15rem}.model-response-markdown li::marker{color:#11a4d4}.model-response-markdown blockquote{color:#cbd5e1;background:#0bda5712;border-left:2px solid #0bda57;margin:.95rem 0;padding:.55rem .85rem}.model-response-markdown hr{border:0;border-top:1px solid #334155cc;margin:1rem 0}.model-response-markdown table{border-collapse:collapse;border:1px solid #334155e6;border-radius:3px;width:max-content;max-width:100%;margin:1rem 0;font-size:.82rem;display:block;overflow-x:auto}.model-response-markdown th,.model-response-markdown td{text-align:left;vertical-align:top;border:1px solid #334155cc;padding:.45rem .6rem}.model-response-markdown th{color:#e2f6fb;background:#11a4d41f;font-weight:700}.model-response-markdown td{color:#dbe7e8;background:#0e11166b}.model-response-markdown :not(pre)>code{color:#11a4d4;background:#111a1f;border:1px solid #e2e8f02e;border-radius:3px;padding:.1rem .3rem;font-size:.86em}.model-response-markdown pre{color:#d1d5db;background:#0e1116;border:1px solid #334155e6;border-left:2px solid #0bda57;border-radius:2px;margin:.95rem 0;padding:.85rem;font-size:.78rem;line-height:1.55;overflow-x:auto}.model-response-markdown pre code{color:inherit;font-size:inherit;background:0 0}.compact-terminal-markdown{color:#d4d4d4;overflow-wrap:anywhere;word-break:break-word;font-size:12px;line-height:1.45}.compact-terminal-markdown>:first-child{margin-top:0}.compact-terminal-markdown>:last-child{margin-bottom:0}.compact-terminal-markdown p{margin:0 0 .55rem}.compact-terminal-reasoning{color:#c7b27a}.compact-terminal-reasoning p{margin-bottom:.35rem}.compact-terminal-reasoning li::marker{color:#f59e0b}.compact-terminal-markdown h1,.compact-terminal-markdown h2,.compact-terminal-markdown h3,.compact-terminal-markdown h4{color:#f3f4f6;margin:.8rem 0 .35rem;font-size:.95rem;line-height:1.3}.compact-terminal-markdown a{color:#38bdf8}.compact-terminal-markdown ul,.compact-terminal-markdown ol{margin:0 0 .55rem;padding-left:1.35rem;list-style-position:outside}.compact-terminal-markdown ul{list-style-type:disc}.compact-terminal-markdown ol{list-style-type:decimal}.compact-terminal-markdown ul ul,.compact-terminal-markdown ol ul{margin:.2rem 0;list-style-type:circle}.compact-terminal-markdown ul ol,.compact-terminal-markdown ol ol{margin:.2rem 0;list-style-type:lower-alpha}.compact-terminal-markdown li{margin:.15rem 0;padding-left:.1rem}.compact-terminal-markdown li::marker{color:#38bdf8}.compact-terminal-markdown li>p{margin:.15rem 0}.compact-terminal-markdown input[type=checkbox]{vertical-align:-.12em;accent-color:#38bdf8;margin:0 .45rem 0 -1.15rem}.compact-terminal-markdown blockquote{color:#d4d4d4;background:#38bdf814;border-left:1px solid #38bdf8;margin:.6rem 0;padding:.45rem .7rem}.compact-terminal-markdown :not(pre)>code{color:#38bdf8;background:#161616;border:1px solid #5a5a5a99;border-radius:2px;padding:.05rem .25rem}.compact-terminal-markdown pre{color:#d4d4d4;background:#111;border:1px solid #2a2a2a;max-width:100%;margin:.65rem 0;padding:.65rem .8rem;font-size:.78rem;line-height:1.5;overflow-x:auto}.compact-terminal-markdown table{border-collapse:collapse;max-width:100%;display:block;overflow-x:auto}.compact-terminal-markdown th,.compact-terminal-markdown td{vertical-align:top;border:1px solid #2a2a2a;padding:.25rem .4rem}.compact-terminal-markdown th{color:#f3f4f6;background:#161616}.compact-terminal-markdown .token.comment,.model-response-markdown .token.comment,.compact-terminal-markdown .token.prolog,.model-response-markdown .token.prolog,.compact-terminal-markdown .token.doctype,.model-response-markdown .token.doctype,.compact-terminal-markdown .token.cdata,.model-response-markdown .token.cdata{color:#737373}.compact-terminal-markdown .token.punctuation,.model-response-markdown .token.punctuation{color:#94a3b8}.compact-terminal-markdown .token.property,.model-response-markdown .token.property,.compact-terminal-markdown .token.tag,.model-response-markdown .token.tag,.compact-terminal-markdown .token.boolean,.model-response-markdown .token.boolean,.compact-terminal-markdown .token.number,.model-response-markdown .token.number,.compact-terminal-markdown .token.constant,.model-response-markdown .token.constant,.compact-terminal-markdown .token.symbol,.model-response-markdown .token.symbol{color:#60a5fa}.compact-terminal-markdown .token.selector,.model-response-markdown .token.selector,.compact-terminal-markdown .token.attr-name,.model-response-markdown .token.attr-name,.compact-terminal-markdown .token.string,.model-response-markdown .token.string,.compact-terminal-markdown .token.char,.model-response-markdown .token.char,.compact-terminal-markdown .token.builtin,.model-response-markdown .token.builtin{color:#22c55e}.compact-terminal-markdown .token.operator,.model-response-markdown .token.operator,.compact-terminal-markdown .token.entity,.model-response-markdown .token.entity,.compact-terminal-markdown .token.url,.model-response-markdown .token.url,.compact-terminal-markdown .token.variable,.model-response-markdown .token.variable{color:#facc15}.compact-terminal-markdown .token.atrule,.model-response-markdown .token.atrule,.compact-terminal-markdown .token.attr-value,.model-response-markdown .token.attr-value,.compact-terminal-markdown .token.function,.model-response-markdown .token.function,.compact-terminal-markdown .token.class-name,.model-response-markdown .token.class-name{color:#38bdf8}.compact-terminal-markdown .token.keyword,.model-response-markdown .token.keyword{color:#d946ef}.compact-terminal-markdown .token.regex,.model-response-markdown .token.regex,.compact-terminal-markdown .token.important,.model-response-markdown .token.important{color:#fb923c}.compact-terminal-markdown .token.deleted,.model-response-markdown .token.deleted{color:#ef4444}.compact-terminal-markdown .token.inserted,.model-response-markdown .token.inserted{color:#22c55e}.compact-terminal-json{--w-rjv-background-color:transparent;--w-rjv-border-left:#2a2a2a;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.compact-terminal-function-call{overflow-wrap:anywhere;word-break:break-word}.compact-terminal-json-toggle{font:inherit;cursor:pointer;background:0 0;border:0;margin:0;padding:0}.compact-terminal-json-toggle:hover{text-underline-offset:2px;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.compact-terminal-json-caret{color:#a3a3a3;width:.75em;text-decoration:none;display:inline-block}.context-files-view .mdxeditor{--baseBg:#151f24;--baseBorderColor:#334155;--baseTextColor:#dbe7e8;--accentBg:#11a4d42e;--accentTextColor:#f8fafc;--accentBaseColor:#11a4d4;--basePageBg:transparent;color:#dbe7e8;flex-direction:column;min-height:100%;display:flex}.context-files-view .mdxeditor-toolbar{z-index:2;background:#151f24;border-bottom:1px solid #1e293b;position:sticky;top:0;overflow-x:auto}.context-files-view .mdxeditor-toolbar [role=toolbar]{gap:.3rem;padding:.45rem .55rem}.context-files-view .mdxeditor-root-contenteditable,.context-files-mdx-content{color:#dbe7e8;overflow-wrap:anywhere;word-break:break-word;width:100%;min-width:0;min-height:calc(100vh - 14rem);padding:1.15rem 1.25rem 5rem}.context-files-mdx-content h1,.context-files-mdx-content h2,.context-files-mdx-content h3,.context-files-mdx-content h4,.context-files-mdx-content h5,.context-files-mdx-content h6{color:#f8fafc;margin:1.2rem 0 .55rem;line-height:1.2}.context-files-mdx-content h1{margin-top:0;font-size:1.6rem}.context-files-mdx-content h2{font-size:1.25rem}.context-files-mdx-content h3{font-size:1.05rem}.context-files-mdx-content ul,.context-files-mdx-content ol{margin:0 0 1rem;padding-left:1.4rem}.context-files-mdx-content blockquote{color:#cbd5e1;border-left:4px solid #11a4d473;margin-left:0;padding-left:.9rem}.context-files-mdx-content table{border-collapse:collapse;width:100%;display:block;overflow-x:auto}.context-files-mdx-content th,.context-files-mdx-content td{border:1px solid #334155;padding:.5rem .6rem}.context-files-view .cm-editor,.context-files-view .cm-gutters{color:#dbe7e8;background:#0e1116}.context-files-view .cm-scroller,.context-files-view pre{max-width:100%;overflow-x:auto}.context-files-view .cm-cursor,.context-files-view .cm-dropCursor{border-left-color:#11a4d4}.context-files-view [class*=_iconButton_],.context-files-view [class*=_toolbarButton_],.context-files-view [class*=_toolbarToggleItem_],.context-files-view [class*=_selectTrigger_],.context-files-view [class*=_toolbarNodeKindSelectTrigger_],.context-files-view [class*=_toolbarButtonSelectTrigger_]{color:#94a3b8!important;box-shadow:none!important;background:0 0!important;border-color:#2a3943!important;font-weight:400!important}.context-files-view [class*=_iconButton_] svg,.context-files-view [class*=_toolbarButton_] svg,.context-files-view [class*=_toolbarToggleItem_] svg,.context-files-view [class*=_selectTrigger_] svg,.context-files-view [class*=_toolbarNodeKindSelectTrigger_] svg,.context-files-view [class*=_toolbarButtonSelectTrigger_] svg{opacity:.88;color:#94a3b8!important;stroke:currentColor!important;width:18px!important;height:18px!important}.context-files-view [class*=_iconButton_]:hover,.context-files-view [class*=_toolbarButton_]:hover,.context-files-view [class*=_toolbarToggleItem_]:hover,.context-files-view [class*=_selectTrigger_]:hover,.context-files-view [class*=_toolbarNodeKindSelectTrigger_]:hover,.context-files-view [class*=_toolbarButtonSelectTrigger_]:hover{color:#cbd5e1!important;background:#ffffff08!important;border-color:#334155!important}.context-files-view [class*=_iconButton_]:hover svg,.context-files-view [class*=_toolbarButton_]:hover svg,.context-files-view [class*=_toolbarToggleItem_]:hover svg,.context-files-view [class*=_selectTrigger_]:hover svg,.context-files-view [class*=_toolbarNodeKindSelectTrigger_]:hover svg,.context-files-view [class*=_toolbarButtonSelectTrigger_]:hover svg{opacity:.94;color:#cbd5e1!important}.context-files-view [data-state=on],.context-files-view [aria-checked=true]{color:#dbe7e8!important;background:#11a4d41f!important;border-color:#11a4d473!important}.context-files-view [data-state=on] svg,.context-files-view [aria-checked=true] svg{opacity:.94;color:#cbd5e1!important}.context-files-view [class*=_selectContent_],.context-files-view [class*=_dropdownMenuContent_],.context-files-view [class*=_dialogContent_]{color:#dbe7e8!important;background:#111a1f!important;border:1px solid #334155!important}.context-files-view [class*=_toolbarToggleSingleGroup_]{gap:.3rem}.context-files-plain-fallback{flex-direction:column;height:100%;min-height:0;display:flex}.context-files-plain-fallback__notice{color:#fde68a;background:#f59e0b1a;border-bottom:1px solid #1e293b;margin:0;padding:.75rem 1rem;font-size:.84rem}.context-files-plain-fallback__textarea{color:#dbe7e8;min-height:0;font:inherit;resize:none;background:#151f24;border:0;outline:none;flex:1;padding:1rem 1.15rem 4rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;line-height:1.55;overflow:auto}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}
|