@tomradex/agent 0.0.0-stage → 0.15.0

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.
@@ -0,0 +1,445 @@
1
+ import { Context, Service } from "cordis";
2
+ import { z } from "zod";
3
+ import { type UndoRunResult } from "@tomradex/domain";
4
+ import type { SettingsSection } from "@tomradex/settings";
5
+ import { AgentStore } from "./store.js";
6
+ import { type MediaRef, type MediaUpload, type MediaContent } from "./media.js";
7
+ import { type AgentEvent, type AgentPolicy, type CardView, type Engine, type CliCheck, type McpLinkView, type EngineTry, type EngineView, type FocusRef, type GeneralExecution, type ParkView, type PendingView, type Playbook, type PlaybookIndexEntry, type PlaybookSource, type RunView, type ThreadMessage, type ThreadSummary, type TurnFollowUp, type UserInput, type WorkflowSpec, type ThreadSurface } from "./types.js";
8
+ import { type TokenBreakdown } from "./tokens.js";
9
+ /** Where a thread's engine would run, or the one reason it cannot yet. `code` is stable for the renderer to translate. */
10
+ /** Which of these threads a plugin opened, and what to call that: thread id → label. */
11
+ export type ThreadOrigins = (projectId: string, threads: ThreadSummary[]) => Iterable<[string, string]>;
12
+ export interface WorkdirStatus {
13
+ cwd: string | null;
14
+ remote: string | null;
15
+ branch: string | null;
16
+ worktree: string | null;
17
+ problem: string | null;
18
+ /**
19
+ * Why there is no folder. no-board, no-board-repo and unknown-repo come with no `problem`: a conversation
20
+ * on a workstream without a repository runs fine without one, and only a Start needs to say so.
21
+ */
22
+ code: "no-checkout" | "no-repos" | "no-board" | "no-board-repo" | "unknown-repo" | "no-hub" | "branch-in-use" | "finished" | "error" | null;
23
+ /** branch-in-use: the other worktree that has the branch. */
24
+ heldAt?: string;
25
+ }
26
+ export interface PlaybookRecommendation {
27
+ workType: "coding" | "general";
28
+ playbookId: string | null;
29
+ confidence: number;
30
+ reason: string;
31
+ source: "llm" | "fallback";
32
+ requires: string[];
33
+ }
34
+ /** What one engine's finished runs added up to over a stretch of time, across every open project. */
35
+ export interface EngineUsage extends TokenBreakdown {
36
+ engine: string;
37
+ title: string;
38
+ runs: number;
39
+ /** Null when none of the runs said what it cost (Codex, the loop): tokens are all there is. */
40
+ costUsd: number | null;
41
+ }
42
+ export interface UsageSummary {
43
+ /** Since local midnight. */
44
+ today: EngineUsage[];
45
+ /** The last seven days, today included. */
46
+ week: EngineUsage[];
47
+ }
48
+ export interface ThreadDetail {
49
+ thread: ThreadSummary;
50
+ messages: ThreadMessage[];
51
+ runs: RunView[];
52
+ cards: CardView[];
53
+ pending: PendingView | null;
54
+ activePermissionMode?: string;
55
+ }
56
+ /** How a General ticket is to be run: which engine, which method, and the engine knobs for this delivery. */
57
+ export interface GeneralStartOptions {
58
+ engine?: string;
59
+ playbook?: string;
60
+ /** Engine option overrides for the thread (model, effort…); replaces earlier overrides when given. */
61
+ options?: Record<string, unknown>;
62
+ /** The UI that owns the thread; a terminal session is a `code` surface thread the panel does not list. */
63
+ surface?: ThreadSurface;
64
+ /** The engine is a CLI the user runs themselves: no engine plugin is checked and none is required. */
65
+ external?: boolean;
66
+ /** Start although tickets this one waits on are still open; the agent is told which. */
67
+ allowBlocked?: boolean;
68
+ }
69
+ /** A General delivery ready to run: the thread is bound to the delivery and the playbook, nothing has been sent yet. */
70
+ export interface GeneralPreparation {
71
+ threadId: string;
72
+ continued: boolean;
73
+ engine: string;
74
+ cwd: string;
75
+ playbook: GeneralExecution["playbook"];
76
+ /** The first user message of a headless run; the engine's system prompt carries the rest. */
77
+ opener: string;
78
+ /** The whole brief for a CLI agent the user runs in a terminal, which gets no system prompt from Tomrade. */
79
+ prompt: string;
80
+ }
81
+ /** What a new thread may be about beyond its workstream: the ticket it implements and the branch it works on. */
82
+ export interface NewThreadOptions {
83
+ itemId?: string | null;
84
+ branch?: string | null;
85
+ title?: string;
86
+ /** The UI that owns this thread. Defaults to the Assistant panel. */
87
+ surface?: ThreadSurface;
88
+ /** The engine knobs the thread opens with — what the thread it was opened from was using. */
89
+ options?: Record<string, unknown> | null;
90
+ }
91
+ /** Current in-memory work plus durable questions, for main-process observers that load mid-run. */
92
+ export interface AgentActivity {
93
+ projectId: string;
94
+ threadId: string;
95
+ run: RunView | null;
96
+ pending: PendingView | null;
97
+ }
98
+ declare module "cordis" {
99
+ interface Context {
100
+ agent: Agent;
101
+ }
102
+ interface Events {
103
+ /** Main-process lifecycle stream for feature plugins; renderer IPC is only one consumer. */
104
+ "agent/event"(threadId: string, event: AgentEvent): void;
105
+ /** The assistant panel switched to this thread, for observers that follow the conversation on screen. */
106
+ "agent/showing"(threadId: string): void;
107
+ }
108
+ }
109
+ declare module "@tomradex/ipc" {
110
+ interface Channels {
111
+ "agent.recommendPlaybook": (projectId: string, input: {
112
+ title: string;
113
+ body?: string | null;
114
+ workType?: "coding" | "general";
115
+ }) => PlaybookRecommendation;
116
+ "agent.startGeneral": (projectId: string, itemId: string, opts?: GeneralStartOptions) => {
117
+ threadId: string;
118
+ runId: string;
119
+ continued: boolean;
120
+ };
121
+ "agent.takeoverGeneral": (projectId: string, itemId: string) => {
122
+ stopped: number;
123
+ };
124
+ "agent.threads": (projectId: string) => ThreadSummary[];
125
+ "agent.thread": (projectId: string, threadId: string) => ThreadDetail;
126
+ "agent.usage": () => UsageSummary;
127
+ /** The project's rules for every agent, as the person wrote them; empty when there are none. */
128
+ "agent.rules": (projectId: string) => {
129
+ body: string;
130
+ file: string | null;
131
+ };
132
+ "agent.setRules": (projectId: string, body: string) => {
133
+ body: string;
134
+ file: string | null;
135
+ };
136
+ "agent.newThread": (projectId: string, engine?: string, boardId?: string | null, opts?: NewThreadOptions) => ThreadSummary;
137
+ /** Put the thread on a workstream (its repository is where a code engine runs), or back on the whole project. */
138
+ "agent.setWorkstream": (projectId: string, threadId: string, boardId: string | null) => ThreadSummary;
139
+ "agent.archiveThread": (projectId: string, threadId: string) => boolean;
140
+ "agent.archivedThreads": (projectId: string) => ThreadSummary[];
141
+ "agent.unarchiveThread": (projectId: string, threadId: string) => boolean;
142
+ "agent.deleteThread": (projectId: string, threadId: string) => boolean;
143
+ /** Where the thread's engine would run: ready, or why not yet (no checkout on this machine). */
144
+ "agent.workdir": (projectId: string, threadId: string) => WorkdirStatus;
145
+ "agent.send": (projectId: string, threadId: string, text: string, focus?: FocusRef[], attachments?: MediaRef[]) => {
146
+ runId: string;
147
+ };
148
+ "agent.media.upload": (projectId: string, threadId: string, upload: MediaUpload) => MediaRef;
149
+ "agent.media.read": (projectId: string, threadId: string, id: string) => MediaContent;
150
+ "agent.mediaCapabilities": (engineId: string, options?: unknown) => {
151
+ input: string[];
152
+ output: string[];
153
+ };
154
+ "agent.answer": (projectId: string, threadId: string, questionId: string, value: string | string[]) => {
155
+ runId: string;
156
+ };
157
+ "agent.cancel": (runId: string) => boolean;
158
+ /** Into the running turn, when its engine steers; false means it was not taken (queue it instead). */
159
+ "agent.steer": (projectId: string, threadId: string, steerId: string, text: string) => Promise<boolean>;
160
+ "agent.showing": (threadId: string) => void;
161
+ "agent.revert": (projectId: string, runId: string) => UndoRunResult;
162
+ /** `fresh` looks for each engine's CLI again instead of answering from a recent probe. */
163
+ "agent.engines": (opts?: {
164
+ fresh?: boolean;
165
+ }) => EngineView[];
166
+ /** Runs a file the person picked as an engine's CLI, before it is saved. */
167
+ "agent.checkCli": (engineId: string, file: string) => CliCheck;
168
+ /** Whether the engine's own user-level configuration reaches this Tomrade's MCP server. */
169
+ "agent.mcpLink": (engineId: string) => McpLinkView;
170
+ "agent.linkMcp": (engineId: string, profile?: string) => McpLinkView;
171
+ "agent.unlinkMcp": (engineId: string, profile?: string) => McpLinkView;
172
+ /** One real turn on the engine in a hidden thread of this project: does it answer? */
173
+ "agent.tryEngine": (projectId: string | null, engineId: string) => EngineTry;
174
+ "agent.setPermissionMode": (projectId: string, threadId: string, mode: string) => ThreadSummary;
175
+ "agent.setEngine": (projectId: string, threadId: string, engineId: string) => ThreadSummary;
176
+ /** Engine option overrides for one thread; null clears them so the engine's settings apply again. */
177
+ "agent.setOptions": (projectId: string, threadId: string, options: Record<string, unknown> | null) => ThreadSummary;
178
+ "agent.playbooks": (projectId?: string) => PlaybookIndexEntry[];
179
+ }
180
+ interface Broadcasts {
181
+ "agent.event": (threadId: string, event: AgentEvent) => void;
182
+ }
183
+ }
184
+ export declare const AgentConfig: z.ZodPreprocess<z.ZodObject<{
185
+ mediaDir: z.ZodOptional<z.ZodString>;
186
+ playbooksDir: z.ZodOptional<z.ZodString>;
187
+ codeMaxWallMs: z.ZodDefault<z.ZodNumber>;
188
+ }, z.core.$strip>>;
189
+ export type AgentConfig = z.infer<typeof AgentConfig>;
190
+ export interface AgentSettings {
191
+ defaultEngine: string;
192
+ /** No longer read: a plan always waits for the person before it is written. */
193
+ confirmWritesOver: number;
194
+ /** A run stopped by a usage limit is continued once the limit lifts. */
195
+ resumeAfterLimit: boolean;
196
+ /** Wall-clock ceiling of one run in a conversation without a ticket; it pauses while a question waits. */
197
+ maxRunMinutes: number;
198
+ engines: Record<string, unknown>;
199
+ /** Per-playbook-id: which source wins when several carry the same id. Absent = most specific wins. */
200
+ playbookChoices: Record<string, PlaybookSource>;
201
+ }
202
+ export declare const AGENT_SECTION: SettingsSection<AgentSettings>;
203
+ export declare class Agent extends Service {
204
+ readonly config: AgentConfig;
205
+ static provide: string;
206
+ static inject: string[];
207
+ static Config: z.ZodPreprocess<z.ZodObject<{
208
+ mediaDir: z.ZodOptional<z.ZodString>;
209
+ playbooksDir: z.ZodOptional<z.ZodString>;
210
+ codeMaxWallMs: z.ZodDefault<z.ZodNumber>;
211
+ }, z.core.$strip>>;
212
+ store: AgentStore;
213
+ private engines;
214
+ private trying;
215
+ private tries;
216
+ private workflows;
217
+ private followUps;
218
+ private playbooks;
219
+ private readonly origins;
220
+ /** Playbooks read from each project's shared copy; refreshed when a run starts. */
221
+ private projectPlaybooks;
222
+ private permissions;
223
+ private permissionQueues;
224
+ private activeByThread;
225
+ private shownThread;
226
+ private activeById;
227
+ private mediaStore?;
228
+ private parkTimers;
229
+ /** A run Tomrade started because a limit lifted, and how many limited runs came before it. */
230
+ private limitTries;
231
+ private resuming;
232
+ constructor(ctx: Context, config: AgentConfig);
233
+ [Service.init](): () => void;
234
+ /** A registered engine, for a plugin that reads the contract (the troubleshooter). */
235
+ engineOf(id: string): Engine | undefined;
236
+ /** The registered engine that answers for an id: the id itself, or the engine that includes it. */
237
+ answering(id: string): string;
238
+ /** The engines a person chooses between: none that another engine took in. */
239
+ allEngines(): Engine[];
240
+ engine(engine: Engine): () => void;
241
+ /** A plugin names the conversations it opened (thread id → a short label in the person's language). */
242
+ threadOrigins(provider: ThreadOrigins): () => void;
243
+ /** The threads with what each plugin says opened them. */
244
+ withOrigins(projectId: string, threads: ThreadSummary[]): ThreadSummary[];
245
+ playbook(playbook: Omit<Playbook, "tools"> & {
246
+ tools?: string[];
247
+ }): () => void;
248
+ policy(policy: AgentPolicy): () => void;
249
+ /** Registers the spec and exposes it as a tool named `spec.id` on every surface. */
250
+ workflow<I, S>(spec: WorkflowSpec<I, S>): () => void;
251
+ /** Registers a check that runs after every turn; see TurnFollowUp. */
252
+ followUp(followUp: TurnFollowUp): () => void;
253
+ thread(projectId: string, threadId: string): ThreadDetail;
254
+ /** The thread the assistant panel last showed; it outlives the panel, so leaving a project keeps it. */
255
+ showing(): string | null;
256
+ show(threadId: string): void;
257
+ activities(): AgentActivity[];
258
+ private pendingView;
259
+ engineViews(fresh?: boolean): Promise<EngineView[]>;
260
+ playbookIndex(projectId?: string): PlaybookIndexEntry[];
261
+ /**
262
+ * `withinMs` is for callers that must not wait on the worker model (an agent creating a ticket):
263
+ * past it, or on any failure, the keyword fallback answers instead.
264
+ */
265
+ recommendPlaybook(projectId: string, input: {
266
+ title: string;
267
+ body?: string | null;
268
+ workType?: "coding" | "general";
269
+ }, opts?: {
270
+ withinMs?: number;
271
+ signal?: AbortSignal;
272
+ }): Promise<PlaybookRecommendation>;
273
+ openPlaybook(id: string, projectId?: string): string | null;
274
+ /**
275
+ * Re-read `.tomrade/playbooks/` from the project's shared copy, when the
276
+ * project has one. Called when a run starts and when the panel asks for the
277
+ * index — never fatal: a project without storage simply has none.
278
+ */
279
+ refreshProjectPlaybooks(projectId: string): Promise<void>;
280
+ /** Runs and their tokens and spend, per engine, today and over the last week — what Tomrade itself recorded, nothing asked of a provider. */
281
+ usage(now?: number): UsageSummary;
282
+ /** `<project folder>/.tomrade/rules.md`: text beside the project's playbooks and decisions, travelling with it. */
283
+ rulesFile(projectId: string): string | null;
284
+ /** The project's rules for every agent, as the person wrote them; null when there are none. */
285
+ rules(projectId: string): string | null;
286
+ /** Empty removes the file. Rules are the person's: only their edit, or a rule they confirmed, lands here. */
287
+ setRules(projectId: string, body: string): void;
288
+ settingsOf(): AgentSettings;
289
+ /** Read when a run starts, so a changed setting reaches the next run. */
290
+ private wallLimit;
291
+ /** The old global "conversation" model role becomes the loop engine's own profile override, so a multi-profile pick survives. */
292
+ private migrateConversationRole;
293
+ /** A removed model profile stops being an engine's pick, so the engine is back on its default rather than a dead id. */
294
+ private forgetRemovedProfiles;
295
+ newThread(projectId: string, engine?: string, boardId?: string | null, opts?: NewThreadOptions): ThreadSummary;
296
+ /** Options the engine's own schema accepts; ones it rejects are dropped rather than leaving a thread that cannot run. */
297
+ private acceptedOptions;
298
+ /** The engines that may take a code turn — a local coding agent, never the in-app model. */
299
+ codeEngines(): Engine[];
300
+ private generalStarts;
301
+ private generalTakeovers;
302
+ private generalScratch;
303
+ private executionCapabilities;
304
+ takeoverGeneral(projectId: string, itemId: string): Promise<{
305
+ stopped: number;
306
+ }>;
307
+ /** A plugin advertises an available capability; this never grants tool permissions. */
308
+ capability(name: string): () => void;
309
+ private withGeneralLock;
310
+ /** Everything Start does for a General ticket short of sending the first message — what a terminal session needs before the user's own CLI takes over. */
311
+ prepareGeneral(projectId: string, itemId: string, opts?: GeneralStartOptions): Promise<GeneralPreparation>;
312
+ /**
313
+ * The engine a General ticket runs on: the one named, else the first that can take one right now.
314
+ * Available is not enough: Tomrade with no model is available, but only to chat.
315
+ */
316
+ private generalEngine;
317
+ startGeneral(projectId: string, itemId: string, opts?: GeneralStartOptions): Promise<{
318
+ threadId: string;
319
+ runId: string;
320
+ continued: boolean;
321
+ }>;
322
+ private prepare;
323
+ /** Runs in flight right now, across projects — what a quit would interrupt. */
324
+ activeRuns(): {
325
+ run: RunView;
326
+ thread: ThreadSummary;
327
+ }[];
328
+ setWorkstream(projectId: string, threadId: string, boardId: string | null): ThreadSummary;
329
+ archiveThread(projectId: string, threadId: string): boolean;
330
+ /** `restored`: a person brought it back by hand, so automatic archiving leaves it alone from now on. */
331
+ unarchiveThread(projectId: string, threadId: string, opts?: {
332
+ restored?: boolean;
333
+ }): boolean;
334
+ /**
335
+ * One real turn on the engine, in a thread nobody sees, deleted after: does it answer now? Never part
336
+ * of readiness — it costs the person's quota and a flaky network would lock out an engine that works.
337
+ */
338
+ tryEngine(projectId: string, engineId: string, signal?: AbortSignal): Promise<EngineTry>;
339
+ /** The last try, while the engine's settings are the ones it ran with. */
340
+ private triedWith;
341
+ private runTry;
342
+ deleteThread(projectId: string, threadId: string): boolean;
343
+ /**
344
+ * Where the thread's engine works, resolved through the workstream's repository and this machine's checkout of it.
345
+ * Only a run or a Start may `create` a missing worktree; looking never does. A finished thread
346
+ * (archived, or its ticket done) gets its worktree back only through `revive` — a fresh Start.
347
+ */
348
+ workdir(projectId: string, threadId: string, opts?: {
349
+ create?: boolean;
350
+ revive?: boolean;
351
+ }): Promise<WorkdirStatus>;
352
+ setPermissionMode(projectId: string, threadId: string, mode: string): ThreadSummary;
353
+ setEngine(projectId: string, threadId: string, asked: string): ThreadSummary;
354
+ /** Engine knobs for this thread alone — what the Start dialog and the panel's quick picks write. Null puts the engine's settings back in charge. */
355
+ setOptions(projectId: string, threadId: string, options: Record<string, unknown> | null): ThreadSummary;
356
+ /** The session to resume: the engine's own, else one an engine it took in left on the thread. */
357
+ private resumeOf;
358
+ /** The engine's settings with the thread's overrides on top — what a turn runs with. */
359
+ optionsOf(thread: Pick<ThreadSummary, "options">, engineId: string): object;
360
+ private media;
361
+ uploadMedia(projectId: string, threadId: string, upload: MediaUpload): MediaRef;
362
+ readMedia(projectId: string, threadId: string, id: string): MediaContent;
363
+ /** Providers publish through the platform so MCP and the in-app loop share output. */
364
+ runSignal(runId: string): AbortSignal;
365
+ publishMedia(runId: string, upload: MediaUpload, caption?: string, source?: MediaRef): MediaRef;
366
+ /** A transport retries the same request id without starting a second run, even after restart. */
367
+ send(projectId: string, threadId: string, input: UserInput, delivery?: {
368
+ requestId: string;
369
+ source?: Record<string, string>;
370
+ }): {
371
+ runId: string;
372
+ };
373
+ /** A message for the running turn: held until the engine reaches a point where the model reads it. */
374
+ steer(projectId: string, threadId: string, steerId: string, text: string): Promise<boolean>;
375
+ /** Stop a suspended question without starting an engine just to clear it. */
376
+ cancelWaiting(projectId: string, threadId: string): boolean;
377
+ cancel(runId: string): boolean;
378
+ revert(projectId: string, runId: string): UndoRunResult;
379
+ /** Set or update a card (the dossier is one). Emits both the live card and a block in the stream the first time. */
380
+ setCard(runId: string, card: {
381
+ id: string;
382
+ schema: string;
383
+ title: string;
384
+ data: unknown;
385
+ }): CardView;
386
+ brief(projectId: string): string | null;
387
+ /** The words the platform writes into a conversation, in the person's language now. */
388
+ private words;
389
+ private makeRunCtx;
390
+ private itemOf;
391
+ /** The ticket a code thread implements, as the engine should hear about it. */
392
+ private ticketOf;
393
+ /** Open dependencies are the human's call at Start; once work is under way they are what the agent is told, not a reason to stop it. */
394
+ private waitsOn;
395
+ private drive;
396
+ /** The turn is over but the run is not: follow-ups may still write into it, or end it on a question. */
397
+ private runFollowUps;
398
+ /** Writes a workflow makes outside a tool call still belong to the run, so they count and revert with it. */
399
+ private asRun;
400
+ /**
401
+ * On-script input goes straight back into the thing that asked: a suspended
402
+ * workflow resumes deterministically, an engine's own question is simply
403
+ * cleared so the engine reads the answer. Off-script input leaves the
404
+ * question standing and hands the turn to the engine, which sees it in
405
+ * `TurnInput.pending` and holds resume_workflow / cancel_workflow.
406
+ */
407
+ private routePending;
408
+ private startWorkflow;
409
+ /** Control is code while a workflow runs: steps transition, ask, or finish; asks persist and suspend. */
410
+ private driveWorkflow;
411
+ private finish;
412
+ /** The thread waiting for a usage limit to lift, if it is. */
413
+ parked(projectId: string, threadId: string): ParkView | null;
414
+ /**
415
+ * Holds a thread until a usage limit lifts: the conversation says so, and at
416
+ * `until` Tomrade continues it — or, when it cannot (a terminal session, the
417
+ * setting off, too many limited runs in a row), says the person can.
418
+ */
419
+ park(projectId: string, threadId: string, limit: {
420
+ until: number | null;
421
+ runId?: string | null;
422
+ attempt?: number;
423
+ resume?: boolean; /** A terminal session's thread holds no conversation to write into. */
424
+ note?: boolean;
425
+ }): ParkView;
426
+ /** Someone continued the thread another way — a terminal session resumed by hand. */
427
+ unpark(projectId: string, threadId: string): void;
428
+ private clearPark;
429
+ private armParked;
430
+ private armPark;
431
+ private liftPark;
432
+ private appendAndBroadcast;
433
+ private broadcast;
434
+ private registerPlatformTools;
435
+ /** `<playbooksDir>/<name>/SKILL.md` — the user slot; a plugin's id may coexist. */
436
+ /**
437
+ * The user's playbooks live in the Hub folder (`<Hub>/playbooks/`) and travel
438
+ * with it; the configured `playbooksDir` is where they lived before the Hub
439
+ * existed, and is moved into the Hub the first time one is chosen.
440
+ */
441
+ private userPlaybooksDir;
442
+ private migratePlaybooksIntoHub;
443
+ loadUserPlaybooks(): void;
444
+ }
445
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Words for the steps an engine takes with its own tools (a shell command, a
3
+ * file edit), so the panel shows what each one did, not just that it ran.
4
+ */
5
+ /** The argument that says most about a call: the command, the file, the pattern. */
6
+ export declare function stepHint(args: unknown, max?: number): string;
7
+ /** The first line of a result that says something. */
8
+ export declare function resultLine(output: unknown, max?: number): string;
9
+ /** The one line under a step: what it was asked to do, and why it failed when it did. */
10
+ export declare function stepSummary(args: unknown, output: unknown, ok: boolean): string;
11
+ //# sourceMappingURL=steps.d.ts.map
@@ -0,0 +1,188 @@
1
+ import type { Db } from "@tomradex/db";
2
+ import type { Author } from "@tomradex/domain";
3
+ import type { Block, CardView, GeneralExecution, ParkView, PendingView, RunStatus, RunView, ThreadMessage, ThreadSummary, ThreadSurface } from "./types.js";
4
+ export declare const MIGRATIONS: {
5
+ version: number;
6
+ up: string;
7
+ }[];
8
+ export interface PendingRecord extends PendingView {
9
+ step: string | null;
10
+ state: unknown;
11
+ waiveRule: string | null;
12
+ waiveOn: string[];
13
+ }
14
+ /**
15
+ * Table declarations for `ctx.db.migrate`: how each table's rows belong to a
16
+ * project (deletion, the legacy split and export all read this), and whether
17
+ * they travel in the project's folder. Cards and pending questions are one
18
+ * machine's working state and never leave it. Conversations are `private`:
19
+ * they never travel to a public repository.
20
+ */
21
+ export declare const TABLES: {
22
+ readonly plugin_agent_threads: {
23
+ readonly project: "project_id";
24
+ readonly export: {
25
+ readonly order: "id";
26
+ readonly private: true;
27
+ };
28
+ };
29
+ readonly plugin_agent_messages: {
30
+ readonly project: {
31
+ readonly via: "thread_id";
32
+ readonly table: "plugin_agent_threads";
33
+ };
34
+ readonly export: {
35
+ readonly order: "thread_id, seq";
36
+ readonly appendOnly: true;
37
+ readonly private: true;
38
+ };
39
+ };
40
+ readonly plugin_agent_runs: {
41
+ readonly project: {
42
+ readonly via: "thread_id";
43
+ readonly table: "plugin_agent_threads";
44
+ };
45
+ readonly export: {
46
+ readonly order: "thread_id, started_at";
47
+ readonly private: true;
48
+ };
49
+ };
50
+ readonly plugin_agent_ledger: {
51
+ readonly project: {
52
+ readonly via: "run_id";
53
+ readonly table: "plugin_agent_runs";
54
+ };
55
+ readonly export: {
56
+ readonly order: "run_id, seq";
57
+ readonly appendOnly: true;
58
+ readonly private: true;
59
+ };
60
+ };
61
+ readonly plugin_agent_cards: {
62
+ readonly project: {
63
+ readonly via: "thread_id";
64
+ readonly table: "plugin_agent_threads";
65
+ };
66
+ readonly export: false;
67
+ };
68
+ readonly plugin_agent_pending: {
69
+ readonly project: {
70
+ readonly via: "thread_id";
71
+ readonly table: "plugin_agent_threads";
72
+ };
73
+ readonly export: false;
74
+ };
75
+ readonly plugin_agent_briefs: {
76
+ readonly project: "project_id";
77
+ readonly export: {
78
+ readonly order: "project_id";
79
+ };
80
+ };
81
+ readonly plugin_agent_inputs: {
82
+ readonly project: {
83
+ readonly via: "thread_id";
84
+ readonly table: "plugin_agent_threads";
85
+ };
86
+ readonly export: false;
87
+ };
88
+ readonly plugin_agent_parked: {
89
+ readonly project: {
90
+ readonly via: "thread_id";
91
+ readonly table: "plugin_agent_threads";
92
+ };
93
+ readonly export: false;
94
+ };
95
+ };
96
+ /** Threads live in their project's file, so every read and write names the project first. */
97
+ export declare class AgentStore {
98
+ private readonly pool;
99
+ /** The engine that answers for a stored engine id — another engine may have taken it in. */
100
+ private readonly engineOf;
101
+ constructor(pool: Db,
102
+ /** The engine that answers for a stored engine id — another engine may have taken it in. */
103
+ engineOf?: (id: string) => string);
104
+ private toThread;
105
+ private db;
106
+ transaction<T>(projectId: string, work: () => T): T;
107
+ inputReceipt(projectId: string, requestId: string): {
108
+ thread_id: string;
109
+ fingerprint: string;
110
+ run_id: string;
111
+ } | undefined;
112
+ recordInput(projectId: string, requestId: string, threadId: string, fingerprint: string, runId: string, source?: Record<string, string>): void;
113
+ threads(projectId: string, surface?: ThreadSurface | ThreadSurface[] | null): ThreadSummary[];
114
+ /** Put away, newest first — so a conversation archived by mistake can be found and brought back. */
115
+ archivedThreads(projectId: string, surface?: ThreadSurface | ThreadSurface[]): ThreadSummary[];
116
+ setArchived(projectId: string, id: string, archived: boolean, opts?: {
117
+ restored?: boolean;
118
+ }): void;
119
+ deleteThread(projectId: string, id: string): void;
120
+ thread(projectId: string, id: string): ThreadSummary | null;
121
+ setExecution(projectId: string, threadId: string, execution: GeneralExecution): void;
122
+ recordPlaybook(projectId: string, runId: string, playbook: GeneralExecution["playbook"]): void;
123
+ createThread(projectId: string, engine: string, title?: string, boardId?: string | null, itemId?: string | null, branch?: string | null, surface?: ThreadSurface): ThreadSummary;
124
+ touchThread(projectId: string, id: string, fields?: {
125
+ title?: string;
126
+ engine?: string;
127
+ boardId?: string | null;
128
+ itemId?: string | null;
129
+ branch?: string | null;
130
+ surface?: ThreadSurface;
131
+ permissionMode?: string | null;
132
+ options?: Record<string, unknown> | null;
133
+ }): void;
134
+ /** Threads started from a ticket, newest first. */
135
+ threadsForItem(projectId: string, itemId: string): ThreadSummary[];
136
+ external(projectId: string, threadId: string): Record<string, string>;
137
+ /** Null forgets the engine's session. */
138
+ setExternal(projectId: string, threadId: string, engine: string, sessionId: string | null): void;
139
+ waivers(projectId: string, threadId: string): Set<string>;
140
+ addWaiver(projectId: string, threadId: string, rule: string): void;
141
+ messages(projectId: string, threadId: string): ThreadMessage[];
142
+ appendMessage(projectId: string, threadId: string, role: ThreadMessage["role"], block: Block, runId: string | null): ThreadMessage;
143
+ runs(projectId: string, threadId: string): RunView[];
144
+ run(projectId: string, id: string): RunView | null;
145
+ /** The project a run belongs to, searched across open files — for a caller that only holds the run id. */
146
+ projectOfRun(runId: string): string | null;
147
+ /** Runs left mid-flight by a killed process — no in-memory controller will ever cancel them. */
148
+ reconcileInterrupted(projectId: string, note?: string): void;
149
+ beginRun(projectId: string, threadId: string, engine: string): RunView;
150
+ endRun(projectId: string, id: string, fields: {
151
+ status: RunStatus;
152
+ stop?: string;
153
+ error?: string;
154
+ usage?: {
155
+ inputTokens?: number;
156
+ outputTokens?: number;
157
+ };
158
+ metrics?: RunView["metrics"];
159
+ costUsd?: number;
160
+ changes: number;
161
+ }): RunView;
162
+ markReverted(projectId: string, id: string, kept: number): RunView;
163
+ appendLedger(projectId: string, runId: string, seq: number, tool: string, args: unknown, ok: boolean, summary: string, ms: number): void;
164
+ cards(projectId: string, threadId: string): CardView[];
165
+ setCard(projectId: string, threadId: string, card: {
166
+ id: string;
167
+ schema: string;
168
+ title: string;
169
+ data: unknown;
170
+ }, by: Author | "human"): CardView;
171
+ pending(projectId: string, threadId: string): PendingRecord | null;
172
+ setPending(projectId: string, threadId: string, record: Omit<PendingRecord, "threadId">): void;
173
+ clearPending(projectId: string, threadId: string): void;
174
+ parked(projectId: string, threadId: string): ParkView | null;
175
+ allParked(projectId: string): ParkView[];
176
+ park(projectId: string, park: ParkView): void;
177
+ unpark(projectId: string, threadId: string): boolean;
178
+ brief(projectId: string): string | null;
179
+ setBrief(projectId: string, body: string): void;
180
+ changesOf(projectId: string, runId: string): number;
181
+ /** What the run netted out to, per entity — the facts behind "Changed N". */
182
+ changedEntities(projectId: string, runId: string): {
183
+ key: string;
184
+ label: string;
185
+ op: "created" | "updated" | "deleted";
186
+ }[];
187
+ }
188
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1,21 @@
1
+ import type { TurnMetrics } from "./evaluation.js";
2
+ /**
3
+ * A run's tokens the one way a ticket's conversation and the Usage pane both
4
+ * show them. Input is every prompt token the model read: new (uncached) +
5
+ * cache read + cache write, so the three parts always add up to it. Engines
6
+ * that report no cache counters count all their input as new.
7
+ */
8
+ export interface TokenBreakdown {
9
+ inputTokens: number;
10
+ uncachedInputTokens: number;
11
+ cacheReadTokens: number;
12
+ cacheWriteTokens: number;
13
+ outputTokens: number;
14
+ }
15
+ export declare const NO_TOKENS: TokenBreakdown;
16
+ export declare function runTokens(usage: {
17
+ inputTokens?: number;
18
+ outputTokens?: number;
19
+ } | null | undefined, metrics?: Pick<TurnMetrics, "cacheReadTokens" | "cacheWriteTokens"> | null): TokenBreakdown | null;
20
+ export declare const addTokens: (a: TokenBreakdown, b: TokenBreakdown | null) => TokenBreakdown;
21
+ //# sourceMappingURL=tokens.d.ts.map