@pasko70/pibo 2.4.2 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-runtime/context-build.js +100 -11
- package/dist/agent-runtime/profile-validation.js +3 -0
- package/dist/agent-runtime/resource-service.js +16 -0
- package/dist/agent-runtime/routed-session.js +30 -23
- package/dist/agent-runtime/testing/fake-adapter.js +7 -0
- package/dist/agent-runtimes/pi/adapter.js +2 -0
- package/dist/agent-runtimes/pi/routed-session.js +42 -27
- package/dist/agent-runtimes/pi/runtime.js +8 -5
- package/dist/apps/chat/web-app.js +21 -6
- package/dist/apps/chat-ui/assets/{dist-CrDtveZB.js → dist-BxJuOpXP.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-3YG57JXi.js → dist-CvA-WTQT.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Cw9po47P.js → dist-DPzsIE8h.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DTRjeLwO.js → dist-Dj5P89Wy.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BeqHbnGN.js → dist-Sll26U24.js} +1 -1
- package/dist/apps/chat-ui/assets/index-0WZI2phJ.css +1 -0
- package/dist/apps/chat-ui/assets/index-BW5XFgYP.js +228 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/{pibo-vscode-ext-2.4.2.vsix → pibo-vscode-ext-2.5.0.vsix} +0 -0
- package/dist/cli.js +16 -6
- package/dist/core/context-build.js +66 -6
- package/dist/core/model-defaults.js +11 -3
- package/dist/core/session-router.js +152 -69
- package/dist/gateway/server.js +1 -0
- package/dist/loops/accounting.js +80 -0
- package/dist/loops/service.js +51 -16
- package/dist/loops/store.js +50 -14
- package/dist/runs/lifecycle.js +26 -1
- package/dist/runs/registry.js +29 -47
- package/dist/runs/tools.js +23 -22
- package/dist/subagents/context.js +48 -0
- package/dist/subagents/runtime-selection.js +28 -0
- package/dist/subagents/tool.js +28 -6
- package/dist/tools/codex-compat.js +1 -0
- package/dist/tools/contract.js +10 -0
- package/dist/tools/mcp-bridge.js +4 -2
- package/dist/tools/runtime/node-backend.js +8 -2
- package/dist/tools/runtime/python-backend.js +8 -2
- package/dist/tools/runtime/tool.js +1 -0
- package/dist/tools/session-tool-set.js +19 -12
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-AjnP3ci-.js +0 -228
- package/dist/apps/chat-ui/assets/index-BJ56TREg.css +0 -1
package/dist/loops/store.js
CHANGED
|
@@ -4,7 +4,7 @@ import { dirname, resolve } from 'node:path';
|
|
|
4
4
|
import { DatabaseSync } from 'node:sqlite';
|
|
5
5
|
import { piboHomePath } from '../core/pibo-home.js';
|
|
6
6
|
import { isPiboThinkingLevel } from '../core/thinking.js';
|
|
7
|
-
import { newGoalTokenAccounting, normalizeLoopTokenAccounting } from './accounting.js';
|
|
7
|
+
import { addLoopAssistantUsage, newGoalTokenAccounting, normalizeLoopTokenAccounting } from './accounting.js';
|
|
8
8
|
function nowIso(now = new Date()) { return now.toISOString(); }
|
|
9
9
|
function parseJson(json) { return JSON.parse(json); }
|
|
10
10
|
function defaultName(prompt) { const normalized = prompt.replace(/\s+/g, ' ').trim(); return normalized ? normalized.slice(0, 80) : 'Loop job'; }
|
|
@@ -418,29 +418,65 @@ export class PiboLoopStore {
|
|
|
418
418
|
this.db.prepare('UPDATE pibo_ralph_jobs SET enabled = ?, state_json = ?, updated_at = ? WHERE id = ?').run(budgetLimited ? 0 : job.enabled ? 1 : 0, JSON.stringify(state), timestamp, id);
|
|
419
419
|
return this.getJob(id);
|
|
420
420
|
}
|
|
421
|
-
|
|
421
|
+
recordGoalAssistantUsage(id, runId, input, now = new Date()) {
|
|
422
422
|
this.db.exec('BEGIN IMMEDIATE');
|
|
423
423
|
try {
|
|
424
|
-
const job = this.
|
|
425
|
-
if (job
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
424
|
+
const job = this.getJob(id);
|
|
425
|
+
if (!job || job.mode !== 'goal') {
|
|
426
|
+
this.db.exec('COMMIT');
|
|
427
|
+
return job;
|
|
428
|
+
}
|
|
429
|
+
const tokens = Math.max(0, Math.floor(input.budgetTokens));
|
|
430
|
+
const nextTokens = (job.state.tokensUsed ?? 0) + tokens;
|
|
431
|
+
const currentStatus = goalStatus(job) ?? 'active';
|
|
432
|
+
const budgetLimited = currentStatus === 'active' && job.tokenBudget !== undefined && nextTokens >= job.tokenBudget;
|
|
433
|
+
const timestamp = nowIso(now);
|
|
434
|
+
const state = {
|
|
435
|
+
...job.state,
|
|
436
|
+
tokensUsed: nextTokens,
|
|
437
|
+
usage: addLoopAssistantUsage(job.state.usage, input.usage, {
|
|
438
|
+
piboSessionId: input.piboSessionId,
|
|
439
|
+
descendant: input.descendant,
|
|
440
|
+
}),
|
|
441
|
+
goalStatus: budgetLimited ? 'budget_limited' : currentStatus,
|
|
442
|
+
};
|
|
443
|
+
if (budgetLimited)
|
|
444
|
+
state.goalEndedAt = job.state.goalEndedAt ?? timestamp;
|
|
445
|
+
this.db.prepare('UPDATE pibo_ralph_jobs SET enabled = ?, state_json = ?, updated_at = ? WHERE id = ?').run(budgetLimited ? 0 : job.enabled ? 1 : 0, JSON.stringify(state), timestamp, id);
|
|
446
|
+
const row = this.db.prepare('SELECT * FROM pibo_ralph_runs WHERE id = ? AND job_id = ?').get(runId, id);
|
|
447
|
+
if (row) {
|
|
448
|
+
const accounting = parseRunAccounting(row.accounting_json) ?? { tokenAccounting: normalizeLoopTokenAccounting(state.tokenAccounting) };
|
|
449
|
+
const turnTokens = (accounting.tokensUsed ?? 0) + tokens;
|
|
450
|
+
const budget = accounting.tokenBudget;
|
|
451
|
+
const before = accounting.tokensUsedBefore ?? 0;
|
|
452
|
+
const nextAccounting = {
|
|
453
|
+
...accounting,
|
|
454
|
+
tokensUsed: turnTokens,
|
|
455
|
+
usage: addLoopAssistantUsage(accounting.usage, input.usage, {
|
|
456
|
+
piboSessionId: input.piboSessionId,
|
|
457
|
+
descendant: input.descendant,
|
|
458
|
+
}),
|
|
459
|
+
...(budget !== undefined ? { overshootTokens: Math.max(0, before + turnTokens - budget) } : {}),
|
|
460
|
+
};
|
|
461
|
+
this.db.prepare('UPDATE pibo_ralph_runs SET accounting_json = ?, updated_at = ? WHERE id = ?').run(runAccountingJson(nextAccounting), timestamp, runId);
|
|
435
462
|
}
|
|
436
463
|
this.db.exec('COMMIT');
|
|
437
|
-
return
|
|
464
|
+
return this.getJob(id);
|
|
438
465
|
}
|
|
439
466
|
catch (error) {
|
|
440
467
|
this.db.exec('ROLLBACK');
|
|
441
468
|
throw error;
|
|
442
469
|
}
|
|
443
470
|
}
|
|
471
|
+
recordGoalTurnUsage(id, runId, tokens, now = new Date()) {
|
|
472
|
+
const run = this.getRun(runId);
|
|
473
|
+
return this.recordGoalAssistantUsage(id, runId, {
|
|
474
|
+
usage: { type: 'assistant_usage', piboSessionId: run?.piboSessionId ?? 'unknown', totalTokens: Math.max(0, Math.floor(tokens)) },
|
|
475
|
+
budgetTokens: tokens,
|
|
476
|
+
piboSessionId: run?.piboSessionId ?? 'unknown',
|
|
477
|
+
descendant: false,
|
|
478
|
+
}, now);
|
|
479
|
+
}
|
|
444
480
|
recordGoalRunTime(id, runId, activeTimeSeconds, now = new Date()) {
|
|
445
481
|
const seconds = Math.max(0, Math.floor(activeTimeSeconds));
|
|
446
482
|
this.db.exec('BEGIN IMMEDIATE');
|
package/dist/runs/lifecycle.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export const PIBO_RUN_CANCELLATION_SETTLEMENT_TIMEOUT_MS = 15_000;
|
|
1
2
|
export class PiboRunExecutionTimeoutError extends Error {
|
|
2
3
|
timeoutPhase;
|
|
3
4
|
constructor(message, timeoutPhase) {
|
|
@@ -18,6 +19,22 @@ export class PiboRunCancelledError extends Error {
|
|
|
18
19
|
this.name = "PiboRunCancelledError";
|
|
19
20
|
}
|
|
20
21
|
}
|
|
22
|
+
export async function waitForRunCancellationSettlement(settled, timeoutMs = PIBO_RUN_CANCELLATION_SETTLEMENT_TIMEOUT_MS) {
|
|
23
|
+
let timer;
|
|
24
|
+
try {
|
|
25
|
+
await Promise.race([
|
|
26
|
+
settled,
|
|
27
|
+
new Promise((_resolve, reject) => {
|
|
28
|
+
timer = setTimeout(() => reject(new Error(`Yielded run did not settle within ${timeoutMs}ms after cancellation.`)), timeoutMs);
|
|
29
|
+
timer.unref?.();
|
|
30
|
+
}),
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
if (timer)
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
21
38
|
export function resolveRunTimeoutMs(toolName, params) {
|
|
22
39
|
if (!params || typeof params !== "object" || Array.isArray(params))
|
|
23
40
|
return undefined;
|
|
@@ -38,7 +55,15 @@ export function foregroundServiceWarning(toolName, params, timeoutMs) {
|
|
|
38
55
|
}
|
|
39
56
|
export function isConfiguredTimeoutError(error) {
|
|
40
57
|
const message = error instanceof Error ? error.message : String(error);
|
|
41
|
-
|
|
58
|
+
const terminalLines = message.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-5);
|
|
59
|
+
return terminalLines.some((line) => {
|
|
60
|
+
const normalized = line.replace(/^error:\s*/i, "");
|
|
61
|
+
return /^(?:command|process|tool execution|yielded run)\s+timed?\s*out\b.*$/i.test(normalized)
|
|
62
|
+
|| /^timed?\s*out(?:\s+after\s+.+)?[.!]?$/i.test(normalized)
|
|
63
|
+
|| /^timeout(?:\s+error)?[.!]?$/i.test(normalized)
|
|
64
|
+
|| /^timeout(?::|\s+)(?:occurred|expired|exceeded|elapsed|reached)\b.*$/i.test(normalized)
|
|
65
|
+
|| /^timeout(?::|\s+)(?:after\s+)?\d+(?:\.\d+)?\s*(?:ms|milliseconds?|s|secs?|seconds?|m|mins?|minutes?|h|hours?)\b.*$/i.test(normalized);
|
|
66
|
+
});
|
|
42
67
|
}
|
|
43
68
|
export function hasMeaningfulTimeoutOutput(value) {
|
|
44
69
|
const text = extractText(value);
|
package/dist/runs/registry.js
CHANGED
|
@@ -10,12 +10,21 @@ function runTimeoutAt(createdAt, timeoutMs) {
|
|
|
10
10
|
function sameOrigin(left, right) {
|
|
11
11
|
if (!left || !right)
|
|
12
12
|
return left === right;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
if (left.eventId !== right.eventId || left.provenance.kind !== right.provenance.kind)
|
|
14
|
+
return false;
|
|
15
|
+
if (left.provenance.kind === "loop-run" && right.provenance.kind === "loop-run") {
|
|
16
|
+
return left.provenance.jobId === right.provenance.jobId
|
|
17
|
+
&& left.provenance.runId === right.provenance.runId
|
|
18
|
+
&& left.provenance.cause === right.provenance.cause
|
|
19
|
+
&& left.provenance.rootEventId === right.provenance.rootEventId;
|
|
20
|
+
}
|
|
21
|
+
if (left.provenance.kind === "subagent-request" && right.provenance.kind === "subagent-request") {
|
|
22
|
+
return left.provenance.requestId === right.provenance.requestId
|
|
23
|
+
&& left.provenance.controllerPiboSessionId === right.provenance.controllerPiboSessionId
|
|
24
|
+
&& left.provenance.loopJobId === right.provenance.loopJobId
|
|
25
|
+
&& left.provenance.loopRunId === right.provenance.loopRunId;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
19
28
|
}
|
|
20
29
|
function formatTimeout(timeoutMs) {
|
|
21
30
|
if (timeoutMs === undefined)
|
|
@@ -218,6 +227,16 @@ export class PiboRunRegistry {
|
|
|
218
227
|
.filter((record) => options.includeDetached || record.completionPolicy !== "detached")
|
|
219
228
|
.map(snapshot);
|
|
220
229
|
}
|
|
230
|
+
listActiveControllerRuns(controllerPiboSessionId) {
|
|
231
|
+
return [...this.runs.values()]
|
|
232
|
+
.filter((record) => record.controllerPiboSessionId === controllerPiboSessionId && !terminal(record.status))
|
|
233
|
+
.map(snapshot);
|
|
234
|
+
}
|
|
235
|
+
listActiveRuns() {
|
|
236
|
+
return [...this.runs.values()]
|
|
237
|
+
.filter((record) => !terminal(record.status))
|
|
238
|
+
.map(snapshot);
|
|
239
|
+
}
|
|
221
240
|
status(controllerPiboSessionId, runId) {
|
|
222
241
|
return snapshot(this.requireRunForController(controllerPiboSessionId, runId));
|
|
223
242
|
}
|
|
@@ -270,21 +289,22 @@ export class PiboRunRegistry {
|
|
|
270
289
|
output.error = record.error;
|
|
271
290
|
return output;
|
|
272
291
|
}
|
|
273
|
-
cancel(controllerPiboSessionId, runId) {
|
|
292
|
+
cancel(controllerPiboSessionId, runId, reason = "Run was cancelled.") {
|
|
274
293
|
const record = this.requireRunForController(controllerPiboSessionId, runId);
|
|
275
294
|
const previousStatus = record.status;
|
|
276
295
|
if (!terminal(record.status)) {
|
|
277
296
|
record.status = "cancelled";
|
|
297
|
+
record.error = reason;
|
|
278
298
|
record.summary = `${record.toolName} run cancelled.`;
|
|
279
299
|
this.finish(record);
|
|
280
300
|
if (record.jobId)
|
|
281
|
-
this.options.store?.fail(record.jobId, this.workerId,
|
|
301
|
+
this.options.store?.fail(record.jobId, this.workerId, reason);
|
|
282
302
|
}
|
|
283
303
|
record.consumed = true;
|
|
284
304
|
record.updatedAt = now();
|
|
285
305
|
this.options.store?.updateRun(runId, record);
|
|
286
306
|
const output = snapshot(record);
|
|
287
|
-
this.notify({ type: "run_changed", run: output, previousStatus, reason
|
|
307
|
+
this.notify({ type: "run_changed", run: output, previousStatus, reason });
|
|
288
308
|
return output;
|
|
289
309
|
}
|
|
290
310
|
ack(controllerPiboSessionId, runId) {
|
|
@@ -356,44 +376,6 @@ export class PiboRunRegistry {
|
|
|
356
376
|
hasPendingNotification(controllerPiboSessionId, options = {}) {
|
|
357
377
|
return [...this.runs.values()].some((record) => this.needsNotification(record, controllerPiboSessionId, options));
|
|
358
378
|
}
|
|
359
|
-
cancelControllerRuns(controllerPiboSessionId, reason = "Controller Pibo session was disposed.") {
|
|
360
|
-
const cancelled = [];
|
|
361
|
-
for (const record of this.runs.values()) {
|
|
362
|
-
if (record.controllerPiboSessionId !== controllerPiboSessionId || terminal(record.status))
|
|
363
|
-
continue;
|
|
364
|
-
record.status = "cancelled";
|
|
365
|
-
record.error = reason;
|
|
366
|
-
record.consumed = true;
|
|
367
|
-
record.summary = `${record.toolName} run cancelled.`;
|
|
368
|
-
this.finish(record);
|
|
369
|
-
this.options.store?.updateRun(record.runId, record);
|
|
370
|
-
if (record.jobId)
|
|
371
|
-
this.options.store?.fail(record.jobId, this.workerId, reason);
|
|
372
|
-
const output = snapshot(record);
|
|
373
|
-
this.notify({ type: "run_changed", run: output, previousStatus: "running", reason });
|
|
374
|
-
cancelled.push(output);
|
|
375
|
-
}
|
|
376
|
-
return cancelled;
|
|
377
|
-
}
|
|
378
|
-
cancelAll(reason = "Run registry was disposed.") {
|
|
379
|
-
const cancelled = [];
|
|
380
|
-
for (const record of this.runs.values()) {
|
|
381
|
-
if (terminal(record.status))
|
|
382
|
-
continue;
|
|
383
|
-
record.status = "cancelled";
|
|
384
|
-
record.error = reason;
|
|
385
|
-
record.consumed = true;
|
|
386
|
-
record.summary = `${record.toolName} run cancelled.`;
|
|
387
|
-
this.finish(record);
|
|
388
|
-
this.options.store?.updateRun(record.runId, record);
|
|
389
|
-
if (record.jobId)
|
|
390
|
-
this.options.store?.fail(record.jobId, this.workerId, reason);
|
|
391
|
-
const output = snapshot(record);
|
|
392
|
-
this.notify({ type: "run_changed", run: output, previousStatus: "running", reason });
|
|
393
|
-
cancelled.push(output);
|
|
394
|
-
}
|
|
395
|
-
return cancelled;
|
|
396
|
-
}
|
|
397
379
|
prune(options = {}) {
|
|
398
380
|
const nowMs = options.nowMs ?? Date.now();
|
|
399
381
|
const consumedTerminalTtlMs = options.consumedTerminalTtlMs ??
|
package/dist/runs/tools.js
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { piboStringEnum } from "../tools/schema.js";
|
|
3
|
-
import { definePiboTool } from "../tools/contract.js";
|
|
4
|
-
import { foregroundServiceWarning, hasMeaningfulTimeoutOutput, isConfiguredTimeoutError, PiboRunCancellationError, PiboRunCancelledError, PiboRunExecutionTimeoutError, resolveRunTimeoutMs } from "./lifecycle.js";
|
|
3
|
+
import { definePiboTool, piboToolTerminalStatus, piboToolTimeoutPhase } from "../tools/contract.js";
|
|
4
|
+
import { foregroundServiceWarning, hasMeaningfulTimeoutOutput, isConfiguredTimeoutError, PiboRunCancellationError, PiboRunCancelledError, PiboRunExecutionTimeoutError, resolveRunTimeoutMs, waitForRunCancellationSettlement } from "./lifecycle.js";
|
|
5
5
|
import { PiboRunResourceLimitError, prepareYieldedRunExecution } from "./resource-isolation.js";
|
|
6
|
+
export const PIBO_RUN_TOOL_NAMES = [
|
|
7
|
+
"pibo_run_start",
|
|
8
|
+
"pibo_run_list",
|
|
9
|
+
"pibo_run_status",
|
|
10
|
+
"pibo_run_wait",
|
|
11
|
+
"pibo_run_read",
|
|
12
|
+
"pibo_run_cancel",
|
|
13
|
+
"pibo_run_ack",
|
|
14
|
+
];
|
|
6
15
|
function resultText(prefix, value) {
|
|
7
16
|
return `${prefix}\n${JSON.stringify(value, null, 2)}`;
|
|
8
17
|
}
|
|
@@ -27,22 +36,6 @@ function requireTool(tools, name) {
|
|
|
27
36
|
}
|
|
28
37
|
return tool;
|
|
29
38
|
}
|
|
30
|
-
async function waitForRunCancellationSettlement(settled, timeoutMs = 15_000) {
|
|
31
|
-
let timer;
|
|
32
|
-
try {
|
|
33
|
-
await Promise.race([
|
|
34
|
-
settled,
|
|
35
|
-
new Promise((_resolve, reject) => {
|
|
36
|
-
timer = setTimeout(() => reject(new Error(`Yielded run did not settle within ${timeoutMs}ms after cancellation.`)), timeoutMs);
|
|
37
|
-
timer.unref?.();
|
|
38
|
-
}),
|
|
39
|
-
]);
|
|
40
|
-
}
|
|
41
|
-
finally {
|
|
42
|
-
if (timer)
|
|
43
|
-
clearTimeout(timer);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
39
|
export function createRunToolDefinitions(yieldableTools, controller) {
|
|
47
40
|
const toolNames = yieldableTools.map((tool) => tool.name);
|
|
48
41
|
return [
|
|
@@ -99,18 +92,23 @@ export function createRunToolDefinitions(yieldableTools, controller) {
|
|
|
99
92
|
if (cancellationFailure)
|
|
100
93
|
throw cancellationFailure;
|
|
101
94
|
},
|
|
102
|
-
async execute() {
|
|
95
|
+
async execute(runId) {
|
|
103
96
|
executionStarted = true;
|
|
104
97
|
try {
|
|
105
98
|
const result = await prepared.execute(() => tool.execute(toolCallId, prepared.params, runSignal, (update) => {
|
|
106
99
|
observedOutput ||= hasMeaningfulTimeoutOutput(update);
|
|
107
100
|
onUpdate?.(update);
|
|
108
|
-
}, ctx));
|
|
101
|
+
}, { ...ctx, yieldedRunId: runId }));
|
|
109
102
|
const resultObject = result;
|
|
110
103
|
const text = textFromToolResult(resultObject);
|
|
111
104
|
if (resultObject.isError === true) {
|
|
112
|
-
|
|
105
|
+
const structuredTimeout = piboToolTerminalStatus(resultObject) === "timed_out";
|
|
106
|
+
if (structuredTimeout) {
|
|
107
|
+
throw new PiboRunExecutionTimeoutError(text ?? `${tool.name} timed out.`, piboToolTimeoutPhase(resultObject) ?? (observedOutput || hasMeaningfulTimeoutOutput(text) ? "lifetime" : "startup"));
|
|
108
|
+
}
|
|
109
|
+
if (timeoutMs !== undefined && isConfiguredTimeoutError(text ?? "")) {
|
|
113
110
|
throw new PiboRunExecutionTimeoutError(text ?? `${tool.name} timed out.`, observedOutput || hasMeaningfulTimeoutOutput(text) ? "lifetime" : "startup");
|
|
111
|
+
}
|
|
114
112
|
throw new Error(text ?? `${tool.name} returned an error result.`);
|
|
115
113
|
}
|
|
116
114
|
return { text, details: resultObject.details ?? result };
|
|
@@ -231,8 +229,11 @@ export function createRunToolDefinitions(yieldableTools, controller) {
|
|
|
231
229
|
}),
|
|
232
230
|
async execute(_toolCallId, params) {
|
|
233
231
|
const run = await controller.cancelRun(params.runId);
|
|
232
|
+
const prefix = run.status === "cancelled"
|
|
233
|
+
? `Cancelled run ${run.runId}.`
|
|
234
|
+
: `Run ${run.runId} reached ${run.status} before cancellation completed.`;
|
|
234
235
|
return {
|
|
235
|
-
content: [{ type: "text", text: resultText(
|
|
236
|
+
content: [{ type: "text", text: resultText(prefix, run) }],
|
|
236
237
|
details: run,
|
|
237
238
|
};
|
|
238
239
|
},
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { listAvailableAgents } from "./tool.js";
|
|
2
|
+
export const PIBO_DELEGATED_AGENT_CONTEXT_PATH = "pibo://runtime/delegated-agents.md";
|
|
3
|
+
export function getDelegatedAgentContextFile(subagents) {
|
|
4
|
+
const agents = listAvailableAgents(subagents);
|
|
5
|
+
if (agents.length === 0)
|
|
6
|
+
return undefined;
|
|
7
|
+
const catalog = agents.map((agent) => {
|
|
8
|
+
const runtime = [
|
|
9
|
+
agent.model ? `${agent.model.provider}/${agent.model.id}` : undefined,
|
|
10
|
+
agent.thinkingLevel ? `thinking ${agent.thinkingLevel}` : undefined,
|
|
11
|
+
].filter(Boolean).join(", ");
|
|
12
|
+
return `- \`${agent.name}\` → \`${agent.profile}\`${runtime ? ` (${runtime})` : ""}: ${agent.description}`;
|
|
13
|
+
}).join("\n");
|
|
14
|
+
return {
|
|
15
|
+
path: PIBO_DELEGATED_AGENT_CONTEXT_PATH,
|
|
16
|
+
content: [
|
|
17
|
+
"# Delegated Agent Management",
|
|
18
|
+
"",
|
|
19
|
+
"This session has Pibo-managed delegated agents. Dispatch is yielded-only: never call `pibo_agents_send_message` directly. Start it through `pibo_run_start`, then manage the returned run ID.",
|
|
20
|
+
"",
|
|
21
|
+
"## Available agents",
|
|
22
|
+
"",
|
|
23
|
+
catalog,
|
|
24
|
+
"",
|
|
25
|
+
"## Required workflow",
|
|
26
|
+
"",
|
|
27
|
+
"```text",
|
|
28
|
+
"pibo_run_start({",
|
|
29
|
+
" toolName: \"pibo_agents_send_message\",",
|
|
30
|
+
" arguments: { name, message, threadKey? },",
|
|
31
|
+
" completionPolicy?: \"tracked\" | \"detached\"",
|
|
32
|
+
"}) -> { runId }",
|
|
33
|
+
"",
|
|
34
|
+
"pibo_run_wait({ runId, timeoutMs? }) # bounded wait only; expiry does not stop the child",
|
|
35
|
+
"pibo_run_status({ runId }) # compact lifecycle state",
|
|
36
|
+
"pibo_agents_observe({ requestIds?: [runId], agentIds?, names?, threadKeys?, kinds?, roles?, ... })",
|
|
37
|
+
"pibo_run_read({ runId }) # terminal result, including the complete final agent message",
|
|
38
|
+
"pibo_run_cancel({ runId }) # explicit request cancellation",
|
|
39
|
+
"pibo_agents_list_agents({}) # available definitions and persistent child instances",
|
|
40
|
+
"pibo_agents_kill({ agentId }) # terminate one persistent child session subtree",
|
|
41
|
+
"```",
|
|
42
|
+
"",
|
|
43
|
+
"Reuse a stable `threadKey` to continue the same child Pibo Session. A wait timeout is only an orchestrator wake-up. Observe progress and decide whether to continue waiting, steer through a new message after the current turn, cancel the request, or kill the child session.",
|
|
44
|
+
"",
|
|
45
|
+
"For substantial reports, ask the child to persist a Markdown artifact and include its path in the complete final message.",
|
|
46
|
+
].join("\n"),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { selectRequestedSubagentModelProfile, selectRequestedSubagentThinkingLevel, } from "../core/model-defaults.js";
|
|
2
|
+
export function resolvePiboSubagentRuntimeSelection(subagent, targetProfile, modelDefaults = {}) {
|
|
3
|
+
const effectiveModel = subagent.model ?? selectRequestedSubagentModelProfile(targetProfile, modelDefaults);
|
|
4
|
+
const effectiveThinkingLevel = subagent.thinkingLevel ?? selectRequestedSubagentThinkingLevel(targetProfile, modelDefaults);
|
|
5
|
+
return {
|
|
6
|
+
...(subagent.model ? { configuredModel: { ...subagent.model } } : {}),
|
|
7
|
+
...(effectiveModel ? { effectiveModel: { ...effectiveModel } } : {}),
|
|
8
|
+
...(subagent.thinkingLevel ? { configuredThinkingLevel: subagent.thinkingLevel } : {}),
|
|
9
|
+
...(effectiveThinkingLevel ? { effectiveThinkingLevel } : {}),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function resolvePiboSubagentRuntimeSelections(subagents, targetProfileResolver, modelDefaults = {}) {
|
|
13
|
+
return subagents.map((subagent) => {
|
|
14
|
+
const targetProfile = targetProfileResolver?.(subagent.targetProfile);
|
|
15
|
+
const selection = targetProfile
|
|
16
|
+
? resolvePiboSubagentRuntimeSelection(subagent, targetProfile, modelDefaults)
|
|
17
|
+
: {
|
|
18
|
+
...(subagent.model ? { configuredModel: { ...subagent.model }, effectiveModel: { ...subagent.model } } : {}),
|
|
19
|
+
...(subagent.thinkingLevel ? { configuredThinkingLevel: subagent.thinkingLevel, effectiveThinkingLevel: subagent.thinkingLevel } : {}),
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
name: subagent.name,
|
|
23
|
+
targetProfile: subagent.targetProfile,
|
|
24
|
+
enabled: subagent.enabled !== false,
|
|
25
|
+
...selection,
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
}
|
package/dist/subagents/tool.js
CHANGED
|
@@ -30,6 +30,13 @@ export function formatAvailableAgentsForPrompt(subagents) {
|
|
|
30
30
|
function resultText(prefix, value) {
|
|
31
31
|
return `${prefix}\n${JSON.stringify(value, null, 2)}`;
|
|
32
32
|
}
|
|
33
|
+
function normalizeAgentSendMessageResult(result, fallbackRequestId) {
|
|
34
|
+
return {
|
|
35
|
+
...result,
|
|
36
|
+
requestId: result.requestId?.trim() || fallbackRequestId,
|
|
37
|
+
finalMessage: typeof result.finalMessage === "string" ? result.finalMessage : result.reply.text,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
33
40
|
export function createAgentToolDefinitions(subagents, controller) {
|
|
34
41
|
const enabled = subagents.filter((subagent) => subagent.enabled !== false);
|
|
35
42
|
if (enabled.length === 0)
|
|
@@ -48,11 +55,11 @@ export function createAgentToolDefinitions(subagents, controller) {
|
|
|
48
55
|
name: "pibo_agents_send_message",
|
|
49
56
|
title: "Pibo Agents Send Message",
|
|
50
57
|
description: [
|
|
51
|
-
"
|
|
58
|
+
"Yielded-only delegated send. Start this tool through pibo_run_start; bounded waits do not limit the child lifetime.",
|
|
52
59
|
"Available agents:",
|
|
53
60
|
catalog,
|
|
54
61
|
].join("\n"),
|
|
55
|
-
promptSnippet: "
|
|
62
|
+
promptSnippet: "Start pibo_agents_send_message through pibo_run_start. Reuse threadKey to continue its child session, and use run wait/status/read/cancel plus agent observe for lifecycle control.",
|
|
56
63
|
executionMode: "parallel",
|
|
57
64
|
inputSchema: Type.Object({
|
|
58
65
|
name: piboStringEnum(names, { description: "Available delegated agent name" }),
|
|
@@ -62,22 +69,35 @@ export function createAgentToolDefinitions(subagents, controller) {
|
|
|
62
69
|
maxLength: 256,
|
|
63
70
|
})),
|
|
64
71
|
}),
|
|
65
|
-
async execute(toolCallId, params, signal) {
|
|
72
|
+
async execute(toolCallId, params, signal, _onUpdate, context) {
|
|
66
73
|
const subagent = byName.get(params.name);
|
|
67
74
|
if (!subagent)
|
|
68
75
|
throw new Error(`Unknown delegated agent "${params.name}"`);
|
|
69
|
-
|
|
76
|
+
if (!context.yieldedRunId) {
|
|
77
|
+
throw new Error("pibo_agents_send_message is yielded-only. Start it through pibo_run_start.");
|
|
78
|
+
}
|
|
79
|
+
const result = normalizeAgentSendMessageResult(await controller.sendMessage({
|
|
70
80
|
subagent,
|
|
71
81
|
message: params.message,
|
|
72
82
|
threadKey: params.threadKey,
|
|
73
83
|
toolCallId,
|
|
84
|
+
requestId: context.yieldedRunId,
|
|
85
|
+
parentProvenance: context.getActiveMessage?.()?.provenance,
|
|
74
86
|
signal,
|
|
75
|
-
});
|
|
87
|
+
}), context.yieldedRunId);
|
|
76
88
|
return {
|
|
77
89
|
content: [{
|
|
78
90
|
type: "text",
|
|
79
|
-
text: `Agent ${result.
|
|
91
|
+
text: `Agent request ${result.requestId} completed (${result.name}, ${result.agentId}, thread ${result.threadKey}).\n\n${result.finalMessage}`,
|
|
80
92
|
}],
|
|
93
|
+
structuredContent: {
|
|
94
|
+
status: "completed",
|
|
95
|
+
requestId: result.requestId,
|
|
96
|
+
agentId: result.agentId,
|
|
97
|
+
threadKey: result.threadKey,
|
|
98
|
+
eventId: result.eventId,
|
|
99
|
+
finalMessage: result.finalMessage,
|
|
100
|
+
},
|
|
81
101
|
details: result,
|
|
82
102
|
};
|
|
83
103
|
},
|
|
@@ -106,11 +126,13 @@ export function createAgentToolDefinitions(subagents, controller) {
|
|
|
106
126
|
executionMode: "parallel",
|
|
107
127
|
annotations: { readOnly: true },
|
|
108
128
|
inputSchema: Type.Object({
|
|
129
|
+
requestIds: Type.Optional(Type.Array(Type.String({ description: "Exact yielded run/request ID" }), { maxItems: 50 })),
|
|
109
130
|
agentIds: Type.Optional(Type.Array(Type.String({ description: "Owned child agentId" }), { maxItems: 50 })),
|
|
110
131
|
names: Type.Optional(Type.Array(piboStringEnum(names), { maxItems: 50 })),
|
|
111
132
|
threadKeys: Type.Optional(Type.Array(Type.String(), { maxItems: 50 })),
|
|
112
133
|
eventTypes: Type.Optional(Type.Array(Type.String({ description: "Exact Pibo output event type" }), { maxItems: 50 })),
|
|
113
134
|
kinds: Type.Optional(Type.Array(piboStringEnum(["message", "thinking", "tool", "error", "lifecycle", "event"]), { maxItems: 6 })),
|
|
135
|
+
roles: Type.Optional(Type.Array(Type.String({ description: "Exact normalized role, for example assistant" }), { maxItems: 20 })),
|
|
114
136
|
since: Type.Optional(Type.String({ description: "Inclusive ISO-8601 lower timestamp bound" })),
|
|
115
137
|
until: Type.Optional(Type.String({ description: "Inclusive ISO-8601 upper timestamp bound" })),
|
|
116
138
|
textContains: Type.Optional(Type.String({ description: "Case-insensitive substring match against normalized observation text" })),
|
|
@@ -4,6 +4,7 @@ import { extname, isAbsolute, resolve } from "node:path";
|
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import { piboStringEnum } from "./schema.js";
|
|
6
6
|
import { definePiboTool } from "./contract.js";
|
|
7
|
+
export const CODEX_COMPAT_TOOL_NAMES = ["apply_patch", "view_image"];
|
|
7
8
|
function resolveCwd(baseCwd, workdir) {
|
|
8
9
|
if (!workdir || workdir.trim().length === 0)
|
|
9
10
|
return baseCwd;
|
package/dist/tools/contract.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export function piboToolTerminalStatus(result) {
|
|
2
|
+
return result.metadata?.piboTerminalStatus === "timed_out" ? "timed_out" : undefined;
|
|
3
|
+
}
|
|
4
|
+
export function piboToolTimeoutPhase(result) {
|
|
5
|
+
const phase = result.metadata?.piboTimeoutPhase;
|
|
6
|
+
return phase === "startup" || phase === "lifetime" ? phase : undefined;
|
|
7
|
+
}
|
|
1
8
|
/** Identity helper that preserves schema-derived input types. */
|
|
2
9
|
export function definePiboTool(definition) {
|
|
3
10
|
definition.label ??= definition.title;
|
|
@@ -14,8 +21,11 @@ export function isPiboToolDefinition(value) {
|
|
|
14
21
|
export function normalizePiboToolResult(result) {
|
|
15
22
|
return {
|
|
16
23
|
content: result.content.map((content) => ({ ...content })),
|
|
24
|
+
...("structuredContent" in result && result.structuredContent !== undefined ? { structuredContent: result.structuredContent } : {}),
|
|
17
25
|
...(result.details !== undefined ? { details: result.details } : {}),
|
|
18
26
|
...(result.isError !== undefined ? { isError: result.isError } : {}),
|
|
27
|
+
...("payloadRefs" in result && result.payloadRefs !== undefined ? { payloadRefs: [...result.payloadRefs] } : {}),
|
|
28
|
+
...("metadata" in result && result.metadata !== undefined ? { metadata: { ...result.metadata } } : {}),
|
|
19
29
|
};
|
|
20
30
|
}
|
|
21
31
|
/** Convert a legacy Pi-shaped definition without leaking Pi types into generic code. */
|
package/dist/tools/mcp-bridge.js
CHANGED
|
@@ -166,10 +166,12 @@ async function convertContent(items, options) {
|
|
|
166
166
|
return { content, payloadRefs };
|
|
167
167
|
}
|
|
168
168
|
async function piboResultToMcp(result, options) {
|
|
169
|
-
const
|
|
169
|
+
const preserveCompleteRunRead = options.tool.name === "pibo_run_read";
|
|
170
|
+
const conversionOptions = preserveCompleteRunRead ? { ...options, writer: undefined } : options;
|
|
171
|
+
const converted = await convertContent(result.content, conversionOptions);
|
|
170
172
|
const payloadRefs = [...new Set([...(result.payloadRefs ?? []), ...converted.payloadRefs])];
|
|
171
173
|
let structuredContent = result.structuredContent ?? toJsonValue(result.details);
|
|
172
|
-
if (structuredContent !== undefined && options.writer) {
|
|
174
|
+
if (structuredContent !== undefined && options.writer && !preserveCompleteRunRead) {
|
|
173
175
|
const encoded = JSON.stringify(structuredContent);
|
|
174
176
|
if (Buffer.byteLength(encoded, "utf8") > options.threshold) {
|
|
175
177
|
const stored = await storeLargeContent({
|
|
@@ -68,8 +68,14 @@ export class NodeRuntimeBackend {
|
|
|
68
68
|
static async start(baseCwd, input) {
|
|
69
69
|
const target = input.target ?? {};
|
|
70
70
|
const backend = new NodeRuntimeBackend(resolveCwd(baseCwd, target.cwd), target.executable ?? process.execPath, target.args ?? [], target.env);
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
try {
|
|
72
|
+
await backend.waitReady(input.timeoutMs ?? 10000);
|
|
73
|
+
return backend;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
await backend.close(true).catch(() => undefined);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
73
79
|
}
|
|
74
80
|
isAlive() {
|
|
75
81
|
return this.alive && !this.child.killed;
|
|
@@ -68,8 +68,14 @@ export class PythonRuntimeBackend {
|
|
|
68
68
|
static async start(baseCwd, input) {
|
|
69
69
|
const target = input.target ?? {};
|
|
70
70
|
const backend = new PythonRuntimeBackend(resolveCwd(baseCwd, target.cwd), target.executable ?? (process.platform === "win32" ? "python" : "python3"), target.args ?? [], target.env);
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
try {
|
|
72
|
+
await backend.waitReady(input.timeoutMs ?? 10000);
|
|
73
|
+
return backend;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
await backend.close(true).catch(() => undefined);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
73
79
|
}
|
|
74
80
|
isAlive() {
|
|
75
81
|
return this.alive && !this.child.killed;
|
|
@@ -163,6 +163,7 @@ export function createRuntimeToolDefinition(controller) {
|
|
|
163
163
|
content: [{ type: "text", text: formatRuntimeResult(result) }],
|
|
164
164
|
details: result,
|
|
165
165
|
isError: isErrorStatus(status),
|
|
166
|
+
...(status === "timeout" ? { metadata: { piboTerminalStatus: "timed_out", piboTimeoutPhase: "startup" } } : {}),
|
|
166
167
|
};
|
|
167
168
|
},
|
|
168
169
|
});
|