@a-t-h-i/bot-lobby 0.6.18 → 0.6.19
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/package.json +1 -1
- package/src/ask/relay.ts +3 -1
- package/src/ask/tool.ts +18 -3
- package/src/ask/types.ts +12 -0
- package/src/ask/web.ts +2 -1
- package/src/delivery/checks.ts +37 -0
- package/src/delivery/lock.ts +78 -0
- package/src/delivery/merge.ts +44 -0
- package/src/delivery/operations.ts +134 -0
- package/src/delivery/pr.ts +40 -0
- package/src/delivery/review.ts +90 -0
- package/src/delivery/rules.ts +46 -0
- package/src/delivery/source.ts +39 -0
- package/src/delivery/transport.ts +36 -0
- package/src/delivery/types.ts +60 -0
- package/src/execution/agent-runner.ts +1 -23
- package/src/index.ts +1 -1
- package/src/lobby/feed.ts +0 -6
- package/src/lobby/host.ts +3 -0
- package/src/lobby/planner.ts +1 -7
- package/src/lobby/pr-review.ts +1 -7
- package/src/lobby/prompt-hub.ts +7 -1
- package/src/lobby/quickfix.ts +3 -16
- package/src/lobby/service.ts +73 -3
- package/src/lobby/sessions.ts +12 -0
- package/src/lobby/task-rows.ts +5 -0
- package/src/master/master.ts +1 -1
- package/src/master/research.ts +0 -1
- package/src/pi/events.ts +0 -2
- package/src/pi/model-settings.ts +7 -26
- package/src/pi/model-support.ts +4 -19
- package/src/schemas/configuration.ts +8 -38
- package/src/schemas/findings.ts +0 -2
- package/src/schemas/task.ts +14 -0
- package/src/state/blocking-requests.ts +23 -0
- package/src/state/persistence.ts +12 -1
- package/src/state/phase-timing.ts +53 -0
- package/src/state/task-state.ts +2 -0
- package/src/state/timing-merge.ts +25 -0
- package/src/webui/api/index.ts +17 -1
- package/src/webui/api/tasks.ts +52 -0
- package/src/webui/protocol.ts +8 -0
- package/src/workflow/approvals.ts +3 -0
- package/src/workflow/workflow.ts +14 -33
- package/webui/dist/assets/index-BJI9lE5R.js +98 -0
- package/webui/dist/assets/index-Dg0tvkeY.css +2 -0
- package/webui/dist/build.json +83 -43
- package/webui/dist/index.html +2 -2
- package/src/execution/fallback.ts +0 -75
- package/src/pi/master-fallback.ts +0 -61
- package/webui/dist/assets/index-CHLNxlA1.js +0 -98
- package/webui/dist/assets/index-m9XcQvH8.css +0 -2
package/webui/dist/index.html
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
14
14
|
<title>bot-lobby</title>
|
|
15
15
|
<meta property="csp-nonce" nonce="__CSP_NONCE__">
|
|
16
|
-
<script type="module" crossorigin src="/assets/index-
|
|
17
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
16
|
+
<script type="module" crossorigin src="/assets/index-BJI9lE5R.js" nonce="__CSP_NONCE__"></script>
|
|
17
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Dg0tvkeY.css" nonce="__CSP_NONCE__">
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
20
20
|
<div id="root"></div>
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fallback models. A subscription or key that runs out mid-task (a usage
|
|
3
|
-
* limit, a rate limit, no credit, the provider down or refusing the key)
|
|
4
|
-
* fails every run on that model the same way, so the run goes to the agent's
|
|
5
|
-
* configured fallback model instead, and the exhausted model is skipped for a
|
|
6
|
-
* while so the next agents do not each spend a failed run finding out.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/** What a provider says when a model cannot be used right now. Deliberately narrow: an ordinary failure is not a reason to switch model. */
|
|
10
|
-
const UNAVAILABLE = [
|
|
11
|
-
/usage limit/i, /rate.?limit/i, /\bquota\b/i, /limit (?:reached|exceeded|will reset)/i, /exceeded (?:your|the|current)/i,
|
|
12
|
-
/out of (?:usage|credits?|extra usage|tokens)/i, /(?:insufficient|no) (?:credits?|funds|balance|quota)/i, /credit balance/i,
|
|
13
|
-
/billing/i, /payment required/i, /\b402\b/, /\b429\b/, /too many requests/i, /resource.?exhausted/i,
|
|
14
|
-
/overloaded/i, /\b529\b/, /(?:at|over) capacity/i, /resets? (?:at|in|on)/i, /subscription/i,
|
|
15
|
-
/unauthori[sz]ed/i, /\b401\b/, /invalid (?:api )?key/i, /no api key/i, /authentication (?:failed|error)/i,
|
|
16
|
-
/model (?:is )?(?:not found|not available|unavailable)/i, /unknown model/i, /does not have access/i,
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
/** True when a failed run's error says its model is out of usage or unavailable, so another model may succeed. */
|
|
20
|
-
export function looksUnavailable(error: string | undefined): boolean {
|
|
21
|
-
return Boolean(error && UNAVAILABLE.some((pattern) => pattern.test(error)));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** How long a model that ran out is skipped before it is tried again. */
|
|
25
|
-
export const COOLDOWN_MS = 20 * 60 * 1000;
|
|
26
|
-
|
|
27
|
-
const exhausted = new Map<string, number>();
|
|
28
|
-
|
|
29
|
-
/** Remember that `model` cannot be used until the cooldown passes. */
|
|
30
|
-
export function markUnavailable(model: string, now = Date.now()): void {
|
|
31
|
-
exhausted.set(model, now + COOLDOWN_MS);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** Whether `model` ran out recently enough to skip. */
|
|
35
|
-
export function isUnavailable(model: string | undefined, now = Date.now()): boolean {
|
|
36
|
-
if (!model) return false;
|
|
37
|
-
const until = exhausted.get(model);
|
|
38
|
-
if (until === undefined) return false;
|
|
39
|
-
if (until <= now) {
|
|
40
|
-
exhausted.delete(model);
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Forget every exhausted model (tests). */
|
|
47
|
-
export function resetUnavailable(): void {
|
|
48
|
-
exhausted.clear();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** A fallback worth switching to: a model other than the one that failed. */
|
|
52
|
-
export function usableFallback<F extends { model: string }>(fallback: F | undefined, current: string | undefined): F | undefined {
|
|
53
|
-
return fallback && fallback.model !== current ? fallback : undefined;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Run `attempt` on `model`; when it fails because that model is out of usage
|
|
58
|
-
* or unavailable, run it again on the fallback. A model already known to be
|
|
59
|
-
* out goes straight to the fallback. `switched` says which model took over.
|
|
60
|
-
*/
|
|
61
|
-
export async function withFallback<T extends { status: string; error?: string }>(
|
|
62
|
-
model: string | undefined,
|
|
63
|
-
thinking: string,
|
|
64
|
-
fallback: { model: string; thinking: string } | undefined,
|
|
65
|
-
attempt: (model: string | undefined, thinking: string) => Promise<T>,
|
|
66
|
-
): Promise<{ result: T; switchedFrom?: string }> {
|
|
67
|
-
const other = usableFallback(fallback, model);
|
|
68
|
-
if (other && isUnavailable(model)) return { result: await attempt(other.model, other.thinking), switchedFrom: model ?? "the session model" };
|
|
69
|
-
const result = await attempt(model, thinking);
|
|
70
|
-
if (other && result.status === "failed" && looksUnavailable(result.error)) {
|
|
71
|
-
if (model) markUnavailable(model);
|
|
72
|
-
return { result: await attempt(other.model, other.thinking), switchedFrom: model ?? "the session model" };
|
|
73
|
-
}
|
|
74
|
-
return { result };
|
|
75
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The master runs in your own Pi session, on the session's model. When that
|
|
3
|
-
* model runs out of usage mid-task (a subscription limit, no credit) the turn
|
|
4
|
-
* ends with an error and the task would stall. With a fallback model set for
|
|
5
|
-
* the master, the session switches to it and the oracle carries on.
|
|
6
|
-
*/
|
|
7
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import { looksUnavailable, markUnavailable } from "../execution/fallback.ts";
|
|
9
|
-
import { fallbackOf } from "../schemas/configuration.ts";
|
|
10
|
-
import { loadConfig } from "../state/project.ts";
|
|
11
|
-
import { modelRef } from "./model-support.ts";
|
|
12
|
-
import { modelLookup } from "./tools.ts";
|
|
13
|
-
import { isSubagentProcess } from "./quiet.ts";
|
|
14
|
-
|
|
15
|
-
/** The provider's error when the last thing in the turn was a failed assistant message that says the model is out of usage or unavailable. */
|
|
16
|
-
export function usageFailure(messages: readonly unknown[]): string | undefined {
|
|
17
|
-
const last = messages.at(-1) as { role?: string; stopReason?: string; errorMessage?: string } | undefined;
|
|
18
|
-
if (last?.role !== "assistant" || last.stopReason !== "error") return undefined;
|
|
19
|
-
return looksUnavailable(last.errorMessage) ? last.errorMessage : undefined;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** What the oracle is told after the switch. */
|
|
23
|
-
export function switchedMessage(from: string, to: string): string {
|
|
24
|
-
return `bot-lobby: your last turn failed because ${from} is out of usage or unavailable, so this session switched to ${to}. Carry on with the task from where you left off; do not repeat work that is already done.`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function registerMasterFallback(pi: ExtensionAPI): void {
|
|
28
|
-
if (isSubagentProcess()) return;
|
|
29
|
-
let failure: string | undefined;
|
|
30
|
-
pi.on("agent_end", (event) => {
|
|
31
|
-
failure = usageFailure(event.messages);
|
|
32
|
-
});
|
|
33
|
-
pi.on("agent_settled", async (_event, ctx: ExtensionContext) => {
|
|
34
|
-
if (!failure) return;
|
|
35
|
-
failure = undefined;
|
|
36
|
-
await switchMaster(pi, ctx);
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function switchMaster(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void> {
|
|
41
|
-
const fallback = fallbackOf(loadConfig().master);
|
|
42
|
-
if (!fallback) return;
|
|
43
|
-
const current = ctx.model ? modelRef(ctx.model) : "the session model";
|
|
44
|
-
if (current === fallback.model) {
|
|
45
|
-
ctx.ui.notify(`bot-lobby: ${current} (the master's fallback) is out of usage too — pick another model or wait.`, "warning");
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
const model = modelLookup(ctx)(fallback.model);
|
|
49
|
-
if (!model) {
|
|
50
|
-
ctx.ui.notify(`bot-lobby: the master's fallback model "${fallback.model}" is not known to Pi.`, "warning");
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
if (!(await pi.setModel(model))) {
|
|
54
|
-
ctx.ui.notify(`bot-lobby: no auth for the master's fallback model ${fallback.model}.`, "warning");
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
pi.setThinkingLevel(fallback.thinking as Parameters<ExtensionAPI["setThinkingLevel"]>[0]);
|
|
58
|
-
if (ctx.model) markUnavailable(current);
|
|
59
|
-
ctx.ui.notify(`bot-lobby: ${current} ran out of usage; the master now runs on ${fallback.model} (${fallback.thinking}). Switch back with /model when it is available again.`, "warning");
|
|
60
|
-
await pi.sendUserMessage(switchedMessage(current, fallback.model));
|
|
61
|
-
}
|