@pasko70/pibo 2.4.1 → 2.4.3
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/agent-runtime/context-build.js +100 -11
- package/dist/agent-runtime/profile-validation.js +3 -0
- package/dist/agent-runtime/resource-service.js +16 -0
- package/dist/agent-runtime/routed-session.js +180 -30
- package/dist/agent-runtime/testing/fake-adapter.js +7 -0
- package/dist/agent-runtimes/pi/adapter.js +2 -0
- package/dist/agent-runtimes/pi/routed-session.js +104 -43
- package/dist/agent-runtimes/pi/runtime.js +8 -5
- package/dist/apps/chat/web-app.js +21 -6
- package/dist/apps/chat-ui/assets/{dist-Cw9po47P.js → dist-C4JGcQjh.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BeqHbnGN.js → dist-CF92Lv76.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DTRjeLwO.js → dist-CJ0JS-bE.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-3YG57JXi.js → dist-DrgKyb9n.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CrDtveZB.js → dist-VoMyV-PE.js} +1 -1
- package/dist/apps/chat-ui/assets/index-0WZI2phJ.css +1 -0
- package/dist/apps/chat-ui/assets/index-DhX1_aRM.js +228 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/{pibo-vscode-ext-2.4.1.vsix → pibo-vscode-ext-2.4.3.vsix} +0 -0
- package/dist/cli.js +16 -6
- package/dist/core/context-build.js +66 -6
- package/dist/core/model-defaults.js +11 -3
- package/dist/core/session-router.js +285 -112
- package/dist/gateway/server.js +1 -0
- package/dist/loops/accounting.js +80 -0
- package/dist/loops/service.js +52 -13
- package/dist/loops/store.js +67 -19
- package/dist/loops/tools.js +3 -1
- package/dist/reliability/store.js +11 -6
- package/dist/runs/lifecycle.js +38 -1
- package/dist/runs/registry.js +46 -45
- package/dist/runs/tools.js +34 -24
- package/dist/subagents/context.js +48 -0
- package/dist/subagents/runtime-selection.js +28 -0
- package/dist/subagents/tool.js +28 -6
- package/dist/tools/codex-compat.js +1 -0
- package/dist/tools/contract.js +10 -0
- package/dist/tools/mcp-bridge.js +4 -2
- package/dist/tools/runtime/node-backend.js +8 -2
- package/dist/tools/runtime/python-backend.js +8 -2
- package/dist/tools/runtime/tool.js +1 -0
- package/dist/tools/session-tool-set.js +19 -12
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-AjnP3ci-.js +0 -228
- package/dist/apps/chat-ui/assets/index-BJ56TREg.css +0 -1
package/dist/gateway/server.js
CHANGED
|
@@ -358,6 +358,7 @@ export class PiboGatewayServer {
|
|
|
358
358
|
findSessions: (input) => this.requireSessionStore().find(input),
|
|
359
359
|
listSessions: () => this.requireSessionStore().list?.() ?? [],
|
|
360
360
|
getSessionRuntimeBinding: (piboSessionId) => this.requireRouter().getSessionRuntimeBinding(piboSessionId),
|
|
361
|
+
getSessionRuntimeProfile: (piboSessionId) => this.requireRouter().getSessionRuntimeProfile(piboSessionId),
|
|
361
362
|
inspectSessionRuntimeHistory: async (piboSessionId) => {
|
|
362
363
|
const session = this.requireSessionStore().get(piboSessionId);
|
|
363
364
|
if (!session)
|
package/dist/loops/accounting.js
CHANGED
|
@@ -1,4 +1,84 @@
|
|
|
1
1
|
export const LOOP_TOKEN_ACCOUNTING_VERSION = 1;
|
|
2
|
+
export function emptyLoopUsageTotals() {
|
|
3
|
+
return {
|
|
4
|
+
inputTokens: 0,
|
|
5
|
+
outputTokens: 0,
|
|
6
|
+
cacheReadTokens: 0,
|
|
7
|
+
cacheWriteTokens: 0,
|
|
8
|
+
reasoningTokens: 0,
|
|
9
|
+
totalTokens: 0,
|
|
10
|
+
costUsd: 0,
|
|
11
|
+
costReportedTurns: 0,
|
|
12
|
+
assistantTurns: 0,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function emptyLoopRecursiveUsage() {
|
|
16
|
+
return {
|
|
17
|
+
controller: emptyLoopUsageTotals(),
|
|
18
|
+
descendants: emptyLoopUsageTotals(),
|
|
19
|
+
total: emptyLoopUsageTotals(),
|
|
20
|
+
sessionIds: [],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function assistantUsageTotals(usage) {
|
|
24
|
+
return {
|
|
25
|
+
inputTokens: normalizedTokenCount(usage.inputTokens),
|
|
26
|
+
outputTokens: normalizedTokenCount(usage.outputTokens),
|
|
27
|
+
cacheReadTokens: normalizedTokenCount(usage.cacheReadTokens),
|
|
28
|
+
cacheWriteTokens: normalizedTokenCount(usage.cacheWriteTokens),
|
|
29
|
+
reasoningTokens: normalizedTokenCount(usage.reasoningTokens),
|
|
30
|
+
totalTokens: normalizedTokenCount(usage.totalTokens),
|
|
31
|
+
costUsd: typeof usage.costUsd === 'number' && Number.isFinite(usage.costUsd) ? Math.max(0, usage.costUsd) : 0,
|
|
32
|
+
costReportedTurns: typeof usage.costUsd === 'number' && Number.isFinite(usage.costUsd) ? 1 : 0,
|
|
33
|
+
assistantTurns: 1,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function normalizeLoopUsageTotals(value) {
|
|
37
|
+
const costUsd = typeof value?.costUsd === 'number' && Number.isFinite(value.costUsd) ? Math.max(0, value.costUsd) : 0;
|
|
38
|
+
return {
|
|
39
|
+
inputTokens: normalizedTokenCount(value?.inputTokens),
|
|
40
|
+
outputTokens: normalizedTokenCount(value?.outputTokens),
|
|
41
|
+
cacheReadTokens: normalizedTokenCount(value?.cacheReadTokens),
|
|
42
|
+
cacheWriteTokens: normalizedTokenCount(value?.cacheWriteTokens),
|
|
43
|
+
reasoningTokens: normalizedTokenCount(value?.reasoningTokens),
|
|
44
|
+
totalTokens: normalizedTokenCount(value?.totalTokens),
|
|
45
|
+
costUsd,
|
|
46
|
+
costReportedTurns: value?.costReportedTurns === undefined && costUsd > 0 ? 1 : normalizedTokenCount(value?.costReportedTurns),
|
|
47
|
+
assistantTurns: normalizedTokenCount(value?.assistantTurns),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function addUsageTotals(left, right) {
|
|
51
|
+
const normalizedLeft = normalizeLoopUsageTotals(left);
|
|
52
|
+
const normalizedRight = normalizeLoopUsageTotals(right);
|
|
53
|
+
return {
|
|
54
|
+
inputTokens: normalizedLeft.inputTokens + normalizedRight.inputTokens,
|
|
55
|
+
outputTokens: normalizedLeft.outputTokens + normalizedRight.outputTokens,
|
|
56
|
+
cacheReadTokens: normalizedLeft.cacheReadTokens + normalizedRight.cacheReadTokens,
|
|
57
|
+
cacheWriteTokens: normalizedLeft.cacheWriteTokens + normalizedRight.cacheWriteTokens,
|
|
58
|
+
reasoningTokens: normalizedLeft.reasoningTokens + normalizedRight.reasoningTokens,
|
|
59
|
+
totalTokens: normalizedLeft.totalTokens + normalizedRight.totalTokens,
|
|
60
|
+
costUsd: normalizedLeft.costUsd + normalizedRight.costUsd,
|
|
61
|
+
costReportedTurns: normalizedLeft.costReportedTurns + normalizedRight.costReportedTurns,
|
|
62
|
+
assistantTurns: normalizedLeft.assistantTurns + normalizedRight.assistantTurns,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function addLoopAssistantUsage(current, usage, input) {
|
|
66
|
+
const base = current
|
|
67
|
+
? {
|
|
68
|
+
controller: normalizeLoopUsageTotals(current.controller),
|
|
69
|
+
descendants: normalizeLoopUsageTotals(current.descendants),
|
|
70
|
+
total: normalizeLoopUsageTotals(current.total),
|
|
71
|
+
sessionIds: Array.isArray(current.sessionIds) ? current.sessionIds.filter((id) => typeof id === 'string') : [],
|
|
72
|
+
}
|
|
73
|
+
: emptyLoopRecursiveUsage();
|
|
74
|
+
const increment = assistantUsageTotals(usage);
|
|
75
|
+
return {
|
|
76
|
+
controller: input.descendant ? { ...base.controller } : addUsageTotals(base.controller, increment),
|
|
77
|
+
descendants: input.descendant ? addUsageTotals(base.descendants, increment) : { ...base.descendants },
|
|
78
|
+
total: addUsageTotals(base.total, increment),
|
|
79
|
+
sessionIds: base.sessionIds.includes(input.piboSessionId) ? [...base.sessionIds] : [...base.sessionIds, input.piboSessionId],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
2
82
|
function normalizedTokenCount(value) {
|
|
3
83
|
return typeof value === 'number' && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
4
84
|
}
|
package/dist/loops/service.js
CHANGED
|
@@ -519,26 +519,65 @@ export class PiboLoopService {
|
|
|
519
519
|
getStopConditionDefinitions() { return this.options.context.getLoopStopConditionDefinitions?.() ?? this.options.context.getRalphStopConditionDefinitions?.() ?? createBuiltInLoopStopConditions(); }
|
|
520
520
|
handleOutputEvent(event) {
|
|
521
521
|
const eventId = 'eventId' in event ? event.eventId : undefined;
|
|
522
|
-
if (
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
522
|
+
if (eventId) {
|
|
523
|
+
if (event.type === 'message_queued')
|
|
524
|
+
this.store.updateRunMessageState(eventId, 'queued');
|
|
525
|
+
else if (event.type === 'message_started')
|
|
526
|
+
this.store.updateRunMessageState(eventId, 'active');
|
|
527
|
+
else if (event.type === 'message_finished')
|
|
528
|
+
this.store.updateRunMessageState(eventId, 'finished');
|
|
529
|
+
else if (event.type === 'session_error' && event.errorDetails?.code === 'loop_continuation_invalidated')
|
|
530
|
+
this.store.updateRunMessageState(eventId, 'invalidated');
|
|
531
|
+
}
|
|
532
532
|
if (event.type !== 'assistant_usage')
|
|
533
533
|
return;
|
|
534
|
-
const
|
|
535
|
-
|
|
534
|
+
const provenance = event.provenance;
|
|
535
|
+
const provenanceRunId = provenance?.kind === 'loop-run'
|
|
536
|
+
? provenance.runId
|
|
537
|
+
: provenance?.kind === 'subagent-request'
|
|
538
|
+
? provenance.loopRunId
|
|
539
|
+
: undefined;
|
|
540
|
+
const provenanceJobId = provenance?.kind === 'loop-run'
|
|
541
|
+
? provenance.jobId
|
|
542
|
+
: provenance?.kind === 'subagent-request'
|
|
543
|
+
? provenance.loopJobId
|
|
544
|
+
: undefined;
|
|
545
|
+
const run = provenanceRunId
|
|
546
|
+
? this.store.getRun(provenanceRunId)
|
|
547
|
+
: eventId
|
|
548
|
+
? this.store.getRunByMessageEventId(eventId)
|
|
549
|
+
: undefined;
|
|
550
|
+
if (!run || (provenanceJobId && run.jobId !== provenanceJobId) || !this.isRunSessionOrDescendant(run, event.piboSessionId))
|
|
551
|
+
return;
|
|
552
|
+
if (provenance?.kind === 'loop-run'
|
|
553
|
+
&& provenance.cause === 'run-reminder'
|
|
554
|
+
&& run.messageEventId !== provenance.rootEventId)
|
|
536
555
|
return;
|
|
537
556
|
const job = this.store.getJob(run.jobId);
|
|
538
557
|
if (!job || job.mode !== 'goal')
|
|
539
558
|
return;
|
|
540
559
|
const basis = run.accounting?.tokenAccounting?.basis ?? goalTokenAccounting(job).basis;
|
|
541
|
-
this.store.
|
|
560
|
+
this.store.recordGoalAssistantUsage(job.id, run.id, {
|
|
561
|
+
usage: event,
|
|
562
|
+
budgetTokens: goalBudgetTokens(event, basis),
|
|
563
|
+
piboSessionId: event.piboSessionId,
|
|
564
|
+
descendant: event.piboSessionId !== run.piboSessionId,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
isRunSessionOrDescendant(run, piboSessionId) {
|
|
568
|
+
if (!run.piboSessionId)
|
|
569
|
+
return false;
|
|
570
|
+
if (run.piboSessionId === piboSessionId)
|
|
571
|
+
return true;
|
|
572
|
+
let current = this.options.context.getSession(piboSessionId);
|
|
573
|
+
const seen = new Set();
|
|
574
|
+
while (current?.parentId && !seen.has(current.parentId)) {
|
|
575
|
+
if (current.parentId === run.piboSessionId)
|
|
576
|
+
return true;
|
|
577
|
+
seen.add(current.parentId);
|
|
578
|
+
current = this.options.context.getSession(current.parentId);
|
|
579
|
+
}
|
|
580
|
+
return false;
|
|
542
581
|
}
|
|
543
582
|
handleProductEvent(event) {
|
|
544
583
|
if (event.type !== 'pibo.loop.fact' && event.type !== 'loop.fact' && event.type !== 'pibo.ralph.fact' && event.type !== 'ralph.fact')
|
package/dist/loops/store.js
CHANGED
|
@@ -4,7 +4,7 @@ import { dirname, resolve } from 'node:path';
|
|
|
4
4
|
import { DatabaseSync } from 'node:sqlite';
|
|
5
5
|
import { piboHomePath } from '../core/pibo-home.js';
|
|
6
6
|
import { isPiboThinkingLevel } from '../core/thinking.js';
|
|
7
|
-
import { newGoalTokenAccounting, normalizeLoopTokenAccounting } from './accounting.js';
|
|
7
|
+
import { addLoopAssistantUsage, newGoalTokenAccounting, normalizeLoopTokenAccounting } from './accounting.js';
|
|
8
8
|
function nowIso(now = new Date()) { return now.toISOString(); }
|
|
9
9
|
function parseJson(json) { return JSON.parse(json); }
|
|
10
10
|
function defaultName(prompt) { const normalized = prompt.replace(/\s+/g, ' ').trim(); return normalized ? normalized.slice(0, 80) : 'Loop job'; }
|
|
@@ -418,29 +418,65 @@ export class PiboLoopStore {
|
|
|
418
418
|
this.db.prepare('UPDATE pibo_ralph_jobs SET enabled = ?, state_json = ?, updated_at = ? WHERE id = ?').run(budgetLimited ? 0 : job.enabled ? 1 : 0, JSON.stringify(state), timestamp, id);
|
|
419
419
|
return this.getJob(id);
|
|
420
420
|
}
|
|
421
|
-
|
|
421
|
+
recordGoalAssistantUsage(id, runId, input, now = new Date()) {
|
|
422
422
|
this.db.exec('BEGIN IMMEDIATE');
|
|
423
423
|
try {
|
|
424
|
-
const job = this.
|
|
425
|
-
if (job
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
424
|
+
const job = this.getJob(id);
|
|
425
|
+
if (!job || job.mode !== 'goal') {
|
|
426
|
+
this.db.exec('COMMIT');
|
|
427
|
+
return job;
|
|
428
|
+
}
|
|
429
|
+
const tokens = Math.max(0, Math.floor(input.budgetTokens));
|
|
430
|
+
const nextTokens = (job.state.tokensUsed ?? 0) + tokens;
|
|
431
|
+
const currentStatus = goalStatus(job) ?? 'active';
|
|
432
|
+
const budgetLimited = currentStatus === 'active' && job.tokenBudget !== undefined && nextTokens >= job.tokenBudget;
|
|
433
|
+
const timestamp = nowIso(now);
|
|
434
|
+
const state = {
|
|
435
|
+
...job.state,
|
|
436
|
+
tokensUsed: nextTokens,
|
|
437
|
+
usage: addLoopAssistantUsage(job.state.usage, input.usage, {
|
|
438
|
+
piboSessionId: input.piboSessionId,
|
|
439
|
+
descendant: input.descendant,
|
|
440
|
+
}),
|
|
441
|
+
goalStatus: budgetLimited ? 'budget_limited' : currentStatus,
|
|
442
|
+
};
|
|
443
|
+
if (budgetLimited)
|
|
444
|
+
state.goalEndedAt = job.state.goalEndedAt ?? timestamp;
|
|
445
|
+
this.db.prepare('UPDATE pibo_ralph_jobs SET enabled = ?, state_json = ?, updated_at = ? WHERE id = ?').run(budgetLimited ? 0 : job.enabled ? 1 : 0, JSON.stringify(state), timestamp, id);
|
|
446
|
+
const row = this.db.prepare('SELECT * FROM pibo_ralph_runs WHERE id = ? AND job_id = ?').get(runId, id);
|
|
447
|
+
if (row) {
|
|
448
|
+
const accounting = parseRunAccounting(row.accounting_json) ?? { tokenAccounting: normalizeLoopTokenAccounting(state.tokenAccounting) };
|
|
449
|
+
const turnTokens = (accounting.tokensUsed ?? 0) + tokens;
|
|
450
|
+
const budget = accounting.tokenBudget;
|
|
451
|
+
const before = accounting.tokensUsedBefore ?? 0;
|
|
452
|
+
const nextAccounting = {
|
|
453
|
+
...accounting,
|
|
454
|
+
tokensUsed: turnTokens,
|
|
455
|
+
usage: addLoopAssistantUsage(accounting.usage, input.usage, {
|
|
456
|
+
piboSessionId: input.piboSessionId,
|
|
457
|
+
descendant: input.descendant,
|
|
458
|
+
}),
|
|
459
|
+
...(budget !== undefined ? { overshootTokens: Math.max(0, before + turnTokens - budget) } : {}),
|
|
460
|
+
};
|
|
461
|
+
this.db.prepare('UPDATE pibo_ralph_runs SET accounting_json = ?, updated_at = ? WHERE id = ?').run(runAccountingJson(nextAccounting), timestamp, runId);
|
|
435
462
|
}
|
|
436
463
|
this.db.exec('COMMIT');
|
|
437
|
-
return
|
|
464
|
+
return this.getJob(id);
|
|
438
465
|
}
|
|
439
466
|
catch (error) {
|
|
440
467
|
this.db.exec('ROLLBACK');
|
|
441
468
|
throw error;
|
|
442
469
|
}
|
|
443
470
|
}
|
|
471
|
+
recordGoalTurnUsage(id, runId, tokens, now = new Date()) {
|
|
472
|
+
const run = this.getRun(runId);
|
|
473
|
+
return this.recordGoalAssistantUsage(id, runId, {
|
|
474
|
+
usage: { type: 'assistant_usage', piboSessionId: run?.piboSessionId ?? 'unknown', totalTokens: Math.max(0, Math.floor(tokens)) },
|
|
475
|
+
budgetTokens: tokens,
|
|
476
|
+
piboSessionId: run?.piboSessionId ?? 'unknown',
|
|
477
|
+
descendant: false,
|
|
478
|
+
}, now);
|
|
479
|
+
}
|
|
444
480
|
recordGoalRunTime(id, runId, activeTimeSeconds, now = new Date()) {
|
|
445
481
|
const seconds = Math.max(0, Math.floor(activeTimeSeconds));
|
|
446
482
|
this.db.exec('BEGIN IMMEDIATE');
|
|
@@ -636,7 +672,11 @@ export class PiboLoopStore {
|
|
|
636
672
|
const completedIterations = (job.state.completedIterations ?? 0) + 1;
|
|
637
673
|
const reachedMaxIterations = job.maxIterations !== undefined && completedIterations >= job.maxIterations;
|
|
638
674
|
const currentGoalStatus = goalStatus(job);
|
|
639
|
-
const nextGoalStatus = job.mode === 'goal'
|
|
675
|
+
const nextGoalStatus = job.mode === 'goal'
|
|
676
|
+
? isTerminalGoalStatus(currentGoalStatus) || currentGoalStatus === 'paused'
|
|
677
|
+
? currentGoalStatus
|
|
678
|
+
: input.goalStatus ?? currentGoalStatus
|
|
679
|
+
: undefined;
|
|
640
680
|
const terminalGoalStatus = job.mode === 'goal' && isTerminalGoalStatus(nextGoalStatus);
|
|
641
681
|
const shouldDisable = terminalGoalStatus || reachedMaxIterations || input.stopAfterRun === true || input.stopEvaluation?.finalAction === 'stop-after-run' || input.stopEvaluation?.finalAction === 'cancel-current-run';
|
|
642
682
|
const state = {
|
|
@@ -833,15 +873,23 @@ export function createLoopMessagePreflight(options = {}) {
|
|
|
833
873
|
const job = store.getJob(jobId);
|
|
834
874
|
const run = store.getRun(runId);
|
|
835
875
|
const status = job?.mode === 'goal' ? goalStatus(job) ?? (job.enabled ? 'active' : 'paused') : undefined;
|
|
876
|
+
const causalReminder = event.provenance.cause === 'run-reminder';
|
|
877
|
+
const validMessageBinding = causalReminder
|
|
878
|
+
? event.source === 'service'
|
|
879
|
+
&& event.text.startsWith('<pibo_run_notification>')
|
|
880
|
+
&& typeof event.provenance.rootEventId === 'string'
|
|
881
|
+
&& run?.messageEventId === event.provenance.rootEventId
|
|
882
|
+
: run?.messageEventId === event.id;
|
|
883
|
+
const validRunState = causalReminder
|
|
884
|
+
? true
|
|
885
|
+
: run?.status === 'running' && Boolean(job?.state.runningAt) && job?.state.lastRunId === runId;
|
|
836
886
|
const allowed = Boolean(job
|
|
837
887
|
&& run
|
|
838
888
|
&& run.jobId === jobId
|
|
839
|
-
&&
|
|
840
|
-
&&
|
|
889
|
+
&& validMessageBinding
|
|
890
|
+
&& validRunState
|
|
841
891
|
&& (!run.piboSessionId || run.piboSessionId === event.piboSessionId)
|
|
842
892
|
&& job.enabled
|
|
843
|
-
&& job.state.runningAt
|
|
844
|
-
&& job.state.lastRunId === runId
|
|
845
893
|
&& (job.mode !== 'goal' || status === 'active'));
|
|
846
894
|
if (allowed)
|
|
847
895
|
return { allowed: true };
|
package/dist/loops/tools.js
CHANGED
|
@@ -34,7 +34,9 @@ function resolveGoalForTurn(store, context, piboSessionId) {
|
|
|
34
34
|
if (provenance?.kind !== 'loop-run')
|
|
35
35
|
return store.getSessionGoalOwner(piboSessionId) ?? store.getLatestGoalForSession(piboSessionId);
|
|
36
36
|
const run = store.getRun(provenance.runId);
|
|
37
|
-
|
|
37
|
+
const expectedEventId = provenance.cause === 'run-reminder' ? provenance.rootEventId : activeMessage?.id;
|
|
38
|
+
const validReminder = provenance.cause !== 'run-reminder' || (activeMessage?.source === 'service' && typeof provenance.rootEventId === 'string');
|
|
39
|
+
if (!run || !validReminder || run.jobId !== provenance.jobId || run.piboSessionId !== piboSessionId || run.messageEventId !== expectedEventId) {
|
|
38
40
|
throw new Error('cannot resolve goal because this turn has stale or invalid Loop provenance');
|
|
39
41
|
}
|
|
40
42
|
const job = store.getJob(provenance.jobId);
|
|
@@ -149,7 +149,8 @@ export class PiboReliabilityStore {
|
|
|
149
149
|
timeout_at TEXT,
|
|
150
150
|
timeout_phase TEXT,
|
|
151
151
|
service_warning TEXT,
|
|
152
|
-
resource_json TEXT
|
|
152
|
+
resource_json TEXT,
|
|
153
|
+
origin_json TEXT
|
|
153
154
|
);
|
|
154
155
|
CREATE INDEX IF NOT EXISTS idx_pibo_runs_controller_updated
|
|
155
156
|
ON pibo_runs(controller_pibo_session_id, updated_at);
|
|
@@ -161,6 +162,7 @@ export class PiboReliabilityStore {
|
|
|
161
162
|
ensurePiboRunColumn(this.db, "timeout_phase", "TEXT");
|
|
162
163
|
ensurePiboRunColumn(this.db, "service_warning", "TEXT");
|
|
163
164
|
ensurePiboRunColumn(this.db, "resource_json", "TEXT");
|
|
165
|
+
ensurePiboRunColumn(this.db, "origin_json", "TEXT");
|
|
164
166
|
this.appendEventStatement = this.db.prepare(`
|
|
165
167
|
INSERT INTO pibo_event_stream (topic, key, event_id, idempotency_key, created_at, retention_class, payload_json)
|
|
166
168
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
@@ -502,10 +504,10 @@ export class PiboReliabilityStore {
|
|
|
502
504
|
INSERT INTO pibo_runs (
|
|
503
505
|
run_id, kind, controller_pibo_session_id, status, completion_policy, consumed, tool_name,
|
|
504
506
|
summary, result_json, error, notified_status, acknowledged_status, created_at, updated_at,
|
|
505
|
-
completed_at, job_id, retryable, max_attempts, timeout_ms, timeout_at, timeout_phase, service_warning, resource_json
|
|
506
|
-
) VALUES (?, 'tool', ?, 'running', ?, 0, ?, ?, NULL, NULL, NULL, NULL, ?, ?, NULL, ?, ?, ?, ?, ?, NULL, ?, ?)
|
|
507
|
+
completed_at, job_id, retryable, max_attempts, timeout_ms, timeout_at, timeout_phase, service_warning, resource_json, origin_json
|
|
508
|
+
) VALUES (?, 'tool', ?, 'running', ?, 0, ?, ?, NULL, NULL, NULL, NULL, ?, ?, NULL, ?, ?, ?, ?, ?, NULL, ?, ?, ?)
|
|
507
509
|
`)
|
|
508
|
-
.run(runId, input.controllerPiboSessionId, input.completionPolicy, input.toolName, `${input.toolName} run is running.`, timestamp, timestamp, job.jobId, input.retryable ? 1 : 0, maxAttempts, input.timeoutMs ?? null, timeoutAt ?? null, input.serviceWarning ?? null, input.resources ? JSON.stringify(input.resources) : null);
|
|
510
|
+
.run(runId, input.controllerPiboSessionId, input.completionPolicy, input.toolName, `${input.toolName} run is running.`, timestamp, timestamp, job.jobId, input.retryable ? 1 : 0, maxAttempts, input.timeoutMs ?? null, timeoutAt ?? null, input.serviceWarning ?? null, input.resources ? JSON.stringify(input.resources) : null, input.origin ? JSON.stringify(input.origin) : null);
|
|
509
511
|
this.claimJob(job.jobId, input.workerId ?? `run-registry:${process.pid}`, 24 * 60 * 60 * 1000);
|
|
510
512
|
return this.requireRun(runId);
|
|
511
513
|
}
|
|
@@ -534,10 +536,11 @@ export class PiboReliabilityStore {
|
|
|
534
536
|
timeout_at = ?,
|
|
535
537
|
timeout_phase = ?,
|
|
536
538
|
service_warning = ?,
|
|
537
|
-
resource_json =
|
|
539
|
+
resource_json = ?,
|
|
540
|
+
origin_json = ?
|
|
538
541
|
WHERE run_id = ?
|
|
539
542
|
`)
|
|
540
|
-
.run(next.status, next.completionPolicy, next.consumed ? 1 : 0, next.summary ?? null, next.result ? JSON.stringify(next.result) : null, next.error ?? null, next.notifiedStatus ?? null, next.acknowledgedStatus ?? null, next.updatedAt, next.completedAt ?? null, next.jobId ?? null, next.retryable ? 1 : 0, next.maxAttempts, next.timeoutMs ?? null, next.timeoutAt ?? null, next.timeoutPhase ?? null, next.serviceWarning ?? null, next.resources ? JSON.stringify(next.resources) : null, runId);
|
|
543
|
+
.run(next.status, next.completionPolicy, next.consumed ? 1 : 0, next.summary ?? null, next.result ? JSON.stringify(next.result) : null, next.error ?? null, next.notifiedStatus ?? null, next.acknowledgedStatus ?? null, next.updatedAt, next.completedAt ?? null, next.jobId ?? null, next.retryable ? 1 : 0, next.maxAttempts, next.timeoutMs ?? null, next.timeoutAt ?? null, next.timeoutPhase ?? null, next.serviceWarning ?? null, next.resources ? JSON.stringify(next.resources) : null, next.origin ? JSON.stringify(next.origin) : null, runId);
|
|
541
544
|
return this.requireRun(runId);
|
|
542
545
|
}
|
|
543
546
|
getRun(runId) {
|
|
@@ -810,6 +813,8 @@ function runFromRow(row) {
|
|
|
810
813
|
output.serviceWarning = row.service_warning;
|
|
811
814
|
if (row.resource_json)
|
|
812
815
|
output.resources = JSON.parse(row.resource_json);
|
|
816
|
+
if (row.origin_json)
|
|
817
|
+
output.origin = JSON.parse(row.origin_json);
|
|
813
818
|
return output;
|
|
814
819
|
}
|
|
815
820
|
function retryDelayMs(attempts, input) {
|
package/dist/runs/lifecycle.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export const PIBO_RUN_CANCELLATION_SETTLEMENT_TIMEOUT_MS = 15_000;
|
|
1
2
|
export class PiboRunExecutionTimeoutError extends Error {
|
|
2
3
|
timeoutPhase;
|
|
3
4
|
constructor(message, timeoutPhase) {
|
|
@@ -6,6 +7,34 @@ export class PiboRunExecutionTimeoutError extends Error {
|
|
|
6
7
|
this.name = "PiboRunExecutionTimeoutError";
|
|
7
8
|
}
|
|
8
9
|
}
|
|
10
|
+
export class PiboRunCancellationError extends Error {
|
|
11
|
+
constructor(message, options) {
|
|
12
|
+
super(message, options);
|
|
13
|
+
this.name = "PiboRunCancellationError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class PiboRunCancelledError extends Error {
|
|
17
|
+
constructor(message = "Yielded run was cancelled.", options) {
|
|
18
|
+
super(message, options);
|
|
19
|
+
this.name = "PiboRunCancelledError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function waitForRunCancellationSettlement(settled, timeoutMs = PIBO_RUN_CANCELLATION_SETTLEMENT_TIMEOUT_MS) {
|
|
23
|
+
let timer;
|
|
24
|
+
try {
|
|
25
|
+
await Promise.race([
|
|
26
|
+
settled,
|
|
27
|
+
new Promise((_resolve, reject) => {
|
|
28
|
+
timer = setTimeout(() => reject(new Error(`Yielded run did not settle within ${timeoutMs}ms after cancellation.`)), timeoutMs);
|
|
29
|
+
timer.unref?.();
|
|
30
|
+
}),
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
if (timer)
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
9
38
|
export function resolveRunTimeoutMs(toolName, params) {
|
|
10
39
|
if (!params || typeof params !== "object" || Array.isArray(params))
|
|
11
40
|
return undefined;
|
|
@@ -26,7 +55,15 @@ export function foregroundServiceWarning(toolName, params, timeoutMs) {
|
|
|
26
55
|
}
|
|
27
56
|
export function isConfiguredTimeoutError(error) {
|
|
28
57
|
const message = error instanceof Error ? error.message : String(error);
|
|
29
|
-
|
|
58
|
+
const terminalLines = message.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-5);
|
|
59
|
+
return terminalLines.some((line) => {
|
|
60
|
+
const normalized = line.replace(/^error:\s*/i, "");
|
|
61
|
+
return /^(?:command|process|tool execution|yielded run)\s+timed?\s*out\b.*$/i.test(normalized)
|
|
62
|
+
|| /^timed?\s*out(?:\s+after\s+.+)?[.!]?$/i.test(normalized)
|
|
63
|
+
|| /^timeout(?:\s+error)?[.!]?$/i.test(normalized)
|
|
64
|
+
|| /^timeout(?::|\s+)(?:occurred|expired|exceeded|elapsed|reached)\b.*$/i.test(normalized)
|
|
65
|
+
|| /^timeout(?::|\s+)(?:after\s+)?\d+(?:\.\d+)?\s*(?:ms|milliseconds?|s|secs?|seconds?|m|mins?|minutes?|h|hours?)\b.*$/i.test(normalized);
|
|
66
|
+
});
|
|
30
67
|
}
|
|
31
68
|
export function hasMeaningfulTimeoutOutput(value) {
|
|
32
69
|
const text = extractText(value);
|
package/dist/runs/registry.js
CHANGED
|
@@ -7,6 +7,25 @@ function now() {
|
|
|
7
7
|
function runTimeoutAt(createdAt, timeoutMs) {
|
|
8
8
|
return timeoutMs === undefined ? undefined : new Date(Date.parse(createdAt) + timeoutMs).toISOString();
|
|
9
9
|
}
|
|
10
|
+
function sameOrigin(left, right) {
|
|
11
|
+
if (!left || !right)
|
|
12
|
+
return left === right;
|
|
13
|
+
if (left.eventId !== right.eventId || left.provenance.kind !== right.provenance.kind)
|
|
14
|
+
return false;
|
|
15
|
+
if (left.provenance.kind === "loop-run" && right.provenance.kind === "loop-run") {
|
|
16
|
+
return left.provenance.jobId === right.provenance.jobId
|
|
17
|
+
&& left.provenance.runId === right.provenance.runId
|
|
18
|
+
&& left.provenance.cause === right.provenance.cause
|
|
19
|
+
&& left.provenance.rootEventId === right.provenance.rootEventId;
|
|
20
|
+
}
|
|
21
|
+
if (left.provenance.kind === "subagent-request" && right.provenance.kind === "subagent-request") {
|
|
22
|
+
return left.provenance.requestId === right.provenance.requestId
|
|
23
|
+
&& left.provenance.controllerPiboSessionId === right.provenance.controllerPiboSessionId
|
|
24
|
+
&& left.provenance.loopJobId === right.provenance.loopJobId
|
|
25
|
+
&& left.provenance.loopRunId === right.provenance.loopRunId;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
10
29
|
function formatTimeout(timeoutMs) {
|
|
11
30
|
if (timeoutMs === undefined)
|
|
12
31
|
return "its configured timeout";
|
|
@@ -83,6 +102,7 @@ export class PiboRunRegistry {
|
|
|
83
102
|
serviceWarning: input.serviceWarning,
|
|
84
103
|
resources: input.resources,
|
|
85
104
|
workerId: this.workerId,
|
|
105
|
+
origin: input.origin,
|
|
86
106
|
});
|
|
87
107
|
const record = recordFromStored(stored);
|
|
88
108
|
this.runs.set(record.runId, record);
|
|
@@ -108,6 +128,7 @@ export class PiboRunRegistry {
|
|
|
108
128
|
...(input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs, timeoutAt: runTimeoutAt(timestamp, input.timeoutMs) } : {}),
|
|
109
129
|
...(input.serviceWarning ? { serviceWarning: input.serviceWarning } : {}),
|
|
110
130
|
...(input.resources ? { resources: structuredClone(input.resources) } : {}),
|
|
131
|
+
...(input.origin ? { origin: structuredClone(input.origin) } : {}),
|
|
111
132
|
};
|
|
112
133
|
this.runs.set(runId, record);
|
|
113
134
|
const output = snapshot(record);
|
|
@@ -206,6 +227,16 @@ export class PiboRunRegistry {
|
|
|
206
227
|
.filter((record) => options.includeDetached || record.completionPolicy !== "detached")
|
|
207
228
|
.map(snapshot);
|
|
208
229
|
}
|
|
230
|
+
listActiveControllerRuns(controllerPiboSessionId) {
|
|
231
|
+
return [...this.runs.values()]
|
|
232
|
+
.filter((record) => record.controllerPiboSessionId === controllerPiboSessionId && !terminal(record.status))
|
|
233
|
+
.map(snapshot);
|
|
234
|
+
}
|
|
235
|
+
listActiveRuns() {
|
|
236
|
+
return [...this.runs.values()]
|
|
237
|
+
.filter((record) => !terminal(record.status))
|
|
238
|
+
.map(snapshot);
|
|
239
|
+
}
|
|
209
240
|
status(controllerPiboSessionId, runId) {
|
|
210
241
|
return snapshot(this.requireRunForController(controllerPiboSessionId, runId));
|
|
211
242
|
}
|
|
@@ -245,7 +276,7 @@ export class PiboRunRegistry {
|
|
|
245
276
|
}
|
|
246
277
|
read(controllerPiboSessionId, runId) {
|
|
247
278
|
const record = this.requireRunForController(controllerPiboSessionId, runId);
|
|
248
|
-
if (terminal(record.status)) {
|
|
279
|
+
if (terminal(record.status) && !record.consumed) {
|
|
249
280
|
record.consumed = true;
|
|
250
281
|
record.updatedAt = now();
|
|
251
282
|
this.options.store?.updateRun(runId, record);
|
|
@@ -258,25 +289,29 @@ export class PiboRunRegistry {
|
|
|
258
289
|
output.error = record.error;
|
|
259
290
|
return output;
|
|
260
291
|
}
|
|
261
|
-
cancel(controllerPiboSessionId, runId) {
|
|
292
|
+
cancel(controllerPiboSessionId, runId, reason = "Run was cancelled.") {
|
|
262
293
|
const record = this.requireRunForController(controllerPiboSessionId, runId);
|
|
263
294
|
const previousStatus = record.status;
|
|
264
295
|
if (!terminal(record.status)) {
|
|
265
296
|
record.status = "cancelled";
|
|
297
|
+
record.error = reason;
|
|
266
298
|
record.summary = `${record.toolName} run cancelled.`;
|
|
267
299
|
this.finish(record);
|
|
268
300
|
if (record.jobId)
|
|
269
|
-
this.options.store?.fail(record.jobId, this.workerId,
|
|
301
|
+
this.options.store?.fail(record.jobId, this.workerId, reason);
|
|
270
302
|
}
|
|
271
303
|
record.consumed = true;
|
|
272
304
|
record.updatedAt = now();
|
|
273
305
|
this.options.store?.updateRun(runId, record);
|
|
274
306
|
const output = snapshot(record);
|
|
275
|
-
this.notify({ type: "run_changed", run: output, previousStatus, reason
|
|
307
|
+
this.notify({ type: "run_changed", run: output, previousStatus, reason });
|
|
276
308
|
return output;
|
|
277
309
|
}
|
|
278
310
|
ack(controllerPiboSessionId, runId) {
|
|
279
311
|
const record = this.requireRunForController(controllerPiboSessionId, runId);
|
|
312
|
+
const consumesTerminalRun = terminal(record.status) && !record.consumed;
|
|
313
|
+
if (record.acknowledgedStatus === record.status && !consumesTerminalRun)
|
|
314
|
+
return { ...snapshot(record), changed: false };
|
|
280
315
|
record.acknowledgedStatus = record.status;
|
|
281
316
|
if (terminal(record.status))
|
|
282
317
|
record.consumed = true;
|
|
@@ -284,7 +319,7 @@ export class PiboRunRegistry {
|
|
|
284
319
|
this.options.store?.updateRun(runId, record);
|
|
285
320
|
const output = snapshot(record);
|
|
286
321
|
this.notify({ type: "run_acknowledged", run: output });
|
|
287
|
-
return output;
|
|
322
|
+
return { ...output, changed: true };
|
|
288
323
|
}
|
|
289
324
|
suppressNotification(controllerPiboSessionId, runId) {
|
|
290
325
|
const record = this.requireRunForController(controllerPiboSessionId, runId);
|
|
@@ -306,14 +341,17 @@ export class PiboRunRegistry {
|
|
|
306
341
|
return suppressed;
|
|
307
342
|
}
|
|
308
343
|
createNotification(controllerPiboSessionId, options = {}) {
|
|
309
|
-
const
|
|
310
|
-
if (
|
|
344
|
+
const pendingRecords = [...this.runs.values()].filter((record) => this.needsNotification(record, controllerPiboSessionId, options));
|
|
345
|
+
if (pendingRecords.length === 0)
|
|
311
346
|
return undefined;
|
|
347
|
+
const origin = pendingRecords[0].origin;
|
|
348
|
+
const records = pendingRecords.filter((record) => sameOrigin(record.origin, origin));
|
|
312
349
|
for (const record of records) {
|
|
313
350
|
record.notifiedStatus = record.status;
|
|
314
351
|
this.options.store?.updateRun(record.runId, record);
|
|
315
352
|
}
|
|
316
353
|
const notification = {
|
|
354
|
+
...(origin ? { origin: structuredClone(origin) } : {}),
|
|
317
355
|
completed: [],
|
|
318
356
|
failed: [],
|
|
319
357
|
timedOut: [],
|
|
@@ -338,44 +376,6 @@ export class PiboRunRegistry {
|
|
|
338
376
|
hasPendingNotification(controllerPiboSessionId, options = {}) {
|
|
339
377
|
return [...this.runs.values()].some((record) => this.needsNotification(record, controllerPiboSessionId, options));
|
|
340
378
|
}
|
|
341
|
-
cancelControllerRuns(controllerPiboSessionId, reason = "Controller Pibo session was disposed.") {
|
|
342
|
-
const cancelled = [];
|
|
343
|
-
for (const record of this.runs.values()) {
|
|
344
|
-
if (record.controllerPiboSessionId !== controllerPiboSessionId || terminal(record.status))
|
|
345
|
-
continue;
|
|
346
|
-
record.status = "cancelled";
|
|
347
|
-
record.error = reason;
|
|
348
|
-
record.consumed = true;
|
|
349
|
-
record.summary = `${record.toolName} run cancelled.`;
|
|
350
|
-
this.finish(record);
|
|
351
|
-
this.options.store?.updateRun(record.runId, record);
|
|
352
|
-
if (record.jobId)
|
|
353
|
-
this.options.store?.fail(record.jobId, this.workerId, reason);
|
|
354
|
-
const output = snapshot(record);
|
|
355
|
-
this.notify({ type: "run_changed", run: output, previousStatus: "running", reason });
|
|
356
|
-
cancelled.push(output);
|
|
357
|
-
}
|
|
358
|
-
return cancelled;
|
|
359
|
-
}
|
|
360
|
-
cancelAll(reason = "Run registry was disposed.") {
|
|
361
|
-
const cancelled = [];
|
|
362
|
-
for (const record of this.runs.values()) {
|
|
363
|
-
if (terminal(record.status))
|
|
364
|
-
continue;
|
|
365
|
-
record.status = "cancelled";
|
|
366
|
-
record.error = reason;
|
|
367
|
-
record.consumed = true;
|
|
368
|
-
record.summary = `${record.toolName} run cancelled.`;
|
|
369
|
-
this.finish(record);
|
|
370
|
-
this.options.store?.updateRun(record.runId, record);
|
|
371
|
-
if (record.jobId)
|
|
372
|
-
this.options.store?.fail(record.jobId, this.workerId, reason);
|
|
373
|
-
const output = snapshot(record);
|
|
374
|
-
this.notify({ type: "run_changed", run: output, previousStatus: "running", reason });
|
|
375
|
-
cancelled.push(output);
|
|
376
|
-
}
|
|
377
|
-
return cancelled;
|
|
378
|
-
}
|
|
379
379
|
prune(options = {}) {
|
|
380
380
|
const nowMs = options.nowMs ?? Date.now();
|
|
381
381
|
const consumedTerminalTtlMs = options.consumedTerminalTtlMs ??
|
|
@@ -461,5 +461,6 @@ function recordFromStored(record) {
|
|
|
461
461
|
timeoutPhase: record.timeoutPhase,
|
|
462
462
|
serviceWarning: record.serviceWarning,
|
|
463
463
|
resources: record.resources,
|
|
464
|
+
origin: record.origin,
|
|
464
465
|
};
|
|
465
466
|
}
|