@turri-dev/cli 0.1.16-next.ce2cd46 → 0.1.17-next.1ecfd4f
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/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 +5 -0
- package/dist/chat-terminal.js +136 -0
- package/dist/chat-ui.d.ts +33 -0
- package/dist/chat-ui.js +192 -0
- package/dist/chats.d.ts +176 -0
- package/dist/chats.js +356 -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 +348 -230
- package/dist/host-session.d.ts +51 -0
- package/dist/host-session.js +58 -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/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 +73 -0
- package/docs/config.md +3 -3
- package/docs/development.md +4 -1
- package/docs/hosting.md +35 -28
- package/docs/using.md +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type AgentSession, type ExtensionFactory, type ExtensionUIContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { AskUserIO } from "./ask.ts";
|
|
3
|
+
import type { ChatInfo, TurriHost } from "./chats.ts";
|
|
4
|
+
import type { JobRegistry } from "./regenerate/jobs.ts";
|
|
5
|
+
import type { WorkLoop } from "./regenerate/loop.ts";
|
|
6
|
+
/**
|
|
7
|
+
* turri's session for a host that runs its chats itself - a desktop app, an editor: the chats, each a pi session with
|
|
8
|
+
* turri in it, on one job registry and work loop. The host shows each chat in its own way - subscribes to its session,
|
|
9
|
+
* prompts it - and tells turri which one its person looks at (`focus`): a pulse a closed chat asked for is reported
|
|
10
|
+
* there.
|
|
11
|
+
*/
|
|
12
|
+
export interface TurriOptions extends Omit<TurriHost, "dialogs"> {
|
|
13
|
+
/** pi's folder - its settings, models and credentials. Default: pi's own (`~/.pi/agent`). */
|
|
14
|
+
agentDir?: string;
|
|
15
|
+
/** Where the chats' conversations are saved. Default: pi's own folder for this project. */
|
|
16
|
+
sessionDir?: string;
|
|
17
|
+
/** The host's own extensions, built into every chat after turri's. */
|
|
18
|
+
extensions?: ExtensionFactory[];
|
|
19
|
+
/** The dialogs a chat's questions are put in - ask_user's, a skill's interview, a review gate's - by the chat's id. */
|
|
20
|
+
dialogs?: (chat: string) => AskUserIO | undefined;
|
|
21
|
+
/** What a chat's other UI calls - its notices, its working line - come to, by the chat's id. */
|
|
22
|
+
uiContext?: (chat: string) => ExtensionUIContext | undefined;
|
|
23
|
+
/** Reopen the chats of the last session here that has ended, in their order - as `turri --continue` does. */
|
|
24
|
+
restore?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface Turri {
|
|
27
|
+
/** The session's job registry: every pulse any chat starts. */
|
|
28
|
+
readonly jobs: JobRegistry;
|
|
29
|
+
/** The session's work loop - once a chat is open: what each codebase has planned, and why. */
|
|
30
|
+
readonly loop: WorkLoop;
|
|
31
|
+
/** The chats, in their order. */
|
|
32
|
+
chats(): ChatInfo[];
|
|
33
|
+
/** A chat's pi session: to subscribe to, prompt and abort. */
|
|
34
|
+
session(id: string): AgentSession | undefined;
|
|
35
|
+
/** A fresh chat - or the saved one `sessionFile` is. At most nine are open. */
|
|
36
|
+
openChat(options?: {
|
|
37
|
+
sessionFile?: string;
|
|
38
|
+
}): Promise<ChatInfo & {
|
|
39
|
+
session: AgentSession;
|
|
40
|
+
}>;
|
|
41
|
+
/** A chat closed: its turn stopped, its conversation deleted - its pulses run on. */
|
|
42
|
+
closeChat(id: string): Promise<void>;
|
|
43
|
+
/** The chat the person looks at now. */
|
|
44
|
+
focus(id: string): void;
|
|
45
|
+
/** Told when a chat is opened, closed, renamed, or starts or stops working. */
|
|
46
|
+
onChats(listener: (chats: ChatInfo[]) => void): () => void;
|
|
47
|
+
/** Every chat let go, and what activate started stopped. */
|
|
48
|
+
dispose(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
/** turri's session in a host, in the process's working folder - the project's. */
|
|
51
|
+
export declare function createTurri(options?: TurriOptions): Promise<Turri>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createAgentSession, DefaultResourceLoader, getAgentDir, SessionManager } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { lastRun } from "./chat-records.js";
|
|
3
|
+
import { createTurriCore, sessionLoop } from "./extension.js";
|
|
4
|
+
import { stopDeployed } from "./regenerate/release.js";
|
|
5
|
+
/** turri's session in a host, in the process's working folder - the project's. */
|
|
6
|
+
export async function createTurri(options = {}) {
|
|
7
|
+
const { dialogs, uiContext, extensions = [], agentDir = getAgentDir(), sessionDir, restore, ...host } = options;
|
|
8
|
+
const core = createTurriCore(host, { persist: true, ...(dialogs !== undefined ? { dialogs } : {}) });
|
|
9
|
+
const cwd = core.cwd;
|
|
10
|
+
core.setDriver({
|
|
11
|
+
async create(chat, sessionFile) {
|
|
12
|
+
const resourceLoader = new DefaultResourceLoader({ cwd, agentDir, extensionFactories: [core.extension, ...extensions] });
|
|
13
|
+
await resourceLoader.reload();
|
|
14
|
+
const sessionManager = sessionFile !== undefined ? SessionManager.open(sessionFile, sessionDir) : SessionManager.create(cwd, sessionDir);
|
|
15
|
+
const { session } = await createAgentSession({ cwd, agentDir, resourceLoader, sessionManager });
|
|
16
|
+
chat.session = session;
|
|
17
|
+
await session.bindExtensions({ uiContext: uiContext?.(chat.id), mode: "tui" });
|
|
18
|
+
},
|
|
19
|
+
async dispose(chat) {
|
|
20
|
+
const session = chat.session;
|
|
21
|
+
if (session === undefined)
|
|
22
|
+
return;
|
|
23
|
+
try {
|
|
24
|
+
await session.abort();
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
session.dispose();
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
if (restore === true) {
|
|
32
|
+
const saved = lastRun(cwd);
|
|
33
|
+
if (saved !== undefined)
|
|
34
|
+
await core.restore(saved);
|
|
35
|
+
const first = core.chats()[0];
|
|
36
|
+
if (first !== undefined)
|
|
37
|
+
await core.show(first.id);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
jobs: core.jobs,
|
|
41
|
+
get loop() {
|
|
42
|
+
return sessionLoop(core);
|
|
43
|
+
},
|
|
44
|
+
chats: () => core.chats(),
|
|
45
|
+
session: (id) => core.session(id),
|
|
46
|
+
async openChat(asked = {}) {
|
|
47
|
+
const chat = await core.open(asked);
|
|
48
|
+
return { ...chat, session: core.session(chat.id) };
|
|
49
|
+
},
|
|
50
|
+
closeChat: (id) => core.close(id),
|
|
51
|
+
focus: (id) => void core.show(id),
|
|
52
|
+
onChats: (listener) => core.onChats(() => listener(core.chats())),
|
|
53
|
+
async dispose() {
|
|
54
|
+
await core.dispose();
|
|
55
|
+
await stopDeployed();
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
package/dist/host.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turri for a host that runs it itself - a desktop app, an editor - imported as `@turri-dev/cli/host`: the
|
|
3
|
-
*
|
|
2
|
+
* turri for a host that runs it itself - a desktop app, an editor - imported as `@turri-dev/cli/host`: the session and
|
|
3
|
+
* its chats, with the host told of its jobs and asking its questions (createTurri), and
|
|
4
4
|
* what a host shows beside it - a spec parsed, checked and coloured, its concepts and links, the standard library, how
|
|
5
5
|
* far each functional spec is built, the work a spec change would plan. It is turri's own code, as the terminal runs it; a 0.x release may change any of it.
|
|
6
6
|
*/
|
|
7
|
-
export {
|
|
7
|
+
export { createTurri, type Turri, type TurriOptions } from "./host-session.ts";
|
|
8
|
+
export { MAX_CHATS, type ChatInfo, type ChatStatus, type TurriHost } from "./chats.ts";
|
|
8
9
|
export type { AskUserIO } from "./ask.ts";
|
|
9
|
-
/** pi itself, as turri
|
|
10
|
+
/** pi itself, as turri is built against it: a host's own extensions and UI are made with this one, not another copy. */
|
|
10
11
|
export * as pi from "@earendil-works/pi-coding-agent";
|
|
11
12
|
export { configForSpec, debugMode, hasProjectConfig, modelSpec, projectModels, readProjectConfig, stacksOf, type ProjectConfig } from "./config.ts";
|
|
12
13
|
export { MODULES_DIR, projectDirOf, resolveTemplateDirs } from "./regenerate/paths.ts";
|
package/dist/host.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turri for a host that runs it itself - a desktop app, an editor - imported as `@turri-dev/cli/host`: the
|
|
3
|
-
*
|
|
2
|
+
* turri for a host that runs it itself - a desktop app, an editor - imported as `@turri-dev/cli/host`: the session and
|
|
3
|
+
* its chats, with the host told of its jobs and asking its questions (createTurri), and
|
|
4
4
|
* what a host shows beside it - a spec parsed, checked and coloured, its concepts and links, the standard library, how
|
|
5
5
|
* far each functional spec is built, the work a spec change would plan. It is turri's own code, as the terminal runs it; a 0.x release may change any of it.
|
|
6
6
|
*/
|
|
7
|
-
// The
|
|
8
|
-
export {
|
|
9
|
-
|
|
7
|
+
// The session and its chats.
|
|
8
|
+
export { createTurri } from "./host-session.js";
|
|
9
|
+
export { MAX_CHATS } from "./chats.js";
|
|
10
|
+
/** pi itself, as turri is built against it: a host's own extensions and UI are made with this one, not another copy. */
|
|
10
11
|
export * as pi from "@earendil-works/pi-coding-agent";
|
|
11
12
|
// The project and its settings.
|
|
12
13
|
export { configForSpec, debugMode, hasProjectConfig, modelSpec, projectModels, readProjectConfig, stacksOf } from "./config.js";
|
package/dist/identity.js
CHANGED
|
@@ -48,6 +48,11 @@ const IDENTITY_BEFORE = [
|
|
|
48
48
|
"turri in the third person, as if it were someone else, and never tell the person to tell or ask turri something:",
|
|
49
49
|
"they are talking to turri. What they should ask for, they ask you for, here.",
|
|
50
50
|
"",
|
|
51
|
+
"The person may have several chats with you open on this project at once, this one among them. What regenerates is",
|
|
52
|
+
"the same in every chat: the pulses and their plan, steering, the engine's coding model and what activate started -",
|
|
53
|
+
"pulses lists every pulse, whichever chat asked for it, and you are told when one asked for here ends. What was said",
|
|
54
|
+
"in another chat is not in this one: when the person speaks of it, ask.",
|
|
55
|
+
"",
|
|
51
56
|
"Your words, and the older ones for the same things - a project's AGENTS.md, a skill, notes or the person may use",
|
|
52
57
|
"either, and they mean the same:",
|
|
53
58
|
"- A pulse is one unit of work regenerating the code - a spec diff regenerated in a codebase, or its conformance",
|
package/dist/pi-launch.js
CHANGED
|
@@ -12,6 +12,9 @@ import { main, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
|
12
12
|
import { ProcessTerminal } from "@earendil-works/pi-tui";
|
|
13
13
|
import "./terminal-images.js";
|
|
14
14
|
import { resumeLine } from "./resume-line.js";
|
|
15
|
+
import { lastRun } from "./chat-records.js";
|
|
16
|
+
import { driveTerminal } from "./chat-terminal.js";
|
|
17
|
+
import turri, { createTurriCore, processSession } from "./extension.js";
|
|
15
18
|
/**
|
|
16
19
|
* pi, started the way its own CLI starts it, for `turri`.
|
|
17
20
|
*
|
|
@@ -21,6 +24,8 @@ import { resumeLine } from "./resume-line.js";
|
|
|
21
24
|
* quiet every `pi` on the machine, so it is answered for this run instead. `--verbose` still shows it.
|
|
22
25
|
*/
|
|
23
26
|
const args = process.argv.slice(2);
|
|
27
|
+
/** Set by cli.ts when pi loads turri's extension itself - an older turri installed it into every pi. */
|
|
28
|
+
const GLOBAL_EXTENSION = "TURRI_EXTENSION_IN_PI";
|
|
24
29
|
if (!args.includes("--verbose")) {
|
|
25
30
|
SettingsManager.prototype.getQuietStartup = () => true;
|
|
26
31
|
}
|
|
@@ -42,4 +47,21 @@ process.stdout.write = ((chunk, ...rest) => write(typeof chunk === "string" ? re
|
|
|
42
47
|
const setup = pathToFileURL(join(dirname(fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"))), "cli", "setup.js")).href;
|
|
43
48
|
const { setupCli } = (await import(__rewriteRelativeImportExtension(setup)));
|
|
44
49
|
setupCli();
|
|
45
|
-
|
|
50
|
+
// turri's session, with its chats: built into pi here, in this process, rather than loaded from a file - one session
|
|
51
|
+
// the terminal's chats share (chats.ts). An older turri installed into every pi loads it itself: cli.ts says so, and
|
|
52
|
+
// it is not built in twice.
|
|
53
|
+
const extensionFactories = process.env[GLOBAL_EXTENSION] === "1" ? [] : [turri];
|
|
54
|
+
// pi's terminal - not its print, json or rpc modes, nor a run given its input on stdin - holds several chats, and
|
|
55
|
+
// records them for `turri --continue`, which opens them all again: the first as pi's own session, the rest beside it.
|
|
56
|
+
const interactive = process.stdin.isTTY === true && !args.some((arg) => ["-p", "--print", "--mode", "--help", "-h", "--version", "-v", "--list-models"].includes(arg) || arg.startsWith("--mode="));
|
|
57
|
+
if (interactive) {
|
|
58
|
+
let restore;
|
|
59
|
+
const asked = args.findIndex((arg) => arg === "--continue" || arg === "-c");
|
|
60
|
+
const saved = asked !== -1 ? lastRun(process.cwd()) : undefined;
|
|
61
|
+
if (saved !== undefined) {
|
|
62
|
+
args.splice(asked, 1, "--session", saved.chats[0].sessionFile);
|
|
63
|
+
restore = saved;
|
|
64
|
+
}
|
|
65
|
+
driveTerminal(processSession(() => createTurriCore({}, { persist: true })), restore !== undefined ? { restore } : {});
|
|
66
|
+
}
|
|
67
|
+
await main(args, { extensionFactories });
|
|
@@ -31,6 +31,8 @@ export interface Job {
|
|
|
31
31
|
model?: string;
|
|
32
32
|
/** The stack it is built with - its config.turri.<stack>.yaml over config.turri.yaml - as chosen when it was queued. */
|
|
33
33
|
stack?: string;
|
|
34
|
+
/** The chat it was asked for in (chat-scope.ts): the one told how it went. */
|
|
35
|
+
chat?: string;
|
|
34
36
|
status: JobStatus;
|
|
35
37
|
/** Monotonic start order. Two jobs can share a millisecond, so ordering uses this. */
|
|
36
38
|
sequence: number;
|
package/dist/regenerate/jobs.js
CHANGED
|
@@ -6,6 +6,7 @@ import { addAgentReport, describeAgents, JobStoppedError, withActivity } from ".
|
|
|
6
6
|
import { projectOfLanes, startJobLog } from "./job-log.js";
|
|
7
7
|
import { firstLine, healing, record } from "./ledger.js";
|
|
8
8
|
import { chosenStack, debugMode, withStack } from "../config.js";
|
|
9
|
+
import { currentChat } from "../chat-scope.js";
|
|
9
10
|
import { pulseEnded, pulseWrote } from "./pulse-files.js";
|
|
10
11
|
const DEFAULT_TAIL_CHARS = 4000;
|
|
11
12
|
const ACTIVITY_LINES = 10;
|
|
@@ -57,6 +58,7 @@ export function createJobRegistry(options) {
|
|
|
57
58
|
// The stack chosen as it is queued - or pinned for the work that queues it - not as it starts: a job that waits
|
|
58
59
|
// for a slot is built with the stack it was asked for, whatever is chosen meanwhile.
|
|
59
60
|
const stack = chosenStack();
|
|
61
|
+
const chat = currentChat()?.id;
|
|
60
62
|
const claimed = [...new Set(input.lanes ?? [])];
|
|
61
63
|
const blockers = [
|
|
62
64
|
...new Set(claimed
|
|
@@ -76,6 +78,7 @@ export function createJobRegistry(options) {
|
|
|
76
78
|
diff: input.diff,
|
|
77
79
|
model: input.model,
|
|
78
80
|
...(stack !== undefined ? { stack } : {}),
|
|
81
|
+
...(chat !== undefined ? { chat } : {}),
|
|
79
82
|
...(input.follows !== undefined ? { follows: input.follows } : {}),
|
|
80
83
|
status: blockers.length > 0 || (limit !== undefined && active >= limit) ? "queued" : "running",
|
|
81
84
|
sequence: (sequence += 1),
|
|
@@ -45,6 +45,11 @@ export type LedgerEvent = {
|
|
|
45
45
|
} | {
|
|
46
46
|
kind: "run.ended";
|
|
47
47
|
ms: number;
|
|
48
|
+
} | {
|
|
49
|
+
kind: "chat.opened";
|
|
50
|
+
reopened: boolean;
|
|
51
|
+
} | {
|
|
52
|
+
kind: "chat.closed";
|
|
48
53
|
} | {
|
|
49
54
|
kind: "job.queued";
|
|
50
55
|
jobKind: string;
|
|
@@ -213,6 +218,8 @@ export type LedgerEntry = LedgerEvent & {
|
|
|
213
218
|
job?: string;
|
|
214
219
|
module?: string;
|
|
215
220
|
agent?: AgentRef;
|
|
221
|
+
/** The chat the work was asked for in, in a session of several (chat-scope.ts). */
|
|
222
|
+
chat?: string;
|
|
216
223
|
};
|
|
217
224
|
/** Where an event happened, when not where the code recording it runs: a job's own place is found by itself. */
|
|
218
225
|
export interface LedgerPlace {
|
|
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { appendFileSync, existsSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { gunzipSync, gzipSync } from "node:zlib";
|
|
5
|
+
import { currentChat } from "../chat-scope.js";
|
|
5
6
|
import { readProjectConfig } from "../config.js";
|
|
6
7
|
import { turriIdentity } from "../environment.js";
|
|
7
8
|
import { currentPlace, onModelEvent } from "./activity.js";
|
|
@@ -110,7 +111,13 @@ export function record(event, place = {}) {
|
|
|
110
111
|
return;
|
|
111
112
|
const job = place.job ?? here.job;
|
|
112
113
|
const module = place.module ?? here.module;
|
|
113
|
-
|
|
114
|
+
const chat = currentChat()?.id;
|
|
115
|
+
write(run, resolve(project), event, {
|
|
116
|
+
...(job !== undefined ? { job } : {}),
|
|
117
|
+
...(module !== undefined ? { module } : {}),
|
|
118
|
+
...(place.agent !== undefined ? { agent: place.agent } : {}),
|
|
119
|
+
...(chat !== undefined ? { chat } : {}),
|
|
120
|
+
});
|
|
114
121
|
}
|
|
115
122
|
/**
|
|
116
123
|
* Run a healer - the debugger, the diagnosis of a failed pulse - and record it: started, ended with what it found, or
|
|
@@ -167,7 +167,7 @@ export declare function createScriptTool(options: {
|
|
|
167
167
|
/** Watches the agent's runs for a loop: told once, then its turn is ended. */
|
|
168
168
|
watch?: LoopWatch;
|
|
169
169
|
}): import("@earendil-works/pi-coding-agent").ToolDefinition<Type.TObject<{}>, {
|
|
170
|
-
loop?: "
|
|
170
|
+
loop?: "stop" | "warn" | undefined;
|
|
171
171
|
command: string;
|
|
172
172
|
exitCode: number;
|
|
173
173
|
}, any> & import("@earendil-works/pi-coding-agent").ToolDefinition<any, any, any>;
|
package/dist/stacks.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Choosing a stack: `config.turri.<stack>.yaml` laid over `config.turri.yaml` for everything built from then on - a
|
|
3
3
|
* backend's testing scripts, then a frontend's. `/stack` and the `stack` tool in the chat, `--stack <name>` on the
|
|
4
|
-
* command line (cli.ts). The choice itself is kept in config.ts (`chooseStack`), for
|
|
4
|
+
* command line (cli.ts). The choice itself is kept in config.ts (`chooseStack`): in the chat it is made in, for that chat
|
|
5
|
+
* alone (chat-scope.ts), each new chat starting from `--stack`.
|
|
5
6
|
*/
|
|
6
7
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
7
8
|
/** The stacks of the projects here: the folder's own, and those of the projects one folder down (specs kept in plain/). */
|
package/dist/stacks.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Choosing a stack: `config.turri.<stack>.yaml` laid over `config.turri.yaml` for everything built from then on - a
|
|
3
3
|
* backend's testing scripts, then a frontend's. `/stack` and the `stack` tool in the chat, `--stack <name>` on the
|
|
4
|
-
* command line (cli.ts). The choice itself is kept in config.ts (`chooseStack`), for
|
|
4
|
+
* command line (cli.ts). The choice itself is kept in config.ts (`chooseStack`): in the chat it is made in, for that chat
|
|
5
|
+
* alone (chat-scope.ts), each new chat starting from `--stack`.
|
|
5
6
|
*/
|
|
6
7
|
import { readdirSync } from "node:fs";
|
|
7
8
|
import { join } from "node:path";
|
|
@@ -74,10 +75,10 @@ export function registerStacks(pi, cwd) {
|
|
|
74
75
|
label: "Stack",
|
|
75
76
|
description: "Choose the stack everything is built with from now on - the project's config.turri.yaml with the stack's " +
|
|
76
77
|
"config.turri.<stack>.yaml over it: its testing scripts, toolchains, models - or none, the project's file alone. " +
|
|
77
|
-
"For
|
|
78
|
+
"For this chat alone - each chat chooses its own; a pulse already running keeps the stack it started with. Without a name, says the stacks and the one chosen.",
|
|
78
79
|
promptSnippet: "choose the stack - its testing scripts and settings - that everything is built with",
|
|
79
80
|
promptGuidelines: [
|
|
80
|
-
"Switch the stack only when the user asks for it, and say which you chose. A module is built with the stack named after it by itself; to build one whose stack has another name, pass that stack as stack to regenerate, conform, glimpse or change_plain instead: it is pinned to that module's pulses, and
|
|
81
|
+
"Switch the stack only when the user asks for it, and say which you chose. A module is built with the stack named after it by itself; to build one whose stack has another name, pass that stack as stack to regenerate, conform, glimpse or change_plain instead: it is pinned to that module's pulses, and this chat's choice stays as it is.",
|
|
81
82
|
],
|
|
82
83
|
parameters: Type.Object({
|
|
83
84
|
name: Type.Optional(Type.String({ description: "The stack to choose, or none. Omitted: the stacks there are, and the one chosen" })),
|
package/docs/README.md
CHANGED
|
@@ -16,6 +16,7 @@ turri is the first coding agent with the power of regenerative software: [\*\*\*
|
|
|
16
16
|
| what my machine needs, and why a check failed | [installation](installation.md) |
|
|
17
17
|
| what turri needs of this machine, and what `turri doctor` checks | [installation](installation.md#what-turri-doctor-checks) |
|
|
18
18
|
| how to start a project, or add a feature, in a conversation | [using](using.md#in-a-conversation) |
|
|
19
|
+
| how to have several chats open at once, and reopen them | [chats](chats.md) |
|
|
19
20
|
| which model codes, and how to change it | [using](using.md#choosing-a-model), [the project config](config.md#models) |
|
|
20
21
|
| what `config.turri.yaml` can set, and what happens without it | [the project config](config.md) |
|
|
21
22
|
| how a backend and a frontend on different stacks share one project | [the project config](config.md#stacks) |
|
|
@@ -31,7 +32,7 @@ turri is the first coding agent with the power of regenerative software: [\*\*\*
|
|
|
31
32
|
| what the agents can and cannot reach | [the sandbox](sandbox.md) |
|
|
32
33
|
| how skill review gates and judges work | [skills and gates](skills-and-gates.md) |
|
|
33
34
|
| how the parser matches the reference implementation | [the \*\*\*plain parser](parser.md) |
|
|
34
|
-
| how a desktop app or an editor runs turri's
|
|
35
|
+
| how a desktop app or an editor runs turri's chats itself | [running turri in a host](hosting.md) |
|
|
35
36
|
| where the code for something lives | [development](development.md#layout) |
|
|
36
37
|
|
|
37
38
|
## All pages
|
|
@@ -40,6 +41,7 @@ turri is the first coding agent with the power of regenerative software: [\*\*\*
|
|
|
40
41
|
| --- | --- |
|
|
41
42
|
| [Installation](installation.md) | doctor, what turri needs of this machine, turri and `pi`, environment variables |
|
|
42
43
|
| [Using turri](using.md) | the conversation, the tools, background jobs, choosing a model, module history, the command line |
|
|
44
|
+
| [Chats](chats.md) | several chats in one session: what each keeps and what they share, `/chat` and its keys, `turri --continue` and `.turri/chats.json` |
|
|
43
45
|
| [The project config](config.md) | `config.turri.yaml` and each stack's `config.turri.<stack>.yaml`: where they are read from, every key, models and their inheritance, conformance, the testing scripts, stacks and choosing one, the old `config.yaml` |
|
|
44
46
|
| [Deploy](deploy.md) | trying what is built: dist/<module>, the last commit, starting it on your machine, `turri release` |
|
|
45
47
|
| [Running unattended](headless.md) | regenerating overnight or in CI: before you leave it, the command, the results, resuming, rebuilding one functionality |
|
|
@@ -49,5 +51,5 @@ turri is the first coding agent with the power of regenerative software: [\*\*\*
|
|
|
49
51
|
| [Skills and gates](skills-and-gates.md) | the gate syntax, what the harness enforces, what a step can do, the rules, trying the gates |
|
|
50
52
|
| [The \*\*\*plain parser](parser.md) | the TypeScript port, `turri validate`, inheritance, the iterator, fidelity |
|
|
51
53
|
| [Theming](theming.md) | the turri themes, contrast, backgrounds, the font |
|
|
52
|
-
| [Running turri in a host](hosting.md) | `@turri-dev/cli/host`: the
|
|
54
|
+
| [Running turri in a host](hosting.md) | `@turri-dev/cli/host`: the session and its chats in a host's own windows - their jobs, their questions - and what a host shows beside them |
|
|
53
55
|
| [Development](development.md) | tests, the source layout, known edges |
|
package/docs/chats.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
|
|
37
|
+
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.
|
|
38
|
+
|
|
39
|
+
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.
|
|
40
|
+
|
|
41
|
+
- **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.
|
|
42
|
+
- **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.
|
|
43
|
+
- **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.
|
|
44
|
+
- **The last chat** is not closed: `/quit` leaves turri.
|
|
45
|
+
- **`/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.
|
|
46
|
+
- **A chat shown mid-turn** draws its reply as far as it is written, and follows it from there.
|
|
47
|
+
|
|
48
|
+
## Coming back
|
|
49
|
+
|
|
50
|
+
`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.
|
|
51
|
+
|
|
52
|
+
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:
|
|
53
|
+
|
|
54
|
+
- **a chat closed** is gone: out of the record, its conversation deleted - it cannot be resumed;
|
|
55
|
+
- **a chat nothing was said in** is never recorded;
|
|
56
|
+
- **a session's chats** are kept until five newer sessions have recorded theirs, then deleted with their record;
|
|
57
|
+
- a session still running is never reopened by another turri.
|
|
58
|
+
|
|
59
|
+
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.
|
|
60
|
+
|
|
61
|
+
## Where there is one chat
|
|
62
|
+
|
|
63
|
+
[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.
|
|
64
|
+
|
|
65
|
+
## In a host
|
|
66
|
+
|
|
67
|
+
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).
|
|
68
|
+
|
|
69
|
+
## How it works
|
|
70
|
+
|
|
71
|
+
`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.
|
|
72
|
+
|
|
73
|
+
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,42 @@ 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
|
+
turri.chats(); // [{ id, title, status: "idle" | "working" | "question" | "unread", shown }]
|
|
93
|
+
turri.onChats((chats) => {}); // a chat opened, closed, renamed, working or done
|
|
94
|
+
await turri.closeChat(chat.id); // its turn stopped, its conversation deleted - its pulses run on
|
|
95
|
+
turri.jobs; // the session's job registry: every pulse any chat started
|
|
96
|
+
turri.loop; // its work loop, once a chat is open
|
|
97
|
+
await turri.dispose(); // every chat let go, what activate started stopped
|
|
93
98
|
```
|
|
94
99
|
|
|
95
|
-
- **The
|
|
96
|
-
- **The
|
|
100
|
+
- **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`).
|
|
101
|
+
- **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.
|
|
102
|
+
- **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'.
|
|
103
|
+
- **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
104
|
- **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
105
|
- **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.** `
|
|
106
|
+
- **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
107
|
|
|
101
108
|
A surface for a desktop app might read:
|
|
102
109
|
|
|
@@ -135,7 +142,7 @@ concepts.find((concept) => concept.definition === "/…/base.plain:8");
|
|
|
135
142
|
|
|
136
143
|
## The ledger in a host
|
|
137
144
|
|
|
138
|
-
|
|
145
|
+
`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
146
|
|
|
140
147
|
```js
|
|
141
148
|
import { onLedgerEvent, pulseView, readLedger, runViews } from "@turri-dev/cli/host";
|
|
@@ -153,7 +160,7 @@ const runs = runViews(projectDir);
|
|
|
153
160
|
const view = pulseView("646d", readLedger(projectDir, { job: "646d" }));
|
|
154
161
|
|
|
155
162
|
// 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(
|
|
163
|
+
const { jobs, more } = earlierPulses(ledgerProjects(projectDir), new Set(turri.jobs.list().map((job) => job.id)));
|
|
157
164
|
```
|
|
158
165
|
|
|
159
166
|
- **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.
|