@pasko70/pibo 1.7.7 → 1.7.9

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.
Files changed (39) hide show
  1. package/dist/apps/chat/chat-user-skill-routes.js +51 -19
  2. package/dist/apps/chat/model-catalog.js +2 -0
  3. package/dist/apps/chat/web-app.js +2 -2
  4. package/dist/apps/chat-ui/assets/{dist-BhCflw5L.js → dist-BLQTYmgi.js} +1 -1
  5. package/dist/apps/chat-ui/assets/{dist-C2wdpulS.js → dist-BLYDOjGT.js} +1 -1
  6. package/dist/apps/chat-ui/assets/{dist-BUfawaFa.js → dist-Bg2k47fY.js} +1 -1
  7. package/dist/apps/chat-ui/assets/{dist-iblHo9vw.js → dist-Bv912fTl.js} +1 -1
  8. package/dist/apps/chat-ui/assets/{dist-NDLYTRqO.js → dist-CCOZbHCt.js} +1 -1
  9. package/dist/apps/chat-ui/assets/{dist-ChMCJ0Xh.js → dist-D6ImmtZ3.js} +1 -1
  10. package/dist/apps/chat-ui/assets/{dist-C5S2MWeq.js → dist-DDQWCHTh.js} +1 -1
  11. package/dist/apps/chat-ui/assets/{dist-XR2wwVeo.js → dist-DTJJ5-iA.js} +1 -1
  12. package/dist/apps/chat-ui/assets/{dist-COMZszUx.js → dist-Daom6uMW.js} +1 -1
  13. package/dist/apps/chat-ui/assets/{dist-CqZsX_X9.js → dist-Dgs6LwMW.js} +1 -1
  14. package/dist/apps/chat-ui/assets/{dist-CJOnkhP1.js → dist-LRUZKp77.js} +1 -1
  15. package/dist/apps/chat-ui/assets/index-BnZ0V5cJ.js +173 -0
  16. package/dist/apps/chat-ui/assets/index-DbRZGRDd.css +1 -0
  17. package/dist/apps/chat-ui/index.html +2 -2
  18. package/dist/apps/chat-vscode-web/assets/index-CiQZTRZH.js +41 -0
  19. package/dist/apps/chat-vscode-web/index.html +1 -1
  20. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  21. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.7.9.vsix +0 -0
  22. package/dist/cli.js +12 -3
  23. package/dist/core/context-guard.js +126 -0
  24. package/dist/core/gateway-resource-guard.js +215 -0
  25. package/dist/core/routed-session.js +76 -1
  26. package/dist/core/runtime.js +40 -2
  27. package/dist/core/session-router.js +2 -0
  28. package/dist/debug/index.js +40 -0
  29. package/dist/mcp/output.js +19 -4
  30. package/dist/providers/openai-gpt56.js +153 -0
  31. package/dist/ralph/cli.js +49 -5
  32. package/dist/session-ui/terminalRows.js +59 -0
  33. package/dist/skills/cli.js +33 -23
  34. package/dist/tools/guides.js +17 -0
  35. package/dist/user-skills/manager.js +106 -8
  36. package/package.json +1 -1
  37. package/dist/apps/chat-ui/assets/index-BStUapSa.css +0 -1
  38. package/dist/apps/chat-ui/assets/index-BYqOi32B.js +0 -173
  39. 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-B4Jk2P5o.js"></script>
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
- return createPiboProfileFromRegistryOrDefault(createDefaultPiboPluginRegistry(), profileName);
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 normalizePiEvent(piboSessionId, event, context) {
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") {
@@ -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
- profile,
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,