pi-roundtable 0.7.19 → 0.8.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/CHANGELOG.md +38 -0
- package/README.md +44 -11
- package/README.zh-TW.md +44 -9
- package/docs/migrating-0.8.md +132 -0
- package/docs/plugins.md +267 -23
- package/examples/discord-adapter.test.ts +42 -0
- package/examples/discord-adapter.ts +26 -0
- package/examples/echo-runtime.ts +1 -1
- package/examples/headless.test.ts +72 -0
- package/examples/headless.ts +31 -0
- package/examples/http.test.ts +31 -1
- package/examples/http.ts +39 -0
- package/package.json +3 -2
- package/src/cli/cli.ts +15 -4
- package/src/cli/doctor.ts +21 -5
- package/src/cli/init.ts +9 -1
- package/src/cli/project.ts +28 -2
- package/src/cli/templates.ts +46 -14
- package/src/core/agents/agent-team-fixture.ts +6 -0
- package/src/core/agents/team-turns.ts +6 -1
- package/src/core/builtin/agent-server.ts +30 -95
- package/src/core/builtin/modules.ts +133 -34
- package/src/core/config/adapters.ts +57 -0
- package/src/core/config/config.ts +174 -77
- package/src/core/contract/channels.ts +4 -0
- package/src/core/contract/providers.ts +3 -2
- package/src/core/contract/runtime.ts +8 -4
- package/src/core/contract/surface.ts +13 -0
- package/src/core/conversations/conversation-registry.ts +42 -0
- package/src/core/conversations/conversation-store.ts +113 -0
- package/src/core/conversations/conversations-plugin.ts +29 -0
- package/src/core/define-roundtable.ts +124 -54
- package/src/core/discord/discord-adapter.ts +9 -0
- package/src/core/discord/owner-cards.ts +19 -8
- package/src/core/domain/conversation.ts +5 -0
- package/src/core/domain/owner-prompts.ts +1 -1
- package/src/core/domain/ports.ts +6 -0
- package/src/core/domain/progress.ts +10 -0
- package/src/core/events.ts +1 -0
- package/src/core/host.ts +6 -3
- package/src/core/http/listeners.ts +174 -21
- package/src/core/http/websocket.ts +316 -0
- package/src/core/i18n/cards.ts +5 -7
- package/src/core/ops/error-reporter.ts +24 -12
- package/src/core/plugin.ts +14 -0
- package/src/core/registry/providers.ts +1 -1
- package/src/core/routing/channel-router.ts +8 -0
- package/src/core/routing/conversation-turns.ts +55 -1
- package/src/core/routing/surface-port.ts +3 -0
- package/src/core/runtime/extensions/confirmation-gate.ts +10 -2
- package/src/core/runtime/extensions/self-compact-guard.ts +11 -0
- package/src/core/runtime/pending-confirmation-store.ts +42 -4
- package/src/core/runtime/pi-agent-runtime.ts +13 -6
- package/src/core/runtime/prompt-slot.ts +2 -2
- package/src/core/runtime/runtime-plugin.ts +160 -0
- package/src/core/runtime/turn-progress.ts +116 -0
- package/src/core/services.ts +15 -1
- package/src/core/shared/unix-server.ts +35 -5
- package/src/core/testing/modules.ts +47 -23
- package/src/core/testing/surface-contract.ts +383 -0
- package/src/core/testing/test-host.ts +27 -9
- package/src/discord/index.ts +2 -0
- package/src/index.ts +18 -0
- package/src/testing.ts +41 -5
- package/templates/adapters/web/.env.example +24 -0
- package/templates/adapters/web/README.md +15 -0
- package/templates/adapters/web/package.json.tmpl +26 -0
- package/templates/adapters/web/persona/assistant.md +2 -0
- package/templates/adapters/web/roundtable.config.ts +55 -0
- package/templates/package.json.tmpl +1 -0
- package/templates/plugins/self-compact.ts +11 -0
- package/templates/roundtable.config.ts +2 -1
|
@@ -13,7 +13,12 @@ import {
|
|
|
13
13
|
precheckPlugin,
|
|
14
14
|
scheduleStorePlugin,
|
|
15
15
|
} from "./builtin/stores.ts";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
type ResolvedConfig,
|
|
18
|
+
type RoundtableConfig,
|
|
19
|
+
resolveConfig,
|
|
20
|
+
} from "./config/config.ts";
|
|
21
|
+
import { conversationsPlugin } from "./conversations/conversations-plugin.ts";
|
|
17
22
|
import { ConfigError } from "./domain/errors.ts";
|
|
18
23
|
import { JudgeError } from "./errors.ts";
|
|
19
24
|
import type { RoundtableOptions } from "./host.ts";
|
|
@@ -23,6 +28,11 @@ import { createLogger, type LogEntry, type Logger } from "./log.ts";
|
|
|
23
28
|
import { formatModelRef, type ModelRef } from "./models.ts";
|
|
24
29
|
import { ErrorReporter } from "./ops/error-reporter.ts";
|
|
25
30
|
import type { RoundtablePlugin } from "./plugin.ts";
|
|
31
|
+
import {
|
|
32
|
+
type AgentSessionsSlot,
|
|
33
|
+
agentSessionsSlot,
|
|
34
|
+
runtimePlugin,
|
|
35
|
+
} from "./runtime/runtime-plugin.ts";
|
|
26
36
|
import { speakerPolicy } from "./speakers.ts";
|
|
27
37
|
import { toolTiers } from "./tool-tiers.ts";
|
|
28
38
|
|
|
@@ -86,7 +96,7 @@ function judgeThrough(modelRuntime: ModelRuntime, ref: ModelRef): JudgeModel {
|
|
|
86
96
|
|
|
87
97
|
/** The listener `config.http` names: its unix socket when it has one, else its TCP port. */
|
|
88
98
|
function publicListener(
|
|
89
|
-
http:
|
|
99
|
+
http: NonNullable<ResolvedConfig["http"]>,
|
|
90
100
|
): ListenerConfig {
|
|
91
101
|
if (http.socketPath)
|
|
92
102
|
return {
|
|
@@ -101,11 +111,95 @@ function publicListener(
|
|
|
101
111
|
};
|
|
102
112
|
}
|
|
103
113
|
|
|
114
|
+
/** The Discord bot and the agent server that lives there, each in its place in the plugin list. */
|
|
115
|
+
interface DiscordAssembly {
|
|
116
|
+
discord: RoundtablePlugin;
|
|
117
|
+
admin: RoundtablePlugin[];
|
|
118
|
+
skills: RoundtablePlugin[];
|
|
119
|
+
agentServer: RoundtablePlugin;
|
|
120
|
+
seeds: RoundtablePlugin;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function discordAssembly(
|
|
124
|
+
config: ResolvedConfig,
|
|
125
|
+
discord: NonNullable<ResolvedConfig["discord"]>,
|
|
126
|
+
shared: {
|
|
127
|
+
modelRuntime: ModelRuntime;
|
|
128
|
+
agentSessions: AgentSessionsSlot;
|
|
129
|
+
errorReporter: ErrorReporter | undefined;
|
|
130
|
+
},
|
|
131
|
+
): DiscordAssembly {
|
|
132
|
+
const { name, owner } = config;
|
|
133
|
+
const speakers = speakerPolicy({
|
|
134
|
+
owners: [owner.id],
|
|
135
|
+
...(config.speakers.admins ? { admins: config.speakers.admins } : {}),
|
|
136
|
+
...(config.speakers.members ? { members: config.speakers.members } : {}),
|
|
137
|
+
});
|
|
138
|
+
const sharedPrompt = config.prompts.shared
|
|
139
|
+
? readPrompt(config.prompts.shared, "prompts.shared")
|
|
140
|
+
: readPrompt(join(ASSETS, "prompts", "shared.md"), "prompts.shared");
|
|
141
|
+
const guest = config.prompts.guest
|
|
142
|
+
? readPrompt(config.prompts.guest, "prompts.guest")
|
|
143
|
+
: readPrompt(join(ASSETS, "prompts", "shared-guest.md"), "prompts.guest");
|
|
144
|
+
const { errorReporter } = shared;
|
|
145
|
+
return {
|
|
146
|
+
discord: discordPlugin({
|
|
147
|
+
token: discord.token,
|
|
148
|
+
ownerId: owner.id,
|
|
149
|
+
ownerName: owner.name,
|
|
150
|
+
speakers,
|
|
151
|
+
rootCommand: discord.rootCommand,
|
|
152
|
+
dataDir: config.dataDir,
|
|
153
|
+
...(discord.refusalHint === undefined
|
|
154
|
+
? {}
|
|
155
|
+
: { refusalHint: discord.refusalHint }),
|
|
156
|
+
}),
|
|
157
|
+
admin: discord.admin ? [discordAdminPlugin({ owner })] : [],
|
|
158
|
+
skills: config.skills
|
|
159
|
+
? [
|
|
160
|
+
skillsPlugin({
|
|
161
|
+
guildId: discord.guild,
|
|
162
|
+
reposDir: config.skills.reposDir ?? join(config.dataDir, "repos"),
|
|
163
|
+
writtenDir: join(config.dataDir, "skills"),
|
|
164
|
+
builtinDir: config.skills.builtinDir ?? join(ASSETS, "skills"),
|
|
165
|
+
}),
|
|
166
|
+
]
|
|
167
|
+
: [],
|
|
168
|
+
agentServer: agentServerPlugin({
|
|
169
|
+
guildId: discord.guild,
|
|
170
|
+
entryChannelId: discord.entryChannel,
|
|
171
|
+
owner,
|
|
172
|
+
assistant: name,
|
|
173
|
+
speakers,
|
|
174
|
+
modelRuntime: shared.modelRuntime,
|
|
175
|
+
dataDir: config.dataDir,
|
|
176
|
+
model: config.model,
|
|
177
|
+
judgeThreshold: config.judge.threshold,
|
|
178
|
+
agents: shared.agentSessions,
|
|
179
|
+
workDir: config.workDir,
|
|
180
|
+
scratchDir: config.scratchDir,
|
|
181
|
+
shellUser: userInfo().username,
|
|
182
|
+
prompts: { shared: sharedPrompt, guest },
|
|
183
|
+
avatarListener: "public",
|
|
184
|
+
// resolveConfig refuses Discord without a public address.
|
|
185
|
+
avatarUrl: config.http?.publicUrl ?? "",
|
|
186
|
+
avatarReference: config.avatar ?? join(ASSETS, "neutral.png"),
|
|
187
|
+
// An ops agent's reports are the agent server's to deliver.
|
|
188
|
+
...(errorReporter && "agent" in errorReporter.destination
|
|
189
|
+
? { errorReporter }
|
|
190
|
+
: {}),
|
|
191
|
+
}),
|
|
192
|
+
seeds: seedsPlugin(config.agents),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
104
196
|
/**
|
|
105
197
|
* The host's options and the plugin list of a configured bot: the built-in plugins in their
|
|
106
198
|
* fixed order (each service one provides is read by the ones after it), then the operator's, then the scheduler, so a due schedule fires only once
|
|
107
199
|
* everything it reaches runs. The memory, Discord administration, and skills addons are left out
|
|
108
|
-
* when the configuration switches them off.
|
|
200
|
+
* when the configuration switches them off. Without `discord` the Discord plugins, the skills,
|
|
201
|
+
* the agent server and its seeds are left out, and the runtime still runs every turn of
|
|
202
|
+
* `context.turns`. Configuration mistakes stop here, naming the key and the fix.
|
|
109
203
|
*/
|
|
110
204
|
export async function defineRoundtable(
|
|
111
205
|
input: RoundtableConfig,
|
|
@@ -122,13 +216,13 @@ export async function defineRoundtable(
|
|
|
122
216
|
}));
|
|
123
217
|
const registry = new ModelRegistry(modelRuntime);
|
|
124
218
|
const errorReporter = config.ops
|
|
125
|
-
? new ErrorReporter({
|
|
219
|
+
? new ErrorReporter({ destination: config.ops, app: name })
|
|
126
220
|
: undefined;
|
|
127
221
|
const { errorSink } = overrides;
|
|
128
222
|
const logger =
|
|
129
223
|
overrides.logger ??
|
|
130
224
|
createLogger(
|
|
131
|
-
|
|
225
|
+
config.slug,
|
|
132
226
|
errorReporter || errorSink
|
|
133
227
|
? (entry) => {
|
|
134
228
|
errorReporter?.record(entry);
|
|
@@ -136,17 +230,15 @@ export async function defineRoundtable(
|
|
|
136
230
|
}
|
|
137
231
|
: undefined,
|
|
138
232
|
);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
? readPrompt(config.prompts.guest, "prompts.guest")
|
|
149
|
-
: readPrompt(join(ASSETS, "prompts", "shared-guest.md"), "prompts.guest");
|
|
233
|
+
// The agent server hands the runtime its per-agent settings once it sets up.
|
|
234
|
+
const agentSessions = agentSessionsSlot();
|
|
235
|
+
const assembly = discord
|
|
236
|
+
? discordAssembly(config, discord, {
|
|
237
|
+
modelRuntime,
|
|
238
|
+
agentSessions,
|
|
239
|
+
errorReporter,
|
|
240
|
+
})
|
|
241
|
+
: undefined;
|
|
150
242
|
return {
|
|
151
243
|
options: {
|
|
152
244
|
logger,
|
|
@@ -154,12 +246,15 @@ export async function defineRoundtable(
|
|
|
154
246
|
locale: config.locale,
|
|
155
247
|
timeZone: config.timeZone,
|
|
156
248
|
assistant: name,
|
|
157
|
-
rootCommand:
|
|
249
|
+
rootCommand: config.slug,
|
|
158
250
|
agentDir: config.agentDir,
|
|
159
251
|
},
|
|
160
252
|
database: { url: config.databaseUrl },
|
|
161
253
|
toolTiers: toolTiers(config.toolTiers),
|
|
162
|
-
listeners: [
|
|
254
|
+
listeners: [
|
|
255
|
+
...(config.http ? [publicListener(config.http)] : []),
|
|
256
|
+
...(overrides.listeners ?? []),
|
|
257
|
+
],
|
|
163
258
|
judgeModel: judgeThrough(modelRuntime, config.judge.model),
|
|
164
259
|
apiKey: (provider) => registry.getApiKeyForProvider(provider),
|
|
165
260
|
...(overrides.aborted ? { aborted: overrides.aborted } : {}),
|
|
@@ -168,17 +263,7 @@ export async function defineRoundtable(
|
|
|
168
263
|
...(config.memory ? [memoryPlugin({ owner })] : []),
|
|
169
264
|
scheduleStorePlugin(),
|
|
170
265
|
precheckPlugin(),
|
|
171
|
-
|
|
172
|
-
token: discord.token,
|
|
173
|
-
ownerId: owner.id,
|
|
174
|
-
ownerName: owner.name,
|
|
175
|
-
speakers,
|
|
176
|
-
rootCommand: discord.rootCommand,
|
|
177
|
-
dataDir: config.dataDir,
|
|
178
|
-
...(discord.refusalHint === undefined
|
|
179
|
-
? {}
|
|
180
|
-
: { refusalHint: discord.refusalHint }),
|
|
181
|
-
}),
|
|
266
|
+
...(assembly ? [assembly.discord] : []),
|
|
182
267
|
modulesPlugin({
|
|
183
268
|
owner,
|
|
184
269
|
assistant: name,
|
|
@@ -186,42 +271,27 @@ export async function defineRoundtable(
|
|
|
186
271
|
agentDir: config.agentDir,
|
|
187
272
|
dataDir: config.dataDir,
|
|
188
273
|
delegation: config.delegation,
|
|
274
|
+
// A conversation's reports are the modules' to deliver.
|
|
275
|
+
...(errorReporter && "conversation" in errorReporter.destination
|
|
276
|
+
? { errorReporter }
|
|
277
|
+
: {}),
|
|
189
278
|
}),
|
|
190
|
-
...(
|
|
191
|
-
...(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
guildId: discord.guild,
|
|
195
|
-
reposDir: config.skills.reposDir ?? join(config.dataDir, "repos"),
|
|
196
|
-
writtenDir: join(config.dataDir, "skills"),
|
|
197
|
-
builtinDir: config.skills.builtinDir ?? join(ASSETS, "skills"),
|
|
198
|
-
}),
|
|
199
|
-
]
|
|
200
|
-
: []),
|
|
201
|
-
agentServerPlugin({
|
|
202
|
-
guildId: discord.guild,
|
|
203
|
-
entryChannelId: discord.entryChannel,
|
|
279
|
+
...(assembly?.admin ?? []),
|
|
280
|
+
...(assembly?.skills ?? []),
|
|
281
|
+
conversationsPlugin(),
|
|
282
|
+
runtimePlugin({
|
|
204
283
|
owner,
|
|
205
|
-
assistant: name,
|
|
206
|
-
speakers,
|
|
207
284
|
modelRuntime,
|
|
208
285
|
agentDir: config.agentDir,
|
|
209
286
|
dataDir: config.dataDir,
|
|
210
287
|
model: config.model,
|
|
211
288
|
thinking: config.thinking,
|
|
212
289
|
judgeThreshold: config.judge.threshold,
|
|
213
|
-
|
|
214
|
-
scratchDir: config.scratchDir,
|
|
215
|
-
shellUser: userInfo().username,
|
|
216
|
-
prompts: { shared, guest },
|
|
217
|
-
avatarListener: "public",
|
|
218
|
-
avatarUrl: config.http.publicUrl,
|
|
219
|
-
avatarReference: config.avatar ?? join(ASSETS, "neutral.png"),
|
|
290
|
+
agents: agentSessions,
|
|
220
291
|
interimText: config.interimText,
|
|
221
292
|
interimPrimaryChars: config.interimPrimaryChars,
|
|
222
|
-
...(errorReporter ? { errorReporter } : {}),
|
|
223
293
|
}),
|
|
224
|
-
|
|
294
|
+
...(assembly ? [assembly.agentServer, assembly.seeds] : []),
|
|
225
295
|
...config.plugins,
|
|
226
296
|
schedulerPlugin(),
|
|
227
297
|
],
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DiscordAdapterConfig, DiscordConfig } from "../config/config.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Discord adapter, for `adapters: [discord({ ... })]` in roundtable.config.ts: the same
|
|
5
|
+
* settings, and the same host, as the top-level `discord`.
|
|
6
|
+
*/
|
|
7
|
+
export function discord(options: DiscordConfig): DiscordAdapterConfig {
|
|
8
|
+
return { adapter: "discord", discord: options };
|
|
9
|
+
}
|
|
@@ -103,7 +103,8 @@ interface Audience {
|
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* The owner's cards in the assistant's channels: approvals of held actions and ask_user questions,
|
|
106
|
-
* answered with buttons, a menu, or a form.
|
|
106
|
+
* answered with buttons, a menu, or a form. The owner may answer, and so may the speaker whose
|
|
107
|
+
* turn asks, when the card allows (a question always; an approval at its tier); an unanswered card
|
|
107
108
|
* expires after 30 minutes and a stopped turn cancels it. Open cards live in memory, so a
|
|
108
109
|
* restart abandons them; pressing one then says it no longer works.
|
|
109
110
|
*/
|
|
@@ -262,12 +263,19 @@ export class OwnerCards implements InteractionModule {
|
|
|
262
263
|
};
|
|
263
264
|
}
|
|
264
265
|
|
|
265
|
-
/**
|
|
266
|
+
/**
|
|
267
|
+
* An approval is for the speaker whose turn held the call, when their tier holds it, and for
|
|
268
|
+
* the owner; the owner's alone by default. Nobody else in the channel may approve it.
|
|
269
|
+
*/
|
|
266
270
|
#approvers(minTier: Tier, speaker: Speaker | undefined): Audience {
|
|
267
271
|
const { ownerId, speakers } = this.#options;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
272
|
+
if (
|
|
273
|
+
minTier === "owner" ||
|
|
274
|
+
!speakers ||
|
|
275
|
+
!speaker ||
|
|
276
|
+
speaker.id === ownerId ||
|
|
277
|
+
!tierAtLeast(speaker.tier, minTier)
|
|
278
|
+
)
|
|
271
279
|
return {
|
|
272
280
|
allows: (user) => user.id === ownerId,
|
|
273
281
|
mentionId: ownerId,
|
|
@@ -276,12 +284,15 @@ export class OwnerCards implements InteractionModule {
|
|
|
276
284
|
};
|
|
277
285
|
return {
|
|
278
286
|
allows: (user) => {
|
|
287
|
+
if (user.id === ownerId) return true;
|
|
288
|
+
if (user.id !== speaker.id) return false;
|
|
289
|
+
// Their tier when they press, in case it was lowered during the turn.
|
|
279
290
|
const who = speakers.resolve({ ...user, name: "" });
|
|
280
291
|
return who !== undefined && tierAtLeast(who.tier, minTier);
|
|
281
292
|
},
|
|
282
|
-
mentionId,
|
|
283
|
-
note: messages().cardApproversNote(
|
|
284
|
-
refusal: messages().cardApproversRefusal
|
|
293
|
+
mentionId: speaker.id,
|
|
294
|
+
note: messages().cardApproversNote(speaker.id),
|
|
295
|
+
refusal: messages().cardApproversRefusal,
|
|
285
296
|
};
|
|
286
297
|
}
|
|
287
298
|
|
|
@@ -26,6 +26,11 @@ export interface PendingConfirmation {
|
|
|
26
26
|
selectionId: string;
|
|
27
27
|
heldAt: Date;
|
|
28
28
|
calls: HeldCall[];
|
|
29
|
+
/**
|
|
30
|
+
* The speaker whose turn held the calls. Besides the owner, only they may approve them, at a
|
|
31
|
+
* tier that holds them; without one, the owner alone may.
|
|
32
|
+
*/
|
|
33
|
+
speakerId?: string;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
/** A file produced for a reply; data is raw bytes, not a path or base64. */
|
|
@@ -33,7 +33,7 @@ export interface OwnerPrompts {
|
|
|
33
33
|
title: string,
|
|
34
34
|
message: string,
|
|
35
35
|
signal?: AbortSignal,
|
|
36
|
-
/** The
|
|
36
|
+
/** The tier the turn's speaker needs to approve it themselves; the owner may always approve, and only the owner when absent. */
|
|
37
37
|
minTier?: Tier,
|
|
38
38
|
): Promise<Approval>;
|
|
39
39
|
/** Asks them a question; undefined when it expired or was cancelled. */
|
package/src/core/domain/ports.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Speaker } from "../speakers.ts";
|
|
|
3
3
|
import type { TurnAttachments } from "./attachment.ts";
|
|
4
4
|
import type { ChannelKey } from "./conversation.ts";
|
|
5
5
|
import type { InterimPosts } from "./interim.ts";
|
|
6
|
+
import type { TurnProgress } from "./progress.ts";
|
|
6
7
|
|
|
7
8
|
export type { AgentTurnScope };
|
|
8
9
|
|
|
@@ -38,6 +39,11 @@ export interface TurnRequest {
|
|
|
38
39
|
* hands the channel its reply goes to. Absent = only the final reply is posted.
|
|
39
40
|
*/
|
|
40
41
|
interim?: InterimPosts;
|
|
42
|
+
/**
|
|
43
|
+
* Hears what the turn writes and which tools it runs, as it goes, until the turn ends. A runtime
|
|
44
|
+
* without live progress never calls it.
|
|
45
|
+
*/
|
|
46
|
+
progress?: (event: TurnProgress) => void;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
/** The judge's question and answer shapes. */
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a running turn reports as it goes, for a surface that shows it live. Text arrives in
|
|
3
|
+
* chunks joined over a short interval; a tool is named with a short one-line preview of its
|
|
4
|
+
* arguments, never their full text, and ends with whether it succeeded. The final reply still
|
|
5
|
+
* arrives through `sendReply`.
|
|
6
|
+
*/
|
|
7
|
+
export type TurnProgress =
|
|
8
|
+
| { type: "text"; delta: string }
|
|
9
|
+
| { type: "tool_start"; id: string; tool: string; preview?: string }
|
|
10
|
+
| { type: "tool_end"; id: string; tool: string; ok: boolean };
|
package/src/core/events.ts
CHANGED
|
@@ -50,6 +50,7 @@ export class EventBus {
|
|
|
50
50
|
readonly sink: EventSink = {
|
|
51
51
|
turnStarted: (turn) => void this.deliver("turnStarted", turn),
|
|
52
52
|
turnEnded: (turn) => void this.deliver("turnEnded", turn),
|
|
53
|
+
turnProgress: (event) => void this.deliver("turnProgress", event),
|
|
53
54
|
changed: () => void this.deliver("changed"),
|
|
54
55
|
};
|
|
55
56
|
}
|
package/src/core/host.ts
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
conversationTurns,
|
|
33
33
|
} from "./routing/conversation-turns.ts";
|
|
34
34
|
import { surfacePort } from "./routing/surface-port.ts";
|
|
35
|
-
import {
|
|
35
|
+
import { CONVERSATIONS, RUNTIME } from "./services.ts";
|
|
36
36
|
import { setTimeZone } from "./time.ts";
|
|
37
37
|
import { type ToolTierTable, toolTiers } from "./tool-tiers.ts";
|
|
38
38
|
|
|
@@ -196,6 +196,8 @@ export class Roundtable {
|
|
|
196
196
|
deleteConversation: (channel) => router().deleteConversation(channel),
|
|
197
197
|
stop: (channel) => router().stop(channel),
|
|
198
198
|
postsInPlace: (channel) => router().postsInPlace(channel),
|
|
199
|
+
owns: (channel) => router().owns(channel),
|
|
200
|
+
takesBackground: (channel) => router().takesBackground(channel),
|
|
199
201
|
};
|
|
200
202
|
}
|
|
201
203
|
|
|
@@ -210,7 +212,7 @@ export class Roundtable {
|
|
|
210
212
|
});
|
|
211
213
|
}
|
|
212
214
|
|
|
213
|
-
/** Turns over the
|
|
215
|
+
/** Turns over the runtime plugin's runtime and the surfaces; every call before linking is refused with NotLinkedError. */
|
|
214
216
|
#turns(): ConversationTurns {
|
|
215
217
|
return conversationTurns({
|
|
216
218
|
linked: () => {
|
|
@@ -222,8 +224,9 @@ export class Roundtable {
|
|
|
222
224
|
runtime: () => {
|
|
223
225
|
if (!this.#services)
|
|
224
226
|
throw new NotLinkedError("the host has not started yet.");
|
|
225
|
-
return this.#services.get(
|
|
227
|
+
return this.#services.get(RUNTIME);
|
|
226
228
|
},
|
|
229
|
+
registry: () => this.#services?.find(CONVERSATIONS),
|
|
227
230
|
surfaces: this.#surfaces(),
|
|
228
231
|
events: this.#events.sink,
|
|
229
232
|
selection: () => {
|