pi-roundtable 0.28.0 → 0.29.1
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 +25 -0
- package/docs/plugins.md +32 -9
- package/package.json +1 -1
- package/src/core/agents/agent-messages.ts +5 -7
- package/src/core/agents/agent-rules.ts +27 -0
- package/src/core/agents/agent-team.ts +5 -0
- package/src/core/agents/agent-tools.ts +18 -10
- package/src/core/agents/message-target.ts +20 -18
- package/src/core/agents/routes.ts +42 -24
- package/src/core/agents/team-editing.ts +5 -5
- package/src/core/agents/team-helpers.ts +76 -5
- package/src/core/agents/team-keys.ts +14 -0
- package/src/core/agents/team-turns.ts +2 -4
- package/src/core/builtin/modules.ts +15 -15
- package/src/core/config/routing.ts +5 -4
- package/src/core/contract/channels.ts +55 -2
- package/src/core/discord/direct-channel.ts +1 -0
- package/src/core/host-registration.ts +4 -0
- package/src/core/modules/notify/notify.ts +3 -2
- package/src/core/modules/notify/session-notify.ts +21 -4
- package/src/core/modules/skills/skill-contract.ts +11 -0
- package/src/core/modules/skills/skill-registry.ts +3 -1
- package/src/core/modules/skills/skill-store.ts +4 -3
- package/src/core/presence/delivery-router.ts +165 -0
- package/src/core/presence/direct-channels.ts +39 -68
- package/src/core/routing/actor-of.ts +40 -0
- package/src/core/routing/background-speaker.ts +50 -0
- package/src/core/routing/channel-router.ts +73 -97
- package/src/core/routing/queued-messages.ts +151 -0
- package/src/index.ts +10 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Admission,
|
|
3
|
+
type ChannelClaim,
|
|
4
|
+
type InboundMessage,
|
|
5
|
+
QUEUED_MARK,
|
|
6
|
+
type QueuedBy,
|
|
7
|
+
type QueuedChange,
|
|
8
|
+
type QueuedMessage,
|
|
9
|
+
} from "../contract/channels.ts";
|
|
10
|
+
import type { Logger } from "../log.ts";
|
|
11
|
+
import type { ChannelKey } from "../sessions.ts";
|
|
12
|
+
|
|
13
|
+
type TurnAdmission = Extract<Admission, { kind: "turn" }>;
|
|
14
|
+
|
|
15
|
+
/** A message waiting for its turn, with what runs it; `admission` and `message` follow its edits. */
|
|
16
|
+
export interface QueuedEntry {
|
|
17
|
+
readonly claim: ChannelClaim;
|
|
18
|
+
/** The message as the claim last admitted it: the speaker the router resolved, the text as edited. */
|
|
19
|
+
message: InboundMessage;
|
|
20
|
+
admission: TurnAdmission;
|
|
21
|
+
readonly queuedAt: number;
|
|
22
|
+
/** Cancelled while it waited: its turn does not run. */
|
|
23
|
+
cancelled: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The messages waiting for their own turn, by channel and message id: the router adds one when
|
|
28
|
+
* the running turn did not take it and takes it out when its turn starts, so a cancel or an edit
|
|
29
|
+
* can only reach a message that has not started. Everything is synchronous, so no cancel or edit
|
|
30
|
+
* races the start of a turn: whichever runs first wins.
|
|
31
|
+
*/
|
|
32
|
+
export class QueuedMessages {
|
|
33
|
+
readonly #entries = new Map<ChannelKey, Map<string, QueuedEntry>>();
|
|
34
|
+
readonly #logger: Logger;
|
|
35
|
+
readonly #now: () => number;
|
|
36
|
+
|
|
37
|
+
constructor(logger: Logger, now: () => number = Date.now) {
|
|
38
|
+
this.#logger = logger;
|
|
39
|
+
this.#now = now;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
add(
|
|
43
|
+
claim: ChannelClaim,
|
|
44
|
+
message: InboundMessage,
|
|
45
|
+
admission: TurnAdmission,
|
|
46
|
+
): QueuedEntry {
|
|
47
|
+
const entry: QueuedEntry = {
|
|
48
|
+
claim,
|
|
49
|
+
message,
|
|
50
|
+
admission,
|
|
51
|
+
queuedAt: this.#now(),
|
|
52
|
+
cancelled: false,
|
|
53
|
+
};
|
|
54
|
+
const channel = this.#entries.get(message.channel) ?? new Map();
|
|
55
|
+
channel.set(message.messageId, entry);
|
|
56
|
+
this.#entries.set(message.channel, channel);
|
|
57
|
+
return entry;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Takes the entry out of the queue's books as its turn starts or the host drops it; false when already out. */
|
|
61
|
+
remove(entry: QueuedEntry): boolean {
|
|
62
|
+
const { channel, messageId } = entry.message;
|
|
63
|
+
const held = this.#entries.get(channel);
|
|
64
|
+
if (held?.get(messageId) !== entry) return false;
|
|
65
|
+
held.delete(messageId);
|
|
66
|
+
if (held.size === 0) this.#entries.delete(channel);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
list(channel: ChannelKey): readonly QueuedMessage[] {
|
|
71
|
+
return [...(this.#entries.get(channel)?.values() ?? [])].map((entry) => ({
|
|
72
|
+
messageId: entry.message.messageId,
|
|
73
|
+
...(entry.message.speaker
|
|
74
|
+
? { principalId: entry.message.speaker.principalId }
|
|
75
|
+
: {}),
|
|
76
|
+
text: entry.message.text,
|
|
77
|
+
queuedAt: entry.queuedAt,
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Cancels the message so its turn never starts. `by` is checked against its author, and absent
|
|
83
|
+
* when the surface itself says the message is gone, such as a deleted one. `unmark` removes the
|
|
84
|
+
* waiting mark from the message, which a deleted message cannot take.
|
|
85
|
+
*/
|
|
86
|
+
cancel(
|
|
87
|
+
channel: ChannelKey,
|
|
88
|
+
messageId: string,
|
|
89
|
+
by: QueuedBy | undefined,
|
|
90
|
+
unmark: boolean,
|
|
91
|
+
): QueuedChange {
|
|
92
|
+
const entry = this.#entries.get(channel)?.get(messageId);
|
|
93
|
+
if (!entry) return "unknown";
|
|
94
|
+
if (by && !this.#authored(entry, by)) return "forbidden";
|
|
95
|
+
this.remove(entry);
|
|
96
|
+
entry.cancelled = true;
|
|
97
|
+
entry.admission.dropped?.();
|
|
98
|
+
if (unmark) this.unmark(entry);
|
|
99
|
+
return "done";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Gives the message's turn the new text: the claim admits the message again with it. When the
|
|
104
|
+
* claim no longer takes it, the old admission stays and the edit is refused.
|
|
105
|
+
*/
|
|
106
|
+
edit(
|
|
107
|
+
channel: ChannelKey,
|
|
108
|
+
messageId: string,
|
|
109
|
+
by: QueuedBy | undefined,
|
|
110
|
+
text: string,
|
|
111
|
+
): QueuedChange {
|
|
112
|
+
const entry = this.#entries.get(channel)?.get(messageId);
|
|
113
|
+
if (!entry) return "unknown";
|
|
114
|
+
if (by && !this.#authored(entry, by)) return "forbidden";
|
|
115
|
+
if (entry.message.text === text) return "done";
|
|
116
|
+
const message = { ...entry.message, text };
|
|
117
|
+
let next: Admission | undefined;
|
|
118
|
+
try {
|
|
119
|
+
next = entry.claim.admit(message);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
this.#logger.warn(
|
|
122
|
+
{ channel, err: error },
|
|
123
|
+
"a claim failed to admit an edited queued message; the old text stays",
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
if (next?.kind !== "turn") return "refused";
|
|
127
|
+
const old = entry.admission;
|
|
128
|
+
entry.message = message;
|
|
129
|
+
entry.admission = next;
|
|
130
|
+
old.dropped?.();
|
|
131
|
+
return "done";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Removes the waiting mark; a failure is logged, since the message may be gone. */
|
|
135
|
+
unmark(entry: QueuedEntry): void {
|
|
136
|
+
const { channel } = entry.message;
|
|
137
|
+
entry.admission.busy
|
|
138
|
+
?.unreact(QUEUED_MARK)
|
|
139
|
+
.catch((error: unknown) =>
|
|
140
|
+
this.#logger.warn(
|
|
141
|
+
{ channel, err: error },
|
|
142
|
+
"the waiting mark of a queued message was not removed",
|
|
143
|
+
),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#authored(entry: QueuedEntry, by: QueuedBy): boolean {
|
|
148
|
+
const author = entry.message.speaker?.principalId;
|
|
149
|
+
return author !== undefined && author === by.principalId;
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,11 @@ export {
|
|
|
4
4
|
AGENT_SERVER_PRIORITY,
|
|
5
5
|
OWNER_TARGET,
|
|
6
6
|
} from "./core/agents/agent-claim.ts";
|
|
7
|
-
export {
|
|
7
|
+
export {
|
|
8
|
+
characterOf,
|
|
9
|
+
helperSurface,
|
|
10
|
+
isHelper,
|
|
11
|
+
} from "./core/agents/agent-rules.ts";
|
|
8
12
|
export type {
|
|
9
13
|
Agent,
|
|
10
14
|
AgentGroup,
|
|
@@ -89,9 +93,13 @@ export type {
|
|
|
89
93
|
InboundMessage,
|
|
90
94
|
MessageObserver,
|
|
91
95
|
ObserveFilter,
|
|
96
|
+
QueuedBy,
|
|
97
|
+
QueuedChange,
|
|
98
|
+
QueuedMessage,
|
|
92
99
|
QueuePort,
|
|
93
100
|
ScheduledOutcome,
|
|
94
101
|
} from "./core/contract/channels.ts";
|
|
102
|
+
export { QUEUED_MARK, STEERED_MARK } from "./core/contract/channels.ts";
|
|
95
103
|
export type {
|
|
96
104
|
ImageDrawer,
|
|
97
105
|
Judge,
|
|
@@ -333,6 +341,7 @@ export type {
|
|
|
333
341
|
SkillVersion,
|
|
334
342
|
} from "./core/modules/skills/skill-rules.ts";
|
|
335
343
|
export { SkillConflictError } from "./core/modules/skills/skill-rules.ts";
|
|
344
|
+
export type { SkillGroup } from "./core/modules/skills/skill-store.ts";
|
|
336
345
|
export type {
|
|
337
346
|
ActionEvent,
|
|
338
347
|
AgentTools,
|