@pasko70/pibo 2.3.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.3.0.vsix → pibo-vscode-ext-2.4.1.vsix} +0 -0
- package/dist/compute/cli.js +13 -0
- package/dist/compute/pool/artifacts.js +116 -0
- package/dist/compute/pool/cli.js +156 -0
- package/dist/compute/pool/config.js +101 -0
- package/dist/compute/pool/docker.js +157 -0
- package/dist/compute/pool/seeds.js +201 -0
- package/dist/compute/pool/service.js +402 -0
- package/dist/compute/pool/store.js +239 -0
- package/dist/compute/pool/types.js +1 -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/resources/lifecycle.js +22 -2
- package/dist/resources/reaper.js +1 -0
- 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 +19 -7
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/builtin/loop/SKILL.md +1 -1
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { chmodSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { DatabaseSync } from "node:sqlite";
|
|
5
|
+
function slotFromRow(row) {
|
|
6
|
+
return {
|
|
7
|
+
id: row.id,
|
|
8
|
+
ordinal: row.ordinal,
|
|
9
|
+
webPort: row.web_port,
|
|
10
|
+
gatewayPort: row.gateway_port,
|
|
11
|
+
publicUrl: row.public_url ?? undefined,
|
|
12
|
+
state: row.state,
|
|
13
|
+
activeLeaseId: row.active_lease_id ?? undefined,
|
|
14
|
+
dirtyReason: row.dirty_reason ?? undefined,
|
|
15
|
+
updatedAt: row.updated_at,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function leaseFromRow(row) {
|
|
19
|
+
return {
|
|
20
|
+
id: row.id,
|
|
21
|
+
slotId: row.slot_id,
|
|
22
|
+
holder: row.holder,
|
|
23
|
+
seedMode: row.seed_mode,
|
|
24
|
+
artifactSha256: row.artifact_sha256,
|
|
25
|
+
artifactRuntimePath: row.artifact_runtime_path,
|
|
26
|
+
packageVersion: row.package_version ?? undefined,
|
|
27
|
+
commit: row.commit_sha ?? undefined,
|
|
28
|
+
containerName: row.container_name,
|
|
29
|
+
publicUrl: row.public_url ?? undefined,
|
|
30
|
+
status: row.status,
|
|
31
|
+
createdAt: row.created_at,
|
|
32
|
+
expiresAt: row.expires_at,
|
|
33
|
+
renewedAt: row.renewed_at ?? undefined,
|
|
34
|
+
releasedAt: row.released_at ?? undefined,
|
|
35
|
+
failedAt: row.failed_at ?? undefined,
|
|
36
|
+
failureSnapshotPath: row.failure_snapshot_path ?? undefined,
|
|
37
|
+
lastError: row.last_error ?? undefined,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export class DeploymentPoolStore {
|
|
41
|
+
path;
|
|
42
|
+
db;
|
|
43
|
+
constructor(path, slots) {
|
|
44
|
+
this.path = path === ":memory:" ? path : resolve(path);
|
|
45
|
+
if (this.path !== ":memory:")
|
|
46
|
+
mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
47
|
+
this.db = new DatabaseSync(this.path);
|
|
48
|
+
this.db.exec("PRAGMA busy_timeout = 10000");
|
|
49
|
+
if (this.path !== ":memory:")
|
|
50
|
+
this.db.exec("PRAGMA journal_mode = WAL");
|
|
51
|
+
this.applySchema();
|
|
52
|
+
this.ensureSlots(slots);
|
|
53
|
+
if (this.path !== ":memory:")
|
|
54
|
+
chmodSync(this.path, 0o600);
|
|
55
|
+
}
|
|
56
|
+
applySchema() {
|
|
57
|
+
this.db.exec(`
|
|
58
|
+
CREATE TABLE IF NOT EXISTS deployment_pool_slots (
|
|
59
|
+
id TEXT PRIMARY KEY,
|
|
60
|
+
ordinal INTEGER NOT NULL UNIQUE,
|
|
61
|
+
web_port INTEGER NOT NULL UNIQUE,
|
|
62
|
+
gateway_port INTEGER NOT NULL UNIQUE,
|
|
63
|
+
public_url TEXT,
|
|
64
|
+
state TEXT NOT NULL,
|
|
65
|
+
active_lease_id TEXT,
|
|
66
|
+
dirty_reason TEXT,
|
|
67
|
+
updated_at TEXT NOT NULL
|
|
68
|
+
);
|
|
69
|
+
CREATE TABLE IF NOT EXISTS deployment_pool_leases (
|
|
70
|
+
id TEXT PRIMARY KEY,
|
|
71
|
+
slot_id TEXT NOT NULL,
|
|
72
|
+
holder TEXT NOT NULL,
|
|
73
|
+
seed_mode TEXT NOT NULL,
|
|
74
|
+
artifact_sha256 TEXT NOT NULL,
|
|
75
|
+
artifact_runtime_path TEXT NOT NULL,
|
|
76
|
+
package_version TEXT,
|
|
77
|
+
commit_sha TEXT,
|
|
78
|
+
container_name TEXT NOT NULL,
|
|
79
|
+
public_url TEXT,
|
|
80
|
+
status TEXT NOT NULL,
|
|
81
|
+
created_at TEXT NOT NULL,
|
|
82
|
+
expires_at TEXT NOT NULL,
|
|
83
|
+
renewed_at TEXT,
|
|
84
|
+
released_at TEXT,
|
|
85
|
+
failed_at TEXT,
|
|
86
|
+
failure_snapshot_path TEXT,
|
|
87
|
+
last_error TEXT
|
|
88
|
+
);
|
|
89
|
+
CREATE INDEX IF NOT EXISTS deployment_pool_leases_status_idx
|
|
90
|
+
ON deployment_pool_leases (status, expires_at);
|
|
91
|
+
CREATE INDEX IF NOT EXISTS deployment_pool_leases_slot_idx
|
|
92
|
+
ON deployment_pool_leases (slot_id, created_at DESC);
|
|
93
|
+
`);
|
|
94
|
+
}
|
|
95
|
+
ensureSlots(slots) {
|
|
96
|
+
const now = new Date().toISOString();
|
|
97
|
+
const insert = this.db.prepare(`
|
|
98
|
+
INSERT INTO deployment_pool_slots (id, ordinal, web_port, gateway_port, public_url, state, updated_at)
|
|
99
|
+
VALUES (?, ?, ?, ?, ?, 'free', ?)
|
|
100
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
101
|
+
ordinal=excluded.ordinal,
|
|
102
|
+
web_port=excluded.web_port,
|
|
103
|
+
gateway_port=excluded.gateway_port,
|
|
104
|
+
public_url=excluded.public_url
|
|
105
|
+
`);
|
|
106
|
+
for (const slot of slots)
|
|
107
|
+
insert.run(slot.id, slot.ordinal, slot.webPort, slot.gatewayPort, slot.publicUrl ?? null, now);
|
|
108
|
+
}
|
|
109
|
+
listSlots() {
|
|
110
|
+
return this.db.prepare("SELECT * FROM deployment_pool_slots ORDER BY ordinal").all().map(slotFromRow);
|
|
111
|
+
}
|
|
112
|
+
getSlot(id) {
|
|
113
|
+
const row = this.db.prepare("SELECT * FROM deployment_pool_slots WHERE id = ?").get(id);
|
|
114
|
+
return row ? slotFromRow(row) : undefined;
|
|
115
|
+
}
|
|
116
|
+
listLeases(options = {}) {
|
|
117
|
+
const where = options.includeInactive ? "" : "WHERE status IN ('provisioning', 'ready', 'releasing')";
|
|
118
|
+
return this.db.prepare(`SELECT * FROM deployment_pool_leases ${where} ORDER BY created_at DESC`).all().map(leaseFromRow);
|
|
119
|
+
}
|
|
120
|
+
getLease(id) {
|
|
121
|
+
const row = this.db.prepare("SELECT * FROM deployment_pool_leases WHERE id = ?").get(id);
|
|
122
|
+
return row ? leaseFromRow(row) : undefined;
|
|
123
|
+
}
|
|
124
|
+
reserveLease(input) {
|
|
125
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
126
|
+
try {
|
|
127
|
+
const active = this.db.prepare("SELECT COUNT(*) AS count FROM deployment_pool_slots WHERE state IN ('provisioning', 'ready', 'releasing')").get();
|
|
128
|
+
if (Number(active.count) >= input.maxActive) {
|
|
129
|
+
const nearest = this.db.prepare(`
|
|
130
|
+
SELECT MIN(l.expires_at) AS nearest_expiry
|
|
131
|
+
FROM deployment_pool_slots s
|
|
132
|
+
JOIN deployment_pool_leases l ON l.id = s.active_lease_id
|
|
133
|
+
WHERE s.state IN ('provisioning', 'ready', 'releasing')
|
|
134
|
+
`).get();
|
|
135
|
+
throw new Error(`Deployment pool capacity reached (${input.maxActive} active)${nearest.nearest_expiry ? `; nearest expiry ${nearest.nearest_expiry}` : ""}`);
|
|
136
|
+
}
|
|
137
|
+
const row = this.db.prepare("SELECT * FROM deployment_pool_slots WHERE state = 'free' ORDER BY ordinal LIMIT 1").get();
|
|
138
|
+
if (!row)
|
|
139
|
+
throw new Error("No free deployment pool slot is available");
|
|
140
|
+
const slot = slotFromRow(row);
|
|
141
|
+
const containerName = `pibo-pool-${slot.id}`;
|
|
142
|
+
this.db.prepare(`
|
|
143
|
+
INSERT INTO deployment_pool_leases (
|
|
144
|
+
id, slot_id, holder, seed_mode, artifact_sha256, artifact_runtime_path,
|
|
145
|
+
package_version, commit_sha, container_name, public_url, status, created_at, expires_at
|
|
146
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'provisioning', ?, ?)
|
|
147
|
+
`).run(input.id, slot.id, input.holder, input.seedMode, input.artifactSha256, input.artifactRuntimePath, input.packageVersion ?? null, input.commit ?? null, containerName, slot.publicUrl ?? null, input.createdAt, input.expiresAt);
|
|
148
|
+
this.db.prepare("UPDATE deployment_pool_slots SET state='provisioning', active_lease_id=?, dirty_reason=NULL, updated_at=? WHERE id=?")
|
|
149
|
+
.run(input.id, input.createdAt, slot.id);
|
|
150
|
+
this.db.exec("COMMIT");
|
|
151
|
+
return { slot: this.getSlot(slot.id), lease: this.getLease(input.id) };
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
this.db.exec("ROLLBACK");
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
markReady(leaseId, now = new Date().toISOString()) {
|
|
159
|
+
const lease = this.requireLease(leaseId);
|
|
160
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
161
|
+
try {
|
|
162
|
+
this.db.prepare("UPDATE deployment_pool_leases SET status='ready', last_error=NULL WHERE id=?").run(leaseId);
|
|
163
|
+
this.db.prepare("UPDATE deployment_pool_slots SET state='ready', dirty_reason=NULL, updated_at=? WHERE id=? AND active_lease_id=?")
|
|
164
|
+
.run(now, lease.slotId, leaseId);
|
|
165
|
+
this.db.exec("COMMIT");
|
|
166
|
+
return this.requireLease(leaseId);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
this.db.exec("ROLLBACK");
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
markReleasing(leaseId, now = new Date().toISOString()) {
|
|
174
|
+
const lease = this.requireLease(leaseId);
|
|
175
|
+
this.db.prepare("UPDATE deployment_pool_leases SET status='releasing' WHERE id=? AND status IN ('provisioning','ready','releasing')").run(leaseId);
|
|
176
|
+
this.db.prepare("UPDATE deployment_pool_slots SET state='releasing', updated_at=? WHERE id=? AND active_lease_id=?").run(now, lease.slotId, leaseId);
|
|
177
|
+
return this.requireLease(leaseId);
|
|
178
|
+
}
|
|
179
|
+
markReleased(leaseId, status, now = new Date().toISOString()) {
|
|
180
|
+
const lease = this.requireLease(leaseId);
|
|
181
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
182
|
+
try {
|
|
183
|
+
this.db.prepare("UPDATE deployment_pool_leases SET status=?, released_at=?, last_error=NULL WHERE id=?").run(status, now, leaseId);
|
|
184
|
+
this.db.prepare("UPDATE deployment_pool_slots SET state='free', active_lease_id=NULL, dirty_reason=NULL, updated_at=? WHERE id=? AND active_lease_id=?")
|
|
185
|
+
.run(now, lease.slotId, leaseId);
|
|
186
|
+
this.db.exec("COMMIT");
|
|
187
|
+
return this.requireLease(leaseId);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
this.db.exec("ROLLBACK");
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
markFailed(leaseId, error, snapshotPath, options = { slotClean: false }) {
|
|
195
|
+
const lease = this.requireLease(leaseId);
|
|
196
|
+
const now = options.now ?? new Date().toISOString();
|
|
197
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
198
|
+
try {
|
|
199
|
+
this.db.prepare("UPDATE deployment_pool_leases SET status='failed', failed_at=?, failure_snapshot_path=?, last_error=? WHERE id=?")
|
|
200
|
+
.run(now, snapshotPath ?? null, error, leaseId);
|
|
201
|
+
if (options.slotClean) {
|
|
202
|
+
this.db.prepare("UPDATE deployment_pool_slots SET state='free', active_lease_id=NULL, dirty_reason=NULL, updated_at=? WHERE id=? AND active_lease_id=?")
|
|
203
|
+
.run(now, lease.slotId, leaseId);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
this.db.prepare("UPDATE deployment_pool_slots SET state='dirty', dirty_reason=?, updated_at=? WHERE id=? AND active_lease_id=?")
|
|
207
|
+
.run(error, now, lease.slotId, leaseId);
|
|
208
|
+
}
|
|
209
|
+
this.db.exec("COMMIT");
|
|
210
|
+
return this.requireLease(leaseId);
|
|
211
|
+
}
|
|
212
|
+
catch (caught) {
|
|
213
|
+
this.db.exec("ROLLBACK");
|
|
214
|
+
throw caught;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
renewLease(leaseId, holder, expiresAt, now = new Date().toISOString()) {
|
|
218
|
+
const result = this.db.prepare(`
|
|
219
|
+
UPDATE deployment_pool_leases SET expires_at=?, renewed_at=?
|
|
220
|
+
WHERE id=? AND holder=? AND status='ready'
|
|
221
|
+
`).run(expiresAt, now, leaseId, holder);
|
|
222
|
+
if (Number(result.changes ?? 0) !== 1)
|
|
223
|
+
throw new Error(`Active deployment lease "${leaseId}" for holder "${holder}" was not found`);
|
|
224
|
+
return this.requireLease(leaseId);
|
|
225
|
+
}
|
|
226
|
+
freeDirtySlot(slotId, now = new Date().toISOString()) {
|
|
227
|
+
this.db.prepare("UPDATE deployment_pool_slots SET state='free', active_lease_id=NULL, dirty_reason=NULL, updated_at=? WHERE id=?")
|
|
228
|
+
.run(now, slotId);
|
|
229
|
+
}
|
|
230
|
+
requireLease(id) {
|
|
231
|
+
const lease = this.getLease(id);
|
|
232
|
+
if (!lease)
|
|
233
|
+
throw new Error(`Deployment lease "${id}" was not found`);
|
|
234
|
+
return lease;
|
|
235
|
+
}
|
|
236
|
+
close() {
|
|
237
|
+
this.db.close();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
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
|
}
|