talon-agent 1.48.0 → 1.50.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.
- package/README.md +3 -0
- package/package.json +1 -1
- package/src/backend/codex/factory.ts +2 -0
- package/src/backend/codex/handler/message.ts +10 -0
- package/src/backend/kilo/factory.ts +2 -0
- package/src/backend/kilo/handler/message.ts +10 -0
- package/src/backend/openai-agents/factory.ts +2 -0
- package/src/backend/openai-agents/handler/message.ts +10 -0
- package/src/backend/opencode/factory.ts +2 -0
- package/src/backend/opencode/handler/message.ts +10 -0
- package/src/backend/shared/index.ts +4 -0
- package/src/backend/shared/turn-interrupt.ts +61 -0
- package/src/bootstrap.ts +12 -5
- package/src/cli/daemon-api.ts +39 -0
- package/src/cli/events.ts +133 -0
- package/src/cli/fs.ts +138 -0
- package/src/cli/index.ts +36 -2
- package/src/cli/tasks.ts +82 -48
- package/src/core/bus/bus.ts +114 -0
- package/src/core/bus/events.ts +64 -0
- package/src/core/bus/index.ts +20 -0
- package/src/core/engine/gateway-actions/index.ts +3 -0
- package/src/core/engine/gateway-actions/native.ts +151 -24
- package/src/core/engine/gateway-actions/vfs.ts +69 -0
- package/src/core/engine/gateway.ts +46 -0
- package/src/core/tasks/table.ts +25 -4
- package/src/core/tasks/types.ts +6 -2
- package/src/core/tools/index.ts +2 -0
- package/src/core/tools/native.ts +19 -6
- package/src/core/tools/types.ts +2 -1
- package/src/core/tools/vfs.ts +61 -0
- package/src/core/vfs/index.ts +113 -0
- package/src/core/vfs/mounts/files.ts +154 -0
- package/src/core/vfs/mounts/plugins.ts +72 -0
- package/src/core/vfs/mounts/proc.ts +125 -0
- package/src/core/vfs/types.ts +103 -0
- package/src/core/vfs/vfs.ts +237 -0
- package/src/core/weaver/weaver.ts +62 -15
- package/src/frontend/native/discovery.ts +3 -0
- package/src/frontend/native/index.ts +10 -1
- package/src/frontend/native/server.ts +65 -6
- package/src/frontend/native/tls.ts +313 -0
- package/src/storage/journal.ts +91 -0
- package/src/storage/repositories/journal-repo.ts +38 -0
- package/src/storage/sql/journal.sql +16 -0
- package/src/storage/sql/schema.sql +15 -0
- package/src/storage/sql/statements.generated.ts +24 -1
- package/src/util/config.ts +9 -0
- package/src/util/log.ts +2 -0
- package/src/util/paths.ts +2 -0
package/src/cli/index.ts
CHANGED
|
@@ -31,6 +31,8 @@ import { runDoctor } from "./doctor.js";
|
|
|
31
31
|
import { startChat } from "./chat.js";
|
|
32
32
|
import { daemonStart, daemonStop, daemonRestart } from "./daemon.js";
|
|
33
33
|
import { showTasks, killTask } from "./tasks.js";
|
|
34
|
+
import { showEvents } from "./events.js";
|
|
35
|
+
import { showLs, showCat } from "./fs.js";
|
|
34
36
|
import { mainMenu } from "./menu.js";
|
|
35
37
|
|
|
36
38
|
export * from "./context.js";
|
|
@@ -58,6 +60,9 @@ const CLI_COMMANDS = [
|
|
|
58
60
|
"doctor",
|
|
59
61
|
"ps",
|
|
60
62
|
"kill",
|
|
63
|
+
"events",
|
|
64
|
+
"ls",
|
|
65
|
+
"cat",
|
|
61
66
|
];
|
|
62
67
|
|
|
63
68
|
/** Route a `talon <command>` invocation. Called by the entry point. */
|
|
@@ -100,11 +105,29 @@ export async function runCli(): Promise<void> {
|
|
|
100
105
|
runDoctor();
|
|
101
106
|
break;
|
|
102
107
|
case "ps":
|
|
103
|
-
await showTasks();
|
|
108
|
+
await showTasks(process.argv[3] === "--all" || process.argv[3] === "-a");
|
|
104
109
|
break;
|
|
105
110
|
case "kill":
|
|
106
111
|
await killTask(process.argv[3]);
|
|
107
112
|
break;
|
|
113
|
+
case "events": {
|
|
114
|
+
const args = process.argv.slice(3);
|
|
115
|
+
const historyAt = args.findIndex((a) => a === "--history");
|
|
116
|
+
const next = historyAt >= 0 ? Number(args[historyAt + 1]) : NaN;
|
|
117
|
+
await showEvents({
|
|
118
|
+
follow: args.includes("-f") || args.includes("--follow"),
|
|
119
|
+
...(historyAt >= 0
|
|
120
|
+
? { history: Number.isInteger(next) && next > 0 ? next : 100 }
|
|
121
|
+
: {}),
|
|
122
|
+
});
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case "ls":
|
|
126
|
+
await showLs(process.argv[3]);
|
|
127
|
+
break;
|
|
128
|
+
case "cat":
|
|
129
|
+
await showCat(process.argv[3]);
|
|
130
|
+
break;
|
|
108
131
|
case "--version":
|
|
109
132
|
case "-v": {
|
|
110
133
|
console.log(pkg.version);
|
|
@@ -122,8 +145,19 @@ export async function runCli(): Promise<void> {
|
|
|
122
145
|
console.log(` ${pc.cyan("run")} Run in foreground (attached)`);
|
|
123
146
|
console.log(` ${pc.cyan("chat")} Terminal chat mode`);
|
|
124
147
|
console.log(` ${pc.cyan("status")} Show bot health`);
|
|
125
|
-
console.log(
|
|
148
|
+
console.log(
|
|
149
|
+
` ${pc.cyan("ps")} List agent tasks (--all includes journal history)`,
|
|
150
|
+
);
|
|
126
151
|
console.log(` ${pc.cyan("kill")} Abort a killable task by id`);
|
|
152
|
+
console.log(
|
|
153
|
+
` ${pc.cyan("events")} Tail the event bus (-f follows, --history [N] reads the journal)`,
|
|
154
|
+
);
|
|
155
|
+
console.log(
|
|
156
|
+
` ${pc.cyan("ls")} List the talon:// namespace (ls proc/tasks)`,
|
|
157
|
+
);
|
|
158
|
+
console.log(
|
|
159
|
+
` ${pc.cyan("cat")} Read a talon:// file (cat proc/events)`,
|
|
160
|
+
);
|
|
127
161
|
console.log(` ${pc.cyan("config")} View/edit configuration`);
|
|
128
162
|
console.log(` ${pc.cyan("logs")} Tail log file`);
|
|
129
163
|
console.log(` ${pc.cyan("doctor")} Validate environment`);
|
package/src/cli/tasks.ts
CHANGED
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
* `talon ps` / `talon kill` — the task table from the outside.
|
|
3
3
|
*
|
|
4
4
|
* Data comes from the running daemon's gateway (`GET /tasks`,
|
|
5
|
-
* `POST /tasks/kill`);
|
|
6
|
-
*
|
|
7
|
-
* lives in
|
|
5
|
+
* `POST /tasks/kill`); `--all` also folds in settled runs from the
|
|
6
|
+
* durable journal in talon.db, which answers across restarts — with or
|
|
7
|
+
* without a daemon. This module only renders — the table lives in
|
|
8
|
+
* core/tasks/, the journal in storage/journal.ts.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import pc from "picocolors";
|
|
11
12
|
import { findRunningInstance } from "../core/daemon/discovery.js";
|
|
13
|
+
import { fetchGateway, requireGatewayPort } from "./daemon-api.js";
|
|
12
14
|
import type {
|
|
13
15
|
KillOutcome,
|
|
14
16
|
TaskRecord,
|
|
15
17
|
TaskState,
|
|
16
18
|
} from "../core/tasks/index.js";
|
|
17
19
|
|
|
18
|
-
const REQUEST_TIMEOUT_MS = 3_000;
|
|
19
|
-
|
|
20
20
|
function formatDuration(ms: number): string {
|
|
21
21
|
const seconds = Math.floor(ms / 1000);
|
|
22
22
|
if (seconds < 60) return `${seconds}s`;
|
|
@@ -66,7 +66,12 @@ function displayOrder(tasks: TaskRecord[]): TaskRecord[] {
|
|
|
66
66
|
(t) => t.state !== "running" && t.state !== "queued",
|
|
67
67
|
);
|
|
68
68
|
live.sort((a, b) => a.id - b.id);
|
|
69
|
-
|
|
69
|
+
// By time, not id: journal history spans daemon restarts, and per-process
|
|
70
|
+
// ids restart with the daemon.
|
|
71
|
+
settled.sort(
|
|
72
|
+
(a, b) =>
|
|
73
|
+
(b.endedAt ?? b.queuedAt) - (a.endedAt ?? a.queuedAt) || b.id - a.id,
|
|
74
|
+
);
|
|
70
75
|
return [...live, ...settled];
|
|
71
76
|
}
|
|
72
77
|
|
|
@@ -107,56 +112,85 @@ function renderTable(tasks: TaskRecord[], now: number): void {
|
|
|
107
112
|
console.log();
|
|
108
113
|
}
|
|
109
114
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
): Promise<
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (!instance) {
|
|
127
|
-
console.log(` ${pc.red("●")} Talon is not running\n`);
|
|
128
|
-
return null;
|
|
115
|
+
/** Settled runs from the journal, minus any already in the live table. */
|
|
116
|
+
async function journalTasks(
|
|
117
|
+
liveTasks: readonly TaskRecord[],
|
|
118
|
+
limit: number,
|
|
119
|
+
): Promise<TaskRecord[]> {
|
|
120
|
+
const { readJournal } = await import("../storage/journal.js");
|
|
121
|
+
const seen = new Set(liveTasks.map((t) => `${t.id}:${t.queuedAt}`));
|
|
122
|
+
const historical: TaskRecord[] = [];
|
|
123
|
+
for (const entry of readJournal({ type: "task.settled", limit })) {
|
|
124
|
+
if (entry.event.type !== "task.settled") continue;
|
|
125
|
+
const task = entry.event.task;
|
|
126
|
+
// (id, queuedAt) identifies a run across surfaces — per-process ids
|
|
127
|
+
// repeat between daemon runs, enqueue times don't.
|
|
128
|
+
if (seen.has(`${task.id}:${task.queuedAt}`)) continue;
|
|
129
|
+
seen.add(`${task.id}:${task.queuedAt}`);
|
|
130
|
+
historical.push(task);
|
|
129
131
|
}
|
|
130
|
-
|
|
131
|
-
console.log(
|
|
132
|
-
` ${pc.yellow("●")} Talon is running (PID ${instance.pid}) but its gateway port is unknown — possibly still starting.\n`,
|
|
133
|
-
);
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
return instance.port;
|
|
132
|
+
return historical;
|
|
137
133
|
}
|
|
138
134
|
|
|
139
|
-
export async function showTasks(): Promise<void> {
|
|
135
|
+
export async function showTasks(all = false): Promise<void> {
|
|
140
136
|
console.log();
|
|
141
|
-
const port = await requireGatewayPort();
|
|
142
|
-
if (port === null) return;
|
|
143
137
|
|
|
144
|
-
let tasks: TaskRecord[];
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
138
|
+
let tasks: TaskRecord[] = [];
|
|
139
|
+
let daemonUp = true;
|
|
140
|
+
if (all) {
|
|
141
|
+
// History works without a daemon — fold in the live table only when
|
|
142
|
+
// one is actually running.
|
|
143
|
+
const instance = await findRunningInstance();
|
|
144
|
+
if (instance?.port) {
|
|
145
|
+
try {
|
|
146
|
+
const body = (await fetchGateway(instance.port, "/tasks")) as {
|
|
147
|
+
tasks?: TaskRecord[];
|
|
148
|
+
};
|
|
149
|
+
tasks = body.tasks ?? [];
|
|
150
|
+
} catch {
|
|
151
|
+
daemonUp = false;
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
daemonUp = false;
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
const port = await requireGatewayPort();
|
|
158
|
+
if (port === null) return;
|
|
159
|
+
try {
|
|
160
|
+
const body = (await fetchGateway(port, "/tasks")) as {
|
|
161
|
+
tasks?: TaskRecord[];
|
|
162
|
+
};
|
|
163
|
+
tasks = body.tasks ?? [];
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.log(
|
|
166
|
+
` ${pc.red("✖")} Could not read the task table: ${err instanceof Error ? err.message : err}\n`,
|
|
167
|
+
);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (all) {
|
|
173
|
+
if (!daemonUp) {
|
|
174
|
+
console.log(
|
|
175
|
+
` ${pc.dim("Talon is not running — journal history only.")}`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
tasks = [...tasks, ...(await journalTasks(tasks, 200))];
|
|
180
|
+
} catch (err) {
|
|
181
|
+
console.log(
|
|
182
|
+
` ${pc.red("✖")} Could not read the journal: ${err instanceof Error ? err.message : err}\n`,
|
|
183
|
+
);
|
|
184
|
+
process.exitCode = 1;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
155
187
|
}
|
|
156
188
|
|
|
157
189
|
if (tasks.length === 0) {
|
|
158
190
|
console.log(
|
|
159
|
-
|
|
191
|
+
all
|
|
192
|
+
? ` ${pc.dim("No tasks — nothing live and the journal is empty.")}\n`
|
|
193
|
+
: ` ${pc.dim("No tasks — the table starts empty on each daemon start. Older runs: talon ps --all")}\n`,
|
|
160
194
|
);
|
|
161
195
|
return;
|
|
162
196
|
}
|
|
@@ -205,7 +239,7 @@ export async function killTask(rawId: string | undefined): Promise<void> {
|
|
|
205
239
|
return;
|
|
206
240
|
case "not-killable":
|
|
207
241
|
console.log(
|
|
208
|
-
` ${pc.yellow("!")} Task ${id} has no abort hook
|
|
242
|
+
` ${pc.yellow("!")} Task ${id} has no abort hook — its backend can't interrupt a running turn.\n`,
|
|
209
243
|
);
|
|
210
244
|
return;
|
|
211
245
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TalonBus — the daemon's internal event spine.
|
|
3
|
+
*
|
|
4
|
+
* One instance (`bus`) serves the whole process. Producers publish typed
|
|
5
|
+
* events (see events.ts); consumers subscribe by type. The bus is the seam
|
|
6
|
+
* that lets subsystems react to each other without importing each other —
|
|
7
|
+
* bootstrap wires dream and pulse as subscribers instead of threading
|
|
8
|
+
* callbacks through the Weaver, and future work (triggers as subscribers,
|
|
9
|
+
* companion task feeds, mesh presence) lands as more publishers/consumers.
|
|
10
|
+
*
|
|
11
|
+
* Delivery contract:
|
|
12
|
+
* - Synchronous, in publish order, to every matching subscriber.
|
|
13
|
+
* - Fire-and-forget: a subscriber that throws (or returns a rejecting
|
|
14
|
+
* promise) is logged and never affects the publisher or its peers.
|
|
15
|
+
* - Subscribers must not block: anything slow belongs behind its own
|
|
16
|
+
* queue or timer, not inside a handler.
|
|
17
|
+
*
|
|
18
|
+
* The bus also keeps a bounded ring of recent events with monotonic ids —
|
|
19
|
+
* the observability surface behind the gateway's `/events/recent` and
|
|
20
|
+
* `talon events`. In-memory only, like the task table: events describe
|
|
21
|
+
* moments in a live process, so there is nothing truthful to persist.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { logError } from "../../util/log.js";
|
|
25
|
+
import type { PublishedEvent, TalonEvent, TalonEventType } from "./events.js";
|
|
26
|
+
|
|
27
|
+
/** Recent events kept for the tail surfaces. */
|
|
28
|
+
const DEFAULT_RECENT_LIMIT = 200;
|
|
29
|
+
|
|
30
|
+
type Handler<T extends TalonEventType> = (
|
|
31
|
+
event: Extract<PublishedEvent, { type: T }>,
|
|
32
|
+
) => void | Promise<void>;
|
|
33
|
+
|
|
34
|
+
type AnyHandler = (event: PublishedEvent) => void | Promise<void>;
|
|
35
|
+
|
|
36
|
+
export class TalonBus {
|
|
37
|
+
private readonly byType = new Map<TalonEventType, Set<AnyHandler>>();
|
|
38
|
+
private readonly all = new Set<AnyHandler>();
|
|
39
|
+
private readonly ring: PublishedEvent[] = [];
|
|
40
|
+
private readonly recentLimit: number;
|
|
41
|
+
private nextId = 1;
|
|
42
|
+
|
|
43
|
+
constructor(recentLimit = DEFAULT_RECENT_LIMIT) {
|
|
44
|
+
this.recentLimit = recentLimit;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Subscribe to one event type. Returns the unsubscribe function. */
|
|
48
|
+
subscribe<T extends TalonEventType>(
|
|
49
|
+
type: T,
|
|
50
|
+
handler: Handler<T>,
|
|
51
|
+
): () => void {
|
|
52
|
+
const set = this.byType.get(type) ?? new Set<AnyHandler>();
|
|
53
|
+
this.byType.set(type, set);
|
|
54
|
+
const anyHandler = handler as AnyHandler;
|
|
55
|
+
set.add(anyHandler);
|
|
56
|
+
return () => {
|
|
57
|
+
set.delete(anyHandler);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Subscribe to every event (tails, forwarders). Returns unsubscribe. */
|
|
62
|
+
subscribeAll(handler: AnyHandler): () => void {
|
|
63
|
+
this.all.add(handler);
|
|
64
|
+
return () => {
|
|
65
|
+
this.all.delete(handler);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Stamp and deliver an event. Never throws. */
|
|
70
|
+
publish(event: TalonEvent): PublishedEvent {
|
|
71
|
+
const published: PublishedEvent = {
|
|
72
|
+
...event,
|
|
73
|
+
id: this.nextId++,
|
|
74
|
+
at: Date.now(),
|
|
75
|
+
};
|
|
76
|
+
this.ring.push(published);
|
|
77
|
+
if (this.ring.length > this.recentLimit) {
|
|
78
|
+
this.ring.splice(0, this.ring.length - this.recentLimit);
|
|
79
|
+
}
|
|
80
|
+
for (const handler of this.byType.get(event.type) ?? []) {
|
|
81
|
+
this.deliver(handler, published);
|
|
82
|
+
}
|
|
83
|
+
for (const handler of this.all) {
|
|
84
|
+
this.deliver(handler, published);
|
|
85
|
+
}
|
|
86
|
+
return published;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Recent events, id-ascending — all of the ring, or only those after
|
|
91
|
+
* `sinceId` (the tail-follow cursor).
|
|
92
|
+
*/
|
|
93
|
+
recent(sinceId = 0): PublishedEvent[] {
|
|
94
|
+
return sinceId > 0
|
|
95
|
+
? this.ring.filter((event) => event.id > sinceId)
|
|
96
|
+
: [...this.ring];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private deliver(handler: AnyHandler, event: PublishedEvent): void {
|
|
100
|
+
try {
|
|
101
|
+
const result = handler(event);
|
|
102
|
+
if (result instanceof Promise) {
|
|
103
|
+
result.catch((err: unknown) => {
|
|
104
|
+
logError("bus", `Subscriber for ${event.type} rejected`, err);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
} catch (err) {
|
|
108
|
+
logError("bus", `Subscriber for ${event.type} threw`, err);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The daemon-wide bus. Tests needing isolation construct their own. */
|
|
114
|
+
export const bus = new TalonBus();
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event vocabulary — every event the Talon bus carries.
|
|
3
|
+
*
|
|
4
|
+
* The union is deliberately small and honest: a type exists here only when
|
|
5
|
+
* something in the runtime actually publishes it. Growing the vocabulary is
|
|
6
|
+
* a one-line union change; each addition should land together with its
|
|
7
|
+
* publisher (and ideally its first subscriber).
|
|
8
|
+
*
|
|
9
|
+
* Two families exist today:
|
|
10
|
+
*
|
|
11
|
+
* - `task.*` — lifecycle of agent work, published by the task table
|
|
12
|
+
* (core/tasks). Uniform across kinds: a heartbeat pass, a dream run, an
|
|
13
|
+
* isolated cron/trigger job, and a chat turn all surface here.
|
|
14
|
+
* - `turn.*` — the chat-domain moments inside a turn that other
|
|
15
|
+
* subsystems key off: `turn.started` fires once the warp is bound and
|
|
16
|
+
* the backend is about to run (never for a no-model refusal);
|
|
17
|
+
* `turn.completed` fires only for a successfully finished turn.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { TaskRecord } from "../tasks/types.js";
|
|
21
|
+
|
|
22
|
+
/** A task left the queue and began running. */
|
|
23
|
+
export interface TaskStartedEvent {
|
|
24
|
+
readonly type: "task.started";
|
|
25
|
+
readonly task: TaskRecord;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A task reached a terminal state (done / failed / killed). */
|
|
29
|
+
export interface TaskSettledEvent {
|
|
30
|
+
readonly type: "task.settled";
|
|
31
|
+
readonly task: TaskRecord;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A chat turn bound its warp and is about to run on the backend. */
|
|
35
|
+
export interface TurnStartedEvent {
|
|
36
|
+
readonly type: "turn.started";
|
|
37
|
+
readonly chatId: string;
|
|
38
|
+
readonly source: string;
|
|
39
|
+
readonly model: string;
|
|
40
|
+
readonly backendId: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A chat turn finished successfully (refusals and failures never emit this). */
|
|
44
|
+
export interface TurnCompletedEvent {
|
|
45
|
+
readonly type: "turn.completed";
|
|
46
|
+
readonly chatId: string;
|
|
47
|
+
readonly source: string;
|
|
48
|
+
readonly durationMs: number;
|
|
49
|
+
readonly inputTokens: number;
|
|
50
|
+
readonly outputTokens: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type TalonEvent =
|
|
54
|
+
TaskStartedEvent | TaskSettledEvent | TurnStartedEvent | TurnCompletedEvent;
|
|
55
|
+
|
|
56
|
+
export type TalonEventType = TalonEvent["type"];
|
|
57
|
+
|
|
58
|
+
/** What subscribers receive: the event plus its bus stamp. */
|
|
59
|
+
export type PublishedEvent = TalonEvent & {
|
|
60
|
+
/** Monotonic per-process sequence number — the tail cursor. */
|
|
61
|
+
readonly id: number;
|
|
62
|
+
/** Publish time, epoch ms. */
|
|
63
|
+
readonly at: number;
|
|
64
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event bus — public surface.
|
|
3
|
+
*
|
|
4
|
+
* See bus.ts for the spine and events.ts for the vocabulary. Publishers
|
|
5
|
+
* today: the task table (task.*) and the Weaver (turn.*). Subscribers are
|
|
6
|
+
* wired at the composition root (bootstrap: dream on turn.started, pulse on
|
|
7
|
+
* turn.completed); read surfaces: gateway `GET /events/recent`, CLI
|
|
8
|
+
* `talon events`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { TalonBus, bus } from "./bus.js";
|
|
12
|
+
export type {
|
|
13
|
+
PublishedEvent,
|
|
14
|
+
TalonEvent,
|
|
15
|
+
TalonEventType,
|
|
16
|
+
TaskSettledEvent,
|
|
17
|
+
TaskStartedEvent,
|
|
18
|
+
TurnCompletedEvent,
|
|
19
|
+
TurnStartedEvent,
|
|
20
|
+
} from "./events.js";
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* - `plugins` — plugin hot-reload
|
|
18
18
|
* - `models` — model / backend discovery
|
|
19
19
|
* - `mesh` — companion device mesh (presence + location)
|
|
20
|
+
* - `vfs` — the unified talon:// namespace (list/read/write)
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
import type { ActionResult } from "../../types.js";
|
|
@@ -33,6 +34,7 @@ import { pluginHandlers } from "./plugins.js";
|
|
|
33
34
|
import { modelHandlers } from "./models.js";
|
|
34
35
|
import { meshHandlers } from "./mesh.js";
|
|
35
36
|
import { nativeHandlers } from "./native.js";
|
|
37
|
+
import { vfsHandlers } from "./vfs.js";
|
|
36
38
|
|
|
37
39
|
// Null-prototype so a request `action` of "toString" / "constructor" / etc.
|
|
38
40
|
// can't resolve an inherited Object.prototype method — `handlers[action]` only
|
|
@@ -49,6 +51,7 @@ const handlers: SharedActionHandlers = Object.assign(Object.create(null), {
|
|
|
49
51
|
...modelHandlers,
|
|
50
52
|
...meshHandlers,
|
|
51
53
|
...nativeHandlers,
|
|
54
|
+
...vfsHandlers,
|
|
52
55
|
});
|
|
53
56
|
|
|
54
57
|
export async function handleSharedAction(
|