@pasko70/pibo 2.4.0 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-runtime/routed-session.js +151 -7
- package/dist/agent-runtimes/codex-native/turn.js +3 -1
- package/dist/agent-runtimes/omp/turn.js +36 -7
- package/dist/agent-runtimes/pi/routed-session.js +62 -16
- package/dist/apps/chat/web-app.js +6 -8
- package/dist/apps/chat-ui/assets/{dist-CUcAofmV.js → dist-3YG57JXi.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Byygd1lH.js → dist-BeqHbnGN.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DusFwy0L.js → dist-CrDtveZB.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-C9BrS7sL.js → dist-Cw9po47P.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D4RU6xu3.js → dist-DTRjeLwO.js} +1 -1
- package/dist/apps/chat-ui/assets/{index-Bifi_kjN.js → index-AjnP3ci-.js} +89 -89
- package/dist/apps/chat-ui/index.html +1 -1
- package/dist/apps/chat-vscode-web/assets/{index-WsLm1mo3.js → index-DvTSSvzN.js} +5 -5
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/{pibo-vscode-ext-2.4.0.vsix → pibo-vscode-ext-2.4.2.vsix} +0 -0
- package/dist/core/session-router.js +153 -63
- package/dist/gateway/web.js +8 -1
- package/dist/loops/accounting.js +27 -0
- package/dist/loops/cli.js +6 -5
- package/dist/loops/prompts.js +13 -5
- package/dist/loops/service.js +8 -2
- package/dist/loops/store.js +27 -11
- package/dist/loops/tools.js +10 -5
- package/dist/mcp/config-command.js +3 -2
- package/dist/mcp/config.js +10 -4
- package/dist/mcp/errors.js +1 -1
- package/dist/mcp/index.js +19 -33
- package/dist/reliability/store.js +11 -6
- package/dist/runs/lifecycle.js +12 -0
- package/dist/runs/registry.js +23 -4
- package/dist/runs/tools.js +12 -3
- package/dist/session-ui/sessionActivity.js +6 -2
- package/dist/signals/status.js +9 -4
- package/dist/tools/guides.js +1 -1
- package/dist/web/channel.js +10 -4
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/builtin/loop/SKILL.md +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#101d22" />
|
|
7
7
|
<title>Pibo</title>
|
|
8
|
-
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-DvTSSvzN.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-CF1-9PKU.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
Binary file
|
package/dist/apps/vscode-artifacts/{pibo-vscode-ext-2.4.0.vsix → pibo-vscode-ext-2.4.2.vsix}
RENAMED
|
Binary file
|
|
@@ -5,7 +5,7 @@ import { RuntimeRoutedSession as RoutedSession, } from "../agent-runtime/routed-
|
|
|
5
5
|
import { runtimeSessionErrorDetails } from "./session-errors.js";
|
|
6
6
|
import { normalizePiboAgentObservationCursor, normalizePiboAgentObservationLimit, normalizePiboAgentObservationOrder, parsePiboAgentObservationTimestamp, piboAgentObservationDetails, piboAgentObservationKind, piboAgentObservationRole, piboAgentObservationSourceFromEvent, piboAgentObservationText, } from "../subagents/observations.js";
|
|
7
7
|
import { PiboRunRegistry } from "../runs/registry.js";
|
|
8
|
-
import { PiboRunExecutionTimeoutError } from "../runs/lifecycle.js";
|
|
8
|
+
import { PiboRunCancellationError, PiboRunCancelledError, PiboRunExecutionTimeoutError } from "../runs/lifecycle.js";
|
|
9
9
|
import { PiboRunResourceLimitError } from "../runs/resource-isolation.js";
|
|
10
10
|
import { createPiboSignalRegistry } from "../signals/registry.js";
|
|
11
11
|
import { createDefaultPiboReliabilityStore } from "../reliability/store.js";
|
|
@@ -150,7 +150,29 @@ function formatRunReminderMessage(notification) {
|
|
|
150
150
|
].join("\n");
|
|
151
151
|
}
|
|
152
152
|
function isRunReminderServiceMessage(event) {
|
|
153
|
-
return event.source === "service" && event.
|
|
153
|
+
return event.source === "service" && event.text.startsWith("<pibo_run_notification>");
|
|
154
|
+
}
|
|
155
|
+
function yieldedRunOrigin(event) {
|
|
156
|
+
if (!event?.id || event.provenance?.kind !== "loop-run")
|
|
157
|
+
return undefined;
|
|
158
|
+
return {
|
|
159
|
+
eventId: event.provenance.rootEventId ?? event.id,
|
|
160
|
+
provenance: {
|
|
161
|
+
kind: event.provenance.kind,
|
|
162
|
+
jobId: event.provenance.jobId,
|
|
163
|
+
runId: event.provenance.runId,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function runReminderProvenance(notification) {
|
|
168
|
+
const origin = notification.origin;
|
|
169
|
+
if (!origin || origin.provenance.kind !== "loop-run")
|
|
170
|
+
return undefined;
|
|
171
|
+
return {
|
|
172
|
+
...origin.provenance,
|
|
173
|
+
cause: "run-reminder",
|
|
174
|
+
rootEventId: origin.eventId,
|
|
175
|
+
};
|
|
154
176
|
}
|
|
155
177
|
function isTerminalRunStatus(status) {
|
|
156
178
|
return status === "completed" || status === "failed" || status === "timed_out" || status === "cancelled";
|
|
@@ -235,7 +257,7 @@ export class PiboSessionRouter {
|
|
|
235
257
|
portableHistoryProvider;
|
|
236
258
|
runtimeResourceSessions = new Map();
|
|
237
259
|
runtimeAuthFingerprints = new Map();
|
|
238
|
-
|
|
260
|
+
activeSubagentRequests = new Map();
|
|
239
261
|
agentObservations = [];
|
|
240
262
|
agentObservationEvictedThroughByParent = new Map();
|
|
241
263
|
nextAgentObservationSequence = 1;
|
|
@@ -388,11 +410,24 @@ export class PiboSessionRouter {
|
|
|
388
410
|
teardownCompleted = true;
|
|
389
411
|
return output;
|
|
390
412
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
413
|
+
let output;
|
|
414
|
+
if (event.action === "abort") {
|
|
415
|
+
const [sessionAbort, childAbort] = await Promise.allSettled([
|
|
416
|
+
session.executeAction(event),
|
|
417
|
+
this.abortActiveSubagentSessions(event.piboSessionId),
|
|
418
|
+
]);
|
|
419
|
+
if (sessionAbort.status === "rejected" && childAbort.status === "rejected") {
|
|
420
|
+
throw new AggregateError([sessionAbort.reason, childAbort.reason], "Failed to abort the session and its active subagent requests.");
|
|
421
|
+
}
|
|
422
|
+
if (sessionAbort.status === "rejected")
|
|
423
|
+
throw sessionAbort.reason;
|
|
424
|
+
if (childAbort.status === "rejected")
|
|
425
|
+
throw childAbort.reason;
|
|
426
|
+
output = sessionAbort.value;
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
output = await session.executeAction(event);
|
|
430
|
+
}
|
|
396
431
|
if (event.action === "kill" || event.action === "kill_all") {
|
|
397
432
|
await this.disposeSessionSubtree(event.piboSessionId, `${event.action} action`, { cancelRuns: event.action === "kill_all" });
|
|
398
433
|
teardownCompleted = true;
|
|
@@ -837,20 +872,10 @@ export class PiboSessionRouter {
|
|
|
837
872
|
const eventWithId = { ...event, id: event.id ?? randomUUID() };
|
|
838
873
|
return await new Promise((resolve, reject) => {
|
|
839
874
|
let settled = false;
|
|
840
|
-
let
|
|
875
|
+
let dispatchPromise;
|
|
841
876
|
let lastAssistantMessage;
|
|
842
877
|
let timeout;
|
|
843
|
-
const
|
|
844
|
-
if (!messageDispatched)
|
|
845
|
-
return;
|
|
846
|
-
void this.emit({
|
|
847
|
-
type: "execution",
|
|
848
|
-
piboSessionId: eventWithId.piboSessionId,
|
|
849
|
-
action: "abort",
|
|
850
|
-
id: randomUUID(),
|
|
851
|
-
}).catch(() => { });
|
|
852
|
-
};
|
|
853
|
-
const finish = (result) => {
|
|
878
|
+
const claimSettlement = () => {
|
|
854
879
|
if (settled)
|
|
855
880
|
return false;
|
|
856
881
|
settled = true;
|
|
@@ -858,16 +883,33 @@ export class PiboSessionRouter {
|
|
|
858
883
|
clearTimeout(timeout);
|
|
859
884
|
signal?.removeEventListener("abort", onAbort);
|
|
860
885
|
unsubscribe();
|
|
886
|
+
return true;
|
|
887
|
+
};
|
|
888
|
+
const finish = (result) => {
|
|
889
|
+
if (!claimSettlement())
|
|
890
|
+
return;
|
|
861
891
|
if (result instanceof Error)
|
|
862
892
|
reject(result);
|
|
863
893
|
else
|
|
864
894
|
resolve(result);
|
|
865
|
-
return true;
|
|
866
895
|
};
|
|
867
|
-
const
|
|
868
|
-
if (!
|
|
896
|
+
const rejectAfterMessageCancellation = (error) => {
|
|
897
|
+
if (!claimSettlement())
|
|
869
898
|
return;
|
|
870
|
-
|
|
899
|
+
void (async () => {
|
|
900
|
+
try {
|
|
901
|
+
await dispatchPromise;
|
|
902
|
+
await this.cancelSessionMessage(eventWithId.piboSessionId, eventWithId.id);
|
|
903
|
+
}
|
|
904
|
+
catch (cancellationError) {
|
|
905
|
+
reject(new PiboRunCancellationError(`Failed to cancel subagent request "${eventWithId.id}" in Pibo session "${eventWithId.piboSessionId}".`, { cause: cancellationError }));
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
reject(error);
|
|
909
|
+
})();
|
|
910
|
+
};
|
|
911
|
+
const onAbort = () => {
|
|
912
|
+
rejectAfterMessageCancellation(subagentAbortError());
|
|
871
913
|
};
|
|
872
914
|
const unsubscribe = this.subscribe((output) => {
|
|
873
915
|
if (output.piboSessionId !== eventWithId.piboSessionId ||
|
|
@@ -886,20 +928,25 @@ export class PiboSessionRouter {
|
|
|
886
928
|
}
|
|
887
929
|
});
|
|
888
930
|
if (signal?.aborted) {
|
|
889
|
-
|
|
931
|
+
finish(subagentAbortError());
|
|
890
932
|
return;
|
|
891
933
|
}
|
|
892
934
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
893
935
|
timeout = setTimeout(() => {
|
|
894
|
-
|
|
895
|
-
if (!finish(timeoutError))
|
|
896
|
-
return;
|
|
897
|
-
abortChild();
|
|
936
|
+
rejectAfterMessageCancellation(new PiboRunExecutionTimeoutError(`Timed out waiting for assistant reply from Pibo session "${eventWithId.piboSessionId}"`, "lifetime"));
|
|
898
937
|
}, timeoutMs);
|
|
899
|
-
|
|
900
|
-
|
|
938
|
+
dispatchPromise = this.emit(eventWithId);
|
|
939
|
+
dispatchPromise.catch((error) => {
|
|
940
|
+
finish(error instanceof Error ? error : new Error(String(error)));
|
|
941
|
+
});
|
|
901
942
|
});
|
|
902
943
|
}
|
|
944
|
+
async cancelSessionMessage(piboSessionId, eventId) {
|
|
945
|
+
const session = this.sessions.get(piboSessionId);
|
|
946
|
+
if (!session || !await session.cancelMessage(eventId)) {
|
|
947
|
+
throw new Error(`Pibo session "${piboSessionId}" no longer owns message "${eventId}".`);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
903
950
|
async disposeAll() {
|
|
904
951
|
if (this.disposePromise)
|
|
905
952
|
return this.disposePromise;
|
|
@@ -946,7 +993,7 @@ export class PiboSessionRouter {
|
|
|
946
993
|
await this.portableToolService.dispose();
|
|
947
994
|
await this.runtimeResourceService.dispose();
|
|
948
995
|
this.runtimeResourceSessions.clear();
|
|
949
|
-
this.
|
|
996
|
+
this.activeSubagentRequests.clear();
|
|
950
997
|
this.agentObservations.length = 0;
|
|
951
998
|
this.agentObservationEvictedThroughByParent.clear();
|
|
952
999
|
await this.telemetryWriter?.dispose();
|
|
@@ -1562,44 +1609,40 @@ export class PiboSessionRouter {
|
|
|
1562
1609
|
this.signalRegistry.project({ type: "session_created", session: created });
|
|
1563
1610
|
return created;
|
|
1564
1611
|
}
|
|
1565
|
-
trackActiveSubagent(parentPiboSessionId,
|
|
1566
|
-
let
|
|
1567
|
-
if (!
|
|
1568
|
-
|
|
1569
|
-
this.
|
|
1612
|
+
trackActiveSubagent(parentPiboSessionId, request) {
|
|
1613
|
+
let requests = this.activeSubagentRequests.get(parentPiboSessionId);
|
|
1614
|
+
if (!requests) {
|
|
1615
|
+
requests = new Set();
|
|
1616
|
+
this.activeSubagentRequests.set(parentPiboSessionId, requests);
|
|
1570
1617
|
}
|
|
1571
|
-
|
|
1618
|
+
requests.add(request);
|
|
1572
1619
|
let active = true;
|
|
1573
1620
|
return () => {
|
|
1574
1621
|
if (!active)
|
|
1575
1622
|
return;
|
|
1576
1623
|
active = false;
|
|
1577
|
-
const current = this.
|
|
1624
|
+
const current = this.activeSubagentRequests.get(parentPiboSessionId);
|
|
1578
1625
|
if (!current)
|
|
1579
1626
|
return;
|
|
1580
|
-
|
|
1581
|
-
if (remaining > 0)
|
|
1582
|
-
current.set(childPiboSessionId, remaining);
|
|
1583
|
-
else
|
|
1584
|
-
current.delete(childPiboSessionId);
|
|
1627
|
+
current.delete(request);
|
|
1585
1628
|
if (current.size === 0)
|
|
1586
|
-
this.
|
|
1629
|
+
this.activeSubagentRequests.delete(parentPiboSessionId);
|
|
1587
1630
|
};
|
|
1588
1631
|
}
|
|
1589
1632
|
async abortActiveSubagentSessions(parentPiboSessionId) {
|
|
1590
|
-
const
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
await Promise.
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
id: randomUUID(),
|
|
1598
|
-
})));
|
|
1633
|
+
const requests = [...(this.activeSubagentRequests.get(parentPiboSessionId) ?? [])];
|
|
1634
|
+
for (const request of requests)
|
|
1635
|
+
request.abortController.abort();
|
|
1636
|
+
const settlements = await Promise.all(requests.map(async (request) => await request.settled));
|
|
1637
|
+
const failures = settlements.flatMap((settlement) => settlement.status === "rejected" ? [settlement.reason] : []);
|
|
1638
|
+
if (failures.length > 0)
|
|
1639
|
+
throw new AggregateError(failures, "Failed to cancel active subagent requests.");
|
|
1599
1640
|
}
|
|
1600
1641
|
createAgentsController(parentPiboSessionId) {
|
|
1601
1642
|
return {
|
|
1602
1643
|
sendMessage: async ({ subagent, message, threadKey, toolCallId, signal }) => {
|
|
1644
|
+
if (signal?.aborted)
|
|
1645
|
+
throw subagentAbortError();
|
|
1603
1646
|
this.assertSubagentDepth(parentPiboSessionId, subagent);
|
|
1604
1647
|
const child = this.resolveSubagentSession(parentPiboSessionId, subagent, threadKey);
|
|
1605
1648
|
const resolvedThreadKey = typeof child.metadata?.threadKey === "string" ? child.metadata.threadKey : "";
|
|
@@ -1619,9 +1662,21 @@ export class PiboSessionRouter {
|
|
|
1619
1662
|
childPiboSessionId: child.id,
|
|
1620
1663
|
threadKey: resolvedThreadKey,
|
|
1621
1664
|
});
|
|
1622
|
-
const
|
|
1665
|
+
const parentAbortController = new AbortController();
|
|
1666
|
+
const requestSignal = signal
|
|
1667
|
+
? AbortSignal.any([signal, parentAbortController.signal])
|
|
1668
|
+
: parentAbortController.signal;
|
|
1669
|
+
let resolveSettled;
|
|
1670
|
+
const settled = new Promise((resolve) => {
|
|
1671
|
+
resolveSettled = resolve;
|
|
1672
|
+
});
|
|
1673
|
+
const untrack = this.trackActiveSubagent(parentPiboSessionId, {
|
|
1674
|
+
abortController: parentAbortController,
|
|
1675
|
+
settled,
|
|
1676
|
+
});
|
|
1677
|
+
let settlement = { status: "fulfilled" };
|
|
1623
1678
|
try {
|
|
1624
|
-
const reply = await this.emitMessageAndWaitForReply(event, subagent.timeoutMs ?? DEFAULT_SUBAGENT_REPLY_TIMEOUT_MS,
|
|
1679
|
+
const reply = await this.emitMessageAndWaitForReply(event, subagent.timeoutMs ?? DEFAULT_SUBAGENT_REPLY_TIMEOUT_MS, requestSignal);
|
|
1625
1680
|
return {
|
|
1626
1681
|
agentId: child.id,
|
|
1627
1682
|
name: subagent.name,
|
|
@@ -1631,8 +1686,17 @@ export class PiboSessionRouter {
|
|
|
1631
1686
|
reply,
|
|
1632
1687
|
};
|
|
1633
1688
|
}
|
|
1689
|
+
catch (error) {
|
|
1690
|
+
const confirmedParentCancellation = parentAbortController.signal.aborted
|
|
1691
|
+
&& error instanceof Error
|
|
1692
|
+
&& error.name === "AbortError";
|
|
1693
|
+
if (!confirmedParentCancellation)
|
|
1694
|
+
settlement = { status: "rejected", reason: error };
|
|
1695
|
+
throw error;
|
|
1696
|
+
}
|
|
1634
1697
|
finally {
|
|
1635
1698
|
untrack();
|
|
1699
|
+
resolveSettled?.(settlement);
|
|
1636
1700
|
}
|
|
1637
1701
|
},
|
|
1638
1702
|
listAgents: () => this.listManagedAgents(parentPiboSessionId),
|
|
@@ -1767,8 +1831,9 @@ export class PiboSessionRouter {
|
|
|
1767
1831
|
const cancel = this.runCancellationHandlers.get(run.runId);
|
|
1768
1832
|
if (!cancel)
|
|
1769
1833
|
return;
|
|
1770
|
-
this.runCancellationHandlers.delete(run.runId);
|
|
1771
1834
|
await cancel();
|
|
1835
|
+
if (this.runCancellationHandlers.get(run.runId) === cancel)
|
|
1836
|
+
this.runCancellationHandlers.delete(run.runId);
|
|
1772
1837
|
}));
|
|
1773
1838
|
const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
1774
1839
|
if (failures.length > 0)
|
|
@@ -1796,14 +1861,32 @@ export class PiboSessionRouter {
|
|
|
1796
1861
|
timeoutMs,
|
|
1797
1862
|
serviceWarning,
|
|
1798
1863
|
resources,
|
|
1864
|
+
origin: yieldedRunOrigin(this.sessions.get(parentPiboSessionId)?.getActiveMessage?.()),
|
|
1799
1865
|
});
|
|
1800
1866
|
}
|
|
1801
1867
|
catch (error) {
|
|
1802
1868
|
admission.release();
|
|
1803
1869
|
throw error;
|
|
1804
1870
|
}
|
|
1805
|
-
|
|
1806
|
-
|
|
1871
|
+
const cancellation = { state: "none" };
|
|
1872
|
+
if (cancel) {
|
|
1873
|
+
this.runCancellationHandlers.set(run.runId, async () => {
|
|
1874
|
+
cancellation.state = "pending";
|
|
1875
|
+
let resolveDecision;
|
|
1876
|
+
cancellation.decision = new Promise((resolve) => { resolveDecision = resolve; });
|
|
1877
|
+
try {
|
|
1878
|
+
await cancel();
|
|
1879
|
+
cancellation.state = "confirmed";
|
|
1880
|
+
}
|
|
1881
|
+
catch (error) {
|
|
1882
|
+
cancellation.state = "failed";
|
|
1883
|
+
throw error;
|
|
1884
|
+
}
|
|
1885
|
+
finally {
|
|
1886
|
+
resolveDecision?.();
|
|
1887
|
+
}
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1807
1890
|
void (async () => {
|
|
1808
1891
|
try {
|
|
1809
1892
|
const result = await execute();
|
|
@@ -1814,6 +1897,12 @@ export class PiboSessionRouter {
|
|
|
1814
1897
|
this.handleTerminalRunReminder(parentPiboSessionId, completed.runId, reminderGeneration);
|
|
1815
1898
|
}
|
|
1816
1899
|
catch (error) {
|
|
1900
|
+
if (error instanceof PiboRunCancelledError) {
|
|
1901
|
+
if (cancellation.state === "pending")
|
|
1902
|
+
await cancellation.decision;
|
|
1903
|
+
if (cancellation.state === "confirmed")
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1817
1906
|
const message = error instanceof Error ? error.message : String(error);
|
|
1818
1907
|
if (resources)
|
|
1819
1908
|
this.runRegistry.updateResources(run.runId, resources);
|
|
@@ -1842,14 +1931,15 @@ export class PiboSessionRouter {
|
|
|
1842
1931
|
return run;
|
|
1843
1932
|
},
|
|
1844
1933
|
cancelRun: async (runId) => {
|
|
1845
|
-
const
|
|
1934
|
+
const current = this.runRegistry.status(parentPiboSessionId, runId);
|
|
1846
1935
|
try {
|
|
1847
|
-
|
|
1936
|
+
if (!isTerminalRunStatus(current.status))
|
|
1937
|
+
await this.invokeRunCancellationHandlers([current]);
|
|
1938
|
+
return this.runRegistry.cancel(parentPiboSessionId, runId);
|
|
1848
1939
|
}
|
|
1849
1940
|
finally {
|
|
1850
1941
|
this.refreshQueuedRunReminders(parentPiboSessionId);
|
|
1851
1942
|
}
|
|
1852
|
-
return cancelled;
|
|
1853
1943
|
},
|
|
1854
1944
|
ackRun: (runId) => {
|
|
1855
1945
|
const run = this.runRegistry.ack(parentPiboSessionId, runId);
|
|
@@ -2067,8 +2157,8 @@ export class PiboSessionRouter {
|
|
|
2067
2157
|
piboSessionId,
|
|
2068
2158
|
text: formatRunReminderMessage(notification),
|
|
2069
2159
|
source: "service",
|
|
2070
|
-
capabilityScope: "run-reminder",
|
|
2071
2160
|
id: randomUUID(),
|
|
2161
|
+
provenance: runReminderProvenance(notification),
|
|
2072
2162
|
});
|
|
2073
2163
|
}
|
|
2074
2164
|
catch (error) {
|
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';
|
|
@@ -530,13 +531,18 @@ export class PiboLoopService {
|
|
|
530
531
|
this.store.updateRunMessageState(eventId, 'invalidated');
|
|
531
532
|
if (event.type !== 'assistant_usage')
|
|
532
533
|
return;
|
|
533
|
-
const
|
|
534
|
+
const provenance = event.provenance?.kind === 'loop-run' ? event.provenance : undefined;
|
|
535
|
+
const run = this.store.getRunByMessageEventId(eventId) ?? (provenance ? this.store.getRun(provenance.runId) : undefined);
|
|
534
536
|
if (!run || run.piboSessionId !== event.piboSessionId)
|
|
535
537
|
return;
|
|
538
|
+
if (provenance && (run.jobId !== provenance.jobId
|
|
539
|
+
|| (provenance.cause === 'run-reminder' && run.messageEventId !== provenance.rootEventId)))
|
|
540
|
+
return;
|
|
536
541
|
const job = this.store.getJob(run.jobId);
|
|
537
542
|
if (!job || job.mode !== 'goal')
|
|
538
543
|
return;
|
|
539
|
-
|
|
544
|
+
const basis = run.accounting?.tokenAccounting?.basis ?? goalTokenAccounting(job).basis;
|
|
545
|
+
this.store.recordGoalTurnUsage(job.id, run.id, goalBudgetTokens(event, basis));
|
|
540
546
|
}
|
|
541
547
|
handleProductEvent(event) {
|
|
542
548
|
if (event.type !== 'pibo.loop.fact' && event.type !== 'loop.fact' && event.type !== 'pibo.ralph.fact' && event.type !== 'ralph.fact')
|