@pasko70/pibo 1.10.0 → 1.10.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/dist/apps/chat/chat-request-normalizers.js +7 -0
- package/dist/apps/chat/data/chat-data-mappers.js +2 -0
- package/dist/apps/chat/loop-api.js +11 -6
- package/dist/apps/chat/web-app.js +30 -10
- package/dist/apps/chat-ui/assets/{dist-LHRs1Nhr.js → dist-3Sts0Afa.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-wE9nop9V.js → dist-4NlLjLs7.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BwKObYnX.js → dist-BA6mhAec.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Y-AA2omI.js → dist-BPl-fzRB.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-GdEM8UW1.js → dist-BPnn9e2B.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DlATLa-U.js → dist-BpfBSMzK.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CS7wdk0Z.js → dist-Cvx5M97R.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-nOLTkZrJ.js → dist-DnXWNQRm.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DUlaXAk7.js → dist-hU-2oAb6.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CKtT8YGm.js → dist-r31x5Fcc.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D-cxLQO1.js → dist-uF6UXzI7.js} +1 -1
- package/dist/apps/chat-ui/assets/index-CaTGYBOS.js +173 -0
- package/dist/apps/chat-ui/assets/{index-C0x9nEcf.css → index-al8DeEjA.css} +1 -1
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-CK4SMZuu.js +41 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/cli-session/localSessionSource.js +34 -12
- package/dist/compute/resource-health.js +35 -2
- package/dist/core/events.js +6 -1
- package/dist/core/routed-session.js +67 -4
- package/dist/core/session-router.js +23 -6
- package/dist/data/ingest-service.js +3 -1
- package/dist/debug/index.js +4 -0
- package/dist/debug/trace.js +42 -7
- package/dist/index.js +1 -0
- package/dist/loops/accounting.js +19 -0
- package/dist/loops/cli.js +22 -6
- package/dist/loops/prompts.js +4 -1
- package/dist/loops/service.js +97 -8
- package/dist/loops/store.js +128 -17
- package/dist/loops/tools.js +24 -7
- package/dist/reliability/store.js +34 -7
- package/dist/runs/lifecycle.js +59 -0
- package/dist/runs/registry.js +47 -1
- package/dist/runs/tools.js +31 -13
- package/dist/session-ui/terminalRows.js +66 -2
- package/dist/shared/trace-async-agent-runs.js +4 -4
- package/dist/shared/trace-event-projection.js +59 -19
- package/dist/shared/trace-nodes.js +5 -0
- package/dist/shared/trace-page-merge.js +15 -0
- package/dist/shared/trace-run-notifications.js +3 -1
- package/dist/signals/projector.js +6 -2
- package/dist/tools/browser-pool.js +50 -0
- package/dist/tools/browser-use-leases.js +12 -8
- package/dist/tools/guides.js +8 -1
- package/dist/tools/index.js +1 -0
- package/package.json +2 -1
- package/skills/builtin/loop/SKILL.md +12 -3
- package/dist/apps/chat-ui/assets/index-8W_yMHQI.js +0 -173
- package/dist/apps/chat-vscode-web/assets/index-BAMxIaI_.js +0 -41
|
@@ -23,6 +23,21 @@ export function mergeOlderTracePage(current, older) {
|
|
|
23
23
|
eventLimit: (current.eventLimit ?? 0) + (older.eventLimit ?? older.pageSize ?? 0),
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
+
export function mergeRefreshedTracePage(current, refreshed) {
|
|
27
|
+
if (current.piboSessionId !== refreshed.piboSessionId)
|
|
28
|
+
return refreshed;
|
|
29
|
+
return {
|
|
30
|
+
...refreshed,
|
|
31
|
+
nodes: mergeTraceNodes(current.nodes, refreshed.nodes),
|
|
32
|
+
rawEvents: current.rawEvents.length ? current.rawEvents : refreshed.rawEvents,
|
|
33
|
+
beforeCursor: current.beforeCursor,
|
|
34
|
+
firstEventSequence: current.firstEventSequence ?? refreshed.firstEventSequence,
|
|
35
|
+
nextBeforeSequence: current.nextBeforeSequence,
|
|
36
|
+
nextBeforeCursor: current.nextBeforeCursor,
|
|
37
|
+
hasOlderEvents: current.hasOlderEvents,
|
|
38
|
+
eventLimit: current.eventLimit ?? refreshed.eventLimit,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
26
41
|
function mergeTraceNodes(olderNodes, currentNodes) {
|
|
27
42
|
const byId = new Map();
|
|
28
43
|
for (const node of flattenTraceNodes([...olderNodes])) {
|
|
@@ -19,7 +19,7 @@ export function parseRunNotificationText(text) {
|
|
|
19
19
|
export function createRunNotificationNode(input) {
|
|
20
20
|
const runs = runNotificationRuns(input.notification);
|
|
21
21
|
const singleRun = runs.length === 1 ? runs[0] : undefined;
|
|
22
|
-
const failedCount = countRunGroup(input.notification.failed);
|
|
22
|
+
const failedCount = countRunGroup(input.notification.failed) + countRunGroup(input.notification.timedOut);
|
|
23
23
|
const runningCount = countRunGroup(input.notification.running);
|
|
24
24
|
return {
|
|
25
25
|
id: input.id,
|
|
@@ -43,6 +43,7 @@ function runNotificationRuns(notification) {
|
|
|
43
43
|
return [
|
|
44
44
|
...runGroup(notification.completed),
|
|
45
45
|
...runGroup(notification.failed),
|
|
46
|
+
...runGroup(notification.timedOut),
|
|
46
47
|
...runGroup(notification.cancelled),
|
|
47
48
|
...runGroup(notification.running),
|
|
48
49
|
];
|
|
@@ -57,6 +58,7 @@ function runNotificationSummary(notification) {
|
|
|
57
58
|
const parts = [
|
|
58
59
|
[countRunGroup(notification.completed), "completed"],
|
|
59
60
|
[countRunGroup(notification.failed), "failed"],
|
|
61
|
+
[countRunGroup(notification.timedOut), "timed out"],
|
|
60
62
|
[countRunGroup(notification.cancelled), "cancelled"],
|
|
61
63
|
[countRunGroup(notification.running), "running"],
|
|
62
64
|
]
|
|
@@ -12,7 +12,7 @@ function node(input, context) {
|
|
|
12
12
|
function runStatus(status) {
|
|
13
13
|
if (status === "completed")
|
|
14
14
|
return "done";
|
|
15
|
-
if (status === "failed")
|
|
15
|
+
if (status === "failed" || status === "timed_out")
|
|
16
16
|
return "error";
|
|
17
17
|
return status;
|
|
18
18
|
}
|
|
@@ -133,6 +133,10 @@ export const outputSignalProducer = {
|
|
|
133
133
|
mutations.push({ type: "set_session_queue", piboSessionId, queuedMessages: event.queuedMessages });
|
|
134
134
|
mutations.push({ type: "upsert_node", node: node({ id: `message:${piboSessionId}:${event.eventId ?? context.now()}`, kind: "message", status: "queued", piboSessionId, metadata: { source: event.source } }, context) });
|
|
135
135
|
}
|
|
136
|
+
if (event.type === "message_steered") {
|
|
137
|
+
mutations.push({ type: "patch_node", nodeId: `session:${piboSessionId}`, patch: { status: "running" } });
|
|
138
|
+
mutations.push({ type: "upsert_node", node: node({ id: `message:${piboSessionId}:${event.eventId ?? context.now()}`, kind: "message", status: "done", piboSessionId, parentNodeId: event.activeEventId ? `turn:${piboSessionId}:${event.activeEventId}` : undefined, completedAt: context.now(), metadata: { source: event.source, delivery: "steer" } }, context) });
|
|
139
|
+
}
|
|
136
140
|
if (event.type === "message_started") {
|
|
137
141
|
mutations.push({ type: "patch_node", nodeId: `session:${piboSessionId}`, patch: { status: "running" } });
|
|
138
142
|
if (event.eventId) {
|
|
@@ -200,7 +204,7 @@ export const runSignalProducer = {
|
|
|
200
204
|
if (data.type !== "run_changed")
|
|
201
205
|
return [];
|
|
202
206
|
const run = data.run;
|
|
203
|
-
return [{ type: "upsert_node", node: node({ id: `run:${run.runId}`, kind: "yielded_run", status: runStatus(run.status), piboSessionId: run.controllerPiboSessionId, startedAt: run.createdAt, completedAt: run.completedAt, error: run.status === "failed" ? { message: run.summary ?? "Run failed.", source: "run" } : undefined, metadata: { runId: run.runId, toolName: run.toolName, completionPolicy: run.completionPolicy, consumed: run.consumed, summary: run.summary, previousStatus: data.previousStatus, reason: data.reason } }, context) }];
|
|
207
|
+
return [{ type: "upsert_node", node: node({ id: `run:${run.runId}`, kind: "yielded_run", status: runStatus(run.status), piboSessionId: run.controllerPiboSessionId, startedAt: run.createdAt, completedAt: run.completedAt, error: run.status === "failed" || run.status === "timed_out" ? { message: run.summary ?? "Run failed.", source: "run" } : undefined, metadata: { runId: run.runId, toolName: run.toolName, completionPolicy: run.completionPolicy, consumed: run.consumed, summary: run.summary, timeoutMs: run.timeoutMs, timeoutAt: run.timeoutAt, timeoutPhase: run.timeoutPhase, serviceWarning: run.serviceWarning, previousStatus: data.previousStatus, reason: data.reason } }, context) }];
|
|
204
208
|
},
|
|
205
209
|
};
|
|
206
210
|
export function createDefaultSignalProducers() {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { access, mkdir, open, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
4
5
|
const DEFAULT_MAX_BROWSER_PROCESSES = 1;
|
|
5
6
|
const DEFAULT_LOCK_TIMEOUT_MS = 5_000;
|
|
6
7
|
const DEFAULT_LOCK_POLL_INTERVAL_MS = 50;
|
|
@@ -239,6 +240,55 @@ export async function acquireBrowserPoolLease(paths, identity, options = {}) {
|
|
|
239
240
|
}
|
|
240
241
|
}, options.lockOptions);
|
|
241
242
|
}
|
|
243
|
+
export async function restartRecordedBrowserPoolChrome(state) {
|
|
244
|
+
if (!state.userDataDir)
|
|
245
|
+
throw new Error("Browser pool has no persisted user-data directory for reacquisition");
|
|
246
|
+
if (!state.cdpPort)
|
|
247
|
+
throw new Error("Browser pool has no persisted CDP port for reacquisition");
|
|
248
|
+
const candidates = [process.env.PIBO_CHROME_BIN, "/usr/bin/google-chrome", "/usr/bin/chromium", "/usr/bin/chromium-browser", "/opt/google/chrome/chrome"].filter((value) => !!value);
|
|
249
|
+
let executable;
|
|
250
|
+
for (const candidate of candidates) {
|
|
251
|
+
try {
|
|
252
|
+
await access(candidate);
|
|
253
|
+
executable = candidate;
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
catch { /* try next */ }
|
|
257
|
+
}
|
|
258
|
+
if (!executable)
|
|
259
|
+
throw new Error("No supported Chrome/Chromium executable is available to reacquire the persisted browser profile");
|
|
260
|
+
await removeDefaultStaleBrowserFiles(state);
|
|
261
|
+
const cdpUrl = `http://127.0.0.1:${state.cdpPort}`;
|
|
262
|
+
const child = spawn(executable, [
|
|
263
|
+
`--remote-debugging-port=${state.cdpPort}`,
|
|
264
|
+
"--remote-debugging-address=127.0.0.1",
|
|
265
|
+
`--user-data-dir=${state.userDataDir}`,
|
|
266
|
+
`--profile-directory=${process.env.PIBO_BROWSER_USE_DEFAULT_PROFILE || "PIBo"}`,
|
|
267
|
+
"--headless=new",
|
|
268
|
+
"--no-sandbox",
|
|
269
|
+
"--disable-gpu",
|
|
270
|
+
"--disable-dev-shm-usage",
|
|
271
|
+
"about:blank",
|
|
272
|
+
], { detached: true, stdio: "ignore" });
|
|
273
|
+
await new Promise((resolve, reject) => {
|
|
274
|
+
child.once("spawn", resolve);
|
|
275
|
+
child.once("error", reject);
|
|
276
|
+
});
|
|
277
|
+
child.unref();
|
|
278
|
+
if (!child.pid)
|
|
279
|
+
throw new Error("Chrome reacquisition did not return a process id");
|
|
280
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
281
|
+
const health = await checkBrowserPoolCdpHealth(cdpUrl, { timeoutMs: 250 });
|
|
282
|
+
if (health.ok)
|
|
283
|
+
return { pid: child.pid, processGroupId: child.pid, cdpPort: state.cdpPort, cdpUrl, userDataDir: state.userDataDir };
|
|
284
|
+
await delay(100);
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
process.kill(-child.pid, "SIGKILL");
|
|
288
|
+
}
|
|
289
|
+
catch { /* already exited */ }
|
|
290
|
+
throw new Error(`Reacquired Chrome did not expose CDP at ${cdpUrl}`);
|
|
291
|
+
}
|
|
242
292
|
export async function releaseBrowserPoolLease(paths, identity, options = {}) {
|
|
243
293
|
const now = options.now ?? (() => new Date());
|
|
244
294
|
const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
|
|
@@ -365,21 +365,19 @@ export async function acquireBrowserUseLease(context, options = {}) {
|
|
|
365
365
|
}
|
|
366
366
|
await writeRegistry(context.status, registry);
|
|
367
367
|
if (reapedCount > 0 && !options.json) {
|
|
368
|
-
console.
|
|
368
|
+
console.error(`Reaped ${reapedCount} stale lease${reapedCount === 1 ? '' : 's'}`);
|
|
369
369
|
}
|
|
370
370
|
if (options.json)
|
|
371
371
|
printLeaseJson(context.status, lease);
|
|
372
372
|
else
|
|
373
373
|
printLeaseEnv(context.status, lease);
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
console.log(`Warning: Browser warm-up failed: ${warmup.error}`);
|
|
378
|
-
}
|
|
374
|
+
const warmup = await warmupBrowserUseLease(context, lease, 15000, options.headed);
|
|
375
|
+
if (!warmup.success && !options.json) {
|
|
376
|
+
console.error(`Warning: Browser warm-up failed: ${warmup.error?.trim() || 'unknown error'}`);
|
|
379
377
|
}
|
|
380
378
|
});
|
|
381
379
|
}
|
|
382
|
-
async function warmupBrowserUseLease(context, lease, timeoutMs = 15000) {
|
|
380
|
+
async function warmupBrowserUseLease(context, lease, timeoutMs = 15000, headed = false) {
|
|
383
381
|
const wrapperPath = join(context.status.homeDir, 'bin', 'browser-use');
|
|
384
382
|
if (!existsSync(wrapperPath)) {
|
|
385
383
|
return { success: false, error: 'browser-use wrapper not found' };
|
|
@@ -393,7 +391,13 @@ async function warmupBrowserUseLease(context, lease, timeoutMs = 15000) {
|
|
|
393
391
|
...(lease.browserPoolLeaseId ? { PIBO_BROWSER_POOL_LEASE_ID: lease.browserPoolLeaseId } : {}),
|
|
394
392
|
};
|
|
395
393
|
return new Promise((resolve) => {
|
|
396
|
-
const
|
|
394
|
+
const args = [
|
|
395
|
+
...(headed ? ['--headed'] : []),
|
|
396
|
+
'--session',
|
|
397
|
+
lease.sessionName,
|
|
398
|
+
'--pibo-ensure-chrome',
|
|
399
|
+
];
|
|
400
|
+
const child = spawn(wrapperPath, args, {
|
|
397
401
|
env,
|
|
398
402
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
399
403
|
});
|
package/dist/tools/guides.js
CHANGED
|
@@ -376,11 +376,18 @@ browser-use close --all
|
|
|
376
376
|
For the Pibo Chat Web App, prefer an isolated authenticated lease when multiple agents may use the browser at the same time:
|
|
377
377
|
|
|
378
378
|
\`\`\`bash
|
|
379
|
+
# Default headless browser
|
|
379
380
|
eval "$(pibo tools browser-use lease acquire --app pibo-chat --holder "$USER")"
|
|
381
|
+
|
|
382
|
+
# For headed mode, use this instead:
|
|
383
|
+
# eval "$(pibo tools browser-use lease acquire --app pibo-chat --holder "$USER" --headed)"
|
|
384
|
+
|
|
380
385
|
browser-use state
|
|
381
386
|
\`\`\`
|
|
382
387
|
|
|
383
|
-
|
|
388
|
+
Acquire only one lease for the task. The warm-up fixes the mode of the managed Chrome process, so choose \`--headed\` during acquisition rather than adding it only to a later Browser Use command.
|
|
389
|
+
|
|
390
|
+
The lease exports \`BROWSER_USE_HOME\`, \`PIBO_BROWSER_USE_SESSION\`, \`PIBO_BROWSER_USE_CHROME_USER_DATA_DIR\`, and \`PIBO_BROWSER_USE_DEFAULT_PROFILE\`. Stdout contains only shell-safe exports and comments so it can be evaluated; warm-up diagnostics are written to stderr. The Pibo browser-use wrapper uses \`PIBO_BROWSER_USE_SESSION\` as the default session, so later commands can omit \`--session\` in that shell.
|
|
384
391
|
|
|
385
392
|
Before acquiring leases, prepare one authenticated template profile:
|
|
386
393
|
|
package/dist/tools/index.js
CHANGED
|
@@ -1004,6 +1004,7 @@ Commands:
|
|
|
1004
1004
|
.option('--max-slots <count>', 'Maximum active slots for the app', parsePositiveInteger)
|
|
1005
1005
|
.option('--template-dir <path>', 'Authenticated Chrome user-data-dir template to clone')
|
|
1006
1006
|
.option('--profile-name <name>', 'Chrome profile name inside each slot')
|
|
1007
|
+
.option('--headed', 'Warm up the leased Chrome browser in headed mode')
|
|
1007
1008
|
.option('--json', 'Print machine-readable lease data')
|
|
1008
1009
|
.action(async (options) => {
|
|
1009
1010
|
await acquireBrowserUseLease({ status: getCliToolStatus(requireEntry('browser-use')) }, options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pasko70/pibo",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"imports": {
|
|
6
6
|
"vscode": "./src/apps/chat-vscode/extension/src/vscode-shim.js"
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"check:product-vocab": "node scripts/legacy-product-vocabulary-gate.mjs",
|
|
48
48
|
"validate:ink-web-derived": "node scripts/ink-cli-web-derived-parity-validate.mjs",
|
|
49
49
|
"compute:limited-worker-smoke": "node scripts/compute-limited-worker-smoke.mjs",
|
|
50
|
+
"validate:goal-endurance": "node scripts/goal-endurance-check.mjs",
|
|
50
51
|
"typecheck": "tsc -p tsconfig.json --noEmit && npm run chat-ui:typecheck && npm run context-files-ui:typecheck && npm run vscode:typecheck",
|
|
51
52
|
"clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
|
|
52
53
|
"prepack": "npm run build"
|
|
@@ -20,7 +20,7 @@ pibo loop conditions
|
|
|
20
20
|
|
|
21
21
|
Goal-capable profiles expose native tools:
|
|
22
22
|
|
|
23
|
-
- `get_goal`: inspect authoritative status,
|
|
23
|
+
- `get_goal`: inspect authoritative status, soft-budget risk, tokens used, remaining tokens, active agent time, and elapsed wall-clock time.
|
|
24
24
|
- `create_goal`: create a persistent Goal only when the user or system explicitly requests one.
|
|
25
25
|
- `update_goal`: mark the current Goal `complete` after a strict completion audit, or `blocked` after the same impasse repeats for at least three consecutive Goal turns.
|
|
26
26
|
|
|
@@ -34,6 +34,7 @@ pibo loop add \
|
|
|
34
34
|
--profile <profile> \
|
|
35
35
|
--prompt "<complete objective>" \
|
|
36
36
|
--token-budget <optional-positive-token-count> \
|
|
37
|
+
--token-reserve <optional-pre-turn-minimum> \
|
|
37
38
|
--max-iterations <optional-run-fallback> \
|
|
38
39
|
--start
|
|
39
40
|
```
|
|
@@ -50,9 +51,17 @@ Prefer creating the job stopped when its prompt, target, profile, or safety boun
|
|
|
50
51
|
|
|
51
52
|
## Token budgets
|
|
52
53
|
|
|
53
|
-
Pibo accumulates
|
|
54
|
+
Goal token budgets are soft: Pibo accumulates usage reported after model responses, so the final turn can overshoot. Each Goal run records tokens used before the turn, remaining tokens before the turn, turn usage, and overshoot.
|
|
54
55
|
|
|
55
|
-
Increase or clear the
|
|
56
|
+
Set `--token-reserve <n>` to require more than `n` tokens to remain before Pibo starts another turn. Increase or clear the budget, or lower the reserve, before resuming a budget-limited Goal.
|
|
57
|
+
|
|
58
|
+
## Time accounting
|
|
59
|
+
|
|
60
|
+
`activeAgentTimeSeconds` accumulates time spent executing Goal runs. `elapsedWallClockSeconds` starts when the Goal is first activated, includes waiting and paused periods, and freezes when the Goal enters a terminal state. A Goal created paused reports zero wall-clock elapsed time until first activation.
|
|
61
|
+
|
|
62
|
+
## Managed browser leases
|
|
63
|
+
|
|
64
|
+
When a Goal owns `resources.browserLeaseIds`, Pibo renews those leases before each turn and while the turn is active. The same lease is retained across non-terminal Goal turns, including gateway service restart, and is released only when the Goal stops or reaches a terminal state. If the browser process disappeared, Pibo attempts to restart Chromium from the persisted managed profile. Failure to restore authenticated access marks the Goal blocked with an operator-facing resource reason.
|
|
56
65
|
|
|
57
66
|
## Operations
|
|
58
67
|
|