@pasko70/pibo 1.10.0 → 1.10.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/apps/chat/chat-request-normalizers.js +7 -0
- package/dist/apps/chat/data/chat-data-mappers.js +2 -0
- package/dist/apps/chat/loop-api.js +11 -6
- package/dist/apps/chat/web-app.js +30 -10
- package/dist/apps/chat-ui/assets/{dist-LHRs1Nhr.js → dist-3Sts0Afa.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-wE9nop9V.js → dist-4NlLjLs7.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BwKObYnX.js → dist-BA6mhAec.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Y-AA2omI.js → dist-BPl-fzRB.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-GdEM8UW1.js → dist-BPnn9e2B.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DlATLa-U.js → dist-BpfBSMzK.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CS7wdk0Z.js → dist-Cvx5M97R.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-nOLTkZrJ.js → dist-DnXWNQRm.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DUlaXAk7.js → dist-hU-2oAb6.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CKtT8YGm.js → dist-r31x5Fcc.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D-cxLQO1.js → dist-uF6UXzI7.js} +1 -1
- package/dist/apps/chat-ui/assets/index-CaTGYBOS.js +173 -0
- package/dist/apps/chat-ui/assets/{index-C0x9nEcf.css → index-al8DeEjA.css} +1 -1
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-CK4SMZuu.js +41 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/cli-session/localSessionSource.js +34 -12
- package/dist/compute/resource-health.js +35 -2
- package/dist/core/events.js +6 -1
- package/dist/core/routed-session.js +67 -4
- package/dist/core/session-router.js +23 -6
- package/dist/data/ingest-service.js +3 -1
- package/dist/debug/index.js +4 -0
- package/dist/debug/trace.js +42 -7
- package/dist/index.js +1 -0
- package/dist/loops/accounting.js +19 -0
- package/dist/loops/cli.js +22 -6
- package/dist/loops/prompts.js +4 -1
- package/dist/loops/service.js +97 -8
- package/dist/loops/store.js +128 -17
- package/dist/loops/tools.js +24 -7
- package/dist/reliability/store.js +34 -7
- package/dist/runs/lifecycle.js +59 -0
- package/dist/runs/registry.js +47 -1
- package/dist/runs/tools.js +31 -13
- package/dist/session-ui/terminalRows.js +66 -2
- package/dist/shared/trace-async-agent-runs.js +4 -4
- package/dist/shared/trace-event-projection.js +59 -19
- package/dist/shared/trace-nodes.js +5 -0
- package/dist/shared/trace-page-merge.js +15 -0
- package/dist/shared/trace-run-notifications.js +3 -1
- package/dist/signals/projector.js +6 -2
- package/dist/tools/browser-pool.js +50 -0
- package/dist/tools/browser-use-leases.js +12 -8
- package/dist/tools/guides.js +8 -1
- package/dist/tools/index.js +1 -0
- package/package.json +2 -1
- package/skills/builtin/loop/SKILL.md +12 -3
- package/dist/apps/chat-ui/assets/index-8W_yMHQI.js +0 -173
- package/dist/apps/chat-vscode-web/assets/index-BAMxIaI_.js +0 -41
|
@@ -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-CK4SMZuu.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-B5QK07zO.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
@@ -29,6 +29,8 @@ export class LocalCliSessionSource {
|
|
|
29
29
|
listeners = new Map();
|
|
30
30
|
openHandles = new Set();
|
|
31
31
|
closed = false;
|
|
32
|
+
closing = false;
|
|
33
|
+
closePromise;
|
|
32
34
|
constructor(options = {}) {
|
|
33
35
|
this.sessionStore =
|
|
34
36
|
options.sessionStore ?? createDefaultPiboDataSessionStore();
|
|
@@ -349,19 +351,39 @@ export class LocalCliSessionSource {
|
|
|
349
351
|
};
|
|
350
352
|
}
|
|
351
353
|
async close() {
|
|
354
|
+
if (this.closePromise)
|
|
355
|
+
return this.closePromise;
|
|
356
|
+
this.closePromise = this.closeUnsafe();
|
|
357
|
+
return this.closePromise;
|
|
358
|
+
}
|
|
359
|
+
async closeUnsafe() {
|
|
352
360
|
if (this.closed)
|
|
353
361
|
return;
|
|
354
|
-
this.
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
this.
|
|
362
|
+
this.closing = true;
|
|
363
|
+
let routerError;
|
|
364
|
+
try {
|
|
365
|
+
if (this.ownsRouter)
|
|
366
|
+
await this.router?.disposeAll?.();
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
routerError = error;
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
this.closed = true;
|
|
373
|
+
for (const handle of [...this.openHandles])
|
|
374
|
+
handle.close();
|
|
375
|
+
this.listeners.clear();
|
|
376
|
+
this.unsubscribeRouter?.();
|
|
377
|
+
if (this.ownsSessionStore)
|
|
378
|
+
this.sessionStore.close?.();
|
|
379
|
+
if (this.ownsDataStore)
|
|
380
|
+
this.dataStore?.close();
|
|
381
|
+
}
|
|
382
|
+
finally {
|
|
383
|
+
this.closing = false;
|
|
384
|
+
}
|
|
385
|
+
if (routerError)
|
|
386
|
+
throw routerError;
|
|
365
387
|
}
|
|
366
388
|
listenerCount(sessionId) {
|
|
367
389
|
if (sessionId)
|
|
@@ -825,7 +847,7 @@ export class LocalCliSessionSource {
|
|
|
825
847
|
listener(update);
|
|
826
848
|
}
|
|
827
849
|
assertOpen() {
|
|
828
|
-
if (this.closed)
|
|
850
|
+
if (this.closed || this.closing)
|
|
829
851
|
throw new CliSourceError("source_closed", "Local CLI session source is closed");
|
|
830
852
|
}
|
|
831
853
|
}
|
|
@@ -73,8 +73,11 @@ export function buildComputeResourceHealth(options = {}) {
|
|
|
73
73
|
if (browserProcessMatchesPool(process, pool.state))
|
|
74
74
|
assignedMainPids.add(process.pid);
|
|
75
75
|
}
|
|
76
|
+
const activeWorkerOwnedMainPids = new Set(mainProcesses
|
|
77
|
+
.filter((process) => Boolean(findOwningActiveWorker(process, processes, workers)))
|
|
78
|
+
.map((process) => process.pid));
|
|
76
79
|
const unassignedMainProcessDetails = mainProcesses
|
|
77
|
-
.filter((process) => !assignedMainPids.has(process.pid))
|
|
80
|
+
.filter((process) => !assignedMainPids.has(process.pid) && !activeWorkerOwnedMainPids.has(process.pid))
|
|
78
81
|
.map((process) => describeUnassignedBrowserProcess(process, workers));
|
|
79
82
|
const unassignedChromiumMainProcesses = unassignedMainProcessDetails.length;
|
|
80
83
|
const activePoolIds = perWorker.filter((pool) => pool.activeLeaseCount > 0).map((pool) => `${pool.workerId}/${pool.poolId}`);
|
|
@@ -148,6 +151,28 @@ function browserLeakMessage(unassignedCount) {
|
|
|
148
151
|
return `${unassignedCount} unmanaged Chromium main process(es) are not associated with a managed browser pool.`;
|
|
149
152
|
return "Chromium main-process count exceeds managed pool expectations.";
|
|
150
153
|
}
|
|
154
|
+
function findOwningActiveWorker(process, processes, workers) {
|
|
155
|
+
const containerId = process.containerId;
|
|
156
|
+
if (containerId && containerId.length >= 12) {
|
|
157
|
+
const byContainer = workers.find((worker) => worker.state === "running" && (worker.id === containerId || worker.id.startsWith(containerId) || containerId.startsWith(worker.id)));
|
|
158
|
+
if (byContainer)
|
|
159
|
+
return byContainer;
|
|
160
|
+
}
|
|
161
|
+
const activeWorkersByPid = new Map(workers
|
|
162
|
+
.filter((worker) => worker.state === "running" && worker.hostPid !== undefined && worker.hostPid > 1)
|
|
163
|
+
.map((worker) => [worker.hostPid, worker]));
|
|
164
|
+
const processByPid = new Map(processes.map((candidate) => [candidate.pid, candidate]));
|
|
165
|
+
const visited = new Set();
|
|
166
|
+
let current = process;
|
|
167
|
+
while (current && current.pid > 1 && !visited.has(current.pid)) {
|
|
168
|
+
visited.add(current.pid);
|
|
169
|
+
const direct = activeWorkersByPid.get(current.pid) ?? activeWorkersByPid.get(current.ppid);
|
|
170
|
+
if (direct)
|
|
171
|
+
return direct;
|
|
172
|
+
current = processByPid.get(current.ppid);
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
151
176
|
function describeUnassignedBrowserProcess(process, workers) {
|
|
152
177
|
const worker = workers.find((candidate) => candidate.hostPid !== undefined && candidate.hostPid > 0 && candidate.hostPid === process.ppid);
|
|
153
178
|
const userDataDir = readChromeArg(process.args, "user-data-dir");
|
|
@@ -248,7 +273,12 @@ async function collectWorkers() {
|
|
|
248
273
|
async function collectProcesses() {
|
|
249
274
|
try {
|
|
250
275
|
const { stdout } = await execFileAsync("ps", ["-eo", "pid=,ppid=,pgid=,etimes=,comm=,args="], { maxBuffer: 10 * 1024 * 1024 });
|
|
251
|
-
|
|
276
|
+
const processes = parseProcessList(stdout);
|
|
277
|
+
await Promise.all(processes.filter((process) => process.isChromium && process.isMainProcess).map(async (process) => {
|
|
278
|
+
const cgroup = await readFile(`/proc/${process.pid}/cgroup`, "utf8").catch(() => "");
|
|
279
|
+
process.containerId = parseDockerContainerIdFromCgroup(cgroup);
|
|
280
|
+
}));
|
|
281
|
+
return { processes };
|
|
252
282
|
}
|
|
253
283
|
catch (error) {
|
|
254
284
|
return { processes: [], error: error instanceof Error ? error.message : String(error) };
|
|
@@ -299,6 +329,9 @@ function detectReaperTimerStatus() {
|
|
|
299
329
|
}
|
|
300
330
|
return readResourceReaperTimerStatus();
|
|
301
331
|
}
|
|
332
|
+
export function parseDockerContainerIdFromCgroup(value) {
|
|
333
|
+
return value.match(/(?:^|\/)docker[-/]([a-f0-9]{12,64})(?:\.scope|\/|$)/im)?.[1];
|
|
334
|
+
}
|
|
302
335
|
function browserProcessMatchesPool(process, state) {
|
|
303
336
|
if (state.pid && process.pid === state.pid)
|
|
304
337
|
return true;
|
package/dist/core/events.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SessionManager, shouldCompact } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { PiboSteeringUnavailableError } from "./events.js";
|
|
2
3
|
import { getOpenAiCodexProviderUsageForActiveModel } from "../auth/openai-codex-usage.js";
|
|
3
4
|
import { normalizeSessionErrorDetails, runtimeSessionErrorDetails } from "./session-errors.js";
|
|
4
5
|
import { expandInlineSkills } from "./skill-expansion.js";
|
|
@@ -422,6 +423,8 @@ export class RoutedSession {
|
|
|
422
423
|
queue = [];
|
|
423
424
|
processing = false;
|
|
424
425
|
disposed = false;
|
|
426
|
+
disposePromise;
|
|
427
|
+
drainPromise;
|
|
425
428
|
fastMode = false;
|
|
426
429
|
fastModePatchedAgents = new WeakSet();
|
|
427
430
|
activeMessage;
|
|
@@ -683,7 +686,32 @@ export class RoutedSession {
|
|
|
683
686
|
};
|
|
684
687
|
this.emit(output);
|
|
685
688
|
this.onStateChange?.({ processing: this.processing, queuedMessages: this.queue.length, disposed: this.disposed });
|
|
686
|
-
|
|
689
|
+
this.startDrain();
|
|
690
|
+
return output;
|
|
691
|
+
}
|
|
692
|
+
async steerMessage(event) {
|
|
693
|
+
this.assertActive();
|
|
694
|
+
const activeMessage = this.activeMessage;
|
|
695
|
+
if (!activeMessage || !this.processing || !this.runtime.session.isStreaming) {
|
|
696
|
+
throw new PiboSteeringUnavailableError();
|
|
697
|
+
}
|
|
698
|
+
const session = this.runtime.session;
|
|
699
|
+
const expandedText = expandInlineSkills(event.text, session.resourceLoader.getSkills().skills);
|
|
700
|
+
try {
|
|
701
|
+
await session.steer(expandedText);
|
|
702
|
+
}
|
|
703
|
+
catch (error) {
|
|
704
|
+
throw new PiboSteeringUnavailableError(`The active session could not accept steering: ${errorMessage(error)}`, { cause: error });
|
|
705
|
+
}
|
|
706
|
+
const output = {
|
|
707
|
+
type: "message_steered",
|
|
708
|
+
piboSessionId: this.piboSessionId,
|
|
709
|
+
eventId: event.id,
|
|
710
|
+
activeEventId: activeMessage.id,
|
|
711
|
+
text: event.text,
|
|
712
|
+
source: event.source,
|
|
713
|
+
};
|
|
714
|
+
this.emit(output);
|
|
687
715
|
return output;
|
|
688
716
|
}
|
|
689
717
|
async executeAction(event) {
|
|
@@ -875,9 +903,26 @@ export class RoutedSession {
|
|
|
875
903
|
return await this.runtime.session.compact(customInstructions);
|
|
876
904
|
}
|
|
877
905
|
async dispose() {
|
|
906
|
+
if (this.disposePromise)
|
|
907
|
+
return this.disposePromise;
|
|
908
|
+
this.disposePromise = this.disposeUnsafe();
|
|
909
|
+
return this.disposePromise;
|
|
910
|
+
}
|
|
911
|
+
async disposeUnsafe() {
|
|
878
912
|
if (this.disposed)
|
|
879
913
|
return;
|
|
914
|
+
const activeMessage = this.activeMessage;
|
|
880
915
|
this.notifyMessagesInterrupted(this.activeAndQueuedMessages(), "session disposed");
|
|
916
|
+
if (activeMessage) {
|
|
917
|
+
const error = "Session disposed while a message was active.";
|
|
918
|
+
this.emit({
|
|
919
|
+
type: "session_error",
|
|
920
|
+
piboSessionId: this.piboSessionId,
|
|
921
|
+
eventId: activeMessage.id,
|
|
922
|
+
error,
|
|
923
|
+
errorDetails: runtimeSessionErrorDetails(error),
|
|
924
|
+
});
|
|
925
|
+
}
|
|
881
926
|
this.cancelProviderRecovery();
|
|
882
927
|
this.queue.length = 0;
|
|
883
928
|
this.onStateChange?.({ processing: this.processing, queuedMessages: this.queue.length, disposed: true });
|
|
@@ -888,7 +933,15 @@ export class RoutedSession {
|
|
|
888
933
|
this.recoverySession = undefined;
|
|
889
934
|
}
|
|
890
935
|
this.disposed = true;
|
|
891
|
-
|
|
936
|
+
const abort = this.runtime.session.abort;
|
|
937
|
+
if (abort)
|
|
938
|
+
await Promise.allSettled([abort.call(this.runtime.session)]);
|
|
939
|
+
try {
|
|
940
|
+
await this.drainPromise;
|
|
941
|
+
}
|
|
942
|
+
finally {
|
|
943
|
+
await this.runtime.dispose();
|
|
944
|
+
}
|
|
892
945
|
}
|
|
893
946
|
async kill() {
|
|
894
947
|
this.notifyMessagesInterrupted(this.activeAndQueuedMessages(), "session killed");
|
|
@@ -918,6 +971,16 @@ export class RoutedSession {
|
|
|
918
971
|
}
|
|
919
972
|
return false;
|
|
920
973
|
}
|
|
974
|
+
startDrain() {
|
|
975
|
+
if (this.drainPromise)
|
|
976
|
+
return;
|
|
977
|
+
const drain = this.drain();
|
|
978
|
+
this.drainPromise = drain;
|
|
979
|
+
void drain.finally(() => {
|
|
980
|
+
if (this.drainPromise === drain)
|
|
981
|
+
this.drainPromise = undefined;
|
|
982
|
+
});
|
|
983
|
+
}
|
|
921
984
|
async drain() {
|
|
922
985
|
if (this.processing || this.disposed)
|
|
923
986
|
return;
|
|
@@ -974,7 +1037,7 @@ export class RoutedSession {
|
|
|
974
1037
|
}
|
|
975
1038
|
}
|
|
976
1039
|
catch (error) {
|
|
977
|
-
if (error instanceof PiboProviderRecoveryCancelledError)
|
|
1040
|
+
if (error instanceof PiboProviderRecoveryCancelledError || this.disposed)
|
|
978
1041
|
return;
|
|
979
1042
|
const message = errorMessage(error);
|
|
980
1043
|
this.emit({
|
|
@@ -1030,7 +1093,7 @@ export class RoutedSession {
|
|
|
1030
1093
|
};
|
|
1031
1094
|
this.emit(output);
|
|
1032
1095
|
this.onStateChange?.({ processing: this.processing, queuedMessages: this.queue.length, disposed: this.disposed });
|
|
1033
|
-
|
|
1096
|
+
this.startDrain();
|
|
1034
1097
|
return output;
|
|
1035
1098
|
}
|
|
1036
1099
|
async runAction(event) {
|
|
@@ -6,6 +6,7 @@ import { RoutedSession } from "./routed-session.js";
|
|
|
6
6
|
import { runtimeSessionErrorDetails } from "./session-errors.js";
|
|
7
7
|
import { createSubagentToolName } from "../subagents/tool.js";
|
|
8
8
|
import { PiboRunRegistry } from "../runs/registry.js";
|
|
9
|
+
import { PiboRunExecutionTimeoutError } from "../runs/lifecycle.js";
|
|
9
10
|
import { createPiboSignalRegistry } from "../signals/registry.js";
|
|
10
11
|
import { createDefaultPiboReliabilityStore } from "../reliability/store.js";
|
|
11
12
|
import { InMemoryPiboSessionStore, } from "../sessions/store.js";
|
|
@@ -85,6 +86,15 @@ function formatRunReminderMessage(notification) {
|
|
|
85
86
|
toolName: run.toolName,
|
|
86
87
|
summary: run.summary,
|
|
87
88
|
})),
|
|
89
|
+
timedOut: notification.timedOut.map((run) => ({
|
|
90
|
+
runId: run.runId,
|
|
91
|
+
kind: run.kind,
|
|
92
|
+
status: run.status,
|
|
93
|
+
toolName: run.toolName,
|
|
94
|
+
summary: run.summary,
|
|
95
|
+
timeoutMs: run.timeoutMs,
|
|
96
|
+
timeoutPhase: run.timeoutPhase,
|
|
97
|
+
})),
|
|
88
98
|
cancelled: notification.cancelled.map((run) => ({
|
|
89
99
|
runId: run.runId,
|
|
90
100
|
kind: run.kind,
|
|
@@ -99,7 +109,7 @@ function formatRunReminderMessage(notification) {
|
|
|
99
109
|
toolName: run.toolName,
|
|
100
110
|
summary: run.summary,
|
|
101
111
|
})),
|
|
102
|
-
instruction: "Use pibo_run_read for completed or
|
|
112
|
+
instruction: "Use pibo_run_read for completed, failed, or timed_out runs. Use pibo_run_wait, pibo_run_status, pibo_run_cancel, or pibo_run_ack for runs you still need to manage.",
|
|
103
113
|
}),
|
|
104
114
|
"</pibo_run_notification>",
|
|
105
115
|
].join("\n");
|
|
@@ -108,7 +118,7 @@ function isRunReminderServiceMessage(event) {
|
|
|
108
118
|
return event.source === "service" && event.text.startsWith("<pibo_run_notification>");
|
|
109
119
|
}
|
|
110
120
|
function isTerminalRunStatus(status) {
|
|
111
|
-
return status === "completed" || status === "failed" || status === "cancelled";
|
|
121
|
+
return status === "completed" || status === "failed" || status === "timed_out" || status === "cancelled";
|
|
112
122
|
}
|
|
113
123
|
function asJsonObject(value) {
|
|
114
124
|
return value ?? {};
|
|
@@ -212,7 +222,9 @@ export class PiboSessionRouter {
|
|
|
212
222
|
this.clearIdleSessionTimer(event.piboSessionId);
|
|
213
223
|
try {
|
|
214
224
|
if (event.type === "message") {
|
|
215
|
-
return
|
|
225
|
+
return event.delivery === "steer"
|
|
226
|
+
? await session.steerMessage(event)
|
|
227
|
+
: session.enqueueMessage(event);
|
|
216
228
|
}
|
|
217
229
|
if (event.action === "abort") {
|
|
218
230
|
this.signalRegistry.project({ type: "session_interrupted", piboSessionId: event.piboSessionId, reason: "abort action" });
|
|
@@ -667,7 +679,7 @@ export class PiboSessionRouter {
|
|
|
667
679
|
}
|
|
668
680
|
createRunToolController(parentPiboSessionId) {
|
|
669
681
|
return {
|
|
670
|
-
startToolRun: ({ toolName, params, completionPolicy, retryable, maxAttempts, execute }) => {
|
|
682
|
+
startToolRun: ({ toolName, params, completionPolicy, retryable, maxAttempts, timeoutMs, serviceWarning, execute }) => {
|
|
671
683
|
assertGatewayResourceAvailableForWork(`yielded run ${toolName}`);
|
|
672
684
|
const run = this.runRegistry.startToolRun({
|
|
673
685
|
controllerPiboSessionId: parentPiboSessionId,
|
|
@@ -676,6 +688,8 @@ export class PiboSessionRouter {
|
|
|
676
688
|
completionPolicy,
|
|
677
689
|
retryable,
|
|
678
690
|
maxAttempts,
|
|
691
|
+
timeoutMs,
|
|
692
|
+
serviceWarning,
|
|
679
693
|
});
|
|
680
694
|
void (async () => {
|
|
681
695
|
try {
|
|
@@ -685,8 +699,11 @@ export class PiboSessionRouter {
|
|
|
685
699
|
this.scheduleRunReminder(parentPiboSessionId, false);
|
|
686
700
|
}
|
|
687
701
|
catch (error) {
|
|
688
|
-
const
|
|
689
|
-
|
|
702
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
703
|
+
const terminalRun = error instanceof PiboRunExecutionTimeoutError
|
|
704
|
+
? this.runRegistry.timeOut(run.runId, message, error.timeoutPhase)
|
|
705
|
+
: this.runRegistry.fail(run.runId, message);
|
|
706
|
+
if (terminalRun)
|
|
690
707
|
this.scheduleRunReminder(parentPiboSessionId, false);
|
|
691
708
|
}
|
|
692
709
|
})();
|
|
@@ -303,7 +303,7 @@ function payloadForOutputEvent(event) {
|
|
|
303
303
|
function previewTextForOutputEvent(event) {
|
|
304
304
|
if (event.type === "assistant_message" || event.type === "assistant_delta" || event.type === "thinking_delta" || event.type === "thinking_finished")
|
|
305
305
|
return previewText(event.text ?? "");
|
|
306
|
-
if (event.type === "message_queued" || event.type === "message_started")
|
|
306
|
+
if (event.type === "message_queued" || event.type === "message_steered" || event.type === "message_started")
|
|
307
307
|
return previewText(event.text);
|
|
308
308
|
if (event.type === "tool_call" || event.type === "tool_execution_started" || event.type === "tool_execution_updated" || event.type === "tool_execution_finished")
|
|
309
309
|
return event.toolName;
|
|
@@ -320,6 +320,8 @@ function previewTextForOutputEvent(event) {
|
|
|
320
320
|
function attributesForOutputEvent(event) {
|
|
321
321
|
if (event.type === "message_queued")
|
|
322
322
|
return { inlineText: event.text, source: event.source, queuedMessages: event.queuedMessages };
|
|
323
|
+
if (event.type === "message_steered")
|
|
324
|
+
return { inlineText: event.text, source: event.source, activeEventId: event.activeEventId };
|
|
323
325
|
if (event.type === "assistant_message" || event.type === "assistant_delta")
|
|
324
326
|
return { assistantIndex: event.assistantIndex, contentIndex: event.contentIndex };
|
|
325
327
|
if (event.type === "thinking_started" || event.type === "thinking_delta" || event.type === "thinking_finished")
|
package/dist/debug/index.js
CHANGED
|
@@ -985,6 +985,10 @@ function compactRunRow(run) {
|
|
|
985
985
|
policy: run.completionPolicy,
|
|
986
986
|
consumed: run.consumed,
|
|
987
987
|
updatedAt: run.updatedAt,
|
|
988
|
+
timeoutMs: run.timeoutMs,
|
|
989
|
+
timeoutAt: run.timeoutAt,
|
|
990
|
+
timeoutPhase: run.timeoutPhase,
|
|
991
|
+
serviceWarning: run.serviceWarning,
|
|
988
992
|
summary: run.summary,
|
|
989
993
|
};
|
|
990
994
|
}
|
package/dist/debug/trace.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { buildTraceView } from "../apps/chat/trace.js";
|
|
2
2
|
import { normalizeSessionErrorDetails } from "../core/session-errors.js";
|
|
3
|
-
import {
|
|
3
|
+
import { compareTraceNodes } from "../shared/trace-nodes.js";
|
|
4
4
|
import { openReadOnlyDebugDatabase, withStorePath } from "./sql.js";
|
|
5
5
|
import { formatNextCommands } from "./next-commands.js";
|
|
6
6
|
export async function inspectDebugTrace(piboSessionId, stores, options = {}) {
|
|
@@ -16,10 +16,11 @@ export async function inspectDebugTrace(piboSessionId, stores, options = {}) {
|
|
|
16
16
|
throw new Error(`Pibo session "${piboSessionId}" not found`);
|
|
17
17
|
const session = sessionFromRow(sessionRow);
|
|
18
18
|
const sessions = sessionsDb.prepare("SELECT * FROM sessions").all().map(sessionFromRow);
|
|
19
|
+
const adapterIssues = [];
|
|
19
20
|
const events = tableExists(chatDb, "event_log")
|
|
20
21
|
? chatDb
|
|
21
22
|
.prepare("SELECT stream_id, session_id, session_sequence, event_id, type, created_at, preview_text, attributes_json FROM event_log WHERE session_id = ? ORDER BY stream_id ASC")
|
|
22
|
-
.all(piboSessionId).map(eventFromRow).filter((event) => event !== undefined)
|
|
23
|
+
.all(piboSessionId).map((row) => eventFromRow(row, adapterIssues)).filter((event) => event !== undefined)
|
|
23
24
|
: [];
|
|
24
25
|
const view = await buildTraceView({
|
|
25
26
|
session,
|
|
@@ -36,7 +37,7 @@ export async function inspectDebugTrace(piboSessionId, stores, options = {}) {
|
|
|
36
37
|
status: traceStatus(view),
|
|
37
38
|
nodes: filtered,
|
|
38
39
|
rawNodeCount: rows.length,
|
|
39
|
-
...(options.check ? { checks: checkTraceView(view) } : {}),
|
|
40
|
+
...(options.check ? { checks: checkTraceView(view, adapterIssues) } : {}),
|
|
40
41
|
nextCommands: buildTraceNextCommands(view.piboSessionId, filtered),
|
|
41
42
|
};
|
|
42
43
|
}
|
|
@@ -168,10 +169,11 @@ function traceStatus(view) {
|
|
|
168
169
|
return "running";
|
|
169
170
|
return "done";
|
|
170
171
|
}
|
|
171
|
-
function checkTraceView(view) {
|
|
172
|
-
const issues = [];
|
|
172
|
+
export function checkTraceView(view, adapterIssues = []) {
|
|
173
|
+
const issues = [...adapterIssues];
|
|
173
174
|
const all = flattenPiboTraceNodes(view.nodes);
|
|
174
175
|
const ids = new Set();
|
|
176
|
+
const stableKeyOwners = new Map();
|
|
175
177
|
for (const node of all) {
|
|
176
178
|
if (ids.has(node.id)) {
|
|
177
179
|
issues.push({
|
|
@@ -206,6 +208,20 @@ function checkTraceView(view) {
|
|
|
206
208
|
message: "Trace node has no conceptual stable key.",
|
|
207
209
|
});
|
|
208
210
|
}
|
|
211
|
+
else {
|
|
212
|
+
const existingOwner = stableKeyOwners.get(node.stableKey);
|
|
213
|
+
if (existingOwner && existingOwner !== node.id) {
|
|
214
|
+
issues.push({
|
|
215
|
+
severity: "warning",
|
|
216
|
+
code: "duplicate_stable_key",
|
|
217
|
+
nodeId: node.id,
|
|
218
|
+
message: `Stable key "${node.stableKey}" is already used by node "${existingOwner}".`,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
stableKeyOwners.set(node.stableKey, node.id);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
209
225
|
}
|
|
210
226
|
for (const node of all) {
|
|
211
227
|
if (node.parentId && !ids.has(node.parentId)) {
|
|
@@ -235,7 +251,7 @@ function checkSiblingOrder(nodes, issues) {
|
|
|
235
251
|
checkSiblingOrder(node.children, issues);
|
|
236
252
|
}
|
|
237
253
|
function compareOrder(left, right) {
|
|
238
|
-
return
|
|
254
|
+
return compareTraceNodes(left, right);
|
|
239
255
|
}
|
|
240
256
|
function flattenPiboTraceNodes(nodes) {
|
|
241
257
|
return nodes.flatMap((node) => [node, ...flattenPiboTraceNodes(node.children)]);
|
|
@@ -288,10 +304,18 @@ function sessionFromRow(row) {
|
|
|
288
304
|
updatedAt: row.updated_at,
|
|
289
305
|
};
|
|
290
306
|
}
|
|
291
|
-
function eventFromRow(row) {
|
|
307
|
+
function eventFromRow(row, issues) {
|
|
292
308
|
const payload = outputPayloadFromV2Row(row);
|
|
293
309
|
if (!payload)
|
|
294
310
|
return undefined;
|
|
311
|
+
if ((row.type === "assistant_delta" || row.type === "thinking_delta") && !nonEmptyEventText(payload)) {
|
|
312
|
+
issues.push({
|
|
313
|
+
severity: "warning",
|
|
314
|
+
code: "missing_delta_text",
|
|
315
|
+
nodeId: row.event_id ?? String(row.stream_id),
|
|
316
|
+
message: `Persisted ${row.type} event has no readable text payload.`,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
295
319
|
return {
|
|
296
320
|
id: String(row.stream_id),
|
|
297
321
|
piboSessionId: row.session_id ?? undefined,
|
|
@@ -315,12 +339,16 @@ function outputPayloadFromV2Row(row) {
|
|
|
315
339
|
const base = { piboSessionId, eventId: row.event_id ?? undefined };
|
|
316
340
|
if (row.type === "assistant_message")
|
|
317
341
|
return compactObject({ ...base, type: "assistant_message", text: row.preview_text ?? "" });
|
|
342
|
+
if (row.type === "assistant_delta")
|
|
343
|
+
return compactObject({ ...base, type: "assistant_delta", text: inlineTextPayload(inlinePayload) ?? row.preview_text ?? "" });
|
|
318
344
|
if (row.type === "message_started")
|
|
319
345
|
return compactObject({ ...base, type: "message_started", text: row.preview_text ?? "" });
|
|
320
346
|
if (row.type === "message_finished")
|
|
321
347
|
return compactObject({ ...base, type: "message_finished" });
|
|
322
348
|
if (row.type === "thinking_started")
|
|
323
349
|
return compactObject({ ...base, type: "thinking_started" });
|
|
350
|
+
if (row.type === "thinking_delta")
|
|
351
|
+
return compactObject({ ...base, type: "thinking_delta", text: inlineTextPayload(inlinePayload) ?? row.preview_text ?? "" });
|
|
324
352
|
if (row.type === "thinking_finished")
|
|
325
353
|
return compactObject({ ...base, type: "thinking_finished", text: row.preview_text ?? "" });
|
|
326
354
|
if (row.type === "tool_call")
|
|
@@ -337,6 +365,13 @@ function outputPayloadFromV2Row(row) {
|
|
|
337
365
|
}
|
|
338
366
|
return compactObject({ ...base, type: row.type });
|
|
339
367
|
}
|
|
368
|
+
function inlineTextPayload(value) {
|
|
369
|
+
return typeof value === "string" ? value : undefined;
|
|
370
|
+
}
|
|
371
|
+
function nonEmptyEventText(event) {
|
|
372
|
+
const text = event.text;
|
|
373
|
+
return typeof text === "string" && text.length > 0;
|
|
374
|
+
}
|
|
340
375
|
function stringAttribute(attributes, key) {
|
|
341
376
|
const value = attributes[key];
|
|
342
377
|
return typeof value === "string" ? value : undefined;
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,7 @@ export { LOCAL_TUI_CHANNEL_NAME, LocalRoutedTuiClient, createLocalRoutedTuiClien
|
|
|
19
19
|
export { createWebHostChannel, DEFAULT_WEB_CHANNEL_HOST, DEFAULT_WEB_CHANNEL_PORT, WEB_CHANNEL_NAME } from "./web/channel.js";
|
|
20
20
|
export { sendGatewayEvent, sendGatewayMessageAndWaitForReply } from "./gateway/request.js";
|
|
21
21
|
export { createSubagentToolDefinitions, createSubagentToolName, } from "./subagents/tool.js";
|
|
22
|
+
export { PiboSteeringUnavailableError } from "./core/events.js";
|
|
22
23
|
export { InMemoryPiboSessionStore, createPiSessionId, createPiboSessionId, createPiboSession, } from "./sessions/store.js";
|
|
23
24
|
export { runPiboCli } from "./cli.js";
|
|
24
25
|
export { DEFAULT_PIBO_CONFIG_PATH, PIBO_CONFIG_KEYS, deletePiboConfigValue, getDefaultPiboConfigPath, getDisplayPiboConfigValue, getPiboConfigValue, isPiboConfigKeySecret, loadPiboConfig, redactPiboConfig, savePiboConfig, setPiboConfigValue, } from "./config/config.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function goalActiveTimeSeconds(job) {
|
|
2
|
+
return Math.max(0, Math.floor(job.state.activeTimeSeconds ?? job.state.timeUsedSeconds ?? 0));
|
|
3
|
+
}
|
|
4
|
+
export function goalElapsedWallClockSeconds(job, now = new Date()) {
|
|
5
|
+
if (job.mode !== 'goal' || !job.state.goalStartedAt)
|
|
6
|
+
return 0;
|
|
7
|
+
const startedAt = Date.parse(job.state.goalStartedAt);
|
|
8
|
+
const endedAt = job.state.goalEndedAt ? Date.parse(job.state.goalEndedAt) : now.getTime();
|
|
9
|
+
if (!Number.isFinite(startedAt) || !Number.isFinite(endedAt))
|
|
10
|
+
return 0;
|
|
11
|
+
return Math.max(0, Math.floor((endedAt - startedAt) / 1000));
|
|
12
|
+
}
|
|
13
|
+
export function goalRemainingTokens(job) {
|
|
14
|
+
return job.tokenBudget === undefined ? undefined : Math.max(0, job.tokenBudget - (job.state.tokensUsed ?? 0));
|
|
15
|
+
}
|
|
16
|
+
export function goalCanStartNextTurn(job) {
|
|
17
|
+
const remaining = goalRemainingTokens(job);
|
|
18
|
+
return remaining === undefined || remaining > (job.tokenReserve ?? 0);
|
|
19
|
+
}
|
package/dist/loops/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
+
import { goalActiveTimeSeconds, goalElapsedWallClockSeconds } from './accounting.js';
|
|
3
4
|
import { createDefaultPiboLoopStore } from './store.js';
|
|
4
5
|
import { createBuiltInLoopStopConditions } from './stopping.js';
|
|
5
6
|
import { DEFAULT_PIBO_PROFILE_NAME } from '../plugins/builtin.js';
|
|
@@ -38,6 +39,9 @@ function maxIterations(value) { if (value === undefined)
|
|
|
38
39
|
function tokenBudget(value) { if (value === undefined)
|
|
39
40
|
return undefined; const parsed = Number(value); if (!Number.isInteger(parsed) || parsed < 1)
|
|
40
41
|
throw new Error('--token-budget must be a positive integer'); return parsed; }
|
|
42
|
+
function tokenReserve(value) { if (value === undefined)
|
|
43
|
+
return undefined; const parsed = Number(value); if (!Number.isInteger(parsed) || parsed < 0)
|
|
44
|
+
throw new Error('--token-reserve must be a non-negative integer'); return parsed; }
|
|
41
45
|
function loopMode(value) { if (value === undefined)
|
|
42
46
|
return undefined; if (value !== 'goal' && value !== 'ralph')
|
|
43
47
|
throw new Error('--mode must be goal or ralph'); return value; }
|
|
@@ -110,8 +114,16 @@ export function formatLoopResourceSummary(resources) {
|
|
|
110
114
|
parts.push('next=pibo compute reap --dry-run --include-dev');
|
|
111
115
|
return parts.length ? parts.join(';') : '-';
|
|
112
116
|
}
|
|
113
|
-
function formatLoopJobLine(job) {
|
|
114
|
-
|
|
117
|
+
function formatLoopJobLine(job) {
|
|
118
|
+
const goal = job.mode === 'goal' ? job.state.goalStatus ?? (job.enabled ? 'active' : 'paused') : '-';
|
|
119
|
+
const budget = job.mode === 'goal' ? job.tokenBudget === undefined ? 'unbounded' : `soft:${job.state.tokensUsed ?? 0}/${job.tokenBudget};reserve=${job.tokenReserve ?? 0}` : '-';
|
|
120
|
+
const time = job.mode === 'goal' ? `activeAgent=${goalActiveTimeSeconds(job)}s;elapsedWall=${goalElapsedWallClockSeconds(job)}s;paused=included` : '-';
|
|
121
|
+
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
|
+
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
|
+
return `${run.id}\t${run.jobId}\t${run.status}\t${run.piboSessionId ?? '-'}\t${run.completedAt ?? '-'}\taccounting=${accounting}\tresources=${formatLoopResourceSummary(run.resources)}`;
|
|
126
|
+
}
|
|
115
127
|
export async function runLoopCli(argv = process.argv, defaults = {}) {
|
|
116
128
|
const program = new Command();
|
|
117
129
|
program.name(defaults.commandName ?? 'pibo loop').description('Manage continuous Pibo loops').helpOption('-h, --help');
|
|
@@ -127,13 +139,13 @@ export async function runLoopCli(argv = process.argv, defaults = {}) {
|
|
|
127
139
|
else
|
|
128
140
|
for (const job of jobs)
|
|
129
141
|
console.log(formatLoopJobLine(job)); store.close(); });
|
|
130
|
-
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>', '
|
|
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 to remain before starting another Goal 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')
|
|
131
143
|
.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())
|
|
132
|
-
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), stopPolicy: base.stopPolicy ?? undefined }; applyRuntimeCreateOptions(input, options); const store = createDefaultPiboLoopStore({ path: program.opts().store }); const job = store.createJob(input); if (options.json)
|
|
144
|
+
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)
|
|
133
145
|
printJson(job);
|
|
134
146
|
else
|
|
135
147
|
console.log(`${job.id}\t${job.enabled ? 'running' : 'stopped'}\t${job.name}`); store.close(); });
|
|
136
|
-
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 Goal token budget').option('--clear-token-budget', 'Clear Goal token budget').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)
|
|
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>', 'Set soft Goal token budget').option('--clear-token-budget', 'Clear Goal token budget').option('--token-reserve <n>', 'Set pre-turn minimum remaining tokens').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)
|
|
137
149
|
patch.mode = loopMode(options.mode); if (options.name !== undefined)
|
|
138
150
|
patch.name = options.name; if (options.description !== undefined)
|
|
139
151
|
patch.description = options.description; if (options.profile !== undefined)
|
|
@@ -143,7 +155,11 @@ export async function runLoopCli(argv = process.argv, defaults = {}) {
|
|
|
143
155
|
throw new Error('Choose either --token-budget or --clear-token-budget, not both'); if (options.clearTokenBudget)
|
|
144
156
|
patch.tokenBudget = null;
|
|
145
157
|
else if (options.tokenBudget !== undefined)
|
|
146
|
-
patch.tokenBudget = tokenBudget(options.tokenBudget);
|
|
158
|
+
patch.tokenBudget = tokenBudget(options.tokenBudget); if (options.tokenReserve !== undefined && options.clearTokenReserve)
|
|
159
|
+
throw new Error('Choose either --token-reserve or --clear-token-reserve, not both'); if (options.clearTokenReserve)
|
|
160
|
+
patch.tokenReserve = null;
|
|
161
|
+
else if (options.tokenReserve !== undefined)
|
|
162
|
+
patch.tokenReserve = tokenReserve(options.tokenReserve); applyRuntimePatchOptions(patch, options); const target = maybeTargetFromOptions(options); if (target)
|
|
147
163
|
patch.target = target; if (Object.keys(patch).length === 0)
|
|
148
164
|
throw new Error('No Loop job update fields provided'); const job = store.updateJob(id, patch); if (!job)
|
|
149
165
|
throw new Error('Loop job not found'); if (options.json)
|
package/dist/loops/prompts.js
CHANGED
|
@@ -21,6 +21,7 @@ function buildGoalTurnPrompt(job, continuation, goalToolsAvailable) {
|
|
|
21
21
|
const tokensUsed = job.state.tokensUsed ?? 0;
|
|
22
22
|
const tokenBudget = job.tokenBudget;
|
|
23
23
|
const remainingTokens = tokenBudget === undefined ? 'unbounded' : String(Math.max(0, tokenBudget - tokensUsed));
|
|
24
|
+
const tokenReserve = job.tokenReserve ?? 0;
|
|
24
25
|
return [
|
|
25
26
|
continuation ? 'Continue working toward the active Pibo loop goal.' : 'Start working toward the active Pibo loop goal.',
|
|
26
27
|
'',
|
|
@@ -36,8 +37,10 @@ function buildGoalTurnPrompt(job, continuation, goalToolsAvailable) {
|
|
|
36
37
|
'- 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.',
|
|
37
38
|
'',
|
|
38
39
|
'Budget:',
|
|
40
|
+
'- Budget enforcement: soft; model usage is reported after a response and the current turn can overshoot the limit.',
|
|
39
41
|
`- Reported tokens used before this turn: ${tokensUsed}`,
|
|
40
|
-
`-
|
|
42
|
+
`- Soft token budget: ${tokenBudget ?? 'none'}`,
|
|
43
|
+
`- Pre-turn token reserve: ${tokenReserve}`,
|
|
41
44
|
`- Reported tokens remaining before this turn: ${remainingTokens}`,
|
|
42
45
|
'',
|
|
43
46
|
'Work from evidence:',
|