@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
package/dist/previews/config.js
CHANGED
|
@@ -1,45 +1,15 @@
|
|
|
1
|
-
import { isIP } from "node:net";
|
|
2
1
|
import { loadPiboConfig } from "../config/config.js";
|
|
2
|
+
import { parsePreviewBaseURL } from "./base-url.js";
|
|
3
3
|
export const DEFAULT_PREVIEW_TTL_MINUTES = 8 * 60;
|
|
4
4
|
export const DEFAULT_PREVIEW_TICKET_TTL_SECONDS = 60;
|
|
5
5
|
export const DEFAULT_PREVIEW_SESSION_TTL_MINUTES = 8 * 60;
|
|
6
|
-
function isLocalPreviewHostname(hostname) {
|
|
7
|
-
return hostname === "localhost" || hostname.endsWith(".localhost");
|
|
8
|
-
}
|
|
9
|
-
function isValidDnsHostname(hostname) {
|
|
10
|
-
return isIP(hostname) === 0 &&
|
|
11
|
-
hostname.length <= 253 &&
|
|
12
|
-
!hostname.endsWith(".") &&
|
|
13
|
-
hostname.split(".").every((label) => label.length >= 1 &&
|
|
14
|
-
label.length <= 63 &&
|
|
15
|
-
/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(label));
|
|
16
|
-
}
|
|
17
6
|
export function loadPreviewConfig() {
|
|
18
7
|
return loadPiboConfig().preview ?? {};
|
|
19
8
|
}
|
|
20
9
|
export function requirePreviewBaseURL(value = loadPreviewConfig().baseURL) {
|
|
21
10
|
if (!value)
|
|
22
11
|
throw new Error("preview.baseURL is required. Set it with `pibo config set preview.baseURL https://preview.example.com`.");
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
url = new URL(value);
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
throw new Error("preview.baseURL must be an absolute HTTP or HTTPS URL");
|
|
29
|
-
}
|
|
30
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
31
|
-
throw new Error("preview.baseURL must use http or https");
|
|
32
|
-
}
|
|
33
|
-
if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) {
|
|
34
|
-
throw new Error("preview.baseURL must contain only scheme, hostname, and optional port");
|
|
35
|
-
}
|
|
36
|
-
if (!isValidDnsHostname(url.hostname)) {
|
|
37
|
-
throw new Error("preview.baseURL hostname must be a DNS hostname without wildcards, IP literals, or a trailing dot");
|
|
38
|
-
}
|
|
39
|
-
if (url.protocol === "http:" && !isLocalPreviewHostname(url.hostname)) {
|
|
40
|
-
throw new Error("preview.baseURL must use https except for localhost development");
|
|
41
|
-
}
|
|
42
|
-
return url;
|
|
12
|
+
return parsePreviewBaseURL(value);
|
|
43
13
|
}
|
|
44
14
|
export function previewPublicURL(previewId, baseURL = requirePreviewBaseURL()) {
|
|
45
15
|
if (previewId.length > 63 || !/^pv-[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(previewId)) {
|
package/dist/ralph/store.js
CHANGED
|
@@ -407,6 +407,11 @@ export class PiboRalphStore {
|
|
|
407
407
|
this.db.exec('COMMIT');
|
|
408
408
|
return undefined;
|
|
409
409
|
}
|
|
410
|
+
if (job.maxIterations !== undefined && (job.state.completedIterations ?? 0) >= job.maxIterations) {
|
|
411
|
+
this.db.prepare('UPDATE pibo_ralph_jobs SET enabled = 0, updated_at = ? WHERE id = ?').run(timestamp, job.id);
|
|
412
|
+
this.db.exec('COMMIT');
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
410
415
|
const run = this.createRunLocked(job, timestamp);
|
|
411
416
|
const state = { ...job.state, runningAt: timestamp, lastRunAt: timestamp, lastRunId: run.id };
|
|
412
417
|
this.updateJobStateLocked(job.id, state, timestamp);
|
|
@@ -5,6 +5,8 @@ import { ensurePrivatePiboHomeForPath, piboHomePath } from "../core/pibo-home.js
|
|
|
5
5
|
import { protectPrivateFileSync } from "../core/private-path.js";
|
|
6
6
|
import { sqliteTableColumns } from "../data/sqlite-schema.js";
|
|
7
7
|
import { DatabaseSync } from "node:sqlite";
|
|
8
|
+
const MAX_ATTEMPTS_ERROR = "Job exhausted retry attempts.";
|
|
9
|
+
const MAX_ATTEMPTS_REASON = "max_attempts";
|
|
8
10
|
function now() {
|
|
9
11
|
return new Date().toISOString();
|
|
10
12
|
}
|
|
@@ -352,41 +354,73 @@ export class PiboReliabilityStore {
|
|
|
352
354
|
claimBatch(workerId, limit, options = {}) {
|
|
353
355
|
const timestamp = now();
|
|
354
356
|
const claimExpiresAt = new Date(Date.now() + (options.visibilityTimeoutMs ?? 30000)).toISOString();
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
.
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
357
|
+
return this.inImmediateTransaction(() => {
|
|
358
|
+
const clauses = [
|
|
359
|
+
"((state = 'pending' AND run_at <= ?) OR (state = 'running' AND claim_expires_at IS NOT NULL AND claim_expires_at <= ?))",
|
|
360
|
+
"(expires_at IS NULL OR expires_at > ?)",
|
|
361
|
+
"attempts < max_attempts",
|
|
362
|
+
];
|
|
363
|
+
const values = [timestamp, timestamp, timestamp];
|
|
364
|
+
if (options.queue) {
|
|
365
|
+
clauses.push("queue = ?");
|
|
366
|
+
values.push(options.queue);
|
|
367
|
+
}
|
|
368
|
+
this.moveExpiredJobs(timestamp);
|
|
369
|
+
this.moveExhaustedRecoverableJobs(timestamp, { queue: options.queue });
|
|
370
|
+
const rows = this.db
|
|
371
|
+
.prepare(`
|
|
372
|
+
UPDATE pibo_jobs
|
|
373
|
+
SET state = 'running',
|
|
374
|
+
worker_id = ?,
|
|
375
|
+
claim_expires_at = ?,
|
|
376
|
+
claim_token = claim_token + 1,
|
|
377
|
+
attempts = attempts + 1,
|
|
378
|
+
updated_at = ?
|
|
379
|
+
WHERE job_id IN (
|
|
380
|
+
SELECT job_id FROM pibo_jobs
|
|
381
|
+
WHERE ${clauses.join(" AND ")}
|
|
382
|
+
ORDER BY priority DESC, run_at ASC, created_at ASC
|
|
383
|
+
LIMIT ?
|
|
384
|
+
)
|
|
385
|
+
RETURNING *
|
|
386
|
+
`)
|
|
387
|
+
.all(workerId, claimExpiresAt, timestamp, ...values, clampLimit(limit));
|
|
388
|
+
return rows.map(jobFromRow);
|
|
389
|
+
});
|
|
384
390
|
}
|
|
385
391
|
claimJob(jobId, workerId, visibilityTimeoutMs = 30000) {
|
|
386
392
|
const timestamp = now();
|
|
387
393
|
const claimExpiresAt = new Date(Date.now() + visibilityTimeoutMs).toISOString();
|
|
388
|
-
|
|
389
|
-
.
|
|
394
|
+
return this.inImmediateTransaction(() => {
|
|
395
|
+
this.moveExpiredJobs(timestamp);
|
|
396
|
+
this.moveExhaustedRecoverableJobs(timestamp, { jobId });
|
|
397
|
+
const row = this.db
|
|
398
|
+
.prepare(`
|
|
399
|
+
UPDATE pibo_jobs
|
|
400
|
+
SET state = 'running',
|
|
401
|
+
worker_id = ?,
|
|
402
|
+
claim_expires_at = ?,
|
|
403
|
+
claim_token = claim_token + 1,
|
|
404
|
+
attempts = attempts + 1,
|
|
405
|
+
updated_at = ?
|
|
406
|
+
WHERE job_id = ?
|
|
407
|
+
AND state = 'pending'
|
|
408
|
+
AND run_at <= ?
|
|
409
|
+
AND attempts < max_attempts
|
|
410
|
+
AND (expires_at IS NULL OR expires_at > ?)
|
|
411
|
+
RETURNING *
|
|
412
|
+
`)
|
|
413
|
+
.get(workerId, claimExpiresAt, timestamp, jobId, timestamp, timestamp);
|
|
414
|
+
return row ? jobFromRow(row) : undefined;
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
claimRecoverableJob(jobId, workerId, visibilityTimeoutMs = 30000, forcePending = false) {
|
|
418
|
+
const timestamp = now();
|
|
419
|
+
const claimExpiresAt = new Date(Date.now() + visibilityTimeoutMs).toISOString();
|
|
420
|
+
return this.inImmediateTransaction(() => {
|
|
421
|
+
this.moveExpiredJobs(timestamp);
|
|
422
|
+
this.moveExhaustedRecoverableJobs(timestamp, { jobId });
|
|
423
|
+
const row = this.db.prepare(`
|
|
390
424
|
UPDATE pibo_jobs
|
|
391
425
|
SET state = 'running',
|
|
392
426
|
worker_id = ?,
|
|
@@ -395,34 +429,16 @@ export class PiboReliabilityStore {
|
|
|
395
429
|
attempts = attempts + 1,
|
|
396
430
|
updated_at = ?
|
|
397
431
|
WHERE job_id = ?
|
|
398
|
-
AND
|
|
399
|
-
|
|
432
|
+
AND (
|
|
433
|
+
(state = 'pending' AND (? = 1 OR run_at <= ?))
|
|
434
|
+
OR (state = 'running' AND claim_expires_at IS NOT NULL AND claim_expires_at <= ?)
|
|
435
|
+
)
|
|
436
|
+
AND attempts < max_attempts
|
|
400
437
|
AND (expires_at IS NULL OR expires_at > ?)
|
|
401
438
|
RETURNING *
|
|
402
|
-
`)
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
claimRecoverableJob(jobId, workerId, visibilityTimeoutMs = 30000, forcePending = false) {
|
|
407
|
-
const timestamp = now();
|
|
408
|
-
const claimExpiresAt = new Date(Date.now() + visibilityTimeoutMs).toISOString();
|
|
409
|
-
const row = this.db.prepare(`
|
|
410
|
-
UPDATE pibo_jobs
|
|
411
|
-
SET state = 'running',
|
|
412
|
-
worker_id = ?,
|
|
413
|
-
claim_expires_at = ?,
|
|
414
|
-
claim_token = claim_token + 1,
|
|
415
|
-
attempts = attempts + 1,
|
|
416
|
-
updated_at = ?
|
|
417
|
-
WHERE job_id = ?
|
|
418
|
-
AND (
|
|
419
|
-
(state = 'pending' AND (? = 1 OR run_at <= ?))
|
|
420
|
-
OR (state = 'running' AND claim_expires_at IS NOT NULL AND claim_expires_at <= ?)
|
|
421
|
-
)
|
|
422
|
-
AND (expires_at IS NULL OR expires_at > ?)
|
|
423
|
-
RETURNING *
|
|
424
|
-
`).get(workerId, claimExpiresAt, timestamp, jobId, forcePending ? 1 : 0, timestamp, timestamp, timestamp);
|
|
425
|
-
return row ? jobFromRow(row) : undefined;
|
|
439
|
+
`).get(workerId, claimExpiresAt, timestamp, jobId, forcePending ? 1 : 0, timestamp, timestamp, timestamp);
|
|
440
|
+
return row ? jobFromRow(row) : undefined;
|
|
441
|
+
});
|
|
426
442
|
}
|
|
427
443
|
updateJobPayload(jobId, workerId, payload, claimToken) {
|
|
428
444
|
const result = this.db.prepare(`
|
|
@@ -436,15 +452,23 @@ export class PiboReliabilityStore {
|
|
|
436
452
|
}
|
|
437
453
|
releaseJob(jobId, workerId, delayMs = 0, claimToken) {
|
|
438
454
|
const timestamp = now();
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
455
|
+
return this.inImmediateTransaction(() => {
|
|
456
|
+
const row = this.getLiveWorkerJob(jobId, workerId, timestamp, claimToken);
|
|
457
|
+
if (!row)
|
|
458
|
+
return false;
|
|
459
|
+
if (row.attempts >= row.max_attempts) {
|
|
460
|
+
this.moveJobToDead(row, MAX_ATTEMPTS_ERROR, MAX_ATTEMPTS_REASON, timestamp);
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
const runAt = new Date(Date.parse(timestamp) + Math.max(0, delayMs)).toISOString();
|
|
464
|
+
const result = this.db.prepare(`
|
|
465
|
+
UPDATE pibo_jobs
|
|
466
|
+
SET state = 'pending', worker_id = NULL, claim_expires_at = NULL, run_at = ?, updated_at = ?
|
|
467
|
+
WHERE job_id = ? AND state = 'running' AND worker_id = ?
|
|
468
|
+
AND (? IS NULL OR claim_token = ?)
|
|
469
|
+
`).run(runAt, timestamp, jobId, workerId, claimToken ?? null, claimToken ?? null);
|
|
470
|
+
return Number(result.changes ?? 0) > 0;
|
|
471
|
+
});
|
|
448
472
|
}
|
|
449
473
|
ack(jobId, workerId, claimToken) {
|
|
450
474
|
const timestamp = now();
|
|
@@ -469,7 +493,7 @@ export class PiboReliabilityStore {
|
|
|
469
493
|
return false;
|
|
470
494
|
const job = jobFromRow(row);
|
|
471
495
|
if (job.attempts >= job.maxAttempts) {
|
|
472
|
-
this.moveJobToDead(row, input.error ??
|
|
496
|
+
this.moveJobToDead(row, input.error ?? MAX_ATTEMPTS_ERROR, MAX_ATTEMPTS_REASON, timestamp);
|
|
473
497
|
return true;
|
|
474
498
|
}
|
|
475
499
|
const delayMs = input.delayMs ?? retryDelayMs(job.attempts, input);
|
|
@@ -602,16 +626,21 @@ export class PiboReliabilityStore {
|
|
|
602
626
|
}
|
|
603
627
|
hasRecoverableJobs(queue, includeDeferredPending = false) {
|
|
604
628
|
const timestamp = now();
|
|
605
|
-
return this.
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
629
|
+
return this.inImmediateTransaction(() => {
|
|
630
|
+
this.moveExpiredJobs(timestamp);
|
|
631
|
+
this.moveExhaustedRecoverableJobs(timestamp, { queue });
|
|
632
|
+
return this.db.prepare(`
|
|
633
|
+
SELECT 1 AS found FROM pibo_jobs
|
|
634
|
+
WHERE queue = ?
|
|
635
|
+
AND (
|
|
636
|
+
(state = 'pending' AND (? = 1 OR run_at <= ?))
|
|
637
|
+
OR (state = 'running' AND claim_expires_at IS NOT NULL AND claim_expires_at <= ?)
|
|
638
|
+
)
|
|
639
|
+
AND attempts < max_attempts
|
|
640
|
+
AND (expires_at IS NULL OR expires_at > ?)
|
|
641
|
+
LIMIT 1
|
|
642
|
+
`).get(queue, includeDeferredPending ? 1 : 0, timestamp, timestamp, timestamp) !== undefined;
|
|
643
|
+
});
|
|
615
644
|
}
|
|
616
645
|
quarantineJob(jobId, reason, correlation = {}) {
|
|
617
646
|
const row = this.db.prepare("SELECT * FROM pibo_jobs WHERE job_id = ?").get(jobId);
|
|
@@ -749,46 +778,62 @@ export class PiboReliabilityStore {
|
|
|
749
778
|
return Number(result.changes ?? 0);
|
|
750
779
|
}
|
|
751
780
|
recoverInterruptedRuns(workerId = `run-registry:${process.pid}`) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
this.
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
781
|
+
return this.inImmediateTransaction(() => {
|
|
782
|
+
const rows = this.db.prepare("SELECT * FROM pibo_runs WHERE status = 'running'").all();
|
|
783
|
+
const recovered = [];
|
|
784
|
+
const timestamp = now();
|
|
785
|
+
for (const row of rows) {
|
|
786
|
+
if (row.job_id && this.hasUnexpiredJobClaim(row.job_id, timestamp, workerId))
|
|
787
|
+
continue;
|
|
788
|
+
const run = runFromRow(row);
|
|
789
|
+
if (run.timeoutAt && run.timeoutAt <= timestamp) {
|
|
790
|
+
const error = `Run deadline ${run.timeoutAt} elapsed before the interrupted runtime recovered.`;
|
|
791
|
+
if (run.jobId)
|
|
792
|
+
this.moveLiveJobToDead(run.jobId, error, "timeout", timestamp);
|
|
793
|
+
recovered.push(this.updateRun(run.runId, {
|
|
794
|
+
status: "timed_out",
|
|
795
|
+
error,
|
|
796
|
+
timeoutPhase: "lifetime",
|
|
797
|
+
summary: `${run.toolName} run started successfully, then reached its configured timeout.`,
|
|
798
|
+
completedAt: timestamp,
|
|
799
|
+
}) ?? run);
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
const jobRow = run.jobId
|
|
803
|
+
? this.db.prepare("SELECT * FROM pibo_jobs WHERE job_id = ?").get(run.jobId)
|
|
804
|
+
: undefined;
|
|
805
|
+
if (run.retryable && jobRow && jobRow.attempts < jobRow.max_attempts) {
|
|
806
|
+
this.releaseJobForRetry(jobRow.job_id, timestamp);
|
|
807
|
+
recovered.push(this.updateRun(run.runId, {
|
|
808
|
+
status: "queued",
|
|
809
|
+
summary: `${run.toolName} run is queued for retry after interruption.`,
|
|
810
|
+
}) ?? run);
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
if (run.retryable && jobRow && jobRow.attempts >= jobRow.max_attempts) {
|
|
814
|
+
this.moveJobToDead(jobRow, MAX_ATTEMPTS_ERROR, MAX_ATTEMPTS_REASON, timestamp);
|
|
815
|
+
recovered.push(this.updateRun(run.runId, {
|
|
816
|
+
status: "failed",
|
|
817
|
+
error: MAX_ATTEMPTS_ERROR,
|
|
818
|
+
summary: `${run.toolName} run failed after exhausting retry attempts.`,
|
|
819
|
+
completedAt: timestamp,
|
|
820
|
+
}) ?? run);
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
const error = run.retryable
|
|
824
|
+
? "Run was interrupted before completion and its retry job is no longer available."
|
|
825
|
+
: "Run was interrupted before completion and the tool is not retryable.";
|
|
826
|
+
if (jobRow)
|
|
827
|
+
this.moveJobToDead(jobRow, error, "interrupted", timestamp);
|
|
783
828
|
recovered.push(this.updateRun(run.runId, {
|
|
784
829
|
status: "failed",
|
|
785
|
-
error
|
|
830
|
+
error,
|
|
786
831
|
summary: `${run.toolName} run failed.`,
|
|
787
832
|
completedAt: timestamp,
|
|
788
833
|
}) ?? run);
|
|
789
834
|
}
|
|
790
|
-
|
|
791
|
-
|
|
835
|
+
return recovered;
|
|
836
|
+
});
|
|
792
837
|
}
|
|
793
838
|
close() {
|
|
794
839
|
this.db.close();
|
|
@@ -843,6 +888,24 @@ export class PiboReliabilityStore {
|
|
|
843
888
|
for (const row of expired)
|
|
844
889
|
this.moveJobToDead(row, row.last_error ?? "Job expired.", "expired", timestamp);
|
|
845
890
|
}
|
|
891
|
+
moveExhaustedRecoverableJobs(timestamp, options = {}) {
|
|
892
|
+
const clauses = [
|
|
893
|
+
"attempts >= max_attempts",
|
|
894
|
+
"(state = 'pending' OR (state = 'running' AND claim_expires_at IS NOT NULL AND claim_expires_at <= ?))",
|
|
895
|
+
];
|
|
896
|
+
const values = [timestamp];
|
|
897
|
+
if (options.jobId) {
|
|
898
|
+
clauses.push("job_id = ?");
|
|
899
|
+
values.push(options.jobId);
|
|
900
|
+
}
|
|
901
|
+
if (options.queue) {
|
|
902
|
+
clauses.push("queue = ?");
|
|
903
|
+
values.push(options.queue);
|
|
904
|
+
}
|
|
905
|
+
const exhausted = this.db.prepare(`SELECT * FROM pibo_jobs WHERE ${clauses.join(" AND ")}`).all(...values);
|
|
906
|
+
for (const row of exhausted)
|
|
907
|
+
this.moveJobToDead(row, MAX_ATTEMPTS_ERROR, MAX_ATTEMPTS_REASON, timestamp);
|
|
908
|
+
}
|
|
846
909
|
hasUnexpiredJobClaim(jobId, timestamp, workerId) {
|
|
847
910
|
const row = this.db
|
|
848
911
|
.prepare(`
|
|
@@ -63,7 +63,8 @@ function applyToolDisplayMode(rows, mode) {
|
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
65
|
function isToolDisplayRow(row) {
|
|
66
|
-
return row.
|
|
66
|
+
return row.sourceNodeIds.some((nodeId) => parseTraceToolNodeIdentity(nodeId) !== undefined)
|
|
67
|
+
|| row.id.startsWith("terminal:tool:")
|
|
67
68
|
|| row.kind === "tool.call"
|
|
68
69
|
|| row.kind === "tool.image"
|
|
69
70
|
|| row.kind === "tool.group.exploring"
|
|
@@ -147,6 +147,65 @@ export class PiboDataSessionStore {
|
|
|
147
147
|
`).run(sequence, new Date().toISOString(), piboSessionId);
|
|
148
148
|
});
|
|
149
149
|
}
|
|
150
|
+
claimOrAttachOutputPart(input) {
|
|
151
|
+
return this.dataStore.transaction(() => {
|
|
152
|
+
const indexAttribute = outputPartIndexAttribute(input.kind);
|
|
153
|
+
const eventTypes = outputPartEventTypes(input.kind);
|
|
154
|
+
const placeholders = eventTypes.map(() => "?").join(", ");
|
|
155
|
+
const rows = this.db.prepare(`
|
|
156
|
+
SELECT type, attributes_json
|
|
157
|
+
FROM event_log
|
|
158
|
+
WHERE session_id = ? AND event_id = ? AND type IN (${placeholders})
|
|
159
|
+
ORDER BY stream_id ASC
|
|
160
|
+
`).all(input.piboSessionId, input.eventId, ...eventTypes);
|
|
161
|
+
let maximum = -1;
|
|
162
|
+
const latestTypeByIndex = new Map();
|
|
163
|
+
for (const row of rows) {
|
|
164
|
+
const attributes = parseJsonObject(row.attributes_json);
|
|
165
|
+
const index = attributes[indexAttribute];
|
|
166
|
+
if (typeof index !== "number" || !Number.isSafeInteger(index) || index < 0)
|
|
167
|
+
continue;
|
|
168
|
+
maximum = Math.max(maximum, index);
|
|
169
|
+
if (attributes.outputPartFingerprint === input.fingerprint
|
|
170
|
+
|| attributes.identityFingerprint === input.identityFingerprint) {
|
|
171
|
+
this.observeOutputPartIndex(input, index);
|
|
172
|
+
return index;
|
|
173
|
+
}
|
|
174
|
+
latestTypeByIndex.set(index, row.type);
|
|
175
|
+
}
|
|
176
|
+
const latestOpen = [...latestTypeByIndex.entries()]
|
|
177
|
+
.filter(([, eventType]) => !outputPartEventIsTerminal(eventType))
|
|
178
|
+
.sort(([left], [right]) => right - left)[0]?.[0];
|
|
179
|
+
if (latestOpen !== undefined) {
|
|
180
|
+
this.observeOutputPartIndex(input, latestOpen);
|
|
181
|
+
return latestOpen;
|
|
182
|
+
}
|
|
183
|
+
const minimum = Math.max(input.proposedIndex, maximum + 1);
|
|
184
|
+
const row = this.db.prepare(`
|
|
185
|
+
INSERT INTO session_output_part_counters (
|
|
186
|
+
pibo_session_id, event_id, part_kind, next_index, updated_at
|
|
187
|
+
) VALUES (?, ?, ?, ? + 1, ?)
|
|
188
|
+
ON CONFLICT(pibo_session_id, event_id, part_kind) DO UPDATE SET
|
|
189
|
+
next_index = MAX(session_output_part_counters.next_index, ?) + 1,
|
|
190
|
+
updated_at = excluded.updated_at
|
|
191
|
+
RETURNING next_index - 1 AS part_index
|
|
192
|
+
`).get(input.piboSessionId, input.eventId, input.kind, minimum, new Date().toISOString(), minimum);
|
|
193
|
+
return row.part_index;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
observeOutputPartIndex(input, index) {
|
|
197
|
+
this.db.prepare(`
|
|
198
|
+
INSERT INTO session_output_part_counters (
|
|
199
|
+
pibo_session_id, event_id, part_kind, next_index, updated_at
|
|
200
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
201
|
+
ON CONFLICT(pibo_session_id, event_id, part_kind) DO UPDATE SET
|
|
202
|
+
next_index = MAX(session_output_part_counters.next_index, excluded.next_index),
|
|
203
|
+
updated_at = CASE
|
|
204
|
+
WHEN excluded.next_index > session_output_part_counters.next_index THEN excluded.updated_at
|
|
205
|
+
ELSE session_output_part_counters.updated_at
|
|
206
|
+
END
|
|
207
|
+
`).run(input.piboSessionId, input.eventId, input.kind, index + 1, new Date().toISOString());
|
|
208
|
+
}
|
|
150
209
|
claimOutputToolInvocationOrdinal(piboSessionId, eventId, toolCallId) {
|
|
151
210
|
return this.dataStore.transaction(() => {
|
|
152
211
|
const durable = this.db.prepare(`
|
|
@@ -562,6 +621,28 @@ function runtimeBindingFromRow(row) {
|
|
|
562
621
|
function isRuntimeBindingState(value) {
|
|
563
622
|
return value === "unbound" || value === "bound" || value === "missing" || value === "error";
|
|
564
623
|
}
|
|
624
|
+
function outputPartIndexAttribute(kind) {
|
|
625
|
+
switch (kind) {
|
|
626
|
+
case "assistant": return "assistantIndex";
|
|
627
|
+
case "thinking": return "thinkingIndex";
|
|
628
|
+
case "usage": return "usageIndex";
|
|
629
|
+
case "compaction": return "compactionIndex";
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function outputPartEventTypes(kind) {
|
|
633
|
+
switch (kind) {
|
|
634
|
+
case "assistant": return ["assistant_delta", "assistant_message"];
|
|
635
|
+
case "thinking": return ["thinking_started", "thinking_delta", "thinking_finished"];
|
|
636
|
+
case "usage": return ["assistant_usage"];
|
|
637
|
+
case "compaction": return ["compaction_start", "compaction_end"];
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
function outputPartEventIsTerminal(eventType) {
|
|
641
|
+
return eventType === "assistant_message"
|
|
642
|
+
|| eventType === "thinking_finished"
|
|
643
|
+
|| eventType === "assistant_usage"
|
|
644
|
+
|| eventType === "compaction_end";
|
|
645
|
+
}
|
|
565
646
|
function parseJsonObject(json) {
|
|
566
647
|
if (!json)
|
|
567
648
|
return {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES, PIBO_AGENT_OBSERVATION_DEFAULT_TOOL_EVENT_TYPES, normalizePiboAgentObservationCursor, normalizePiboAgentObservationLimit, normalizePiboAgentObservationOrder, normalizePiboAgentObservationToolDetail, parsePiboAgentObservationTimestamp, piboAgentObservationKind, piboAgentObservationToolSummary, } from "./observations.js";
|
|
2
|
+
export function preparePiboAgentObservationQuery(input = {}) {
|
|
3
|
+
const order = normalizePiboAgentObservationOrder(input.order);
|
|
4
|
+
const limit = normalizePiboAgentObservationLimit(input.limit);
|
|
5
|
+
const toolDetail = normalizePiboAgentObservationToolDetail(input.toolDetail);
|
|
6
|
+
const afterSequence = normalizePiboAgentObservationCursor(input.afterSequence);
|
|
7
|
+
const since = parsePiboAgentObservationTimestamp(input.since, "since");
|
|
8
|
+
const until = parsePiboAgentObservationTimestamp(input.until, "until");
|
|
9
|
+
if (since !== undefined && until !== undefined && since > until) {
|
|
10
|
+
throw new Error("Agent observation since must not be after until.");
|
|
11
|
+
}
|
|
12
|
+
if (input.toolCallIds && input.toolCallIds.length > 50) {
|
|
13
|
+
throw new Error("Agent observation toolCallIds must contain at most 50 entries.");
|
|
14
|
+
}
|
|
15
|
+
const requestIds = input.requestIds ? new Set(input.requestIds) : undefined;
|
|
16
|
+
const toolCallIds = input.toolCallIds ? new Set(input.toolCallIds) : undefined;
|
|
17
|
+
const agentIds = input.agentIds ? new Set(input.agentIds) : undefined;
|
|
18
|
+
const names = input.names ? new Set(input.names) : undefined;
|
|
19
|
+
const threadKeys = input.threadKeys ? new Set(input.threadKeys) : undefined;
|
|
20
|
+
const eventTypes = input.eventTypes ? new Set(input.eventTypes) : undefined;
|
|
21
|
+
const kinds = input.kinds ? new Set(input.kinds) : undefined;
|
|
22
|
+
const roles = input.roles ? new Set(input.roles) : undefined;
|
|
23
|
+
const textContains = input.textContains?.toLowerCase();
|
|
24
|
+
const defaultMessageView = eventTypes === undefined && kinds === undefined;
|
|
25
|
+
const explicitlySelectsTools = input.eventTypes?.some((eventType) => piboAgentObservationKind(eventType) === "tool") === true
|
|
26
|
+
|| input.kinds?.includes("tool") === true
|
|
27
|
+
|| toolCallIds !== undefined;
|
|
28
|
+
const includeTools = input.includeTools === true || (input.includeTools === undefined && explicitlySelectsTools);
|
|
29
|
+
const defaultEventTypes = includeTools
|
|
30
|
+
? [...PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES, ...PIBO_AGENT_OBSERVATION_DEFAULT_TOOL_EVENT_TYPES]
|
|
31
|
+
: [...PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES];
|
|
32
|
+
const defaultEventTypeSet = new Set(defaultEventTypes);
|
|
33
|
+
const scanEventTypes = defaultMessageView
|
|
34
|
+
? defaultEventTypes
|
|
35
|
+
: input.eventTypes && input.eventTypes.length > 0
|
|
36
|
+
? [...input.eventTypes]
|
|
37
|
+
: undefined;
|
|
38
|
+
return {
|
|
39
|
+
filters: {
|
|
40
|
+
...input,
|
|
41
|
+
...(defaultMessageView ? { eventTypes: defaultEventTypes } : {}),
|
|
42
|
+
...(afterSequence !== undefined ? { afterSequence } : {}),
|
|
43
|
+
order,
|
|
44
|
+
limit,
|
|
45
|
+
includeTools,
|
|
46
|
+
toolDetail,
|
|
47
|
+
includeDetails: input.includeDetails === true,
|
|
48
|
+
},
|
|
49
|
+
...(afterSequence !== undefined ? { afterSequence } : {}),
|
|
50
|
+
order,
|
|
51
|
+
scanOrder: afterSequence !== undefined ? "asc" : order,
|
|
52
|
+
limit,
|
|
53
|
+
includeTools,
|
|
54
|
+
toolDetail,
|
|
55
|
+
...(scanEventTypes ? { scanEventTypes } : {}),
|
|
56
|
+
matches(observation) {
|
|
57
|
+
if (requestIds && (!observation.requestId || !requestIds.has(observation.requestId)))
|
|
58
|
+
return false;
|
|
59
|
+
if (toolCallIds && (!observation.toolCallId || !toolCallIds.has(observation.toolCallId)))
|
|
60
|
+
return false;
|
|
61
|
+
if (agentIds && !agentIds.has(observation.agentId))
|
|
62
|
+
return false;
|
|
63
|
+
if (names && !names.has(observation.name))
|
|
64
|
+
return false;
|
|
65
|
+
if (threadKeys && (!observation.threadKey || !threadKeys.has(observation.threadKey)))
|
|
66
|
+
return false;
|
|
67
|
+
if (observation.kind === "tool" && !includeTools)
|
|
68
|
+
return false;
|
|
69
|
+
if (defaultMessageView && !defaultEventTypeSet.has(observation.eventType))
|
|
70
|
+
return false;
|
|
71
|
+
if (eventTypes && !eventTypes.has(observation.eventType))
|
|
72
|
+
return false;
|
|
73
|
+
if (kinds && !kinds.has(observation.kind))
|
|
74
|
+
return false;
|
|
75
|
+
if (roles && (!observation.role || !roles.has(observation.role)))
|
|
76
|
+
return false;
|
|
77
|
+
if (afterSequence !== undefined && observation.sequence <= afterSequence)
|
|
78
|
+
return false;
|
|
79
|
+
const createdAt = Date.parse(observation.createdAt);
|
|
80
|
+
if (since !== undefined && createdAt < since)
|
|
81
|
+
return false;
|
|
82
|
+
if (until !== undefined && createdAt > until)
|
|
83
|
+
return false;
|
|
84
|
+
if (textContains && !(observation.text ?? "").toLowerCase().includes(textContains))
|
|
85
|
+
return false;
|
|
86
|
+
return true;
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export function selectPiboAgentObservationPage(observations, query, options = {}) {
|
|
91
|
+
const matches = [];
|
|
92
|
+
for (const observation of observations) {
|
|
93
|
+
if (!query.matches(observation))
|
|
94
|
+
continue;
|
|
95
|
+
matches.push(observation);
|
|
96
|
+
if (matches.length > query.limit)
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
const pageLimited = matches.length > query.limit;
|
|
100
|
+
const selected = matches.slice(0, query.limit);
|
|
101
|
+
if (query.afterSequence !== undefined && query.order === "desc")
|
|
102
|
+
selected.reverse();
|
|
103
|
+
const projected = selected.map((observation) => {
|
|
104
|
+
const { details, ...visible } = observation;
|
|
105
|
+
const compact = visible.kind === "tool" && query.toolDetail === "summary"
|
|
106
|
+
? { ...visible, text: piboAgentObservationToolSummary(visible.text, visible.isError, details) }
|
|
107
|
+
: visible;
|
|
108
|
+
return query.filters.includeDetails === true && details !== undefined ? { ...compact, details } : compact;
|
|
109
|
+
});
|
|
110
|
+
const evictedThrough = options.evictedThrough ?? 0;
|
|
111
|
+
const retentionTruncated = query.afterSequence === undefined
|
|
112
|
+
? evictedThrough > 0
|
|
113
|
+
: query.afterSequence < evictedThrough;
|
|
114
|
+
const nextAfterSequence = projected.reduce((maximum, observation) => Math.max(maximum, observation.sequence), query.afterSequence === undefined ? 0 : Math.max(query.afterSequence, evictedThrough));
|
|
115
|
+
return {
|
|
116
|
+
filters: query.filters,
|
|
117
|
+
observations: projected,
|
|
118
|
+
nextAfterSequence,
|
|
119
|
+
truncated: retentionTruncated || pageLimited,
|
|
120
|
+
};
|
|
121
|
+
}
|