@turri-dev/cli 0.1.8-next.e553476 → 0.1.8
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 +1 -3
- package/dist/cli.js +6 -2
- package/dist/config-edit.d.ts +1 -1
- package/dist/config-edit.js +4 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.js +28 -1
- package/dist/extension.js +75 -9
- package/dist/genome.js +2 -0
- package/dist/host.d.ts +3 -1
- package/dist/host.js +4 -1
- package/dist/init-config.d.ts +3 -0
- package/dist/init-config.js +15 -0
- package/dist/ledger-cli.d.ts +1 -0
- package/dist/ledger-cli.js +50 -0
- package/dist/ledger-tools.d.ts +10 -0
- package/dist/ledger-tools.js +64 -0
- package/dist/loop-cli.js +4 -0
- package/dist/pi.d.ts +2 -0
- package/dist/pi.js +24 -4
- package/dist/regenerate/activity.d.ts +9 -0
- package/dist/regenerate/activity.js +11 -0
- package/dist/regenerate/agent-env.d.ts +7 -0
- package/dist/regenerate/agent-env.js +126 -5
- package/dist/regenerate/agent-shell.js +5 -0
- package/dist/regenerate/chunk-plan.d.ts +5 -6
- package/dist/regenerate/chunk-plan.js +43 -10
- package/dist/regenerate/conformance-agents.js +2 -2
- package/dist/regenerate/conformance.js +6 -3
- package/dist/regenerate/debug-log.d.ts +3 -1
- package/dist/regenerate/debug-log.js +10 -2
- package/dist/regenerate/diagnose.js +12 -2
- package/dist/regenerate/environment-error.d.ts +6 -0
- package/dist/regenerate/environment-error.js +19 -1
- 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 +51 -5
- package/dist/regenerate/ledger-view.d.ts +92 -0
- package/dist/regenerate/ledger-view.js +345 -0
- package/dist/regenerate/ledger.d.ts +275 -0
- package/dist/regenerate/ledger.js +349 -0
- package/dist/regenerate/lineage.d.ts +9 -1
- package/dist/regenerate/lineage.js +64 -12
- package/dist/regenerate/loop.js +12 -3
- package/dist/regenerate/pulse-files.d.ts +14 -0
- package/dist/regenerate/pulse-files.js +63 -0
- package/dist/regenerate/regenerate.js +7 -5
- package/dist/regenerate/regression.js +4 -2
- package/dist/regenerate/services.js +3 -0
- package/dist/regenerate/side-panel.d.ts +2 -0
- package/dist/regenerate/side-panel.js +4 -3
- package/dist/regenerate/test-scripts.js +18 -5
- package/dist/regenerate/testing.js +3 -0
- package/dist/regenerate/unit-review.js +3 -0
- package/dist/run-report.js +9 -5
- package/docs/config.md +23 -2
- package/docs/development.md +3 -1
- package/docs/headless.md +5 -4
- package/docs/hosting.md +29 -0
- package/docs/installation.md +1 -0
- package/docs/regenerating.md +7 -7
- package/docs/sandbox.md +5 -5
- package/docs/using.md +36 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
<p align="center"><img src="docs/images/turri-banner.gif" alt="turri, beside its mark - the first coding agent with the power of regenerative software: npm install -g @turri-dev/cli - and a pixel jellyfish regenerating, from a polyp on the sea floor"></p>
|
|
2
2
|
|
|
3
|
-
<h1 align="center">turri<br>the first coding agent with the power of<br>regenerative software<br> </h1>
|
|
4
|
-
|
|
5
3
|
<p align="center">
|
|
6
4
|
<a href="https://www.npmjs.com/package/@turri-dev/cli"><img src="https://img.shields.io/npm/v/@turri-dev/cli?label=npm" alt="npm version"></a>
|
|
7
5
|
<a href="LICENSE"><img src="https://img.shields.io/npm/l/@turri-dev/cli" alt="MIT licence"></a>
|
|
@@ -154,7 +152,7 @@ It asks you about the problem, the stack and the product, writes \*\*\*plain, an
|
|
|
154
152
|
|
|
155
153
|
- the **panel** above the input shows every build - its stage, what its agents are doing, its model and cost - and the queue behind it;
|
|
156
154
|
- ask to try it at any time - "let me try it" - and **`release`** puts what is built so far in `dist/<module>` and **`activate`** starts it, installing what it needs - nothing to approve;
|
|
157
|
-
- the **side panel** (`ctrl+h`) takes the screen and opens on **regen** - \*\*\*plain, every item marked planned, working, regenerated, validating, conform or needs you - and `p` turns it to the **pulses** (`/pulses`) - every pulse, running, queued and finished, and what each regenerates; what its agents did is in each pulse's log in `.turri/jobs
|
|
155
|
+
- the **side panel** (`ctrl+h`) takes the screen and opens on **regen** - \*\*\*plain, every item marked planned, working, regenerated, validating, conform or needs you - and `p` turns it to the **pulses** (`/pulses`) - every pulse, running, queued and finished, and what each regenerates; what its agents did is in each pulse's log in `.turri/jobs/`, kept with `--debug` (`debug: true`);
|
|
158
156
|
- **`/halt`** halts a pulse, **`/lineage`** shows every generation of a module, and the **`drift`** tool says what is left to regenerate;
|
|
159
157
|
- **`/btw <question>`** asks turri something on the side - answered at once, even in the middle of a skill's interview (typed as the answer, under "Something else…"), and left out of the conversation;
|
|
160
158
|
- **`/turri-config`** changes any setting of the project's `config.turri.yaml` - the models, review, conformance tests, how many pulses at once - and says when it takes effect: now, at your next message, or at the next pulse. No restart: `/turri-config review false`, or `/turri-config` alone to pick from the list;
|
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
|
};
|
|
@@ -96,6 +97,7 @@ const USAGE = [
|
|
|
96
97
|
"",
|
|
97
98
|
` ${"options".padEnd(12)} --harness-model <m> the model you chat with --coding-model <m> the model renders run on`,
|
|
98
99
|
` ${"".padEnd(12)} --stack <name> build with config.turri.<name>.yaml over config.turri.yaml - here and in every command`,
|
|
100
|
+
` ${"".padEnd(12)} --debug keep what turri writes for debugging it - agents' logs and sessions - as debug: true does`,
|
|
99
101
|
` ${"".padEnd(12)} --use-theme <t> another theme than turri-dark - and every option of pi's own`,
|
|
100
102
|
"",
|
|
101
103
|
...Object.entries(COMMANDS).map(([name, { blurb }]) => ` ${name.padEnd(12)} ${blurb}`),
|
|
@@ -109,7 +111,7 @@ const USAGE = [
|
|
|
109
111
|
].join("\n");
|
|
110
112
|
dropNoise();
|
|
111
113
|
// --stack <name>, for any command and for the conversation: the stack everything is built with (stacks.ts), taken out
|
|
112
|
-
// before the rest is read - a command never sees it.
|
|
114
|
+
// before the rest is read - a command never sees it. --debug too: TURRI_DEBUG for this run, and the pi it starts.
|
|
113
115
|
const argv = await withoutStack(process.argv.slice(2));
|
|
114
116
|
const [command, ...rest] = argv;
|
|
115
117
|
async function withoutStack(args) {
|
|
@@ -121,6 +123,8 @@ async function withoutStack(args) {
|
|
|
121
123
|
stack = args[++at] ?? "";
|
|
122
124
|
else if (arg.startsWith("--stack="))
|
|
123
125
|
stack = arg.slice("--stack=".length);
|
|
126
|
+
else if (arg === "--debug")
|
|
127
|
+
process.env.TURRI_DEBUG = "1";
|
|
124
128
|
else
|
|
125
129
|
kept.push(arg);
|
|
126
130
|
}
|
|
@@ -173,7 +177,7 @@ process.argv = [
|
|
|
173
177
|
// A headless run is stopped by a signal - SIGTERM, SIGHUP, Ctrl-C - whatever else listens for it: it exits, and what
|
|
174
178
|
// its agents left running goes with it (shell.ts kills every process group on exit). Left to the libraries' own
|
|
175
179
|
// 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)) {
|
|
180
|
+
if (!["validate", "check", "drift", "lineage", "ledger"].includes(command)) {
|
|
177
181
|
for (const [signal, code] of [["SIGTERM", 143], ["SIGHUP", 129], ["SIGINT", 130]]) {
|
|
178
182
|
process.on(signal, () => {
|
|
179
183
|
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,9 @@ 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: "debug", about: "whether turri keeps what it writes for debugging it - agents' logs and sessions, pulse logs, outputs", kind: "boolean", unset: "false", effect: "next-pulse", projectOnly: true },
|
|
80
|
+
{ key: "cache-keep", about: "megabytes of package caches the project keeps; 0 keeps none", kind: "number", options: ["0", "1024", "4096", "16384"], min: 0, unset: "4096", effect: "next-start", projectOnly: true },
|
|
81
|
+
{ 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
82
|
...roles,
|
|
79
83
|
];
|
|
80
84
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -260,6 +260,23 @@ 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;
|
|
268
|
+
/**
|
|
269
|
+
* Whether turri keeps what it writes for a person debugging it: every agent's whole log and saved session, each
|
|
270
|
+
* pulse's log, the testing scripts' and services' outputs, the agents' temporary files, and the dependencies installed
|
|
271
|
+
* in the modules a chain has left behind. Off, only what turri reads to work is kept. The project's alone. Unset: false.
|
|
272
|
+
*/
|
|
273
|
+
debug?: boolean;
|
|
274
|
+
/**
|
|
275
|
+
* Megabytes of package caches - plain_modules/.turri/cache, and the caches in the agents' home folders - the project
|
|
276
|
+
* keeps: past it, the least recently written are removed when a run first starts an agent's command. 0 keeps none.
|
|
277
|
+
* The project's alone. Unset: 4096.
|
|
278
|
+
*/
|
|
279
|
+
cacheKeep?: number;
|
|
263
280
|
/** Each agent role's own model, thinking and turn limit; `default` for every role not named. */
|
|
264
281
|
models?: Partial<Record<AgentRole | "default", RoleChoice>>;
|
|
265
282
|
/** Seconds each turn of an agent a render starts has, for every role without its own. Unset: 120. 0: none. */
|
|
@@ -288,6 +305,9 @@ export declare const CONFIG_KEYS: {
|
|
|
288
305
|
readonly maxLinesPerPulse: "max-lines-per-pulse";
|
|
289
306
|
readonly agentEnv: "agent-env";
|
|
290
307
|
readonly toolchains: "toolchains";
|
|
308
|
+
readonly ledgerKeep: "ledger-keep";
|
|
309
|
+
readonly debug: "debug";
|
|
310
|
+
readonly cacheKeep: "cache-keep";
|
|
291
311
|
readonly models: "models";
|
|
292
312
|
readonly turnLimit: "turn-limit";
|
|
293
313
|
};
|
|
@@ -310,6 +330,13 @@ export declare function readProjectConfig(root: string, stack?: string): Project
|
|
|
310
330
|
* edited. Never set in the conversation.
|
|
311
331
|
*/
|
|
312
332
|
type RunOverrides = Partial<Pick<ProjectConfig, "harnessModel" | "codingModel" | "judgeModel" | "conformanceTests" | "review" | "mutationTesting">>;
|
|
333
|
+
/** Megabytes of package caches a project keeps when `cache-keep:` does not say. */
|
|
334
|
+
export declare const DEFAULT_CACHE_KEEP_MB = 4096;
|
|
335
|
+
/**
|
|
336
|
+
* Whether turri keeps what it writes for a person debugging it, in this project: TURRI_DEBUG - `--debug` sets it, and
|
|
337
|
+
* `0` or `false` turns it off - else the project's `debug:`. Off by default.
|
|
338
|
+
*/
|
|
339
|
+
export declare function debugMode(project: string): boolean;
|
|
313
340
|
/** Read a command's options as the config's keys for the rest of this process. */
|
|
314
341
|
export declare function overrideConfig(overrides: RunOverrides): void;
|
|
315
342
|
/** `--conformance-tests <value>`: the config's own values - all (or true), chosen, module, false. Undefined for anything else. */
|
package/dist/config.js
CHANGED
|
@@ -379,6 +379,9 @@ export const CONFIG_KEYS = {
|
|
|
379
379
|
maxLinesPerPulse: "max-lines-per-pulse",
|
|
380
380
|
agentEnv: "agent-env",
|
|
381
381
|
toolchains: "toolchains",
|
|
382
|
+
ledgerKeep: "ledger-keep",
|
|
383
|
+
debug: "debug",
|
|
384
|
+
cacheKeep: "cache-keep",
|
|
382
385
|
models: "models",
|
|
383
386
|
turnLimit: "turn-limit",
|
|
384
387
|
};
|
|
@@ -431,7 +434,7 @@ function readStackedConfig(root, stack) {
|
|
|
431
434
|
// The project's alone: one conversation, one module built when none is named.
|
|
432
435
|
const merged = { ...project };
|
|
433
436
|
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)
|
|
437
|
+
if (key === "module" || key === "harnessModel" || key === "parallelJobs" || key === "rulesOff" || key === "compactAt" || key === "ledgerKeep" || key === "debug" || key === "cacheKeep" || key === "toolchains" || key === "agentEnv" || own[key] === undefined)
|
|
435
438
|
continue;
|
|
436
439
|
merged[key] = own[key];
|
|
437
440
|
}
|
|
@@ -443,6 +446,23 @@ function readStackedConfig(root, stack) {
|
|
|
443
446
|
return withoutUndefined(merged);
|
|
444
447
|
}
|
|
445
448
|
let runOverrides = {};
|
|
449
|
+
/** Megabytes of package caches a project keeps when `cache-keep:` does not say. */
|
|
450
|
+
export const DEFAULT_CACHE_KEEP_MB = 4096;
|
|
451
|
+
/**
|
|
452
|
+
* Whether turri keeps what it writes for a person debugging it, in this project: TURRI_DEBUG - `--debug` sets it, and
|
|
453
|
+
* `0` or `false` turns it off - else the project's `debug:`. Off by default.
|
|
454
|
+
*/
|
|
455
|
+
export function debugMode(project) {
|
|
456
|
+
const said = process.env.TURRI_DEBUG?.trim().toLowerCase();
|
|
457
|
+
if (said !== undefined && said !== "")
|
|
458
|
+
return said !== "0" && said !== "false";
|
|
459
|
+
try {
|
|
460
|
+
return readProjectConfig(project).debug === true;
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
446
466
|
/** Read a command's options as the config's keys for the rest of this process. */
|
|
447
467
|
export function overrideConfig(overrides) {
|
|
448
468
|
runOverrides = { ...overrides };
|
|
@@ -542,6 +562,13 @@ function readConfigFile(path) {
|
|
|
542
562
|
: undefined,
|
|
543
563
|
maxSpecsPerPulse: positiveInteger(raw[CONFIG_KEYS.maxSpecsPerPulse]),
|
|
544
564
|
maxLinesPerPulse: positiveInteger(raw[CONFIG_KEYS.maxLinesPerPulse]),
|
|
565
|
+
ledgerKeep: typeof raw[CONFIG_KEYS.ledgerKeep] === "number" && Number.isInteger(raw[CONFIG_KEYS.ledgerKeep]) && raw[CONFIG_KEYS.ledgerKeep] >= 0
|
|
566
|
+
? raw[CONFIG_KEYS.ledgerKeep]
|
|
567
|
+
: undefined,
|
|
568
|
+
debug: typeof raw[CONFIG_KEYS.debug] === "boolean" ? raw[CONFIG_KEYS.debug] : undefined,
|
|
569
|
+
cacheKeep: typeof raw[CONFIG_KEYS.cacheKeep] === "number" && Number.isInteger(raw[CONFIG_KEYS.cacheKeep]) && raw[CONFIG_KEYS.cacheKeep] >= 0
|
|
570
|
+
? raw[CONFIG_KEYS.cacheKeep]
|
|
571
|
+
: undefined,
|
|
545
572
|
models: modelsSetting(raw[CONFIG_KEYS.models]),
|
|
546
573
|
turnLimit: turnLimitSetting(raw[CONFIG_KEYS.turnLimit]),
|
|
547
574
|
};
|
package/dist/extension.js
CHANGED
|
@@ -44,6 +44,9 @@ 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";
|
|
49
|
+
import { ledgerTool } from "./ledger-tools.js";
|
|
47
50
|
import { describeSpecConflict } from "./regenerate/stop-report.js";
|
|
48
51
|
import { inheritProviders, inheritThinkingLevel } from "./pi.js";
|
|
49
52
|
import { beginCapture, captureConsole, describeCaptured, dropNoise } from "./quiet.js";
|
|
@@ -379,6 +382,24 @@ function turriExtension(pi, host) {
|
|
|
379
382
|
ctx.ui.notify(event.job !== undefined ? `${event.job}: ${line}` : line, "warning");
|
|
380
383
|
});
|
|
381
384
|
});
|
|
385
|
+
// What this session does, as facts, in the project's .turri/ledger - a pulse's own in its project. Said once, in the
|
|
386
|
+
// chat, when it cannot be written.
|
|
387
|
+
const ledgerTrouble = [];
|
|
388
|
+
let tellLedgerTrouble;
|
|
389
|
+
if (ledgerWanted()) {
|
|
390
|
+
startLedger({
|
|
391
|
+
mode: Object.keys(host).length > 0 ? "host" : "chat",
|
|
392
|
+
project: cwd,
|
|
393
|
+
warn: (message) => (tellLedgerTrouble !== undefined ? tellLedgerTrouble(message) : ledgerTrouble.push(message)),
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
pi.on("session_start", (_event, ctx) => {
|
|
397
|
+
if (!ctx.hasUI)
|
|
398
|
+
return;
|
|
399
|
+
tellLedgerTrouble = (message) => ctx.ui.notify(message, "warning");
|
|
400
|
+
for (const message of ledgerTrouble.splice(0))
|
|
401
|
+
tellLedgerTrouble(message);
|
|
402
|
+
});
|
|
382
403
|
// What a turri that was killed left running - its commands' process groups - stopped as this one starts.
|
|
383
404
|
const orphans = sweepOrphanedGroups();
|
|
384
405
|
let orphansSaid = orphans === 0;
|
|
@@ -1448,28 +1469,40 @@ async function deliver(jobs, input) {
|
|
|
1448
1469
|
const panelOpeners = new WeakMap();
|
|
1449
1470
|
/** By registry: the last pulse that failed, stopped or did not pass, not yet looked at - ctrl+h opens on it. */
|
|
1450
1471
|
const needsLooking = new WeakMap();
|
|
1472
|
+
/** What heads a pulse told from the ledger: it is not this session's, and the facts are all there is of it. */
|
|
1473
|
+
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, " +
|
|
1474
|
+
"for the read tool.";
|
|
1451
1475
|
function registerJobTools(pi, jobs, cwd) {
|
|
1452
1476
|
pi.registerTool({
|
|
1453
1477
|
name: "diagnose",
|
|
1454
1478
|
label: "Diagnose a pulse",
|
|
1455
1479
|
description: "Why a pulse did not land, from its whole transcript: how it ended and its diagnosis, the stages that went wrong, the " +
|
|
1456
1480
|
"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.",
|
|
1481
|
+
"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
1482
|
promptSnippet: "diagnose: why a pulse did not land, from its transcript",
|
|
1459
1483
|
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
1484
|
parameters: Type.Object({ job_id: Type.String({ description: "The pulse's id" }) }),
|
|
1461
1485
|
async execute(_id, params) {
|
|
1462
1486
|
const job = jobs.get(params.job_id);
|
|
1463
|
-
if (job === undefined)
|
|
1464
|
-
|
|
1465
|
-
|
|
1487
|
+
if (job === undefined) {
|
|
1488
|
+
const past = describePastPulse(ledgerProjects(cwd), params.job_id);
|
|
1489
|
+
if (past !== undefined)
|
|
1490
|
+
return { content: [{ type: "text", text: `${pastPulseNote}\n\n${past}` }], details: { status: "earlier" } };
|
|
1491
|
+
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" } };
|
|
1492
|
+
}
|
|
1493
|
+
// What the ledger recorded of it besides: its rounds, test runs, healers and commits, as facts.
|
|
1494
|
+
const facts = describePastPulse(ledgerProjects(cwd), job.id);
|
|
1495
|
+
const text = facts !== undefined ? `${whyFailed(job)}\n\nWhat the ledger recorded of it:\n${facts}` : whyFailed(job);
|
|
1496
|
+
return { content: [{ type: "text", text }], details: { status: job.status } };
|
|
1466
1497
|
},
|
|
1467
1498
|
});
|
|
1499
|
+
pi.registerTool(ledgerTool({ cwd }));
|
|
1468
1500
|
pi.registerTool({
|
|
1469
1501
|
name: "pulses",
|
|
1470
1502
|
label: "Pulses",
|
|
1471
1503
|
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."
|
|
1504
|
+
"with a pulse's id, that pulse - whether it is still running, its latest output, and its report once it has finished. " +
|
|
1505
|
+
"A pulse of an earlier session is told from the ledger.",
|
|
1473
1506
|
promptSnippet: "pulses: every pulse, or one - what is regenerating and how it is going",
|
|
1474
1507
|
parameters: Type.Object({
|
|
1475
1508
|
job_id: Type.Optional(Type.String({ description: "The id returned when the pulse started. Omitted: every pulse" })),
|
|
@@ -1477,7 +1510,7 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1477
1510
|
}),
|
|
1478
1511
|
async execute(_id, params) {
|
|
1479
1512
|
if (params.job_id === undefined) {
|
|
1480
|
-
return { content: [{ type: "text", text: listJobs(jobs) }], details: { status: "all" } };
|
|
1513
|
+
return { content: [{ type: "text", text: listJobs(jobs, cwd) }], details: { status: "all" } };
|
|
1481
1514
|
}
|
|
1482
1515
|
const existing = jobs.get(params.job_id);
|
|
1483
1516
|
const planned = shownJobs(jobs).find((job) => job.id === params.job_id && jobs.get(job.id) === undefined);
|
|
@@ -1488,6 +1521,9 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1488
1521
|
};
|
|
1489
1522
|
}
|
|
1490
1523
|
if (existing === undefined) {
|
|
1524
|
+
const past = describePastPulse(ledgerProjects(cwd), params.job_id);
|
|
1525
|
+
if (past !== undefined)
|
|
1526
|
+
return { content: [{ type: "text", text: `${pastPulseNote}\n\n${past}` }], details: { status: "earlier" } };
|
|
1491
1527
|
const known = shownJobs(jobs).map((job) => job.id);
|
|
1492
1528
|
return {
|
|
1493
1529
|
content: [
|
|
@@ -1601,6 +1637,21 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1601
1637
|
const panelBeside = () => (panelDocked && panelTerminal !== undefined ? dockedWidth(paddedColumns(panelTerminal.columns)) : 0);
|
|
1602
1638
|
// 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
1639
|
const jobsTree = createJobsTreeState();
|
|
1640
|
+
// Earlier sessions' pulses, from the ledger: read as the panel first shows them - they do not change while this one runs.
|
|
1641
|
+
let earlier;
|
|
1642
|
+
const earlierJobs = () => {
|
|
1643
|
+
if (earlier !== undefined)
|
|
1644
|
+
return earlier;
|
|
1645
|
+
try {
|
|
1646
|
+
const found = earlierPulses(ledgerProjects(cwd), new Set(jobs.list().map((job) => job.id)));
|
|
1647
|
+
earlier = found.jobs;
|
|
1648
|
+
jobsTree.earlierMore = found.more;
|
|
1649
|
+
}
|
|
1650
|
+
catch {
|
|
1651
|
+
earlier = [];
|
|
1652
|
+
}
|
|
1653
|
+
return earlier;
|
|
1654
|
+
};
|
|
1604
1655
|
const mapState = createSpecMapState();
|
|
1605
1656
|
// The map from the files - the .plain files asked for, else the project's - with the work loop's plan laid over them.
|
|
1606
1657
|
const specMap = createSpecMapSource({
|
|
@@ -1694,6 +1745,7 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1694
1745
|
specMap.refresh();
|
|
1695
1746
|
const view = createSidePanel({
|
|
1696
1747
|
jobs: () => shownJobs(jobs),
|
|
1748
|
+
earlier: earlierJobs,
|
|
1697
1749
|
tree: jobsTree,
|
|
1698
1750
|
map: () => specMap.read(),
|
|
1699
1751
|
mapState,
|
|
@@ -1967,13 +2019,27 @@ function deployedLabel(name, what) {
|
|
|
1967
2019
|
function shownJobs(jobs) {
|
|
1968
2020
|
return [...(workLoops.get(jobs)?.loop.planned() ?? []), ...jobs.list()];
|
|
1969
2021
|
}
|
|
1970
|
-
function listJobs(jobs) {
|
|
2022
|
+
function listJobs(jobs, cwd) {
|
|
1971
2023
|
const all = [...shownJobs(jobs)].reverse().filter((job) => job.replacedBy === undefined);
|
|
1972
2024
|
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
2025
|
const listed = deployed.length > 0 ? `\n\nActivated from dist - /halt activated <name> halts one:\n${deployed.join("\n")}` : "";
|
|
2026
|
+
const before = earlierRuns(jobs, cwd);
|
|
1974
2027
|
if (all.length === 0)
|
|
1975
|
-
return `No pulses have been started.${listed}`;
|
|
1976
|
-
return `${jobLines(all)}${listed}`;
|
|
2028
|
+
return `No pulses have been started in this session.${listed}${before}`;
|
|
2029
|
+
return `${jobLines(all)}${listed}${before}`;
|
|
2030
|
+
}
|
|
2031
|
+
/** Earlier sessions' last runs and their pulses, from the ledger - nothing when it has none. */
|
|
2032
|
+
function earlierRuns(jobs, cwd) {
|
|
2033
|
+
try {
|
|
2034
|
+
const found = earlierPulses(ledgerProjects(cwd), new Set(jobs.list().map((job) => job.id)));
|
|
2035
|
+
if (found.runs.length === 0)
|
|
2036
|
+
return "";
|
|
2037
|
+
const more = found.more > 0 ? `\n\n${found.more} earlier run${found.more === 1 ? "" : "s"} beyond these are in the ledger.` : "";
|
|
2038
|
+
return `\n\nEarlier sessions, from the ledger - pulses or diagnose with a pulse's id tells one:\n${describeRuns(found.runs)}${more}`;
|
|
2039
|
+
}
|
|
2040
|
+
catch {
|
|
2041
|
+
return "";
|
|
2042
|
+
}
|
|
1977
2043
|
}
|
|
1978
2044
|
function jobLines(all) {
|
|
1979
2045
|
return all
|
package/dist/genome.js
CHANGED
|
@@ -136,6 +136,8 @@ export async function runSkill(opts) {
|
|
|
136
136
|
rules: true,
|
|
137
137
|
system: toolchainsBlock(),
|
|
138
138
|
resumeFrom: resumed?.sessionFile,
|
|
139
|
+
// Its run picks it back up (resume) and the side questions read it (btw): saved whatever the mode.
|
|
140
|
+
saveSession: true,
|
|
139
141
|
});
|
|
140
142
|
const manifest = resumed ?? {
|
|
141
143
|
skill: skill.name,
|
package/dist/host.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { createTurriExtension, type TurriHost } from "./extension.ts";
|
|
|
8
8
|
export type { AskUserIO } from "./ask.ts";
|
|
9
9
|
/** pi itself, as turri's extension is built against it: a host makes its session with this one, not another copy. */
|
|
10
10
|
export * as pi from "@earendil-works/pi-coding-agent";
|
|
11
|
-
export { configForSpec, hasProjectConfig, modelSpec, projectModels, readProjectConfig, stacksOf, type ProjectConfig } from "./config.ts";
|
|
11
|
+
export { configForSpec, debugMode, hasProjectConfig, modelSpec, projectModels, readProjectConfig, stacksOf, type ProjectConfig } from "./config.ts";
|
|
12
12
|
export { MODULES_DIR, projectDirOf, resolveTemplateDirs } from "./regenerate/paths.ts";
|
|
13
13
|
export { BEDROCK_DEFAULT, BEDROCK_REGIONS, bedrockModels, initConfig, VENDORS, type BedrockChoice, type Vendor } from "./init-config.ts";
|
|
14
14
|
export { checkPlainFile, type CheckResult } from "./plain/check.ts";
|
|
@@ -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
|
@@ -9,7 +9,7 @@ export { createTurriExtension } from "./extension.js";
|
|
|
9
9
|
/** pi itself, as turri's extension is built against it: a host makes its session with this one, not another copy. */
|
|
10
10
|
export * as pi from "@earendil-works/pi-coding-agent";
|
|
11
11
|
// The project and its settings.
|
|
12
|
-
export { configForSpec, hasProjectConfig, modelSpec, projectModels, readProjectConfig, stacksOf } from "./config.js";
|
|
12
|
+
export { configForSpec, debugMode, hasProjectConfig, modelSpec, projectModels, readProjectConfig, stacksOf } from "./config.js";
|
|
13
13
|
export { MODULES_DIR, projectDirOf, resolveTemplateDirs } from "./regenerate/paths.js";
|
|
14
14
|
// A project's first config.turri.yaml, as `turri init-config` writes it - the vendor whose models it takes chosen by the host.
|
|
15
15
|
export { BEDROCK_DEFAULT, BEDROCK_REGIONS, bedrockModels, initConfig, VENDORS } from "./init-config.js";
|
|
@@ -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,9 @@ export declare const DEFAULTS: {
|
|
|
66
66
|
readonly compactAt: 100000;
|
|
67
67
|
readonly maxSpecsPerPulse: 3;
|
|
68
68
|
readonly maxLinesPerPulse: 120;
|
|
69
|
+
readonly ledgerKeep: 100;
|
|
70
|
+
readonly debug: false;
|
|
71
|
+
readonly cacheKeep: 4096;
|
|
69
72
|
};
|
|
70
73
|
/** How many pulses run at once without a setting: half this machine's cores, one to four - as regenerate/jobs.ts has it. */
|
|
71
74
|
export declare function defaultParallel(cores?: number): number;
|
package/dist/init-config.js
CHANGED
|
@@ -118,6 +118,9 @@ export const DEFAULTS = {
|
|
|
118
118
|
compactAt: 100000,
|
|
119
119
|
maxSpecsPerPulse: 3,
|
|
120
120
|
maxLinesPerPulse: 120,
|
|
121
|
+
ledgerKeep: 100,
|
|
122
|
+
debug: false,
|
|
123
|
+
cacheKeep: 4096,
|
|
121
124
|
};
|
|
122
125
|
/** How many pulses run at once without a setting: half this machine's cores, one to four - as regenerate/jobs.ts has it. */
|
|
123
126
|
export function defaultParallel(cores = availableParallelism()) {
|
|
@@ -183,6 +186,18 @@ export function quickStartConfig(vendor = "other", bedrock) {
|
|
|
183
186
|
"# Folders of this machine an agent's commands may read beside the usual ones - a toolchain somewhere unusual: [/srv/zig].",
|
|
184
187
|
"toolchains: []",
|
|
185
188
|
"",
|
|
189
|
+
"# Megabytes of the ledger - what turri did, as facts, in .turri/ledger, never committed - the project keeps; past it,",
|
|
190
|
+
"# the oldest runs' are pruned. 0 keeps none.",
|
|
191
|
+
`ledger-keep: ${DEFAULTS.ledgerKeep}`,
|
|
192
|
+
"",
|
|
193
|
+
"# Megabytes of package caches - npm's, pip's, Maven's... in plain_modules/.turri/cache - the project keeps; past it,",
|
|
194
|
+
"# the least recently written are removed. 0 keeps none.",
|
|
195
|
+
`cache-keep: ${DEFAULTS.cacheKeep}`,
|
|
196
|
+
"",
|
|
197
|
+
"# Whether turri keeps what it writes for debugging it: every agent's whole log and session, each pulse's log, the",
|
|
198
|
+
"# test runs' outputs, the agents' temporary files. Off, only what turri reads to work is kept. TURRI_DEBUG=1 for one run.",
|
|
199
|
+
`debug: ${DEFAULTS.debug}`,
|
|
200
|
+
"",
|
|
186
201
|
"# The testing scripts, by path from the project root - written here by the skill that writes them. One not named is",
|
|
187
202
|
"# looked for at the root, then in test_scripts/. Each runs the static checks of what it tests - the formatter, the",
|
|
188
203
|
"# 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
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
type AnyTool = ToolDefinition<any, any, any>;
|
|
3
|
+
/**
|
|
4
|
+
* ledger: what turri did, as the ledger recorded it - in this session and every one before it. One pulse, by its id,
|
|
5
|
+
* as facts; the last runs, a line a pulse; or the events of some kinds, counted and listed. Reads only.
|
|
6
|
+
*/
|
|
7
|
+
export declare function ledgerTool(options: {
|
|
8
|
+
cwd: string;
|
|
9
|
+
}): AnyTool;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { describeEvents, describePastPulse, describeRuns, ledgerProjects, runViews, sinceOf } from "./regenerate/ledger-view.js";
|
|
4
|
+
/** How many runs `ledger` lists when it is asked for none. */
|
|
5
|
+
const RUNS_SHOWN = 3;
|
|
6
|
+
/**
|
|
7
|
+
* ledger: what turri did, as the ledger recorded it - in this session and every one before it. One pulse, by its id,
|
|
8
|
+
* as facts; the last runs, a line a pulse; or the events of some kinds, counted and listed. Reads only.
|
|
9
|
+
*/
|
|
10
|
+
export function ledgerTool(options) {
|
|
11
|
+
return defineTool({
|
|
12
|
+
name: "ledger",
|
|
13
|
+
label: "Ledger",
|
|
14
|
+
description: "What turri did here, as its ledger recorded it - facts, not transcripts - in this session and every earlier one. " +
|
|
15
|
+
"With pulse: that pulse, this session's or an earlier one's - how it ended, its rounds and test runs (how many failed " +
|
|
16
|
+
"the same way), the judge's and the reviews' verdicts, the healers (the debugger, the diagnosis) and whether they " +
|
|
17
|
+
"failed, the model provider's trouble, what turri refused and stopped, what it committed or rolled back, its last " +
|
|
18
|
+
"stages and its log. With kinds (and module, since): how many events of those kinds there were, and the most recent, " +
|
|
19
|
+
"a line each - healer.failed, provider.error, script.run, gate.round, judge.verdict, guard.refused, history.failed, " +
|
|
20
|
+
"job.finished, ...; a group by its first part, healer for every healer event. Otherwise: the last runs, a line a pulse - " +
|
|
21
|
+
"how each ended, how long, what it cost. Starts nothing and calls no model.",
|
|
22
|
+
promptSnippet: "ledger: what turri did - a pulse as facts, the last runs, or events counted by kind - this session and earlier ones",
|
|
23
|
+
promptGuidelines: [
|
|
24
|
+
"Use ledger for what turri recorded of the work - a pulse's rounds, test runs, verdicts, healers and commits, this session's or an earlier one's; how often something happened; what earlier runs did and cost - rather than reading .turri/ledger with the shell. pulses is what is running and planned now; a pulse's whole transcript is its log, which ledger names when it is kept (debug mode).",
|
|
25
|
+
],
|
|
26
|
+
parameters: Type.Object({
|
|
27
|
+
pulse: Type.Optional(Type.String({ description: "A pulse's id, or the start of it: that pulse, as the ledger has it" })),
|
|
28
|
+
kinds: Type.Optional(Type.Array(Type.String(), { description: "Event kinds to count and list - healer.failed, or healer for the whole group" })),
|
|
29
|
+
module: Type.Optional(Type.String({ description: "With kinds: only the events of this module" })),
|
|
30
|
+
since: Type.Optional(Type.String({ description: "With kinds: only events since then - 24h, 7d, 30m, or a date and time" })),
|
|
31
|
+
runs: Type.Optional(Type.Number({ description: `Without pulse or kinds: how many of the last runs to list (default ${RUNS_SHOWN})` })),
|
|
32
|
+
limit: Type.Optional(Type.Number({ description: "With kinds: how many of the most recent events to list (default 20)" })),
|
|
33
|
+
}),
|
|
34
|
+
async execute(_id, params) {
|
|
35
|
+
const text = (body, details = {}) => ({ content: [{ type: "text", text: body }], details });
|
|
36
|
+
const projects = ledgerProjects(options.cwd);
|
|
37
|
+
if (projects.length === 0)
|
|
38
|
+
return text("The ledger is empty here: nothing has run in this folder, or in a project under it, since it began.", { found: false });
|
|
39
|
+
if (params.pulse !== undefined) {
|
|
40
|
+
const described = describePastPulse(projects, params.pulse.trim());
|
|
41
|
+
return described !== undefined ? text(described, { found: true }) : text(`The ledger has no pulse ${params.pulse}.`, { found: false });
|
|
42
|
+
}
|
|
43
|
+
if (params.kinds !== undefined && params.kinds.length > 0) {
|
|
44
|
+
let since;
|
|
45
|
+
if (params.since !== undefined) {
|
|
46
|
+
since = sinceOf(params.since);
|
|
47
|
+
if (since === undefined)
|
|
48
|
+
return text(`since takes 24h, 7d, 30m or a date and time, not ${JSON.stringify(params.since)}.`, { found: false });
|
|
49
|
+
}
|
|
50
|
+
return text(describeEvents(projects, {
|
|
51
|
+
kinds: params.kinds,
|
|
52
|
+
...(params.module !== undefined ? { module: params.module } : {}),
|
|
53
|
+
...(since !== undefined ? { since } : {}),
|
|
54
|
+
...(params.limit !== undefined ? { limit: params.limit } : {}),
|
|
55
|
+
}), { found: true });
|
|
56
|
+
}
|
|
57
|
+
const count = params.runs !== undefined && Number.isFinite(params.runs) ? Math.max(1, Math.floor(params.runs)) : RUNS_SHOWN;
|
|
58
|
+
const runs = projects.flatMap((project) => runViews(project)).sort((a, b) => (b.started?.ts ?? "").localeCompare(a.started?.ts ?? ""));
|
|
59
|
+
const shown = runs.slice(0, count);
|
|
60
|
+
const more = runs.length - shown.length;
|
|
61
|
+
return text(`${describeRuns(shown)}${more > 0 ? `\n\n${more} earlier run${more === 1 ? "" : "s"} beyond these: ask with runs for more.` : ""}`, { found: true, runs: shown.length });
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
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.d.ts
CHANGED
|
@@ -184,6 +184,8 @@ export declare function createExecutor(opts: {
|
|
|
184
184
|
rules?: boolean;
|
|
185
185
|
/** Continue the session saved in this file, instead of starting a new one. */
|
|
186
186
|
resumeFrom?: string;
|
|
187
|
+
/** Save its session to disk, debug mode or not: it is picked back up later (a skill's run, resumed). */
|
|
188
|
+
saveSession?: boolean;
|
|
187
189
|
/** Added to its system prompt: its sandbox, above all - what it can reach (see confine.ts sandboxNote). */
|
|
188
190
|
system?: string;
|
|
189
191
|
/**
|