cortad 0.3.0-rc.8 → 0.3.0-rc.9
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/lib/text.mjs +23 -3
- package/local.mjs +9 -2
- package/package.json +1 -1
package/lib/text.mjs
CHANGED
|
@@ -39,11 +39,28 @@ function planLine(p) {
|
|
|
39
39
|
return parts.length ? `${p.name}: ${parts.join(", ")}.` : `Plan: ${p.name}.`;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// The number with what it stands on: a 100 beside an app that stopped, or from five readings, read
|
|
43
|
+
// as a verdict to two agents. Under the 22-reading floor it is provisional and says so.
|
|
44
|
+
function scoreLine(d) {
|
|
45
|
+
const floor = has(d.decided) && d.decided < 22;
|
|
46
|
+
return `${floor ? "Provisional score" : "Score"} ${num(d.score)} of 100${interval(d.ci)}${floor ? `, from ${plural(d.decided, "decided reading")}: under the 22-reading floor, so not a verdict yet` : ""}.`;
|
|
47
|
+
}
|
|
48
|
+
const haltLine = (h) => `${h.side === "ours" ? "We lost your app" : "Your app stopped answering"} after ${plural(h.after, "turn")}${h.why ? `: ${clip(h.why, 200)}` : ""}. The score covers the turns before that.`;
|
|
49
|
+
const LAYER_WORDS = {
|
|
50
|
+
retrieval: "what the store returned behind the reply, not how the reply was written",
|
|
51
|
+
tools: "the tool call behind the reply",
|
|
52
|
+
memory: "what the app kept between turns",
|
|
53
|
+
application: "an error in the app around the model",
|
|
54
|
+
instructions: "the model against its own instructions",
|
|
55
|
+
upsell: "what the reply offered beyond the ask",
|
|
56
|
+
};
|
|
57
|
+
|
|
42
58
|
function runLines(d, prefix = "") {
|
|
43
59
|
const done = finished(d);
|
|
44
60
|
const played = d.of ? `${num(d.played ?? 0)} of ${plural(d.of, "trial")} played` : "no trial played yet";
|
|
45
61
|
const lines = [`${upper(`${prefix}${d.kind === "verify" ? "verify" : "run"}`)} ${d.jobId}: ${d.status === "succeeded" || !d.status ? (done ? "finished" : "running") : d.status}, ${played}.`];
|
|
46
|
-
if (done && typeof d.score === "number") lines.push(
|
|
62
|
+
if (done && typeof d.score === "number") lines.push(scoreLine(d));
|
|
63
|
+
if (done && d.halted) lines.push(haltLine(d.halted));
|
|
47
64
|
if (done && has(d.findings)) lines.push(`${plural(d.findings, "finding")}.`);
|
|
48
65
|
const reads = readingsLine(d);
|
|
49
66
|
if (reads) lines.push(reads);
|
|
@@ -58,7 +75,7 @@ function runLines(d, prefix = "") {
|
|
|
58
75
|
function readingsLine(d) {
|
|
59
76
|
if (!has(d.readings) && !has(d.questionsAsked)) return null;
|
|
60
77
|
const r = has(d.readings) ? plural(d.readings, "reading") : null;
|
|
61
|
-
const q = has(d.questionsAsked) ? plural(d.questionsAsked, "question") : null;
|
|
78
|
+
const q = has(d.questionsAsked) ? `${plural(d.questionsAsked, "question")}${has(d.questionsOf) && d.questionsOf > d.questionsAsked ? ` (${num(d.questionsOf - d.questionsAsked)} of the ${num(d.questionsOf)} in the set never came up)` : ""}` : null;
|
|
62
79
|
const split = [has(d.decided) && `${num(d.decided)} decided`, has(d.unclear) && `${num(d.unclear)} unclear`].filter(Boolean).join(", ");
|
|
63
80
|
return `${r && q ? `${r} of ${q}` : r ?? `${q} asked`}${split ? `: ${split}` : ""}.`;
|
|
64
81
|
}
|
|
@@ -154,7 +171,8 @@ export const findingsText = (d, page = 1) => pageOf(findingsPages(d), page, (n)
|
|
|
154
171
|
|
|
155
172
|
function findingsHead(d) {
|
|
156
173
|
const lines = [`Run ${d.runId}: ${plural(d.findings?.length ?? 0, "finding")}.`];
|
|
157
|
-
if (typeof d.score === "number") lines.push(
|
|
174
|
+
if (typeof d.score === "number") lines.push(scoreLine(d));
|
|
175
|
+
if (d.halted) lines.push(haltLine(d.halted));
|
|
158
176
|
const reads = readingsLine(d);
|
|
159
177
|
if (reads) lines.push(reads);
|
|
160
178
|
if (d.baseRunId) lines.push(`Compared with run ${d.baseRunId}.`);
|
|
@@ -201,7 +219,9 @@ function findingBlock(f, placed) {
|
|
|
201
219
|
if (!placed && f.file) lines.push(` At ${at(f.file, f.line)}`);
|
|
202
220
|
if (f.criteria) lines.push(` Criteria: ${clip(f.criteria, 600)}`);
|
|
203
221
|
if (f.door) lines.push(` Endpoint: ${f.door}`);
|
|
222
|
+
else if (f.doors?.length) lines.push(` Endpoints: ${f.doors.join(", ")}`);
|
|
204
223
|
if (f.where) lines.push(` Situation: ${f.where}`);
|
|
224
|
+
if (f.layer && LAYER_WORDS[f.layer]) lines.push(` Layer: ${f.layer}, ${LAYER_WORDS[f.layer]}.`);
|
|
205
225
|
const r = f.rate;
|
|
206
226
|
// The break count, whichever way the question is phrased: "held in 21 of 33" under a question
|
|
207
227
|
// that asks whether the passages MISS the point left an agent unable to tell pass from fail.
|
package/local.mjs
CHANGED
|
@@ -687,14 +687,17 @@ async function installOnce(said) {
|
|
|
687
687
|
|
|
688
688
|
// The port their start command names, moved when something else already holds it: two apps on one
|
|
689
689
|
// laptop both said --port 8000, and the second attached to the first's server as its own.
|
|
690
|
+
// Ports this program tried and lost: two connects on one machine can pick the same free port in
|
|
691
|
+
// the same second, and the one that binds second sees "address already in use".
|
|
692
|
+
const lost = new Set();
|
|
690
693
|
async function freePortAbove(port) {
|
|
691
|
-
for (let next = port + 1; next < port + 30; next++) if (!(await listenerOn(next))) return next;
|
|
694
|
+
for (let next = port + 1; next < port + 30; next++) if (!lost.has(next) && !(await listenerOn(next))) return next;
|
|
692
695
|
return null;
|
|
693
696
|
}
|
|
694
697
|
async function freed(cmd, lifted) {
|
|
695
698
|
const m = /(--port[= ]|-p |\bPORT=)(\d{4,5})\b/.exec(cmd);
|
|
696
699
|
const named = m ? Number(m[2]) : Number(lifted.PORT) || 0;
|
|
697
|
-
if (!named || !(await listenerOn(named))) return { cmd, lifted };
|
|
700
|
+
if (!named || (!lost.has(named) && !(await listenerOn(named)))) return { cmd, lifted };
|
|
698
701
|
const port = await freePortAbove(named);
|
|
699
702
|
if (!port) return { cmd, lifted };
|
|
700
703
|
say(`port ${named} is taken on this machine, so your app starts on ${port}`);
|
|
@@ -723,6 +726,10 @@ async function start(waitMs) {
|
|
|
723
726
|
// that holds the port is theirs and already running, which startApp turns into attaching to it.
|
|
724
727
|
if (/app crashed - waiting for file changes|waiting for (?:file )?changes before restart|Failed running|EADDRINUSE|address already in use/i.test(seen)) {
|
|
725
728
|
await stopApp(mine.pid);
|
|
729
|
+
// The port this start named was taken between the check and the bind: the next free one, at
|
|
730
|
+
// most three times, before the failure is theirs to read.
|
|
731
|
+
const named = /(?:--port[= ]|-p |\bPORT=)(\d{4,5})\b/.exec(cmd)?.[1] ?? lifted.PORT;
|
|
732
|
+
if (/EADDRINUSE|address already in use/i.test(seen) && named && lost.size < 3) { lost.add(Number(named)); return start(waitMs); }
|
|
726
733
|
return { port: null, exited: 1, tail: tail() };
|
|
727
734
|
}
|
|
728
735
|
// The port is what the app's own process group listens on. Never a guess: a developer's
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cortad",
|
|
3
|
-
"version": "0.3.0-rc.
|
|
3
|
+
"version": "0.3.0-rc.9",
|
|
4
4
|
"description": "Connects the AI app on your machine to Cortad for test conversations, and gives your coding agent the MCP and skill to run them. No dependencies.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cortad": "local.mjs"
|