@pasko70/pibo 2.4.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-runtime/routed-session.js +1 -0
- package/dist/agent-runtimes/codex-native/turn.js +3 -1
- package/dist/agent-runtimes/omp/turn.js +36 -7
- 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.1.vsix} +0 -0
- 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 +3 -1
- package/dist/loops/store.js +10 -6
- package/dist/loops/tools.js +7 -4
- 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/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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#101d22" />
|
|
7
7
|
<title>Pibo</title>
|
|
8
|
-
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-DvTSSvzN.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-CF1-9PKU.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
Binary file
|
package/dist/apps/vscode-artifacts/{pibo-vscode-ext-2.4.0.vsix → pibo-vscode-ext-2.4.1.vsix}
RENAMED
|
Binary file
|
package/dist/gateway/web.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CHAT_WEB_APP_NAME } from "../apps/chat/web-app.js";
|
|
1
2
|
import { createDefaultPiboPlugins } from "../plugins/builtin.js";
|
|
2
3
|
import { createPiboBetterAuthPlugin } from "../plugins/better-auth.js";
|
|
3
4
|
import { createPiboChatCustomAgentProfilesPlugin } from "../plugins/chat-custom-agents.js";
|
|
@@ -144,7 +145,13 @@ export function createWebPiboPluginRegistry(options = {}) {
|
|
|
144
145
|
plugins: [
|
|
145
146
|
...createDefaultPiboPlugins(),
|
|
146
147
|
useDevAuth ? createPiboDevAuthPlugin() : createPiboBetterAuthPlugin(resolvedOptions.auth),
|
|
147
|
-
createPiboWebHostPlugin({
|
|
148
|
+
createPiboWebHostPlugin({
|
|
149
|
+
announce: false,
|
|
150
|
+
canonicalBaseURL: useDevAuth ? undefined : authBaseURL(resolvedOptions),
|
|
151
|
+
gatewayMode: webGatewayMode(resolvedOptions, useDevAuth),
|
|
152
|
+
...resolvedOptions.web,
|
|
153
|
+
landingAppName: CHAT_WEB_APP_NAME,
|
|
154
|
+
}),
|
|
148
155
|
createPiboCronPlugin({ cronStorePath: resolvedOptions.chat?.cronStorePath, dataStorePath: resolvedOptions.chat?.dataStorePath, dataPayloadRootDir: resolvedOptions.chat?.dataPayloadRootDir }),
|
|
149
156
|
createPiboChatUserSkillsPlugin({
|
|
150
157
|
globalRoot: resolvedOptions.chat?.userSkillGlobalRoot,
|
package/dist/loops/accounting.js
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
export const LOOP_TOKEN_ACCOUNTING_VERSION = 1;
|
|
2
|
+
function normalizedTokenCount(value) {
|
|
3
|
+
return typeof value === 'number' && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
4
|
+
}
|
|
5
|
+
export function normalizeLoopTokenAccounting(value, fallback = 'total') {
|
|
6
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
7
|
+
const candidate = value;
|
|
8
|
+
if (candidate.version === LOOP_TOKEN_ACCOUNTING_VERSION && (candidate.basis === 'total' || candidate.basis === 'uncached')) {
|
|
9
|
+
return { version: LOOP_TOKEN_ACCOUNTING_VERSION, basis: candidate.basis };
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return { version: LOOP_TOKEN_ACCOUNTING_VERSION, basis: fallback };
|
|
13
|
+
}
|
|
14
|
+
export function newGoalTokenAccounting() {
|
|
15
|
+
return { version: LOOP_TOKEN_ACCOUNTING_VERSION, basis: 'uncached' };
|
|
16
|
+
}
|
|
17
|
+
export function goalTokenAccounting(job) {
|
|
18
|
+
return normalizeLoopTokenAccounting(job.state.tokenAccounting);
|
|
19
|
+
}
|
|
20
|
+
export function goalBudgetTokens(usage, basis) {
|
|
21
|
+
const totalTokens = normalizedTokenCount(usage.totalTokens);
|
|
22
|
+
if (basis === 'total')
|
|
23
|
+
return totalTokens;
|
|
24
|
+
const cacheReadTokens = normalizedTokenCount(usage.cacheReadTokens);
|
|
25
|
+
const cacheWriteTokens = normalizedTokenCount(usage.cacheWriteTokens);
|
|
26
|
+
return Math.max(0, totalTokens - cacheReadTokens - cacheWriteTokens);
|
|
27
|
+
}
|
|
1
28
|
export function goalActiveTimeSeconds(job) {
|
|
2
29
|
return Math.max(0, Math.floor(job.state.activeTimeSeconds ?? job.state.timeUsedSeconds ?? 0));
|
|
3
30
|
}
|
package/dist/loops/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import { goalActiveTimeSeconds, goalElapsedWallClockSeconds } from './accounting.js';
|
|
3
|
+
import { goalActiveTimeSeconds, goalElapsedWallClockSeconds, goalTokenAccounting } from './accounting.js';
|
|
4
4
|
import { createDefaultPiboLoopStore } from './store.js';
|
|
5
5
|
import { createBuiltInLoopStopConditions } from './stopping.js';
|
|
6
6
|
import { DEFAULT_PIBO_PROFILE_NAME } from '../plugins/builtin.js';
|
|
@@ -116,12 +116,13 @@ export function formatLoopResourceSummary(resources) {
|
|
|
116
116
|
}
|
|
117
117
|
function formatLoopJobLine(job) {
|
|
118
118
|
const goal = job.mode === 'goal' ? job.state.goalStatus ?? (job.enabled ? 'active' : 'paused') : '-';
|
|
119
|
-
const
|
|
119
|
+
const tokenBasis = job.mode === 'goal' ? goalTokenAccounting(job).basis : undefined;
|
|
120
|
+
const budget = job.mode === 'goal' ? job.tokenBudget === undefined ? `unbounded:${tokenBasis}` : `soft:${tokenBasis}:${job.state.tokensUsed ?? 0}/${job.tokenBudget};reserve=${job.tokenReserve ?? 0}` : '-';
|
|
120
121
|
const time = job.mode === 'goal' ? `activeAgent=${goalActiveTimeSeconds(job)}s;elapsedWall=${goalElapsedWallClockSeconds(job)}s;paused=included` : '-';
|
|
121
122
|
return `${job.id}\t${job.mode}\t${job.enabled ? 'running' : 'stopped'}\t${job.state.runningAt ? 'active' : '-'}\tgoal=${goal}\tbudget=${budget}\ttime=${time}\tresources=${formatLoopResourceSummary(job.resources)}\t${job.name}`;
|
|
122
123
|
}
|
|
123
124
|
function formatLoopRunLine(run) {
|
|
124
|
-
const accounting = run.accounting ? `tokens=${run.accounting.tokensUsed ?? 0};remainingBefore=${run.accounting.remainingTokensBefore ?? 'unbounded'};overshoot=${run.accounting.overshootTokens ?? 0};activeAgent=${run.accounting.activeTimeSeconds ?? 0}s` : '-';
|
|
125
|
+
const accounting = run.accounting ? `basis=${run.accounting.tokenAccounting?.basis ?? 'total'};tokens=${run.accounting.tokensUsed ?? 0};remainingBefore=${run.accounting.remainingTokensBefore ?? 'unbounded'};overshoot=${run.accounting.overshootTokens ?? 0};activeAgent=${run.accounting.activeTimeSeconds ?? 0}s` : '-';
|
|
125
126
|
return `${run.id}\t${run.jobId}\t${run.status}\t${run.piboSessionId ?? '-'}\t${run.completedAt ?? '-'}\taccounting=${accounting}\tresources=${formatLoopResourceSummary(run.resources)}`;
|
|
126
127
|
}
|
|
127
128
|
export async function runLoopCli(argv = process.argv, defaults = {}) {
|
|
@@ -139,13 +140,13 @@ export async function runLoopCli(argv = process.argv, defaults = {}) {
|
|
|
139
140
|
else
|
|
140
141
|
for (const job of jobs)
|
|
141
142
|
console.log(formatLoopJobLine(job)); store.close(); });
|
|
142
|
-
program.command('add').description(defaults.mode === 'ralph' ? 'Create a Ralph job' : 'Create a Loop job').option('--template <id>', 'Built-in job template id').option('--mode <mode>', 'Loop mode: goal or ralph').option('--prompt <text>', 'Task prompt').option('--name <name>', 'Job name').option('--description <text>', 'Job description').option('--profile <profile>', 'Agent profile', DEFAULT_PIBO_PROFILE_NAME).option('--room <room-id>', 'Target room id').option('--default-chat', 'Target the shared default chat').option('--max-iterations <n>', 'Stop after n completed run attempts').option('--token-budget <n>', 'Set a soft Goal token budget; the final turn can overshoot').option('--token-reserve <n>', 'Require more than n tokens
|
|
143
|
+
program.command('add').description(defaults.mode === 'ralph' ? 'Create a Ralph job' : 'Create a Loop job').option('--template <id>', 'Built-in job template id').option('--mode <mode>', 'Loop mode: goal or ralph').option('--prompt <text>', 'Task prompt').option('--name <name>', 'Job name').option('--description <text>', 'Job description').option('--profile <profile>', 'Agent profile', DEFAULT_PIBO_PROFILE_NAME).option('--room <room-id>', 'Target room id').option('--default-chat', 'Target the shared default chat').option('--max-iterations <n>', 'Stop after n completed run attempts').option('--token-budget <n>', 'Set a soft Goal token budget; new Goals use uncached accounting and the final turn can overshoot').option('--token-reserve <n>', 'Require more than n tokens under the Goal accounting basis before starting another turn').option('--model <provider/model>', 'Runtime model override, for example openai/gpt-5').option('--thinking <level>', 'Runtime thinking level override: off, minimal, low, medium, high, xhigh, max').option('--fast', 'Enable runtime fast mode').option('--no-fast', 'Disable runtime fast mode')
|
|
143
144
|
.option('--start', 'Start immediately').option('--json', 'Print JSON').action((options) => { const base = templatePatch(options.template); const prompt = options.prompt ?? base.prompt; if (typeof prompt !== 'string' || !prompt.trim())
|
|
144
145
|
throw new Error('Choose --template <id> or provide --prompt <text>'); const input = { mode: loopMode(options.mode) ?? base.mode ?? defaults.mode ?? 'goal', name: options.name ?? base.name, description: options.description ?? base.description, enabled: options.start === true, target: targetFromOptions(options), profile: options.profile, prompt, maxIterations: options.maxIterations !== undefined ? maxIterations(options.maxIterations) : typeof base.maxIterations === 'number' ? base.maxIterations : undefined, tokenBudget: tokenBudget(options.tokenBudget), tokenReserve: tokenReserve(options.tokenReserve), stopPolicy: base.stopPolicy ?? undefined }; applyRuntimeCreateOptions(input, options); const store = createDefaultPiboLoopStore({ path: program.opts().store }); const job = store.createJob(input); if (options.json)
|
|
145
146
|
printJson(job);
|
|
146
147
|
else
|
|
147
148
|
console.log(`${job.id}\t${job.enabled ? 'running' : 'stopped'}\t${job.name}`); store.close(); });
|
|
148
|
-
program.command('edit').argument('<id>', defaults.mode === 'ralph' ? 'Ralph job id' : 'Loop job id').description(defaults.mode === 'ralph' ? 'Update a Ralph job' : 'Update a Loop job').option('--template <id>', 'Apply a built-in job template before explicit overrides').option('--mode <mode>', 'Set loop mode: goal or ralph').option('--prompt <text>', 'Task prompt').option('--name <name>', 'Job name').option('--description <text>', 'Job description').option('--profile <profile>', 'Agent profile').option('--room <room-id>', 'Target room id').option('--default-chat', 'Target the shared default chat').option('--max-iterations <n>', 'Stop after n completed run attempts').option('--token-budget <n>',
|
|
149
|
+
program.command('edit').argument('<id>', defaults.mode === 'ralph' ? 'Ralph job id' : 'Loop job id').description(defaults.mode === 'ralph' ? 'Update a Ralph job' : 'Update a Loop job').option('--template <id>', 'Apply a built-in job template before explicit overrides').option('--mode <mode>', 'Set loop mode: goal or ralph').option('--prompt <text>', 'Task prompt').option('--name <name>', 'Job name').option('--description <text>', 'Job description').option('--profile <profile>', 'Agent profile').option('--room <room-id>', 'Target room id').option('--default-chat', 'Target the shared default chat').option('--max-iterations <n>', 'Stop after n completed run attempts').option('--token-budget <n>', "Set the soft Goal token budget under the job's persisted accounting basis").option('--clear-token-budget', 'Clear Goal token budget').option('--token-reserve <n>', "Set the pre-turn minimum remaining tokens under the job's persisted accounting basis").option('--clear-token-reserve', 'Clear Goal token reserve').option('--model <provider/model>', 'Set runtime model override, for example openai/gpt-5').option('--clear-model', 'Clear runtime model override').option('--thinking <level>', 'Set runtime thinking level override: off, minimal, low, medium, high, xhigh, max').option('--clear-thinking', 'Clear runtime thinking level override').option('--fast', 'Enable runtime fast mode').option('--no-fast', 'Disable runtime fast mode').option('--clear-fast', 'Clear runtime fast mode override').option('--json', 'Print JSON').action((id, options) => { const store = createDefaultPiboLoopStore({ path: program.opts().store }); const patch = { ...templatePatch(options.template) }; if (options.mode !== undefined)
|
|
149
150
|
patch.mode = loopMode(options.mode); if (options.name !== undefined)
|
|
150
151
|
patch.name = options.name; if (options.description !== undefined)
|
|
151
152
|
patch.description = options.description; if (options.profile !== undefined)
|
package/dist/loops/prompts.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { goalTokenAccounting } from './accounting.js';
|
|
1
2
|
const completionMarkerInstruction = 'When and only when the full objective is proven complete, end with the XML completion marker on its own line. Compose it from the opening tag <promise>, the word COMPLETE, and the closing tag </promise>. Do not quote, negate, explain, or mention the literal marker before completion.';
|
|
2
3
|
export function buildLoopTurnPrompt(job, continuation, goalToolsAvailable = true) {
|
|
3
4
|
if (job.mode === 'ralph')
|
|
@@ -22,6 +23,11 @@ function buildGoalTurnPrompt(job, continuation, goalToolsAvailable) {
|
|
|
22
23
|
const tokenBudget = job.tokenBudget;
|
|
23
24
|
const remainingTokens = tokenBudget === undefined ? 'unbounded' : String(Math.max(0, tokenBudget - tokensUsed));
|
|
24
25
|
const tokenReserve = job.tokenReserve ?? 0;
|
|
26
|
+
const tokenAccounting = goalTokenAccounting(job);
|
|
27
|
+
const tokenBasis = tokenAccounting.basis === 'uncached' ? 'uncached' : 'total';
|
|
28
|
+
const accountingPolicy = tokenAccounting.basis === 'uncached'
|
|
29
|
+
? '- Cache-read and cache-write tokens do not consume the budget.'
|
|
30
|
+
: '- Legacy compatibility: cache-read and cache-write tokens remain included because prior persisted counters cannot be reconstructed safely.';
|
|
25
31
|
return [
|
|
26
32
|
continuation ? 'Continue working toward the active Pibo loop goal.' : 'Start working toward the active Pibo loop goal.',
|
|
27
33
|
'',
|
|
@@ -37,11 +43,13 @@ function buildGoalTurnPrompt(job, continuation, goalToolsAvailable) {
|
|
|
37
43
|
'- Temporary rough edges are acceptable while work moves toward the requested end state. Completion still requires the requested end state to be true and verified.',
|
|
38
44
|
'',
|
|
39
45
|
'Budget:',
|
|
40
|
-
|
|
41
|
-
`-
|
|
42
|
-
|
|
43
|
-
`-
|
|
44
|
-
`-
|
|
46
|
+
`- Accounting basis: ${tokenBasis} tokens (version ${tokenAccounting.version}).`,
|
|
47
|
+
`- Budget enforcement: soft; ${tokenBasis} model usage is reported after a response and the current turn can overshoot the limit.`,
|
|
48
|
+
accountingPolicy,
|
|
49
|
+
`- Reported ${tokenBasis} tokens used before this turn: ${tokensUsed}`,
|
|
50
|
+
`- Soft ${tokenBasis} token budget: ${tokenBudget ?? 'none'}`,
|
|
51
|
+
`- Pre-turn ${tokenBasis} token reserve: ${tokenReserve}`,
|
|
52
|
+
`- Reported ${tokenBasis} tokens remaining before this turn: ${remainingTokens}`,
|
|
45
53
|
'',
|
|
46
54
|
'Work from evidence:',
|
|
47
55
|
'Use the current workspace, repository, runtime, and external state as authoritative. Previous conversation context can help locate relevant work, but inspect current state before relying on it. Improve, replace, or remove existing work as needed to satisfy the actual objective.',
|
package/dist/loops/service.js
CHANGED
|
@@ -6,6 +6,7 @@ import { PiboDataStore } from '../data/pibo-store.js';
|
|
|
6
6
|
import { ChatRoomService } from '../apps/chat/data/room-service.js';
|
|
7
7
|
import { isPiboRoomArchived } from '../apps/chat/types/rooms.js';
|
|
8
8
|
import { acquireBrowserPoolLease, browserPoolPaths, releaseBrowserPoolLease, restartRecordedBrowserPoolChrome } from '../tools/browser-pool.js';
|
|
9
|
+
import { goalBudgetTokens, goalTokenAccounting } from './accounting.js';
|
|
9
10
|
import { createDefaultPiboLoopStore } from './store.js';
|
|
10
11
|
import { createBuiltInLoopStopConditions, evaluateLoopStopPolicy } from './stopping.js';
|
|
11
12
|
import { buildLoopTurnPrompt } from './prompts.js';
|
|
@@ -536,7 +537,8 @@ export class PiboLoopService {
|
|
|
536
537
|
const job = this.store.getJob(run.jobId);
|
|
537
538
|
if (!job || job.mode !== 'goal')
|
|
538
539
|
return;
|
|
539
|
-
|
|
540
|
+
const basis = run.accounting?.tokenAccounting?.basis ?? goalTokenAccounting(job).basis;
|
|
541
|
+
this.store.recordGoalTurnUsage(job.id, run.id, goalBudgetTokens(event, basis));
|
|
540
542
|
}
|
|
541
543
|
handleProductEvent(event) {
|
|
542
544
|
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,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) {
|
|
@@ -807,6 +810,7 @@ export class PiboLoopStore {
|
|
|
807
810
|
createRunLocked(job, timestamp) {
|
|
808
811
|
const tokensUsedBefore = job.state.tokensUsed ?? 0;
|
|
809
812
|
const accounting = job.mode === 'goal' ? {
|
|
813
|
+
tokenAccounting: normalizeLoopTokenAccounting(job.state.tokenAccounting),
|
|
810
814
|
...(job.tokenBudget !== undefined ? { tokenBudget: job.tokenBudget, remainingTokensBefore: Math.max(0, job.tokenBudget - tokensUsedBefore) } : {}),
|
|
811
815
|
...(job.tokenReserve !== undefined ? { tokenReserve: job.tokenReserve } : {}),
|
|
812
816
|
tokensUsedBefore,
|
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;
|
|
@@ -68,11 +68,13 @@ function nonNegativeInteger(value, field) {
|
|
|
68
68
|
}
|
|
69
69
|
function goalPayload(job) {
|
|
70
70
|
const tokenBudget = job.tokenBudget;
|
|
71
|
+
const tokenAccounting = goalTokenAccounting(job);
|
|
71
72
|
return {
|
|
72
73
|
goalId: job.id,
|
|
73
74
|
objective: job.prompt,
|
|
74
75
|
status: effectiveGoalStatus(job),
|
|
75
76
|
budgetType: tokenBudget === undefined ? 'unbounded' : 'soft',
|
|
77
|
+
tokenAccounting,
|
|
76
78
|
tokenBudget: tokenBudget ?? null,
|
|
77
79
|
tokenReserve: job.tokenReserve ?? 0,
|
|
78
80
|
tokensUsed: job.state.tokensUsed ?? 0,
|
|
@@ -130,8 +132,8 @@ function createCreateGoalTool(context, options) {
|
|
|
130
132
|
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
133
|
inputSchema: Type.Object({
|
|
132
134
|
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.' })),
|
|
135
|
+
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.' })),
|
|
136
|
+
token_reserve: Type.Optional(Type.Number({ description: 'Optional non-negative minimum remaining uncached tokens required before Pibo starts another turn.' })),
|
|
135
137
|
}),
|
|
136
138
|
async execute(_toolCallId, params) {
|
|
137
139
|
try {
|
|
@@ -182,11 +184,12 @@ function createUpdateGoalTool(context, options) {
|
|
|
182
184
|
const job = store.updateGoalStatus(existing.id, status);
|
|
183
185
|
if (!job)
|
|
184
186
|
throw new Error('goal no longer exists');
|
|
187
|
+
const tokenBasis = goalTokenAccounting(job).basis;
|
|
185
188
|
return toolResult({
|
|
186
189
|
ok: true,
|
|
187
190
|
goal: goalPayload(job),
|
|
188
191
|
...(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` }
|
|
192
|
+
? { completionBudgetReport: `${job.state.tokensUsed ?? 0}/${job.tokenBudget} reported ${tokenBasis} tokens consumed against a soft budget before the current model turn finishes` }
|
|
190
193
|
: {}),
|
|
191
194
|
});
|
|
192
195
|
});
|
|
@@ -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,
|
|
@@ -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.1",
|
|
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.1",
|
|
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
|
|