@turri-dev/cli 0.1.3-next.2babf06 → 0.1.3-next.6fab174
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 -1
- package/dist/extension.js +17 -18
- package/dist/identity.js +1 -1
- package/dist/loop-tools.js +1 -1
- package/dist/regenerate/confine.js +1 -0
- package/dist/regenerate/environment-error.js +46 -9
- package/docs/regenerating.md +11 -1
- package/docs/using.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -212,7 +212,7 @@ It runs the interview skill, asks you about the problem, the stack and the produ
|
|
|
212
212
|
model and cost - and the queue behind it;
|
|
213
213
|
- ask to try it at any time - "let me try it" - and **`release`** puts what is built so far in
|
|
214
214
|
`dist/<module>` and **`activate`** starts it, installing what it needs - nothing to approve;
|
|
215
|
-
- the **side panel** (`ctrl+h`) takes the screen and opens on **regen**
|
|
215
|
+
- 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/`;
|
|
216
216
|
- **`/halt`** halts a pulse, **`/lineage`** shows every generation of a
|
|
217
217
|
module, and the **`drift`** tool says what is left to regenerate;
|
|
218
218
|
- **`/btw <question>`** asks turri something on the side - answered at once, even in the middle of a skill's interview
|
package/dist/extension.js
CHANGED
|
@@ -1695,22 +1695,17 @@ function registerJobTools(pi, jobs, cwd) {
|
|
|
1695
1695
|
},
|
|
1696
1696
|
});
|
|
1697
1697
|
};
|
|
1698
|
-
//
|
|
1699
|
-
//
|
|
1700
|
-
// in each pulse's log.
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
return;
|
|
1710
|
-
openPanel(ctx, view);
|
|
1711
|
-
},
|
|
1712
|
-
});
|
|
1713
|
-
}
|
|
1698
|
+
// /pulses opens the side panel on every pulse, regenerating, planned and finished, and what each regenerates; regen -
|
|
1699
|
+
// the spec, with how far the code has been regenerated into it - is PANEL_KEY's, no command of its own beside
|
|
1700
|
+
// /regenerate. What the agents did is in each pulse's log.
|
|
1701
|
+
pi.registerCommand("pulses", {
|
|
1702
|
+
description: `The pulses, in the side panel: every pulse, regenerating, planned and finished, and what each regenerates. ${PANEL_KEY} opens the panel too.`,
|
|
1703
|
+
handler: async (_args, ctx) => {
|
|
1704
|
+
if (!ctx.hasUI)
|
|
1705
|
+
return;
|
|
1706
|
+
openPanel(ctx, "pulses");
|
|
1707
|
+
},
|
|
1708
|
+
});
|
|
1714
1709
|
panelOpeners.set(jobs, (ctx) => openPanel(ctx));
|
|
1715
1710
|
questionPeek = {
|
|
1716
1711
|
open: (ctx) => openPanel(ctx),
|
|
@@ -1785,8 +1780,12 @@ export const JOB_FINISHED_REACTION = [
|
|
|
1785
1780
|
"A pulse that stopped on its environment - \"stopped: turri's sandbox kept its agents from…\" or \"stopped: the",
|
|
1786
1781
|
"environment lacks…\" - stops the same way again until that is fixed: never regenerate it before. Heal it, then regenerate the",
|
|
1787
1782
|
"module to resume:",
|
|
1788
|
-
"- turri's sandbox kept the agents from a folder on this machine: nothing is missing. Add the folder its report
|
|
1789
|
-
" `toolchains:` in config.turri.yaml - a list, keeping what is there - say so in a line, and resume.",
|
|
1783
|
+
"- turri's sandbox kept the agents from reading a folder on this machine: nothing is missing. Add the folder its report",
|
|
1784
|
+
" names to `toolchains:` in config.turri.yaml - a list, keeping what is there - say so in a line, and resume.",
|
|
1785
|
+
"- turri's sandbox kept the agents from writing a file - a lock a build tool takes, say: no setting lets them, and",
|
|
1786
|
+
" `toolchains:` does not - never add it there, and never resume before the fix. Do what the report says if it is the",
|
|
1787
|
+
" project's to change - a tool told to use `$HOME` in the build's own files - say so, and resume; otherwise tell the",
|
|
1788
|
+
" person, in a line or two, what the report says and that \"resume\" picks it up once it is done.",
|
|
1790
1789
|
"- the machine lacks a toolchain or a tool - a JDK, Maven, Gradle, Go, uv, Node, a system library: first find out, in your",
|
|
1791
1790
|
" shell, whether it is installed somewhere off the PATH (`command -v`, Homebrew's opt folders, /Library/Java/",
|
|
1792
1791
|
" JavaVirtualMachines, a version manager the person uses - mise, asdf, sdkman, nvm). Then ask the person with ask_user",
|
package/dist/identity.js
CHANGED
|
@@ -25,7 +25,7 @@ export const TURRI_IDENTITY = [
|
|
|
25
25
|
"- A pulse is one unit of work regenerating the code - a spec diff regenerated in a codebase, or its conformance",
|
|
26
26
|
" tests. It was called a job, and still is in places: the job_id a tool takes is a pulse's id, and parallel-jobs and",
|
|
27
27
|
" allow-to-stop-jobs in config.turri.yaml are about pulses. \"Job\" in what you read is a pulse; say pulse.",
|
|
28
|
-
"- regen is the side panel (
|
|
28
|
+
"- regen is the side panel (ctrl+h): the spec with each functionality and item marked built, regenerating,",
|
|
29
29
|
" planned or waiting - and behind it the pulses (/pulses), every pulse regenerating, planned and finished. Say regen,",
|
|
30
30
|
" lowercase. It was the regeneration, the job queue, or the job panel.",
|
|
31
31
|
"- mutate, in a skill's activity, is write_spec_diff: a spec change written and regenerated in one step.",
|
package/dist/loop-tools.js
CHANGED
|
@@ -73,7 +73,7 @@ export async function loopReply(loop, statuses, wait) {
|
|
|
73
73
|
export function stopJob(jobs, named, reason) {
|
|
74
74
|
const matches = jobs.list().filter((job) => job.id.startsWith(named.trim()));
|
|
75
75
|
if (matches.length === 0)
|
|
76
|
-
return `No pulse ${named}. /
|
|
76
|
+
return `No pulse ${named}. /pulses shows them.`;
|
|
77
77
|
if (matches.length > 1)
|
|
78
78
|
return `'${named}' matches ${matches.map((job) => job.id).join(", ")}. Give more of the id.`;
|
|
79
79
|
const target = matches[0];
|
|
@@ -188,6 +188,7 @@ export function sandboxNote(fence) {
|
|
|
188
188
|
const report = fence.environmentTool !== undefined
|
|
189
189
|
? `report it as an environment error with ${fence.environmentTool}`
|
|
190
190
|
: "say so plainly - what is missing and what needs it - and end your turn";
|
|
191
|
+
lines.push("- A command refused a file outside your folder - `Operation not permitted`, `Permission denied`, a lock it could not", ` take - is these walls, not the code, and nothing you change gets past them: ${report} at once, naming the file`, " and the command that wanted it.");
|
|
191
192
|
lines.push(fence.write.length > 0
|
|
192
193
|
? "- The project's dependencies are yours: declare them in the build manifest, and let the build tools fetch them."
|
|
193
194
|
: "- You install nothing.", `${fence.write.length > 0 ? " Installing anything else is not" : " Nor is installing anything on the machine yours"}: no toolchains, system packages, drivers, global packages or \`sudo\`. If the`, ` machine lacks something the code needs, ${report}, name what is missing, and go no further with what depends on it.`, " The person decides.", "- Ports and processes: you may stop only what you started. If a port is taken by something else, use another port.", " `ps` may show nothing but what you started: check a server you started with `kill -0 $!`, its log, or a request to it.", "- Temporary files go in `$TMPDIR`: `/tmp` is outside your walls.");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { basename, dirname, isAbsolute, sep } from "node:path";
|
|
3
4
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
4
5
|
import { Type } from "typebox";
|
|
@@ -35,7 +36,7 @@ export class EnvironmentError extends Error {
|
|
|
35
36
|
sandbox;
|
|
36
37
|
constructor(details) {
|
|
37
38
|
super(details.sandbox !== undefined
|
|
38
|
-
? `${details.moduleName} stopped: turri's sandbox kept its agents from ${details.sandbox.paths[0]}, which is on this machine.`
|
|
39
|
+
? `${details.moduleName} stopped: turri's sandbox kept its agents from ${details.sandbox.access === "write" ? "writing" : "reading"} ${details.sandbox.paths[0]}, which is on this machine.`
|
|
39
40
|
: `${details.moduleName} stopped: the environment lacks ${details.missing}.`);
|
|
40
41
|
this.name = "EnvironmentError";
|
|
41
42
|
this.moduleName = details.moduleName;
|
|
@@ -50,6 +51,10 @@ export class EnvironmentError extends Error {
|
|
|
50
51
|
this.sandbox = details.sandbox;
|
|
51
52
|
}
|
|
52
53
|
}
|
|
54
|
+
/** A file's name that says it is a lock: `lockfile`, `x.lock`, `x.lck`, `.lock-x`, `x.lock.y`. */
|
|
55
|
+
const LOCK_FILE = /(?:^|[._-])(?:lock|lck|lockfile)(?:$|[._-])/i;
|
|
56
|
+
/** What says the sandbox refused a write, not a read: writing, creating, a lock not taken - not a name with "lock" in it. */
|
|
57
|
+
const WRITE_REFUSED = /\b(?:writ(?:e|es|ing|able)|creat(?:e|es|ing)|acquir(?:e|ing)|(?:take|obtain|get) (?:a |the )?lock|locking|read-only file system)\b/i;
|
|
53
58
|
/** A folder named for a version - `24.16.0`, `v22.3.0`, `3.12.4` - the root of a version manager's install. */
|
|
54
59
|
const VERSION = /^v?\d+\.\d+/;
|
|
55
60
|
/** The parts of an install below its root, where the root ends: `…/24.16.0/lib/node_modules/…` ends before `lib`. */
|
|
@@ -74,27 +79,59 @@ export function sandboxGapOf(text, options = {}) {
|
|
|
74
79
|
const system = (options.platform ?? process.platform) === "darwin" ? MACOS_MACHINE : LINUX_MACHINE;
|
|
75
80
|
const within = (path, root) => path === root || path.startsWith(`${root}/`);
|
|
76
81
|
const named = [...text.matchAll(/(?:^|[\s'"`(=:])(\/[^\s'"`()<>,;]+)/g)].map((match) => match[1].replace(/[.:]+$/, ""));
|
|
77
|
-
const
|
|
78
|
-
|
|
82
|
+
const isLock = (path) => LOCK_FILE.test(basename(path));
|
|
83
|
+
// A lock file the tool could not create is not there: its folder is.
|
|
84
|
+
const found = [...new Set(named)].filter((path) => path.split("/").length > 3 &&
|
|
85
|
+
(exists(path) || (isLock(path) && exists(dirname(path)))) &&
|
|
86
|
+
!system.some((root) => within(path, root)) &&
|
|
87
|
+
(options.project === undefined || !within(path, options.project)));
|
|
88
|
+
if (found.length === 0)
|
|
89
|
+
return undefined;
|
|
90
|
+
// The lock first: it is what the build could not take.
|
|
91
|
+
const paths = [...found.filter(isLock), ...found.filter((path) => !isLock(path))];
|
|
92
|
+
const lock = isLock(paths[0]);
|
|
93
|
+
return { paths, folder: toolchainFolder(paths[0]), access: lock || WRITE_REFUSED.test(text) ? "write" : "read", ...(lock ? { lock } : {}) };
|
|
79
94
|
}
|
|
80
95
|
/**
|
|
81
96
|
* What the person is told, in the chat, at the end of `turri regenerate`, and in the job's log. `rerun`: the command that runs
|
|
82
97
|
* the work again, as the pulse knows it - the spec the person ran; without it, the module's own spec by its name.
|
|
83
98
|
*/
|
|
84
99
|
export function describeEnvironmentError(error, rerun) {
|
|
85
|
-
|
|
100
|
+
const again = `Then re-run: \`${rerun ?? `turri regenerate ${error.moduleName}.plain`}\`, or say "resume" in the chat.`;
|
|
101
|
+
const needed = `Needed by: ${error.neededBy}${error.shownBy !== undefined ? ` (${error.shownBy})` : ""}.`;
|
|
102
|
+
const gap = error.sandbox;
|
|
103
|
+
if (gap?.access === "write") {
|
|
104
|
+
const what = gap.lock === true ? `the lock file \`${gap.paths[0]}\`, which the build takes as it runs` : `\`${gap.paths[0]}\``;
|
|
105
|
+
const home = homedir();
|
|
106
|
+
const inHome = gap.paths[0] === home || gap.paths[0].startsWith(`${home}/`);
|
|
107
|
+
return [
|
|
108
|
+
`**${error.moduleName} stopped: turri's sandbox kept its agents from writing ${what} - it is on this machine; nothing is missing, and nothing needs installing.**`,
|
|
109
|
+
needed,
|
|
110
|
+
"An agent's commands write only their module's folders, their own home and temporary folder, and the package caches turri " +
|
|
111
|
+
"points the build tools at - no setting lets them write anywhere else, and `toolchains:` lets them read a folder, not write it.",
|
|
112
|
+
inHome
|
|
113
|
+
? "To fix: it is in your home, not the agents' own - the tool found your home without `$HOME`, as a JVM does for `user.home`, " +
|
|
114
|
+
"or a setting of yours points it there. Have it use `$HOME` instead - for a JVM tool, `-Duser.home=$HOME` where the build " +
|
|
115
|
+
"starts its JVM - and the agents' own home takes it."
|
|
116
|
+
: `To fix: it is in a toolchain's own install, \`${gap.folder}\`, which the build writes into as it runs - and the agents may ` +
|
|
117
|
+
"not change a toolchain. Have the build keep it elsewhere if the tool has a setting for it, or install a copy of the " +
|
|
118
|
+
"toolchain that keeps its writes in its cache.",
|
|
119
|
+
again,
|
|
120
|
+
].join("\n");
|
|
121
|
+
}
|
|
122
|
+
if (gap !== undefined) {
|
|
86
123
|
return [
|
|
87
|
-
`**${error.moduleName} stopped: turri's sandbox kept its agents from reading \`${
|
|
88
|
-
|
|
89
|
-
`To fix: let the agents read that toolchain - add its folder to \`toolchains:\` in config.yaml, keeping what is there: \`toolchains: [${
|
|
90
|
-
|
|
124
|
+
`**${error.moduleName} stopped: turri's sandbox kept its agents from reading \`${gap.paths[0]}\` - it is on this machine; nothing is missing.**`,
|
|
125
|
+
needed,
|
|
126
|
+
`To fix: let the agents read that toolchain - add its folder to \`toolchains:\` in config.turri.yaml, keeping what is there: \`toolchains: [${gap.folder}]\`. Nothing needs installing.`,
|
|
127
|
+
again,
|
|
91
128
|
].join("\n");
|
|
92
129
|
}
|
|
93
130
|
return [
|
|
94
131
|
`**${error.moduleName} stopped: the environment lacks ${error.missing}.**`,
|
|
95
132
|
`Needed by: ${error.neededBy}${error.shownBy !== undefined ? ` (${error.shownBy})` : ""}.`,
|
|
96
133
|
`To fix: ${error.fix ?? "provide it on this machine, then run it again."}`,
|
|
97
|
-
|
|
134
|
+
again,
|
|
98
135
|
].join("\n");
|
|
99
136
|
}
|
|
100
137
|
/** The name agents know the tool by - their wall text names it. */
|
package/docs/regenerating.md
CHANGED
|
@@ -577,10 +577,20 @@ not an install:
|
|
|
577
577
|
> **lunch stopped: turri's sandbox kept its agents from reading `…/mise/installs/node/24.16.0/lib/node_modules/npm/bin/npm-cli.js` - it is on this machine; nothing is missing.**
|
|
578
578
|
> To fix: let the agents read that toolchain - add its folder to `toolchains:` in config.turri.yaml, keeping what is there: `toolchains: [/Users/…/mise/installs/node/24.16.0]`. Nothing needs installing.
|
|
579
579
|
|
|
580
|
+
A write the sandbox refused is told apart from a read - a report naming a lock file (`*.lock`, `*.lck`, `lockfile`, even
|
|
581
|
+
one never made), or saying a write, a create or a lock not taken. No setting lets the agents write outside their
|
|
582
|
+
module's folders, their own home and temporary folder and the package caches, and `toolchains:` only lets them read; so
|
|
583
|
+
the stop names the lock and what fixes it instead. In your home - a tool that found it without `$HOME`, as a JVM does for
|
|
584
|
+
`user.home` - it is having the tool use `$HOME` (`-Duser.home=$HOME` where the build starts its JVM); in a toolchain's own
|
|
585
|
+
install, it is a setting of the tool's that keeps the write elsewhere, or a copy of the toolchain that keeps its writes in
|
|
586
|
+
its cache. The agents are told such a refusal is the sandbox's, to report at once - not to work around it for half an
|
|
587
|
+
hour.
|
|
588
|
+
|
|
580
589
|
**The conversation heals it.** Told of the stop, it never regenerates the module again before the cause is fixed - a pulse
|
|
581
590
|
stopped on its environment stops the same way again - and fixes what it can:
|
|
582
591
|
|
|
583
|
-
- **hidden by the sandbox**: it adds the folder to `toolchains:` itself, says so in a line, and resumes
|
|
592
|
+
- **hidden by the sandbox**: it adds the folder to `toolchains:` itself, says so in a line, and resumes - a refused
|
|
593
|
+
write, which no setting fixes, it fixes in the project's own files when it can, and otherwise tells you what to do;
|
|
584
594
|
- **a toolchain or a tool missing** - a JDK, Maven, Go, uv: it looks first for one installed off the PATH - Homebrew's opt
|
|
585
595
|
folders, `/Library/Java/JavaVirtualMachines`, mise, asdf, sdkman, nvm - then asks you, with the exact command for this
|
|
586
596
|
machine (`brew install openjdk@21`, `mise use -g java@21`), and on your yes runs it in its shell, checks the command is
|
package/docs/using.md
CHANGED
|
@@ -127,7 +127,7 @@ come waits for a free slot, and says so.
|
|
|
127
127
|
as GitHub names a hunk's function: each section is named once.
|
|
128
128
|
- **The side panel** (`ctrl+h`, as it was left - but on the pulse that last failed, stopped or did not pass, when one has
|
|
129
129
|
since it was last opened: the live panel that showed it is gone with the last pulse, and its announcement in the chat
|
|
130
|
-
says `ctrl+h shows this pulse`; **`/
|
|
130
|
+
says `ctrl+h shows this pulse`; **`/pulses`** on the pulses; not `ctrl+j`, which a terminal without the Kitty
|
|
131
131
|
keyboard protocol sends as a line feed, the same as `shift+enter`, and which a dialog takes for
|
|
132
132
|
`enter`; both stay pi's new-line keys. `ctrl+h` is the byte old terminals sent for backspace; a
|
|
133
133
|
terminal set to send `^H` for its Backspace key would open the panel on it) takes the whole screen - the panel or the chat, one at a time -
|