@turri-dev/cli 0.1.8-next.01c7342 → 0.1.8-next.b03162b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2 -1
- package/dist/config-edit.d.ts +1 -1
- package/dist/config-edit.js +2 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +5 -1
- package/dist/extension.js +69 -8
- package/dist/host.d.ts +2 -0
- package/dist/host.js +3 -0
- package/dist/init-config.d.ts +1 -0
- package/dist/init-config.js +5 -0
- package/dist/ledger-cli.d.ts +1 -0
- package/dist/ledger-cli.js +50 -0
- package/dist/loop-cli.js +4 -0
- package/dist/pi.js +17 -0
- package/dist/regenerate/activity.d.ts +9 -0
- package/dist/regenerate/activity.js +11 -0
- package/dist/regenerate/agent-shell.js +5 -0
- package/dist/regenerate/chunk-plan.d.ts +0 -6
- package/dist/regenerate/chunk-plan.js +16 -4
- package/dist/regenerate/conformance.js +3 -0
- package/dist/regenerate/diagnose.js +4 -2
- package/dist/regenerate/job-log.d.ts +3 -1
- package/dist/regenerate/job-log.js +8 -5
- package/dist/regenerate/jobs-tree.d.ts +7 -3
- package/dist/regenerate/jobs-tree.js +21 -6
- package/dist/regenerate/jobs.d.ts +9 -0
- package/dist/regenerate/jobs.js +38 -3
- package/dist/regenerate/ledger-view.d.ts +73 -0
- package/dist/regenerate/ledger-view.js +286 -0
- package/dist/regenerate/ledger.d.ts +274 -0
- package/dist/regenerate/ledger.js +349 -0
- package/dist/regenerate/lineage.js +18 -1
- package/dist/regenerate/loop.js +10 -1
- package/dist/regenerate/regenerate.js +4 -2
- package/dist/regenerate/regression.js +2 -0
- package/dist/regenerate/side-panel.d.ts +2 -0
- package/dist/regenerate/side-panel.js +4 -3
- package/dist/regenerate/test-scripts.js +12 -1
- package/dist/regenerate/unit-review.js +3 -0
- package/docs/config.md +3 -1
- package/docs/development.md +1 -0
- package/docs/hosting.md +28 -0
- package/docs/installation.md +1 -0
- package/docs/regenerating.md +1 -1
- package/docs/using.md +27 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -81,6 +81,7 @@ const COMMANDS = {
|
|
|
81
81
|
"spec-diff": { module: "./spec-diff-cli.ts", blurb: "apply a change to the specs, given as a git diff, and regenerate it" },
|
|
82
82
|
validate: { module: "./validate-cli.ts", blurb: "validate a ***plain spec, regenerating nothing" },
|
|
83
83
|
lineage: { module: "./lineage-cli.ts", blurb: "a module's lineage: what was regenerated into it, and when" },
|
|
84
|
+
ledger: { module: "./ledger-cli.ts", blurb: "what turri did here: the last runs and their pulses, or one pulse, from the ledger" },
|
|
84
85
|
release: { module: "./release-cli.ts", blurb: "release what is regenerated to dist/<module> and activate it on this machine" },
|
|
85
86
|
cultivate: { module: "./cultivate-cli.ts", blurb: "run a gated skill" },
|
|
86
87
|
};
|
|
@@ -173,7 +174,7 @@ process.argv = [
|
|
|
173
174
|
// A headless run is stopped by a signal - SIGTERM, SIGHUP, Ctrl-C - whatever else listens for it: it exits, and what
|
|
174
175
|
// its agents left running goes with it (shell.ts kills every process group on exit). Left to the libraries' own
|
|
175
176
|
// handlers, one ran on for minutes after it was told to stop, building in the same codebase as the run after it.
|
|
176
|
-
if (!["validate", "check", "drift", "lineage"].includes(command)) {
|
|
177
|
+
if (!["validate", "check", "drift", "lineage", "ledger"].includes(command)) {
|
|
177
178
|
for (const [signal, code] of [["SIGTERM", 143], ["SIGHUP", 129], ["SIGINT", 130]]) {
|
|
178
179
|
process.on(signal, () => {
|
|
179
180
|
console.error(`\nstopped (${signal})`);
|
package/dist/config-edit.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* kept; `models:` and `agent-env:`, which nest, are written again whole, their own comments lost.
|
|
6
6
|
*/
|
|
7
7
|
/** When a change takes effect. Nothing turri reads needs a restart: what is fixed at its start is changed live. */
|
|
8
|
-
export type Effect = "now" | "next-message" | "next-pulse" | "next-plan" | "next-test-run" | "next-landing" | "next-regenerate";
|
|
8
|
+
export type Effect = "now" | "next-message" | "next-pulse" | "next-plan" | "next-test-run" | "next-landing" | "next-regenerate" | "next-start";
|
|
9
9
|
export type SettingKind = "boolean" | "choice" | "number" | "model" | "list" | "path" | "budget";
|
|
10
10
|
export interface Setting {
|
|
11
11
|
/** As written in the file - a nested one by its path: `models.coding`, `models.coding.turn-limit`, `agent-env.files`. */
|
package/dist/config-edit.js
CHANGED
|
@@ -11,6 +11,7 @@ export const EFFECT_TEXT = {
|
|
|
11
11
|
"next-test-run": "in effect from the next test run - the running pulse's too",
|
|
12
12
|
"next-landing": "in effect from the next pulse that lands - the running one too",
|
|
13
13
|
"next-regenerate": "in effect for the next regeneration that names no .plain file",
|
|
14
|
+
"next-start": "in effect the next time turri starts here",
|
|
14
15
|
};
|
|
15
16
|
const SECONDS = ["0", "60", "120", "180", "300"];
|
|
16
17
|
/** A role's line: which agents it is, for the list. */
|
|
@@ -75,6 +76,7 @@ export function settings(rules = []) {
|
|
|
75
76
|
{ key: "agent-env.files", about: "dotenv files whose variables the agents' commands get", kind: "list", unset: "none", effect: "next-pulse", projectOnly: true },
|
|
76
77
|
{ key: "agent-env.vars", about: "variables from turri's environment the agents' commands get, by name", kind: "list", unset: "none", effect: "next-pulse", projectOnly: true },
|
|
77
78
|
{ key: "toolchains", about: "folders of this machine an agent's commands may read beside the usual ones", kind: "list", unset: "none", effect: "next-pulse", projectOnly: true },
|
|
79
|
+
{ key: "ledger-keep", about: "megabytes of the ledger - what turri did, as facts - the project keeps; 0 keeps none", kind: "number", options: ["0", "50", "100", "500"], min: 0, unset: "100", effect: "next-start", projectOnly: true },
|
|
78
80
|
...roles,
|
|
79
81
|
];
|
|
80
82
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -260,6 +260,11 @@ export interface ProjectConfig {
|
|
|
260
260
|
* toolchain installed somewhere unusual. A module's own are added to the project's.
|
|
261
261
|
*/
|
|
262
262
|
toolchains?: string[];
|
|
263
|
+
/**
|
|
264
|
+
* Megabytes of the ledger - what turri did, as facts, in .turri/ledger - the project keeps: past it, the oldest runs'
|
|
265
|
+
* are pruned when a run first writes. 0 keeps none, and writes none. The project's alone. Unset: 100.
|
|
266
|
+
*/
|
|
267
|
+
ledgerKeep?: number;
|
|
263
268
|
/** Each agent role's own model, thinking and turn limit; `default` for every role not named. */
|
|
264
269
|
models?: Partial<Record<AgentRole | "default", RoleChoice>>;
|
|
265
270
|
/** Seconds each turn of an agent a render starts has, for every role without its own. Unset: 120. 0: none. */
|
|
@@ -288,6 +293,7 @@ export declare const CONFIG_KEYS: {
|
|
|
288
293
|
readonly maxLinesPerPulse: "max-lines-per-pulse";
|
|
289
294
|
readonly agentEnv: "agent-env";
|
|
290
295
|
readonly toolchains: "toolchains";
|
|
296
|
+
readonly ledgerKeep: "ledger-keep";
|
|
291
297
|
readonly models: "models";
|
|
292
298
|
readonly turnLimit: "turn-limit";
|
|
293
299
|
};
|
package/dist/config.js
CHANGED
|
@@ -379,6 +379,7 @@ export const CONFIG_KEYS = {
|
|
|
379
379
|
maxLinesPerPulse: "max-lines-per-pulse",
|
|
380
380
|
agentEnv: "agent-env",
|
|
381
381
|
toolchains: "toolchains",
|
|
382
|
+
ledgerKeep: "ledger-keep",
|
|
382
383
|
models: "models",
|
|
383
384
|
turnLimit: "turn-limit",
|
|
384
385
|
};
|
|
@@ -431,7 +432,7 @@ function readStackedConfig(root, stack) {
|
|
|
431
432
|
// The project's alone: one conversation, one module built when none is named.
|
|
432
433
|
const merged = { ...project };
|
|
433
434
|
for (const key of Object.keys(CONFIG_KEYS)) {
|
|
434
|
-
if (key === "module" || key === "harnessModel" || key === "parallelJobs" || key === "rulesOff" || key === "compactAt" || key === "toolchains" || key === "agentEnv" || own[key] === undefined)
|
|
435
|
+
if (key === "module" || key === "harnessModel" || key === "parallelJobs" || key === "rulesOff" || key === "compactAt" || key === "ledgerKeep" || key === "toolchains" || key === "agentEnv" || own[key] === undefined)
|
|
435
436
|
continue;
|
|
436
437
|
merged[key] = own[key];
|
|
437
438
|
}
|
|
@@ -542,6 +543,9 @@ function readConfigFile(path) {
|
|
|
542
543
|
: undefined,
|
|
543
544
|
maxSpecsPerPulse: positiveInteger(raw[CONFIG_KEYS.maxSpecsPerPulse]),
|
|
544
545
|
maxLinesPerPulse: positiveInteger(raw[CONFIG_KEYS.maxLinesPerPulse]),
|
|
546
|
+
ledgerKeep: typeof raw[CONFIG_KEYS.ledgerKeep] === "number" && Number.isInteger(raw[CONFIG_KEYS.ledgerKeep]) && raw[CONFIG_KEYS.ledgerKeep] >= 0
|
|
547
|
+
? raw[CONFIG_KEYS.ledgerKeep]
|
|
548
|
+
: undefined,
|
|
545
549
|
models: modelsSetting(raw[CONFIG_KEYS.models]),
|
|
546
550
|
turnLimit: turnLimitSetting(raw[CONFIG_KEYS.turnLimit]),
|
|
547
551
|
};
|
package/dist/extension.js
CHANGED
|
@@ -44,6 +44,8 @@ import { describeDiagnosis, diagnoseFailure } from "./regenerate/diagnose.js";
|
|
|
44
44
|
import { HISTORY_DIR, historyLog, historyShow } from "./regenerate/lineage.js";
|
|
45
45
|
import { defaultBuildDir } from "./regenerate/paths.js";
|
|
46
46
|
import { describeTurnIncident, describeUsage, onModelEvent } from "./regenerate/activity.js";
|
|
47
|
+
import { ledgerWanted, startLedger } from "./regenerate/ledger.js";
|
|
48
|
+
import { describePastPulse, describeRuns, earlierPulses, ledgerProjects } from "./regenerate/ledger-view.js";
|
|
47
49
|
import { describeSpecConflict } from "./regenerate/stop-report.js";
|
|
48
50
|
import { inheritProviders, inheritThinkingLevel } from "./pi.js";
|
|
49
51
|
import { beginCapture, captureConsole, describeCaptured, dropNoise } from "./quiet.js";
|
|
@@ -379,6 +381,24 @@ function turriExtension(pi, host) {
|
|
|
379
381
|
ctx.ui.notify(event.job !== undefined ? `${event.job}: ${line}` : line, "warning");
|
|
380
382
|
});
|
|
381
383
|
});
|
|
384
|
+
// What this session does, as facts, in the project's .turri/ledger - a pulse's own in its project. Said once, in the
|
|
385
|
+
// chat, when it cannot be written.
|
|
386
|
+
const ledgerTrouble = [];
|
|
387
|
+
let tellLedgerTrouble;
|
|
388
|
+
if (ledgerWanted()) {
|
|
389
|
+
startLedger({
|
|
390
|
+
mode: Object.keys(host).length > 0 ? "host" : "chat",
|
|
391
|
+
project: cwd,
|
|
392
|
+
warn: (message) => (tellLedgerTrouble !== undefined ? tellLedgerTrouble(message) : ledgerTrouble.push(message)),
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
pi.on("session_start", (_event, ctx) => {
|
|
396
|
+
if (!ctx.hasUI)
|
|
397
|
+
return;
|
|
398
|
+
tellLedgerTrouble = (message) => ctx.ui.notify(message, "warning");
|
|
399
|
+
for (const message of ledgerTrouble.splice(0))
|
|
400
|
+
tellLedgerTrouble(message);
|
|
401
|
+
});
|
|
382
402
|
// What a turri that was killed left running - its commands' process groups - stopped as this one starts.
|
|
383
403
|
const orphans = sweepOrphanedGroups();
|
|
384
404
|
let orphansSaid = orphans === 0;
|
|
@@ -1448,20 +1468,27 @@ async function deliver(jobs, input) {
|
|
|
1448
1468
|
const panelOpeners = new WeakMap();
|
|
1449
1469
|
/** By registry: the last pulse that failed, stopped or did not pass, not yet looked at - ctrl+h opens on it. */
|
|
1450
1470
|
const needsLooking = new WeakMap();
|
|
1471
|
+
/** What heads a pulse told from the ledger: it is not this session's, and the facts are all there is of it. */
|
|
1472
|
+
const pastPulseNote = "A pulse of an earlier session, as the ledger recorded it - facts, not its transcript. Its whole log is the file it names, " +
|
|
1473
|
+
"for the read tool.";
|
|
1451
1474
|
function registerJobTools(pi, jobs, cwd) {
|
|
1452
1475
|
pi.registerTool({
|
|
1453
1476
|
name: "diagnose",
|
|
1454
1477
|
label: "Diagnose a pulse",
|
|
1455
1478
|
description: "Why a pulse did not land, from its whole transcript: how it ended and its diagnosis, the stages that went wrong, the " +
|
|
1456
1479
|
"last failing test run with its output, the last review that sent the work back, and the tool calls that failed - " +
|
|
1457
|
-
"and where the whole log is.",
|
|
1480
|
+
"and where the whole log is. A pulse of an earlier session is told from the ledger - what it recorded of it, as facts.",
|
|
1458
1481
|
promptSnippet: "diagnose: why a pulse did not land, from its transcript",
|
|
1459
1482
|
promptGuidelines: ["When a pulse failed or did not land, call diagnose before you explain it or change the spec: go by what happened, not by the report alone."],
|
|
1460
1483
|
parameters: Type.Object({ job_id: Type.String({ description: "The pulse's id" }) }),
|
|
1461
1484
|
async execute(_id, params) {
|
|
1462
1485
|
const job = jobs.get(params.job_id);
|
|
1463
|
-
if (job === undefined)
|
|
1464
|
-
|
|
1486
|
+
if (job === undefined) {
|
|
1487
|
+
const past = describePastPulse(ledgerProjects(cwd), params.job_id);
|
|
1488
|
+
if (past !== undefined)
|
|
1489
|
+
return { content: [{ type: "text", text: `${pastPulseNote}\n\n${past}` }], details: { status: "earlier" } };
|
|
1490
|
+
return { content: [{ type: "text", text: `No pulse ${params.job_id} has run in this session, and the ledger has none by that id.` }], details: { status: "unknown" } };
|
|
1491
|
+
}
|
|
1465
1492
|
return { content: [{ type: "text", text: whyFailed(job) }], details: { status: job.status } };
|
|
1466
1493
|
},
|
|
1467
1494
|
});
|
|
@@ -1469,7 +1496,8 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1469
1496
|
name: "pulses",
|
|
1470
1497
|
label: "Pulses",
|
|
1471
1498
|
description: "What is regenerating: with no pulse, every pulse of this session - running, planned and finished - and what deploy started; " +
|
|
1472
|
-
"with a pulse's id, that pulse - whether it is still running, its latest output, and its report once it has finished."
|
|
1499
|
+
"with a pulse's id, that pulse - whether it is still running, its latest output, and its report once it has finished. " +
|
|
1500
|
+
"A pulse of an earlier session is told from the ledger.",
|
|
1473
1501
|
promptSnippet: "pulses: every pulse, or one - what is regenerating and how it is going",
|
|
1474
1502
|
parameters: Type.Object({
|
|
1475
1503
|
job_id: Type.Optional(Type.String({ description: "The id returned when the pulse started. Omitted: every pulse" })),
|
|
@@ -1477,7 +1505,7 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1477
1505
|
}),
|
|
1478
1506
|
async execute(_id, params) {
|
|
1479
1507
|
if (params.job_id === undefined) {
|
|
1480
|
-
return { content: [{ type: "text", text: listJobs(jobs) }], details: { status: "all" } };
|
|
1508
|
+
return { content: [{ type: "text", text: listJobs(jobs, cwd) }], details: { status: "all" } };
|
|
1481
1509
|
}
|
|
1482
1510
|
const existing = jobs.get(params.job_id);
|
|
1483
1511
|
const planned = shownJobs(jobs).find((job) => job.id === params.job_id && jobs.get(job.id) === undefined);
|
|
@@ -1488,6 +1516,9 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1488
1516
|
};
|
|
1489
1517
|
}
|
|
1490
1518
|
if (existing === undefined) {
|
|
1519
|
+
const past = describePastPulse(ledgerProjects(cwd), params.job_id);
|
|
1520
|
+
if (past !== undefined)
|
|
1521
|
+
return { content: [{ type: "text", text: `${pastPulseNote}\n\n${past}` }], details: { status: "earlier" } };
|
|
1491
1522
|
const known = shownJobs(jobs).map((job) => job.id);
|
|
1492
1523
|
return {
|
|
1493
1524
|
content: [
|
|
@@ -1601,6 +1632,21 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1601
1632
|
const panelBeside = () => (panelDocked && panelTerminal !== undefined ? dockedWidth(paddedColumns(panelTerminal.columns)) : 0);
|
|
1602
1633
|
// What is open in the pulse list and the map, and where their cursors are: the conversation's, as left each time it opens.
|
|
1603
1634
|
const jobsTree = createJobsTreeState();
|
|
1635
|
+
// Earlier sessions' pulses, from the ledger: read as the panel first shows them - they do not change while this one runs.
|
|
1636
|
+
let earlier;
|
|
1637
|
+
const earlierJobs = () => {
|
|
1638
|
+
if (earlier !== undefined)
|
|
1639
|
+
return earlier;
|
|
1640
|
+
try {
|
|
1641
|
+
const found = earlierPulses(ledgerProjects(cwd), new Set(jobs.list().map((job) => job.id)));
|
|
1642
|
+
earlier = found.jobs;
|
|
1643
|
+
jobsTree.earlierMore = found.more;
|
|
1644
|
+
}
|
|
1645
|
+
catch {
|
|
1646
|
+
earlier = [];
|
|
1647
|
+
}
|
|
1648
|
+
return earlier;
|
|
1649
|
+
};
|
|
1604
1650
|
const mapState = createSpecMapState();
|
|
1605
1651
|
// The map from the files - the .plain files asked for, else the project's - with the work loop's plan laid over them.
|
|
1606
1652
|
const specMap = createSpecMapSource({
|
|
@@ -1694,6 +1740,7 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1694
1740
|
specMap.refresh();
|
|
1695
1741
|
const view = createSidePanel({
|
|
1696
1742
|
jobs: () => shownJobs(jobs),
|
|
1743
|
+
earlier: earlierJobs,
|
|
1697
1744
|
tree: jobsTree,
|
|
1698
1745
|
map: () => specMap.read(),
|
|
1699
1746
|
mapState,
|
|
@@ -1967,13 +2014,27 @@ function deployedLabel(name, what) {
|
|
|
1967
2014
|
function shownJobs(jobs) {
|
|
1968
2015
|
return [...(workLoops.get(jobs)?.loop.planned() ?? []), ...jobs.list()];
|
|
1969
2016
|
}
|
|
1970
|
-
function listJobs(jobs) {
|
|
2017
|
+
function listJobs(jobs, cwd) {
|
|
1971
2018
|
const all = [...shownJobs(jobs)].reverse().filter((job) => job.replacedBy === undefined);
|
|
1972
2019
|
const deployed = runningDeployed().map((item) => `deploy ${item.name} running ${item.moduleName}: ${item.url ?? item.command} (pid ${item.pid}, output in ${relative(process.cwd(), item.log) || item.log})`);
|
|
1973
2020
|
const listed = deployed.length > 0 ? `\n\nActivated from dist - /halt activated <name> halts one:\n${deployed.join("\n")}` : "";
|
|
2021
|
+
const before = earlierRuns(jobs, cwd);
|
|
1974
2022
|
if (all.length === 0)
|
|
1975
|
-
return `No pulses have been started.${listed}`;
|
|
1976
|
-
return `${jobLines(all)}${listed}`;
|
|
2023
|
+
return `No pulses have been started in this session.${listed}${before}`;
|
|
2024
|
+
return `${jobLines(all)}${listed}${before}`;
|
|
2025
|
+
}
|
|
2026
|
+
/** Earlier sessions' last runs and their pulses, from the ledger - nothing when it has none. */
|
|
2027
|
+
function earlierRuns(jobs, cwd) {
|
|
2028
|
+
try {
|
|
2029
|
+
const found = earlierPulses(ledgerProjects(cwd), new Set(jobs.list().map((job) => job.id)));
|
|
2030
|
+
if (found.runs.length === 0)
|
|
2031
|
+
return "";
|
|
2032
|
+
const more = found.more > 0 ? `\n\n${found.more} earlier run${found.more === 1 ? "" : "s"} beyond these are in the ledger.` : "";
|
|
2033
|
+
return `\n\nEarlier sessions, from the ledger - pulses or diagnose with a pulse's id tells one:\n${describeRuns(found.runs)}${more}`;
|
|
2034
|
+
}
|
|
2035
|
+
catch {
|
|
2036
|
+
return "";
|
|
2037
|
+
}
|
|
1977
2038
|
}
|
|
1978
2039
|
function jobLines(all) {
|
|
1979
2040
|
return all
|
package/dist/host.d.ts
CHANGED
|
@@ -19,3 +19,5 @@ export { createWorkLoop, describeModules, functionalityCompletion, promptOf, typ
|
|
|
19
19
|
export { createJobRegistry, defaultParallelJobs, type Job, type JobRegistry } from "./regenerate/jobs.ts";
|
|
20
20
|
export { describeTurnIncident, onModelEvent, type TranscriptEntry } from "./regenerate/activity.ts";
|
|
21
21
|
export { sweepOrphanedGroups } from "./regenerate/shell.ts";
|
|
22
|
+
export { ledgerDir, onLedgerEvent, readLedger, type AgentRef, type LedgerEntry, type LedgerEvent, type LedgerKind, type LedgerQuery } from "./regenerate/ledger.ts";
|
|
23
|
+
export { asJob, describePulseView, describeRuns, earlierPulses, ledgerProjects, pulseView, runViews, type PulseView, type RunView } from "./regenerate/ledger-view.ts";
|
package/dist/host.js
CHANGED
|
@@ -23,3 +23,6 @@ export { createWorkLoop, describeModules, functionalityCompletion, promptOf } fr
|
|
|
23
23
|
export { createJobRegistry, defaultParallelJobs } from "./regenerate/jobs.js";
|
|
24
24
|
export { describeTurnIncident, onModelEvent } from "./regenerate/activity.js";
|
|
25
25
|
export { sweepOrphanedGroups } from "./regenerate/shell.js";
|
|
26
|
+
// What turri did, as facts: the ledger, heard live and read back - this session's pulses and every earlier one's.
|
|
27
|
+
export { ledgerDir, onLedgerEvent, readLedger } from "./regenerate/ledger.js";
|
|
28
|
+
export { asJob, describePulseView, describeRuns, earlierPulses, ledgerProjects, pulseView, runViews } from "./regenerate/ledger-view.js";
|
package/dist/init-config.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ export declare const DEFAULTS: {
|
|
|
66
66
|
readonly compactAt: 100000;
|
|
67
67
|
readonly maxSpecsPerPulse: 3;
|
|
68
68
|
readonly maxLinesPerPulse: 120;
|
|
69
|
+
readonly ledgerKeep: 100;
|
|
69
70
|
};
|
|
70
71
|
/** How many pulses run at once without a setting: half this machine's cores, one to four - as regenerate/jobs.ts has it. */
|
|
71
72
|
export declare function defaultParallel(cores?: number): number;
|
package/dist/init-config.js
CHANGED
|
@@ -118,6 +118,7 @@ export const DEFAULTS = {
|
|
|
118
118
|
compactAt: 100000,
|
|
119
119
|
maxSpecsPerPulse: 3,
|
|
120
120
|
maxLinesPerPulse: 120,
|
|
121
|
+
ledgerKeep: 100,
|
|
121
122
|
};
|
|
122
123
|
/** How many pulses run at once without a setting: half this machine's cores, one to four - as regenerate/jobs.ts has it. */
|
|
123
124
|
export function defaultParallel(cores = availableParallelism()) {
|
|
@@ -183,6 +184,10 @@ export function quickStartConfig(vendor = "other", bedrock) {
|
|
|
183
184
|
"# Folders of this machine an agent's commands may read beside the usual ones - a toolchain somewhere unusual: [/srv/zig].",
|
|
184
185
|
"toolchains: []",
|
|
185
186
|
"",
|
|
187
|
+
"# Megabytes of the ledger - what turri did, as facts, in .turri/ledger, never committed - the project keeps; past it,",
|
|
188
|
+
"# the oldest runs' are pruned. 0 keeps none.",
|
|
189
|
+
`ledger-keep: ${DEFAULTS.ledgerKeep}`,
|
|
190
|
+
"",
|
|
186
191
|
"# The testing scripts, by path from the project root - written here by the skill that writes them. One not named is",
|
|
187
192
|
"# looked for at the root, then in test_scripts/. Each runs the static checks of what it tests - the formatter, the",
|
|
188
193
|
"# linter, the type checker - with the tests, exiting 80 when the tests pass and only the checks do not. The prepare",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { readLedger } from "./regenerate/ledger.js";
|
|
3
|
+
import { describePastPulse, describeRuns, ledgerProjects, runViews } from "./regenerate/ledger-view.js";
|
|
4
|
+
const USAGE = `usage: turri ledger [pulse] [--runs <n>] [--json]
|
|
5
|
+
|
|
6
|
+
What turri did here, from the project's ledger (.turri/ledger): the last runs, a line a pulse - how each ended, how
|
|
7
|
+
long it took and what it cost. With a pulse's id, or the start of it, that pulse: its rounds, test runs, verdicts,
|
|
8
|
+
healers, what turri refused and what its history recorded. --json prints the events themselves, a JSON line each.
|
|
9
|
+
|
|
10
|
+
--runs <n> how many runs to show, the most recent first (default 5)
|
|
11
|
+
--json the events, as the ledger has them - the pulse's alone with a pulse`;
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
14
|
+
console.log(USAGE);
|
|
15
|
+
process.exit(0);
|
|
16
|
+
}
|
|
17
|
+
const runsAt = args.indexOf("--runs");
|
|
18
|
+
const runs = runsAt >= 0 ? Number(args[runsAt + 1]) : 5;
|
|
19
|
+
if (runsAt >= 0 && (!Number.isInteger(runs) || runs < 1)) {
|
|
20
|
+
console.error(`--runs takes a number of runs, at least 1\n\n${USAGE}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const [pulse] = args.filter((arg, at) => !arg.startsWith("-") && (runsAt < 0 || at !== runsAt + 1));
|
|
24
|
+
const projects = ledgerProjects(process.cwd());
|
|
25
|
+
if (projects.length === 0) {
|
|
26
|
+
console.log("No ledger here: turri has not run in this folder, or in a project under it, since the ledger began.");
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
if (args.includes("--json")) {
|
|
30
|
+
for (const project of projects)
|
|
31
|
+
for (const entry of readLedger(project, pulse !== undefined ? { job: pulse } : {}))
|
|
32
|
+
console.log(JSON.stringify(entry));
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
if (pulse !== undefined) {
|
|
36
|
+
const described = describePastPulse(projects, pulse);
|
|
37
|
+
if (described === undefined) {
|
|
38
|
+
console.error(`The ledger has no pulse ${pulse}.`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
console.log(described);
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
for (const project of projects) {
|
|
45
|
+
if (projects.length > 1)
|
|
46
|
+
console.log(`── ${resolve(project)}\n`);
|
|
47
|
+
console.log(describeRuns(runViews(project).slice(0, runs)));
|
|
48
|
+
if (projects.length > 1)
|
|
49
|
+
console.log("");
|
|
50
|
+
}
|
package/dist/loop-cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { configForSpec, inheritModel, modelOnly, modelSpec, projectModels, readP
|
|
|
5
5
|
import { PACKAGE_ROOT } from "./environment.js";
|
|
6
6
|
import { c } from "./module-report.js";
|
|
7
7
|
import { describeTurnIncident, onModelEvent } from "./regenerate/activity.js";
|
|
8
|
+
import { ledgerWanted, startLedger } from "./regenerate/ledger.js";
|
|
8
9
|
import { tryItCommand } from "./regenerate/delivery.js";
|
|
9
10
|
import { describeDiagnosis, diagnoseFailure } from "./regenerate/diagnose.js";
|
|
10
11
|
import { createJobRegistry, pulseName } from "./regenerate/jobs.js";
|
|
@@ -32,6 +33,9 @@ export async function runLoop(options) {
|
|
|
32
33
|
// The models as the config has them - with a command's own options read as its keys (headlessOptions), as the chat reads them.
|
|
33
34
|
const model = modelSpec(projectModels(project).coding);
|
|
34
35
|
const projectDir = projectDirOf(plainFile);
|
|
36
|
+
// What the run does, as facts, in the project's .turri/ledger.
|
|
37
|
+
if (ledgerWanted() && options.dryRun !== true)
|
|
38
|
+
startLedger({ mode: options.command?.[0] ?? "headless", project: projectDir, warn: (message) => console.error(message) });
|
|
35
39
|
const buildDir = options.buildDir !== undefined && options.dryRun !== true ? resolve(options.buildDir) : undefined;
|
|
36
40
|
if (buildDir !== undefined)
|
|
37
41
|
refuseBuildDir(buildDir, projectDir);
|
package/dist/pi.js
CHANGED
|
@@ -9,6 +9,7 @@ import { checkAgainstScript, ENVIRONMENT_ERROR_TOOL, EnvironmentError, environme
|
|
|
9
9
|
import { MODULES_DIR, projectRootOf, sessionsDir } from "./regenerate/paths.js";
|
|
10
10
|
import { currentStopSignal, describeToolCall, describeToolResult, JobStoppedError, shownName, reportActivity, reportNow, reportAgent, reportTranscript, reportModelEvent, reportUsage, } from "./regenerate/activity.js";
|
|
11
11
|
import { debugLogOf, startDebugLog } from "./regenerate/debug-log.js";
|
|
12
|
+
import { firstLine, record } from "./regenerate/ledger.js";
|
|
12
13
|
let thinkingSource;
|
|
13
14
|
/**
|
|
14
15
|
* Where sub-agents take their thinking level from: the conversation that started them.
|
|
@@ -280,6 +281,8 @@ async function askOnce(session, text, onDelta) {
|
|
|
280
281
|
// Calls to one tool failing one after another - arguments it refuses, or errors - with nothing going through.
|
|
281
282
|
let failing = 0;
|
|
282
283
|
let failingTool;
|
|
284
|
+
/** When each tool call under way started, by its id: how long it took is recorded as it ends. */
|
|
285
|
+
const toolStarted = new Map();
|
|
283
286
|
harnessStops.delete(session);
|
|
284
287
|
reportNow("waiting for the model");
|
|
285
288
|
// Every entry says which agent it is from, and what it runs on - for reading one agent at a time.
|
|
@@ -348,6 +351,7 @@ async function askOnce(session, text, onDelta) {
|
|
|
348
351
|
}
|
|
349
352
|
}
|
|
350
353
|
if (event.type === "tool_execution_start") {
|
|
354
|
+
toolStarted.set(event.toolCallId, Date.now());
|
|
351
355
|
const args = JSON.stringify(event.args ?? {});
|
|
352
356
|
transcript({ kind: "tool", text: `${shownName(event.toolName)} ${clipText(args, MAX_TOOL_ARGS)}`, full: `${shownName(event.toolName)} ${args}` });
|
|
353
357
|
const took = Date.now() - stepStarted;
|
|
@@ -369,6 +373,9 @@ async function askOnce(session, text, onDelta) {
|
|
|
369
373
|
stopTurn(session, `${failing} ${shownName(event.toolName)} calls failed in a row`);
|
|
370
374
|
}
|
|
371
375
|
const output = resultText(event.result);
|
|
376
|
+
const started = toolStarted.get(event.toolCallId);
|
|
377
|
+
toolStarted.delete(event.toolCallId);
|
|
378
|
+
record({ kind: "tool.call", tool: event.toolName, ms: started !== undefined ? Date.now() - started : 0, ok: !event.isError, resultChars: output.length }, { agent: modelEventOf(session) });
|
|
372
379
|
transcript({ kind: "result", text: clipText(output, MAX_TOOL_RESULT), error: event.isError, full: output });
|
|
373
380
|
const line = describeToolResult(event.toolName, event.result, event.isError);
|
|
374
381
|
if (line !== undefined)
|
|
@@ -483,6 +490,7 @@ async function askOnce(session, text, onDelta) {
|
|
|
483
490
|
// A turn turri ended is ended, whatever the provider was doing then: what comes back of the abort is not the provider failing.
|
|
484
491
|
if (providerError !== undefined && stoppedFor === undefined) {
|
|
485
492
|
debug?.note(`turn ended with a provider error after ${seconds(Date.now() - turnStarted)}: ${providerError}`);
|
|
493
|
+
record({ kind: "provider.error", error: firstLine(providerError, 300), transient: TRANSIENT_PROVIDER_ERROR.test(providerError) }, { agent: modelEventOf(session) });
|
|
486
494
|
throw new ProviderError(`${session.model?.id ?? "model"}: ${providerError}`);
|
|
487
495
|
}
|
|
488
496
|
debug?.note(`turn finished after ${seconds(Date.now() - turnStarted)}`);
|
|
@@ -659,6 +667,11 @@ export function guardRepeats(tools, session, budget) {
|
|
|
659
667
|
// Calls refused in the restricted turn under way, and which turn's set that was.
|
|
660
668
|
let refused = 0;
|
|
661
669
|
let refusedIn;
|
|
670
|
+
/** The agent a refusal is of, for the ledger. */
|
|
671
|
+
const guardPlace = () => {
|
|
672
|
+
const current = session();
|
|
673
|
+
return current !== undefined ? { agent: modelEventOf(current) } : {};
|
|
674
|
+
};
|
|
662
675
|
return tools.map((tool) => ({
|
|
663
676
|
...tool,
|
|
664
677
|
execute: async (...args) => {
|
|
@@ -669,6 +682,7 @@ export function guardRepeats(tools, session, budget) {
|
|
|
669
682
|
if (refusedIn !== only)
|
|
670
683
|
(refused = 0), (refusedIn = only);
|
|
671
684
|
refused += 1;
|
|
685
|
+
record({ kind: "guard.refused", rule: "restricted", tool: tool.name, times: refused }, guardPlace());
|
|
672
686
|
if (refused > REFUSED_BEFORE_STOP) {
|
|
673
687
|
reportActivity(` stopped the agent's turn: ${refused} calls to tools this turn does not have`);
|
|
674
688
|
stopTurn(current, `${refused} calls to tools this turn does not have`);
|
|
@@ -679,6 +693,7 @@ export function guardRepeats(tools, session, budget) {
|
|
|
679
693
|
}
|
|
680
694
|
calls += 1;
|
|
681
695
|
if (budget !== undefined && !budget.finish.includes(tool.name) && calls > budget.calls) {
|
|
696
|
+
record({ kind: "guard.refused", rule: "budget", tool: tool.name, times: calls - budget.calls }, guardPlace());
|
|
682
697
|
if (calls > budget.calls * 2) {
|
|
683
698
|
reportActivity(` stopped the agent's turn: ${calls} tool calls, past its budget of ${budget.calls}`);
|
|
684
699
|
stopTurn(session(), `${calls} tool calls, past its budget of ${budget.calls}`);
|
|
@@ -707,6 +722,8 @@ export function guardRepeats(tools, session, budget) {
|
|
|
707
722
|
// Anything else may have changed what a look would find.
|
|
708
723
|
looks.clear();
|
|
709
724
|
}
|
|
725
|
+
if (repeats > REPEATS_ALLOWED)
|
|
726
|
+
record({ kind: "guard.refused", rule: "repeat", tool: tool.name, times: repeats }, guardPlace());
|
|
710
727
|
if (repeats > REPEATS_BEFORE_STOP) {
|
|
711
728
|
reportActivity(` stopped the agent's turn: it made the same ${tool.name} call ${repeats} times with nothing changed`);
|
|
712
729
|
stopTurn(session(), `the same ${tool.name} call ${repeats} times with nothing changed`);
|
|
@@ -36,6 +36,9 @@ export interface ActivitySink {
|
|
|
36
36
|
kind: string;
|
|
37
37
|
summary: string;
|
|
38
38
|
};
|
|
39
|
+
/** The project the job works in, and its module: where what it does is recorded (ledger.ts). */
|
|
40
|
+
project?: string;
|
|
41
|
+
module?: string;
|
|
39
42
|
/** The spec was found to contradict itself - for the person to decide. */
|
|
40
43
|
specConflict?(conflict: SpecConflict): void;
|
|
41
44
|
/** The functional spec being built now, or nothing when none is. */
|
|
@@ -89,6 +92,12 @@ export declare function currentJob(): {
|
|
|
89
92
|
kind: string;
|
|
90
93
|
summary: string;
|
|
91
94
|
} | undefined;
|
|
95
|
+
/** Where this code runs: the job it runs for, its project and its module - each when it runs for one. */
|
|
96
|
+
export declare function currentPlace(): {
|
|
97
|
+
job?: string;
|
|
98
|
+
project?: string;
|
|
99
|
+
module?: string;
|
|
100
|
+
};
|
|
92
101
|
/**
|
|
93
102
|
* The conversation an agent is having, as it happens: what it was asked, what it wrote and
|
|
94
103
|
* thought, what it called and what came back. Text and thinking arrive as deltas and are
|
|
@@ -22,6 +22,17 @@ export function currentStopSignal() {
|
|
|
22
22
|
export function currentJob() {
|
|
23
23
|
return store.getStore()?.job;
|
|
24
24
|
}
|
|
25
|
+
/** Where this code runs: the job it runs for, its project and its module - each when it runs for one. */
|
|
26
|
+
export function currentPlace() {
|
|
27
|
+
const sink = store.getStore();
|
|
28
|
+
if (sink === undefined)
|
|
29
|
+
return {};
|
|
30
|
+
return {
|
|
31
|
+
...(sink.job !== undefined ? { job: sink.job.id } : {}),
|
|
32
|
+
...(sink.project !== undefined ? { project: sink.project } : {}),
|
|
33
|
+
...(sink.module !== undefined ? { module: sink.module } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
25
36
|
export function reportTranscript(entry) {
|
|
26
37
|
store.getStore()?.transcript(entry);
|
|
27
38
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { dirname, join, resolve, sep } from "node:path";
|
|
2
2
|
import { readProjectConfig, stackOf } from "../config.js";
|
|
3
3
|
import { reportNote } from "./activity.js";
|
|
4
|
+
import { record } from "./ledger.js";
|
|
4
5
|
import { agentEnvironment, DARWIN_TEMP_SUFFIX, packageCacheDir, TOOLCHAIN_VARIABLES } from "./agent-env.js";
|
|
5
6
|
import { afterCommand, auditMessage, beforeCommand } from "./audit.js";
|
|
6
7
|
import { checkCommand, machineReadable } from "./command-check.js";
|
|
@@ -67,6 +68,9 @@ export function agentShell(role, options = {}) {
|
|
|
67
68
|
throw error;
|
|
68
69
|
}
|
|
69
70
|
const result = afterCommand(before);
|
|
71
|
+
if (result.putBack.length + result.left.length + result.lost.length + result.conversation.length > 0) {
|
|
72
|
+
record({ kind: "audit.putBack", putBack: result.putBack.length, left: result.left.length, lost: result.lost.length, conversation: result.conversation.length });
|
|
73
|
+
}
|
|
70
74
|
if (result.putBack.length > 0)
|
|
71
75
|
reportNote(` an agent's command changed ${result.putBack.join(", ")} - put back`);
|
|
72
76
|
if (result.left.length > 0)
|
|
@@ -90,6 +94,7 @@ export function agentShell(role, options = {}) {
|
|
|
90
94
|
async run(command, runOptions = {}) {
|
|
91
95
|
const checked = checkCommand(command, commandFence);
|
|
92
96
|
if (!checked.ok) {
|
|
97
|
+
record({ kind: "command.refused", rule: checked.rule });
|
|
93
98
|
reportNote(` refused an agent's command (${checked.rule}): ${command.length > 120 ? `${command.slice(0, 120)}...` : command}`);
|
|
94
99
|
const output = `Not run: ${checked.reason}`;
|
|
95
100
|
runOptions.onData?.(Buffer.from(output));
|
|
@@ -147,12 +147,6 @@ export interface ChunkAgents {
|
|
|
147
147
|
export declare function groupProblems(specs: NewSpec[], answer: GroupAnswer, maxSpecs?: number): string[];
|
|
148
148
|
/** What is wrong with a placement: every new req once, each where there is such a job. */
|
|
149
149
|
export declare function placeProblems(input: PlaceInput, answer: PlaceAnswer): string[];
|
|
150
|
-
/**
|
|
151
|
-
* Cut the queue's work - from `input.base` to `input.current` - into jobs, as the three steps say. Returns the draft
|
|
152
|
-
* the work loop runs: checked (checkDraft), or, when nothing holds, the whole work as one job. `notes` is given what
|
|
153
|
-
* the plan did not do as asked - the person's wishes an agent did not follow and why, an answer that did not hold, a
|
|
154
|
-
* group split for its size - for the conversation to say, rather than guess.
|
|
155
|
-
*/
|
|
156
150
|
export declare function planChunks(input: PlanInput, agents?: ChunkAgents, notes?: string[]): Promise<DraftJob[]>;
|
|
157
151
|
export declare function groupPrompt(specs: NewSpec[], steering?: Steering[], maxSpecs?: number): string;
|
|
158
152
|
export declare function placePrompt(input: PlaceInput): string;
|
|
@@ -4,6 +4,7 @@ import { DEFAULT_CONCEPTS, extractConceptsFromDefinition, extractConceptsFromSpe
|
|
|
4
4
|
import { ACCEPTANCE_TESTS, DEFINITIONS, FUNCTIONAL_REQUIREMENTS, NON_FUNCTIONAL_REQUIREMENTS, REQUIRED_FUNCTIONAL_SPECS, TEST_REQUIREMENTS } from "../plain/spec.js";
|
|
5
5
|
import { ask, BEGIN, createExecutor } from "../pi.js";
|
|
6
6
|
import { reportActivity, reportStage } from "./activity.js";
|
|
7
|
+
import { firstLine, record } from "./ledger.js";
|
|
7
8
|
import { steeringBlock } from "./steer.js";
|
|
8
9
|
import { marksRegenerateAgain } from "./records.js";
|
|
9
10
|
import { validateConcepts } from "../plain/concepts.js";
|
|
@@ -245,6 +246,11 @@ function titleOf(change) {
|
|
|
245
246
|
* the plan did not do as asked - the person's wishes an agent did not follow and why, an answer that did not hold, a
|
|
246
247
|
* group split for its size - for the conversation to say, rather than guess.
|
|
247
248
|
*/
|
|
249
|
+
/** A planner agent that failed: the plan falls back, and the ledger says why. */
|
|
250
|
+
function plannerFailed(step, error) {
|
|
251
|
+
record({ kind: "planner.failed", step, error: firstLine(error instanceof Error ? error.message : String(error), 300) });
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
248
254
|
export async function planChunks(input, agents, notes) {
|
|
249
255
|
if (input.track !== "conformance")
|
|
250
256
|
return cut(input, agents, notes);
|
|
@@ -370,7 +376,7 @@ async function cut(input, agents, notes) {
|
|
|
370
376
|
...list.map((group) => ({ title: group.title, specs: group.specs.map((number) => bySpec.get(number)), definitions: [], reqs: [] })),
|
|
371
377
|
];
|
|
372
378
|
const conformance = input.track === "conformance";
|
|
373
|
-
const grouped = conformance ? undefined : ((await agents?.group(numbered, (answer) => groupProblems(numbered, answer, maxSpecs), input.steering, maxSpecs).catch(() =>
|
|
379
|
+
const grouped = conformance ? undefined : ((await agents?.group(numbered, (answer) => groupProblems(numbered, answer, maxSpecs), input.steering, maxSpecs).catch((error) => plannerFailed("group", error))) ?? undefined);
|
|
374
380
|
const groups = conformance
|
|
375
381
|
? conformanceGroups(numbered, input.separately === true, maxLines, (list) => {
|
|
376
382
|
const trial = chunksFor(list);
|
|
@@ -386,10 +392,14 @@ async function cut(input, agents, notes) {
|
|
|
386
392
|
if (grouped?.notes !== undefined && grouped.notes.trim() !== "")
|
|
387
393
|
say(`the grouping: ${grouped.notes.trim()}`);
|
|
388
394
|
if (!conformance && agents !== undefined) {
|
|
389
|
-
if (grouped === undefined)
|
|
395
|
+
if (grouped === undefined) {
|
|
396
|
+
record({ kind: "planner.fellBack", step: "group", why: "no grouping was given" });
|
|
390
397
|
say("the grouping was not given - a functional spec a pulse");
|
|
391
|
-
|
|
398
|
+
}
|
|
399
|
+
else if (groupProblems(numbered, grouped, maxSpecs).length > 0) {
|
|
400
|
+
record({ kind: "planner.fellBack", step: "group", why: firstLine(groupProblems(numbered, grouped, maxSpecs).join(" "), 300) });
|
|
392
401
|
say(`the grouping did not hold (${groupProblems(numbered, grouped, maxSpecs).join(" ")}) - a functional spec a pulse`);
|
|
402
|
+
}
|
|
393
403
|
}
|
|
394
404
|
let chunks = chunksFor(groups);
|
|
395
405
|
// 2. The definitions each group uses.
|
|
@@ -407,10 +417,12 @@ async function cut(input, agents, notes) {
|
|
|
407
417
|
implementationReqs: implementationReqs.map((change, index) => ({ number: index + 1, text: textOf(change) })),
|
|
408
418
|
testReqs: testReqs.map((change, index) => ({ number: index + 1, text: textOf(change) })),
|
|
409
419
|
};
|
|
410
|
-
const answer = (await agents?.place(placeInput, (given) => placeProblems(placeInput, given)).catch(() =>
|
|
420
|
+
const answer = (await agents?.place(placeInput, (given) => placeProblems(placeInput, given)).catch((error) => plannerFailed("place", error))) ?? undefined;
|
|
411
421
|
if (answer?.notes !== undefined && answer.notes.trim() !== "")
|
|
412
422
|
say(`the placement: ${answer.notes.trim()}`);
|
|
413
423
|
placement = answer !== undefined && placeProblems(placeInput, answer).length === 0 ? answer : undefined;
|
|
424
|
+
if (placement === undefined && agents !== undefined)
|
|
425
|
+
record({ kind: "planner.fellBack", step: "place", why: answer === undefined ? "no placement was given" : firstLine(placeProblems(placeInput, answer).join(" "), 300) });
|
|
414
426
|
if (placement === undefined && agents !== undefined)
|
|
415
427
|
say("the placement did not hold - every implementation req with the first pulse, every test req with the first conformance pulse");
|
|
416
428
|
}
|
|
@@ -6,6 +6,7 @@ import { ACCEPTANCE_TESTS, DEFINITIONS, FUNCTIONAL_REQUIREMENTS, NON_FUNCTIONAL_
|
|
|
6
6
|
import { reportActivity, reportLogNote, reportSpec, reportStage, unitOf } from "./activity.js";
|
|
7
7
|
import { confinedTools } from "./confine.js";
|
|
8
8
|
import { abandonWork, diffSinceSnapshot, landWork, moduleDirOf, startWork, workDiff, workSnapshot } from "./lineage.js";
|
|
9
|
+
import { firstLine, record } from "./ledger.js";
|
|
9
10
|
import { createWorkLedger, describeAccounts, SUBMIT_WORK_NOTE, unanswered, unansweredPrompt } from "./submit-work.js";
|
|
10
11
|
import { CONFORMANCE_COMPACTION } from "./compaction.js";
|
|
11
12
|
import { NOTES_TURN, notesForSession, passedOutcome, readNotes, reflectOnReview, withNotes } from "./notes.js";
|
|
@@ -417,6 +418,7 @@ export async function renderConformanceTests(options) {
|
|
|
417
418
|
// every functionality's tests - and the code's unit testing script.
|
|
418
419
|
stage(`running the conformance testing script on the whole suite (attempt ${rounds + 1} of ${maxRounds})`);
|
|
419
420
|
rounds += 1;
|
|
421
|
+
record({ kind: "gate.round", gate: "conformance", round: rounds, of: maxRounds });
|
|
420
422
|
let recheck = await runConformanceScript(buildDir, [conformanceDir], scripts);
|
|
421
423
|
runs.push({ command: recheck.command, exitCode: recheck.exitCode });
|
|
422
424
|
options.onRun?.(recheck.command, recheck.exitCode);
|
|
@@ -537,6 +539,7 @@ export async function renderConformanceTests(options) {
|
|
|
537
539
|
onDelta: options.onDelta,
|
|
538
540
|
});
|
|
539
541
|
const reason = verdict.reason.split("\n")[0].slice(0, 160);
|
|
542
|
+
record({ kind: "judge.verdict", culprit: verdict.culprit, round: rounds, reason: firstLine(verdict.reason, 300) });
|
|
540
543
|
// Stopped by the time limit while it was still getting on: no agent can give it more time. The pulse stops now -
|
|
541
544
|
// rolled back, no retry - and the person is told how to raise the limit.
|
|
542
545
|
if (verdict.culprit === "environment" && recheck.timedOut === true) {
|
|
@@ -49,8 +49,10 @@ export async function diagnoseFailure(options) {
|
|
|
49
49
|
if (diagnosis === undefined)
|
|
50
50
|
await ask(session, "You have not called submit_diagnosis. Call it now, with what you know.");
|
|
51
51
|
}
|
|
52
|
-
catch {
|
|
53
|
-
// Without a diagnosis the person still gets both failures, as they were.
|
|
52
|
+
catch (error) {
|
|
53
|
+
// Without a diagnosis the person still gets both failures, as they were - and the ledger why there is none.
|
|
54
|
+
if (diagnosis === undefined)
|
|
55
|
+
throw error;
|
|
54
56
|
}
|
|
55
57
|
finally {
|
|
56
58
|
session.dispose();
|