plures 1.16.0 → 1.18.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/README.md +2 -0
- package/dist/{chunk-Z75ZTXHZ.js → chunk-RKIJTDS3.js} +38 -1
- package/dist/cli.cjs +182 -1
- package/dist/cli.js +145 -1
- package/dist/index.cjs +38 -1
- package/dist/index.d.cts +140 -2
- package/dist/index.d.ts +140 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,8 @@ if (event.type === "task.completed" && event.data.result?.agreement === 1) {
|
|
|
45
45
|
## API
|
|
46
46
|
|
|
47
47
|
- `plures.check(id, data, overrides?)`: run a check from your app's map, one line before the action it guards: `const check = await plures.check("refunds", { order_total, reason }); if (check.answer !== "Approve") return`. The check saved on Plures supplies the question, options, which fields people see (the rest come back in `kept_private`), who decides, how often, its conditions and what happens on a timeout, so you change them on Plures, not in code. `checked` says whether a person looked; `skipped_because` says why not. Overrides: `question`, `options`, `verifiers`, `team`, `route`, `sample`, `timeout`, `always`.
|
|
48
|
+
- `plures.turns.log({ question, answer, intent, rerouted_to, retrieval, sources, session, channel })`: answer reviews for assistants. Send each turn after it's answered (don't await it in the user's path); Plures picks the ones that went wrong (rerouted, refused, empty, no sources, asked again) plus a few at random, and your team grades them. `plures.turns.insights(app)` and `plures.turns.export(app, "routes" | "retrieval" | "qa" | "missed")` turn the grades into routing examples and eval sets.
|
|
49
|
+
- `plures.drift.list(app)`, `.fix(app, id)`: drift cases, when a rubric or an answer intent's grades fall. `npx plures drift fix <case> --app <app>` has Claude Code read the repo with the graded examples that got worse, find the cause, fix it and add eval cases; on GitHub, Plures opens the pull request and then checks the grades come back.
|
|
48
50
|
- `npx plures check add <id> --app <app>`: Claude Code puts that line before the action on a new branch. On GitHub (the dashboard's **Add it to my code**) Plures then opens a pull request.
|
|
49
51
|
- `plures.tasks.create(params, { idempotencyKey? })`: send a task. Params: `question`, `options` (2 to 8, strings or `{ id, label }`), `context`, `image_url`, `audio_url` or `video_url` with `media_seconds` (a clip up to 180 seconds that people play through before answering), `sensitive`, `instructions`, `verifiers` (1, 3, 5, 7 or 9), `max_verifiers`, `presence_check` (default true), `webhook_url`, `metadata`, `expires_in`, `route` (`"team"` sends it straight to your own people, $0.05 per internal vote live), `category` (matches your routing rules) and `team` (one of your named teams), `team_votes` and `team_unanimous` (internal consensus: how many of your team vote, and whether they must all agree; a split goes to the team's lead).
|
|
50
52
|
- `plures.tasks.retrieve(id)`, `plures.tasks.list({ status?, limit?, starting_after?, app? })`, `plures.tasks.cancel(id)`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
var VERSION = "1.
|
|
2
|
+
var VERSION = "1.18.0";
|
|
3
3
|
var PluresError = class extends Error {
|
|
4
4
|
constructor(message, status, type) {
|
|
5
5
|
super(message);
|
|
@@ -169,6 +169,43 @@ var Plures = class {
|
|
|
169
169
|
return (await r.text()).split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
170
170
|
}
|
|
171
171
|
};
|
|
172
|
+
/**
|
|
173
|
+
* Answer reviews: send each turn your assistant answers, with where it routed and what it
|
|
174
|
+
* retrieved. Plures picks the ones worth a look (rerouted, refused, empty, no sources, asked
|
|
175
|
+
* again, plus a few at random) and your team grades them. Free, and nothing waits on it: call
|
|
176
|
+
* it after the reply and don't await it in the user's path.
|
|
177
|
+
*/
|
|
178
|
+
turns = {
|
|
179
|
+
log: (t) => this.req("POST", "/api/v1/turns", t),
|
|
180
|
+
/** Grade a turn from your own tools. */
|
|
181
|
+
grade: (id, g) => this.req("POST", `/api/v1/turns/${encodeURIComponent(id)}/grade`, g),
|
|
182
|
+
/** What the grades say: per intent, misroutes, retrieval misses and missed asks. */
|
|
183
|
+
insights: (app, opts = {}) => this.req("GET", `/api/v1/apps/${encodeURIComponent(app)}/turns/insights${opts.days ? `?days=${opts.days}` : ""}`),
|
|
184
|
+
/** Graded turns as eval data: routes (routing examples), retrieval ({ query, doc, must_contain }), qa ({ question, fact } or { question, unanswerable }), missed. */
|
|
185
|
+
export: async (app, kind, opts = {}) => {
|
|
186
|
+
const q = new URLSearchParams({ kind, ...opts.since ? { since: opts.since } : {} });
|
|
187
|
+
const r = await fetch(`${this.base}/api/v1/apps/${encodeURIComponent(app)}/turns/export?${q}`, { headers: { authorization: `Bearer ${this.apiKey}`, "user-agent": this.ua } });
|
|
188
|
+
if (!r.ok) {
|
|
189
|
+
const d = await r.json().catch(() => ({}));
|
|
190
|
+
throw new PluresError(d?.error?.message ?? `HTTP ${r.status}`, r.status, d?.error?.type ?? "api_error");
|
|
191
|
+
}
|
|
192
|
+
return (await r.text()).split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Drift: when a rubric's grades or an answer intent's grades fall, Plures opens a case, the repo's
|
|
197
|
+
* runner reads the code with the examples that got worse and opens a fix, and Plures checks the
|
|
198
|
+
* fix brings quality back. npx plures drift fix does the reading in your repo.
|
|
199
|
+
*/
|
|
200
|
+
drift = {
|
|
201
|
+
list: (app) => this.req("GET", `/api/v1/apps/${encodeURIComponent(app)}/drift`),
|
|
202
|
+
/** Runs the fix on GitHub through the repo's Plures workflow. */
|
|
203
|
+
fix: (app, id) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/drift/${encodeURIComponent(id)}/fix`, {}),
|
|
204
|
+
/** What got worse: graded examples, the change that started it, and the steps on the map. */
|
|
205
|
+
packet: (app, id) => this.req("GET", `/api/v1/apps/${encodeURIComponent(app)}/drift/${encodeURIComponent(id)}/packet`),
|
|
206
|
+
/** After reading the code: the diagnosis, and the branch with the fix if there is one. Plures opens the pull request. */
|
|
207
|
+
report: (app, id, p) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/drift/${encodeURIComponent(id)}/report`, p)
|
|
208
|
+
};
|
|
172
209
|
/** Your prepaid credit. A live task that could cost more than available_cents is refused with a 402 insufficient_credits error. */
|
|
173
210
|
balance = () => this.req("GET", "/api/v1/balance");
|
|
174
211
|
/**
|
package/dist/cli.cjs
CHANGED
|
@@ -30,7 +30,7 @@ var import_node_path = require("path");
|
|
|
30
30
|
var import_node_fs = require("fs");
|
|
31
31
|
|
|
32
32
|
// src/index.ts
|
|
33
|
-
var VERSION = "1.
|
|
33
|
+
var VERSION = "1.18.0";
|
|
34
34
|
var PluresError = class extends Error {
|
|
35
35
|
constructor(message, status, type) {
|
|
36
36
|
super(message);
|
|
@@ -200,6 +200,43 @@ var Plures = class {
|
|
|
200
200
|
return (await r.text()).split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
201
201
|
}
|
|
202
202
|
};
|
|
203
|
+
/**
|
|
204
|
+
* Answer reviews: send each turn your assistant answers, with where it routed and what it
|
|
205
|
+
* retrieved. Plures picks the ones worth a look (rerouted, refused, empty, no sources, asked
|
|
206
|
+
* again, plus a few at random) and your team grades them. Free, and nothing waits on it: call
|
|
207
|
+
* it after the reply and don't await it in the user's path.
|
|
208
|
+
*/
|
|
209
|
+
turns = {
|
|
210
|
+
log: (t) => this.req("POST", "/api/v1/turns", t),
|
|
211
|
+
/** Grade a turn from your own tools. */
|
|
212
|
+
grade: (id, g) => this.req("POST", `/api/v1/turns/${encodeURIComponent(id)}/grade`, g),
|
|
213
|
+
/** What the grades say: per intent, misroutes, retrieval misses and missed asks. */
|
|
214
|
+
insights: (app2, opts = {}) => this.req("GET", `/api/v1/apps/${encodeURIComponent(app2)}/turns/insights${opts.days ? `?days=${opts.days}` : ""}`),
|
|
215
|
+
/** Graded turns as eval data: routes (routing examples), retrieval ({ query, doc, must_contain }), qa ({ question, fact } or { question, unanswerable }), missed. */
|
|
216
|
+
export: async (app2, kind, opts = {}) => {
|
|
217
|
+
const q = new URLSearchParams({ kind, ...opts.since ? { since: opts.since } : {} });
|
|
218
|
+
const r = await fetch(`${this.base}/api/v1/apps/${encodeURIComponent(app2)}/turns/export?${q}`, { headers: { authorization: `Bearer ${this.apiKey}`, "user-agent": this.ua } });
|
|
219
|
+
if (!r.ok) {
|
|
220
|
+
const d = await r.json().catch(() => ({}));
|
|
221
|
+
throw new PluresError(d?.error?.message ?? `HTTP ${r.status}`, r.status, d?.error?.type ?? "api_error");
|
|
222
|
+
}
|
|
223
|
+
return (await r.text()).split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
/**
|
|
227
|
+
* Drift: when a rubric's grades or an answer intent's grades fall, Plures opens a case, the repo's
|
|
228
|
+
* runner reads the code with the examples that got worse and opens a fix, and Plures checks the
|
|
229
|
+
* fix brings quality back. npx plures drift fix does the reading in your repo.
|
|
230
|
+
*/
|
|
231
|
+
drift = {
|
|
232
|
+
list: (app2) => this.req("GET", `/api/v1/apps/${encodeURIComponent(app2)}/drift`),
|
|
233
|
+
/** Runs the fix on GitHub through the repo's Plures workflow. */
|
|
234
|
+
fix: (app2, id) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app2)}/drift/${encodeURIComponent(id)}/fix`, {}),
|
|
235
|
+
/** What got worse: graded examples, the change that started it, and the steps on the map. */
|
|
236
|
+
packet: (app2, id) => this.req("GET", `/api/v1/apps/${encodeURIComponent(app2)}/drift/${encodeURIComponent(id)}/packet`),
|
|
237
|
+
/** After reading the code: the diagnosis, and the branch with the fix if there is one. Plures opens the pull request. */
|
|
238
|
+
report: (app2, id, p) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app2)}/drift/${encodeURIComponent(id)}/report`, p)
|
|
239
|
+
};
|
|
203
240
|
/** Your prepaid credit. A live task that could cost more than available_cents is refused with a 402 insufficient_credits error. */
|
|
204
241
|
balance = () => this.req("GET", "/api/v1/balance");
|
|
205
242
|
/**
|
|
@@ -784,9 +821,152 @@ ${cyan("\u2713")} The check is on the branch ${bold(br)}. Review it with ${cyan(
|
|
|
784
821
|
die(e instanceof PluresError ? e.message : String(e));
|
|
785
822
|
}
|
|
786
823
|
}
|
|
824
|
+
async function driftFix(id) {
|
|
825
|
+
if (!id) die("npx plures drift fix <case id> [--app <app>]");
|
|
826
|
+
const cl = client();
|
|
827
|
+
const report2 = async (p) => cl.drift.report(app, id, p);
|
|
828
|
+
const fail = async (msg) => {
|
|
829
|
+
await report2({ error: msg }).catch(() => {
|
|
830
|
+
});
|
|
831
|
+
die(msg);
|
|
832
|
+
};
|
|
833
|
+
let packet;
|
|
834
|
+
try {
|
|
835
|
+
packet = await cl.drift.packet(app, id);
|
|
836
|
+
} catch (e) {
|
|
837
|
+
return fail(e instanceof PluresError ? e.message : String(e));
|
|
838
|
+
}
|
|
839
|
+
if (!hasClaude()) return fail("Claude Code isn't installed here, so nothing could read the code.");
|
|
840
|
+
const git = (...a) => (0, import_node_child_process.spawnSync)("git", a, { encoding: "utf8" });
|
|
841
|
+
if (git("status", "--porcelain").stdout.trim()) return fail("This repo has changes that aren't committed. Commit or stash them first.");
|
|
842
|
+
const childEnv = { ...process.env };
|
|
843
|
+
const ownClaude = Boolean(process.env.ANTHROPIC_API_KEY || process.env.CLAUDE_CODE_OAUTH_TOKEN || process.env.ANTHROPIC_AUTH_TOKEN);
|
|
844
|
+
if (!ownClaude && process.env.CI) {
|
|
845
|
+
try {
|
|
846
|
+
const run = await cl.maps.startRun(app);
|
|
847
|
+
childEnv.ANTHROPIC_BASE_URL = run.base_url;
|
|
848
|
+
childEnv.ANTHROPIC_API_KEY = run.token;
|
|
849
|
+
} catch (e) {
|
|
850
|
+
return fail(e instanceof PluresError ? e.message : String(e));
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
const session = packet.session || `drift_${(0, import_node_crypto.randomBytes)(9).toString("base64url").replace(/[-_]/g, "")}`;
|
|
854
|
+
const source = process.env.GITHUB_ACTIONS ? "github" : "cli";
|
|
855
|
+
let queue = [], phase = "reading", sent = "";
|
|
856
|
+
const flush = async () => {
|
|
857
|
+
const events = queue;
|
|
858
|
+
queue = [];
|
|
859
|
+
if (!events.length && sent === phase) return;
|
|
860
|
+
sent = phase;
|
|
861
|
+
await cl.maps.progress(app, { session, phase, source, events }).catch(() => {
|
|
862
|
+
});
|
|
863
|
+
};
|
|
864
|
+
const tick = setInterval(() => {
|
|
865
|
+
void flush();
|
|
866
|
+
}, 900);
|
|
867
|
+
void flush();
|
|
868
|
+
const DIAG = ".plures-drift.json";
|
|
869
|
+
console.log(`${bold("Plures drift")} ${packet.label}
|
|
870
|
+
${dim("Claude Code reads this repo with the graded examples that got worse. Only file names and the diagnosis go to Plures.")}
|
|
871
|
+
`);
|
|
872
|
+
const prompt = `Quality fell in this codebase's AI assistant, and people graded the examples. Find out why in the code and fix it.
|
|
873
|
+
|
|
874
|
+
What Plures measured (JSON):
|
|
875
|
+
${JSON.stringify({ ...packet, session: void 0 }, null, 2).slice(0, 6e4)}
|
|
876
|
+
|
|
877
|
+
Do this:
|
|
878
|
+
1. Read the code behind the steps listed (prompts, the intent router, retrieval, the answer step). Compare the examples that got worse with the ones that were fine before${packet.version_after ? `, and look for what changed in ${packet.version_after}` : ""}. Use git log or the prompt files if that helps.
|
|
879
|
+
2. Fix the cause in the code with the smallest change that would make the worse examples come out right without breaking the good ones. Prefer prompt, routing and retrieval fixes over rewrites. Don't change unrelated code or formatting.
|
|
880
|
+
3. If the repo has eval files (like evals/*.jsonl), add cases from the worse examples in the same format, so this can't come back unnoticed.
|
|
881
|
+
4. Write ${DIAG} in the repo root, as JSON: { "summary": "the cause in one or two sentences", "confidence": "high" | "medium" | "low", "causes": [{ "title": "...", "detail": "...", "files": [{ "file": "src/...", "line": 12 }] }], "changes": [{ "file": "src/...", "why": "..." }], "evals_added": 0 }. If you change nothing, say why in summary and leave changes empty.
|
|
882
|
+
Don't run the app or tests, and never include secrets or customer data in ${DIAG}.`;
|
|
883
|
+
const rel = (p) => typeof p === "string" && p ? (0, import_node_path.isAbsolute)(p) ? (0, import_node_path.relative)(process.cwd(), p) : p : null;
|
|
884
|
+
const code = await new Promise((resolve) => {
|
|
885
|
+
const p = (0, import_node_child_process.spawn)("claude", ["-p", prompt, "--output-format", "stream-json", "--verbose", "--allowedTools", "Read,Grep,Glob,Edit,Write,Bash(git log:*),Bash(git show:*),Bash(git diff:*)"], { stdio: ["ignore", "pipe", "inherit"], env: childEnv });
|
|
886
|
+
let buf = "";
|
|
887
|
+
p.stdout.on("data", (chunk) => {
|
|
888
|
+
buf += chunk.toString();
|
|
889
|
+
let i;
|
|
890
|
+
while ((i = buf.indexOf("\n")) >= 0) {
|
|
891
|
+
const line = buf.slice(0, i);
|
|
892
|
+
buf = buf.slice(i + 1);
|
|
893
|
+
let ev;
|
|
894
|
+
try {
|
|
895
|
+
ev = JSON.parse(line);
|
|
896
|
+
} catch {
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
for (const b of ev.type === "assistant" ? ev.message?.content ?? [] : []) {
|
|
900
|
+
if (b.type !== "tool_use") continue;
|
|
901
|
+
const f = rel(b.input?.file_path ?? b.input?.path);
|
|
902
|
+
if (!f || f.startsWith("..") || f.endsWith(DIAG)) continue;
|
|
903
|
+
if (b.name === "Read") {
|
|
904
|
+
queue.push({ path: f, tool: "Read" });
|
|
905
|
+
console.log(` ${dim("reading")} ${f}`);
|
|
906
|
+
} else if (b.name === "Edit" || b.name === "Write") {
|
|
907
|
+
phase = "drawing";
|
|
908
|
+
queue.push({ path: f, tool: "Edit" });
|
|
909
|
+
console.log(` ${violet("fixing")} ${f}`);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
p.on("close", (n) => resolve(n ?? 1));
|
|
915
|
+
p.on("error", () => resolve(1));
|
|
916
|
+
});
|
|
917
|
+
clearInterval(tick);
|
|
918
|
+
let diagnosis = null;
|
|
919
|
+
try {
|
|
920
|
+
diagnosis = JSON.parse((0, import_node_fs.readFileSync)(DIAG, "utf8"));
|
|
921
|
+
} catch {
|
|
922
|
+
}
|
|
923
|
+
try {
|
|
924
|
+
(0, import_node_child_process.spawnSync)("rm", ["-f", DIAG]);
|
|
925
|
+
} catch {
|
|
926
|
+
}
|
|
927
|
+
if (code !== 0 || !diagnosis) {
|
|
928
|
+
phase = "failed";
|
|
929
|
+
await flush();
|
|
930
|
+
return fail("Claude Code didn't write a diagnosis. Nothing was changed on Plures.");
|
|
931
|
+
}
|
|
932
|
+
const changed = git("status", "--porcelain").stdout.trim();
|
|
933
|
+
let branch2 = null;
|
|
934
|
+
if (changed) {
|
|
935
|
+
branch2 = `plures/drift-${id.replace(/[^a-z0-9_-]+/gi, "-")}-${Date.now().toString(36)}`;
|
|
936
|
+
for (const a of [["checkout", "-b", branch2], ["add", "-A"], ["commit", "-m", `Fix drift: ${String(packet.label).slice(0, 60)}`]]) {
|
|
937
|
+
const r = git(...a);
|
|
938
|
+
if (r.status !== 0) {
|
|
939
|
+
phase = "failed";
|
|
940
|
+
await flush();
|
|
941
|
+
return fail(`git ${a[0]} failed: ${(r.stderr || r.stdout).trim().slice(0, 200)}`);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
if (process.env.GITHUB_ACTIONS) {
|
|
945
|
+
const push = git("push", "-u", "origin", branch2);
|
|
946
|
+
if (push.status !== 0) {
|
|
947
|
+
phase = "failed";
|
|
948
|
+
await flush();
|
|
949
|
+
return fail(`Pushing the branch failed: ${(push.stderr || push.stdout).trim().slice(0, 200)}`);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
phase = "done";
|
|
954
|
+
await flush();
|
|
955
|
+
try {
|
|
956
|
+
const r = await report2({ diagnosis, branch: process.env.GITHUB_ACTIONS ? branch2 : null });
|
|
957
|
+
const d = diagnosis;
|
|
958
|
+
console.log(`
|
|
959
|
+
${cyan("\u2713")} ${d.summary ?? "Diagnosed."}`);
|
|
960
|
+
if (r.pull_request) console.log(`${cyan("\u2713")} Pull request: ${r.pull_request}`);
|
|
961
|
+
else if (branch2) console.log(`${cyan("\u2713")} The fix is on the branch ${bold(branch2)}. Review it with ${cyan("git show")}, then push it and open a pull request.`);
|
|
962
|
+
} catch (e) {
|
|
963
|
+
die(e instanceof PluresError ? e.message : String(e));
|
|
964
|
+
}
|
|
965
|
+
}
|
|
787
966
|
async function main() {
|
|
788
967
|
const [cmd, sub, arg] = argv;
|
|
789
968
|
if (cmd === "check" && sub === "add") return checkAdd(arg);
|
|
969
|
+
if (cmd === "drift" && sub === "fix") return driftFix(arg);
|
|
790
970
|
if (cmd !== "map") {
|
|
791
971
|
console.log(`Plures
|
|
792
972
|
|
|
@@ -798,6 +978,7 @@ async function main() {
|
|
|
798
978
|
npx plures map github on merge it, which turns mapping on
|
|
799
979
|
npx plures map github run map now on GitHub, and watch it build
|
|
800
980
|
npx plures check add <id> put a check into your code, on a new branch
|
|
981
|
+
npx plures drift fix <case> find why quality fell and fix it, on a new branch
|
|
801
982
|
|
|
802
983
|
Docs: https://plures.si/docs`);
|
|
803
984
|
return;
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
Plures,
|
|
4
4
|
PluresError,
|
|
5
5
|
VERSION
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-RKIJTDS3.js";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { spawnSync, spawn } from "child_process";
|
|
@@ -537,9 +537,152 @@ ${cyan("\u2713")} The check is on the branch ${bold(br)}. Review it with ${cyan(
|
|
|
537
537
|
die(e instanceof PluresError ? e.message : String(e));
|
|
538
538
|
}
|
|
539
539
|
}
|
|
540
|
+
async function driftFix(id) {
|
|
541
|
+
if (!id) die("npx plures drift fix <case id> [--app <app>]");
|
|
542
|
+
const cl = client();
|
|
543
|
+
const report2 = async (p) => cl.drift.report(app, id, p);
|
|
544
|
+
const fail = async (msg) => {
|
|
545
|
+
await report2({ error: msg }).catch(() => {
|
|
546
|
+
});
|
|
547
|
+
die(msg);
|
|
548
|
+
};
|
|
549
|
+
let packet;
|
|
550
|
+
try {
|
|
551
|
+
packet = await cl.drift.packet(app, id);
|
|
552
|
+
} catch (e) {
|
|
553
|
+
return fail(e instanceof PluresError ? e.message : String(e));
|
|
554
|
+
}
|
|
555
|
+
if (!hasClaude()) return fail("Claude Code isn't installed here, so nothing could read the code.");
|
|
556
|
+
const git = (...a) => spawnSync("git", a, { encoding: "utf8" });
|
|
557
|
+
if (git("status", "--porcelain").stdout.trim()) return fail("This repo has changes that aren't committed. Commit or stash them first.");
|
|
558
|
+
const childEnv = { ...process.env };
|
|
559
|
+
const ownClaude = Boolean(process.env.ANTHROPIC_API_KEY || process.env.CLAUDE_CODE_OAUTH_TOKEN || process.env.ANTHROPIC_AUTH_TOKEN);
|
|
560
|
+
if (!ownClaude && process.env.CI) {
|
|
561
|
+
try {
|
|
562
|
+
const run = await cl.maps.startRun(app);
|
|
563
|
+
childEnv.ANTHROPIC_BASE_URL = run.base_url;
|
|
564
|
+
childEnv.ANTHROPIC_API_KEY = run.token;
|
|
565
|
+
} catch (e) {
|
|
566
|
+
return fail(e instanceof PluresError ? e.message : String(e));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const session = packet.session || `drift_${randomBytes(9).toString("base64url").replace(/[-_]/g, "")}`;
|
|
570
|
+
const source = process.env.GITHUB_ACTIONS ? "github" : "cli";
|
|
571
|
+
let queue = [], phase = "reading", sent = "";
|
|
572
|
+
const flush = async () => {
|
|
573
|
+
const events = queue;
|
|
574
|
+
queue = [];
|
|
575
|
+
if (!events.length && sent === phase) return;
|
|
576
|
+
sent = phase;
|
|
577
|
+
await cl.maps.progress(app, { session, phase, source, events }).catch(() => {
|
|
578
|
+
});
|
|
579
|
+
};
|
|
580
|
+
const tick = setInterval(() => {
|
|
581
|
+
void flush();
|
|
582
|
+
}, 900);
|
|
583
|
+
void flush();
|
|
584
|
+
const DIAG = ".plures-drift.json";
|
|
585
|
+
console.log(`${bold("Plures drift")} ${packet.label}
|
|
586
|
+
${dim("Claude Code reads this repo with the graded examples that got worse. Only file names and the diagnosis go to Plures.")}
|
|
587
|
+
`);
|
|
588
|
+
const prompt = `Quality fell in this codebase's AI assistant, and people graded the examples. Find out why in the code and fix it.
|
|
589
|
+
|
|
590
|
+
What Plures measured (JSON):
|
|
591
|
+
${JSON.stringify({ ...packet, session: void 0 }, null, 2).slice(0, 6e4)}
|
|
592
|
+
|
|
593
|
+
Do this:
|
|
594
|
+
1. Read the code behind the steps listed (prompts, the intent router, retrieval, the answer step). Compare the examples that got worse with the ones that were fine before${packet.version_after ? `, and look for what changed in ${packet.version_after}` : ""}. Use git log or the prompt files if that helps.
|
|
595
|
+
2. Fix the cause in the code with the smallest change that would make the worse examples come out right without breaking the good ones. Prefer prompt, routing and retrieval fixes over rewrites. Don't change unrelated code or formatting.
|
|
596
|
+
3. If the repo has eval files (like evals/*.jsonl), add cases from the worse examples in the same format, so this can't come back unnoticed.
|
|
597
|
+
4. Write ${DIAG} in the repo root, as JSON: { "summary": "the cause in one or two sentences", "confidence": "high" | "medium" | "low", "causes": [{ "title": "...", "detail": "...", "files": [{ "file": "src/...", "line": 12 }] }], "changes": [{ "file": "src/...", "why": "..." }], "evals_added": 0 }. If you change nothing, say why in summary and leave changes empty.
|
|
598
|
+
Don't run the app or tests, and never include secrets or customer data in ${DIAG}.`;
|
|
599
|
+
const rel = (p) => typeof p === "string" && p ? isAbsolute(p) ? relative(process.cwd(), p) : p : null;
|
|
600
|
+
const code = await new Promise((resolve) => {
|
|
601
|
+
const p = spawn("claude", ["-p", prompt, "--output-format", "stream-json", "--verbose", "--allowedTools", "Read,Grep,Glob,Edit,Write,Bash(git log:*),Bash(git show:*),Bash(git diff:*)"], { stdio: ["ignore", "pipe", "inherit"], env: childEnv });
|
|
602
|
+
let buf = "";
|
|
603
|
+
p.stdout.on("data", (chunk) => {
|
|
604
|
+
buf += chunk.toString();
|
|
605
|
+
let i;
|
|
606
|
+
while ((i = buf.indexOf("\n")) >= 0) {
|
|
607
|
+
const line = buf.slice(0, i);
|
|
608
|
+
buf = buf.slice(i + 1);
|
|
609
|
+
let ev;
|
|
610
|
+
try {
|
|
611
|
+
ev = JSON.parse(line);
|
|
612
|
+
} catch {
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
for (const b of ev.type === "assistant" ? ev.message?.content ?? [] : []) {
|
|
616
|
+
if (b.type !== "tool_use") continue;
|
|
617
|
+
const f = rel(b.input?.file_path ?? b.input?.path);
|
|
618
|
+
if (!f || f.startsWith("..") || f.endsWith(DIAG)) continue;
|
|
619
|
+
if (b.name === "Read") {
|
|
620
|
+
queue.push({ path: f, tool: "Read" });
|
|
621
|
+
console.log(` ${dim("reading")} ${f}`);
|
|
622
|
+
} else if (b.name === "Edit" || b.name === "Write") {
|
|
623
|
+
phase = "drawing";
|
|
624
|
+
queue.push({ path: f, tool: "Edit" });
|
|
625
|
+
console.log(` ${violet("fixing")} ${f}`);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
p.on("close", (n) => resolve(n ?? 1));
|
|
631
|
+
p.on("error", () => resolve(1));
|
|
632
|
+
});
|
|
633
|
+
clearInterval(tick);
|
|
634
|
+
let diagnosis = null;
|
|
635
|
+
try {
|
|
636
|
+
diagnosis = JSON.parse(readFileSync(DIAG, "utf8"));
|
|
637
|
+
} catch {
|
|
638
|
+
}
|
|
639
|
+
try {
|
|
640
|
+
spawnSync("rm", ["-f", DIAG]);
|
|
641
|
+
} catch {
|
|
642
|
+
}
|
|
643
|
+
if (code !== 0 || !diagnosis) {
|
|
644
|
+
phase = "failed";
|
|
645
|
+
await flush();
|
|
646
|
+
return fail("Claude Code didn't write a diagnosis. Nothing was changed on Plures.");
|
|
647
|
+
}
|
|
648
|
+
const changed = git("status", "--porcelain").stdout.trim();
|
|
649
|
+
let branch2 = null;
|
|
650
|
+
if (changed) {
|
|
651
|
+
branch2 = `plures/drift-${id.replace(/[^a-z0-9_-]+/gi, "-")}-${Date.now().toString(36)}`;
|
|
652
|
+
for (const a of [["checkout", "-b", branch2], ["add", "-A"], ["commit", "-m", `Fix drift: ${String(packet.label).slice(0, 60)}`]]) {
|
|
653
|
+
const r = git(...a);
|
|
654
|
+
if (r.status !== 0) {
|
|
655
|
+
phase = "failed";
|
|
656
|
+
await flush();
|
|
657
|
+
return fail(`git ${a[0]} failed: ${(r.stderr || r.stdout).trim().slice(0, 200)}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
if (process.env.GITHUB_ACTIONS) {
|
|
661
|
+
const push = git("push", "-u", "origin", branch2);
|
|
662
|
+
if (push.status !== 0) {
|
|
663
|
+
phase = "failed";
|
|
664
|
+
await flush();
|
|
665
|
+
return fail(`Pushing the branch failed: ${(push.stderr || push.stdout).trim().slice(0, 200)}`);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
phase = "done";
|
|
670
|
+
await flush();
|
|
671
|
+
try {
|
|
672
|
+
const r = await report2({ diagnosis, branch: process.env.GITHUB_ACTIONS ? branch2 : null });
|
|
673
|
+
const d = diagnosis;
|
|
674
|
+
console.log(`
|
|
675
|
+
${cyan("\u2713")} ${d.summary ?? "Diagnosed."}`);
|
|
676
|
+
if (r.pull_request) console.log(`${cyan("\u2713")} Pull request: ${r.pull_request}`);
|
|
677
|
+
else if (branch2) console.log(`${cyan("\u2713")} The fix is on the branch ${bold(branch2)}. Review it with ${cyan("git show")}, then push it and open a pull request.`);
|
|
678
|
+
} catch (e) {
|
|
679
|
+
die(e instanceof PluresError ? e.message : String(e));
|
|
680
|
+
}
|
|
681
|
+
}
|
|
540
682
|
async function main() {
|
|
541
683
|
const [cmd, sub, arg] = argv;
|
|
542
684
|
if (cmd === "check" && sub === "add") return checkAdd(arg);
|
|
685
|
+
if (cmd === "drift" && sub === "fix") return driftFix(arg);
|
|
543
686
|
if (cmd !== "map") {
|
|
544
687
|
console.log(`Plures
|
|
545
688
|
|
|
@@ -551,6 +694,7 @@ async function main() {
|
|
|
551
694
|
npx plures map github on merge it, which turns mapping on
|
|
552
695
|
npx plures map github run map now on GitHub, and watch it build
|
|
553
696
|
npx plures check add <id> put a check into your code, on a new branch
|
|
697
|
+
npx plures drift fix <case> find why quality fell and fix it, on a new branch
|
|
554
698
|
|
|
555
699
|
Docs: https://plures.si/docs`);
|
|
556
700
|
return;
|
package/dist/index.cjs
CHANGED
|
@@ -26,7 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
default: () => index_default
|
|
27
27
|
});
|
|
28
28
|
module.exports = __toCommonJS(index_exports);
|
|
29
|
-
var VERSION = "1.
|
|
29
|
+
var VERSION = "1.18.0";
|
|
30
30
|
var PluresError = class extends Error {
|
|
31
31
|
constructor(message, status, type) {
|
|
32
32
|
super(message);
|
|
@@ -196,6 +196,43 @@ var Plures = class {
|
|
|
196
196
|
return (await r.text()).split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
197
197
|
}
|
|
198
198
|
};
|
|
199
|
+
/**
|
|
200
|
+
* Answer reviews: send each turn your assistant answers, with where it routed and what it
|
|
201
|
+
* retrieved. Plures picks the ones worth a look (rerouted, refused, empty, no sources, asked
|
|
202
|
+
* again, plus a few at random) and your team grades them. Free, and nothing waits on it: call
|
|
203
|
+
* it after the reply and don't await it in the user's path.
|
|
204
|
+
*/
|
|
205
|
+
turns = {
|
|
206
|
+
log: (t) => this.req("POST", "/api/v1/turns", t),
|
|
207
|
+
/** Grade a turn from your own tools. */
|
|
208
|
+
grade: (id, g) => this.req("POST", `/api/v1/turns/${encodeURIComponent(id)}/grade`, g),
|
|
209
|
+
/** What the grades say: per intent, misroutes, retrieval misses and missed asks. */
|
|
210
|
+
insights: (app, opts = {}) => this.req("GET", `/api/v1/apps/${encodeURIComponent(app)}/turns/insights${opts.days ? `?days=${opts.days}` : ""}`),
|
|
211
|
+
/** Graded turns as eval data: routes (routing examples), retrieval ({ query, doc, must_contain }), qa ({ question, fact } or { question, unanswerable }), missed. */
|
|
212
|
+
export: async (app, kind, opts = {}) => {
|
|
213
|
+
const q = new URLSearchParams({ kind, ...opts.since ? { since: opts.since } : {} });
|
|
214
|
+
const r = await fetch(`${this.base}/api/v1/apps/${encodeURIComponent(app)}/turns/export?${q}`, { headers: { authorization: `Bearer ${this.apiKey}`, "user-agent": this.ua } });
|
|
215
|
+
if (!r.ok) {
|
|
216
|
+
const d = await r.json().catch(() => ({}));
|
|
217
|
+
throw new PluresError(d?.error?.message ?? `HTTP ${r.status}`, r.status, d?.error?.type ?? "api_error");
|
|
218
|
+
}
|
|
219
|
+
return (await r.text()).split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* Drift: when a rubric's grades or an answer intent's grades fall, Plures opens a case, the repo's
|
|
224
|
+
* runner reads the code with the examples that got worse and opens a fix, and Plures checks the
|
|
225
|
+
* fix brings quality back. npx plures drift fix does the reading in your repo.
|
|
226
|
+
*/
|
|
227
|
+
drift = {
|
|
228
|
+
list: (app) => this.req("GET", `/api/v1/apps/${encodeURIComponent(app)}/drift`),
|
|
229
|
+
/** Runs the fix on GitHub through the repo's Plures workflow. */
|
|
230
|
+
fix: (app, id) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/drift/${encodeURIComponent(id)}/fix`, {}),
|
|
231
|
+
/** What got worse: graded examples, the change that started it, and the steps on the map. */
|
|
232
|
+
packet: (app, id) => this.req("GET", `/api/v1/apps/${encodeURIComponent(app)}/drift/${encodeURIComponent(id)}/packet`),
|
|
233
|
+
/** After reading the code: the diagnosis, and the branch with the fix if there is one. Plures opens the pull request. */
|
|
234
|
+
report: (app, id, p) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/drift/${encodeURIComponent(id)}/report`, p)
|
|
235
|
+
};
|
|
199
236
|
/** Your prepaid credit. A live task that could cost more than available_cents is refused with a 402 insufficient_credits error. */
|
|
200
237
|
balance = () => this.req("GET", "/api/v1/balance");
|
|
201
238
|
/**
|
package/dist/index.d.cts
CHANGED
|
@@ -4,6 +4,67 @@ type Option = {
|
|
|
4
4
|
};
|
|
5
5
|
/** "verifiers" asks ID verified people first; "team" goes straight to your own people ($0.05 per internal vote live). */
|
|
6
6
|
type Route = "verifiers" | "team";
|
|
7
|
+
type DriftDiagnosis = {
|
|
8
|
+
summary: string;
|
|
9
|
+
confidence: "high" | "medium" | "low";
|
|
10
|
+
causes: {
|
|
11
|
+
title: string;
|
|
12
|
+
detail: string;
|
|
13
|
+
files: {
|
|
14
|
+
file: string;
|
|
15
|
+
line?: number | null;
|
|
16
|
+
}[];
|
|
17
|
+
}[];
|
|
18
|
+
changes: {
|
|
19
|
+
file: string;
|
|
20
|
+
why: string;
|
|
21
|
+
}[];
|
|
22
|
+
evals_added: number;
|
|
23
|
+
};
|
|
24
|
+
type DriftCase = {
|
|
25
|
+
object: "drift_case";
|
|
26
|
+
id: string;
|
|
27
|
+
app: string;
|
|
28
|
+
mode: "live" | "test";
|
|
29
|
+
source: "rubric" | "answers";
|
|
30
|
+
key: string;
|
|
31
|
+
label: string;
|
|
32
|
+
status: "open" | "diagnosing" | "diagnosed" | "fix_open" | "verifying" | "recovered" | "dismissed" | "failed";
|
|
33
|
+
baseline: number | null;
|
|
34
|
+
at_open: number | null;
|
|
35
|
+
current: number | null;
|
|
36
|
+
points: number;
|
|
37
|
+
version_before: string | null;
|
|
38
|
+
version_after: string | null;
|
|
39
|
+
diagnosis: DriftDiagnosis | null;
|
|
40
|
+
pr_url: string | null;
|
|
41
|
+
merged_at: string | null;
|
|
42
|
+
post_n: number;
|
|
43
|
+
recovered_at: string | null;
|
|
44
|
+
opened_at: string;
|
|
45
|
+
};
|
|
46
|
+
type TurnParams = {
|
|
47
|
+
/** Which version of your assistant answered, like "prompt v15", so a drop can be traced to a change. */ version?: string;
|
|
48
|
+
/** What the user said or typed. */ question: string;
|
|
49
|
+
/** What the assistant replied ("" if it said nothing). */ answer: string;
|
|
50
|
+
/** Where your router sent it. */ intent?: string;
|
|
51
|
+
/** If the model corrected the router. */ rerouted_to?: string | null;
|
|
52
|
+
/** How it retrieved, like "full", "rerank" or "agentic". */ retrieval?: string;
|
|
53
|
+
/** What it used. [] means it answered with none. */ sources?: {
|
|
54
|
+
id?: string;
|
|
55
|
+
title?: string;
|
|
56
|
+
snippet?: string;
|
|
57
|
+
score?: number;
|
|
58
|
+
}[];
|
|
59
|
+
/** It said it didn't know. Plures guesses from the answer if you leave it out. */ refused?: boolean;
|
|
60
|
+
/** A conversation or call id, so Plures can tell when the user asks again. */ session?: string;
|
|
61
|
+
channel?: "voice" | "text";
|
|
62
|
+
/** A recording people can play, for voice. */ audio_url?: string;
|
|
63
|
+
/** Every intent your router can pick, so graders can choose the right one. */ intents?: string[];
|
|
64
|
+
/** Send this one for review whatever the sample says. */ review?: boolean;
|
|
65
|
+
app?: string;
|
|
66
|
+
metadata?: Record<string, unknown>;
|
|
67
|
+
};
|
|
7
68
|
type CheckOverrides = {
|
|
8
69
|
question?: string;
|
|
9
70
|
options?: string[];
|
|
@@ -419,7 +480,7 @@ type WebhookEvent = {
|
|
|
419
480
|
created_at: string;
|
|
420
481
|
data: Task;
|
|
421
482
|
};
|
|
422
|
-
declare const VERSION = "1.
|
|
483
|
+
declare const VERSION = "1.18.0";
|
|
423
484
|
type GitHubLink = {
|
|
424
485
|
object: "github_link";
|
|
425
486
|
app: string;
|
|
@@ -733,6 +794,83 @@ declare class Plures {
|
|
|
733
794
|
metadata: Record<string, unknown>;
|
|
734
795
|
}[]>;
|
|
735
796
|
};
|
|
797
|
+
/**
|
|
798
|
+
* Answer reviews: send each turn your assistant answers, with where it routed and what it
|
|
799
|
+
* retrieved. Plures picks the ones worth a look (rerouted, refused, empty, no sources, asked
|
|
800
|
+
* again, plus a few at random) and your team grades them. Free, and nothing waits on it: call
|
|
801
|
+
* it after the reply and don't await it in the user's path.
|
|
802
|
+
*/
|
|
803
|
+
turns: {
|
|
804
|
+
log: (t: TurnParams) => Promise<{
|
|
805
|
+
object: "turn";
|
|
806
|
+
id: string;
|
|
807
|
+
app: string;
|
|
808
|
+
picked: boolean;
|
|
809
|
+
reason: string | null;
|
|
810
|
+
signals: string[];
|
|
811
|
+
asked_again_of?: string;
|
|
812
|
+
}>;
|
|
813
|
+
/** Grade a turn from your own tools. */
|
|
814
|
+
grade: (id: string, g: {
|
|
815
|
+
intent_ok?: boolean;
|
|
816
|
+
right_intent?: string;
|
|
817
|
+
grounded?: "yes" | "partly" | "no" | "should_refuse" | "should_answer";
|
|
818
|
+
complete?: boolean;
|
|
819
|
+
missed?: string;
|
|
820
|
+
fact?: string;
|
|
821
|
+
doc?: string;
|
|
822
|
+
note?: string;
|
|
823
|
+
email?: string;
|
|
824
|
+
}) => Promise<{
|
|
825
|
+
object: "turn_grade";
|
|
826
|
+
turn: string;
|
|
827
|
+
}>;
|
|
828
|
+
/** What the grades say: per intent, misroutes, retrieval misses and missed asks. */
|
|
829
|
+
insights: (app: string, opts?: {
|
|
830
|
+
days?: number;
|
|
831
|
+
}) => Promise<Record<string, unknown>>;
|
|
832
|
+
/** Graded turns as eval data: routes (routing examples), retrieval ({ query, doc, must_contain }), qa ({ question, fact } or { question, unanswerable }), missed. */
|
|
833
|
+
export: (app: string, kind: "routes" | "retrieval" | "qa" | "missed", opts?: {
|
|
834
|
+
since?: string;
|
|
835
|
+
}) => Promise<Record<string, unknown>[]>;
|
|
836
|
+
};
|
|
837
|
+
/**
|
|
838
|
+
* Drift: when a rubric's grades or an answer intent's grades fall, Plures opens a case, the repo's
|
|
839
|
+
* runner reads the code with the examples that got worse and opens a fix, and Plures checks the
|
|
840
|
+
* fix brings quality back. npx plures drift fix does the reading in your repo.
|
|
841
|
+
*/
|
|
842
|
+
drift: {
|
|
843
|
+
list: (app: string) => Promise<{
|
|
844
|
+
object: "list";
|
|
845
|
+
data: DriftCase[];
|
|
846
|
+
}>;
|
|
847
|
+
/** Runs the fix on GitHub through the repo's Plures workflow. */
|
|
848
|
+
fix: (app: string, id: string) => Promise<{
|
|
849
|
+
object: "drift_fix";
|
|
850
|
+
case: string;
|
|
851
|
+
status: string;
|
|
852
|
+
session: string;
|
|
853
|
+
}>;
|
|
854
|
+
/** What got worse: graded examples, the change that started it, and the steps on the map. */
|
|
855
|
+
packet: (app: string, id: string) => Promise<Record<string, unknown> & {
|
|
856
|
+
case: string;
|
|
857
|
+
label: string;
|
|
858
|
+
session: string | null;
|
|
859
|
+
kind: "rubric" | "answers";
|
|
860
|
+
}>;
|
|
861
|
+
/** After reading the code: the diagnosis, and the branch with the fix if there is one. Plures opens the pull request. */
|
|
862
|
+
report: (app: string, id: string, p: {
|
|
863
|
+
diagnosis: DriftDiagnosis;
|
|
864
|
+
branch?: string | null;
|
|
865
|
+
} | {
|
|
866
|
+
error: string;
|
|
867
|
+
}) => Promise<{
|
|
868
|
+
object: "drift_fix";
|
|
869
|
+
case: string;
|
|
870
|
+
status: string;
|
|
871
|
+
pull_request?: string;
|
|
872
|
+
}>;
|
|
873
|
+
};
|
|
736
874
|
/** Your prepaid credit. A live task that could cost more than available_cents is refused with a 402 insufficient_credits error. */
|
|
737
875
|
balance: () => Promise<Balance>;
|
|
738
876
|
/**
|
|
@@ -760,4 +898,4 @@ declare class Plures {
|
|
|
760
898
|
static verifyWebhook(rawBody: string, signatureHeader: string | null, secret: string, toleranceSec?: number): Promise<WebhookEvent>;
|
|
761
899
|
}
|
|
762
900
|
|
|
763
|
-
export { type AgentMap, type App, type Balance, type CheckOverrides, type CheckResult, type Consensus, type CreateTask, type Escalation, type GitHubLink, type MapChanges, type MapCheckpoint, type MapDraft, type MapKey, type MapNode, type Mix, type Option, Plures, PluresError, type Release, type Result, type Route, type Routing, type RoutingRule, type RoutingUpdate, type Rubric, type RubricParams, type Sample, type StoredAgentMap, type Task, type TaskStatus, type TeamTally, type TeamVote, type TestMix, VERSION, type WebhookEvent, Plures as default };
|
|
901
|
+
export { type AgentMap, type App, type Balance, type CheckOverrides, type CheckResult, type Consensus, type CreateTask, type DriftCase, type DriftDiagnosis, type Escalation, type GitHubLink, type MapChanges, type MapCheckpoint, type MapDraft, type MapKey, type MapNode, type Mix, type Option, Plures, PluresError, type Release, type Result, type Route, type Routing, type RoutingRule, type RoutingUpdate, type Rubric, type RubricParams, type Sample, type StoredAgentMap, type Task, type TaskStatus, type TeamTally, type TeamVote, type TestMix, type TurnParams, VERSION, type WebhookEvent, Plures as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,67 @@ type Option = {
|
|
|
4
4
|
};
|
|
5
5
|
/** "verifiers" asks ID verified people first; "team" goes straight to your own people ($0.05 per internal vote live). */
|
|
6
6
|
type Route = "verifiers" | "team";
|
|
7
|
+
type DriftDiagnosis = {
|
|
8
|
+
summary: string;
|
|
9
|
+
confidence: "high" | "medium" | "low";
|
|
10
|
+
causes: {
|
|
11
|
+
title: string;
|
|
12
|
+
detail: string;
|
|
13
|
+
files: {
|
|
14
|
+
file: string;
|
|
15
|
+
line?: number | null;
|
|
16
|
+
}[];
|
|
17
|
+
}[];
|
|
18
|
+
changes: {
|
|
19
|
+
file: string;
|
|
20
|
+
why: string;
|
|
21
|
+
}[];
|
|
22
|
+
evals_added: number;
|
|
23
|
+
};
|
|
24
|
+
type DriftCase = {
|
|
25
|
+
object: "drift_case";
|
|
26
|
+
id: string;
|
|
27
|
+
app: string;
|
|
28
|
+
mode: "live" | "test";
|
|
29
|
+
source: "rubric" | "answers";
|
|
30
|
+
key: string;
|
|
31
|
+
label: string;
|
|
32
|
+
status: "open" | "diagnosing" | "diagnosed" | "fix_open" | "verifying" | "recovered" | "dismissed" | "failed";
|
|
33
|
+
baseline: number | null;
|
|
34
|
+
at_open: number | null;
|
|
35
|
+
current: number | null;
|
|
36
|
+
points: number;
|
|
37
|
+
version_before: string | null;
|
|
38
|
+
version_after: string | null;
|
|
39
|
+
diagnosis: DriftDiagnosis | null;
|
|
40
|
+
pr_url: string | null;
|
|
41
|
+
merged_at: string | null;
|
|
42
|
+
post_n: number;
|
|
43
|
+
recovered_at: string | null;
|
|
44
|
+
opened_at: string;
|
|
45
|
+
};
|
|
46
|
+
type TurnParams = {
|
|
47
|
+
/** Which version of your assistant answered, like "prompt v15", so a drop can be traced to a change. */ version?: string;
|
|
48
|
+
/** What the user said or typed. */ question: string;
|
|
49
|
+
/** What the assistant replied ("" if it said nothing). */ answer: string;
|
|
50
|
+
/** Where your router sent it. */ intent?: string;
|
|
51
|
+
/** If the model corrected the router. */ rerouted_to?: string | null;
|
|
52
|
+
/** How it retrieved, like "full", "rerank" or "agentic". */ retrieval?: string;
|
|
53
|
+
/** What it used. [] means it answered with none. */ sources?: {
|
|
54
|
+
id?: string;
|
|
55
|
+
title?: string;
|
|
56
|
+
snippet?: string;
|
|
57
|
+
score?: number;
|
|
58
|
+
}[];
|
|
59
|
+
/** It said it didn't know. Plures guesses from the answer if you leave it out. */ refused?: boolean;
|
|
60
|
+
/** A conversation or call id, so Plures can tell when the user asks again. */ session?: string;
|
|
61
|
+
channel?: "voice" | "text";
|
|
62
|
+
/** A recording people can play, for voice. */ audio_url?: string;
|
|
63
|
+
/** Every intent your router can pick, so graders can choose the right one. */ intents?: string[];
|
|
64
|
+
/** Send this one for review whatever the sample says. */ review?: boolean;
|
|
65
|
+
app?: string;
|
|
66
|
+
metadata?: Record<string, unknown>;
|
|
67
|
+
};
|
|
7
68
|
type CheckOverrides = {
|
|
8
69
|
question?: string;
|
|
9
70
|
options?: string[];
|
|
@@ -419,7 +480,7 @@ type WebhookEvent = {
|
|
|
419
480
|
created_at: string;
|
|
420
481
|
data: Task;
|
|
421
482
|
};
|
|
422
|
-
declare const VERSION = "1.
|
|
483
|
+
declare const VERSION = "1.18.0";
|
|
423
484
|
type GitHubLink = {
|
|
424
485
|
object: "github_link";
|
|
425
486
|
app: string;
|
|
@@ -733,6 +794,83 @@ declare class Plures {
|
|
|
733
794
|
metadata: Record<string, unknown>;
|
|
734
795
|
}[]>;
|
|
735
796
|
};
|
|
797
|
+
/**
|
|
798
|
+
* Answer reviews: send each turn your assistant answers, with where it routed and what it
|
|
799
|
+
* retrieved. Plures picks the ones worth a look (rerouted, refused, empty, no sources, asked
|
|
800
|
+
* again, plus a few at random) and your team grades them. Free, and nothing waits on it: call
|
|
801
|
+
* it after the reply and don't await it in the user's path.
|
|
802
|
+
*/
|
|
803
|
+
turns: {
|
|
804
|
+
log: (t: TurnParams) => Promise<{
|
|
805
|
+
object: "turn";
|
|
806
|
+
id: string;
|
|
807
|
+
app: string;
|
|
808
|
+
picked: boolean;
|
|
809
|
+
reason: string | null;
|
|
810
|
+
signals: string[];
|
|
811
|
+
asked_again_of?: string;
|
|
812
|
+
}>;
|
|
813
|
+
/** Grade a turn from your own tools. */
|
|
814
|
+
grade: (id: string, g: {
|
|
815
|
+
intent_ok?: boolean;
|
|
816
|
+
right_intent?: string;
|
|
817
|
+
grounded?: "yes" | "partly" | "no" | "should_refuse" | "should_answer";
|
|
818
|
+
complete?: boolean;
|
|
819
|
+
missed?: string;
|
|
820
|
+
fact?: string;
|
|
821
|
+
doc?: string;
|
|
822
|
+
note?: string;
|
|
823
|
+
email?: string;
|
|
824
|
+
}) => Promise<{
|
|
825
|
+
object: "turn_grade";
|
|
826
|
+
turn: string;
|
|
827
|
+
}>;
|
|
828
|
+
/** What the grades say: per intent, misroutes, retrieval misses and missed asks. */
|
|
829
|
+
insights: (app: string, opts?: {
|
|
830
|
+
days?: number;
|
|
831
|
+
}) => Promise<Record<string, unknown>>;
|
|
832
|
+
/** Graded turns as eval data: routes (routing examples), retrieval ({ query, doc, must_contain }), qa ({ question, fact } or { question, unanswerable }), missed. */
|
|
833
|
+
export: (app: string, kind: "routes" | "retrieval" | "qa" | "missed", opts?: {
|
|
834
|
+
since?: string;
|
|
835
|
+
}) => Promise<Record<string, unknown>[]>;
|
|
836
|
+
};
|
|
837
|
+
/**
|
|
838
|
+
* Drift: when a rubric's grades or an answer intent's grades fall, Plures opens a case, the repo's
|
|
839
|
+
* runner reads the code with the examples that got worse and opens a fix, and Plures checks the
|
|
840
|
+
* fix brings quality back. npx plures drift fix does the reading in your repo.
|
|
841
|
+
*/
|
|
842
|
+
drift: {
|
|
843
|
+
list: (app: string) => Promise<{
|
|
844
|
+
object: "list";
|
|
845
|
+
data: DriftCase[];
|
|
846
|
+
}>;
|
|
847
|
+
/** Runs the fix on GitHub through the repo's Plures workflow. */
|
|
848
|
+
fix: (app: string, id: string) => Promise<{
|
|
849
|
+
object: "drift_fix";
|
|
850
|
+
case: string;
|
|
851
|
+
status: string;
|
|
852
|
+
session: string;
|
|
853
|
+
}>;
|
|
854
|
+
/** What got worse: graded examples, the change that started it, and the steps on the map. */
|
|
855
|
+
packet: (app: string, id: string) => Promise<Record<string, unknown> & {
|
|
856
|
+
case: string;
|
|
857
|
+
label: string;
|
|
858
|
+
session: string | null;
|
|
859
|
+
kind: "rubric" | "answers";
|
|
860
|
+
}>;
|
|
861
|
+
/** After reading the code: the diagnosis, and the branch with the fix if there is one. Plures opens the pull request. */
|
|
862
|
+
report: (app: string, id: string, p: {
|
|
863
|
+
diagnosis: DriftDiagnosis;
|
|
864
|
+
branch?: string | null;
|
|
865
|
+
} | {
|
|
866
|
+
error: string;
|
|
867
|
+
}) => Promise<{
|
|
868
|
+
object: "drift_fix";
|
|
869
|
+
case: string;
|
|
870
|
+
status: string;
|
|
871
|
+
pull_request?: string;
|
|
872
|
+
}>;
|
|
873
|
+
};
|
|
736
874
|
/** Your prepaid credit. A live task that could cost more than available_cents is refused with a 402 insufficient_credits error. */
|
|
737
875
|
balance: () => Promise<Balance>;
|
|
738
876
|
/**
|
|
@@ -760,4 +898,4 @@ declare class Plures {
|
|
|
760
898
|
static verifyWebhook(rawBody: string, signatureHeader: string | null, secret: string, toleranceSec?: number): Promise<WebhookEvent>;
|
|
761
899
|
}
|
|
762
900
|
|
|
763
|
-
export { type AgentMap, type App, type Balance, type CheckOverrides, type CheckResult, type Consensus, type CreateTask, type Escalation, type GitHubLink, type MapChanges, type MapCheckpoint, type MapDraft, type MapKey, type MapNode, type Mix, type Option, Plures, PluresError, type Release, type Result, type Route, type Routing, type RoutingRule, type RoutingUpdate, type Rubric, type RubricParams, type Sample, type StoredAgentMap, type Task, type TaskStatus, type TeamTally, type TeamVote, type TestMix, VERSION, type WebhookEvent, Plures as default };
|
|
901
|
+
export { type AgentMap, type App, type Balance, type CheckOverrides, type CheckResult, type Consensus, type CreateTask, type DriftCase, type DriftDiagnosis, type Escalation, type GitHubLink, type MapChanges, type MapCheckpoint, type MapDraft, type MapKey, type MapNode, type Mix, type Option, Plures, PluresError, type Release, type Result, type Route, type Routing, type RoutingRule, type RoutingUpdate, type Rubric, type RubricParams, type Sample, type StoredAgentMap, type Task, type TaskStatus, type TeamTally, type TeamVote, type TestMix, type TurnParams, VERSION, type WebhookEvent, Plures as default };
|
package/dist/index.js
CHANGED