paseo-bm-plugin 0.0.0-placeholder.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/LICENSE +21 -0
- package/README.md +53 -0
- package/client/agent-tree.ts +308 -0
- package/client/answer-state.ts +62 -0
- package/client/bead-chips.tsx +147 -0
- package/client/beads-header-button.ts +108 -0
- package/client/beads-model.ts +581 -0
- package/client/beads-screen.tsx +516 -0
- package/client/beads-tab.tsx +58 -0
- package/client/chat-card.tsx +636 -0
- package/client/chat-cards.ts +1038 -0
- package/client/dashboard-actions.tsx +255 -0
- package/client/dashboard-model.ts +947 -0
- package/client/dashboard-view.ts +215 -0
- package/client/dashboard.tsx +318 -0
- package/client/launch-manager.ts +323 -0
- package/client/launcher.tsx +516 -0
- package/client/markdown-view.tsx +112 -0
- package/client/markdown.ts +145 -0
- package/client/settings.tsx +104 -0
- package/client/setup-model.ts +552 -0
- package/client/setup-screen.tsx +913 -0
- package/client/slot.ts +47 -0
- package/client/tree.tsx +204 -0
- package/client/ui.tsx +262 -0
- package/client/waiting-pills-model.ts +156 -0
- package/client/waiting-pills.tsx +201 -0
- package/index.client.tsx +232 -0
- package/index.server.ts +168 -0
- package/package.json +35 -0
- package/paseo-plugin.json +6 -0
- package/roles/manager.md +181 -0
- package/roles/reviewer.md +160 -0
- package/roles/worker.md +407 -0
- package/server/agent-labels.ts +194 -0
- package/server/agent-role.ts +102 -0
- package/server/answer-marks.ts +120 -0
- package/server/bead-actions.ts +88 -0
- package/server/bead-work.ts +80 -0
- package/server/beads-store.ts +342 -0
- package/server/bm-report.ts +433 -0
- package/server/chat-peers.ts +65 -0
- package/server/chat-rpc.ts +122 -0
- package/server/chat-waiting.ts +182 -0
- package/server/collector.ts +629 -0
- package/server/config-writer.ts +222 -0
- package/server/cost.ts +88 -0
- package/server/dashboard-rpc.ts +662 -0
- package/server/fallback-detect.ts +183 -0
- package/server/fallback-handover.ts +365 -0
- package/server/fallback-manager.ts +170 -0
- package/server/fallback-reviewer.ts +198 -0
- package/server/fallback-rpc.ts +306 -0
- package/server/fallback-settings.ts +322 -0
- package/server/fallback-state.ts +518 -0
- package/server/fallback-switch.ts +191 -0
- package/server/fallback-wait.ts +188 -0
- package/server/format-check.ts +352 -0
- package/server/install-home.ts +187 -0
- package/server/live-timeline.ts +129 -0
- package/server/manager-instructions.ts +9 -0
- package/server/manager.ts +647 -0
- package/server/model-costs.ts +238 -0
- package/server/notice-queue.ts +315 -0
- package/server/notices.ts +81 -0
- package/server/paseo-cli.ts +115 -0
- package/server/provider-id.ts +12 -0
- package/server/review-budget.ts +208 -0
- package/server/reviewer-instructions.ts +9 -0
- package/server/role-choices.ts +161 -0
- package/server/role-extras.ts +270 -0
- package/server/role-hook.ts +347 -0
- package/server/role-mode.ts +397 -0
- package/server/role-settings-rpc.ts +325 -0
- package/server/roles.ts +96 -0
- package/server/settings-notices.ts +112 -0
- package/server/setup-rpc.ts +70 -0
- package/server/setup-skills.ts +121 -0
- package/server/setup-tools.ts +162 -0
- package/server/shell.ts +68 -0
- package/server/stop-propagation.ts +365 -0
- package/server/tools-check.ts +118 -0
- package/server/trace-store.ts +1137 -0
- package/server/traces.ts +1356 -0
- package/server/worker-instructions.ts +9 -0
- package/server/workflow-steps.ts +422 -0
- package/shared/bead-ids.ts +25 -0
- package/shared/bm-fallback.ts +91 -0
- package/shared/bm-format.ts +424 -0
- package/shared/bm-questions.ts +213 -0
- package/shared/bm-report.ts +433 -0
- package/shared/contracts.ts +1371 -0
- package/shared/fallback-patterns.ts +201 -0
- package/shared/fallback.ts +46 -0
- package/shared/new-request.ts +20 -0
- package/shared/order.ts +22 -0
- package/shared/prices.ts +65 -0
- package/shared/settings.ts +57 -0
- package/shared/sole-worker.ts +20 -0
- package/shared/version.ts +6 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text patterns of a provider-plan failure, and the classifier that applies
|
|
3
|
+
* them (delta 20260921 §4.4.4, REQ-065 b).
|
|
4
|
+
*
|
|
5
|
+
* No Paseo provider classifies a usage limit (proposal S5), so the plugin reads
|
|
6
|
+
* the words of the failure: a failed turn's error message, or the short last
|
|
7
|
+
* reply of a turn that did nothing else (`server/fallback-detect.ts`).
|
|
8
|
+
*
|
|
9
|
+
* | Class | Meaning | Fallback |
|
|
10
|
+
* |-------|-------------------------------|----------|
|
|
11
|
+
* | L1 | plan usage limit | yes |
|
|
12
|
+
* | L2 | billing | yes |
|
|
13
|
+
* | L4 | not logged in | yes |
|
|
14
|
+
* | L5 | provider unavailable | yes |
|
|
15
|
+
* | L3 | temporary rate limit | no |
|
|
16
|
+
* | L6 | no pattern matched | no |
|
|
17
|
+
*
|
|
18
|
+
* The defaults are a starting point never checked on a real incident
|
|
19
|
+
* (proposal §1.5): the phase 2a-16 acceptance and the entry to phase 2a-18
|
|
20
|
+
* check them again. So they are one table of data, and the optional `patterns`
|
|
21
|
+
* of `role-fallback.json` (edited by hand only) replaces the defaults of any
|
|
22
|
+
* class it lists.
|
|
23
|
+
*
|
|
24
|
+
* The text is cut to `MAX_MATCH_CHARS` before matching. That bounds a pattern
|
|
25
|
+
* whose cost grows with the square of the text; it does not bound one that
|
|
26
|
+
* backtracks exponentially, such as `(a+)+$`, which already takes seconds on
|
|
27
|
+
* 25 characters and would hang the plugin inside the daemon. So a user
|
|
28
|
+
* pattern with a nested quantifier — a repeated group that itself holds a
|
|
29
|
+
* quantifier — is dropped like a broken one (owner decision Q16 a). Rarer
|
|
30
|
+
* shapes such as `(a|aa)*$` still get through: an accepted risk of a file
|
|
31
|
+
* edited by hand only. The defaults have no nested quantifier.
|
|
32
|
+
*
|
|
33
|
+
* Shared by server and client: no Node, no React.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** A class a pattern can name. L6 is the absence of a match, so no pattern names it. */
|
|
37
|
+
export type PatternClass = "L1" | "L2" | "L3" | "L4" | "L5";
|
|
38
|
+
|
|
39
|
+
/** What a text classifies as. */
|
|
40
|
+
export type TextClass = PatternClass | "L6";
|
|
41
|
+
|
|
42
|
+
/** The classes that lead to a fallback; L3 and L6 do not. */
|
|
43
|
+
export type FallbackClass = "L1" | "L2" | "L4" | "L5";
|
|
44
|
+
|
|
45
|
+
/** The optional `patterns` of `role-fallback.json`: regular expression sources per class. */
|
|
46
|
+
export type UserPatterns = Partial<Record<PatternClass, readonly string[]>>;
|
|
47
|
+
|
|
48
|
+
/** One class and its patterns, compiled case-insensitive, in match order. */
|
|
49
|
+
export type PatternTable = ReadonlyArray<{ readonly class: PatternClass; readonly patterns: readonly RegExp[] }>;
|
|
50
|
+
|
|
51
|
+
/** Characters of a text that are matched; the rest is ignored. */
|
|
52
|
+
export const MAX_MATCH_CHARS = 2000;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The default patterns as regular expression sources, in match order: the
|
|
56
|
+
* first class with a matching pattern wins. L1 comes before L3 because a
|
|
57
|
+
* plan-limit message may also say "rate limit".
|
|
58
|
+
*/
|
|
59
|
+
export const DEFAULT_PATTERNS: ReadonlyArray<{ readonly class: PatternClass; readonly patterns: readonly string[] }> = [
|
|
60
|
+
{ class: "L1", patterns: ["usage limit", "limit reached", "hit your (usage )?limit", "limit (will )?reset", "resets? (at|in) "] },
|
|
61
|
+
{
|
|
62
|
+
class: "L2",
|
|
63
|
+
patterns: ["credit balance", "billing", "subscription (has )?(expired|ended|inactive)", "payment (required|failed)", "quota exceeded"],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
class: "L4",
|
|
67
|
+
patterns: ["not logged in", "please (run )?/?login", "invalid api key", "authentication (failed|error)", "\\b401\\b", "oauth token (has )?expired"],
|
|
68
|
+
},
|
|
69
|
+
{ class: "L5", patterns: ["provider (is )?unavailable", "process exited with code", "command not found", "ENOENT"] },
|
|
70
|
+
{ class: "L3", patterns: ["rate[ _]limit", "overloaded", "\\b429\\b", "\\b529\\b", "too many requests"] },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const FALLBACK_CLASSES: ReadonlySet<string> = new Set(["L1", "L2", "L4", "L5"]);
|
|
74
|
+
|
|
75
|
+
/** True for L1, L2, L4 and L5. */
|
|
76
|
+
export function isFallbackClass(value: unknown): value is FallbackClass {
|
|
77
|
+
return typeof value === "string" && FALLBACK_CLASSES.has(value);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function describeError(error: unknown): string {
|
|
81
|
+
return error instanceof Error ? error.message : String(error);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* True when `source` repeats a group that holds a quantifier: `(a+)+`,
|
|
86
|
+
* `(\w*)*`, `(?:x{2,}){3,}`. A group counts as repeated after `*`, `+` or a
|
|
87
|
+
* `{n,}` / `{n,m}` with m > 1; `?` and `{0,1}` do not repeat. Escapes and
|
|
88
|
+
* character classes are skipped, so `\(a+\)+` and `[(a+)+]` are fine. Only a
|
|
89
|
+
* syntax check: it runs on a source that already compiles.
|
|
90
|
+
*/
|
|
91
|
+
export function hasNestedQuantifier(source: string): boolean {
|
|
92
|
+
/** Per open group: whether anything inside it is quantified. */
|
|
93
|
+
const groups: boolean[] = [];
|
|
94
|
+
/** Whether the group closed just before the current position held a quantifier. */
|
|
95
|
+
let closedHeldQuantifier = false;
|
|
96
|
+
const markQuantified = () => {
|
|
97
|
+
for (let depth = 0; depth < groups.length; depth += 1) groups[depth] = true;
|
|
98
|
+
};
|
|
99
|
+
let index = 0;
|
|
100
|
+
while (index < source.length) {
|
|
101
|
+
const char = source[index]!;
|
|
102
|
+
if (char === "\\") {
|
|
103
|
+
index += 2;
|
|
104
|
+
closedHeldQuantifier = false;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (char === "[") {
|
|
108
|
+
// Skip the class: `]` right after `[` or `[^` is a literal.
|
|
109
|
+
index += 1;
|
|
110
|
+
if (source[index] === "^") index += 1;
|
|
111
|
+
if (source[index] === "]") index += 1;
|
|
112
|
+
while (index < source.length && source[index] !== "]") index += source[index] === "\\" ? 2 : 1;
|
|
113
|
+
index += 1;
|
|
114
|
+
closedHeldQuantifier = false;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (char === "(") {
|
|
118
|
+
groups.push(false);
|
|
119
|
+
index += 1;
|
|
120
|
+
closedHeldQuantifier = false;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (char === ")") {
|
|
124
|
+
closedHeldQuantifier = groups.pop() ?? false;
|
|
125
|
+
index += 1;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
let repeats = false;
|
|
129
|
+
let quantifier = false;
|
|
130
|
+
let length = 1;
|
|
131
|
+
if (char === "*" || char === "+") {
|
|
132
|
+
quantifier = true;
|
|
133
|
+
repeats = true;
|
|
134
|
+
} else if (char === "?") {
|
|
135
|
+
quantifier = true;
|
|
136
|
+
} else if (char === "{") {
|
|
137
|
+
const range = /^\{(\d*)(,(\d*))?\}/.exec(source.slice(index));
|
|
138
|
+
if (range !== null) {
|
|
139
|
+
quantifier = true;
|
|
140
|
+
length = range[0].length;
|
|
141
|
+
const upper = range[2] === undefined ? Number(range[1]) : range[3] === "" ? Infinity : Number(range[3]);
|
|
142
|
+
repeats = upper > 1;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (quantifier) {
|
|
146
|
+
if (repeats && closedHeldQuantifier) return true;
|
|
147
|
+
markQuantified();
|
|
148
|
+
// A lazy suffix (`+?`, `*?`, `{2,}?`) belongs to this quantifier.
|
|
149
|
+
if (source[index + length] === "?") length += 1;
|
|
150
|
+
}
|
|
151
|
+
closedHeldQuantifier = false;
|
|
152
|
+
index += length;
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Compiles the table. A class the user lists with at least one entry uses the
|
|
159
|
+
* user's patterns instead of the defaults; an empty or missing list keeps the
|
|
160
|
+
* defaults. A user pattern that does not compile, is empty (it would match
|
|
161
|
+
* every text) or has a nested quantifier (`hasNestedQuantifier`) is dropped
|
|
162
|
+
* with one `[paseo-bm]` line; the rest of its class still counts. Never throws.
|
|
163
|
+
*/
|
|
164
|
+
export function compilePatterns(
|
|
165
|
+
user?: UserPatterns | null,
|
|
166
|
+
log: (message: string) => void = (message) => console.warn(message),
|
|
167
|
+
): PatternTable {
|
|
168
|
+
return DEFAULT_PATTERNS.map((row) => {
|
|
169
|
+
const own: unknown = user?.[row.class];
|
|
170
|
+
if (!Array.isArray(own) || own.length === 0) {
|
|
171
|
+
return { class: row.class, patterns: row.patterns.map((source) => new RegExp(source, "i")) };
|
|
172
|
+
}
|
|
173
|
+
const patterns: RegExp[] = [];
|
|
174
|
+
for (const source of own as unknown[]) {
|
|
175
|
+
try {
|
|
176
|
+
if (typeof source !== "string" || source === "") throw new Error("a pattern must be a non-empty string");
|
|
177
|
+
const compiled = new RegExp(source, "i");
|
|
178
|
+
if (hasNestedQuantifier(source)) throw new Error("a repeated group holds a quantifier, which can hang the plugin (for example (a+)+)");
|
|
179
|
+
patterns.push(compiled);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
log(`[paseo-bm] role-fallback.json: ${row.class} pattern ${JSON.stringify(source)} is ignored: ${describeError(error)}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { class: row.class, patterns };
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Classifies a text against a compiled table; a non-string or empty text is L6. */
|
|
189
|
+
export function matchClass(text: unknown, table: PatternTable): TextClass {
|
|
190
|
+
if (typeof text !== "string" || text === "") return "L6";
|
|
191
|
+
const cut = text.slice(0, MAX_MATCH_CHARS);
|
|
192
|
+
for (const row of table) {
|
|
193
|
+
if (row.patterns.some((pattern) => pattern.test(cut))) return row.class;
|
|
194
|
+
}
|
|
195
|
+
return "L6";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Compiles and classifies in one call. Pure apart from the log line of a dropped user pattern. */
|
|
199
|
+
export function classifyText(text: unknown, user?: UserPatterns | null, log?: (message: string) => void): TextClass {
|
|
200
|
+
return matchClass(text, compilePatterns(user, log));
|
|
201
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fallback aliases (delta 20260921 §4.4.1, REQ-065 f, REQ-031 a).
|
|
3
|
+
*
|
|
4
|
+
* Entry `n` (1…3) of a role's fallback chain runs on its own provider alias
|
|
5
|
+
* `bm-<role>-fallback-<n>`, which extends the entry's base provider. The alias
|
|
6
|
+
* has no agent profile: the entry's model, thinking and mode live in
|
|
7
|
+
* `role-fallback.json`, because the chain is paseo-bm's own concept (ADR-008).
|
|
8
|
+
*
|
|
9
|
+
* Shared by server and client: no Node, no React.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** A role that can have a fallback chain. */
|
|
13
|
+
export type FallbackRole = "manager" | "worker" | "reviewer";
|
|
14
|
+
|
|
15
|
+
/** Roles whose chain is offered: every role since phase 2a-17 (2a-16 offered the Worker's only). */
|
|
16
|
+
export const FALLBACK_ROLES: readonly FallbackRole[] = ["worker", "reviewer", "manager"];
|
|
17
|
+
|
|
18
|
+
/** Longest chain per role. */
|
|
19
|
+
export const MAX_FALLBACK_ENTRIES = 3;
|
|
20
|
+
|
|
21
|
+
/** A fallback alias id, the role it runs in group 1 and its position in group 2. */
|
|
22
|
+
export const FALLBACK_ALIAS_PATTERN = /^bm-(manager|worker|reviewer)-fallback-([1-3])$/;
|
|
23
|
+
|
|
24
|
+
/** `bm-<role>-fallback-<n>`; throws on a position outside 1…3 (a programming error). */
|
|
25
|
+
export function fallbackAlias(role: FallbackRole, n: number): string {
|
|
26
|
+
if (!Number.isInteger(n) || n < 1 || n > MAX_FALLBACK_ENTRIES) {
|
|
27
|
+
throw new RangeError(`fallback position must be 1…${MAX_FALLBACK_ENTRIES}, got ${n}`);
|
|
28
|
+
}
|
|
29
|
+
return `bm-${role}-fallback-${n}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The role and position of a fallback alias id (no `/<model>`), or null. */
|
|
33
|
+
export function fallbackAliasOf(alias: unknown): { role: FallbackRole; position: number } | null {
|
|
34
|
+
if (typeof alias !== "string") return null;
|
|
35
|
+
const match = FALLBACK_ALIAS_PATTERN.exec(alias);
|
|
36
|
+
return match === null ? null : { role: match[1] as FallbackRole, position: Number(match[2]) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Where an alias id stands in its role's chain: 0 for the role's main alias
|
|
41
|
+
* (`bm-worker`), 1…3 for a fallback alias, null for anything else.
|
|
42
|
+
*/
|
|
43
|
+
export function positionOfAlias(alias: unknown): number | null {
|
|
44
|
+
if (alias === "bm-manager" || alias === "bm-worker" || alias === "bm-reviewer") return 0;
|
|
45
|
+
return fallbackAliasOf(alias)?.position ?? null;
|
|
46
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The flag `/bm-worker-new` puts on the first line of the message it sends.
|
|
3
|
+
*
|
|
4
|
+
* It lives in `shared/` because both halves need it and neither may import the
|
|
5
|
+
* other: the client writes the flag, the server's collector takes it back off.
|
|
6
|
+
*
|
|
7
|
+
* It is a flag on the USER's words, not a notice of the plugin's own, so it is
|
|
8
|
+
* deliberately absent from `isPluginNotice`. Listing it there would make the
|
|
9
|
+
* collector record the whole message as `origin: "agent"` and the Dashboard
|
|
10
|
+
* would lose the request text itself (delta 20260917f §4.1).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const NEW_REQUEST_MARKER = "BM-NEW-REQUEST";
|
|
14
|
+
|
|
15
|
+
/** The user's words, with the flag line removed if it carried one. */
|
|
16
|
+
export function stripNewRequestMarker(text: string): string {
|
|
17
|
+
if (!text.startsWith(NEW_REQUEST_MARKER)) return text;
|
|
18
|
+
const newline = text.indexOf("\n");
|
|
19
|
+
return newline === -1 ? "" : text.slice(newline + 1).replace(/^\s+/, "");
|
|
20
|
+
}
|
package/shared/order.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two tiny helpers every trace and bead view needs: chronological order and
|
|
3
|
+
* first-wins de-duplication. Pure and environment-neutral.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Oldest first by an ISO timestamp; a missing timestamp sorts first. */
|
|
7
|
+
export function byAt<T extends { at: string | null }>(a: T, b: T): number {
|
|
8
|
+
const left = a.at ?? "";
|
|
9
|
+
const right = b.at ?? "";
|
|
10
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Keeps the first item for each key, in the original order. */
|
|
14
|
+
export function uniqueBy<T>(items: readonly T[], key: (item: T) => string): T[] {
|
|
15
|
+
const seen = new Set<string>();
|
|
16
|
+
return items.filter((item) => {
|
|
17
|
+
const value = key(item);
|
|
18
|
+
if (seen.has(value)) return false;
|
|
19
|
+
seen.add(value);
|
|
20
|
+
return true;
|
|
21
|
+
});
|
|
22
|
+
}
|
package/shared/prices.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Model price table bundled with the release (WP-201, REQ-052c).
|
|
5
|
+
*
|
|
6
|
+
* Source of truth: docs/design/paseo-bm-dashboard.md §9.
|
|
7
|
+
*
|
|
8
|
+
* Two rules make this table safe to ship:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Never fetched at run time.** Runtime code reads this constant and
|
|
11
|
+
* nothing else; the Dashboard makes no network call (REQ-052e).
|
|
12
|
+
* 2. **A model that is not listed gets no price.** The cost then reports
|
|
13
|
+
* `costBasis: "unavailable"` and the UI shows tokens only (REQ-052d).
|
|
14
|
+
* Guessing a rate would be worse than showing nothing.
|
|
15
|
+
*
|
|
16
|
+
* The figures are *Anthropic first-party API* rates, checked on the date in
|
|
17
|
+
* `PRICES_UPDATED_AT`. They are not an invoice: a provider-reported cost always
|
|
18
|
+
* wins over this table, and the same model on Amazon Bedrock or Google Vertex
|
|
19
|
+
* is billed at that partner's own rates, which are deliberately absent here.
|
|
20
|
+
*
|
|
21
|
+
* Cache reads are priced separately and are an order of magnitude cheaper than
|
|
22
|
+
* fresh input. Charging `cachedInputTokens` at the input rate is the one
|
|
23
|
+
* mistake that would make every estimate wildly too high, which is why the
|
|
24
|
+
* table carries an explicit `cacheReadUsdPerMTok` per row instead of a ratio
|
|
25
|
+
* applied in code.
|
|
26
|
+
*
|
|
27
|
+
* Models deliberately NOT in this table, so they resolve to "unavailable":
|
|
28
|
+
* - Codex / OpenAI models (for example `gpt-5.6-sol`, which the `bm-reviewer`
|
|
29
|
+
* profile uses on this machine). No sourced figure was available when this
|
|
30
|
+
* table was written, and Design §9 says other providers' rates come from
|
|
31
|
+
* that provider's own published page. Adding a guessed number is forbidden.
|
|
32
|
+
* - Claude Fable / Mythos tiers. Their cache-read rate differs by version, and
|
|
33
|
+
* an unverified rate belongs nowhere near a cost display.
|
|
34
|
+
*
|
|
35
|
+
* This module is `shared/`, so it stays free of Node and React Native imports.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/** The day the rates below were checked against the provider's published prices. */
|
|
39
|
+
export const PRICES_UPDATED_AT = "2026-06-24";
|
|
40
|
+
|
|
41
|
+
/** Rates for one model, in US dollars per one million tokens. */
|
|
42
|
+
export const modelPriceSchema = z.object({
|
|
43
|
+
inputUsdPerMTok: z.number().nonnegative(),
|
|
44
|
+
cacheReadUsdPerMTok: z.number().nonnegative(),
|
|
45
|
+
outputUsdPerMTok: z.number().nonnegative(),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export type ModelPrice = z.infer<typeof modelPriceSchema>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Keyed by the bare model id Paseo reports in an agent's `runtimeInfo`/profile
|
|
52
|
+
* (never `provider/model`): resolving the key is WP-209's job.
|
|
53
|
+
*/
|
|
54
|
+
export const MODEL_PRICES: Readonly<Record<string, ModelPrice>> = {
|
|
55
|
+
"claude-opus-5": { inputUsdPerMTok: 5, cacheReadUsdPerMTok: 0.5, outputUsdPerMTok: 25 },
|
|
56
|
+
"claude-opus-4-8": { inputUsdPerMTok: 5, cacheReadUsdPerMTok: 0.5, outputUsdPerMTok: 25 },
|
|
57
|
+
"claude-opus-4-7": { inputUsdPerMTok: 5, cacheReadUsdPerMTok: 0.5, outputUsdPerMTok: 25 },
|
|
58
|
+
"claude-opus-4-6": { inputUsdPerMTok: 5, cacheReadUsdPerMTok: 0.5, outputUsdPerMTok: 25 },
|
|
59
|
+
"claude-sonnet-5": { inputUsdPerMTok: 2, cacheReadUsdPerMTok: 0.2, outputUsdPerMTok: 10 },
|
|
60
|
+
"claude-sonnet-4-6": { inputUsdPerMTok: 3, cacheReadUsdPerMTok: 0.3, outputUsdPerMTok: 15 },
|
|
61
|
+
"claude-haiku-4-5": { inputUsdPerMTok: 1, cacheReadUsdPerMTok: 0.1, outputUsdPerMTok: 5 },
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** Every id that has a bundled rate. */
|
|
65
|
+
export const PRICED_MODEL_IDS = Object.keys(MODEL_PRICES) as readonly string[];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { defineSettings } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Host-scoped settings of the paseo-bm Dashboard (WP-201, REQ-055d).
|
|
6
|
+
*
|
|
7
|
+
* Source of truth: docs/design/paseo-bm-dashboard.md §3.6.
|
|
8
|
+
*
|
|
9
|
+
* Only one value for now: the size at which the Dashboard *warns* about the
|
|
10
|
+
* trace store. It never deletes on its own — ADR-007 decision 6 forbids that,
|
|
11
|
+
* so this threshold is a notice, not a retention policy.
|
|
12
|
+
*
|
|
13
|
+
* Paseo 0.8 offers exactly one settings scope, `"host"`, so this threshold is
|
|
14
|
+
* one value for the whole machine rather than per workspace (REQ-055e). The
|
|
15
|
+
* screen has to say that out loud; Q-042 records the option of a per-workspace
|
|
16
|
+
* threshold and deliberately leaves it unbuilt.
|
|
17
|
+
*
|
|
18
|
+
* This module is `shared/`, so it stays free of Node and React Native imports.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** 200 MB — the agreed default warning threshold (Q-040). */
|
|
22
|
+
export const DEFAULT_WARN_ABOVE_BYTES = 200 * 1024 * 1024;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Stored shape. `warnAboveBytes` must be a positive integer: zero would warn
|
|
26
|
+
* forever and a negative number is meaningless, so both are rejected at the
|
|
27
|
+
* write and the previously stored value stays in effect (REQ-055f).
|
|
28
|
+
*/
|
|
29
|
+
export const dashboardSettingsSchema = z.object({
|
|
30
|
+
warnAboveBytes: z.number().int().positive().default(DEFAULT_WARN_ABOVE_BYTES),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Keeps a stored threshold across a future schema version bump.
|
|
35
|
+
*
|
|
36
|
+
* Version 1 has nothing to migrate *from*, but the hook has to exist now:
|
|
37
|
+
* without it, bumping `version` later would silently drop the user's threshold
|
|
38
|
+
* back to the default. It ignores `fromVersion` on purpose: there is only one
|
|
39
|
+
* version so far, and the value it carries is validated the same way whatever
|
|
40
|
+
* version wrote it. A stored value that no longer validates is dropped on
|
|
41
|
+
* purpose — falling back to a known-good default beats carrying a broken one.
|
|
42
|
+
*/
|
|
43
|
+
export function migrateDashboardSettings(values: unknown): unknown {
|
|
44
|
+
const stored = (values as { warnAboveBytes?: unknown } | null | undefined)?.warnAboveBytes;
|
|
45
|
+
return dashboardSettingsSchema.safeParse({ warnAboveBytes: stored }).success
|
|
46
|
+
? { warnAboveBytes: stored }
|
|
47
|
+
: {};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The definition `index.server.ts` registers and the settings screen reads with `useSettings`. */
|
|
51
|
+
export const dashboardSettings = defineSettings({
|
|
52
|
+
id: "paseo-bm",
|
|
53
|
+
scope: "host",
|
|
54
|
+
version: 1,
|
|
55
|
+
schema: dashboardSettingsSchema,
|
|
56
|
+
migrate: migrateDashboardSettings,
|
|
57
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which Worker a request belongs to (delta 20260918f F12, owner decision Q7 a).
|
|
3
|
+
*
|
|
4
|
+
* The one live `worker` carrying the request id; nobody when there is none, or
|
|
5
|
+
* more than one. An archived agent never counts: sending to it would bring it
|
|
6
|
+
* back (ADR-005), and a broken Worker the Manager replaced keeps the request's
|
|
7
|
+
* label. Nor does a Worker a fallback Worker replaced (`replaced`, delta
|
|
8
|
+
* 20260921 §4.4.8): the replacement carries the same request id. `chat.waiting` (server) and the chat card (client) both decide with
|
|
9
|
+
* this, so a pill and its card always agree.
|
|
10
|
+
*
|
|
11
|
+
* Pure and environment-neutral.
|
|
12
|
+
*/
|
|
13
|
+
export function soleWorkerOf<P extends { role: string; requestId: string | null; archived: boolean; replaced?: boolean }>(
|
|
14
|
+
peers: readonly P[],
|
|
15
|
+
requestId: string | null,
|
|
16
|
+
): P | null {
|
|
17
|
+
if (requestId === null) return null;
|
|
18
|
+
const live = peers.filter((peer) => peer.role === "worker" && !peer.archived && peer.replaced !== true && peer.requestId === requestId);
|
|
19
|
+
return live.length === 1 ? live[0]! : null;
|
|
20
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// GENERATED FILE — do not edit by hand.
|
|
2
|
+
// Regenerated from the package version by scripts/generate-plugin-version.mjs,
|
|
3
|
+
// which the build runs before packing. Design ref: docs/design/paseo-bm.md §2.4.
|
|
4
|
+
|
|
5
|
+
/** Version of the paseo-bm payload, baked in at build time. */
|
|
6
|
+
export const PLUGIN_VERSION = "0.3.0-alpha.5";
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": ["ES2023"],
|
|
7
|
+
"types": ["react", "node"],
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"allowSyntheticDefaultImports": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["**/*.ts", "**/*.tsx"]
|
|
16
|
+
}
|