@pasko70/pibo 3.0.2 → 3.1.1
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/compute-image/Dockerfile +47 -0
- package/compute-image/Dockerfile.dockerignore +5 -0
- 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 +12 -6
- package/dist/agent-runtimes/pi/routed-session.js +10 -4
- package/dist/apps/chat/agent-store.js +55 -3
- package/dist/apps/chat/data/project-service.js +119 -15
- package/dist/apps/chat/data/room-service.js +21 -0
- package/dist/apps/chat/loop-api.js +10 -2
- package/dist/apps/chat/web-app.js +84 -25
- package/dist/apps/chat-ui/assets/{dist-CvaPTBTN.js → dist-CUQJqggN.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BPotHecb.js → dist-ClUlQWYN.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BzqQKeVO.js → dist-Djul9BmZ.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-paagejNj.js → dist-GD0JGdCi.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-B6GZgWSj.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/compute/cli.js +35 -16
- package/dist/compute/docker.js +341 -94
- package/dist/config/config.js +3 -0
- package/dist/core/output-render-sequence.js +148 -1
- package/dist/core/session-router.js +33 -105
- package/dist/data/ingest-service.js +14 -6
- package/dist/data/pibo-store.js +8 -1
- package/dist/data/schema.js +135 -5
- package/dist/debug/agents.js +91 -89
- package/dist/debug/index.js +2 -0
- package/dist/debug/pty.js +127 -48
- package/dist/gateway/cli.js +4 -0
- 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 +6 -6
- 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 +174 -111
- package/dist/session-ui/terminalRows.js +2 -1
- package/dist/sessions/pibo-data-store.js +81 -0
- package/dist/subagents/observation-query.js +121 -0
- 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/npm-shrinkwrap.json +9 -2
- package/package.json +8 -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-6JJV-eic.js +0 -228
- package/dist/apps/chat-ui/assets/index-CeL9JPP5.css +0 -1
- package/dist/apps/chat-vscode-web/assets/index-U-MSErGa.js +0 -43
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export const NODE_RUNTIME_WORKER_SOURCE = String.raw `
|
|
2
2
|
const vm = require("node:vm");
|
|
3
3
|
const util = require("node:util");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const childProcess = require("node:child_process");
|
|
6
|
+
const { AsyncLocalStorage } = require("node:async_hooks");
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
const outputStorage = new AsyncLocalStorage();
|
|
6
9
|
|
|
7
10
|
function bounded(value, maxBytes = 8192) {
|
|
8
11
|
const text = String(value);
|
|
@@ -48,14 +51,91 @@ function errorSummary(error) {
|
|
|
48
51
|
return out;
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
function appendOutput(output, stream, chunk) {
|
|
55
|
+
if (output) output[stream] += String(chunk);
|
|
56
|
+
else if (stream === "stdout") process.stdout.write(String(chunk));
|
|
57
|
+
else process.stderr.write(String(chunk));
|
|
58
|
+
}
|
|
59
|
+
|
|
51
60
|
function appendStdout(chunk) {
|
|
52
|
-
|
|
61
|
+
appendOutput(outputStorage.getStore(), "stdout", chunk);
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
function appendStderr(chunk) {
|
|
56
|
-
|
|
65
|
+
appendOutput(outputStorage.getStore(), "stderr", chunk);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function routedStdio(stdio) {
|
|
69
|
+
if (stdio === "inherit") {
|
|
70
|
+
return { stdio: ["inherit", "pipe", "pipe"], stdout: true, stderr: true };
|
|
71
|
+
}
|
|
72
|
+
if (!Array.isArray(stdio)) return undefined;
|
|
73
|
+
const next = [...stdio];
|
|
74
|
+
const stdout = next[1] === "inherit" || next[1] === 1;
|
|
75
|
+
const stderr = next[2] === "inherit" || next[2] === 2;
|
|
76
|
+
if (!stdout && !stderr) return undefined;
|
|
77
|
+
if (stdout) next[1] = "pipe";
|
|
78
|
+
if (stderr) next[2] = "pipe";
|
|
79
|
+
return { stdio: next, stdout, stderr };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function captureSpawnedOutput(child, output, route) {
|
|
83
|
+
if (route.stdout && child.stdout) child.stdout.on("data", (chunk) => appendOutput(output, "stdout", chunk));
|
|
84
|
+
if (route.stderr && child.stderr) child.stderr.on("data", (chunk) => appendOutput(output, "stderr", chunk));
|
|
85
|
+
if (child.spawnargs && child.spawnargs.length > 0 && child.stdout && typeof child.stdout.unref === "function") child.stdout.unref();
|
|
86
|
+
if (child.spawnargs && child.spawnargs.length > 0 && child.stderr && typeof child.stderr.unref === "function") child.stderr.unref();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function routedSpawn(command, args, options) {
|
|
90
|
+
const hasArgs = Array.isArray(args);
|
|
91
|
+
const actualArgs = hasArgs ? args : [];
|
|
92
|
+
const actualOptions = (hasArgs ? options : args) || {};
|
|
93
|
+
const output = outputStorage.getStore();
|
|
94
|
+
const route = output ? routedStdio(actualOptions.stdio) : undefined;
|
|
95
|
+
if (!route) return hasArgs ? childProcess.spawn(command, actualArgs, actualOptions) : childProcess.spawn(command, actualOptions);
|
|
96
|
+
const child = childProcess.spawn(command, actualArgs, { ...actualOptions, stdio: route.stdio });
|
|
97
|
+
captureSpawnedOutput(child, output, route);
|
|
98
|
+
return child;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function routedSpawnSync(command, args, options) {
|
|
102
|
+
const hasArgs = Array.isArray(args);
|
|
103
|
+
const actualArgs = hasArgs ? args : [];
|
|
104
|
+
const actualOptions = (hasArgs ? options : args) || {};
|
|
105
|
+
const output = outputStorage.getStore();
|
|
106
|
+
const route = output ? routedStdio(actualOptions.stdio) : undefined;
|
|
107
|
+
if (!route) return hasArgs ? childProcess.spawnSync(command, actualArgs, actualOptions) : childProcess.spawnSync(command, actualOptions);
|
|
108
|
+
const result = childProcess.spawnSync(command, actualArgs, { ...actualOptions, stdio: route.stdio });
|
|
109
|
+
if (route.stdout && result.stdout != null) appendOutput(output, "stdout", result.stdout);
|
|
110
|
+
if (route.stderr && result.stderr != null) appendOutput(output, "stderr", result.stderr);
|
|
111
|
+
if (route.stdout) {
|
|
112
|
+
result.stdout = null;
|
|
113
|
+
if (result.output) result.output[1] = null;
|
|
114
|
+
}
|
|
115
|
+
if (route.stderr) {
|
|
116
|
+
result.stderr = null;
|
|
117
|
+
if (result.output) result.output[2] = null;
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
57
120
|
}
|
|
58
121
|
|
|
122
|
+
const childProcessProxy = new Proxy(childProcess, {
|
|
123
|
+
get(target, prop, receiver) {
|
|
124
|
+
if (prop === "spawn") return routedSpawn;
|
|
125
|
+
if (prop === "spawnSync") return routedSpawnSync;
|
|
126
|
+
return Reflect.get(target, prop, receiver);
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
function runtimeRequire(specifier) {
|
|
131
|
+
if (specifier === "child_process" || specifier === "node:child_process") return childProcessProxy;
|
|
132
|
+
return require(specifier);
|
|
133
|
+
}
|
|
134
|
+
runtimeRequire.resolve = require.resolve;
|
|
135
|
+
runtimeRequire.cache = require.cache;
|
|
136
|
+
runtimeRequire.extensions = require.extensions;
|
|
137
|
+
runtimeRequire.main = require.main;
|
|
138
|
+
|
|
59
139
|
const processProxy = new Proxy(process, {
|
|
60
140
|
get(target, prop, receiver) {
|
|
61
141
|
if (prop === "stdout") return { write: (chunk) => { appendStdout(chunk); return true; } };
|
|
@@ -75,7 +155,7 @@ const consoleProxy = {
|
|
|
75
155
|
|
|
76
156
|
const context = vm.createContext({
|
|
77
157
|
console: consoleProxy,
|
|
78
|
-
require,
|
|
158
|
+
require: runtimeRequire,
|
|
79
159
|
process: processProxy,
|
|
80
160
|
Buffer,
|
|
81
161
|
URL,
|
|
@@ -97,34 +177,33 @@ async function execute(req) {
|
|
|
97
177
|
const mode = req.mode || "exec";
|
|
98
178
|
const code = req.code || "";
|
|
99
179
|
const output = { stdout: "", stderr: "" };
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
180
|
+
return await outputStorage.run(output, async () => {
|
|
181
|
+
try {
|
|
182
|
+
const value = await (async () => {
|
|
183
|
+
if (mode === "eval") {
|
|
184
|
+
return await vm.runInContext(code, context, { filename: "<pibo-runtime>", timeout: Number(req.timeoutMs || 30000) });
|
|
185
|
+
}
|
|
186
|
+
const result = vm.runInContext(code, context, { filename: "<pibo-runtime>", timeout: Number(req.timeoutMs || 30000) });
|
|
187
|
+
if (result && typeof result.then === "function") await result;
|
|
188
|
+
return undefined;
|
|
189
|
+
})();
|
|
190
|
+
return {
|
|
191
|
+
id: req.id,
|
|
192
|
+
status: "ok",
|
|
193
|
+
stdout: output.stdout,
|
|
194
|
+
stderr: output.stderr,
|
|
195
|
+
result: value === undefined ? null : summarize(value),
|
|
196
|
+
};
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return {
|
|
199
|
+
id: req.id,
|
|
200
|
+
status: "error",
|
|
201
|
+
stdout: output.stdout,
|
|
202
|
+
stderr: output.stderr,
|
|
203
|
+
error: errorSummary(error),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
});
|
|
128
207
|
}
|
|
129
208
|
|
|
130
209
|
async function inspectValue(req) {
|
|
@@ -167,7 +246,7 @@ function listVars(req) {
|
|
|
167
246
|
}
|
|
168
247
|
|
|
169
248
|
function writeResponse(resp) {
|
|
170
|
-
|
|
249
|
+
fs.writeSync(3, JSON.stringify(resp) + "\n");
|
|
171
250
|
}
|
|
172
251
|
|
|
173
252
|
async function handle(req) {
|
|
@@ -46,13 +46,23 @@ export class PythonRuntimeBackend {
|
|
|
46
46
|
this.child = spawn(executable, [...args, "-u", "-c", PYTHON_RUNTIME_WORKER_SOURCE], {
|
|
47
47
|
cwd,
|
|
48
48
|
env: { ...process.env, ...(env ?? {}) },
|
|
49
|
-
stdio: "pipe",
|
|
49
|
+
stdio: ["pipe", "pipe", "pipe", "pipe"],
|
|
50
|
+
});
|
|
51
|
+
const protocol = this.child.stdio[3];
|
|
52
|
+
if (!protocol)
|
|
53
|
+
throw new Error("Python runtime protocol pipe was not created");
|
|
54
|
+
const responses = createInterface({ input: protocol });
|
|
55
|
+
responses.on("line", (line) => this.handleLine(line));
|
|
56
|
+
this.child.stdout.on("data", (chunk) => {
|
|
57
|
+
this.diagnostics += String(chunk);
|
|
50
58
|
});
|
|
51
|
-
const stdout = createInterface({ input: this.child.stdout });
|
|
52
|
-
stdout.on("line", (line) => this.handleLine(line));
|
|
53
59
|
this.child.stderr.on("data", (chunk) => {
|
|
54
60
|
this.diagnostics += String(chunk);
|
|
55
61
|
});
|
|
62
|
+
this.child.stdin.on("error", (error) => {
|
|
63
|
+
this.alive = false;
|
|
64
|
+
this.rejectAll(error);
|
|
65
|
+
});
|
|
56
66
|
this.child.once("error", (error) => {
|
|
57
67
|
this.alive = false;
|
|
58
68
|
this.readyReject(error);
|
|
@@ -78,7 +88,7 @@ export class PythonRuntimeBackend {
|
|
|
78
88
|
}
|
|
79
89
|
}
|
|
80
90
|
isAlive() {
|
|
81
|
-
return this.alive
|
|
91
|
+
return this.alive;
|
|
82
92
|
}
|
|
83
93
|
getRecord() {
|
|
84
94
|
return { pid: this.child.pid, cwd: this.cwd, executable: this.executable };
|
|
@@ -145,7 +155,8 @@ export class PythonRuntimeBackend {
|
|
|
145
155
|
async interrupt() {
|
|
146
156
|
if (!this.isAlive())
|
|
147
157
|
return { status: "failed", sessionId: "", message: "Runtime worker is not alive" };
|
|
148
|
-
this.child.kill("SIGINT")
|
|
158
|
+
if (!this.child.kill("SIGINT"))
|
|
159
|
+
return { status: "failed", sessionId: "", message: "Runtime worker is not alive" };
|
|
149
160
|
return { status: "ok", sessionId: "", message: "Sent SIGINT to runtime worker" };
|
|
150
161
|
}
|
|
151
162
|
async close(force = false) {
|
|
@@ -1,13 +1,151 @@
|
|
|
1
1
|
export const PYTHON_RUNTIME_WORKER_SOURCE = String.raw `
|
|
2
|
-
import
|
|
2
|
+
import contextvars
|
|
3
3
|
import inspect
|
|
4
|
-
import io
|
|
5
4
|
import json
|
|
5
|
+
import os
|
|
6
6
|
import signal
|
|
7
|
+
import subprocess
|
|
7
8
|
import sys
|
|
9
|
+
import threading
|
|
8
10
|
import traceback
|
|
9
11
|
|
|
10
12
|
user_globals = {"__name__": "__pibo_runtime__"}
|
|
13
|
+
protocol_stream = os.fdopen(3, "w", encoding="utf-8", buffering=1, closefd=False)
|
|
14
|
+
output_context = contextvars.ContextVar("pibo_runtime_output", default=None)
|
|
15
|
+
original_stdout = sys.stdout
|
|
16
|
+
original_stderr = sys.stderr
|
|
17
|
+
original_popen = subprocess.Popen
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def append_output(output, stream, value):
|
|
21
|
+
text = str(value)
|
|
22
|
+
if output is None:
|
|
23
|
+
target = original_stdout if stream == "stdout" else original_stderr
|
|
24
|
+
target.write(text)
|
|
25
|
+
target.flush()
|
|
26
|
+
return
|
|
27
|
+
with output["lock"]:
|
|
28
|
+
output[stream].append(text)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BinaryOutputProxy:
|
|
32
|
+
def __init__(self, stream, fallback):
|
|
33
|
+
self.stream = stream
|
|
34
|
+
self.fallback = fallback
|
|
35
|
+
|
|
36
|
+
def write(self, value):
|
|
37
|
+
data = bytes(value)
|
|
38
|
+
append_output(output_context.get(), self.stream, data.decode("utf-8", "replace"))
|
|
39
|
+
return len(data)
|
|
40
|
+
|
|
41
|
+
def flush(self):
|
|
42
|
+
self.fallback.flush()
|
|
43
|
+
|
|
44
|
+
def __getattr__(self, name):
|
|
45
|
+
return getattr(self.fallback, name)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class OutputProxy:
|
|
49
|
+
def __init__(self, stream, fallback):
|
|
50
|
+
self.stream = stream
|
|
51
|
+
self.fallback = fallback
|
|
52
|
+
self.encoding = getattr(fallback, "encoding", "utf-8")
|
|
53
|
+
self.errors = getattr(fallback, "errors", "replace")
|
|
54
|
+
self.buffer = BinaryOutputProxy(stream, fallback.buffer)
|
|
55
|
+
|
|
56
|
+
def write(self, value):
|
|
57
|
+
append_output(output_context.get(), self.stream, value)
|
|
58
|
+
return len(str(value))
|
|
59
|
+
|
|
60
|
+
def flush(self):
|
|
61
|
+
self.fallback.flush()
|
|
62
|
+
|
|
63
|
+
def __getattr__(self, name):
|
|
64
|
+
return getattr(self.fallback, name)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
sys.stdout = OutputProxy("stdout", original_stdout)
|
|
68
|
+
sys.stderr = OutputProxy("stderr", original_stderr)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def drain_child_output(read_fd, output, stream):
|
|
72
|
+
try:
|
|
73
|
+
with os.fdopen(read_fd, "rb", closefd=True) as source:
|
|
74
|
+
while True:
|
|
75
|
+
chunk = source.read(65536)
|
|
76
|
+
if not chunk:
|
|
77
|
+
return
|
|
78
|
+
append_output(output, stream, chunk.decode("utf-8", "replace"))
|
|
79
|
+
except Exception:
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def join_child_output(threads):
|
|
84
|
+
for thread in threads:
|
|
85
|
+
thread.join(0.2)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def routed_popen(*args, **kwargs):
|
|
89
|
+
output = output_context.get()
|
|
90
|
+
if output is None:
|
|
91
|
+
return original_popen(*args, **kwargs)
|
|
92
|
+
|
|
93
|
+
options = dict(kwargs)
|
|
94
|
+
routes = []
|
|
95
|
+
if options.get("stdout") is None:
|
|
96
|
+
read_fd, write_fd = os.pipe()
|
|
97
|
+
options["stdout"] = write_fd
|
|
98
|
+
routes.append((read_fd, write_fd, "stdout"))
|
|
99
|
+
if options.get("stderr") is None:
|
|
100
|
+
read_fd, write_fd = os.pipe()
|
|
101
|
+
options["stderr"] = write_fd
|
|
102
|
+
routes.append((read_fd, write_fd, "stderr"))
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
process = original_popen(*args, **options)
|
|
106
|
+
except Exception:
|
|
107
|
+
for read_fd, write_fd, _ in routes:
|
|
108
|
+
os.close(read_fd)
|
|
109
|
+
os.close(write_fd)
|
|
110
|
+
raise
|
|
111
|
+
|
|
112
|
+
process_threads = []
|
|
113
|
+
for read_fd, write_fd, stream in routes:
|
|
114
|
+
os.close(write_fd)
|
|
115
|
+
thread = threading.Thread(target=drain_child_output, args=(read_fd, output, stream), daemon=True)
|
|
116
|
+
with output["lock"]:
|
|
117
|
+
output["threads"].append(thread)
|
|
118
|
+
process_threads.append(thread)
|
|
119
|
+
thread.start()
|
|
120
|
+
|
|
121
|
+
if process_threads:
|
|
122
|
+
original_wait = process.wait
|
|
123
|
+
original_poll = process.poll
|
|
124
|
+
original_communicate = process.communicate
|
|
125
|
+
|
|
126
|
+
def wait_with_output(*wait_args, **wait_kwargs):
|
|
127
|
+
result = original_wait(*wait_args, **wait_kwargs)
|
|
128
|
+
join_child_output(process_threads)
|
|
129
|
+
return result
|
|
130
|
+
|
|
131
|
+
def poll_with_output(*poll_args, **poll_kwargs):
|
|
132
|
+
result = original_poll(*poll_args, **poll_kwargs)
|
|
133
|
+
if result is not None:
|
|
134
|
+
join_child_output(process_threads)
|
|
135
|
+
return result
|
|
136
|
+
|
|
137
|
+
def communicate_with_output(*communicate_args, **communicate_kwargs):
|
|
138
|
+
result = original_communicate(*communicate_args, **communicate_kwargs)
|
|
139
|
+
join_child_output(process_threads)
|
|
140
|
+
return result
|
|
141
|
+
|
|
142
|
+
process.wait = wait_with_output
|
|
143
|
+
process.poll = poll_with_output
|
|
144
|
+
process.communicate = communicate_with_output
|
|
145
|
+
return process
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
subprocess.Popen = routed_popen
|
|
11
149
|
|
|
12
150
|
|
|
13
151
|
def bounded(value, max_bytes=8192):
|
|
@@ -70,31 +208,44 @@ def error_summary(exc):
|
|
|
70
208
|
return out
|
|
71
209
|
|
|
72
210
|
|
|
211
|
+
def output_snapshot(output):
|
|
212
|
+
with output["lock"]:
|
|
213
|
+
threads = list(output["threads"])
|
|
214
|
+
for thread in threads:
|
|
215
|
+
thread.join(0.05)
|
|
216
|
+
with output["lock"]:
|
|
217
|
+
return "".join(output["stdout"]), "".join(output["stderr"])
|
|
218
|
+
|
|
219
|
+
|
|
73
220
|
def execute(req):
|
|
74
221
|
mode = req.get("mode") or "exec"
|
|
75
222
|
code = req.get("code") or ""
|
|
76
223
|
if mode == "auto":
|
|
77
224
|
mode = "exec"
|
|
78
|
-
|
|
79
|
-
|
|
225
|
+
output = {"stdout": [], "stderr": [], "threads": [], "lock": threading.Lock()}
|
|
226
|
+
token = output_context.set(output)
|
|
80
227
|
try:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
228
|
+
if mode == "eval":
|
|
229
|
+
value = eval(compile(code, "<pibo-runtime>", "eval"), user_globals, user_globals)
|
|
230
|
+
else:
|
|
231
|
+
exec(compile(code, "<pibo-runtime>", "exec"), user_globals, user_globals)
|
|
232
|
+
value = None
|
|
233
|
+
stdout, stderr = output_snapshot(output)
|
|
87
234
|
return {
|
|
88
235
|
"id": req.get("id"),
|
|
89
236
|
"status": "ok",
|
|
90
|
-
"stdout": stdout
|
|
91
|
-
"stderr": stderr
|
|
237
|
+
"stdout": stdout,
|
|
238
|
+
"stderr": stderr,
|
|
92
239
|
"result": summarize(value) if value is not None else None,
|
|
93
240
|
}
|
|
94
241
|
except KeyboardInterrupt as exc:
|
|
95
|
-
|
|
242
|
+
stdout, stderr = output_snapshot(output)
|
|
243
|
+
return {"id": req.get("id"), "status": "interrupted", "stdout": stdout, "stderr": stderr, "error": error_summary(exc)}
|
|
96
244
|
except Exception as exc:
|
|
97
|
-
|
|
245
|
+
stdout, stderr = output_snapshot(output)
|
|
246
|
+
return {"id": req.get("id"), "status": "error", "stdout": stdout, "stderr": stderr, "error": error_summary(exc)}
|
|
247
|
+
finally:
|
|
248
|
+
output_context.reset(token)
|
|
98
249
|
|
|
99
250
|
|
|
100
251
|
def inspect_value(req):
|
|
@@ -147,8 +298,8 @@ def list_vars(req):
|
|
|
147
298
|
|
|
148
299
|
|
|
149
300
|
def write_response(resp):
|
|
150
|
-
|
|
151
|
-
|
|
301
|
+
protocol_stream.write(json.dumps(resp, ensure_ascii=False) + "\n")
|
|
302
|
+
protocol_stream.flush()
|
|
152
303
|
|
|
153
304
|
|
|
154
305
|
def main():
|
|
@@ -106,12 +106,7 @@ export class RuntimeSessionRegistry {
|
|
|
106
106
|
result.executionCount = session.executionCount;
|
|
107
107
|
session.lastExecAt = startedAt;
|
|
108
108
|
session.updatedAt = nowIso();
|
|
109
|
-
|
|
110
|
-
session.status = "failed";
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
session.status = "idle";
|
|
114
|
-
}
|
|
109
|
+
session.status = session.backend.isAlive() ? "idle" : "failed";
|
|
115
110
|
this.appendHistory(session, {
|
|
116
111
|
id: randomUUID(),
|
|
117
112
|
startedAt,
|
|
@@ -131,6 +126,7 @@ export class RuntimeSessionRegistry {
|
|
|
131
126
|
if (!session)
|
|
132
127
|
return notFoundInspect(input.sessionId);
|
|
133
128
|
const result = await session.backend.inspect(input);
|
|
129
|
+
this.reconcileSession(session);
|
|
134
130
|
return { ...result, sessionId: session.sessionId };
|
|
135
131
|
}
|
|
136
132
|
async vars(controllerPiboSessionId, input) {
|
|
@@ -138,6 +134,7 @@ export class RuntimeSessionRegistry {
|
|
|
138
134
|
if (!session)
|
|
139
135
|
return notFoundVars(input.sessionId);
|
|
140
136
|
const result = await session.backend.vars(input);
|
|
137
|
+
this.reconcileSession(session);
|
|
141
138
|
return { ...result, sessionId: session.sessionId };
|
|
142
139
|
}
|
|
143
140
|
async interrupt(controllerPiboSessionId, input) {
|
|
@@ -145,7 +142,12 @@ export class RuntimeSessionRegistry {
|
|
|
145
142
|
const sessionId = input.sessionId ?? session?.sessionId ?? "auto";
|
|
146
143
|
if (!session)
|
|
147
144
|
return { status: "not_found", sessionId, message: `Runtime session "${sessionId}" was not found.` };
|
|
145
|
+
if (session.status === "closed" || session.status === "failed")
|
|
146
|
+
return { status: "failed", sessionId, message: "Runtime worker is not alive" };
|
|
147
|
+
if (session.status !== "busy")
|
|
148
|
+
return { status: "ok", sessionId, message: "Runtime session is idle; no active execution to interrupt" };
|
|
148
149
|
const result = await session.backend.interrupt();
|
|
150
|
+
this.reconcileSession(session);
|
|
149
151
|
return { ...result, sessionId: session.sessionId };
|
|
150
152
|
}
|
|
151
153
|
async close(controllerPiboSessionId, input) {
|
|
@@ -176,6 +178,7 @@ export class RuntimeSessionRegistry {
|
|
|
176
178
|
status: "ok",
|
|
177
179
|
sessions: [...this.sessions.values()]
|
|
178
180
|
.filter((session) => session.controllerPiboSessionId === controllerPiboSessionId)
|
|
181
|
+
.map((session) => this.reconcileSession(session))
|
|
179
182
|
.map(toRecord),
|
|
180
183
|
};
|
|
181
184
|
}
|
|
@@ -189,6 +192,7 @@ export class RuntimeSessionRegistry {
|
|
|
189
192
|
}
|
|
190
193
|
pruneIdle(now = Date.now(), idleTimeoutMs = 30 * 60 * 1000) {
|
|
191
194
|
for (const session of this.sessions.values()) {
|
|
195
|
+
this.reconcileSession(session);
|
|
192
196
|
if (session.status !== "idle")
|
|
193
197
|
continue;
|
|
194
198
|
if (now - Date.parse(session.updatedAt) > idleTimeoutMs) {
|
|
@@ -208,10 +212,12 @@ export class RuntimeSessionRegistry {
|
|
|
208
212
|
};
|
|
209
213
|
}
|
|
210
214
|
getDefault(controllerPiboSessionId, runtime) {
|
|
211
|
-
return [...this.sessions.values()].find((session) =>
|
|
212
|
-
session.runtime
|
|
213
|
-
|
|
214
|
-
|
|
215
|
+
return [...this.sessions.values()].find((session) => {
|
|
216
|
+
if (session.controllerPiboSessionId !== controllerPiboSessionId || session.runtime !== runtime)
|
|
217
|
+
return false;
|
|
218
|
+
const status = this.reconcileSession(session).status;
|
|
219
|
+
return status !== "closed" && status !== "failed";
|
|
220
|
+
});
|
|
215
221
|
}
|
|
216
222
|
async getOrStartDefault(controllerPiboSessionId, input) {
|
|
217
223
|
const runtime = input.runtime ?? "python";
|
|
@@ -230,6 +236,13 @@ export class RuntimeSessionRegistry {
|
|
|
230
236
|
const session = this.sessions.get(sessionId);
|
|
231
237
|
if (!session || session.controllerPiboSessionId !== controllerPiboSessionId)
|
|
232
238
|
return undefined;
|
|
239
|
+
return this.reconcileSession(session);
|
|
240
|
+
}
|
|
241
|
+
reconcileSession(session) {
|
|
242
|
+
if (session.status !== "closed" && session.status !== "failed" && !session.backend.isAlive()) {
|
|
243
|
+
session.status = "failed";
|
|
244
|
+
session.updatedAt = nowIso();
|
|
245
|
+
}
|
|
233
246
|
return session;
|
|
234
247
|
}
|
|
235
248
|
appendHistory(session, entry) {
|
|
@@ -148,7 +148,6 @@ export function updateUserSkill(id, input, cwd = process.cwd()) {
|
|
|
148
148
|
writeFileSync(skillPath, markdown, "utf-8");
|
|
149
149
|
}
|
|
150
150
|
else if (name !== existing.name || description !== parsed.description) {
|
|
151
|
-
const currentMarkdown = existsSync(existing.path) ? readFileSync(existing.path, "utf-8") : "";
|
|
152
151
|
const { body } = parseSkillMd(currentMarkdown);
|
|
153
152
|
writeFileSync(skillPath, buildSkillMd(name, description, body), "utf-8");
|
|
154
153
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pasko70/pibo",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@pasko70/pibo",
|
|
9
|
-
"version": "3.
|
|
9
|
+
"version": "3.1.1",
|
|
10
10
|
"workspaces": [
|
|
11
11
|
"packages/workflows"
|
|
12
12
|
],
|
|
13
13
|
"dependencies": {
|
|
14
|
+
"@balena/dockerignore": "1.0.2",
|
|
14
15
|
"@earendil-works/pi-agent-core": "0.84.2",
|
|
15
16
|
"@earendil-works/pi-ai": "0.84.2",
|
|
16
17
|
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
@@ -968,6 +969,12 @@
|
|
|
968
969
|
"node": ">=6.9.0"
|
|
969
970
|
}
|
|
970
971
|
},
|
|
972
|
+
"node_modules/@balena/dockerignore": {
|
|
973
|
+
"version": "1.0.2",
|
|
974
|
+
"resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz",
|
|
975
|
+
"integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==",
|
|
976
|
+
"license": "Apache-2.0"
|
|
977
|
+
},
|
|
971
978
|
"node_modules/@better-auth/core": {
|
|
972
979
|
"version": "1.6.30",
|
|
973
980
|
"resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.6.30.tgz",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pasko70/pibo",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"packages/workflows"
|
|
@@ -14,7 +14,12 @@
|
|
|
14
14
|
"docs/ops/**",
|
|
15
15
|
"README.md",
|
|
16
16
|
"src/mcp/LICENSE.mcp-cli",
|
|
17
|
-
"npm-shrinkwrap.json"
|
|
17
|
+
"npm-shrinkwrap.json",
|
|
18
|
+
"compute-image/Dockerfile",
|
|
19
|
+
"compute-image/Dockerfile.dockerignore",
|
|
20
|
+
"scripts/docker-entrypoint.sh",
|
|
21
|
+
"scripts/prepare-browser-use-wrapper.sh",
|
|
22
|
+
"scripts/prepare-agent-browser-wrapper.sh"
|
|
18
23
|
],
|
|
19
24
|
"bin": {
|
|
20
25
|
"pibo": "dist/bin/pibo.js",
|
|
@@ -58,6 +63,7 @@
|
|
|
58
63
|
"postpack": "node scripts/package-shrinkwrap.mjs clean"
|
|
59
64
|
},
|
|
60
65
|
"dependencies": {
|
|
66
|
+
"@balena/dockerignore": "1.0.2",
|
|
61
67
|
"@earendil-works/pi-agent-core": "0.84.2",
|
|
62
68
|
"@earendil-works/pi-ai": "0.84.2",
|
|
63
69
|
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# Xvfb starten (virtueller Display-Server für Browser-Automation)
|
|
5
|
+
if ! pgrep -x Xvfb >/dev/null 2>&1; then
|
|
6
|
+
echo "[docker-entrypoint] Starting Xvfb on DISPLAY=:99 ..."
|
|
7
|
+
Xvfb :99 -screen 0 1920x1080x24 -ac -nolisten tcp &
|
|
8
|
+
sleep 0.5
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
export DISPLAY=:99
|
|
12
|
+
|
|
13
|
+
# Sicherstellen, dass die Browser-Wrapper existieren
|
|
14
|
+
if [ ! -x "$HOME/.pibo/tools/browser-use/home/bin/browser-use" ]; then
|
|
15
|
+
echo "[docker-entrypoint] Preparing browser-use wrapper ..."
|
|
16
|
+
/app/scripts/prepare-browser-use-wrapper.sh
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
if [ ! -x "$HOME/.pibo/tools/agent-browser/home/bin/agent-browser" ]; then
|
|
20
|
+
echo "[docker-entrypoint] Preparing agent-browser wrapper ..."
|
|
21
|
+
/app/scripts/prepare-agent-browser-wrapper.sh
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# PATH erweitern
|
|
25
|
+
export PATH="$HOME/.pibo/tools/agent-browser/home/bin:$HOME/.pibo/tools/agent-browser/node/node_modules/.bin:$HOME/.pibo/tools/browser-use/home/bin:$HOME/.pibo/tools/browser-use/.venv/bin:$PATH"
|
|
26
|
+
export BROWSER_USE_HOME="$HOME/.pibo/tools/browser-use/home"
|
|
27
|
+
export AGENT_BROWSER_HOME="$HOME/.pibo/tools/agent-browser/home"
|
|
28
|
+
|
|
29
|
+
# Pibo-CLI-Argumente verarbeiten
|
|
30
|
+
case "${1:-gateway}" in
|
|
31
|
+
gateway)
|
|
32
|
+
echo "[docker-entrypoint] Starting Pibo gateway on 0.0.0.0:4789 ..."
|
|
33
|
+
exec node -e "import('./dist/gateway/server.js').then(m => m.runGatewayServer({ host: '0.0.0.0' }))"
|
|
34
|
+
;;
|
|
35
|
+
gateway:web)
|
|
36
|
+
echo "[docker-entrypoint] Starting Pibo gateway:web with local auth on 0.0.0.0:4789 ..."
|
|
37
|
+
exec node -e "import('./dist/gateway/web.js').then(m => m.runWebGatewayServer({ authMode: 'local', web: { host: '0.0.0.0' } }))"
|
|
38
|
+
;;
|
|
39
|
+
shell|bash|sh)
|
|
40
|
+
exec /bin/sh
|
|
41
|
+
;;
|
|
42
|
+
*)
|
|
43
|
+
# Alles andere direkt an Pibo weiterleiten
|
|
44
|
+
exec node dist/bin/pibo.js "$@"
|
|
45
|
+
;;
|
|
46
|
+
esac
|