@pasko70/pibo 2.4.0 → 2.4.2
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/routed-session.js +151 -7
- package/dist/agent-runtimes/codex-native/turn.js +3 -1
- package/dist/agent-runtimes/omp/turn.js +36 -7
- package/dist/agent-runtimes/pi/routed-session.js +62 -16
- package/dist/apps/chat/web-app.js +6 -8
- package/dist/apps/chat-ui/assets/{dist-CUcAofmV.js → dist-3YG57JXi.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Byygd1lH.js → dist-BeqHbnGN.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DusFwy0L.js → dist-CrDtveZB.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-C9BrS7sL.js → dist-Cw9po47P.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D4RU6xu3.js → dist-DTRjeLwO.js} +1 -1
- package/dist/apps/chat-ui/assets/{index-Bifi_kjN.js → index-AjnP3ci-.js} +89 -89
- package/dist/apps/chat-ui/index.html +1 -1
- package/dist/apps/chat-vscode-web/assets/{index-WsLm1mo3.js → index-DvTSSvzN.js} +5 -5
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/{pibo-vscode-ext-2.4.0.vsix → pibo-vscode-ext-2.4.2.vsix} +0 -0
- package/dist/core/session-router.js +153 -63
- package/dist/gateway/web.js +8 -1
- package/dist/loops/accounting.js +27 -0
- package/dist/loops/cli.js +6 -5
- package/dist/loops/prompts.js +13 -5
- package/dist/loops/service.js +8 -2
- package/dist/loops/store.js +27 -11
- package/dist/loops/tools.js +10 -5
- package/dist/mcp/config-command.js +3 -2
- package/dist/mcp/config.js +10 -4
- package/dist/mcp/errors.js +1 -1
- package/dist/mcp/index.js +19 -33
- package/dist/reliability/store.js +11 -6
- package/dist/runs/lifecycle.js +12 -0
- package/dist/runs/registry.js +23 -4
- package/dist/runs/tools.js +12 -3
- package/dist/session-ui/sessionActivity.js +6 -2
- package/dist/signals/status.js +9 -4
- package/dist/tools/guides.js +1 -1
- package/dist/web/channel.js +10 -4
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/builtin/loop/SKILL.md +1 -1
package/dist/loops/store.js
CHANGED
|
@@ -4,6 +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
8
|
function nowIso(now = new Date()) { return now.toISOString(); }
|
|
8
9
|
function parseJson(json) { return JSON.parse(json); }
|
|
9
10
|
function defaultName(prompt) { const normalized = prompt.replace(/\s+/g, ' ').trim(); return normalized ? normalized.slice(0, 80) : 'Loop job'; }
|
|
@@ -80,7 +81,9 @@ function parseRunAccounting(json) {
|
|
|
80
81
|
return undefined;
|
|
81
82
|
try {
|
|
82
83
|
const value = JSON.parse(json);
|
|
83
|
-
return value && typeof value === 'object' && !Array.isArray(value)
|
|
84
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
85
|
+
? { ...value, tokenAccounting: normalizeLoopTokenAccounting(value.tokenAccounting) }
|
|
86
|
+
: undefined;
|
|
84
87
|
}
|
|
85
88
|
catch {
|
|
86
89
|
return undefined;
|
|
@@ -104,7 +107,7 @@ function normalizeJobState(state, mode, enabled, createdAt) {
|
|
|
104
107
|
return state;
|
|
105
108
|
const activeTimeSeconds = Math.max(0, Math.floor(state.activeTimeSeconds ?? state.timeUsedSeconds ?? 0));
|
|
106
109
|
const goalStartedAt = state.goalStartedAt ?? (enabled || (state.completedIterations ?? 0) > 0 || (state.tokensUsed ?? 0) > 0 || (state.goalStatus !== undefined && state.goalStatus !== 'paused') ? createdAt : undefined);
|
|
107
|
-
const normalized = { ...state, activeTimeSeconds, ...(goalStartedAt ? { goalStartedAt } : {}) };
|
|
110
|
+
const normalized = { ...state, tokenAccounting: normalizeLoopTokenAccounting(state.tokenAccounting), activeTimeSeconds, ...(goalStartedAt ? { goalStartedAt } : {}) };
|
|
108
111
|
delete normalized.timeUsedSeconds;
|
|
109
112
|
return normalized;
|
|
110
113
|
}
|
|
@@ -296,7 +299,7 @@ export class PiboLoopStore {
|
|
|
296
299
|
const enabled = input.enabled === true;
|
|
297
300
|
const state = {
|
|
298
301
|
completedIterations: 0,
|
|
299
|
-
...(mode === 'goal' ? { goalStatus: enabled ? 'active' : 'paused', tokensUsed: 0, activeTimeSeconds: 0, ...(enabled ? { goalStartedAt: timestamp } : {}) } : {}),
|
|
302
|
+
...(mode === 'goal' ? { goalStatus: enabled ? 'active' : 'paused', tokenAccounting: newGoalTokenAccounting(), tokensUsed: 0, activeTimeSeconds: 0, ...(enabled ? { goalStartedAt: timestamp } : {}) } : {}),
|
|
300
303
|
...(input.initialPiboSessionId?.trim() ? { lastPiboSessionId: input.initialPiboSessionId.trim() } : {}),
|
|
301
304
|
};
|
|
302
305
|
const job = { id: mode === 'ralph' ? `ralph_${randomUUID()}` : `loop_${randomUUID()}`, mode, name: (input.name ?? defaultName(input.prompt)).trim(), description: input.description?.trim() || undefined, enabled, target, profile: input.profile, prompt: input.prompt, maxIterations: normalizeMaxIterations(input.maxIterations), tokenBudget: normalizeTokenBudget(input.tokenBudget), tokenReserve: normalizeTokenReserve(input.tokenReserve), stopPolicy: normalizeLoopStopPolicy(input.stopPolicy), ...runtimeOptions, ...(resources ? { resources } : {}), state, createdAt: timestamp, updatedAt: timestamp };
|
|
@@ -422,7 +425,7 @@ export class PiboLoopStore {
|
|
|
422
425
|
if (job?.mode === 'goal') {
|
|
423
426
|
const row = this.db.prepare('SELECT * FROM pibo_ralph_runs WHERE id = ? AND job_id = ?').get(runId, id);
|
|
424
427
|
if (row) {
|
|
425
|
-
const accounting = parseRunAccounting(row.accounting_json) ?? {};
|
|
428
|
+
const accounting = parseRunAccounting(row.accounting_json) ?? { tokenAccounting: normalizeLoopTokenAccounting(job.state.tokenAccounting) };
|
|
426
429
|
const turnTokens = (accounting.tokensUsed ?? 0) + Math.max(0, Math.floor(tokens));
|
|
427
430
|
const budget = accounting.tokenBudget;
|
|
428
431
|
const before = accounting.tokensUsedBefore ?? 0;
|
|
@@ -446,7 +449,7 @@ export class PiboLoopStore {
|
|
|
446
449
|
if (job?.mode === 'goal') {
|
|
447
450
|
const row = this.db.prepare('SELECT * FROM pibo_ralph_runs WHERE id = ? AND job_id = ?').get(runId, id);
|
|
448
451
|
if (row) {
|
|
449
|
-
const accounting = { ...(parseRunAccounting(row.accounting_json) ?? {}), activeTimeSeconds: seconds };
|
|
452
|
+
const accounting = { ...(parseRunAccounting(row.accounting_json) ?? { tokenAccounting: normalizeLoopTokenAccounting(job.state.tokenAccounting) }), activeTimeSeconds: seconds };
|
|
450
453
|
this.db.prepare('UPDATE pibo_ralph_runs SET accounting_json = ?, updated_at = ? WHERE id = ?').run(runAccountingJson(accounting), nowIso(now), runId);
|
|
451
454
|
}
|
|
452
455
|
}
|
|
@@ -481,7 +484,7 @@ export class PiboLoopStore {
|
|
|
481
484
|
const enabled = patch.enabled ?? existing.enabled;
|
|
482
485
|
let state = mode === existing.mode
|
|
483
486
|
? { ...existing.state }
|
|
484
|
-
: { completedIterations: existing.state.completedIterations ?? 0, ...(mode === 'goal' ? { goalStatus: enabled ? 'active' : 'paused', tokensUsed: 0, activeTimeSeconds: 0, ...(enabled ? { goalStartedAt: nowIso(now) } : {}) } : {}) };
|
|
487
|
+
: { completedIterations: existing.state.completedIterations ?? 0, ...(mode === 'goal' ? { goalStatus: enabled ? 'active' : 'paused', tokenAccounting: newGoalTokenAccounting(), tokensUsed: 0, activeTimeSeconds: 0, ...(enabled ? { goalStartedAt: nowIso(now) } : {}) } : {}) };
|
|
485
488
|
if (mode === 'goal' && patch.enabled !== undefined) {
|
|
486
489
|
const currentGoalStatus = goalStatus({ mode, enabled: existing.enabled, state: existing.state }) ?? 'paused';
|
|
487
490
|
if (patch.enabled) {
|
|
@@ -633,7 +636,11 @@ export class PiboLoopStore {
|
|
|
633
636
|
const completedIterations = (job.state.completedIterations ?? 0) + 1;
|
|
634
637
|
const reachedMaxIterations = job.maxIterations !== undefined && completedIterations >= job.maxIterations;
|
|
635
638
|
const currentGoalStatus = goalStatus(job);
|
|
636
|
-
const nextGoalStatus = job.mode === 'goal'
|
|
639
|
+
const nextGoalStatus = job.mode === 'goal'
|
|
640
|
+
? isTerminalGoalStatus(currentGoalStatus) || currentGoalStatus === 'paused'
|
|
641
|
+
? currentGoalStatus
|
|
642
|
+
: input.goalStatus ?? currentGoalStatus
|
|
643
|
+
: undefined;
|
|
637
644
|
const terminalGoalStatus = job.mode === 'goal' && isTerminalGoalStatus(nextGoalStatus);
|
|
638
645
|
const shouldDisable = terminalGoalStatus || reachedMaxIterations || input.stopAfterRun === true || input.stopEvaluation?.finalAction === 'stop-after-run' || input.stopEvaluation?.finalAction === 'cancel-current-run';
|
|
639
646
|
const state = {
|
|
@@ -807,6 +814,7 @@ export class PiboLoopStore {
|
|
|
807
814
|
createRunLocked(job, timestamp) {
|
|
808
815
|
const tokensUsedBefore = job.state.tokensUsed ?? 0;
|
|
809
816
|
const accounting = job.mode === 'goal' ? {
|
|
817
|
+
tokenAccounting: normalizeLoopTokenAccounting(job.state.tokenAccounting),
|
|
810
818
|
...(job.tokenBudget !== undefined ? { tokenBudget: job.tokenBudget, remainingTokensBefore: Math.max(0, job.tokenBudget - tokensUsedBefore) } : {}),
|
|
811
819
|
...(job.tokenReserve !== undefined ? { tokenReserve: job.tokenReserve } : {}),
|
|
812
820
|
tokensUsedBefore,
|
|
@@ -829,15 +837,23 @@ export function createLoopMessagePreflight(options = {}) {
|
|
|
829
837
|
const job = store.getJob(jobId);
|
|
830
838
|
const run = store.getRun(runId);
|
|
831
839
|
const status = job?.mode === 'goal' ? goalStatus(job) ?? (job.enabled ? 'active' : 'paused') : undefined;
|
|
840
|
+
const causalReminder = event.provenance.cause === 'run-reminder';
|
|
841
|
+
const validMessageBinding = causalReminder
|
|
842
|
+
? event.source === 'service'
|
|
843
|
+
&& event.text.startsWith('<pibo_run_notification>')
|
|
844
|
+
&& typeof event.provenance.rootEventId === 'string'
|
|
845
|
+
&& run?.messageEventId === event.provenance.rootEventId
|
|
846
|
+
: run?.messageEventId === event.id;
|
|
847
|
+
const validRunState = causalReminder
|
|
848
|
+
? true
|
|
849
|
+
: run?.status === 'running' && Boolean(job?.state.runningAt) && job?.state.lastRunId === runId;
|
|
832
850
|
const allowed = Boolean(job
|
|
833
851
|
&& run
|
|
834
852
|
&& run.jobId === jobId
|
|
835
|
-
&&
|
|
836
|
-
&&
|
|
853
|
+
&& validMessageBinding
|
|
854
|
+
&& validRunState
|
|
837
855
|
&& (!run.piboSessionId || run.piboSessionId === event.piboSessionId)
|
|
838
856
|
&& job.enabled
|
|
839
|
-
&& job.state.runningAt
|
|
840
|
-
&& job.state.lastRunId === runId
|
|
841
857
|
&& (job.mode !== 'goal' || status === 'active'));
|
|
842
858
|
if (allowed)
|
|
843
859
|
return { allowed: true };
|
package/dist/loops/tools.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { piboStringEnum } from "../tools/schema.js";
|
|
3
3
|
import { definePiboTool } from "../tools/contract.js";
|
|
4
|
-
import { goalActiveTimeSeconds, goalCanStartNextTurn, goalElapsedWallClockSeconds, goalRemainingTokens } from './accounting.js';
|
|
4
|
+
import { goalActiveTimeSeconds, goalCanStartNextTurn, goalElapsedWallClockSeconds, goalRemainingTokens, goalTokenAccounting } from './accounting.js';
|
|
5
5
|
import { createDefaultPiboLoopStore } from './store.js';
|
|
6
6
|
export const PIBO_GOAL_TOOL_NAMES = ['get_goal', 'create_goal', 'update_goal'];
|
|
7
7
|
let configuredStorePath;
|
|
@@ -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);
|
|
@@ -68,11 +70,13 @@ function nonNegativeInteger(value, field) {
|
|
|
68
70
|
}
|
|
69
71
|
function goalPayload(job) {
|
|
70
72
|
const tokenBudget = job.tokenBudget;
|
|
73
|
+
const tokenAccounting = goalTokenAccounting(job);
|
|
71
74
|
return {
|
|
72
75
|
goalId: job.id,
|
|
73
76
|
objective: job.prompt,
|
|
74
77
|
status: effectiveGoalStatus(job),
|
|
75
78
|
budgetType: tokenBudget === undefined ? 'unbounded' : 'soft',
|
|
79
|
+
tokenAccounting,
|
|
76
80
|
tokenBudget: tokenBudget ?? null,
|
|
77
81
|
tokenReserve: job.tokenReserve ?? 0,
|
|
78
82
|
tokensUsed: job.state.tokensUsed ?? 0,
|
|
@@ -130,8 +134,8 @@ function createCreateGoalTool(context, options) {
|
|
|
130
134
|
promptSnippet: 'Call create_goal only when the user or system explicitly requests a persistent goal. Do not infer a goal from an ordinary task.',
|
|
131
135
|
inputSchema: Type.Object({
|
|
132
136
|
objective: Type.String({ description: 'Concrete objective to pursue across automatic continuations.' }),
|
|
133
|
-
token_budget: Type.Optional(Type.Number({ description: 'Optional soft token budget. Usage arrives after each model response, so the final turn can overshoot.' })),
|
|
134
|
-
token_reserve: Type.Optional(Type.Number({ description: 'Optional non-negative minimum remaining tokens required before Pibo starts another turn.' })),
|
|
137
|
+
token_budget: Type.Optional(Type.Number({ description: 'Optional soft uncached-token budget. Cache reads and writes are excluded. Usage arrives after each model response, so the final turn can overshoot.' })),
|
|
138
|
+
token_reserve: Type.Optional(Type.Number({ description: 'Optional non-negative minimum remaining uncached tokens required before Pibo starts another turn.' })),
|
|
135
139
|
}),
|
|
136
140
|
async execute(_toolCallId, params) {
|
|
137
141
|
try {
|
|
@@ -182,11 +186,12 @@ function createUpdateGoalTool(context, options) {
|
|
|
182
186
|
const job = store.updateGoalStatus(existing.id, status);
|
|
183
187
|
if (!job)
|
|
184
188
|
throw new Error('goal no longer exists');
|
|
189
|
+
const tokenBasis = goalTokenAccounting(job).basis;
|
|
185
190
|
return toolResult({
|
|
186
191
|
ok: true,
|
|
187
192
|
goal: goalPayload(job),
|
|
188
193
|
...(status === 'complete' && job.tokenBudget !== undefined
|
|
189
|
-
? { completionBudgetReport: `${job.state.tokensUsed ?? 0}/${job.tokenBudget} reported tokens consumed against a soft budget before the current model turn finishes` }
|
|
194
|
+
? { completionBudgetReport: `${job.state.tokensUsed ?? 0}/${job.tokenBudget} reported ${tokenBasis} tokens consumed against a soft budget before the current model turn finishes` }
|
|
190
195
|
: {}),
|
|
191
196
|
});
|
|
192
197
|
});
|
|
@@ -36,8 +36,9 @@ MCP config lookup order:
|
|
|
36
36
|
1. -c/--config <path>
|
|
37
37
|
2. MCP_CONFIG_PATH
|
|
38
38
|
3. ./mcp_servers.json
|
|
39
|
-
4.
|
|
40
|
-
5. ~/.
|
|
39
|
+
4. ~/mcp_servers.json
|
|
40
|
+
5. ~/.mcp_servers.json
|
|
41
|
+
6. ~/.config/mcp/mcp_servers.json
|
|
41
42
|
`);
|
|
42
43
|
}
|
|
43
44
|
export function printConfigSchema() {
|
package/dist/mcp/config.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createHash } from 'node:crypto';
|
|
|
5
5
|
import { existsSync } from 'node:fs';
|
|
6
6
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
7
7
|
import { homedir } from 'node:os';
|
|
8
|
-
import { dirname, join, resolve } from 'node:path';
|
|
8
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
9
9
|
import { ErrorCode, configInvalidJsonError, configMissingFieldError, configNotFoundError, configSearchError, formatCliError, serverNotFoundError, } from './errors.js';
|
|
10
10
|
export const DEFAULT_MCP_CONFIG_FILE = 'mcp_servers.json';
|
|
11
11
|
// ============================================================================
|
|
@@ -306,9 +306,15 @@ export function getDefaultConfigPaths() {
|
|
|
306
306
|
const home = homedir();
|
|
307
307
|
// Current directory
|
|
308
308
|
paths.push(resolve(DEFAULT_MCP_CONFIG_FILE));
|
|
309
|
-
// Home directory variants
|
|
310
|
-
|
|
311
|
-
|
|
309
|
+
// Home directory variants. Include the non-dot filename because `pibo mcp
|
|
310
|
+
// config` creates it when invoked from the home directory, and services can
|
|
311
|
+
// later run with a different working directory. Ignore empty or relative
|
|
312
|
+
// platform home values instead of treating them as paths below the cwd.
|
|
313
|
+
if (home && isAbsolute(home)) {
|
|
314
|
+
paths.push(join(home, DEFAULT_MCP_CONFIG_FILE));
|
|
315
|
+
paths.push(join(home, '.mcp_servers.json'));
|
|
316
|
+
paths.push(join(home, '.config', 'mcp', 'mcp_servers.json'));
|
|
317
|
+
}
|
|
312
318
|
return paths;
|
|
313
319
|
}
|
|
314
320
|
export function getConfigSearchPaths(explicitPath) {
|
package/dist/mcp/errors.js
CHANGED
|
@@ -24,7 +24,7 @@ export function configSearchError() {
|
|
|
24
24
|
code: ErrorCode.CLIENT_ERROR,
|
|
25
25
|
type: 'CONFIG_NOT_FOUND',
|
|
26
26
|
message: 'No mcp_servers.json found in search paths',
|
|
27
|
-
details: 'Searched: ./mcp_servers.json, ~/.mcp_servers.json, ~/.config/mcp/mcp_servers.json',
|
|
27
|
+
details: 'Searched: ./mcp_servers.json, ~/mcp_servers.json, ~/.mcp_servers.json, ~/.config/mcp/mcp_servers.json',
|
|
28
28
|
suggestion: 'Create mcp_servers.json in current directory or use -c/--config to specify path',
|
|
29
29
|
};
|
|
30
30
|
}
|
package/dist/mcp/index.js
CHANGED
|
@@ -9,9 +9,8 @@
|
|
|
9
9
|
* mcp-cli call <server> <tool> Call tool (reads JSON from stdin if no args)
|
|
10
10
|
* mcp-cli call <server> <tool> {} Call tool with JSON args
|
|
11
11
|
*/
|
|
12
|
-
import { readFileSync } from 'node:fs';
|
|
13
12
|
import { configCommand } from './config-command.js';
|
|
14
|
-
import { ensureConfigExists, } from './config.js';
|
|
13
|
+
import { ensureConfigExists, listServerNames, loadConfigUnresolved, } from './config.js';
|
|
15
14
|
import { ErrorCode, ambiguousCommandError, formatCliError, missingArgumentError, tooManyArgumentsError, unknownOptionError, unknownSubcommandError, } from './errors.js';
|
|
16
15
|
import { registryCommand } from './registry.js';
|
|
17
16
|
import { VERSION } from './version.js';
|
|
@@ -127,37 +126,8 @@ function parseArgs(args) {
|
|
|
127
126
|
result.command = 'info';
|
|
128
127
|
const remaining = positional.slice(1);
|
|
129
128
|
const { server, tool } = parseServerTool(remaining);
|
|
130
|
-
// info requires a server argument - show available servers in error
|
|
131
129
|
if (!server) {
|
|
132
|
-
|
|
133
|
-
let availableServers = [];
|
|
134
|
-
const configPaths = [
|
|
135
|
-
result.configPath,
|
|
136
|
-
process.env.MCP_CONFIG_PATH,
|
|
137
|
-
'./mcp_servers.json',
|
|
138
|
-
`${process.env.HOME}/.mcp_servers.json`,
|
|
139
|
-
`${process.env.HOME}/.config/mcp/mcp_servers.json`,
|
|
140
|
-
].filter(Boolean);
|
|
141
|
-
for (const cfgPath of configPaths) {
|
|
142
|
-
try {
|
|
143
|
-
const content = readFileSync(cfgPath, 'utf-8');
|
|
144
|
-
const config = JSON.parse(content);
|
|
145
|
-
if (config.mcpServers) {
|
|
146
|
-
availableServers = Object.keys(config.mcpServers);
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
// Try next path
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
const serverList = availableServers.length > 0
|
|
155
|
-
? availableServers.join(', ')
|
|
156
|
-
: '(none found)';
|
|
157
|
-
console.error('Error [MISSING_ARGUMENT]: Missing required argument for info: server');
|
|
158
|
-
console.error(` Available servers: ${serverList}`);
|
|
159
|
-
console.error(` Suggestion: Use 'pibo mcp info <server>' to see server details, or just 'pibo mcp' to list all`);
|
|
160
|
-
process.exit(ErrorCode.CLIENT_ERROR);
|
|
130
|
+
return result;
|
|
161
131
|
}
|
|
162
132
|
result.server = server;
|
|
163
133
|
result.tool = tool;
|
|
@@ -371,10 +341,26 @@ export async function runMcpCli(argv = process.argv) {
|
|
|
371
341
|
}
|
|
372
342
|
break;
|
|
373
343
|
case 'info':
|
|
344
|
+
if (!args.server) {
|
|
345
|
+
let availableServers = [];
|
|
346
|
+
try {
|
|
347
|
+
availableServers = listServerNames(await loadConfigUnresolved(args.configPath));
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
// Keep the missing-argument error focused when no valid config exists.
|
|
351
|
+
}
|
|
352
|
+
const serverList = availableServers.length > 0
|
|
353
|
+
? availableServers.join(', ')
|
|
354
|
+
: '(none found)';
|
|
355
|
+
console.error('Error [MISSING_ARGUMENT]: Missing required argument for info: server');
|
|
356
|
+
console.error(` Available servers: ${serverList}`);
|
|
357
|
+
console.error(` Suggestion: Use 'pibo mcp info <server>' to see server details, or just 'pibo mcp' to list all`);
|
|
358
|
+
process.exitCode = ErrorCode.CLIENT_ERROR;
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
374
361
|
await ensureConfigExists(args.configPath);
|
|
375
362
|
{
|
|
376
363
|
const { infoCommand } = await import('./commands/info.js');
|
|
377
|
-
// info always has a server (validated in parseArgs)
|
|
378
364
|
await infoCommand({
|
|
379
365
|
target: buildTarget(args.server, args.tool),
|
|
380
366
|
withDescriptions: args.withDescriptions,
|
|
@@ -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
|
@@ -6,6 +6,18 @@ export class PiboRunExecutionTimeoutError extends Error {
|
|
|
6
6
|
this.name = "PiboRunExecutionTimeoutError";
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
+
export class PiboRunCancellationError extends Error {
|
|
10
|
+
constructor(message, options) {
|
|
11
|
+
super(message, options);
|
|
12
|
+
this.name = "PiboRunCancellationError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class PiboRunCancelledError extends Error {
|
|
16
|
+
constructor(message = "Yielded run was cancelled.", options) {
|
|
17
|
+
super(message, options);
|
|
18
|
+
this.name = "PiboRunCancelledError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
9
21
|
export function resolveRunTimeoutMs(toolName, params) {
|
|
10
22
|
if (!params || typeof params !== "object" || Array.isArray(params))
|
|
11
23
|
return undefined;
|
package/dist/runs/registry.js
CHANGED
|
@@ -7,6 +7,16 @@ 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
|
+
return left.eventId === right.eventId
|
|
14
|
+
&& left.provenance.kind === right.provenance.kind
|
|
15
|
+
&& left.provenance.jobId === right.provenance.jobId
|
|
16
|
+
&& left.provenance.runId === right.provenance.runId
|
|
17
|
+
&& left.provenance.cause === right.provenance.cause
|
|
18
|
+
&& left.provenance.rootEventId === right.provenance.rootEventId;
|
|
19
|
+
}
|
|
10
20
|
function formatTimeout(timeoutMs) {
|
|
11
21
|
if (timeoutMs === undefined)
|
|
12
22
|
return "its configured timeout";
|
|
@@ -83,6 +93,7 @@ export class PiboRunRegistry {
|
|
|
83
93
|
serviceWarning: input.serviceWarning,
|
|
84
94
|
resources: input.resources,
|
|
85
95
|
workerId: this.workerId,
|
|
96
|
+
origin: input.origin,
|
|
86
97
|
});
|
|
87
98
|
const record = recordFromStored(stored);
|
|
88
99
|
this.runs.set(record.runId, record);
|
|
@@ -108,6 +119,7 @@ export class PiboRunRegistry {
|
|
|
108
119
|
...(input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs, timeoutAt: runTimeoutAt(timestamp, input.timeoutMs) } : {}),
|
|
109
120
|
...(input.serviceWarning ? { serviceWarning: input.serviceWarning } : {}),
|
|
110
121
|
...(input.resources ? { resources: structuredClone(input.resources) } : {}),
|
|
122
|
+
...(input.origin ? { origin: structuredClone(input.origin) } : {}),
|
|
111
123
|
};
|
|
112
124
|
this.runs.set(runId, record);
|
|
113
125
|
const output = snapshot(record);
|
|
@@ -245,7 +257,7 @@ export class PiboRunRegistry {
|
|
|
245
257
|
}
|
|
246
258
|
read(controllerPiboSessionId, runId) {
|
|
247
259
|
const record = this.requireRunForController(controllerPiboSessionId, runId);
|
|
248
|
-
if (terminal(record.status)) {
|
|
260
|
+
if (terminal(record.status) && !record.consumed) {
|
|
249
261
|
record.consumed = true;
|
|
250
262
|
record.updatedAt = now();
|
|
251
263
|
this.options.store?.updateRun(runId, record);
|
|
@@ -277,6 +289,9 @@ export class PiboRunRegistry {
|
|
|
277
289
|
}
|
|
278
290
|
ack(controllerPiboSessionId, runId) {
|
|
279
291
|
const record = this.requireRunForController(controllerPiboSessionId, runId);
|
|
292
|
+
const consumesTerminalRun = terminal(record.status) && !record.consumed;
|
|
293
|
+
if (record.acknowledgedStatus === record.status && !consumesTerminalRun)
|
|
294
|
+
return { ...snapshot(record), changed: false };
|
|
280
295
|
record.acknowledgedStatus = record.status;
|
|
281
296
|
if (terminal(record.status))
|
|
282
297
|
record.consumed = true;
|
|
@@ -284,7 +299,7 @@ export class PiboRunRegistry {
|
|
|
284
299
|
this.options.store?.updateRun(runId, record);
|
|
285
300
|
const output = snapshot(record);
|
|
286
301
|
this.notify({ type: "run_acknowledged", run: output });
|
|
287
|
-
return output;
|
|
302
|
+
return { ...output, changed: true };
|
|
288
303
|
}
|
|
289
304
|
suppressNotification(controllerPiboSessionId, runId) {
|
|
290
305
|
const record = this.requireRunForController(controllerPiboSessionId, runId);
|
|
@@ -306,14 +321,17 @@ export class PiboRunRegistry {
|
|
|
306
321
|
return suppressed;
|
|
307
322
|
}
|
|
308
323
|
createNotification(controllerPiboSessionId, options = {}) {
|
|
309
|
-
const
|
|
310
|
-
if (
|
|
324
|
+
const pendingRecords = [...this.runs.values()].filter((record) => this.needsNotification(record, controllerPiboSessionId, options));
|
|
325
|
+
if (pendingRecords.length === 0)
|
|
311
326
|
return undefined;
|
|
327
|
+
const origin = pendingRecords[0].origin;
|
|
328
|
+
const records = pendingRecords.filter((record) => sameOrigin(record.origin, origin));
|
|
312
329
|
for (const record of records) {
|
|
313
330
|
record.notifiedStatus = record.status;
|
|
314
331
|
this.options.store?.updateRun(record.runId, record);
|
|
315
332
|
}
|
|
316
333
|
const notification = {
|
|
334
|
+
...(origin ? { origin: structuredClone(origin) } : {}),
|
|
317
335
|
completed: [],
|
|
318
336
|
failed: [],
|
|
319
337
|
timedOut: [],
|
|
@@ -461,5 +479,6 @@ function recordFromStored(record) {
|
|
|
461
479
|
timeoutPhase: record.timeoutPhase,
|
|
462
480
|
serviceWarning: record.serviceWarning,
|
|
463
481
|
resources: record.resources,
|
|
482
|
+
origin: record.origin,
|
|
464
483
|
};
|
|
465
484
|
}
|
package/dist/runs/tools.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { piboStringEnum } from "../tools/schema.js";
|
|
3
3
|
import { definePiboTool } from "../tools/contract.js";
|
|
4
|
-
import { foregroundServiceWarning, hasMeaningfulTimeoutOutput, isConfiguredTimeoutError, PiboRunExecutionTimeoutError, resolveRunTimeoutMs } from "./lifecycle.js";
|
|
4
|
+
import { foregroundServiceWarning, hasMeaningfulTimeoutOutput, isConfiguredTimeoutError, PiboRunCancellationError, PiboRunCancelledError, PiboRunExecutionTimeoutError, resolveRunTimeoutMs } from "./lifecycle.js";
|
|
5
5
|
import { PiboRunResourceLimitError, prepareYieldedRunExecution } from "./resource-isolation.js";
|
|
6
6
|
function resultText(prefix, value) {
|
|
7
7
|
return `${prefix}\n${JSON.stringify(value, null, 2)}`;
|
|
@@ -73,6 +73,7 @@ export function createRunToolDefinitions(yieldableTools, controller) {
|
|
|
73
73
|
resolveExecutionSettled = resolve;
|
|
74
74
|
});
|
|
75
75
|
let observedOutput = false;
|
|
76
|
+
let cancellationFailure;
|
|
76
77
|
const run = controller.startToolRun({
|
|
77
78
|
toolName: tool.name,
|
|
78
79
|
params: params.arguments,
|
|
@@ -95,6 +96,8 @@ export function createRunToolDefinitions(yieldableTools, controller) {
|
|
|
95
96
|
throw processCancellationError;
|
|
96
97
|
if (executionStarted)
|
|
97
98
|
await waitForRunCancellationSettlement(executionSettled);
|
|
99
|
+
if (cancellationFailure)
|
|
100
|
+
throw cancellationFailure;
|
|
98
101
|
},
|
|
99
102
|
async execute() {
|
|
100
103
|
executionStarted = true;
|
|
@@ -113,8 +116,13 @@ export function createRunToolDefinitions(yieldableTools, controller) {
|
|
|
113
116
|
return { text, details: resultObject.details ?? result };
|
|
114
117
|
}
|
|
115
118
|
catch (error) {
|
|
116
|
-
if (error instanceof
|
|
119
|
+
if (error instanceof PiboRunCancellationError)
|
|
120
|
+
cancellationFailure = error;
|
|
121
|
+
if (error instanceof PiboRunExecutionTimeoutError || error instanceof PiboRunResourceLimitError || error instanceof PiboRunCancellationError)
|
|
117
122
|
throw error;
|
|
123
|
+
if (runAbortController.signal.aborted) {
|
|
124
|
+
throw new PiboRunCancelledError("Yielded run was cancelled; execution ended after cancellation.", { cause: error });
|
|
125
|
+
}
|
|
118
126
|
if (timeoutMs !== undefined && isConfiguredTimeoutError(error))
|
|
119
127
|
throw new PiboRunExecutionTimeoutError(error instanceof Error ? error.message : String(error), observedOutput ? "lifetime" : "startup");
|
|
120
128
|
throw error;
|
|
@@ -240,8 +248,9 @@ export function createRunToolDefinitions(yieldableTools, controller) {
|
|
|
240
248
|
}),
|
|
241
249
|
async execute(_toolCallId, params) {
|
|
242
250
|
const run = controller.ackRun(params.runId);
|
|
251
|
+
const prefix = run.changed ? `Acknowledged run ${run.runId}.` : `Run ${run.runId} was already acknowledged in state ${run.status}; no state changed.`;
|
|
243
252
|
return {
|
|
244
|
-
content: [{ type: "text", text: resultText(
|
|
253
|
+
content: [{ type: "text", text: resultText(prefix, run) }],
|
|
245
254
|
details: run,
|
|
246
255
|
};
|
|
247
256
|
},
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { resolveSessionSignalStatus } from "../signals/status.js";
|
|
1
2
|
export function resolveSessionActivity(signal, fallback = {}) {
|
|
2
3
|
if (!signal)
|
|
3
4
|
return fallbackSessionActivity(fallback);
|
|
4
5
|
const latestTurn = signal.latestTurn;
|
|
5
6
|
const isTurnActive = latestTurn?.state === "running";
|
|
6
7
|
const isTreeActive = signal.isTreeActive || isTurnActive;
|
|
7
|
-
const hasError = signal.hasError || signal.hasErrorDescendant || signal.aggregateStatus === "error";
|
|
8
8
|
return {
|
|
9
|
-
status:
|
|
9
|
+
status: resolveSessionSignalStatus({
|
|
10
|
+
isTreeActive,
|
|
11
|
+
localStatus: signal.localStatus,
|
|
12
|
+
latestTurn,
|
|
13
|
+
}),
|
|
10
14
|
isTreeActive,
|
|
11
15
|
isTurnActive,
|
|
12
16
|
activeTurnId: isTurnActive ? latestTurn.eventId : undefined,
|
package/dist/signals/status.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
export function resolveSessionSignalStatus(snapshot) {
|
|
2
|
+
if (snapshot.isTreeActive || snapshot.latestTurn?.state === "running")
|
|
3
|
+
return "running";
|
|
4
|
+
if (snapshot.localStatus === "error" || snapshot.latestTurn?.state === "failed")
|
|
5
|
+
return "error";
|
|
6
|
+
return "idle";
|
|
7
|
+
}
|
|
1
8
|
export function summarizeSessionSignalStatus(snapshot) {
|
|
2
|
-
const
|
|
3
|
-
const hasError = snapshot.hasError || snapshot.hasErrorDescendant || snapshot.aggregateStatus === "error";
|
|
4
|
-
const isTreeActive = snapshot.isTreeActive || isTurnActive;
|
|
9
|
+
const isTreeActive = snapshot.isTreeActive || snapshot.latestTurn?.state === "running";
|
|
5
10
|
return {
|
|
6
11
|
piboSessionId: snapshot.piboSessionId,
|
|
7
12
|
rootPiboSessionId: snapshot.rootPiboSessionId,
|
|
8
13
|
updatedAt: snapshot.updatedAt,
|
|
9
|
-
status:
|
|
14
|
+
status: resolveSessionSignalStatus(snapshot),
|
|
10
15
|
isTreeActive,
|
|
11
16
|
};
|
|
12
17
|
}
|
package/dist/tools/guides.js
CHANGED
|
@@ -46,7 +46,7 @@ pibo loop stop <job-id>
|
|
|
46
46
|
pibo loop cancel <job-id>
|
|
47
47
|
\`\`\`
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
New Goals persist token-accounting version \`1\` with basis \`uncached\`: their budgets count uncached input and output usage reported by completed assistant model messages, while cache-read and cache-write tokens remain telemetry and do not consume the budget. Legacy persisted Goal jobs and runs without an accounting descriptor remain on version \`1\` basis \`total\`, including cache reads and writes, so their existing counters are neither relabeled nor numerically reconstructed without source data. Inspect \`pibo loop list --all --json\` and \`pibo loop runs --job <job-id> --json\` for the persisted basis. Both bases are soft because usage arrives after the response, so one request can overshoot.
|
|
50
50
|
`,
|
|
51
51
|
};
|
|
52
52
|
export const RALPH_GUIDE = {
|
package/dist/web/channel.js
CHANGED
|
@@ -284,11 +284,17 @@ export function createWebHostChannel(options = {}) {
|
|
|
284
284
|
await sendResponse(nodeResponse, response ?? notFound());
|
|
285
285
|
return;
|
|
286
286
|
}
|
|
287
|
-
if (url.pathname === "/" && apps[0]) {
|
|
288
|
-
await sendResponse(nodeResponse, redirect(apps[0].mountPath));
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
287
|
if (url.pathname === "/") {
|
|
288
|
+
const landingApp = options.landingAppName
|
|
289
|
+
? apps.find((candidate) => candidate.name === options.landingAppName)
|
|
290
|
+
: apps[0];
|
|
291
|
+
if (options.landingAppName && !landingApp) {
|
|
292
|
+
throw new Error(`Configured landing web app "${options.landingAppName}" is not registered`);
|
|
293
|
+
}
|
|
294
|
+
if (landingApp) {
|
|
295
|
+
await sendResponse(nodeResponse, redirect(`${landingApp.mountPath}${url.search}`));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
292
298
|
await sendResponse(nodeResponse, responseHtml("<!doctype html><title>Pibo</title><p>No web apps registered.</p>"));
|
|
293
299
|
return;
|
|
294
300
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pasko70/pibo",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@pasko70/pibo",
|
|
9
|
-
"version": "2.4.
|
|
9
|
+
"version": "2.4.2",
|
|
10
10
|
"workspaces": [
|
|
11
11
|
"packages/workflows"
|
|
12
12
|
],
|
package/package.json
CHANGED
|
@@ -51,7 +51,7 @@ Prefer creating the job stopped when its prompt, target, profile, or safety boun
|
|
|
51
51
|
|
|
52
52
|
## Token budgets
|
|
53
53
|
|
|
54
|
-
Goal token budgets are soft
|
|
54
|
+
Goal token budgets are soft because usage arrives after model responses, so the final turn can overshoot. New Goals use versioned `uncached` accounting: cache-read and cache-write tokens do not consume the budget. Legacy persisted Goals without an accounting descriptor remain on `total` accounting, including cache usage, because their existing counters cannot be reconstructed safely. Job status and each Goal run expose the accounting basis together with tokens used before the turn, remaining tokens, turn usage, and overshoot.
|
|
55
55
|
|
|
56
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
57
|
|