@pasko70/pibo 3.0.1 → 3.1.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/README.md +20 -6
- package/compute-image/Dockerfile +47 -0
- package/compute-image/Dockerfile.dockerignore +5 -0
- package/dist/agent-runtime/context-build.js +26 -2
- package/dist/agent-runtime/contract.js +78 -28
- package/dist/agent-runtime/portable-history.js +19 -9
- package/dist/agent-runtime/registry.js +8 -2
- package/dist/agent-runtime/routed-session.js +120 -15
- package/dist/agent-runtimes/codex-native/adapter.js +12 -0
- package/dist/agent-runtimes/codex-native/resource-delivery.js +35 -7
- package/dist/agent-runtimes/omp/adapter.js +3 -7
- package/dist/agent-runtimes/pi/adapter.js +5 -3
- package/dist/agent-runtimes/pi/routed-session.js +59 -12
- package/dist/agent-runtimes/pi/runtime.js +6 -0
- package/dist/apps/chat/agent-profiles.js +31 -2
- package/dist/apps/chat/agent-store.js +98 -6
- package/dist/apps/chat/chat-request-normalizers.js +31 -0
- package/dist/apps/chat/data/chat-data-mappers.js +61 -11
- package/dist/apps/chat/data/project-service.js +119 -15
- package/dist/apps/chat/data/room-service.js +21 -0
- package/dist/apps/chat/data/session-query-service.js +33 -10
- package/dist/apps/chat/data/timeline-query-service.js +60 -35
- package/dist/apps/chat/loop-api.js +10 -2
- package/dist/apps/chat/output-compactor.js +144 -21
- package/dist/apps/chat/output-event-policy.js +179 -2
- package/dist/apps/chat/stream.js +16 -7
- package/dist/apps/chat/trace-v2.js +1 -0
- package/dist/apps/chat/web-app.js +317 -86
- package/dist/apps/chat-ui/assets/{dist-BDIATCQt.js → dist-CUQJqggN.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-mqCviI-c.js → dist-ClUlQWYN.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-JnW4v8UE.js → dist-Djul9BmZ.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-lRiLP9rr.js → dist-GD0JGdCi.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BBDMeU5-.js → dist-W5HOqHym.js} +1 -1
- package/dist/apps/chat-ui/assets/index-BcjkX-iP.js +228 -0
- package/dist/apps/chat-ui/assets/index-CmqRSbBU.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-JvrPUGvI.js +43 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/cli-ui/InkSessionApp.js +111 -12
- package/dist/apps/cli-ui/cliSessionsCommand.js +34 -8
- package/dist/cli-session/localSessionSource.js +153 -161
- package/dist/cli.js +7 -11
- package/dist/compute/cli.js +35 -16
- package/dist/compute/docker.js +341 -94
- package/dist/config/config.js +3 -0
- package/dist/core/context-build.js +7 -16
- package/dist/core/output-persistence-retry.js +484 -0
- package/dist/core/output-render-sequence.js +331 -0
- package/dist/core/profiles.js +24 -0
- package/dist/core/provider-recovery.js +7 -1
- package/dist/core/session-model.js +37 -1
- package/dist/core/session-router.js +78 -110
- package/dist/data/ingest-service.js +91 -9
- package/dist/data/navigation-store.js +5 -5
- package/dist/data/pibo-store.js +8 -1
- package/dist/data/schema.js +243 -3
- package/dist/data/session-store.js +3 -3
- package/dist/debug/agents.js +91 -89
- package/dist/debug/index.js +46 -2
- package/dist/debug/pty.js +127 -48
- package/dist/gateway/cli.js +4 -0
- package/dist/gateway/web.js +8 -8
- package/dist/local/client.js +3 -2
- package/dist/loops/channel.js +3 -1
- package/dist/loops/cli.js +2 -1
- package/dist/loops/service.js +90 -11
- package/dist/loops/store.js +100 -74
- package/dist/mcp/agent-context.js +13 -26
- package/dist/mcp/commands/info.js +3 -1
- package/dist/mcp/config-command.js +46 -2
- package/dist/mcp/config.js +18 -4
- package/dist/plugins/context-files-store.js +36 -6
- package/dist/plugins/context-files.js +52 -1
- package/dist/plugins/registry.js +2 -0
- package/dist/plugins/user-profile-resources.js +22 -0
- package/dist/previews/base-url.js +34 -0
- package/dist/previews/config.js +2 -32
- package/dist/ralph/store.js +5 -0
- package/dist/reliability/store.js +378 -106
- package/dist/session-ui/terminalRows.js +8 -4
- package/dist/sessions/pibo-data-store.js +145 -0
- package/dist/sessions/store.js +42 -0
- package/dist/setup/cli.js +422 -6
- package/dist/setup/installation-profiles.js +464 -0
- package/dist/shared/deterministic-digest.js +94 -0
- package/dist/shared/trace-engine.js +54 -0
- package/dist/shared/trace-event-projection.js +124 -67
- package/dist/shared/trace-history.js +47 -12
- package/dist/shared/trace-live-reducer.js +23 -4
- package/dist/shared/trace-nodes.js +17 -28
- package/dist/shared/trace-order.js +41 -5
- package/dist/shared/trace-page-merge.js +44 -14
- package/dist/shared/trace-patch-nodes.js +2 -0
- package/dist/shared/trace-subagent-links.js +4 -1
- package/dist/shared/trace-tool-identity.js +15 -8
- package/dist/signals/projector.js +28 -6
- package/dist/subagents/observation-query.js +121 -0
- package/dist/subagents/tool.js +7 -3
- package/dist/tools/agent-browser-leases.js +58 -23
- package/dist/tools/agent-browser-wrapper.js +1 -1
- package/dist/tools/browser-use-leases.js +129 -16
- package/dist/tools/browser-use-wrapper.js +1 -1
- package/dist/tools/index.js +27 -11
- package/dist/tools/python-runtime.js +10 -3
- package/dist/tools/registry.js +1 -1
- package/dist/tools/runtime/node-backend.js +16 -5
- package/dist/tools/runtime/node-worker-source.js +112 -33
- package/dist/tools/runtime/python-backend.js +16 -5
- package/dist/tools/runtime/python-worker-source.js +167 -16
- package/dist/tools/runtime/registry.js +23 -10
- package/dist/user-skills/store.js +0 -1
- package/docs/ops/vscode-extension-release.md +9 -1
- package/npm-shrinkwrap.json +9 -2
- package/package.json +9 -2
- package/scripts/docker-entrypoint.sh +46 -0
- package/scripts/prepare-agent-browser-wrapper.sh +70 -0
- package/scripts/prepare-browser-use-wrapper.sh +255 -0
- package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +0 -228
- package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +0 -1
- package/dist/apps/chat-vscode-web/assets/index-Dtw2Z7jz.js +0 -43
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.1.vsix +0 -0
package/README.md
CHANGED
|
@@ -24,6 +24,19 @@ pibo --help
|
|
|
24
24
|
|
|
25
25
|
The current package declares Node.js 24+. Some Linux distributions still ship Node 22 through `apt`, so install a recent Node release first when needed. A user-local Node/npm setup through `nvm`, `fnm`, or a user-writable npm prefix works well.
|
|
26
26
|
|
|
27
|
+
Package installation has no host-level side effects. For a self-hosted server, choose a supported setup profile after installing the CLI:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Recommended complete workstation
|
|
31
|
+
pibo setup plan --profile batteries-included --domain pibo.example.com
|
|
32
|
+
sudo pibo setup install --profile batteries-included --domain pibo.example.com --apply --yes
|
|
33
|
+
|
|
34
|
+
# Minimal gateway and Chat Web only
|
|
35
|
+
pibo setup plan --profile vanilla --domain pibo.example.com
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
See [Installation profiles](./docs/project/installation-profiles.md) for components, resource and security costs, staging, upgrades, and rollback.
|
|
39
|
+
|
|
27
40
|
For a user-local global npm prefix:
|
|
28
41
|
|
|
29
42
|
```bash
|
|
@@ -67,17 +80,18 @@ A fresh server install usually needs only three decisions:
|
|
|
67
80
|
2. Install Node.js 24+ for that user or system-wide.
|
|
68
81
|
3. Install Pibo through npm.
|
|
69
82
|
|
|
70
|
-
Use
|
|
83
|
+
Use an installation profile for the normal first run. Batteries Included is recommended; Vanilla is the minimal controlled base:
|
|
71
84
|
|
|
72
85
|
```bash
|
|
73
86
|
pibo setup doctor --domain pibo.example.com --expected-ip <server-ip>
|
|
74
|
-
pibo setup
|
|
75
|
-
pibo setup
|
|
87
|
+
pibo setup plan --profile batteries-included --domain pibo.example.com
|
|
88
|
+
pibo setup install --profile batteries-included --domain pibo.example.com --write-to /tmp/pibo-setup
|
|
89
|
+
sudo pibo setup install --profile batteries-included --domain pibo.example.com --apply --yes
|
|
76
90
|
```
|
|
77
91
|
|
|
78
|
-
|
|
92
|
+
The package install remains side-effect free. The explicit setup command records ownership, checks pinned downloads, configures the selected components, and exposes status, upgrade, and uninstall plans. See [Installation profiles](./docs/project/installation-profiles.md).
|
|
79
93
|
|
|
80
|
-
|
|
94
|
+
Core developers can still use the topology-specific developer-host planner to add production/dev separation plus Docker compute workers:
|
|
81
95
|
|
|
82
96
|
```bash
|
|
83
97
|
pibo setup developer-host \
|
|
@@ -159,7 +173,7 @@ pibo mcp # discover and call configured MCP servers
|
|
|
159
173
|
pibo tools # install and inspect curated external CLI tools
|
|
160
174
|
pibo pi-packages # register Pi Coding Agent packages
|
|
161
175
|
pibo debug # inspect local Pibo data stores
|
|
162
|
-
pibo setup # plan
|
|
176
|
+
pibo setup # plan and manage supported host installation profiles
|
|
163
177
|
pibo profile # inspect runtime profiles
|
|
164
178
|
pibo tui # start the direct Pi TUI
|
|
165
179
|
pibo tui:routed # start the routed Pibo TUI
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
FROM node:24-slim
|
|
2
|
+
|
|
3
|
+
RUN apt-get update && apt-get install -y \
|
|
4
|
+
python3 python3-venv python3-dev \
|
|
5
|
+
build-essential curl git ca-certificates \
|
|
6
|
+
chromium chromium-driver xvfb xauth x11-xserver-utils \
|
|
7
|
+
fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 \
|
|
8
|
+
libatk1.0-0 libcups2 libdbus-1-3 libdrm2 libgbm1 libgtk-3-0 libnspr4 \
|
|
9
|
+
libnss3 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 xdg-utils \
|
|
10
|
+
dbus dbus-x11 \
|
|
11
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
12
|
+
|
|
13
|
+
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
14
|
+
ENV PATH="/root/.local/bin:${PATH}"
|
|
15
|
+
ENV DISPLAY=:99
|
|
16
|
+
ENV PIBO_BROWSER_USE_CHROME=/usr/bin/chromium
|
|
17
|
+
|
|
18
|
+
# The host stages only the published package archive and this Dockerfile, so an
|
|
19
|
+
# installed package's production node_modules never enters the build context.
|
|
20
|
+
COPY pibo-package.tgz /tmp/pibo-package.tgz
|
|
21
|
+
|
|
22
|
+
WORKDIR /app
|
|
23
|
+
RUN npm init -y >/dev/null && \
|
|
24
|
+
npm install --omit=dev --no-audit --no-fund /tmp/pibo-package.tgz && \
|
|
25
|
+
rm -f /tmp/pibo-package.tgz
|
|
26
|
+
|
|
27
|
+
RUN mkdir -p /root/.pibo/tools/browser-use/home/bin && \
|
|
28
|
+
uv venv /root/.pibo/tools/browser-use/.venv --python 3.12 && \
|
|
29
|
+
uv pip install --python /root/.pibo/tools/browser-use/.venv/bin/python \
|
|
30
|
+
'browser-use[cli]==0.12.6'
|
|
31
|
+
|
|
32
|
+
RUN mkdir -p /root/.pibo/tools/agent-browser/home/bin \
|
|
33
|
+
/root/.pibo/tools/agent-browser/home/profiles \
|
|
34
|
+
/root/.pibo/tools/agent-browser/node && \
|
|
35
|
+
npm install --prefix /root/.pibo/tools/agent-browser/node agent-browser@0.27.0
|
|
36
|
+
|
|
37
|
+
WORKDIR /app/node_modules/@pasko70/pibo
|
|
38
|
+
RUN sed -i 's/\r$//' scripts/*.sh && \
|
|
39
|
+
chmod +x scripts/*.sh && \
|
|
40
|
+
scripts/prepare-browser-use-wrapper.sh && \
|
|
41
|
+
scripts/prepare-agent-browser-wrapper.sh && \
|
|
42
|
+
mkdir -p /root/.pibo /root/.browser-use /root/.pibo/tools/agent-browser/home
|
|
43
|
+
|
|
44
|
+
EXPOSE 4788 4789 56663
|
|
45
|
+
|
|
46
|
+
ENTRYPOINT ["/app/node_modules/@pasko70/pibo/scripts/docker-entrypoint.sh"]
|
|
47
|
+
CMD ["gateway:web"]
|
|
@@ -17,6 +17,7 @@ export function profileWithRuntimeInstance(profile, runtimeInstanceId) {
|
|
|
17
17
|
parentSessionId: profile.parentSessionId,
|
|
18
18
|
model: undefined,
|
|
19
19
|
mainModel: undefined,
|
|
20
|
+
mainModelFallbacks: [],
|
|
20
21
|
subagentModel: undefined,
|
|
21
22
|
thinkingLevel: profile.thinkingLevel,
|
|
22
23
|
mainThinkingLevel: profile.mainThinkingLevel,
|
|
@@ -30,6 +31,7 @@ export function profileWithRuntimeInstance(profile, runtimeInstanceId) {
|
|
|
30
31
|
mcpServers: profile.mcpServers,
|
|
31
32
|
piPackages: profile.piPackages,
|
|
32
33
|
contextFiles: profile.contextFiles,
|
|
34
|
+
diagnostics: profile.diagnostics,
|
|
33
35
|
builtinTools: profile.builtinTools,
|
|
34
36
|
builtinToolNames: profile.builtinToolNames,
|
|
35
37
|
autoContextFiles: profile.autoContextFiles,
|
|
@@ -229,6 +231,7 @@ export function buildPortableRuntimeContextSnapshot(input) {
|
|
|
229
231
|
metadata: {
|
|
230
232
|
activeModel: runtimeManifest.effectiveModel,
|
|
231
233
|
mainModel: profile.mainModel,
|
|
234
|
+
mainModelFallbacks: profile.mainModelFallbacks,
|
|
232
235
|
subagentModel: profile.subagentModel,
|
|
233
236
|
mainThinkingLevel: profile.mainThinkingLevel ?? profile.thinkingLevel,
|
|
234
237
|
subagentThinkingLevel: profile.subagentThinkingLevel ?? profile.thinkingLevel,
|
|
@@ -240,6 +243,21 @@ export function buildPortableRuntimeContextSnapshot(input) {
|
|
|
240
243
|
?? input.runtime.capabilities.nativeSubagents.enabledByDefault,
|
|
241
244
|
},
|
|
242
245
|
});
|
|
246
|
+
for (const [index, diagnostic] of profile.diagnostics.entries()) {
|
|
247
|
+
addNode({
|
|
248
|
+
id: `profile-diagnostic-${index}`,
|
|
249
|
+
kind: "diagnostic",
|
|
250
|
+
title: diagnostic.code,
|
|
251
|
+
source: "profile",
|
|
252
|
+
state: diagnostic.severity === "error" ? "error" : "warning",
|
|
253
|
+
badges: [diagnostic.severity.toUpperCase(), "PROFILE"],
|
|
254
|
+
hydratedText: diagnostic.message,
|
|
255
|
+
metadata: diagnostic.resourceName ? {
|
|
256
|
+
resourceKind: diagnostic.resourceKind,
|
|
257
|
+
resourceName: diagnostic.resourceName,
|
|
258
|
+
} : undefined,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
243
261
|
for (const [index, diagnostic] of input.runtime.diagnostics.entries()) {
|
|
244
262
|
addNode({
|
|
245
263
|
id: `runtime-diagnostic-${index}`,
|
|
@@ -265,9 +283,11 @@ export function buildPortableRuntimeContextSnapshot(input) {
|
|
|
265
283
|
});
|
|
266
284
|
}
|
|
267
285
|
const resourceDiagnostics = input.resources?.diagnostics ?? [];
|
|
268
|
-
const warnings =
|
|
286
|
+
const warnings = profile.diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length
|
|
287
|
+
+ input.runtime.diagnostics.filter((diagnostic) => diagnostic.severity === "warning").length
|
|
269
288
|
+ resourceDiagnostics.filter((diagnostic) => diagnostic.severity === "warning").length;
|
|
270
|
-
const errors =
|
|
289
|
+
const errors = profile.diagnostics.filter((diagnostic) => diagnostic.severity === "error").length
|
|
290
|
+
+ input.runtime.diagnostics.filter((diagnostic) => diagnostic.severity === "error").length
|
|
271
291
|
+ resourceDiagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
|
|
272
292
|
return {
|
|
273
293
|
version: 1,
|
|
@@ -287,6 +307,10 @@ export function buildPortableRuntimeContextSnapshot(input) {
|
|
|
287
307
|
},
|
|
288
308
|
nodes,
|
|
289
309
|
diagnostics: [
|
|
310
|
+
...profile.diagnostics.map((diagnostic) => ({
|
|
311
|
+
type: diagnostic.severity,
|
|
312
|
+
message: `[${diagnostic.code}] ${diagnostic.message}`,
|
|
313
|
+
})),
|
|
290
314
|
...input.runtime.diagnostics.map((diagnostic) => ({ type: diagnostic.severity, message: diagnostic.message })),
|
|
291
315
|
...resourceDiagnostics.map((diagnostic) => ({ type: diagnostic.severity, message: diagnostic.message })),
|
|
292
316
|
],
|
|
@@ -1,46 +1,96 @@
|
|
|
1
1
|
import { AgentRuntimeContractError } from "./errors.js";
|
|
2
|
+
import { validateAgentRuntimeCapabilities } from "./capabilities.js";
|
|
2
3
|
import { hasPendingNativeSession } from "../sessions/runtime-binding.js";
|
|
4
|
+
const REQUIRED_SESSION_METHODS = ["getBinding", "subscribe", "prompt", "abort", "dispose", "getStatus"];
|
|
5
|
+
const BINDING_STATES = new Set(["unbound", "bound", "missing", "error"]);
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
8
|
+
}
|
|
3
9
|
export function validateAgentRuntimeSessionContract(session) {
|
|
4
10
|
const errors = [];
|
|
5
|
-
|
|
6
|
-
|
|
11
|
+
if (!isRecord(session))
|
|
12
|
+
return ["session must be an object"];
|
|
13
|
+
for (const field of ["adapterId", "runtimeInstanceId", "cwd"]) {
|
|
14
|
+
if (typeof session[field] !== "string" || !session[field].trim()) {
|
|
15
|
+
errors.push(`session.${field} must be a non-empty string`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
for (const method of REQUIRED_SESSION_METHODS) {
|
|
19
|
+
if (typeof session[method] !== "function")
|
|
20
|
+
errors.push(`session.${method}() is required`);
|
|
21
|
+
}
|
|
22
|
+
let capabilities;
|
|
23
|
+
if (!isRecord(session.capabilities)) {
|
|
24
|
+
errors.push("session.capabilities must be an object");
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const capabilityErrors = validateAgentRuntimeCapabilities(session.capabilities);
|
|
28
|
+
errors.push(...capabilityErrors.map((error) => `session.capabilities.${error}`));
|
|
29
|
+
if (capabilityErrors.length === 0)
|
|
30
|
+
capabilities = session.capabilities;
|
|
31
|
+
}
|
|
32
|
+
const controls = isRecord(session.controls) ? session.controls : undefined;
|
|
33
|
+
if (session.controls !== undefined && !controls)
|
|
34
|
+
errors.push("session.controls must be an object when provided");
|
|
7
35
|
const requireMethod = (enabled, path, method) => {
|
|
8
36
|
if (enabled && typeof controls?.[method] !== "function") {
|
|
9
37
|
errors.push(`${path} requires controls.${String(method)}()`);
|
|
10
38
|
}
|
|
11
39
|
};
|
|
12
|
-
if (capabilities
|
|
40
|
+
if (capabilities?.input.steering && typeof session.steer !== "function") {
|
|
13
41
|
errors.push("input.steering requires session.steer()");
|
|
14
42
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
if (capabilities) {
|
|
44
|
+
requireMethod(capabilities.lifecycle.listNativeSessions, "lifecycle.listNativeSessions", "listSessions");
|
|
45
|
+
requireMethod(capabilities.lifecycle.fork, "lifecycle.fork", "forkSession");
|
|
46
|
+
requireMethod(capabilities.lifecycle.clone, "lifecycle.clone", "cloneSession");
|
|
47
|
+
requireMethod(capabilities.lifecycle.tree, "lifecycle.tree", "getSessionTree");
|
|
48
|
+
requireMethod(capabilities.lifecycle.tree, "lifecycle.tree", "navigateSessionTree");
|
|
49
|
+
requireMethod(capabilities.models.switchInSession, "models.switchInSession", "setModel");
|
|
50
|
+
requireMethod(capabilities.reasoning.supported, "reasoning.supported", "getReasoning");
|
|
51
|
+
requireMethod(capabilities.reasoning.supported, "reasoning.supported", "setReasoning");
|
|
52
|
+
requireMethod(capabilities.approvals.supported, "approvals.supported", "respondToApproval");
|
|
53
|
+
requireMethod(capabilities.approvals.structuredUserInput, "approvals.structuredUserInput", "respondToUserInput");
|
|
54
|
+
requireMethod(capabilities.maintenance.compaction, "maintenance.compaction", "compact");
|
|
55
|
+
}
|
|
56
|
+
if (typeof session.getBinding === "function") {
|
|
57
|
+
try {
|
|
58
|
+
const binding = session.getBinding();
|
|
59
|
+
if (!isRecord(binding)) {
|
|
60
|
+
errors.push("session.getBinding() must return an object");
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
for (const field of ["piboSessionId", "runtimeInstanceId", "adapterId"]) {
|
|
64
|
+
if (typeof binding[field] !== "string" || !binding[field].trim()) {
|
|
65
|
+
errors.push(`session.getBinding().${field} must be a non-empty string`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!BINDING_STATES.has(String(binding.state))) {
|
|
69
|
+
errors.push("session.getBinding().state is invalid");
|
|
70
|
+
}
|
|
71
|
+
if (binding.adapterId !== session.adapterId) {
|
|
72
|
+
errors.push(`binding.adapterId "${String(binding.adapterId)}" does not match session.adapterId "${String(session.adapterId)}"`);
|
|
73
|
+
}
|
|
74
|
+
if (binding.runtimeInstanceId !== session.runtimeInstanceId) {
|
|
75
|
+
errors.push(`binding.runtimeInstanceId "${String(binding.runtimeInstanceId)}" does not match session.runtimeInstanceId "${String(session.runtimeInstanceId)}"`);
|
|
76
|
+
}
|
|
77
|
+
if (binding.state === "bound" && !binding.nativeSessionId) {
|
|
78
|
+
errors.push("a bound session requires binding.nativeSessionId");
|
|
79
|
+
}
|
|
80
|
+
if (binding.state === "unbound" && binding.nativeSessionId && !hasPendingNativeSession(binding)) {
|
|
81
|
+
errors.push("an unbound session must not expose binding.nativeSessionId");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
errors.push("session.getBinding() must not throw during contract validation");
|
|
87
|
+
}
|
|
38
88
|
}
|
|
39
89
|
return errors;
|
|
40
90
|
}
|
|
41
|
-
export function assertAgentRuntimeSessionContract(session) {
|
|
91
|
+
export function assertAgentRuntimeSessionContract(session, runtimeInstanceId) {
|
|
42
92
|
const errors = validateAgentRuntimeSessionContract(session);
|
|
43
93
|
if (errors.length > 0) {
|
|
44
|
-
throw new AgentRuntimeContractError(session.runtimeInstanceId, `Agent runtime session contract failed: ${errors.join("; ")}`);
|
|
94
|
+
throw new AgentRuntimeContractError(runtimeInstanceId ?? (isRecord(session) && typeof session.runtimeInstanceId === "string" ? session.runtimeInstanceId : "unknown"), `Agent runtime session contract failed: ${errors.join("; ")}`);
|
|
45
95
|
}
|
|
46
96
|
}
|
|
@@ -46,6 +46,9 @@ function boundedIdentifierText(value, fallback, maxLength = 512) {
|
|
|
46
46
|
function optionalBoundedIdentifierText(value) {
|
|
47
47
|
return value ? boundedIdentifierText(value, "portable-id") : undefined;
|
|
48
48
|
}
|
|
49
|
+
function toolInvocationKey(turnId, toolCallId) {
|
|
50
|
+
return JSON.stringify([turnId ?? null, toolCallId]);
|
|
51
|
+
}
|
|
49
52
|
function boundedText(value, maxBytes = MAX_ENTRY_BYTES) {
|
|
50
53
|
if (Buffer.byteLength(value, "utf8") <= maxBytes)
|
|
51
54
|
return value;
|
|
@@ -274,18 +277,22 @@ export class PiboDataPortableHistoryProvider {
|
|
|
274
277
|
for (const row of rows) {
|
|
275
278
|
if (!row.tool_call_id)
|
|
276
279
|
continue;
|
|
280
|
+
const invocationKey = toolInvocationKey(row.turn_id, row.tool_call_id);
|
|
277
281
|
if (row.type === "tool_call") {
|
|
278
282
|
const attributes = parseObject(row.attributes_json);
|
|
279
|
-
const existing = calls.get(
|
|
283
|
+
const existing = calls.get(invocationKey);
|
|
280
284
|
if (!existing || attributes.argsComplete === true)
|
|
281
|
-
calls.set(
|
|
285
|
+
calls.set(invocationKey, row);
|
|
282
286
|
}
|
|
283
287
|
else {
|
|
284
|
-
results.set(
|
|
288
|
+
results.set(invocationKey, row);
|
|
285
289
|
}
|
|
286
290
|
}
|
|
287
291
|
const entries = [];
|
|
288
|
-
for (const
|
|
292
|
+
for (const row of calls.values()) {
|
|
293
|
+
const toolCallId = row.tool_call_id;
|
|
294
|
+
if (!toolCallId)
|
|
295
|
+
continue;
|
|
289
296
|
const attributes = parseObject(row.attributes_json);
|
|
290
297
|
const portableToolCallId = boundedIdentifierText(toolCallId, `tool-call-${row.stream_id}`);
|
|
291
298
|
const toolName = boundedIdentifierText(typeof attributes.toolName === "string" ? attributes.toolName : row.preview_text ?? "tool", "tool", 256);
|
|
@@ -309,7 +316,10 @@ export class PiboDataPortableHistoryProvider {
|
|
|
309
316
|
toolName,
|
|
310
317
|
});
|
|
311
318
|
}
|
|
312
|
-
for (const
|
|
319
|
+
for (const row of results.values()) {
|
|
320
|
+
const toolCallId = row.tool_call_id;
|
|
321
|
+
if (!toolCallId)
|
|
322
|
+
continue;
|
|
313
323
|
const attributes = parseObject(row.attributes_json);
|
|
314
324
|
const portableToolCallId = boundedIdentifierText(toolCallId, `tool-call-${row.stream_id}`);
|
|
315
325
|
const toolName = boundedIdentifierText(typeof attributes.toolName === "string" ? attributes.toolName : row.preview_text ?? "tool", "tool", 256);
|
|
@@ -374,18 +384,18 @@ function normalizeToolPairs(entries) {
|
|
|
374
384
|
if (entry.type !== "message")
|
|
375
385
|
continue;
|
|
376
386
|
if (entry.role === "tool" && entry.toolCallId)
|
|
377
|
-
resultIds.add(entry.toolCallId);
|
|
387
|
+
resultIds.add(toolInvocationKey(entry.turnId, entry.toolCallId));
|
|
378
388
|
if (!Array.isArray(entry.content))
|
|
379
389
|
continue;
|
|
380
390
|
for (const part of entry.content) {
|
|
381
391
|
if (part.type === "tool_call")
|
|
382
|
-
callIds.add(part.toolCallId);
|
|
392
|
+
callIds.add(toolInvocationKey(entry.turnId, part.toolCallId));
|
|
383
393
|
}
|
|
384
394
|
}
|
|
385
395
|
return entries.map((entry) => {
|
|
386
396
|
if (entry.type !== "message")
|
|
387
397
|
return entry;
|
|
388
|
-
if (entry.role === "tool" && entry.toolCallId && !callIds.has(entry.toolCallId)) {
|
|
398
|
+
if (entry.role === "tool" && entry.toolCallId && !callIds.has(toolInvocationKey(entry.turnId, entry.toolCallId))) {
|
|
389
399
|
return {
|
|
390
400
|
...entry,
|
|
391
401
|
role: "user",
|
|
@@ -402,7 +412,7 @@ function normalizeToolPairs(entries) {
|
|
|
402
412
|
if (entry.role !== "assistant" || !Array.isArray(entry.content))
|
|
403
413
|
return entry;
|
|
404
414
|
const content = entry.content.map((part) => {
|
|
405
|
-
if (part.type !== "tool_call" || resultIds.has(part.toolCallId))
|
|
415
|
+
if (part.type !== "tool_call" || resultIds.has(toolInvocationKey(entry.turnId, part.toolCallId)))
|
|
406
416
|
return part;
|
|
407
417
|
return {
|
|
408
418
|
type: "text",
|
|
@@ -450,10 +450,16 @@ export class AgentRuntimeAdapterRegistry {
|
|
|
450
450
|
const adapter = this.requireInstance(instanceId);
|
|
451
451
|
const session = await adapter.openSession(input);
|
|
452
452
|
try {
|
|
453
|
-
assertAgentRuntimeSessionContract(session);
|
|
453
|
+
assertAgentRuntimeSessionContract(session, instanceId);
|
|
454
454
|
}
|
|
455
455
|
catch (error) {
|
|
456
|
-
|
|
456
|
+
try {
|
|
457
|
+
if (typeof session?.dispose === "function")
|
|
458
|
+
await session.dispose();
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
// Preserve the admission error when best-effort cleanup also fails.
|
|
462
|
+
}
|
|
457
463
|
throw error;
|
|
458
464
|
}
|
|
459
465
|
return session;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PiboSteeringUnavailableError, } from "../core/events.js";
|
|
2
|
-
import {
|
|
2
|
+
import { PIBO_PROVIDER_RECOVERY_PROMPT, isPiboProviderFallbackError } from "../core/provider-recovery.js";
|
|
3
|
+
import { normalizeSessionErrorDetails, runtimeSessionErrorDetails } from "../core/session-errors.js";
|
|
3
4
|
import { isPiboThinkingLevel } from "../core/thinking.js";
|
|
4
5
|
import { AgentRuntimeCapabilityUnavailableError } from "./errors.js";
|
|
5
6
|
const RUN_REMINDER_CAPABILITY_TOOLS = new Set([
|
|
@@ -13,7 +14,7 @@ const RUN_REMINDER_CAPABILITY_TOOLS = new Set([
|
|
|
13
14
|
const RUN_REMINDER_MAX_TOOL_EXECUTIONS = 64;
|
|
14
15
|
const RUN_REMINDER_MAX_PROVIDER_ROUNDS = 64;
|
|
15
16
|
const RUN_REMINDER_MAX_ACTIVE_TOKENS = 2_000_000;
|
|
16
|
-
const RUN_REMINDER_MAX_DURATION_MS = 15 * 60 * 1000;
|
|
17
|
+
export const RUN_REMINDER_MAX_DURATION_MS = 15 * 60 * 1000;
|
|
17
18
|
const RUN_REMINDER_MAX_REPEATED_TOOL_CALLS = 12;
|
|
18
19
|
function serializedToolArgs(value) {
|
|
19
20
|
try {
|
|
@@ -29,6 +30,23 @@ function errorMessage(error) {
|
|
|
29
30
|
function promptSource(source) {
|
|
30
31
|
return source === "user" || source === "ui" ? "interactive" : "rpc";
|
|
31
32
|
}
|
|
33
|
+
function sameModel(left, right) {
|
|
34
|
+
return Boolean(left && right && left.provider === right.provider && left.id === right.id);
|
|
35
|
+
}
|
|
36
|
+
function uniqueModels(models) {
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
return models.flatMap((model) => {
|
|
39
|
+
const provider = model.provider.trim();
|
|
40
|
+
const id = model.id.trim();
|
|
41
|
+
if (!provider || !id)
|
|
42
|
+
return [];
|
|
43
|
+
const key = `${provider}\u0000${id}`;
|
|
44
|
+
if (seen.has(key))
|
|
45
|
+
return [];
|
|
46
|
+
seen.add(key);
|
|
47
|
+
return [{ provider, id }];
|
|
48
|
+
});
|
|
49
|
+
}
|
|
32
50
|
function runtimeCapabilityError(session, capability) {
|
|
33
51
|
return new AgentRuntimeCapabilityUnavailableError(capability, session.runtimeInstanceId, `Runtime instance "${session.runtimeInstanceId}" does not support ${capability}.`);
|
|
34
52
|
}
|
|
@@ -120,6 +138,10 @@ export class RuntimeRoutedSession {
|
|
|
120
138
|
nextThinkingIndex = 0;
|
|
121
139
|
sessionIdentityOperationInFlight = false;
|
|
122
140
|
forkCandidatesRequest;
|
|
141
|
+
primaryModel;
|
|
142
|
+
modelFallbacks;
|
|
143
|
+
suppressProviderFailures = false;
|
|
144
|
+
pendingProviderFailure;
|
|
123
145
|
unsubscribe;
|
|
124
146
|
constructor(piboSessionId, runtimeSession, emit, pluginRegistry, options = {}) {
|
|
125
147
|
this.piboSessionId = piboSessionId;
|
|
@@ -128,6 +150,10 @@ export class RuntimeRoutedSession {
|
|
|
128
150
|
this.pluginRegistry = pluginRegistry;
|
|
129
151
|
this.options = options;
|
|
130
152
|
this.runtime = runtimeSession.getNativeCompatibilityHandle?.() ?? runtimeSession;
|
|
153
|
+
this.primaryModel = runtimeSession.getStatus().activeModel
|
|
154
|
+
? { ...runtimeSession.getStatus().activeModel }
|
|
155
|
+
: undefined;
|
|
156
|
+
this.modelFallbacks = uniqueModels(options.modelFallbacks ?? []);
|
|
131
157
|
this.unsubscribe = runtimeSession.subscribe((event) => this.handleRuntimeEvent(event));
|
|
132
158
|
}
|
|
133
159
|
enqueueMessage(event, onAccepted = () => { }) {
|
|
@@ -182,14 +208,20 @@ export class RuntimeRoutedSession {
|
|
|
182
208
|
this.assertActive();
|
|
183
209
|
if (event.action === "compact")
|
|
184
210
|
return this.enqueueCompactAction(event);
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
211
|
+
const sessionIdentityOperation = event.action === "session.fork"
|
|
212
|
+
? "fork"
|
|
213
|
+
: event.action === "session.clone"
|
|
214
|
+
? "clone"
|
|
215
|
+
: event.action === "session.switch"
|
|
216
|
+
? "switch"
|
|
217
|
+
: undefined;
|
|
218
|
+
if (sessionIdentityOperation) {
|
|
219
|
+
this.assertSessionIdentityOperationIdle(sessionIdentityOperation);
|
|
188
220
|
this.sessionIdentityOperationInFlight = true;
|
|
189
221
|
this.notifyState();
|
|
190
222
|
}
|
|
191
223
|
try {
|
|
192
|
-
if (
|
|
224
|
+
if (sessionIdentityOperation)
|
|
193
225
|
await this.options.onBeforeSessionIdentityOperation?.(event);
|
|
194
226
|
const result = await this.runAction(event);
|
|
195
227
|
if (isSessionOperationResult(result))
|
|
@@ -205,7 +237,7 @@ export class RuntimeRoutedSession {
|
|
|
205
237
|
return output;
|
|
206
238
|
}
|
|
207
239
|
finally {
|
|
208
|
-
if (
|
|
240
|
+
if (sessionIdentityOperation) {
|
|
209
241
|
this.sessionIdentityOperationInFlight = false;
|
|
210
242
|
this.notifyState();
|
|
211
243
|
this.startDrain();
|
|
@@ -407,7 +439,9 @@ export class RuntimeRoutedSession {
|
|
|
407
439
|
const setModel = this.runtimeSession.controls?.setModel;
|
|
408
440
|
if (!setModel)
|
|
409
441
|
throw runtimeCapabilityError(this.runtimeSession, "in-session model switching");
|
|
410
|
-
|
|
442
|
+
const selected = await setModel(model);
|
|
443
|
+
this.primaryModel = { ...selected };
|
|
444
|
+
return selected;
|
|
411
445
|
}
|
|
412
446
|
setThinkingLevel(level) {
|
|
413
447
|
const setReasoning = this.runtimeSession.controls?.setReasoning;
|
|
@@ -642,10 +676,10 @@ export class RuntimeRoutedSession {
|
|
|
642
676
|
}));
|
|
643
677
|
return;
|
|
644
678
|
case "error":
|
|
645
|
-
this.
|
|
679
|
+
this.handleRuntimeFailure(event.message, event.details);
|
|
646
680
|
return;
|
|
647
681
|
case "turn_failed":
|
|
648
|
-
this.
|
|
682
|
+
this.handleRuntimeFailure(event.message, event.details);
|
|
649
683
|
return;
|
|
650
684
|
case "native_event":
|
|
651
685
|
this.options.onNativeEventTelemetry?.(this.piboSessionId, event.event, {
|
|
@@ -661,6 +695,14 @@ export class RuntimeRoutedSession {
|
|
|
661
695
|
return;
|
|
662
696
|
}
|
|
663
697
|
}
|
|
698
|
+
handleRuntimeFailure(message, details) {
|
|
699
|
+
const normalized = normalizeSessionErrorDetails(message, details);
|
|
700
|
+
if (this.suppressProviderFailures && isPiboProviderFallbackError(message, normalized)) {
|
|
701
|
+
this.pendingProviderFailure = { message, details: normalized };
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
this.emitRuntimeError(message, normalized);
|
|
705
|
+
}
|
|
664
706
|
emitRuntimeError(message, details) {
|
|
665
707
|
if (this.activeMessageFailed)
|
|
666
708
|
return;
|
|
@@ -706,6 +748,71 @@ export class RuntimeRoutedSession {
|
|
|
706
748
|
this.notifyState();
|
|
707
749
|
}
|
|
708
750
|
}
|
|
751
|
+
async promptWithModelFallbacks(event, inFlight) {
|
|
752
|
+
const setModel = this.runtimeSession.controls?.setModel;
|
|
753
|
+
const fallbackModels = this.modelFallbacks.filter((model) => !sameModel(model, this.primaryModel));
|
|
754
|
+
if (!setModel || fallbackModels.length === 0) {
|
|
755
|
+
await this.runtimeSession.prompt({
|
|
756
|
+
text: event.text,
|
|
757
|
+
source: promptSource(event.source),
|
|
758
|
+
capabilityScope: event.capabilityScope,
|
|
759
|
+
});
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
this.suppressProviderFailures = true;
|
|
763
|
+
let switchedModel = false;
|
|
764
|
+
let finalFailure;
|
|
765
|
+
try {
|
|
766
|
+
for (let attempt = 0; attempt <= fallbackModels.length; attempt += 1) {
|
|
767
|
+
if (this.disposed || inFlight.cancelled)
|
|
768
|
+
return;
|
|
769
|
+
if (attempt > 0) {
|
|
770
|
+
try {
|
|
771
|
+
await setModel(fallbackModels[attempt - 1]);
|
|
772
|
+
switchedModel = true;
|
|
773
|
+
}
|
|
774
|
+
catch {
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
this.pendingProviderFailure = undefined;
|
|
779
|
+
let promptError;
|
|
780
|
+
try {
|
|
781
|
+
await this.runtimeSession.prompt({
|
|
782
|
+
text: attempt === 0 ? event.text : PIBO_PROVIDER_RECOVERY_PROMPT,
|
|
783
|
+
source: attempt === 0 ? promptSource(event.source) : "rpc",
|
|
784
|
+
capabilityScope: event.capabilityScope,
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
catch (error) {
|
|
788
|
+
promptError = error;
|
|
789
|
+
}
|
|
790
|
+
if (this.disposed || inFlight.cancelled)
|
|
791
|
+
return;
|
|
792
|
+
const failure = this.pendingProviderFailure;
|
|
793
|
+
if (!failure) {
|
|
794
|
+
if (promptError)
|
|
795
|
+
throw promptError;
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
finalFailure = failure;
|
|
799
|
+
}
|
|
800
|
+
if (finalFailure)
|
|
801
|
+
this.emitRuntimeError(finalFailure.message, finalFailure.details);
|
|
802
|
+
}
|
|
803
|
+
finally {
|
|
804
|
+
this.suppressProviderFailures = false;
|
|
805
|
+
this.pendingProviderFailure = undefined;
|
|
806
|
+
if (switchedModel && this.primaryModel && !this.disposed) {
|
|
807
|
+
try {
|
|
808
|
+
await setModel(this.primaryModel);
|
|
809
|
+
}
|
|
810
|
+
catch (error) {
|
|
811
|
+
console.warn(`Failed to restore primary model for Pibo session "${this.piboSessionId}": ${errorMessage(error)}`);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
709
816
|
async processQueuedMessage(event) {
|
|
710
817
|
const inFlight = this.beginInFlightMessage(event);
|
|
711
818
|
try {
|
|
@@ -743,11 +850,7 @@ export class RuntimeRoutedSession {
|
|
|
743
850
|
});
|
|
744
851
|
if (inFlight.cancelled)
|
|
745
852
|
return;
|
|
746
|
-
await this.
|
|
747
|
-
text: event.text,
|
|
748
|
-
source: promptSource(event.source),
|
|
749
|
-
capabilityScope: event.capabilityScope,
|
|
750
|
-
});
|
|
853
|
+
await this.promptWithModelFallbacks(event, inFlight);
|
|
751
854
|
if (this.disposed || inFlight.cancelled)
|
|
752
855
|
return;
|
|
753
856
|
if (!this.activeMessageFailed) {
|
|
@@ -776,6 +879,8 @@ export class RuntimeRoutedSession {
|
|
|
776
879
|
}
|
|
777
880
|
}
|
|
778
881
|
finally {
|
|
882
|
+
this.suppressProviderFailures = false;
|
|
883
|
+
this.pendingProviderFailure = undefined;
|
|
779
884
|
this.activeMessage = undefined;
|
|
780
885
|
this.activeMessageFailed = false;
|
|
781
886
|
this.resetContentIndices();
|