pi-roundtable 0.22.5 → 0.23.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 +55 -1
- package/README.md +35 -9
- package/README.zh-TW.md +51 -13
- package/docs/migrating-0.8.md +2 -2
- package/docs/plugins.md +350 -28
- package/examples/discord-adapter.ts +9 -1
- package/examples/seeds.test.ts +2 -2
- package/examples/seeds.ts +1 -2
- package/package.json +6 -2
- package/src/characters/catalog.ts +1055 -0
- package/src/characters/characters.css +68 -0
- package/src/characters/colors.ts +108 -0
- package/src/characters/compose.ts +281 -0
- package/src/characters/css.ts +63 -0
- package/src/characters/index.ts +33 -0
- package/src/characters/parse.ts +196 -0
- package/src/characters/parts-face.ts +354 -0
- package/src/characters/parts-hair.ts +56 -0
- package/src/characters/parts-hats.ts +57 -0
- package/src/characters/parts-outfits.ts +81 -0
- package/src/characters/parts-props.ts +137 -0
- package/src/characters/spec.ts +224 -0
- package/src/characters/suggest.ts +101 -0
- package/src/cli/checks/characters.ts +23 -0
- package/src/cli/checks/images.ts +5 -3
- package/src/cli/doctor.ts +2 -0
- package/src/core/agents/agent-claim.ts +30 -11
- package/src/core/agents/agent-dashboard.ts +1 -1
- package/src/core/agents/agent-messages.ts +43 -15
- package/src/core/agents/agent-prompt.ts +2 -4
- package/src/core/agents/agent-rules.ts +42 -3
- package/src/core/agents/agent-schema.ts +37 -15
- package/src/core/agents/agent-store.ts +57 -13
- package/src/core/agents/agent-team-fixture.ts +90 -14
- package/src/core/agents/agent-team.ts +83 -50
- package/src/core/agents/agent-tools.ts +94 -41
- package/src/core/agents/avatar-studio.ts +40 -4
- package/src/core/agents/character-png.ts +40 -0
- package/src/core/agents/group-messages.ts +7 -4
- package/src/core/agents/group-turns.ts +27 -14
- package/src/core/agents/team-editing.ts +99 -58
- package/src/core/agents/team-keys.ts +177 -11
- package/src/core/agents/team-lifecycle.ts +151 -84
- package/src/core/agents/team-options.ts +13 -6
- package/src/core/agents/team-status.ts +31 -13
- package/src/core/agents/team-text.ts +13 -3
- package/src/core/agents/team-turn-types.ts +25 -7
- package/src/core/agents/team-turns.ts +143 -28
- package/src/core/attachments/attachment-fetcher.ts +162 -64
- package/src/core/attachments/prompt-block.ts +7 -1
- package/src/core/attachments/tool-attachment.ts +20 -1
- package/src/core/attachments/turn-attachments.ts +18 -2
- package/src/core/builtin/agent-server.ts +103 -40
- package/src/core/builtin/discord.ts +4 -17
- package/src/core/builtin/modules.ts +6 -5
- package/src/core/builtin/threads.ts +37 -0
- package/src/core/config/agent-team.ts +43 -0
- package/src/core/config/config.ts +50 -59
- package/src/core/config/limits.ts +93 -0
- package/src/core/config/ops.ts +34 -0
- package/src/core/config/schema.ts +26 -0
- package/src/core/contract/channels.ts +20 -0
- package/src/core/contract/surface.ts +77 -7
- package/src/core/define-roundtable.ts +70 -42
- package/src/core/discord/agent-commands.ts +108 -86
- package/src/core/discord/agent-panel.ts +11 -2
- package/src/core/discord/connection.ts +1 -1
- package/src/core/discord/discord-surface.ts +10 -1
- package/src/core/discord/dispatch-thread-host.ts +1 -1
- package/src/core/domain/conversation.ts +24 -0
- package/src/core/holds.ts +221 -15
- package/src/core/host-registration.ts +2 -1
- package/src/core/http/listeners.ts +63 -3
- package/src/core/i18n/agent-panel.ts +14 -21
- package/src/core/interactions/prompts.ts +12 -0
- package/src/core/modes/autonomy.ts +23 -0
- package/src/core/modes/conversation-modes.ts +213 -0
- package/src/core/modes/modes-plugin.ts +24 -0
- package/src/core/modules/delegation/delegator.ts +4 -4
- package/src/core/modules/host-shell/shell-arithmetic.ts +81 -0
- package/src/core/modules/host-shell/shell-lexer-types.ts +53 -0
- package/src/core/modules/host-shell/shell-lexer.ts +493 -0
- package/src/core/modules/host-shell/shell-paths.ts +85 -4
- package/src/core/modules/host-shell/shell-policy.ts +341 -332
- package/src/core/modules/host-shell/shell-programs.ts +298 -0
- package/src/core/modules/host-shell/shell-quoting.ts +237 -0
- package/src/core/modules/host-shell/shell-scripts.ts +114 -0
- package/src/core/modules/host-shell/shell-wrappers.ts +104 -0
- package/src/core/modules/schedules/precheck-tools.ts +17 -0
- package/src/core/modules/schedules/schedules.ts +9 -3
- package/src/core/plugin.ts +8 -1
- package/src/core/presence/direct-channels.ts +30 -0
- package/src/core/presentation/reply-body.ts +25 -0
- package/src/core/registry/contributions.ts +31 -7
- package/src/core/registry/providers.ts +1 -1
- package/src/core/routing/conversation-turns.ts +2 -15
- package/src/core/routing/surface-port.ts +5 -1
- package/src/core/runtime/extensions/approval-card.ts +1 -5
- package/src/core/runtime/extensions/approval-fields.ts +3 -7
- package/src/core/runtime/extensions/confirmation-gate-extension.ts +13 -2
- package/src/core/runtime/extensions/confirmation-gate.ts +41 -40
- package/src/core/runtime/extensions/gate-refusals.ts +23 -0
- package/src/core/runtime/extensions/held-calls.ts +16 -1
- package/src/core/runtime/extensions/hold-describer.ts +58 -8
- package/src/core/runtime/extensions/hold-lookups.ts +19 -19
- package/src/core/runtime/extensions/yolo.ts +105 -0
- package/src/core/runtime/pi-agent-runtime.ts +3 -5
- package/src/core/runtime/runtime-plugin.ts +42 -1
- package/src/core/runtime/runtime-types.ts +11 -0
- package/src/core/runtime/turn-budget.ts +32 -5
- package/src/core/runtime/turn-limits.ts +14 -3
- package/src/core/services.ts +61 -9
- package/src/core/shared/attachment-reader.ts +6 -0
- package/src/core/shared/unix-server.ts +10 -1
- package/src/core/testing/modules.ts +12 -8
- package/src/core/testing/surface-threads.ts +58 -0
- package/src/core/testing/thread-host.ts +17 -5
- package/src/core/threads/dispatch-threads.ts +299 -0
- package/src/core/threads/host-threads.ts +19 -0
- package/src/core/tool-set.snapshot.json +5 -0
- package/src/index.ts +27 -1
- package/src/kit/characters.ts +5 -0
- package/src/kit/index.ts +2 -0
- package/src/kit/shell.ts +1 -0
- package/src/kit/support.ts +1 -1
- package/src/kit/threads.ts +1 -1
- package/src/testing.ts +4 -0
- package/templates/agents.ts +1 -1
- package/src/core/discord/dispatch-threads.ts +0 -194
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"agent": {
|
|
48
48
|
"tools": [
|
|
49
|
+
"agent_avatar",
|
|
49
50
|
"agent_create",
|
|
50
51
|
"agent_get",
|
|
51
52
|
"agent_list",
|
|
@@ -107,6 +108,7 @@
|
|
|
107
108
|
"skill_update"
|
|
108
109
|
],
|
|
109
110
|
"selection": [
|
|
111
|
+
"agent_avatar",
|
|
110
112
|
"agent_create",
|
|
111
113
|
"agent_get",
|
|
112
114
|
"agent_list",
|
|
@@ -134,6 +136,7 @@
|
|
|
134
136
|
},
|
|
135
137
|
"group seat": {
|
|
136
138
|
"tools": [
|
|
139
|
+
"agent_avatar",
|
|
137
140
|
"agent_create",
|
|
138
141
|
"agent_get",
|
|
139
142
|
"agent_list",
|
|
@@ -194,6 +197,7 @@
|
|
|
194
197
|
"skill_update"
|
|
195
198
|
],
|
|
196
199
|
"selection": [
|
|
200
|
+
"agent_avatar",
|
|
197
201
|
"agent_create",
|
|
198
202
|
"agent_get",
|
|
199
203
|
"agent_list",
|
|
@@ -220,6 +224,7 @@
|
|
|
220
224
|
}
|
|
221
225
|
},
|
|
222
226
|
"tiers": {
|
|
227
|
+
"agent_avatar": "admin",
|
|
223
228
|
"agent_create": "admin",
|
|
224
229
|
"agent_get": "member",
|
|
225
230
|
"agent_list": "member",
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export {
|
|
|
4
4
|
AGENT_SERVER_PRIORITY,
|
|
5
5
|
OWNER_TARGET,
|
|
6
6
|
} from "./core/agents/agent-claim.ts";
|
|
7
|
+
export { characterOf } from "./core/agents/agent-rules.ts";
|
|
7
8
|
export type {
|
|
8
9
|
Agent,
|
|
9
10
|
AgentGroup,
|
|
@@ -11,11 +12,17 @@ export type {
|
|
|
11
12
|
AgentStatus,
|
|
12
13
|
} from "./core/agents/agent-store.ts";
|
|
13
14
|
export type { AvatarMode } from "./core/agents/agent-tools.ts";
|
|
15
|
+
export {
|
|
16
|
+
agentRoomKey,
|
|
17
|
+
groupRoomKey,
|
|
18
|
+
roomOf,
|
|
19
|
+
} from "./core/agents/team-keys.ts";
|
|
14
20
|
export type {
|
|
15
21
|
AgentStatus as TeamAgentStatus,
|
|
16
22
|
GroupStatus,
|
|
17
23
|
TeamStatus,
|
|
18
24
|
} from "./core/agents/team-status.ts";
|
|
25
|
+
export { MAX_ATTACHMENT_BYTES } from "./core/attachments/attachment-fetcher.ts";
|
|
19
26
|
export type { AttachmentRetention } from "./core/attachments/attachment-retention.ts";
|
|
20
27
|
export type {
|
|
21
28
|
ToolAttachment,
|
|
@@ -24,6 +31,7 @@ export type {
|
|
|
24
31
|
export {
|
|
25
32
|
AGENT_SERVER_PLUGIN,
|
|
26
33
|
AGENT_TEAM_SERVICE,
|
|
34
|
+
LOCAL_TEAM_ID,
|
|
27
35
|
} from "./core/builtin/agent-server.ts";
|
|
28
36
|
export type {
|
|
29
37
|
AccessConfig,
|
|
@@ -39,6 +47,7 @@ export type {
|
|
|
39
47
|
RoundtableConfig,
|
|
40
48
|
TierConfig,
|
|
41
49
|
} from "./core/config/config.ts";
|
|
50
|
+
export type { LimitsConfig } from "./core/config/limits.ts";
|
|
42
51
|
export type { SkillsConfig } from "./core/config/skills-config.ts";
|
|
43
52
|
export type {
|
|
44
53
|
AttachmentPort,
|
|
@@ -51,6 +60,8 @@ export type {
|
|
|
51
60
|
export { AttachmentRefusal } from "./core/contract/attachments.ts";
|
|
52
61
|
export type {
|
|
53
62
|
Admission,
|
|
63
|
+
AttachmentContent,
|
|
64
|
+
AttachmentFile,
|
|
54
65
|
AttachmentRef,
|
|
55
66
|
BackgroundRunsAs,
|
|
56
67
|
BackgroundTarget,
|
|
@@ -80,7 +91,11 @@ export type {
|
|
|
80
91
|
} from "./core/contract/runtime.ts";
|
|
81
92
|
export type { ServiceKey, Services } from "./core/contract/services.ts";
|
|
82
93
|
export { serviceKey } from "./core/contract/services.ts";
|
|
83
|
-
export type {
|
|
94
|
+
export type {
|
|
95
|
+
ChatSurface,
|
|
96
|
+
SurfacePort,
|
|
97
|
+
SurfaceThreads,
|
|
98
|
+
} from "./core/contract/surface.ts";
|
|
84
99
|
export { channelKey, parseChannelKey } from "./core/contract/surface.ts";
|
|
85
100
|
export type {
|
|
86
101
|
ConversationRecord,
|
|
@@ -120,6 +135,7 @@ export type {
|
|
|
120
135
|
HeldCall,
|
|
121
136
|
OutboundReply,
|
|
122
137
|
PendingConfirmation,
|
|
138
|
+
PostAuthor,
|
|
123
139
|
ReplyFile,
|
|
124
140
|
TranscriptEntry,
|
|
125
141
|
TurnResult,
|
|
@@ -155,6 +171,7 @@ export {
|
|
|
155
171
|
ProviderError,
|
|
156
172
|
} from "./core/errors.ts";
|
|
157
173
|
export type {
|
|
174
|
+
ClassedHold,
|
|
158
175
|
HoldCheck,
|
|
159
176
|
HoldContext,
|
|
160
177
|
HoldDescription,
|
|
@@ -168,6 +185,7 @@ export type {
|
|
|
168
185
|
ListenerAddress,
|
|
169
186
|
ListenerConfig,
|
|
170
187
|
} from "./core/http/listeners.ts";
|
|
188
|
+
export { DEFAULT_MAX_REQUEST_BODY_BYTES } from "./core/http/listeners.ts";
|
|
171
189
|
export type {
|
|
172
190
|
RouteSocket,
|
|
173
191
|
WebSocketAccept,
|
|
@@ -226,6 +244,12 @@ export {
|
|
|
226
244
|
export type { JudgeModel } from "./core/judging/model-judge.ts";
|
|
227
245
|
export type { LogEntry, LogFn, Logger } from "./core/log.ts";
|
|
228
246
|
export type { ThinkingLevel, ThinkingSetting } from "./core/models.ts";
|
|
247
|
+
export type {
|
|
248
|
+
AutoApproval,
|
|
249
|
+
ConversationMode,
|
|
250
|
+
ConversationModes,
|
|
251
|
+
ModeChange,
|
|
252
|
+
} from "./core/modes/conversation-modes.ts";
|
|
229
253
|
export {
|
|
230
254
|
PERSONAL_TARGET,
|
|
231
255
|
type PerPrincipalLimits,
|
|
@@ -328,10 +352,12 @@ export {
|
|
|
328
352
|
IDENTITY,
|
|
329
353
|
IDENTITY_LINKS,
|
|
330
354
|
MEMORY,
|
|
355
|
+
MODES,
|
|
331
356
|
PRECHECKS,
|
|
332
357
|
RUNTIME,
|
|
333
358
|
SCHEDULES,
|
|
334
359
|
SKILLS,
|
|
360
|
+
THREADS,
|
|
335
361
|
WORKER_SESSIONS,
|
|
336
362
|
} from "./core/services.ts";
|
|
337
363
|
export type {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A character as a PNG: `composeSvg` rasterized by canvas. `size` defaults to 512; the other
|
|
3
|
+
* options are the composer's, defaulting to the idle face on the modern light disc as Discord shows it.
|
|
4
|
+
*/
|
|
5
|
+
export { characterPng as renderCharacterPng } from "../core/agents/character-png.ts";
|
package/src/kit/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export {
|
|
|
17
17
|
withAttachmentsBlock,
|
|
18
18
|
withReference,
|
|
19
19
|
} from "./channels.ts";
|
|
20
|
+
export { renderCharacterPng } from "./characters.ts";
|
|
20
21
|
export type {
|
|
21
22
|
CompactionEngine,
|
|
22
23
|
CompactionHistory,
|
|
@@ -134,6 +135,7 @@ export {
|
|
|
134
135
|
SHELL_TOOLS,
|
|
135
136
|
shellHoldRule,
|
|
136
137
|
shellHoldRuleFor,
|
|
138
|
+
YOLO_CARDED_SHELL_CLASSES,
|
|
137
139
|
} from "./shell.ts";
|
|
138
140
|
export {
|
|
139
141
|
checkRepoName,
|
package/src/kit/shell.ts
CHANGED
package/src/kit/support.ts
CHANGED
|
@@ -12,7 +12,6 @@ export type {
|
|
|
12
12
|
} from "../core/agents/agent-ports.ts";
|
|
13
13
|
export type { Backlog, GroupMessage } from "../core/agents/agent-rules.ts";
|
|
14
14
|
export type { AgentOps } from "../core/agents/agent-tools.ts";
|
|
15
|
-
export type { ThreadHost } from "../core/discord/dispatch-threads.ts";
|
|
16
15
|
export type { Notifier, OwnerNotifier } from "../core/domain/ports.ts";
|
|
17
16
|
export type {
|
|
18
17
|
EffortPolicy,
|
|
@@ -24,3 +23,4 @@ export type { DelegationWorker } from "../core/modules/delegation/delegator.ts";
|
|
|
24
23
|
export type { AgentChannelLookup } from "../core/modules/schedules/schedules.ts";
|
|
25
24
|
export type { AssistantLike } from "../core/shared/session-messages.ts";
|
|
26
25
|
export type { SpeakerFacts, SpeakerPolicy } from "../core/speakers.ts";
|
|
26
|
+
export type { ThreadHost } from "../core/threads/dispatch-threads.ts";
|
package/src/kit/threads.ts
CHANGED
package/src/testing.ts
CHANGED
|
@@ -38,6 +38,7 @@ import type {
|
|
|
38
38
|
import { NotLinkedError, PluginError } from "./core/errors.ts";
|
|
39
39
|
import { EventBus } from "./core/events.ts";
|
|
40
40
|
import type { HoldCheck } from "./core/holds.ts";
|
|
41
|
+
import { type ListenerConfig, listenerAddress } from "./core/http/listeners.ts";
|
|
41
42
|
import { contactsOf } from "./core/identity/identity-view.ts";
|
|
42
43
|
import type { OwnerIdentity } from "./core/identity.ts";
|
|
43
44
|
import { ConfirmationJudge } from "./core/judging/confirmation-judge.ts";
|
|
@@ -256,6 +257,8 @@ export interface TestPluginOptions {
|
|
|
256
257
|
apiKeys?: Readonly<Record<string, string>>;
|
|
257
258
|
/** How long the router holds a bare forward for the message that follows it; the host option `conversations.forwardJoinMs`. */
|
|
258
259
|
forwardJoinMs?: number;
|
|
260
|
+
/** The addresses `context.listener(id)` reports; a plugin that reads one gets `undefined` for any not listed. */
|
|
261
|
+
listeners?: readonly ListenerConfig[];
|
|
259
262
|
}
|
|
260
263
|
|
|
261
264
|
export interface RecordedEvent {
|
|
@@ -542,6 +545,7 @@ export async function testPlugin(
|
|
|
542
545
|
Object.hasOwn(options.apiKeys ?? {}, provider)
|
|
543
546
|
? options.apiKeys?.[provider]
|
|
544
547
|
: undefined,
|
|
548
|
+
listener: (id) => listenerAddress(options.listeners ?? [], id),
|
|
545
549
|
};
|
|
546
550
|
const registry = await collectContributions(
|
|
547
551
|
[plugin],
|
package/templates/agents.ts
CHANGED
|
@@ -10,7 +10,7 @@ export const agents: AgentSeed[] = [
|
|
|
10
10
|
displayName: "Guide",
|
|
11
11
|
prompt:
|
|
12
12
|
"You are Guide, a friendly assistant. Answer briefly and ask when a request is unclear.",
|
|
13
|
-
|
|
13
|
+
character: { class: "wizard", palette: "teal" },
|
|
14
14
|
// The agent in the entry channel is the coordinator: it answers there and can create the others.
|
|
15
15
|
channelId: env("DISCORD_ENTRY_CHANNEL_ID"),
|
|
16
16
|
},
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
existsSync,
|
|
3
|
-
mkdirSync,
|
|
4
|
-
readFileSync,
|
|
5
|
-
renameSync,
|
|
6
|
-
writeFileSync,
|
|
7
|
-
} from "node:fs";
|
|
8
|
-
import { dirname } from "node:path";
|
|
9
|
-
import type { ChannelKey } from "../domain/conversation.ts";
|
|
10
|
-
import type { InterimMessage, InterimPosts } from "../domain/interim.ts";
|
|
11
|
-
import { messages } from "../i18n/index.ts";
|
|
12
|
-
import type { Logger } from "../log.ts";
|
|
13
|
-
|
|
14
|
-
const PREFIX = "discord:";
|
|
15
|
-
/** Discord's limit on a thread name. */
|
|
16
|
-
const THREAD_NAME_LIMIT = 100;
|
|
17
|
-
|
|
18
|
-
/** The chat side of dispatch threads; the Discord surface implements it. */
|
|
19
|
-
export interface ThreadHost {
|
|
20
|
-
/**
|
|
21
|
-
* Posts the start line in the parent and opens a public thread from it; `line` gets the
|
|
22
|
-
* thread's mention once it exists. Undefined when the parent cannot host threads, such as a
|
|
23
|
-
* DM or a thread; throws when Discord refuses.
|
|
24
|
-
*/
|
|
25
|
-
open(
|
|
26
|
-
parentId: string,
|
|
27
|
-
name: string,
|
|
28
|
-
line: (thread?: string) => string,
|
|
29
|
-
): Promise<string | undefined>;
|
|
30
|
-
post(threadId: string, text: string): Promise<void>;
|
|
31
|
-
/**
|
|
32
|
-
* Posts one message of at most Discord's 2000 characters and resolves to it, so it can be
|
|
33
|
-
* edited in place; without it a thread has no `interim` posts.
|
|
34
|
-
*/
|
|
35
|
-
send?(threadId: string, text: string): Promise<InterimMessage>;
|
|
36
|
-
/** Archives the thread, and locks it when allowed. */
|
|
37
|
-
close(threadId: string): Promise<void>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** One background dispatch's thread: its progress while it runs, then its report. */
|
|
41
|
-
export interface DispatchThread {
|
|
42
|
-
readonly id: string;
|
|
43
|
-
/** The thread as a conversation channel, where its owner cards are posted. */
|
|
44
|
-
readonly channel: ChannelKey;
|
|
45
|
-
/** `<#id>`, for the report turn and the start line. */
|
|
46
|
-
readonly mention: string;
|
|
47
|
-
/** Posts in the thread; a failure is logged. */
|
|
48
|
-
post(text: string): Promise<void>;
|
|
49
|
-
/**
|
|
50
|
-
* Where the dispatch posts its progress as it goes, under the same name as its report, such
|
|
51
|
-
* as a coding worker's interim text; absent when the host cannot edit a message it posted.
|
|
52
|
-
* Unlike `post`, a failure rejects.
|
|
53
|
-
*/
|
|
54
|
-
readonly interim?: InterimPosts;
|
|
55
|
-
/** Posts the final report, if any, then archives the thread. Runs once; never rejects. */
|
|
56
|
-
close(report?: string): Promise<void>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface DispatchThreadsOptions {
|
|
60
|
-
host: ThreadHost;
|
|
61
|
-
/** Threads still open, so a start after a crash can archive them. */
|
|
62
|
-
ledgerPath: string;
|
|
63
|
-
/** Channels whose claims keep reports in place rather than opening threads. */
|
|
64
|
-
excluded?: (channel: ChannelKey) => boolean;
|
|
65
|
-
logger: Logger;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
interface LedgerEntry {
|
|
69
|
-
title: string;
|
|
70
|
-
parent: ChannelKey;
|
|
71
|
-
startedAt: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** The thread's name: the title on one line, within Discord's limit. */
|
|
75
|
-
export function threadName(title: string): string {
|
|
76
|
-
const line = title.replace(/\s+/g, " ").trim() || "dispatch";
|
|
77
|
-
return line.length > THREAD_NAME_LIMIT
|
|
78
|
-
? `${line.slice(0, THREAD_NAME_LIMIT - 1)}…`
|
|
79
|
-
: line;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Background dispatches (coding tasks, delegated tasks, messages between agents) each get a
|
|
84
|
-
* thread in the channel that started them, leaving one start line there. A channel that cannot
|
|
85
|
-
* host one, or a failed creation, gets no thread, and the dispatch posts as before.
|
|
86
|
-
*/
|
|
87
|
-
export class DispatchThreads {
|
|
88
|
-
readonly #options: DispatchThreadsOptions;
|
|
89
|
-
readonly #open = new Map<string, LedgerEntry>();
|
|
90
|
-
|
|
91
|
-
constructor(options: DispatchThreadsOptions) {
|
|
92
|
-
this.#options = options;
|
|
93
|
-
const { ledgerPath } = options;
|
|
94
|
-
if (!existsSync(ledgerPath)) return;
|
|
95
|
-
let parsed: unknown;
|
|
96
|
-
try {
|
|
97
|
-
parsed = JSON.parse(readFileSync(ledgerPath, "utf8"));
|
|
98
|
-
} catch (error) {
|
|
99
|
-
throw new Error(`${ledgerPath} is not valid JSON`, { cause: error });
|
|
100
|
-
}
|
|
101
|
-
if (typeof parsed !== "object" || parsed === null)
|
|
102
|
-
throw new Error(`${ledgerPath} must hold a JSON object`);
|
|
103
|
-
for (const [id, entry] of Object.entries(parsed))
|
|
104
|
-
this.#open.set(id, entry as LedgerEntry);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Opens the dispatch's thread under `origin`, posting `intro` first; never rejects. */
|
|
108
|
-
async open(
|
|
109
|
-
origin: ChannelKey | undefined,
|
|
110
|
-
title: string,
|
|
111
|
-
intro?: string,
|
|
112
|
-
): Promise<DispatchThread | undefined> {
|
|
113
|
-
const { host, excluded, logger } = this.#options;
|
|
114
|
-
if (!origin?.startsWith(PREFIX) || excluded?.(origin)) return undefined;
|
|
115
|
-
const name = threadName(title);
|
|
116
|
-
let id: string | undefined;
|
|
117
|
-
try {
|
|
118
|
-
id = await host.open(origin.slice(PREFIX.length), name, (thread) =>
|
|
119
|
-
messages().threadStarted(name, thread),
|
|
120
|
-
);
|
|
121
|
-
} catch (error) {
|
|
122
|
-
logger.warn({ origin, err: error }, "dispatch thread not opened");
|
|
123
|
-
return undefined;
|
|
124
|
-
}
|
|
125
|
-
if (!id) return undefined;
|
|
126
|
-
this.#open.set(id, {
|
|
127
|
-
title: name,
|
|
128
|
-
parent: origin,
|
|
129
|
-
startedAt: new Date().toISOString(),
|
|
130
|
-
});
|
|
131
|
-
this.#write();
|
|
132
|
-
logger.info({ origin, thread: id }, "dispatch thread opened");
|
|
133
|
-
const thread = this.#thread(id);
|
|
134
|
-
if (intro) await thread.post(intro);
|
|
135
|
-
return thread;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** Archives the threads a restart left open; their dispatches never report. */
|
|
139
|
-
async sweep(): Promise<void> {
|
|
140
|
-
for (const id of [...this.#open.keys()])
|
|
141
|
-
await this.#thread(id).close(messages().restartNotice);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
#thread(id: string): DispatchThread {
|
|
145
|
-
const { host, logger } = this.#options;
|
|
146
|
-
let closed = false;
|
|
147
|
-
const send = host.send?.bind(host);
|
|
148
|
-
const post = async (text: string) => {
|
|
149
|
-
try {
|
|
150
|
-
await host.post(id, text);
|
|
151
|
-
} catch (error) {
|
|
152
|
-
logger.warn({ thread: id, err: error }, "dispatch thread post failed");
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
return {
|
|
156
|
-
id,
|
|
157
|
-
channel: `${PREFIX}${id}`,
|
|
158
|
-
mention: `<#${id}>`,
|
|
159
|
-
post,
|
|
160
|
-
...(send ? { interim: { post: (text: string) => send(id, text) } } : {}),
|
|
161
|
-
close: async (report) => {
|
|
162
|
-
if (closed) return;
|
|
163
|
-
closed = true;
|
|
164
|
-
if (report) await post(report);
|
|
165
|
-
try {
|
|
166
|
-
await host.close(id);
|
|
167
|
-
} catch (error) {
|
|
168
|
-
logger.warn(
|
|
169
|
-
{ thread: id, err: error },
|
|
170
|
-
"dispatch thread not archived",
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
this.#open.delete(id);
|
|
174
|
-
this.#write();
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
#write(): void {
|
|
180
|
-
const path = this.#options.ledgerPath;
|
|
181
|
-
try {
|
|
182
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
183
|
-
const temporary = `${path}.tmp`;
|
|
184
|
-
writeFileSync(
|
|
185
|
-
temporary,
|
|
186
|
-
`${JSON.stringify(Object.fromEntries(this.#open), null, 2)}\n`,
|
|
187
|
-
{ mode: 0o600 },
|
|
188
|
-
);
|
|
189
|
-
renameSync(temporary, path);
|
|
190
|
-
} catch (error) {
|
|
191
|
-
this.#options.logger.warn({ err: error }, "dispatch threads not saved");
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|