@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
|
@@ -68,6 +68,28 @@ async function withRegistryLock(status, action) {
|
|
|
68
68
|
function isExpired(lease, now = new Date()) {
|
|
69
69
|
return new Date(lease.expiresAt).getTime() <= now.getTime();
|
|
70
70
|
}
|
|
71
|
+
function leaseTimestamp(lease) {
|
|
72
|
+
const updatedAt = Date.parse(lease.updatedAt);
|
|
73
|
+
if (Number.isFinite(updatedAt))
|
|
74
|
+
return updatedAt;
|
|
75
|
+
const createdAt = Date.parse(lease.createdAt);
|
|
76
|
+
return Number.isFinite(createdAt) ? createdAt : 0;
|
|
77
|
+
}
|
|
78
|
+
function deduplicateLeaseIdentities(registry) {
|
|
79
|
+
const leasesById = new Map();
|
|
80
|
+
for (const lease of registry.leases) {
|
|
81
|
+
const existing = leasesById.get(lease.id);
|
|
82
|
+
if (!existing) {
|
|
83
|
+
leasesById.set(lease.id, lease);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const preferLease = lease.status === 'active' && existing.status !== 'active'
|
|
87
|
+
|| lease.status === existing.status && leaseTimestamp(lease) > leaseTimestamp(existing);
|
|
88
|
+
if (preferLease)
|
|
89
|
+
leasesById.set(lease.id, lease);
|
|
90
|
+
}
|
|
91
|
+
registry.leases = [...leasesById.values()];
|
|
92
|
+
}
|
|
71
93
|
function copyFilter(source) {
|
|
72
94
|
const base = source.split(/[\\/]/).pop() || '';
|
|
73
95
|
return ![
|
|
@@ -114,6 +136,7 @@ export async function acquireAgentBrowserLease(status, options) {
|
|
|
114
136
|
const maxSlots = options.maxSlots ?? 4;
|
|
115
137
|
const result = await withRegistryLock(status, async () => {
|
|
116
138
|
const registry = readRegistry(status);
|
|
139
|
+
deduplicateLeaseIdentities(registry);
|
|
117
140
|
const now = new Date();
|
|
118
141
|
for (const lease of registry.leases) {
|
|
119
142
|
if (lease.status === 'active' && isExpired(lease, now)) {
|
|
@@ -131,34 +154,44 @@ export async function acquireAgentBrowserLease(status, options) {
|
|
|
131
154
|
suggestion: 'Release a lease or rerun with a higher --max-slots value.',
|
|
132
155
|
}));
|
|
133
156
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
status
|
|
149
|
-
createdAt
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
let lease = registry.leases.find((item) => item.app === app && item.status === 'released');
|
|
158
|
+
if (lease) {
|
|
159
|
+
lease.holder = holder;
|
|
160
|
+
lease.status = 'active';
|
|
161
|
+
lease.updatedAt = nowIso();
|
|
162
|
+
lease.expiresAt = new Date(now.getTime() + ttlMs).toISOString();
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const used = new Set(active.map((item) => item.slot));
|
|
166
|
+
let slotNumber = 1;
|
|
167
|
+
while (used.has(`slot-${String(slotNumber).padStart(3, '0')}`))
|
|
168
|
+
slotNumber += 1;
|
|
169
|
+
const slot = `slot-${String(slotNumber).padStart(3, '0')}`;
|
|
170
|
+
const sessionName = `pibo-auth-${app}-${slot}`;
|
|
171
|
+
const profileDir = leaseProfileDir(status, app, slot);
|
|
172
|
+
const createdAt = nowIso();
|
|
173
|
+
lease = {
|
|
174
|
+
id: `${app}-${slot}`,
|
|
175
|
+
app,
|
|
176
|
+
holder,
|
|
177
|
+
slot,
|
|
178
|
+
sessionName,
|
|
179
|
+
profileDir,
|
|
180
|
+
status: 'active',
|
|
181
|
+
createdAt,
|
|
182
|
+
updatedAt: createdAt,
|
|
183
|
+
expiresAt: new Date(now.getTime() + ttlMs).toISOString(),
|
|
184
|
+
};
|
|
185
|
+
registry.leases.push(lease);
|
|
186
|
+
}
|
|
187
|
+
await rm(lease.profileDir, { recursive: true, force: true });
|
|
154
188
|
const sourceTemplate = templateDir(status, app);
|
|
155
189
|
if (existsSync(sourceTemplate)) {
|
|
156
|
-
await cp(sourceTemplate, profileDir, { recursive: true, force: true, filter: copyFilter });
|
|
190
|
+
await cp(sourceTemplate, lease.profileDir, { recursive: true, force: true, filter: copyFilter });
|
|
157
191
|
}
|
|
158
192
|
else {
|
|
159
|
-
await mkdir(profileDir, { recursive: true });
|
|
193
|
+
await mkdir(lease.profileDir, { recursive: true });
|
|
160
194
|
}
|
|
161
|
-
registry.leases.push(lease);
|
|
162
195
|
await writeRegistry(status, registry);
|
|
163
196
|
return lease;
|
|
164
197
|
});
|
|
@@ -187,6 +220,7 @@ export async function listAgentBrowserLeases(status, json = false) {
|
|
|
187
220
|
export async function releaseAgentBrowserLease(status, id, options) {
|
|
188
221
|
await withRegistryLock(status, async () => {
|
|
189
222
|
const registry = readRegistry(status);
|
|
223
|
+
deduplicateLeaseIdentities(registry);
|
|
190
224
|
const lease = registry.leases.find((item) => item.id === id);
|
|
191
225
|
if (!lease)
|
|
192
226
|
throw new Error(`Agent Browser lease not found: ${id}`);
|
|
@@ -202,6 +236,7 @@ export async function reapStaleAgentBrowserLeases(status, json = false) {
|
|
|
202
236
|
let released = 0;
|
|
203
237
|
await withRegistryLock(status, async () => {
|
|
204
238
|
const registry = readRegistry(status);
|
|
239
|
+
deduplicateLeaseIdentities(registry);
|
|
205
240
|
const now = new Date();
|
|
206
241
|
for (const lease of registry.leases) {
|
|
207
242
|
if (lease.status === 'active' && isExpired(lease, now)) {
|
|
@@ -89,7 +89,7 @@ exec "$real_agent_browser" "\${args[@]}"
|
|
|
89
89
|
`;
|
|
90
90
|
}
|
|
91
91
|
export function ensureAgentBrowserWrapper(status) {
|
|
92
|
-
if (!status.homeDir || !status.executablePath)
|
|
92
|
+
if (!status.installed || !status.homeDir || !status.executablePath)
|
|
93
93
|
return undefined;
|
|
94
94
|
const wrapperPath = join(status.homeDir, 'bin', 'agent-browser');
|
|
95
95
|
mkdirSync(dirname(wrapperPath), { recursive: true });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
-
import { cp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { cp, mkdir, rm, stat, writeFile } from 'node:fs/promises';
|
|
4
5
|
import { join, resolve } from 'node:path';
|
|
5
6
|
import { ErrorCode, formatCliError } from '../cli-errors.js';
|
|
6
7
|
import { BROWSER_USE_DEFAULT_PROFILE } from './browser-use-wrapper.js';
|
|
@@ -10,6 +11,8 @@ const DEFAULT_APP = 'pibo-chat';
|
|
|
10
11
|
const DEFAULT_TTL_MINUTES = 8 * 60;
|
|
11
12
|
const DEFAULT_TEMPLATE_PROFILE = 'pibo-auth-template';
|
|
12
13
|
const AUTH_POOL_DIR = 'auth-pool';
|
|
14
|
+
const REGISTRY_LOCK_OWNER_FILE = 'owner.json';
|
|
15
|
+
const LEGACY_REGISTRY_LOCK_STALE_MS = 10 * 60_000;
|
|
13
16
|
function shellQuote(value) {
|
|
14
17
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
15
18
|
}
|
|
@@ -32,6 +35,9 @@ function registryPath(status) {
|
|
|
32
35
|
function lockDir(status) {
|
|
33
36
|
return join(status.homeDir, AUTH_POOL_DIR, '.leases.lock');
|
|
34
37
|
}
|
|
38
|
+
function lockOwnerPath(lock) {
|
|
39
|
+
return join(lock, REGISTRY_LOCK_OWNER_FILE);
|
|
40
|
+
}
|
|
35
41
|
function appPoolDir(status, app) {
|
|
36
42
|
return join(status.homeDir, AUTH_POOL_DIR, sanitizeIdPart(app));
|
|
37
43
|
}
|
|
@@ -110,29 +116,135 @@ async function writeRegistry(status, registry) {
|
|
|
110
116
|
async function withRegistryLock(status, action) {
|
|
111
117
|
const lock = lockDir(status);
|
|
112
118
|
await mkdir(join(status.homeDir, AUTH_POOL_DIR), { recursive: true });
|
|
113
|
-
|
|
119
|
+
const token = randomUUID();
|
|
114
120
|
for (let attempt = 0; attempt < 50; attempt += 1) {
|
|
115
121
|
try {
|
|
116
122
|
await mkdir(lock);
|
|
117
|
-
|
|
123
|
+
const processStartId = currentProcessStartId();
|
|
124
|
+
try {
|
|
125
|
+
await writeFile(lockOwnerPath(lock), `${JSON.stringify({
|
|
126
|
+
pid: process.pid,
|
|
127
|
+
token,
|
|
128
|
+
acquiredAt: Date.now(),
|
|
129
|
+
...(processStartId ? { processStartId } : {}),
|
|
130
|
+
})}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
await rm(lock, { recursive: true, force: true });
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
118
136
|
break;
|
|
119
137
|
}
|
|
120
138
|
catch (error) {
|
|
121
139
|
if (error.code !== 'EEXIST')
|
|
122
140
|
throw error;
|
|
141
|
+
await recoverStaleRegistryLock(lock);
|
|
142
|
+
if (!existsSync(lock))
|
|
143
|
+
continue;
|
|
123
144
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
124
145
|
}
|
|
125
146
|
}
|
|
126
|
-
if (!
|
|
147
|
+
if (!await registryLockOwnedBy(lock, token)) {
|
|
127
148
|
throw new Error('Timed out waiting for browser-use lease registry lock');
|
|
128
149
|
}
|
|
129
150
|
try {
|
|
130
151
|
return await action();
|
|
131
152
|
}
|
|
132
153
|
finally {
|
|
154
|
+
await releaseRegistryLock(lock, token);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function currentProcessStartId() {
|
|
158
|
+
if (process.platform !== 'linux')
|
|
159
|
+
return undefined;
|
|
160
|
+
const identity = linuxProcessIdentity(process.pid);
|
|
161
|
+
return identity.liveness === 'active' ? identity.startId : undefined;
|
|
162
|
+
}
|
|
163
|
+
function linuxProcessIdentity(pid) {
|
|
164
|
+
let raw;
|
|
165
|
+
try {
|
|
166
|
+
raw = readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
const code = error.code;
|
|
170
|
+
return code === 'ENOENT' || code === 'ESRCH' ? { liveness: 'dead' } : { liveness: 'ambiguous' };
|
|
171
|
+
}
|
|
172
|
+
const commandEnd = raw.lastIndexOf(')');
|
|
173
|
+
if (commandEnd < 2 || commandEnd + 2 >= raw.length)
|
|
174
|
+
return { liveness: 'ambiguous' };
|
|
175
|
+
const fields = raw.slice(commandEnd + 2).trim().split(/\s+/);
|
|
176
|
+
if (fields[0] === 'Z')
|
|
177
|
+
return { liveness: 'dead' };
|
|
178
|
+
const startTicks = fields[19];
|
|
179
|
+
if (!startTicks || !/^[1-9][0-9]*$/.test(startTicks))
|
|
180
|
+
return { liveness: 'ambiguous' };
|
|
181
|
+
return { liveness: 'active', startId: `${pid}:${startTicks}` };
|
|
182
|
+
}
|
|
183
|
+
function validRegistryLockOwner(value) {
|
|
184
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
185
|
+
return undefined;
|
|
186
|
+
const owner = value;
|
|
187
|
+
if (!Number.isSafeInteger(owner.pid)
|
|
188
|
+
|| (owner.pid ?? 0) <= 0
|
|
189
|
+
|| typeof owner.token !== 'string'
|
|
190
|
+
|| owner.token.length === 0
|
|
191
|
+
|| !Number.isFinite(owner.acquiredAt)
|
|
192
|
+
|| (owner.processStartId !== undefined && typeof owner.processStartId !== 'string'))
|
|
193
|
+
return undefined;
|
|
194
|
+
return owner;
|
|
195
|
+
}
|
|
196
|
+
function readRegistryLockOwner(lock) {
|
|
197
|
+
try {
|
|
198
|
+
return validRegistryLockOwner(JSON.parse(readFileSync(lockOwnerPath(lock), 'utf8')));
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function registryLockOwnerLiveness(owner) {
|
|
205
|
+
if (process.platform === 'linux') {
|
|
206
|
+
const identity = linuxProcessIdentity(owner.pid);
|
|
207
|
+
if (identity.liveness !== 'active')
|
|
208
|
+
return identity.liveness;
|
|
209
|
+
if (!owner.processStartId || !identity.startId)
|
|
210
|
+
return 'ambiguous';
|
|
211
|
+
return owner.processStartId === identity.startId ? 'active' : 'dead';
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
process.kill(owner.pid, 0);
|
|
215
|
+
return 'active';
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
return error.code === 'ESRCH' ? 'dead' : 'ambiguous';
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async function registryLockOwnedBy(lock, token) {
|
|
222
|
+
return readRegistryLockOwner(lock)?.token === token;
|
|
223
|
+
}
|
|
224
|
+
async function releaseRegistryLock(lock, token) {
|
|
225
|
+
if (await registryLockOwnedBy(lock, token)) {
|
|
133
226
|
await rm(lock, { recursive: true, force: true });
|
|
134
227
|
}
|
|
135
228
|
}
|
|
229
|
+
async function recoverStaleRegistryLock(lock) {
|
|
230
|
+
const owner = readRegistryLockOwner(lock);
|
|
231
|
+
if (owner) {
|
|
232
|
+
if (registryLockOwnerLiveness(owner) === 'dead' && readRegistryLockOwner(lock)?.token === owner.token) {
|
|
233
|
+
await rm(lock, { recursive: true, force: true });
|
|
234
|
+
}
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
const lockStat = await stat(lock);
|
|
239
|
+
if (Date.now() - lockStat.mtimeMs > LEGACY_REGISTRY_LOCK_STALE_MS) {
|
|
240
|
+
await rm(lock, { recursive: true, force: true });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
if (error.code !== 'ENOENT')
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
136
248
|
export function isExpired(lease, now = new Date()) {
|
|
137
249
|
return new Date(lease.expiresAt).getTime() <= now.getTime();
|
|
138
250
|
}
|
|
@@ -346,7 +458,7 @@ export async function acquireBrowserUseLease(context, options = {}) {
|
|
|
346
458
|
const ttlMinutes = options.ttlMinutes ?? DEFAULT_TTL_MINUTES;
|
|
347
459
|
const profileName = options.profileName?.trim() || BROWSER_USE_DEFAULT_PROFILE;
|
|
348
460
|
const templateDir = options.templateDir?.trim() || defaultTemplateDir(context.status);
|
|
349
|
-
await withRegistryLock(context.status, async () => {
|
|
461
|
+
const { lease, reapedCount } = await withRegistryLock(context.status, async () => {
|
|
350
462
|
const registry = readRegistry(context.status);
|
|
351
463
|
const reapedCount = await reapStaleLeasesInRegistry(context.status, registry);
|
|
352
464
|
let lease = selectReusableLease(context.status, registry.leases, app);
|
|
@@ -380,18 +492,19 @@ export async function acquireBrowserUseLease(context, options = {}) {
|
|
|
380
492
|
registry.leases.push(lease);
|
|
381
493
|
}
|
|
382
494
|
await writeRegistry(context.status, registry);
|
|
383
|
-
|
|
384
|
-
console.error(`Reaped ${reapedCount} stale lease${reapedCount === 1 ? '' : 's'}`);
|
|
385
|
-
}
|
|
386
|
-
if (options.json)
|
|
387
|
-
printLeaseJson(context.status, lease);
|
|
388
|
-
else
|
|
389
|
-
printLeaseEnv(context.status, lease);
|
|
390
|
-
const warmup = await warmupBrowserUseLease(context, lease, 15000, options.headed);
|
|
391
|
-
if (!warmup.success && !options.json) {
|
|
392
|
-
console.error(`Warning: Browser warm-up failed: ${warmup.error?.trim() || 'unknown error'}`);
|
|
393
|
-
}
|
|
495
|
+
return { lease, reapedCount };
|
|
394
496
|
});
|
|
497
|
+
if (reapedCount > 0 && !options.json) {
|
|
498
|
+
console.error(`Reaped ${reapedCount} stale lease${reapedCount === 1 ? '' : 's'}`);
|
|
499
|
+
}
|
|
500
|
+
if (options.json)
|
|
501
|
+
printLeaseJson(context.status, lease);
|
|
502
|
+
else
|
|
503
|
+
printLeaseEnv(context.status, lease);
|
|
504
|
+
const warmup = await warmupBrowserUseLease(context, lease, 15000, options.headed);
|
|
505
|
+
if (!warmup.success && !options.json) {
|
|
506
|
+
console.error(`Warning: Browser warm-up failed: ${warmup.error?.trim() || 'unknown error'}`);
|
|
507
|
+
}
|
|
395
508
|
}
|
|
396
509
|
async function warmupBrowserUseLease(context, lease, timeoutMs = 15000, headed = false) {
|
|
397
510
|
const wrapperPath = join(context.status.homeDir, 'bin', 'browser-use');
|
|
@@ -771,7 +771,7 @@ exec "$real_browser_use" "$@"
|
|
|
771
771
|
`;
|
|
772
772
|
}
|
|
773
773
|
export function ensureBrowserUseWrapper(status) {
|
|
774
|
-
if (status.entry.name !== 'browser-use')
|
|
774
|
+
if (!status.installed || status.entry.name !== 'browser-use')
|
|
775
775
|
return undefined;
|
|
776
776
|
const wrapperPath = join(status.homeDir, 'bin', 'browser-use');
|
|
777
777
|
mkdirSync(dirname(wrapperPath), { recursive: true });
|
package/dist/tools/index.js
CHANGED
|
@@ -60,17 +60,21 @@ function printShow(name) {
|
|
|
60
60
|
const wrapperPath = ensureBrowserUseWrapper(status);
|
|
61
61
|
console.log(` wrapper: ${wrapperPath ?? 'not generated'}`);
|
|
62
62
|
console.log(` executable: ${status.executablePath}`);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
if (wrapperPath) {
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log(' IMPORTANT: Always use the wrapper path above, not the raw executable.');
|
|
66
|
+
console.log(' The wrapper manages persistent Chrome profiles and CDP automatically.');
|
|
67
|
+
}
|
|
66
68
|
}
|
|
67
69
|
else if (entry.name === 'agent-browser') {
|
|
68
70
|
const wrapperPath = ensureAgentBrowserWrapper(status);
|
|
69
71
|
console.log(` wrapper: ${wrapperPath ?? 'not generated'}`);
|
|
70
72
|
console.log(` executable: ${status.executablePath}`);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
if (wrapperPath) {
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log(' IMPORTANT: Always use the wrapper path above, not the raw executable.');
|
|
76
|
+
console.log(' The wrapper keeps Agent Browser state under the Pibo tool home.');
|
|
77
|
+
}
|
|
74
78
|
}
|
|
75
79
|
else {
|
|
76
80
|
console.log(` executable: ${status.executablePath}`);
|
|
@@ -88,8 +92,9 @@ function printShow(name) {
|
|
|
88
92
|
}
|
|
89
93
|
console.log('');
|
|
90
94
|
console.log('Next:');
|
|
91
|
-
if (entry.kind !== 'internal')
|
|
92
|
-
console.log(` pibo tools env ${entry.name}`);
|
|
95
|
+
if (entry.kind !== 'internal') {
|
|
96
|
+
console.log(status.installed ? ` pibo tools env ${entry.name}` : ` pibo tools install ${entry.name}`);
|
|
97
|
+
}
|
|
93
98
|
console.log(` pibo tools guide ${entry.name} ${entry.guides[0]?.name ?? ''}`.trimEnd());
|
|
94
99
|
if (entry.name === 'browser-use') {
|
|
95
100
|
console.log(' pibo tools browser-use');
|
|
@@ -133,9 +138,21 @@ function printGuide(name, guideName) {
|
|
|
133
138
|
}
|
|
134
139
|
console.log(guide.content);
|
|
135
140
|
}
|
|
136
|
-
function
|
|
141
|
+
function requireInstalledTool(name) {
|
|
137
142
|
const entry = requireEntry(name);
|
|
138
143
|
const status = getCliToolStatus(entry);
|
|
144
|
+
if (!status.installed) {
|
|
145
|
+
throw new Error(formatCliError({
|
|
146
|
+
code: ErrorCode.CLIENT_ERROR,
|
|
147
|
+
type: 'CLI_TOOL_NOT_INSTALLED',
|
|
148
|
+
message: `Tool "${name}" is not installed`,
|
|
149
|
+
suggestion: `Run pibo tools install ${name}.`,
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
return { entry, status };
|
|
153
|
+
}
|
|
154
|
+
function printPath(name) {
|
|
155
|
+
const { entry, status } = requireInstalledTool(name);
|
|
139
156
|
if (entry.name === 'browser-use') {
|
|
140
157
|
const wrapperPath = ensureBrowserUseWrapper(status);
|
|
141
158
|
if (wrapperPath) {
|
|
@@ -153,8 +170,7 @@ function printPath(name) {
|
|
|
153
170
|
console.log(status.executablePath);
|
|
154
171
|
}
|
|
155
172
|
function printEnv(name) {
|
|
156
|
-
const entry =
|
|
157
|
-
const status = getCliToolStatus(entry);
|
|
173
|
+
const { entry, status } = requireInstalledTool(name);
|
|
158
174
|
const desktop = detectDesktopEnv();
|
|
159
175
|
if (entry.kind === 'internal') {
|
|
160
176
|
console.log(`# ${entry.name} is built into pibo; no extra environment is required.`);
|
|
@@ -52,7 +52,7 @@ function runBuffered(command, args, env = process.env) {
|
|
|
52
52
|
function isRootUser() {
|
|
53
53
|
return typeof process.getuid === 'function' && process.getuid() === 0;
|
|
54
54
|
}
|
|
55
|
-
async function
|
|
55
|
+
async function findUvCommand() {
|
|
56
56
|
const uv = await runBuffered('uv', ['--version']);
|
|
57
57
|
if (uv.ok)
|
|
58
58
|
return 'uv';
|
|
@@ -62,7 +62,14 @@ async function resolveUvCommand() {
|
|
|
62
62
|
if (local.ok)
|
|
63
63
|
return localUv;
|
|
64
64
|
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
async function ensureUvCommand() {
|
|
68
|
+
const uvCommand = await findUvCommand();
|
|
69
|
+
if (uvCommand)
|
|
70
|
+
return uvCommand;
|
|
65
71
|
if (process.platform === 'linux' && isRootUser()) {
|
|
72
|
+
const localUv = join(homedir(), '.local', 'bin', 'uv');
|
|
66
73
|
console.log('uv was not found on PATH. Installing uv automatically.');
|
|
67
74
|
await runInheritedCommand('sh', ['-c', 'curl -LsSf https://astral.sh/uv/install.sh | sh']);
|
|
68
75
|
const installed = await runBuffered(localUv, ['--version'], {
|
|
@@ -107,7 +114,7 @@ export function runInheritedCommand(command, args, env = process.env) {
|
|
|
107
114
|
}
|
|
108
115
|
export async function printToolPythonRuntimeDoctor(name, spec) {
|
|
109
116
|
const paths = getToolPythonRuntimePaths(name, spec);
|
|
110
|
-
const uvCommand = await
|
|
117
|
+
const uvCommand = await findUvCommand();
|
|
111
118
|
const uv = uvCommand ? await runBuffered(uvCommand, ['--version']) : { ok: false, output: 'missing' };
|
|
112
119
|
const python = uv.ok
|
|
113
120
|
? await runBuffered(uvCommand, ['python', 'find', spec.pythonVersion])
|
|
@@ -148,7 +155,7 @@ export async function printToolPythonRuntimeDoctor(name, spec) {
|
|
|
148
155
|
}
|
|
149
156
|
}
|
|
150
157
|
export async function installToolPythonRuntime(name, spec) {
|
|
151
|
-
const uvCommand = await
|
|
158
|
+
const uvCommand = await ensureUvCommand();
|
|
152
159
|
if (!uvCommand) {
|
|
153
160
|
throw new Error(formatCliError({
|
|
154
161
|
code: ErrorCode.CLIENT_ERROR,
|
package/dist/tools/registry.js
CHANGED
|
@@ -220,7 +220,7 @@ export async function installCliTool(entry, runSetup) {
|
|
|
220
220
|
if ((entry.name === 'browser-use' || entry.name === 'agent-browser') && process.platform === 'linux' && !hasDesktopDisplay(detectDesktopEnv())) {
|
|
221
221
|
printLinuxVirtualDisplayHint(' ');
|
|
222
222
|
}
|
|
223
|
-
console.log(` env: pibo tools env ${entry.name}`);
|
|
223
|
+
console.log(status.installed ? ` env: pibo tools env ${entry.name}` : ` install: pibo tools install ${entry.name}`);
|
|
224
224
|
}
|
|
225
225
|
export async function removeCliTool(entry) {
|
|
226
226
|
if (entry.kind === 'internal') {
|
|
@@ -46,13 +46,23 @@ export class NodeRuntimeBackend {
|
|
|
46
46
|
this.child = spawn(executable, [...args, "-e", NODE_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("Node 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 NodeRuntimeBackend {
|
|
|
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 };
|
|
@@ -146,7 +156,8 @@ export class NodeRuntimeBackend {
|
|
|
146
156
|
async interrupt() {
|
|
147
157
|
if (!this.isAlive())
|
|
148
158
|
return { status: "failed", sessionId: "", message: "Runtime worker is not alive" };
|
|
149
|
-
this.child.kill("SIGINT")
|
|
159
|
+
if (!this.child.kill("SIGINT"))
|
|
160
|
+
return { status: "failed", sessionId: "", message: "Runtime worker is not alive" };
|
|
150
161
|
return { status: "ok", sessionId: "", message: "Sent SIGINT to runtime worker" };
|
|
151
162
|
}
|
|
152
163
|
async close(force = false) {
|