@pasko70/pibo 1.7.7 → 1.7.8
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/chat-user-skill-routes.js +51 -19
- package/dist/apps/chat/model-catalog.js +2 -0
- package/dist/apps/chat/web-app.js +2 -2
- package/dist/apps/chat-ui/assets/{dist-BhCflw5L.js → dist-BLQTYmgi.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-C2wdpulS.js → dist-BLYDOjGT.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BUfawaFa.js → dist-Bg2k47fY.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-iblHo9vw.js → dist-Bv912fTl.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-NDLYTRqO.js → dist-CCOZbHCt.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-ChMCJ0Xh.js → dist-D6ImmtZ3.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-C5S2MWeq.js → dist-DDQWCHTh.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-XR2wwVeo.js → dist-DTJJ5-iA.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-COMZszUx.js → dist-Daom6uMW.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CqZsX_X9.js → dist-Dgs6LwMW.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CJOnkhP1.js → dist-LRUZKp77.js} +1 -1
- package/dist/apps/chat-ui/assets/index-BnZ0V5cJ.js +173 -0
- package/dist/apps/chat-ui/assets/index-DbRZGRDd.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-CiQZTRZH.js +41 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/cli.js +12 -3
- package/dist/core/context-guard.js +126 -0
- package/dist/core/gateway-resource-guard.js +215 -0
- package/dist/core/routed-session.js +76 -1
- package/dist/core/runtime.js +40 -2
- package/dist/core/session-router.js +2 -0
- package/dist/debug/index.js +40 -0
- package/dist/mcp/output.js +19 -4
- package/dist/providers/openai-gpt56.js +91 -0
- package/dist/ralph/cli.js +49 -5
- package/dist/session-ui/terminalRows.js +59 -0
- package/dist/skills/cli.js +33 -23
- package/dist/tools/guides.js +17 -0
- package/dist/user-skills/manager.js +106 -8
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-BStUapSa.css +0 -1
- package/dist/apps/chat-ui/assets/index-BYqOi32B.js +0 -173
- package/dist/apps/chat-vscode-web/assets/index-B4Jk2P5o.js +0 -41
|
@@ -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-CiQZTRZH.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-B5QK07zO.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { PIBO_CONFIG_KEYS, getDefaultPiboConfigPath, deletePiboConfigValue, getDisplayPiboConfigValue, loadPiboConfig, redactPiboConfig, savePiboConfig, setPiboConfigValue, } from "./config/config.js";
|
|
4
4
|
import { parsePiboThinkingLevel } from "./core/thinking.js";
|
|
5
|
+
import { piboHomePath } from "./core/pibo-home.js";
|
|
5
6
|
async function createCliProfile(profileName) {
|
|
6
7
|
const { createDefaultPiboPluginRegistry, createGatewayProducerPiboProfile, createPiboProfileFromRegistryOrDefault } = await import("./plugins/builtin.js");
|
|
7
8
|
if (profileName === "gateway-producer" || profileName === "pibo-gateway-producer") {
|
|
8
9
|
return createGatewayProducerPiboProfile();
|
|
9
10
|
}
|
|
10
|
-
|
|
11
|
+
const registry = createDefaultPiboPluginRegistry();
|
|
12
|
+
const chatAgentStorePath = piboHomePath("chat-agents.sqlite");
|
|
13
|
+
if (existsSync(chatAgentStorePath)) {
|
|
14
|
+
const { createPiboChatCustomAgentProfilesPlugin } = await import("./plugins/chat-custom-agents.js");
|
|
15
|
+
registry.registerPlugin(createPiboChatCustomAgentProfilesPlugin({ agentStorePath: chatAgentStorePath }));
|
|
16
|
+
}
|
|
17
|
+
return createPiboProfileFromRegistryOrDefault(registry, profileName);
|
|
11
18
|
}
|
|
12
19
|
function printJson(value) {
|
|
13
20
|
console.log(JSON.stringify(value, null, 2));
|
|
@@ -311,8 +318,10 @@ export async function runPiboCli(argv = process.argv) {
|
|
|
311
318
|
});
|
|
312
319
|
program
|
|
313
320
|
.command("profile")
|
|
321
|
+
.helpOption("-h, --help", "Display help for command")
|
|
314
322
|
.argument("[profile]")
|
|
315
323
|
.description("Inspect a pibo profile")
|
|
324
|
+
.addHelpText("after", "\nProfiles include built-in plugin profiles plus active saved Chat custom agents from $PIBO_HOME/chat-agents.sqlite. Archived custom agents are not exposed.\n")
|
|
316
325
|
.action(async (profile) => {
|
|
317
326
|
const { inspectPiboProfile } = await import("./core/runtime.js");
|
|
318
327
|
printJson(await inspectPiboProfile({ profile: await createCliProfile(profile) }));
|
|
@@ -435,7 +444,7 @@ Commands:
|
|
|
435
444
|
cron Manage scheduled Pibo jobs
|
|
436
445
|
ralph Manage continuous Ralph jobs
|
|
437
446
|
vscode Manage the Pibo VS Code extension
|
|
438
|
-
profile Inspect a pibo profile
|
|
447
|
+
profile Inspect a pibo profile, including active saved Chat custom agents
|
|
439
448
|
tui Start the direct Pi TUI
|
|
440
449
|
tui:routed Start the local routed Pibo TUI
|
|
441
450
|
tui:sessions Start the reduced Web Chat-derived session UI
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { DEFAULT_COMPACTION_SETTINGS, buildSessionContext, estimateTokens, } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
export const PIBO_CONTEXT_GUARD_NOTICE = "Context safety interrupted this response before adding it to long-term context. Pibo is compacting the session before continuing.";
|
|
3
|
+
const DEFAULT_MIN_COMPACTION_RESERVE_TOKENS = 1024;
|
|
4
|
+
const FALLBACK_CONTEXT_WINDOW = 0;
|
|
5
|
+
function textTokensByLength(length) {
|
|
6
|
+
return Math.ceil(length / 4);
|
|
7
|
+
}
|
|
8
|
+
function finitePositive(value) {
|
|
9
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
|
|
10
|
+
}
|
|
11
|
+
function guardReserveTokens(contextWindow, options) {
|
|
12
|
+
const configured = finitePositive(options.reserveTokens);
|
|
13
|
+
if (configured !== undefined)
|
|
14
|
+
return configured;
|
|
15
|
+
const minReserve = finitePositive(options.minReserveTokens) ?? DEFAULT_MIN_COMPACTION_RESERVE_TOKENS;
|
|
16
|
+
const requestedReserve = Math.max(minReserve, Math.floor(contextWindow * 0.2));
|
|
17
|
+
return Math.min(DEFAULT_COMPACTION_SETTINGS.reserveTokens ?? 16384, requestedReserve, Math.floor(contextWindow * 0.5));
|
|
18
|
+
}
|
|
19
|
+
function estimateMessageTokens(message) {
|
|
20
|
+
return estimateTokens(message);
|
|
21
|
+
}
|
|
22
|
+
function estimateBranchContextTokens(ctx) {
|
|
23
|
+
const leafId = ctx.sessionManager.getLeafId();
|
|
24
|
+
const branch = ctx.sessionManager.getBranch(leafId ?? undefined);
|
|
25
|
+
const sessionContext = buildSessionContext(branch, leafId);
|
|
26
|
+
return sessionContext.messages.reduce((tokens, message) => tokens + estimateMessageTokens(message), 0);
|
|
27
|
+
}
|
|
28
|
+
function contextWindowFrom(ctx) {
|
|
29
|
+
const usageWindow = finitePositive(ctx.getContextUsage()?.contextWindow);
|
|
30
|
+
if (usageWindow !== undefined)
|
|
31
|
+
return usageWindow;
|
|
32
|
+
return finitePositive(ctx.model?.contextWindow) ?? FALLBACK_CONTEXT_WINDOW;
|
|
33
|
+
}
|
|
34
|
+
function baselineContextTokens(ctx) {
|
|
35
|
+
const usageTokens = finitePositive(ctx.getContextUsage()?.tokens);
|
|
36
|
+
const estimatedTokens = estimateBranchContextTokens(ctx);
|
|
37
|
+
return usageTokens === undefined ? estimatedTokens : Math.max(usageTokens, estimatedTokens);
|
|
38
|
+
}
|
|
39
|
+
export function projectAssistantContextGuard(ctx, assistantTokens, options = {}) {
|
|
40
|
+
const contextWindow = contextWindowFrom(ctx);
|
|
41
|
+
if (contextWindow <= 0)
|
|
42
|
+
return undefined;
|
|
43
|
+
const contextTokens = baselineContextTokens(ctx);
|
|
44
|
+
const reserveTokens = guardReserveTokens(contextWindow, options);
|
|
45
|
+
const projectedTokens = contextTokens + assistantTokens + reserveTokens;
|
|
46
|
+
return {
|
|
47
|
+
contextTokens,
|
|
48
|
+
assistantTokens,
|
|
49
|
+
reserveTokens,
|
|
50
|
+
contextWindow,
|
|
51
|
+
projectedTokens,
|
|
52
|
+
exceedsLimit: projectedTokens > contextWindow,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function assistantDeltaChars(event) {
|
|
56
|
+
if (!event.assistantMessageEvent || typeof event.assistantMessageEvent !== "object")
|
|
57
|
+
return 0;
|
|
58
|
+
const assistantEvent = event.assistantMessageEvent;
|
|
59
|
+
if (typeof assistantEvent.delta === "string")
|
|
60
|
+
return assistantEvent.delta.length;
|
|
61
|
+
if (typeof assistantEvent.content === "string")
|
|
62
|
+
return assistantEvent.content.length;
|
|
63
|
+
if (assistantEvent.toolCall) {
|
|
64
|
+
return String(assistantEvent.toolCall.name ?? "").length + JSON.stringify(assistantEvent.toolCall.arguments ?? {}).length;
|
|
65
|
+
}
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
function replacementAssistantMessage(message) {
|
|
69
|
+
return {
|
|
70
|
+
...message,
|
|
71
|
+
content: [{ type: "text", text: PIBO_CONTEXT_GUARD_NOTICE }],
|
|
72
|
+
stopReason: "aborted",
|
|
73
|
+
errorMessage: undefined,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function compactionInstructions(projection) {
|
|
77
|
+
const usage = projection
|
|
78
|
+
? ` Projected context was ${projection.projectedTokens} / ${projection.contextWindow} tokens, including ${projection.assistantTokens} tokens from the interrupted assistant response and ${projection.reserveTokens} reserved tokens for compaction.`
|
|
79
|
+
: "";
|
|
80
|
+
return `Pibo interrupted the previous assistant response before persisting the full output because it would exceed the safe context budget.${usage} Summarize the durable conversation up to the guard notice, preserve the user's current task and important recent facts, and leave enough context budget for the next response.`;
|
|
81
|
+
}
|
|
82
|
+
export function createPiboAssistantContextGuardExtension(options = {}) {
|
|
83
|
+
return (pi) => {
|
|
84
|
+
const state = { deltaChars: 0, tripped: false, compactQueued: false };
|
|
85
|
+
function resetAssistantState() {
|
|
86
|
+
state.deltaChars = 0;
|
|
87
|
+
state.tripped = false;
|
|
88
|
+
state.compactQueued = false;
|
|
89
|
+
state.lastProjection = undefined;
|
|
90
|
+
}
|
|
91
|
+
function tripIfNeeded(ctx, assistantTokens) {
|
|
92
|
+
const projection = projectAssistantContextGuard(ctx, assistantTokens, options);
|
|
93
|
+
state.lastProjection = projection;
|
|
94
|
+
if (!projection?.exceedsLimit)
|
|
95
|
+
return false;
|
|
96
|
+
state.tripped = true;
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
pi.on("message_start", (event) => {
|
|
100
|
+
if (event.message.role === "assistant")
|
|
101
|
+
resetAssistantState();
|
|
102
|
+
});
|
|
103
|
+
pi.on("message_update", (event, ctx) => {
|
|
104
|
+
if (event.message.role !== "assistant")
|
|
105
|
+
return;
|
|
106
|
+
state.deltaChars += assistantDeltaChars(event);
|
|
107
|
+
const assistantTokens = Math.max(estimateMessageTokens(event.message), textTokensByLength(state.deltaChars));
|
|
108
|
+
if (tripIfNeeded(ctx, assistantTokens))
|
|
109
|
+
ctx.abort();
|
|
110
|
+
});
|
|
111
|
+
pi.on("message_end", (event, ctx) => {
|
|
112
|
+
if (event.message.role !== "assistant")
|
|
113
|
+
return;
|
|
114
|
+
const assistantTokens = Math.max(estimateMessageTokens(event.message), textTokensByLength(state.deltaChars));
|
|
115
|
+
if (!state.tripped && !tripIfNeeded(ctx, assistantTokens))
|
|
116
|
+
return;
|
|
117
|
+
return { message: replacementAssistantMessage(event.message) };
|
|
118
|
+
});
|
|
119
|
+
pi.on("agent_end", (_event, ctx) => {
|
|
120
|
+
if (!state.tripped || state.compactQueued)
|
|
121
|
+
return;
|
|
122
|
+
state.compactQueued = true;
|
|
123
|
+
ctx.compact({ customInstructions: compactionInstructions(state.lastProjection) });
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { freemem, totalmem } from "node:os";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { getHeapStatistics } from "node:v8";
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
const DEFAULT_POLICY = Object.freeze({
|
|
7
|
+
mode: "warn",
|
|
8
|
+
minFreeMemoryBytes: 256 * 1024 * 1024,
|
|
9
|
+
minHeapAvailableBytes: 64 * 1024 * 1024,
|
|
10
|
+
maxRssBytes: 1536 * 1024 * 1024,
|
|
11
|
+
knownDaemonWarningRssBytes: 2 * 1024 * 1024 * 1024,
|
|
12
|
+
});
|
|
13
|
+
export function resolveGatewayResourceGuardPolicy(env = process.env) {
|
|
14
|
+
return {
|
|
15
|
+
mode: parseMode(env.PIBO_GATEWAY_RESOURCE_GUARD, DEFAULT_POLICY.mode),
|
|
16
|
+
minFreeMemoryBytes: parseByteThreshold(env.PIBO_GATEWAY_MIN_FREE_MEMORY_BYTES, DEFAULT_POLICY.minFreeMemoryBytes),
|
|
17
|
+
minHeapAvailableBytes: parseByteThreshold(env.PIBO_GATEWAY_MIN_HEAP_AVAILABLE_BYTES, DEFAULT_POLICY.minHeapAvailableBytes),
|
|
18
|
+
maxRssBytes: parseByteThreshold(env.PIBO_GATEWAY_MAX_RSS_BYTES, DEFAULT_POLICY.maxRssBytes),
|
|
19
|
+
knownDaemonWarningRssBytes: parseByteThreshold(env.PIBO_GATEWAY_KNOWN_DAEMON_WARNING_RSS_BYTES, DEFAULT_POLICY.knownDaemonWarningRssBytes),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function collectGatewayProcessMemory() {
|
|
23
|
+
const memory = process.memoryUsage();
|
|
24
|
+
const heap = getHeapStatistics();
|
|
25
|
+
return {
|
|
26
|
+
pid: process.pid,
|
|
27
|
+
rssBytes: memory.rss,
|
|
28
|
+
heapUsedBytes: memory.heapUsed,
|
|
29
|
+
heapTotalBytes: memory.heapTotal,
|
|
30
|
+
heapLimitBytes: heap.heap_size_limit,
|
|
31
|
+
heapAvailableBytes: Math.max(0, heap.heap_size_limit - memory.heapUsed),
|
|
32
|
+
externalBytes: memory.external,
|
|
33
|
+
arrayBuffersBytes: memory.arrayBuffers,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function buildGatewayResourceSnapshot(options = {}) {
|
|
37
|
+
const policy = resolveGatewayResourceGuardPolicy(options.env);
|
|
38
|
+
const gateway = collectGatewayProcessMemory();
|
|
39
|
+
const host = { freeBytes: freemem(), totalBytes: totalmem() };
|
|
40
|
+
const processResult = processResultFromOptions(gateway.pid, options, policy);
|
|
41
|
+
const checks = evaluateGatewayResourceChecks({ gateway, host, policy, knownDaemons: processResult.knownDaemons });
|
|
42
|
+
const severity = maxSeverity(checks.map((check) => check.severity));
|
|
43
|
+
return {
|
|
44
|
+
generatedAt: (options.now ?? new Date()).toISOString(),
|
|
45
|
+
readOnly: true,
|
|
46
|
+
policy,
|
|
47
|
+
gateway,
|
|
48
|
+
host,
|
|
49
|
+
checks,
|
|
50
|
+
processes: processResult,
|
|
51
|
+
severity,
|
|
52
|
+
guardAction: guardAction(policy, severity),
|
|
53
|
+
nextCommands: [
|
|
54
|
+
"pibo debug resources --json",
|
|
55
|
+
"pibo compute health --json",
|
|
56
|
+
"pibo debug telemetry sessions --active",
|
|
57
|
+
"pibo debug runs list <pibo-session-id> --json",
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export async function collectGatewayResourceSnapshot(options = {}) {
|
|
62
|
+
if (options.includeProcesses === false || options.processListOutput !== undefined || options.processListError !== undefined) {
|
|
63
|
+
return buildGatewayResourceSnapshot(options);
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const { stdout } = await execFileAsync("ps", ["-eo", "pid=,ppid=,rss=,comm=,args="], { maxBuffer: 10 * 1024 * 1024 });
|
|
67
|
+
return buildGatewayResourceSnapshot({ ...options, processListOutput: stdout });
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
return buildGatewayResourceSnapshot({ ...options, processListError: error instanceof Error ? error.message : String(error) });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function assertGatewayResourceAvailableForWork(workLabel, env = process.env) {
|
|
74
|
+
const snapshot = buildGatewayResourceSnapshot({ env, includeProcesses: false });
|
|
75
|
+
if (snapshot.guardAction !== "block")
|
|
76
|
+
return;
|
|
77
|
+
const reasons = snapshot.checks.filter((check) => check.severity === "critical").map((check) => check.message).join("; ");
|
|
78
|
+
throw new Error(`Gateway resource guard blocked ${workLabel} before starting: ${reasons}`);
|
|
79
|
+
}
|
|
80
|
+
export function parseHostProcessResourceList(output, gatewayPid, policy = DEFAULT_POLICY) {
|
|
81
|
+
const rows = [];
|
|
82
|
+
for (const line of output.split("\n")) {
|
|
83
|
+
if (!line.trim())
|
|
84
|
+
continue;
|
|
85
|
+
const match = line.match(/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s*(.*)$/);
|
|
86
|
+
if (!match)
|
|
87
|
+
continue;
|
|
88
|
+
const pid = Number(match[1]);
|
|
89
|
+
const ppid = Number(match[2]);
|
|
90
|
+
const rssBytes = Number(match[3]) * 1024;
|
|
91
|
+
if (!Number.isInteger(pid) || !Number.isInteger(ppid) || !Number.isFinite(rssBytes))
|
|
92
|
+
continue;
|
|
93
|
+
const commandName = match[4] ?? "";
|
|
94
|
+
const args = match[5] ?? "";
|
|
95
|
+
const daemonLabel = knownDaemonLabel(commandName, args);
|
|
96
|
+
const kind = pid === gatewayPid ? "gateway" : ppid === gatewayPid ? "child" : daemonLabel ? "known-daemon" : "other";
|
|
97
|
+
rows.push({ pid, ppid, rssBytes, commandName, args: sanitizeArgsPreview(args), kind, label: daemonLabel });
|
|
98
|
+
}
|
|
99
|
+
return rows
|
|
100
|
+
.filter((row) => row.kind === "gateway" || row.kind === "child" || row.kind === "known-daemon")
|
|
101
|
+
.sort((a, b) => resourceProcessRank(a, policy) - resourceProcessRank(b, policy) || b.rssBytes - a.rssBytes);
|
|
102
|
+
}
|
|
103
|
+
export function renderGatewayResourceSnapshotText(snapshot) {
|
|
104
|
+
const lines = [`Gateway resource health: ${snapshot.severity} (guard=${snapshot.policy.mode}, action=${snapshot.guardAction})`];
|
|
105
|
+
lines.push(`Generated at: ${snapshot.generatedAt}`);
|
|
106
|
+
lines.push(`Gateway PID: ${snapshot.gateway.pid}`);
|
|
107
|
+
lines.push(`Gateway memory: rss=${snapshot.gateway.rssBytes} heapUsed=${snapshot.gateway.heapUsedBytes} heapAvailable=${snapshot.gateway.heapAvailableBytes} heapLimit=${snapshot.gateway.heapLimitBytes}`);
|
|
108
|
+
lines.push(`Host memory: free=${snapshot.host.freeBytes} total=${snapshot.host.totalBytes}`);
|
|
109
|
+
lines.push(`Thresholds: minFree=${snapshot.policy.minFreeMemoryBytes} minHeapAvailable=${snapshot.policy.minHeapAvailableBytes} maxRss=${snapshot.policy.maxRssBytes} daemonWarnRss=${snapshot.policy.knownDaemonWarningRssBytes}`);
|
|
110
|
+
lines.push(`Related processes: children=${snapshot.processes.children.length} knownDaemons=${snapshot.processes.knownDaemons.length} processList=${snapshot.processes.available ? "available" : "unavailable"}`);
|
|
111
|
+
if (snapshot.processes.error)
|
|
112
|
+
lines.push(`Process list error: ${snapshot.processes.error}`);
|
|
113
|
+
const visibleProcesses = [...snapshot.processes.children, ...snapshot.processes.knownDaemons].slice(0, 10);
|
|
114
|
+
if (visibleProcesses.length > 0) {
|
|
115
|
+
lines.push("PID\tPPID\tRSS_BYTES\tKIND\tLABEL\tCOMMAND");
|
|
116
|
+
for (const process of visibleProcesses) {
|
|
117
|
+
lines.push(`${process.pid}\t${process.ppid}\t${process.rssBytes}\t${process.kind}\t${process.label ?? "-"}\t${process.commandName}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
lines.push("Checks:");
|
|
121
|
+
for (const check of snapshot.checks)
|
|
122
|
+
lines.push(`- [${check.severity}] ${check.id}: ${check.message}`);
|
|
123
|
+
lines.push("Next commands:");
|
|
124
|
+
for (const command of snapshot.nextCommands)
|
|
125
|
+
lines.push(`- ${command}`);
|
|
126
|
+
return lines.join("\n");
|
|
127
|
+
}
|
|
128
|
+
function evaluateGatewayResourceChecks(input) {
|
|
129
|
+
const checks = [];
|
|
130
|
+
if (input.policy.mode === "off") {
|
|
131
|
+
checks.push({ id: "guard-disabled", severity: "ok", message: "Gateway resource guard is disabled." });
|
|
132
|
+
return checks;
|
|
133
|
+
}
|
|
134
|
+
checks.push(input.host.freeBytes < input.policy.minFreeMemoryBytes
|
|
135
|
+
? { id: "host-memory-reserve", severity: "critical", message: `Host free memory ${input.host.freeBytes} is below reserve ${input.policy.minFreeMemoryBytes}.` }
|
|
136
|
+
: { id: "host-memory-reserve", severity: "ok", message: `Host free memory ${input.host.freeBytes} satisfies reserve ${input.policy.minFreeMemoryBytes}.` });
|
|
137
|
+
checks.push(input.gateway.heapAvailableBytes < input.policy.minHeapAvailableBytes
|
|
138
|
+
? { id: "gateway-heap-reserve", severity: "critical", message: `Gateway heap availability ${input.gateway.heapAvailableBytes} is below reserve ${input.policy.minHeapAvailableBytes}.` }
|
|
139
|
+
: { id: "gateway-heap-reserve", severity: "ok", message: `Gateway heap availability ${input.gateway.heapAvailableBytes} satisfies reserve ${input.policy.minHeapAvailableBytes}.` });
|
|
140
|
+
checks.push(input.gateway.rssBytes > input.policy.maxRssBytes
|
|
141
|
+
? { id: "gateway-rss-limit", severity: "critical", message: `Gateway RSS ${input.gateway.rssBytes} exceeds limit ${input.policy.maxRssBytes}.` }
|
|
142
|
+
: { id: "gateway-rss-limit", severity: "ok", message: `Gateway RSS ${input.gateway.rssBytes} is within limit ${input.policy.maxRssBytes}.` });
|
|
143
|
+
const heavyDaemons = input.knownDaemons.filter((process) => process.rssBytes >= input.policy.knownDaemonWarningRssBytes);
|
|
144
|
+
if (heavyDaemons.length > 0)
|
|
145
|
+
checks.push({ id: "known-heavy-daemons", severity: "warning", message: `${heavyDaemons.length} known heavy daemon(s) exceed RSS warning threshold: ${heavyDaemons.map((process) => `${process.label ?? process.commandName}:${process.rssBytes}`).join(", ")}.` });
|
|
146
|
+
return checks;
|
|
147
|
+
}
|
|
148
|
+
function processResultFromOptions(gatewayPid, options, policy) {
|
|
149
|
+
if (options.processListError)
|
|
150
|
+
return { available: false, error: options.processListError, gatewayPid, children: [], knownDaemons: [] };
|
|
151
|
+
if (options.processListOutput === undefined)
|
|
152
|
+
return { available: false, gatewayPid, children: [], knownDaemons: [] };
|
|
153
|
+
const rows = parseHostProcessResourceList(options.processListOutput, gatewayPid, policy);
|
|
154
|
+
return {
|
|
155
|
+
available: true,
|
|
156
|
+
gatewayPid,
|
|
157
|
+
children: rows.filter((row) => row.kind === "child"),
|
|
158
|
+
knownDaemons: rows.filter((row) => row.kind === "known-daemon"),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function parseMode(value, fallback) {
|
|
162
|
+
const normalized = value?.trim().toLowerCase();
|
|
163
|
+
if (normalized === "off" || normalized === "0" || normalized === "false")
|
|
164
|
+
return "off";
|
|
165
|
+
if (normalized === "block" || normalized === "strict")
|
|
166
|
+
return "block";
|
|
167
|
+
if (normalized === "warn" || normalized === "1" || normalized === "true" || normalized === undefined || normalized === "")
|
|
168
|
+
return "warn";
|
|
169
|
+
return fallback;
|
|
170
|
+
}
|
|
171
|
+
function parseByteThreshold(value, fallback) {
|
|
172
|
+
if (value === undefined || value.trim() === "")
|
|
173
|
+
return fallback;
|
|
174
|
+
const parsed = Number(value);
|
|
175
|
+
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : fallback;
|
|
176
|
+
}
|
|
177
|
+
function knownDaemonLabel(commandName, args) {
|
|
178
|
+
const combined = `${commandName} ${args}`;
|
|
179
|
+
if (/comfyui|main\.py.*--port\s+8188/i.test(combined))
|
|
180
|
+
return "ComfyUI";
|
|
181
|
+
if (/unity(\.exe)?|Unity Editor/i.test(combined))
|
|
182
|
+
return "Unity";
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
function sanitizeArgsPreview(args) {
|
|
186
|
+
const redacted = args
|
|
187
|
+
.replace(/(token|access_token|refresh_token|password|passwd|cookie|secret)=([^\s]+)/gi, "$1=<redacted>")
|
|
188
|
+
.replace(/(--(?:token|password|cookie|secret))\s+([^\s]+)/gi, "$1 <redacted>");
|
|
189
|
+
return redacted.length > 220 ? `${redacted.slice(0, 217)}...` : redacted;
|
|
190
|
+
}
|
|
191
|
+
function resourceProcessRank(process, policy) {
|
|
192
|
+
if (process.kind === "gateway")
|
|
193
|
+
return 0;
|
|
194
|
+
if (process.kind === "child")
|
|
195
|
+
return 1;
|
|
196
|
+
if (process.rssBytes >= policy.knownDaemonWarningRssBytes)
|
|
197
|
+
return 2;
|
|
198
|
+
return 3;
|
|
199
|
+
}
|
|
200
|
+
function guardAction(policy, severity) {
|
|
201
|
+
if (policy.mode === "off")
|
|
202
|
+
return "allow";
|
|
203
|
+
if (policy.mode === "block" && severity === "critical")
|
|
204
|
+
return "block";
|
|
205
|
+
if (severity === "warning" || severity === "critical")
|
|
206
|
+
return "warn";
|
|
207
|
+
return "allow";
|
|
208
|
+
}
|
|
209
|
+
function maxSeverity(values) {
|
|
210
|
+
if (values.includes("critical"))
|
|
211
|
+
return "critical";
|
|
212
|
+
if (values.includes("warning"))
|
|
213
|
+
return "warning";
|
|
214
|
+
return "ok";
|
|
215
|
+
}
|
|
@@ -187,10 +187,85 @@ function normalizeToolExecutionEvent(piboSessionId, candidate) {
|
|
|
187
187
|
}
|
|
188
188
|
return undefined;
|
|
189
189
|
}
|
|
190
|
-
function
|
|
190
|
+
function normalizeProviderWebSearchEvent(piboSessionId, candidate) {
|
|
191
|
+
const item = recordValue(candidate.item) ?? recordValue(candidate.assistantMessageEvent?.item);
|
|
192
|
+
const itemType = stringValue(item?.type);
|
|
193
|
+
const rawEventType = stringValue(candidate.type) ?? stringValue(candidate.assistantMessageEvent?.type);
|
|
194
|
+
const rawType = rawEventType?.toLowerCase().includes("web_search") ? rawEventType : itemType;
|
|
195
|
+
if (!rawType || !rawType.toLowerCase().includes("web_search"))
|
|
196
|
+
return undefined;
|
|
197
|
+
const action = recordValue(candidate.action) ?? recordValue(candidate.assistantMessageEvent?.action) ?? recordValue(item?.action);
|
|
198
|
+
const status = providerWebSearchStatus(rawEventType ?? rawType, candidate.status ?? candidate.assistantMessageEvent?.status ?? item?.status);
|
|
199
|
+
if (!status)
|
|
200
|
+
return undefined;
|
|
201
|
+
const rawId = stringValue(candidate.toolCallId) ??
|
|
202
|
+
stringValue(candidate.item_id) ??
|
|
203
|
+
stringValue(item?.id) ??
|
|
204
|
+
stringValue(candidate.assistantMessageEvent?.toolCall?.id) ??
|
|
205
|
+
(typeof candidate.assistantMessageEvent?.contentIndex === "number"
|
|
206
|
+
? `content-${candidate.assistantMessageEvent.contentIndex}`
|
|
207
|
+
: undefined) ??
|
|
208
|
+
"active";
|
|
209
|
+
const toolCallId = rawId.startsWith("provider:web_search:") ? rawId : `provider:web_search:${rawId}`;
|
|
210
|
+
const query = stringValue(candidate.query) ??
|
|
211
|
+
stringValue(candidate.assistantMessageEvent?.query) ??
|
|
212
|
+
stringValue(action?.query) ??
|
|
213
|
+
stringValue(item?.query) ??
|
|
214
|
+
stringValue(item?.search_query);
|
|
215
|
+
const sources = firstArray(candidate.sources, candidate.assistantMessageEvent?.sources, item?.sources, item?.results, item?.citations);
|
|
216
|
+
const args = { providerTool: "web_search", ...(query ? { query } : {}) };
|
|
217
|
+
if (status === "running") {
|
|
218
|
+
return {
|
|
219
|
+
type: "tool_execution_started",
|
|
220
|
+
piboSessionId,
|
|
221
|
+
toolCallId,
|
|
222
|
+
toolName: "web_search",
|
|
223
|
+
args,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const error = candidate.error ?? candidate.assistantMessageEvent?.error ?? item?.error;
|
|
227
|
+
return {
|
|
228
|
+
type: "tool_execution_finished",
|
|
229
|
+
piboSessionId,
|
|
230
|
+
toolCallId,
|
|
231
|
+
toolName: "web_search",
|
|
232
|
+
result: status === "error"
|
|
233
|
+
? (error ?? "Web search failed")
|
|
234
|
+
: {
|
|
235
|
+
...(query ? { query } : {}),
|
|
236
|
+
...(sources ? { sources, sourceCount: sources.length } : {}),
|
|
237
|
+
},
|
|
238
|
+
isError: status === "error",
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function providerWebSearchStatus(rawType, rawStatus) {
|
|
242
|
+
const type = rawType.toLowerCase();
|
|
243
|
+
const status = stringValue(rawStatus)?.toLowerCase();
|
|
244
|
+
if (type.includes("failed") || type.includes("error") || status === "failed" || status === "error")
|
|
245
|
+
return "error";
|
|
246
|
+
if (type.includes("completed") || type.includes("done") || type.includes("end") || status === "completed" || status === "done")
|
|
247
|
+
return "done";
|
|
248
|
+
if (type.includes("started") || type.includes("added") || type.includes("in_progress") || type.includes("searching") || status === "in_progress" || status === "running" || status === "searching")
|
|
249
|
+
return "running";
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
function recordValue(value) {
|
|
253
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
254
|
+
}
|
|
255
|
+
function firstArray(...values) {
|
|
256
|
+
for (const value of values) {
|
|
257
|
+
if (Array.isArray(value))
|
|
258
|
+
return value;
|
|
259
|
+
}
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
export function normalizePiEvent(piboSessionId, event, context) {
|
|
191
263
|
if (!event || typeof event !== "object")
|
|
192
264
|
return undefined;
|
|
193
265
|
const candidate = event;
|
|
266
|
+
const providerToolEvent = normalizeProviderWebSearchEvent(piboSessionId, candidate);
|
|
267
|
+
if (providerToolEvent)
|
|
268
|
+
return providerToolEvent;
|
|
194
269
|
if (candidate.type === "message_update" &&
|
|
195
270
|
candidate.assistantMessageEvent?.type === "text_delta" &&
|
|
196
271
|
typeof candidate.assistantMessageEvent.delta === "string") {
|
package/dist/core/runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { isAbsolute, resolve } from "node:path";
|
|
3
3
|
import { AuthStorage, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createBashToolDefinition, getAgentDir, InteractiveMode, SessionManager, } from "@mariozechner/pi-coding-agent";
|
|
4
|
-
import { DEFAULT_BUILTIN_TOOL_NAMES, } from "./profiles.js";
|
|
4
|
+
import { DEFAULT_BUILTIN_TOOL_NAMES, InitialSessionContext, } from "./profiles.js";
|
|
5
5
|
import { loadPiboModelDefaults, selectRequestedModelProfile, selectRequestedThinkingLevel } from "./model-defaults.js";
|
|
6
6
|
import { createDefaultPiboProfile } from "../plugins/builtin.js";
|
|
7
7
|
import { createSubagentToolDefinitions, createSubagentToolName, } from "../subagents/tool.js";
|
|
@@ -14,11 +14,13 @@ import { getMcpAgentContextFile } from "../mcp/agent-context.js";
|
|
|
14
14
|
import { createPiboSystemPromptTemplateExtension } from "./system-prompt-template.js";
|
|
15
15
|
import { getActivePiboBasePromptPath } from "./base-prompt.js";
|
|
16
16
|
import { createPiboCompactionPromptExtension } from "./compaction-prompt.js";
|
|
17
|
+
import { createPiboAssistantContextGuardExtension } from "./context-guard.js";
|
|
17
18
|
import { getPiPackageRuntimeOptions } from "../pi-packages/runtime.js";
|
|
18
19
|
import { getDefaultPiboWorkspace } from "./workspace.js";
|
|
19
20
|
import { DEFAULT_USER_TIMEZONE } from "./user-settings.js";
|
|
20
21
|
import { registerMiniMaxProvider } from "../providers/minimax.js";
|
|
21
22
|
import { registerGlmProvider } from "../providers/glm.js";
|
|
23
|
+
import { registerOpenAiGpt56Models } from "../providers/openai-gpt56.js";
|
|
22
24
|
import { PIBO_APP_CONTEXT } from "../app-context.js";
|
|
23
25
|
import { createRuntimeToolDefinition } from "../tools/runtime/tool.js";
|
|
24
26
|
import { RuntimeSessionRegistry } from "../tools/runtime/registry.js";
|
|
@@ -146,6 +148,7 @@ function getBuiltinToolAllowlist(profile, customTools) {
|
|
|
146
148
|
function getProfileExtensionFactories(profile, extensionFactories) {
|
|
147
149
|
const piboPromptTemplateExtension = createPiboSystemPromptTemplateExtension();
|
|
148
150
|
const piboCompactionPromptExtension = createPiboCompactionPromptExtension();
|
|
151
|
+
const piboContextGuardExtension = createPiboAssistantContextGuardExtension();
|
|
149
152
|
const providerToolExtensions = profile.tools
|
|
150
153
|
.filter((tool) => tool.enabled !== false)
|
|
151
154
|
.filter(isWebSearchProviderTool)
|
|
@@ -154,6 +157,7 @@ function getProfileExtensionFactories(profile, extensionFactories) {
|
|
|
154
157
|
return [
|
|
155
158
|
piboPromptTemplateExtension,
|
|
156
159
|
piboCompactionPromptExtension,
|
|
160
|
+
piboContextGuardExtension,
|
|
157
161
|
...providerToolExtensions,
|
|
158
162
|
...(extensionFactories ?? []),
|
|
159
163
|
];
|
|
@@ -161,6 +165,7 @@ function getProfileExtensionFactories(profile, extensionFactories) {
|
|
|
161
165
|
return [
|
|
162
166
|
piboPromptTemplateExtension,
|
|
163
167
|
piboCompactionPromptExtension,
|
|
168
|
+
piboContextGuardExtension,
|
|
164
169
|
createCodexCompatExtension({
|
|
165
170
|
isChildSession: profile.parentSessionId !== undefined,
|
|
166
171
|
}),
|
|
@@ -238,6 +243,7 @@ export async function createPiboRuntime(options = {}) {
|
|
|
238
243
|
}),
|
|
239
244
|
},
|
|
240
245
|
});
|
|
246
|
+
registerOpenAiGpt56Models(services.modelRegistry);
|
|
241
247
|
registerMiniMaxProvider(services.modelRegistry);
|
|
242
248
|
registerGlmProvider(services.modelRegistry);
|
|
243
249
|
const ownsLocalRuntimeRegistry = options.runtimeToolController === undefined && profile.tools.some(isEnabledRuntimeTool);
|
|
@@ -336,14 +342,32 @@ function resolveProfileModel(profile, services, cwd, modelDefaults, activeModel)
|
|
|
336
342
|
export async function inspectPiboProfile(options = {}) {
|
|
337
343
|
const cwd = options.cwd ?? process.cwd();
|
|
338
344
|
const profile = options.profile ?? createDefaultPiboProfile();
|
|
345
|
+
const runtimeProfile = new InitialSessionContext({
|
|
346
|
+
profileName: profile.profileName,
|
|
347
|
+
sessionId: profile.sessionId,
|
|
348
|
+
parentSessionId: profile.parentSessionId,
|
|
349
|
+
skills: profile.skills,
|
|
350
|
+
tools: profile.tools,
|
|
351
|
+
subagents: profile.subagents,
|
|
352
|
+
mcpServers: profile.mcpServers,
|
|
353
|
+
piPackages: profile.piPackages,
|
|
354
|
+
contextFiles: profile.contextFiles,
|
|
355
|
+
builtinTools: profile.builtinTools,
|
|
356
|
+
builtinToolNames: profile.builtinToolNames,
|
|
357
|
+
autoContextFiles: profile.autoContextFiles,
|
|
358
|
+
toolPackages: profile.toolPackages,
|
|
359
|
+
});
|
|
339
360
|
const hasEnabledSubagents = profile.subagents.some((subagent) => subagent.enabled !== false);
|
|
340
361
|
const hasYieldableTools = profile.toolPackages.runControl === true ||
|
|
341
362
|
hasEnabledSubagents ||
|
|
342
363
|
profile.tools.some((tool) => tool.enabled !== false && (tool.definition !== undefined || tool.createDefinition !== undefined) && tool.yieldable !== false);
|
|
343
364
|
const runtime = await createPiboRuntime({
|
|
344
365
|
cwd,
|
|
345
|
-
|
|
366
|
+
...options,
|
|
367
|
+
profile: runtimeProfile,
|
|
346
368
|
persistSession: false,
|
|
369
|
+
modelDefaults: {},
|
|
370
|
+
activeModel: undefined,
|
|
347
371
|
subagentRunner: options.subagentRunner ?? (hasEnabledSubagents ? createInspectionSubagentRunner() : undefined),
|
|
348
372
|
runToolController: options.runToolController ?? (hasYieldableTools ? createInspectionRunToolController() : undefined),
|
|
349
373
|
});
|
|
@@ -363,6 +387,19 @@ export async function inspectPiboProfile(options = {}) {
|
|
|
363
387
|
}));
|
|
364
388
|
return {
|
|
365
389
|
profileName: profile.profileName,
|
|
390
|
+
...(profile.model ? { model: { ...profile.model } } : {}),
|
|
391
|
+
...(profile.mainModel ? { mainModel: { ...profile.mainModel } } : {}),
|
|
392
|
+
...(profile.subagentModel ? { subagentModel: { ...profile.subagentModel } } : {}),
|
|
393
|
+
...(profile.thinkingLevel ? { thinkingLevel: profile.thinkingLevel } : {}),
|
|
394
|
+
...(profile.mainThinkingLevel ? { mainThinkingLevel: profile.mainThinkingLevel } : {}),
|
|
395
|
+
...(profile.subagentThinkingLevel ? { subagentThinkingLevel: profile.subagentThinkingLevel } : {}),
|
|
396
|
+
...(profile.fast !== undefined ? { fast: profile.fast } : {}),
|
|
397
|
+
...(profile.mainFast !== undefined ? { mainFast: profile.mainFast } : {}),
|
|
398
|
+
...(profile.subagentFast !== undefined ? { subagentFast: profile.subagentFast } : {}),
|
|
399
|
+
builtinTools: profile.builtinTools,
|
|
400
|
+
builtinToolNames: [...profile.builtinToolNames],
|
|
401
|
+
autoContextFiles: profile.autoContextFiles,
|
|
402
|
+
toolPackages: { ...profile.toolPackages },
|
|
366
403
|
skills: resourceLoader.getSkills().skills.map((skill) => ({
|
|
367
404
|
name: skill.name,
|
|
368
405
|
path: skill.filePath,
|
|
@@ -381,6 +418,7 @@ export async function inspectPiboProfile(options = {}) {
|
|
|
381
418
|
active: activeToolNames.has(toolName),
|
|
382
419
|
};
|
|
383
420
|
}),
|
|
421
|
+
mcpServers: [...profile.mcpServers],
|
|
384
422
|
piPackages: profile.piPackages.map((pkg) => ({
|
|
385
423
|
id: pkg.id,
|
|
386
424
|
active: pkg.enabled !== false,
|
|
@@ -15,6 +15,7 @@ import { loadPiboUserSettings } from "./user-settings.js";
|
|
|
15
15
|
import { resolvePiboSessionActiveModel } from "./session-model.js";
|
|
16
16
|
import { isPiboThinkingLevel } from "./thinking.js";
|
|
17
17
|
import { RuntimeSessionRegistry } from "../tools/runtime/registry.js";
|
|
18
|
+
import { assertGatewayResourceAvailableForWork } from "./gateway-resource-guard.js";
|
|
18
19
|
import { withWorkflowSessionKind } from "../sessions/workflow-session-kind.js";
|
|
19
20
|
import { PiboRuntimeTelemetryRecorder } from "./runtime-telemetry.js";
|
|
20
21
|
import { createPiboProviderTelemetryExtension } from "./provider-telemetry.js";
|
|
@@ -487,6 +488,7 @@ export class PiboSessionRouter {
|
|
|
487
488
|
createRunToolController(parentPiboSessionId) {
|
|
488
489
|
return {
|
|
489
490
|
startToolRun: ({ toolName, params, completionPolicy, retryable, maxAttempts, execute }) => {
|
|
491
|
+
assertGatewayResourceAvailableForWork(`yielded run ${toolName}`);
|
|
490
492
|
const run = this.runRegistry.startToolRun({
|
|
491
493
|
controllerPiboSessionId: parentPiboSessionId,
|
|
492
494
|
toolName,
|