@turri-dev/cli 0.1.16 → 0.1.17-next.63f454c
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/ask.d.ts +2 -0
- package/dist/ask.js +2 -0
- package/dist/chat-commands.d.ts +25 -0
- package/dist/chat-commands.js +162 -0
- package/dist/chat-records.d.ts +44 -0
- package/dist/chat-records.js +102 -0
- package/dist/chat-scope.d.ts +14 -0
- package/dist/chat-scope.js +15 -0
- package/dist/chat-terminal.d.ts +23 -0
- package/dist/chat-terminal.js +163 -0
- package/dist/chat-ui.d.ts +33 -0
- package/dist/chat-ui.js +192 -0
- package/dist/chats.d.ts +192 -0
- package/dist/chats.js +378 -0
- package/dist/cli.js +5 -7
- package/dist/config.d.ts +8 -2
- package/dist/config.js +18 -4
- package/dist/extension.d.ts +20 -33
- package/dist/extension.js +427 -272
- package/dist/genome.d.ts +13 -7
- package/dist/genome.js +13 -3
- package/dist/host-session.d.ts +58 -0
- package/dist/host-session.js +72 -0
- package/dist/host.d.ts +5 -4
- package/dist/host.js +6 -5
- package/dist/identity.js +5 -0
- package/dist/pi-launch.js +23 -1
- package/dist/question-dialog.d.ts +4 -0
- package/dist/question-dialog.js +13 -2
- package/dist/regenerate/activity.d.ts +8 -1
- package/dist/regenerate/activity.js +23 -2
- package/dist/regenerate/jobs.d.ts +2 -0
- package/dist/regenerate/jobs.js +3 -0
- package/dist/regenerate/ledger.d.ts +7 -0
- package/dist/regenerate/ledger.js +8 -1
- package/dist/regenerate/test-scripts.d.ts +1 -1
- package/dist/stacks.d.ts +2 -1
- package/dist/stacks.js +4 -3
- package/docs/README.md +4 -2
- package/docs/chats.md +75 -0
- package/docs/config.md +3 -3
- package/docs/development.md +4 -1
- package/docs/hosting.md +37 -28
- package/docs/skills-and-gates.md +2 -2
- package/docs/using.md +2 -2
- package/package.json +1 -1
package/docs/chats.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Chats
|
|
2
|
+
|
|
3
|
+
One `turri` is one session on one project, and a session holds up to nine chats: conversations with turri side by side, each working while you look at another. Ask one chat to regenerate the backend and open another to talk the frontend's \*\*\*plain through; the first goes on with its turn, its tools and its pulses meanwhile.
|
|
4
|
+
|
|
5
|
+
## What each chat keeps, and what they share
|
|
6
|
+
|
|
7
|
+
Each chat has its own:
|
|
8
|
+
|
|
9
|
+
- **history** - what was said in it, and nothing said in another; a chat asked about another's says so and asks;
|
|
10
|
+
- **turns** - a hidden chat keeps working, and starts a turn of its own when a pulse it asked for ends;
|
|
11
|
+
- **model and thinking level** - [pi](https://pi.dev)'s `/model`, and `/turri-config harness-model` in that chat;
|
|
12
|
+
- **stack** - `/stack` and the chat's `stack` tool choose it for that chat alone; a new chat starts with `--stack`'s, or none (see [stacks](config.md#stacks));
|
|
13
|
+
- **questions** - what it asks waits in it until you look at it.
|
|
14
|
+
|
|
15
|
+
The session's, shared by every chat:
|
|
16
|
+
|
|
17
|
+
- **the pulses and their plan** - one job registry and one work loop on the project: a pulse any chat starts is in every chat's side panel and `pulses`, and two chats asking for the same module get one plan, not two;
|
|
18
|
+
- **the coding model** chosen with `engine` or `--coding-model`, and `parallel-jobs`;
|
|
19
|
+
- **steering** - what `steer` was told, every plan follows;
|
|
20
|
+
- **conformance** chosen with `conform`, module by module;
|
|
21
|
+
- **what `activate` started** - it runs until turri ends or `/halt activated <name>`, whatever chats are opened or closed;
|
|
22
|
+
- **the ledger** - one run, each event with the id of the chat its work was asked for in (`chat`), and `chat.opened` and `chat.closed` as they happen.
|
|
23
|
+
|
|
24
|
+
A pulse's end is told to the chat that asked for it, shown or not - its agent reacts to it there; the chat's mark in the strip says it has something new. When that chat has been closed, the chat shown is told. Closing a chat stops its turn, never its pulses.
|
|
25
|
+
|
|
26
|
+
## In the terminal
|
|
27
|
+
|
|
28
|
+
| | |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `/chat new`, `alt+n` | a fresh chat, with the session's settings and an empty history, shown at once |
|
|
31
|
+
| `/chat 2`, `alt+2` | chat 2, shown - `alt+1` … `alt+9` |
|
|
32
|
+
| `/chat close`, `alt+w` | the chat shown, closed - and the next one shown; `/chat close 3` closes chat 3 |
|
|
33
|
+
| `/chat` | a list of the chats, to pick one - or a new one |
|
|
34
|
+
| `/new` | the same as `/chat new` |
|
|
35
|
+
| `/resume` | a saved chat, opened as a chat of its own beside the others - or shown, when it is open |
|
|
36
|
+
| `ctrl+c` | what the chat shown is doing, stopped - its turn, a skill or a tool in it, a `!` command, a compaction; in a chat doing nothing, a draft in the editor cleared, else turri left, every chat with it |
|
|
37
|
+
|
|
38
|
+
A line above the editor names each chat once there are two, a `│` between them: its number, its title - "new chat" until something is said in it, then its first message, until `/name` gives it one - and, after it, what it is doing: a spinner while it works, `?` while a question waits in it, `•` when it said something since you last looked. The chat shown is in the accent colour.
|
|
39
|
+
|
|
40
|
+
On macOS, `alt` is Option (⌥), and reaches turri only when the terminal sends Option as Meta - otherwise ⌥N is the dead key for ñ and ⌥1 types `¡`, and nothing reaches turri. turri says so as a session's second chat opens. To turn it on: Ghostty, `macos-option-as-alt = true` in its config; iTerm2, Profiles → Keys → Left Option key: Esc+; Terminal.app, Profiles → Keyboard → Use Option as Meta key; Kitty, `macos_option_as_alt yes`; WezTerm, `send_composed_key_when_left_alt_is_pressed = false`. `/chat` works without it.
|
|
41
|
+
|
|
42
|
+
- **A hidden chat's questions** wait until you show it, then come up one after another. What it says in passing is said as you show it; what it keeps on the screen - a skill's progress, the job panel - is put back each time; what you had typed in its editor and not sent is still there.
|
|
43
|
+
- **The side panel** (`ctrl+h`) is the session's, not a chat's: docked beside the chat or over it, it stays as another chat is shown, and opening it from any chat finds it as it was left.
|
|
44
|
+
- **A long command** - `/regenerate`, planning a module from scratch - does its work in the background once it is under way: the chat, and switching to another, stay free while the planner plans.
|
|
45
|
+
- **`ctrl+c`** reaches the chat shown and nothing else: another chat's turn and skill go on, and so do the pulses - `/halt` stops one. A skill stopped keeps its run, for the model to continue with `resume`. At a question - the chat's `ask_user`, a skill's interview - `ctrl+c` declines it, and the asker is told so and goes on - each question it is pressed at, however many in a row. A skill is stopped by `ctrl+c` with no question open. A second press straight after one that stopped something does nothing, so pressing twice to be sure does not leave turri; once the chat is doing nothing, the next one does - with what every other chat is doing. A host has the same as `interrupt(id)` ([hosting](hosting.md)).
|
|
46
|
+
- **The last chat** is not closed: `/quit` leaves turri.
|
|
47
|
+
- **`/fork`** and **`/reload`** work on the chat shown, as [pi](https://pi.dev)'s do: the chat's history forked, or turri's extension rebuilt in it.
|
|
48
|
+
- **A chat shown mid-turn** draws its reply as far as it is written, and follows it from there.
|
|
49
|
+
|
|
50
|
+
## Coming back
|
|
51
|
+
|
|
52
|
+
`turri --continue` (`-c`) opens every chat of the last session here again, in their order - the first shown, the others beside it. A plain `turri` starts with one fresh chat and deletes nothing: the chats of the sessions before stay on disk, and `--continue` opens the most recent one's - the last session that recorded a chat.
|
|
53
|
+
|
|
54
|
+
turri records which chats are open in `.turri/chats.json` - a run a session, each chat with its title and [pi](https://pi.dev)'s saved conversation - and keeps it from piling up:
|
|
55
|
+
|
|
56
|
+
- **a chat closed** is gone: out of the record, its conversation deleted - it cannot be resumed;
|
|
57
|
+
- **a chat nothing was said in** is never recorded;
|
|
58
|
+
- **a session's chats** are kept until five newer sessions have recorded theirs, then deleted with their record;
|
|
59
|
+
- a session still running is never reopened by another turri.
|
|
60
|
+
|
|
61
|
+
In [debug mode](config.md#debug-mode) nothing is deleted. The `engine`'s choices come back too: the coding model a reopened chat chose, when it is newer than the session's.
|
|
62
|
+
|
|
63
|
+
## Where there is one chat
|
|
64
|
+
|
|
65
|
+
[pi](https://pi.dev)'s print, json and rpc modes - `turri -p "…"`, `--mode json`, `--mode rpc` - and a `turri` given its input on stdin have one chat and record none. So does `npm run chat`, pi with turri's extension loaded from its file. The command line's `turri regenerate`, `conform` and the rest have no chat.
|
|
66
|
+
|
|
67
|
+
## In a host
|
|
68
|
+
|
|
69
|
+
A host - turri's desktop app - runs the same session with `createTurri`, opening, closing and showing chats in its own windows: see [running turri in a host](hosting.md#the-session-in-a-host).
|
|
70
|
+
|
|
71
|
+
## How it works
|
|
72
|
+
|
|
73
|
+
`src/chats.ts` is the session: its chats, their status, which chat a pulse is reported to, and the record (`src/chat-records.ts`). turri's extension is built into each chat's [pi](https://pi.dev) session and links itself to its chat; every tool, command and handler it registers runs inside the chat's scope (`src/chat-scope.ts`), which carries the chat's id - onto the pulses it queues and the ledger's events - and its stack.
|
|
74
|
+
|
|
75
|
+
In the terminal (`src/chat-terminal.ts`), [pi](https://pi.dev)'s screen shows one session at a time, and replaces it - disposing the old one - on `/new` and `/resume`. turri instead puts the chosen chat's live session in pi's runtime and has the screen drawn from it again, nothing disposed. A hidden chat draws on a stand-in for the screen (`src/chat-ui.ts`) that holds its dialogs, notices and widgets until it is shown. pi has no hooks for this: its classes are patched where `pi-launch.ts` starts it, and `tests/chat-terminal.test.ts` checks they still have what is patched.
|
package/docs/config.md
CHANGED
|
@@ -241,11 +241,11 @@ coding-model:
|
|
|
241
241
|
|
|
242
242
|
Which stack builds a module, first match wins:
|
|
243
243
|
|
|
244
|
-
- **The one a call names.** `regenerate`, `conform`, `glimpse` and `change_plain` take a `stack`; the chat's model passes it for a module whose stack has a name of its own, leaving the
|
|
244
|
+
- **The one a call names.** `regenerate`, `conform`, `glimpse` and `change_plain` take a `stack`; the chat's model passes it for a module whose stack has a name of its own, leaving the chat's choice as it is.
|
|
245
245
|
- **The stack named after the module** - `config.turri.backend.yaml` builds `backend.plain` - with nothing to choose or pass. Name each stack after the module it builds, and a backend and a frontend are each built with their own, side by side - in the chat, in a brood as each change is written, and on the command line.
|
|
246
|
-
- **The one chosen
|
|
246
|
+
- **The one chosen in the chat** - each [chat](chats.md) its own - with `/stack` - a list of the project's stacks and none - or `/stack backend`, `/stack none`. The chat's model has a `stack` tool too, and switches the stack when you ask it to. `/stack` alone says which is chosen now. On the command line, with `--stack <name>` on any command - `turri regenerate`, `conform`, `change-plain`, `glimpse`, `release`, `cultivate` - and on `turri --stack backend` for the chat; or with `TURRI_STACK` in the environment.
|
|
247
247
|
|
|
248
|
-
|
|
248
|
+
A chat's choice is its own, and lasts while it is open: a new chat starts with `--stack`'s, or none, and nothing remembers it - turri started again has none chosen. A stack the project has no file for is refused, with the stacks it has. With none chosen, `config.turri.yaml` alone is used.
|
|
249
249
|
|
|
250
250
|
How a stack's config is made:
|
|
251
251
|
|
package/docs/development.md
CHANGED
|
@@ -33,7 +33,10 @@ The workflow signs in to npm with npm's trusted publishing (OIDC), not a token:
|
|
|
33
33
|
| `bin/turri.mjs` | the launcher: checks the Node version, then loads `src/cli.ts` |
|
|
34
34
|
| `src/cli.ts` | the `turri` command and its subcommands |
|
|
35
35
|
| `src/setup.ts`, `src/doctor-tool.ts`, `src/environment.ts`, `src/stack.ts` | `turri doctor` and the chat's `doctor` tool, and what they check - this machine, and in a project what its modules need of it |
|
|
36
|
-
| `src/extension.ts` | the [pi](https://pi.dev) extension: every tool and command the conversation has |
|
|
36
|
+
| `src/extension.ts` | the [pi](https://pi.dev) extension: every tool and command the conversation has - and `createTurriCore`, the session it is built into |
|
|
37
|
+
| `src/chats.ts`, `src/chat-scope.ts`, `src/chat-records.ts` | the session's [chats](chats.md): which chat a pulse is told to, each chat's scope - its id and stack - and `.turri/chats.json` |
|
|
38
|
+
| `src/chat-terminal.ts`, `src/chat-ui.ts`, `src/chat-commands.ts` | the terminal's chats: [pi](https://pi.dev)'s screen switched between live sessions, a hidden chat's stand-in for it, `/chat` and its keys |
|
|
39
|
+
| `src/host-session.ts` | `createTurri`, the session for a host |
|
|
37
40
|
| `src/skill.ts` | parses `SKILL.md` into steps and gates |
|
|
38
41
|
| `src/graph.ts` | compiles a skill into a LangGraph `StateGraph`: step -> gate -> route |
|
|
39
42
|
| `src/judge.ts` | runs independent judges, applies the quorum, builds the critique |
|
package/docs/hosting.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Running turri in a host
|
|
2
2
|
|
|
3
|
-
`turri` runs its
|
|
3
|
+
`turri` runs its chats in a terminal. A host - a desktop app, an editor - can run the same session itself, its chats in its own windows, and show what turri's terminal would draw in its own way: the jobs, the questions, the \*\*\*plain description. It imports turri from npm as `@turri-dev/cli/host`.
|
|
4
4
|
|
|
5
5
|
## What the host entry gives
|
|
6
6
|
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
| export | what it is |
|
|
10
10
|
| --- | --- |
|
|
11
|
-
| `
|
|
12
|
-
| `
|
|
11
|
+
| `createTurri(options)` | a turri session: its [chats](chats.md), each a [pi](https://pi.dev) session with every tool, slash command, rule and skill the terminal has, on one job registry and work loop. The host opens, closes and shows them, is told of their jobs and asks their questions - see [the session in a host](#the-session-in-a-host). |
|
|
12
|
+
| `MAX_CHATS`, `ChatInfo`, `ChatStatus` | the most chats a session has open - nine - and what the host is told of each. |
|
|
13
|
+
| `pi` | [pi](https://pi.dev) itself, as turri is built against it. Make the host's own extensions and UI with this one, not another copy. |
|
|
13
14
|
| `readProjectConfig`, `projectModels`, `modelSpec`, `configForSpec`, `hasProjectConfig`, `stacksOf` | the project's settings, as [config.turri.yaml](config.md) has them. |
|
|
14
15
|
| `configSettings`, `currentValue`, `readConfigText`, `overriddenBy`, `EFFECT_TEXT` | every setting of config.turri.yaml as [/turri-config](config.md#changing-it-from-the-chat) knows it - its key, what it does, its kind (a yes or no, one of a few words, a number, a model, a list, a path, a budget), its words or the values offered first, what it is unset and when a change takes effect - and what the file has now (`currentValue(readConfigText(dir), key)`), and what wins over it. Change one as the person would, by sending `/turri-config <key> <value>` to the chat: turri checks it and writes it with the file's comments kept. |
|
|
15
16
|
| `debugMode` | whether turri keeps, in a project, what it writes only for debugging it - `debug:`, or `TURRI_DEBUG` over it (see [debug mode](config.md#debug-mode)). |
|
|
@@ -67,36 +68,44 @@ const checks = await doctor({ dir: project, env: engineEnv });
|
|
|
67
68
|
|
|
68
69
|
What matters only in a terminal - `turri` on the PATH, a plain `pi` an older turri made load turri - is left to `turri doctor`.
|
|
69
70
|
|
|
70
|
-
## The
|
|
71
|
+
## The session in a host
|
|
71
72
|
|
|
72
73
|
```js
|
|
73
|
-
import {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
surface: SURFACE, // the host's UI, in its own words, told to the model in place of the terminal's
|
|
86
|
-
systemPrompt: (prompt) => prompt, // the last word on the whole system prompt, each turn
|
|
87
|
-
}),
|
|
88
|
-
],
|
|
74
|
+
import { createTurri, pi } from "@turri-dev/cli/host";
|
|
75
|
+
|
|
76
|
+
process.chdir(project); // the session is the project's in the process's working folder
|
|
77
|
+
const turri = await createTurri({
|
|
78
|
+
onJobChange: (job) => {}, // a job started, moved on, did something, or ended - any chat's
|
|
79
|
+
onPlanChange: () => {}, // what is planned changed with no job
|
|
80
|
+
dialogs: (chat) => ({ select, input }), // a chat's questions, put in the host's own dialogs
|
|
81
|
+
uiContext: (chat) => uiContext, // a chat's other UI calls - notices, the working line
|
|
82
|
+
surface: SURFACE, // the host's UI, in its own words, told to the model in place of the terminal's
|
|
83
|
+
systemPrompt: (prompt) => prompt, // the last word on the whole system prompt, each turn
|
|
84
|
+
extensions: [browser, terminal], // the host's own extensions, built into every chat after turri's
|
|
85
|
+
restore: true, // reopen the last session's chats here, as `turri --continue` does
|
|
89
86
|
});
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
87
|
+
|
|
88
|
+
const chat = await turri.openChat(); // { id, title, status, shown, session }: a fresh chat, shown
|
|
89
|
+
chat.session.subscribe((event) => {}); // pi's session: its events, prompt(), abort()
|
|
90
|
+
await chat.session.prompt("add a cart to the shop");
|
|
91
|
+
turri.focus(other.id); // the chat the person looks at now
|
|
92
|
+
await turri.interrupt(chat.id); // ctrl+c: "stopped" what it was doing, else "ended" the session - "ignored", "missing"
|
|
93
|
+
turri.chats(); // [{ id, title, status: "idle" | "working" | "question" | "unread", shown }]
|
|
94
|
+
turri.onChats((chats) => {}); // a chat opened, closed, renamed, working or done
|
|
95
|
+
await turri.closeChat(chat.id); // its turn stopped, its conversation deleted - its pulses run on
|
|
96
|
+
turri.jobs; // the session's job registry: every pulse any chat started
|
|
97
|
+
turri.loop; // its work loop, once a chat is open
|
|
98
|
+
await turri.dispose(); // every chat let go, what activate started stopped
|
|
93
99
|
```
|
|
94
100
|
|
|
95
|
-
- **The
|
|
96
|
-
- **
|
|
101
|
+
- **The chats.** Up to nine, each its own history, model, thinking level and stack, all on the session's one job registry and work loop - see [chats](chats.md). `openChat({ sessionFile })` reopens a saved one. A pulse's end is told to the chat that asked for it, its agent reacting there; to the chat `focus` last named when that one is closed. A chat is never hidden from turri's side: what the host does not show, it still runs, and its questions go to the host's dialogs at once - the host decides where they wait. `status` says a turn is running (`working`) or that it said something since it was last focused (`unread`).
|
|
102
|
+
- **Interrupting.** `interrupt(id)` is the terminal's ctrl+c for a host's key or button: it stops what that chat is doing - its turn, a skill or a tool in it, a `!` command, a compaction - and nothing in the other chats (`stopped`). A skill stopped keeps its run, to be continued. A question of the chat's open in the host's dialogs when its turn is stopped is over - declined - though its dialog is the host's to close. A chat doing nothing ends the session, every chat with it, as `dispose` does (`ended`): turri never exits the host's process, so the host closes what showed it. An interrupt straight after one that stopped something is `ignored`, so pressing twice to be sure ends nothing. `session.abort()` is the chat's turn alone, and never ends the session.
|
|
103
|
+
- **The record.** The chats are recorded in `.turri/chats.json` as the terminal records them, and `restore: true` reopens the last ended session's - without it, a host starts with none and deletes nothing. A chat closed is deleted; a session's chats are kept until five newer sessions have recorded theirs.
|
|
104
|
+
- **The jobs.** The host is told of each job as it changes, and has the registry and the loop themselves, so it shows the very jobs the chats run. Its buttons are best a chat's commands - `session.prompt("/regenerate app.plain")` - so the plan is the chats'.
|
|
105
|
+
- **The questions.** `dialogs(chat)` gives a chat's two dialogs: `select(title, options, context, at)` and `input(title, context)`, each resolving with the answer, or undefined when the person dismissed it. The chat's questions, a skill's interview and a review gate's escalation are put in them; without them, in the `uiContext`'s own dialogs.
|
|
97
106
|
- **The surface.** What turri tells the model of where the person watches the work is the terminal's by default: regen, the side panel on `ctrl+h`, `/pulses`. In a host none of that is there, and the model would send the person to keys and panels that do not exist. `surface` is the host's own description of its UI - the views it has in place of the terminal's, what its marks and buttons mean, where questions appear - in its own words. Given, turri leaves every terminal instruction out of what the model is told and puts the surface in turri's identity, as a `<surface>` block, where the identity speaks of regen. It reaches the side questions and the tools' own words too: a pulse not found says the `pulses` tool lists them, not `/pulses`. Text only the person sees in the terminal - the job panel, the side panel - is the terminal's and stays as it is. Without `surface`, the prompt is the terminal's, byte for byte. What the conversation is to do once something is released belongs there too - open it in the host's preview, say what to test: after a pulse released under `release-after-pulse`, the conversation does what it was told to do once something is released.
|
|
98
107
|
- **The system prompt.** `systemPrompt(prompt)` gets the whole system prompt for the turn - [pi](https://pi.dev)'s, turri's identity and blocks, the rules - after turri's own `before_agent_start` work, and returns what is sent. It is for what `surface` does not cover; to describe the UI, use `surface`, rather than words appended after turri's that contradict them.
|
|
99
|
-
- **The mode.** `
|
|
108
|
+
- **The mode.** Each chat is bound in [pi](https://pi.dev)'s `tui` mode: the work runs in the background and keeps the chat free, as the terminal does. Its other UI calls - its notices, its working line, a confirm - come to `uiContext(chat)`, as [pi](https://pi.dev)'s [SDK](https://github.com/earendil-works/pi) describes it.
|
|
100
109
|
|
|
101
110
|
A surface for a desktop app might read:
|
|
102
111
|
|
|
@@ -135,7 +144,7 @@ concepts.find((concept) => concept.definition === "/…/base.plain:8");
|
|
|
135
144
|
|
|
136
145
|
## The ledger in a host
|
|
137
146
|
|
|
138
|
-
|
|
147
|
+
`createTurri` starts the ledger itself, in the process's working folder - the project's - so a host needs nothing to have one: its pulses are recorded as `host` runs, each event with the chat its work was asked for in, and the chats' `pulses` and `diagnose` already tell the model of earlier sessions' pulses. What the host can do with it is show it.
|
|
139
148
|
|
|
140
149
|
```js
|
|
141
150
|
import { onLedgerEvent, pulseView, readLedger, runViews } from "@turri-dev/cli/host";
|
|
@@ -153,7 +162,7 @@ const runs = runViews(projectDir);
|
|
|
153
162
|
const view = pulseView("646d", readLedger(projectDir, { job: "646d" }));
|
|
154
163
|
|
|
155
164
|
// Earlier sessions' pulses as jobs, to list beside this session's - none this session's registry knows.
|
|
156
|
-
const { jobs, more } = earlierPulses(ledgerProjects(projectDir), new Set(
|
|
165
|
+
const { jobs, more } = earlierPulses(ledgerProjects(projectDir), new Set(turri.jobs.list().map((job) => job.id)));
|
|
157
166
|
```
|
|
158
167
|
|
|
159
168
|
- **Live.** `onLedgerEvent(listener)` is told every event as turri records it, with the project it is about, until what it returns is called; a listener that throws misses that event and nothing else. It is the pulse's own detail - its stages, test runs, verdicts, refusals - where `onJobChange` is the job as a whole.
|
package/docs/skills-and-gates.md
CHANGED
|
@@ -43,7 +43,7 @@ A skill declares its steps with a `## Steps` section whose `###` headings are th
|
|
|
43
43
|
|
|
44
44
|
`questioning:` under `metadata:` names the steps the questioning level caps (`src/questioning.ts`) - `brood`'s `[stack, product]`, `bud`'s `[product]`. When there are any and someone to ask, the harness itself asks the first question of the first step: how many questions those steps may ask - light (3), medium (5), thorough (10) or best judgement - and tells the agent the answer beside each step's instructions, with how broad each question is to be: broad at light, a whole area at once, narrow at thorough, an edge case at a time. Every level covers the same ground, in less or more depth. In a capped step `ask_user` refuses a question past the cap; the step's closing confirmation, asked with `confirms_phase: true`, is never counted. `end_questions` ends the step's questions sooner - best judgement's only end. What the questions leave open, the agent decides itself and says what it chose. The level, and how many questions each step has asked, are kept in the run's `run.json`, so a resumed run keeps them. A step it does not name, or a run with nobody to ask, is not capped.
|
|
45
45
|
|
|
46
|
-
`change-plain-per-answer:` under `metadata:` names the steps where every answer is a \*\*\*plain change - `brood`'s and `bud`'s `[product]`, at every questioning level. There `ask_user` refuses the next question until the last answer is written with `change_plain` (a confirmation, or a question dismissed, owes nothing), and `change_plain` no longer waits for an answer before a change: the agent writes as many as the step needs, those it decides itself included. The level limits the questions, never the \*\*\*plain changes.
|
|
46
|
+
`change-plain-per-answer:` under `metadata:` names the steps where every answer is a \*\*\*plain change - `brood`'s and `bud`'s `[product]`, at every questioning level. There `ask_user` refuses the next question until the last answer is written with `change_plain` (a confirmation, or a question dismissed or declined, owes nothing), and `change_plain` no longer waits for an answer before a change: the agent writes as many as the step needs, those it decides itself included. The level limits the questions, never the \*\*\*plain changes.
|
|
47
47
|
|
|
48
48
|
A step ends when its agent's turn does. A turn that ends on a question in the reply would end the step there, half done, on a question no one reads - the person sees only what `ask_user` asks. So such a step is sent back, twice at most: to ask with `ask_user` if the question is truly the person's, or, in an unpaced skill, to decide it itself - then to carry on until the step is done.
|
|
49
49
|
|
|
@@ -81,7 +81,7 @@ A gate can also have a `continuous` reviewer: a read-only agent that re-reads th
|
|
|
81
81
|
- **Regenerate, and conform.** A skill that names them in `allowed-tools` gets the conversation's own `regenerate`, `drift`, `halt` and `conform`, on its loop: `conform` schedules a conformance pulse for functional specs not built yet, as it does from the chat. The skill never waits for them.
|
|
82
82
|
- **Choose the model that regenerates**, when the user says so - `engine`, the same tool the conversation has.
|
|
83
83
|
|
|
84
|
-
The harness still owns the sequence. A step chooses what to ask; it does not choose whether the gate afterwards runs. A run is saved as it goes, so an interrupted one can be resumed where it stopped.
|
|
84
|
+
The harness still owns the sequence. A step chooses what to ask; it does not choose whether the gate afterwards runs. A run is saved as it goes, so an interrupted one can be resumed where it stopped - a run stopped with the chat's turn (`ctrl+c`, `esc`) too: its agents end their turns there, and what it started on the work loop goes on.
|
|
85
85
|
|
|
86
86
|
While a skill runs in the chat, `cultivate`'s output leads with what its agent is doing now: the skill, its step and how long each has taken, its last few tool calls, and the end of what it is saying, refreshed every few seconds. [pi](https://pi.dev) shows only a running tool's first lines, and a step can take many minutes without a new line in the trace. The result the conversation gets back keeps the trace alone.
|
|
87
87
|
|
package/docs/using.md
CHANGED
|
@@ -7,7 +7,7 @@ cd ~/my-project
|
|
|
7
7
|
turri
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
That is [pi](https://pi.dev), with turri's tools, skills and themes loaded - `turri` alone, or with [pi](https://pi.dev)'s options (`turri -c`, `turri --harness-model <m>`). It opens with turri's own banner - the jellyfish it is named after, regenerating until the conversation fills the screen: a pixel polyp on the sea floor grows into the jellyfish - a bell from pink down to indigo, an orange gut, neon tentacles - which swims a while, sinks back to the floor, and the polyp grows again, round and round: a picture in a terminal that shows images (Kitty, Ghostty, WezTerm, Warp, iTerm2), and drawn at a third of the size in half blocks in any other, tmux included (`src/jellyfish.ts` draws it, ported from the design system's pixel kit, `scripts/jellyfish-frames.mjs` writes the pictures) - turri's version (and, run from a clone, the commit it is on, and whether the clone has changes) and where its settings are changed - and only when something is wrong, skills that failed to load or agents' commands less fenced than they should be - rather than [pi](https://pi.dev)'s startup header and resource list; `turri --verbose` shows those too. [pi](https://pi.dev)'s "Update Available" notice is left out - turri's [pi](https://pi.dev) is a pinned dependency `pi update` never touches - and the terminal's tab reads `🪼 turri - <folder>`, not [pi](https://pi.dev)'s `π`. Talk to it: ask it to add a feature to \*\*\*plain, regenerate a module, fix a module's \*\*\*plain. It decides which skill or tool applies - but not *how* a skill runs: `cultivate` hands the work to the gated runner, which owns the steps, the gates and the judges (see [skills and gates](skills-and-gates.md)).
|
|
10
|
+
That is [pi](https://pi.dev), with turri's tools, skills and themes loaded - `turri` alone, or with [pi](https://pi.dev)'s options (`turri -c`, `turri --harness-model <m>`). It holds several chats side by side, each working while another is shown: `/chat new` or `alt+n` opens one, `alt+1` … `alt+9` switch, and `turri -c` opens them all again - see [chats](chats.md). It opens with turri's own banner - the jellyfish it is named after, regenerating until the conversation fills the screen: a pixel polyp on the sea floor grows into the jellyfish - a bell from pink down to indigo, an orange gut, neon tentacles - which swims a while, sinks back to the floor, and the polyp grows again, round and round: a picture in a terminal that shows images (Kitty, Ghostty, WezTerm, Warp, iTerm2), and drawn at a third of the size in half blocks in any other, tmux included (`src/jellyfish.ts` draws it, ported from the design system's pixel kit, `scripts/jellyfish-frames.mjs` writes the pictures) - turri's version (and, run from a clone, the commit it is on, and whether the clone has changes) and where its settings are changed - and only when something is wrong, skills that failed to load or agents' commands less fenced than they should be - rather than [pi](https://pi.dev)'s startup header and resource list; `turri --verbose` shows those too. [pi](https://pi.dev)'s "Update Available" notice is left out - turri's [pi](https://pi.dev) is a pinned dependency `pi update` never touches - and the terminal's tab reads `🪼 turri - <folder>`, not [pi](https://pi.dev)'s `π`. Talk to it: ask it to add a feature to \*\*\*plain, regenerate a module, fix a module's \*\*\*plain. It decides which skill or tool applies - but not *how* a skill runs: `cultivate` hands the work to the gated runner, which owns the steps, the gates and the judges (see [skills and gates](skills-and-gates.md)).
|
|
11
11
|
|
|
12
12
|
To start a project, just say what you want - `create a small server in Java Spring Boot` - and turri asks how to start it, with three choices: `/brood`, the interview; `/spawn`, the quick start; or `Just do it`, no skill. A skill picked there starts at once, with what you asked for as its context - nothing to type again; saying so in your message - `just build it`, or the command itself - skips the question. Just do it starts the project in the conversation: turri takes the stack you named, or chooses one this machine can build, and says it in a line; writes the module importing the stack from the standard library, `config.turri.yaml` (conformance tests off unless you ask for them) and the unit testing script (with `implement-unit-testing-script`, unless there is one); then the smallest thing that runs, regenerated at once, and while it regenerates the main thing you described and at most one more, on their happy path - nothing you did not describe. You say `go on`, or what to change, and every change after is \*\*\*plain it writes and regenerates (`src/new-project.ts`).
|
|
13
13
|
|
|
@@ -129,7 +129,7 @@ Each may leave out either part, and a bare string (`coding-model: provider/id`,
|
|
|
129
129
|
|
|
130
130
|
On the command line, `turri --harness-model <m> --coding-model <m>` wins over them ([pi](https://pi.dev)'s own `--model` works for the harness model too), and in the conversation [pi](https://pi.dev)'s `/model` changes the harness model and `engine`, below, the coding model. `model:`, from before there were two, is read as `coding-model`. With neither, regenerations run on the conversation's model.
|
|
131
131
|
|
|
132
|
-
Every regeneration runs on the coding model unless you say otherwise. Say it in words - "regenerate \*\*\*plain changes with gpt 5.5", "build the functionalities on Gemini 3.6 Flash", "use Claude for everything" - and the agent calls `engine`, which holds for the rest of the session (a
|
|
132
|
+
Every regeneration runs on the coding model unless you say otherwise. Say it in words - "regenerate \*\*\*plain changes with gpt 5.5", "build the functionalities on Gemini 3.6 Flash", "use Claude for everything" - and the agent calls `engine`, which holds for the rest of the session, in every [chat](chats.md) - and in it again when a chat that chose it is reopened:
|
|
133
133
|
|
|
134
134
|
| kind of work | covers |
|
|
135
135
|
| --- | --- |
|