pi-roundtable 0.1.0 → 0.2.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 +148 -0
- package/README.md +10 -6
- package/README.zh-TW.md +144 -0
- package/docs/plugins.md +1448 -66
- package/examples/echo-runtime.test.ts +122 -0
- package/examples/echo-runtime.ts +107 -0
- package/examples/events.test.ts +1 -0
- package/examples/events.ts +2 -2
- package/examples/fake-surface.test.ts +180 -0
- package/examples/fake-surface.ts +109 -0
- package/examples/interactions.test.ts +12 -3
- package/examples/interactions.ts +19 -19
- package/examples/shared-services.test.ts +74 -0
- package/examples/shared-services.ts +60 -0
- package/examples/study-room.test.ts +69 -0
- package/examples/study-room.ts +52 -0
- package/examples/support-desk.test.ts +34 -0
- package/examples/support-desk.ts +40 -0
- package/package.json +11 -3
- package/src/cli/checks/database.ts +11 -30
- package/src/cli/checks/images.ts +19 -0
- package/src/cli/doctor.ts +6 -0
- package/src/cli/main.ts +2 -1
- package/src/core/agents/agent-claim.ts +42 -16
- package/src/core/agents/agent-messages.ts +4 -4
- package/src/core/agents/agent-ports.ts +8 -21
- package/src/core/agents/agent-prompt.ts +3 -1
- package/src/core/agents/agent-store.ts +6 -4
- package/src/core/agents/agent-team-fixture.ts +389 -0
- package/src/core/agents/agent-team.ts +26 -25
- package/src/core/agents/agent-tools.ts +34 -15
- package/src/core/agents/avatar-studio.ts +40 -6
- package/src/core/agents/fallback-avatar.ts +85 -0
- package/src/core/agents/team-editing.ts +49 -10
- package/src/core/agents/team-keys.ts +22 -9
- package/src/core/agents/team-layout.ts +4 -3
- package/src/core/agents/team-lifecycle.ts +28 -2
- package/src/core/agents/team-options.ts +9 -5
- package/src/core/agents/team-status.ts +4 -4
- package/src/core/agents/team-text.ts +8 -6
- package/src/core/agents/team-turn-types.ts +4 -4
- package/src/core/agents/team-turns.ts +6 -5
- package/src/core/builtin/agent-server.ts +187 -121
- package/src/core/builtin/discord-admin.ts +41 -0
- package/src/core/builtin/discord.ts +65 -40
- package/src/core/builtin/modules.ts +67 -58
- package/src/core/builtin/session-tool.ts +24 -0
- package/src/core/builtin/skills.ts +72 -0
- package/src/core/builtin/stores.ts +55 -31
- package/src/core/config/config.ts +45 -4
- package/src/core/config/schema.ts +6 -0
- package/src/core/contract/channels.ts +54 -14
- package/src/core/contract/providers.ts +17 -0
- package/src/core/contract/runtime.ts +138 -0
- package/src/core/contract/services.ts +45 -0
- package/src/core/contract/surface.ts +88 -0
- package/src/core/db/migrations.ts +118 -3
- package/src/core/define-roundtable.ts +70 -46
- package/src/core/define.ts +2 -1
- package/src/core/discord/agent-commands.ts +39 -14
- package/src/core/discord/agent-panel.ts +66 -0
- package/src/core/discord/channel-executor.ts +6 -3
- package/src/core/discord/channel-operations.ts +14 -11
- package/src/core/discord/command-collection.ts +46 -0
- package/src/core/{registry/interactions.ts → discord/compose-commands.ts} +5 -5
- package/src/core/discord/connection.ts +35 -0
- package/src/core/discord/discord-surface.ts +32 -94
- package/src/core/discord/dispatch-threads.ts +2 -2
- package/src/core/discord/inbound-message.ts +99 -0
- package/src/core/discord/interaction-module.ts +57 -1
- package/src/core/discord/owner-cards.ts +4 -4
- package/src/core/discord/owner-command.ts +44 -11
- package/src/core/discord/owner-discord.ts +2 -2
- package/src/core/discord/schedule-commands.ts +21 -18
- package/src/core/discord/stop-button.ts +44 -1
- package/src/core/domain/attachment.ts +12 -8
- package/src/core/domain/conversation.ts +3 -4
- package/src/core/domain/owner-prompts.ts +5 -5
- package/src/core/domain/ports.ts +9 -53
- package/src/core/freeze.ts +9 -0
- package/src/core/host.ts +255 -82
- package/src/core/http/listeners.ts +55 -20
- package/src/core/i18n/agent-panel.ts +4 -0
- package/src/core/i18n/index.ts +12 -2
- package/src/core/i18n/owner.ts +7 -1
- package/src/core/i18n/schedules.ts +12 -6
- package/src/core/identity.ts +1 -1
- package/src/core/judging/effort-judge.ts +25 -3
- package/src/core/log.ts +21 -3
- package/src/core/models.ts +2 -2
- package/src/core/modules/background/background-turns.ts +6 -4
- package/src/core/modules/delegation/delegate.ts +3 -2
- package/src/core/modules/delegation/delegator.ts +15 -13
- package/src/core/modules/delegation/{sol-worker.ts → web-research-worker.ts} +6 -6
- package/src/core/modules/discord-admin/discord-admin.ts +6 -6
- package/src/core/modules/host-shell/shell-policy.ts +8 -3
- package/src/core/modules/memory/owner-memory-store.ts +27 -25
- package/src/core/modules/memory/owner-memory.ts +9 -13
- package/src/core/modules/schedules/recurrence.ts +5 -3
- package/src/core/modules/schedules/schedule-store.ts +11 -11
- package/src/core/modules/schedules/schedule-tools.ts +22 -20
- package/src/core/modules/schedules/scheduler.ts +7 -2
- package/src/core/modules/schedules/schedules.ts +9 -3
- package/src/core/modules/skills/skill-registry.ts +3 -2
- package/src/core/modules/skills/skill-store.ts +2 -15
- package/src/core/modules/skills/skill-tools.ts +10 -4
- package/src/core/ops/error-reporter.ts +1 -1
- package/src/core/plugin.ts +232 -28
- package/src/core/registry/contributions.ts +173 -14
- package/src/core/registry/providers.ts +35 -5
- package/src/core/registry/services.ts +229 -0
- package/src/core/routing/channel-queue.ts +5 -0
- package/src/core/routing/channel-router.ts +21 -7
- package/src/core/routing/conversation-turns.ts +139 -0
- package/src/core/routing/message-text.ts +9 -2
- package/src/core/routing/surface-port.ts +39 -0
- package/src/core/runtime/conversation-sessions.ts +4 -3
- package/src/core/runtime/extensions/confirmation-fixture.ts +11 -0
- package/src/core/runtime/extensions/confirmation-gate.ts +11 -9
- package/src/core/runtime/mcp.ts +1 -1
- package/src/core/runtime/pending-confirmation-store.ts +10 -12
- package/src/core/runtime/pi-agent-runtime.ts +9 -6
- package/src/core/runtime/prompt-slot.ts +8 -3
- package/src/core/runtime/runtime-types.ts +9 -35
- package/src/core/runtime/session-factory.ts +21 -5
- package/src/core/runtime/text-tools.ts +1 -3
- package/src/core/services.ts +238 -90
- package/src/core/sessions.ts +3 -2
- package/src/core/shared/{profile-tools.ts → active-tools.ts} +2 -2
- package/src/core/shared/delegate-tool.ts +4 -3
- package/src/core/shared/schedule-tools.ts +22 -13
- package/src/core/shared/session-messages.ts +1 -1
- package/src/core/speakers.ts +6 -38
- package/src/core/testing/database.ts +1 -7
- package/src/core/testing/eager-catalog.ts +30 -0
- package/src/core/testing/locale.ts +23 -5
- package/src/core/testing/modules.ts +105 -41
- package/src/core/testing/test-host.ts +279 -0
- package/src/core/testing/tool-set.ts +64 -0
- package/src/core/time.ts +6 -1
- package/src/core/tool-set.snapshot.json +288 -0
- package/src/core/tool-tiers.ts +17 -57
- package/src/discord/index.ts +64 -0
- package/src/index.ts +181 -10
- package/src/kit/channels.ts +14 -0
- package/src/kit/domain.ts +18 -0
- package/src/kit/holds.ts +4 -0
- package/src/kit/index.ts +129 -0
- package/src/kit/judging.ts +10 -0
- package/src/kit/memory.ts +3 -0
- package/src/kit/mirror.ts +19 -0
- package/src/kit/presentation.ts +7 -0
- package/src/kit/shell.ts +7 -0
- package/src/kit/skills.ts +8 -0
- package/src/kit/support.ts +21 -0
- package/src/kit/threads.ts +7 -0
- package/src/kit/tools.ts +14 -0
- package/src/kit/worker.ts +16 -0
- package/src/testing.ts +394 -34
- package/examples/guide.test.ts +0 -52
- package/src/cli/add-plugin.test.ts +0 -107
- package/src/cli/checks/basic.test.ts +0 -276
- package/src/cli/checks/database.test.ts +0 -117
- package/src/cli/checks/discord.test.ts +0 -200
- package/src/cli/cli.test.ts +0 -122
- package/src/cli/config-edit.test.ts +0 -130
- package/src/cli/doctor.test.ts +0 -184
- package/src/cli/init.test.ts +0 -127
- package/src/cli/size.test.ts +0 -9
- package/src/cli/templates.test.ts +0 -129
- package/src/cli/testing/fixtures.ts +0 -106
- package/src/core/agents/agent-claim.test.ts +0 -142
- package/src/core/agents/agent-dashboard.test.ts +0 -120
- package/src/core/agents/agent-guild.test.ts +0 -124
- package/src/core/agents/agent-store.test.ts +0 -205
- package/src/core/agents/avatar-studio.test.ts +0 -88
- package/src/core/agents/group-round.test.ts +0 -143
- package/src/core/agents/owner-identity.test.ts +0 -172
- package/src/core/agents/team-turns.test.ts +0 -278
- package/src/core/attachments/attachments.test.ts +0 -85
- package/src/core/boundary.test.ts +0 -45
- package/src/core/builtin/modules.test.ts +0 -127
- package/src/core/config/config.test.ts +0 -135
- package/src/core/contract/discord.ts +0 -33
- package/src/core/db/migrations.test.ts +0 -237
- package/src/core/define-roundtable.test.ts +0 -134
- package/src/core/define.test.ts +0 -144
- package/src/core/discord/agent-commands.test.ts +0 -58
- package/src/core/discord/agent-discord.test.ts +0 -70
- package/src/core/discord/dispatch-thread-host.test.ts +0 -114
- package/src/core/discord/dispatch-threads.test.ts +0 -94
- package/src/core/discord/owner-cards.test.ts +0 -435
- package/src/core/discord/owner-discord.test.ts +0 -325
- package/src/core/domain/expression.ts +0 -21
- package/src/core/domain/profile.ts +0 -32
- package/src/core/drain.test.ts +0 -46
- package/src/core/events.test.ts +0 -81
- package/src/core/holds.test.ts +0 -54
- package/src/core/host.test.ts +0 -536
- package/src/core/http/listeners.test.ts +0 -164
- package/src/core/i18n/i18n.test.ts +0 -139
- package/src/core/identity.test.ts +0 -18
- package/src/core/judging/effort-judge.test.ts +0 -112
- package/src/core/judging/model-judge.test.ts +0 -126
- package/src/core/modules/delegation/delegator.test.ts +0 -170
- package/src/core/modules/memory/owner-memory-store.test.ts +0 -244
- package/src/core/modules/schedules/schedule.test.ts +0 -358
- package/src/core/modules/skills/skill-kind.test.ts +0 -55
- package/src/core/modules/skills/skill-registry.test.ts +0 -376
- package/src/core/ops/error-reporter.test.ts +0 -283
- package/src/core/presentation/card-cadence.ts +0 -41
- package/src/core/presentation/presentation.test.ts +0 -166
- package/src/core/public-entry.test.ts +0 -59
- package/src/core/registry/contributions.test.ts +0 -215
- package/src/core/registry/interactions.test.ts +0 -87
- package/src/core/registry/providers.test.ts +0 -103
- package/src/core/routing/channel-queue.test.ts +0 -31
- package/src/core/routing/channel-router.test.ts +0 -326
- package/src/core/routing/conversation-kind.ts +0 -14
- package/src/core/routing/settle-turn.test.ts +0 -21
- package/src/core/runtime/compaction-tiers.test.ts +0 -227
- package/src/core/runtime/extensions/ask-user.test.ts +0 -114
- package/src/core/runtime/extensions/self-compact-guard.test.ts +0 -50
- package/src/core/runtime/pending-confirmation-store.test.ts +0 -52
- package/src/core/runtime/session-archive.test.ts +0 -20
- package/src/core/runtime/steerable-run.test.ts +0 -264
- package/src/core/runtime/text-tools.test.ts +0 -110
- package/src/core/runtime/turn-answer.test.ts +0 -72
- package/src/core/runtime/worker-task.test.ts +0 -82
- package/src/core/services.test.ts +0 -28
- package/src/core/sessions.test.ts +0 -85
- package/src/core/shared/unix-server.ts +0 -18
- package/src/core/size.test.ts +0 -9
- package/src/core/speakers.test.ts +0 -78
- package/src/core/testing/file-size.ts +0 -34
- package/src/core/time.test.ts +0 -166
- package/src/core/tool-tiers.test.ts +0 -117
- package/src/entries.test.ts +0 -112
- package/src/testing.test.ts +0 -156
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { PluginError } from "./errors.ts";
|
|
3
|
-
import { compileSessionPlan, planOrder, type SessionTool } from "./sessions.ts";
|
|
4
|
-
|
|
5
|
-
const tool = (name: string, phase: SessionTool["phase"]): SessionTool => ({
|
|
6
|
-
name,
|
|
7
|
-
phase,
|
|
8
|
-
...(phase === "compaction" ? { engine: `${name}-engine` } : {}),
|
|
9
|
-
snapshot: () => ({ revision: 0, factory: () => null }),
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const names = (tools: readonly SessionTool[]) => tools.map((t) => t.name);
|
|
13
|
-
|
|
14
|
-
describe("compileSessionPlan", () => {
|
|
15
|
-
test("orders tools, then the compactor, then MCP, keeping registration order in each", () => {
|
|
16
|
-
const plan = compileSessionPlan([
|
|
17
|
-
tool("mcp-a", "mcp"),
|
|
18
|
-
tool("memory", "tools"),
|
|
19
|
-
tool("summarizer", "compaction"),
|
|
20
|
-
tool("notify", "tools"),
|
|
21
|
-
tool("mcp-b", "mcp"),
|
|
22
|
-
]);
|
|
23
|
-
expect(names(planOrder(plan))).toEqual([
|
|
24
|
-
"memory",
|
|
25
|
-
"notify",
|
|
26
|
-
"summarizer",
|
|
27
|
-
"mcp-a",
|
|
28
|
-
"mcp-b",
|
|
29
|
-
]);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test("a plan without a compactor has none", () => {
|
|
33
|
-
const plan = compileSessionPlan([tool("memory", "tools")]);
|
|
34
|
-
expect(plan.compaction).toBeUndefined();
|
|
35
|
-
expect(names(planOrder(plan))).toEqual(["memory"]);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("refuses a name registered twice", () => {
|
|
39
|
-
expect(() =>
|
|
40
|
-
compileSessionPlan([tool("memory", "tools"), tool("memory", "mcp")]),
|
|
41
|
-
).toThrow(
|
|
42
|
-
new PluginError(
|
|
43
|
-
"session tool memory is registered twice. Rename one of the two.",
|
|
44
|
-
),
|
|
45
|
-
);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test("refuses the names of the core's own extensions", () => {
|
|
49
|
-
for (const name of [
|
|
50
|
-
"read-attachment",
|
|
51
|
-
"confirmation-gate",
|
|
52
|
-
"ask-user",
|
|
53
|
-
"self-compact-guard",
|
|
54
|
-
"profile-tools",
|
|
55
|
-
]) {
|
|
56
|
-
expect(() => compileSessionPlan([tool(name, "tools")])).toThrow(
|
|
57
|
-
new PluginError(
|
|
58
|
-
`session tool ${name} takes a core extension name. Rename it.`,
|
|
59
|
-
),
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test("refuses a compactor that names no engine", () => {
|
|
65
|
-
const { engine: _engine, ...unmarked } = tool("summarizer", "compaction");
|
|
66
|
-
expect(() => compileSessionPlan([unmarked])).toThrow(
|
|
67
|
-
new PluginError(
|
|
68
|
-
"compactor summarizer needs an engine: the value its compactions record as details.engine.",
|
|
69
|
-
),
|
|
70
|
-
);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("refuses a second compactor", () => {
|
|
74
|
-
expect(() =>
|
|
75
|
-
compileSessionPlan([
|
|
76
|
-
tool("summarizer", "compaction"),
|
|
77
|
-
tool("pi", "compaction"),
|
|
78
|
-
]),
|
|
79
|
-
).toThrow(
|
|
80
|
-
new PluginError(
|
|
81
|
-
"only one compactor may run; got summarizer, pi. Keep one.",
|
|
82
|
-
),
|
|
83
|
-
);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { rmSync } from "node:fs";
|
|
2
|
-
import type { Server } from "bun";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Serves HTTP on a unix socket without Bun's 10-second idle timeout, which would cut long
|
|
6
|
-
* turns and quiet model streams. Bun 1.4.2 honors `idleTimeout` on unix sockets, but its
|
|
7
|
-
* types reject the option there, hence the cast. A stale socket file is removed first.
|
|
8
|
-
*/
|
|
9
|
-
export function serveUnix(
|
|
10
|
-
socketPath: string,
|
|
11
|
-
fetch: (request: Request) => Response | Promise<Response>,
|
|
12
|
-
): Server<undefined> {
|
|
13
|
-
rmSync(socketPath, { force: true });
|
|
14
|
-
const options = { unix: socketPath, idleTimeout: 0, fetch };
|
|
15
|
-
return Bun.serve(
|
|
16
|
-
options as unknown as Parameters<typeof Bun.serve>[0],
|
|
17
|
-
) as Server<undefined>;
|
|
18
|
-
}
|
package/src/core/size.test.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { expect, test } from "bun:test";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { oversizedFiles } from "./testing/file-size.ts";
|
|
4
|
-
|
|
5
|
-
test("no file of the core is over the size limit", () => {
|
|
6
|
-
expect(
|
|
7
|
-
oversizedFiles(resolve(import.meta.dir, "../.."), ["src/core"]),
|
|
8
|
-
).toEqual([]);
|
|
9
|
-
});
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { ConfigError } from "./domain/errors.ts";
|
|
3
|
-
import { parseTierMembers, speakerPolicy, tierAtLeast } from "./speakers.ts";
|
|
4
|
-
|
|
5
|
-
const OWNER = "100000000000000001";
|
|
6
|
-
const ADMIN = "100000000000000002";
|
|
7
|
-
const MEMBER = "100000000000000003";
|
|
8
|
-
const ROLE = "200000000000000001";
|
|
9
|
-
const STRANGER = "100000000000000009";
|
|
10
|
-
const author = (id: string, roleIds: string[] = []) => ({
|
|
11
|
-
id,
|
|
12
|
-
name: `u${id.slice(-1)}`,
|
|
13
|
-
roleIds,
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
describe("speakerPolicy", () => {
|
|
17
|
-
test("names only the owner by default, so nobody else is a speaker", () => {
|
|
18
|
-
const policy = speakerPolicy({ owners: [OWNER] });
|
|
19
|
-
expect(policy.resolve(author(OWNER))?.tier).toBe("owner");
|
|
20
|
-
expect(policy.resolve(author(STRANGER, [ROLE]))).toBeUndefined();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test("admins and members come from users or roles, and the highest tier wins", () => {
|
|
24
|
-
const policy = speakerPolicy({
|
|
25
|
-
owners: [OWNER],
|
|
26
|
-
admins: { users: [ADMIN], roles: [ROLE] },
|
|
27
|
-
members: { users: [MEMBER, ADMIN] },
|
|
28
|
-
});
|
|
29
|
-
expect(policy.resolve(author(ADMIN))?.tier).toBe("admin");
|
|
30
|
-
expect(policy.resolve(author(STRANGER, [ROLE]))?.tier).toBe("admin");
|
|
31
|
-
expect(policy.resolve(author(MEMBER))?.tier).toBe("member");
|
|
32
|
-
expect(policy.resolve(author(OWNER, [ROLE]))?.tier).toBe("owner");
|
|
33
|
-
expect(policy.resolve(author(STRANGER))).toBeUndefined();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("@everyone makes every author a member", () => {
|
|
37
|
-
const policy = speakerPolicy({
|
|
38
|
-
owners: [OWNER],
|
|
39
|
-
members: { everyone: true },
|
|
40
|
-
});
|
|
41
|
-
expect(policy.resolve(author(STRANGER))).toEqual({
|
|
42
|
-
id: STRANGER,
|
|
43
|
-
name: "u9",
|
|
44
|
-
tier: "member",
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("tiers order owner above admin above member", () => {
|
|
50
|
-
expect(tierAtLeast("owner", "admin")).toBe(true);
|
|
51
|
-
expect(tierAtLeast("admin", "admin")).toBe(true);
|
|
52
|
-
expect(tierAtLeast("member", "admin")).toBe(false);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe("parseTierMembers", () => {
|
|
56
|
-
test("reads users, roles, and @everyone", () => {
|
|
57
|
-
expect(
|
|
58
|
-
parseTierMembers(
|
|
59
|
-
`user:${ADMIN}, role:${ROLE}, ${MEMBER}, @everyone`,
|
|
60
|
-
"X",
|
|
61
|
-
true,
|
|
62
|
-
),
|
|
63
|
-
).toEqual({ users: [ADMIN, MEMBER], roles: [ROLE], everyone: true });
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test("unset or empty means nobody", () => {
|
|
67
|
-
expect(parseTierMembers(undefined, "X", true)).toBeUndefined();
|
|
68
|
-
expect(parseTierMembers(" , ", "X", true)).toBeUndefined();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("refuses a malformed entry, and @everyone where it is not allowed", () => {
|
|
72
|
-
expect(() => parseTierMembers("nope", "X", true)).toThrow(ConfigError);
|
|
73
|
-
expect(() => parseTierMembers("group:1", "X", true)).toThrow(ConfigError);
|
|
74
|
-
expect(() => parseTierMembers("@everyone", "X", false)).toThrow(
|
|
75
|
-
/@everyone/,
|
|
76
|
-
);
|
|
77
|
-
});
|
|
78
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { readdirSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join, relative } from "node:path";
|
|
3
|
-
|
|
4
|
-
/** The most lines a source file may have before it is split along its responsibilities. */
|
|
5
|
-
export const MAX_SOURCE_LINES = 500;
|
|
6
|
-
/** The most lines a test file may have. */
|
|
7
|
-
export const MAX_TEST_LINES = 700;
|
|
8
|
-
|
|
9
|
-
function typeScriptFiles(dir: string): string[] {
|
|
10
|
-
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
11
|
-
if (entry.name === "node_modules") return [];
|
|
12
|
-
const path = join(dir, entry.name);
|
|
13
|
-
if (entry.isDirectory()) return typeScriptFiles(path);
|
|
14
|
-
return /\.tsx?$/.test(entry.name) ? [path] : [];
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** The files under `dirs` over their limit, each as `<path> has <n> lines`. */
|
|
19
|
-
export function oversizedFiles(
|
|
20
|
-
root: string,
|
|
21
|
-
dirs: readonly string[],
|
|
22
|
-
): string[] {
|
|
23
|
-
return dirs
|
|
24
|
-
.flatMap((dir) => typeScriptFiles(join(root, dir)))
|
|
25
|
-
.flatMap((file) => {
|
|
26
|
-
const lines = readFileSync(file, "utf8").split("\n").length - 1;
|
|
27
|
-
const limit = /\.test\.tsx?$/.test(file)
|
|
28
|
-
? MAX_TEST_LINES
|
|
29
|
-
: MAX_SOURCE_LINES;
|
|
30
|
-
return lines > limit
|
|
31
|
-
? [`${relative(root, file)} has ${lines} lines, over ${limit}`]
|
|
32
|
-
: [];
|
|
33
|
-
});
|
|
34
|
-
}
|
package/src/core/time.test.ts
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { setLocale } from "./i18n/index.ts";
|
|
3
|
-
import { nextRun, parseRecurrence } from "./modules/schedules/recurrence.ts";
|
|
4
|
-
import { scheduleToolSpecs } from "./shared/schedule-tools.ts";
|
|
5
|
-
import { useTestLocale } from "./testing/locale.ts";
|
|
6
|
-
import {
|
|
7
|
-
setTimeZone,
|
|
8
|
-
timeZone,
|
|
9
|
-
zonedDate,
|
|
10
|
-
zonedInstant,
|
|
11
|
-
zonedStamp,
|
|
12
|
-
zonedToday,
|
|
13
|
-
} from "./time.ts";
|
|
14
|
-
|
|
15
|
-
afterEach(useTestLocale);
|
|
16
|
-
|
|
17
|
-
describe("configured time zone", () => {
|
|
18
|
-
test("defaults to UTC before a host configures the process", () => {
|
|
19
|
-
const result = Bun.spawnSync({
|
|
20
|
-
cmd: [
|
|
21
|
-
"bun",
|
|
22
|
-
"-e",
|
|
23
|
-
'import { timeZone } from "./src/core/time.ts"; console.log(timeZone())',
|
|
24
|
-
],
|
|
25
|
-
});
|
|
26
|
-
expect(result.exitCode).toBe(0);
|
|
27
|
-
expect(result.stdout.toString().trim()).toBe("UTC");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("rejects an invalid zone without changing the current one", () => {
|
|
31
|
-
setTimeZone("UTC");
|
|
32
|
-
expect(() => setTimeZone("Not/A_Zone")).toThrow("Not/A_Zone");
|
|
33
|
-
expect(timeZone()).toBe("UTC");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("Taipei agrees with the former +8 arithmetic for a year-long sweep", () => {
|
|
37
|
-
setTimeZone("Asia/Taipei");
|
|
38
|
-
const start = Date.parse("2026-01-01T00:00:00Z");
|
|
39
|
-
for (let i = 0; i < 366; i += 1) {
|
|
40
|
-
const at = new Date(start + i * 86_400_000 + i * 61_000);
|
|
41
|
-
const old = new Date(at.getTime() + 8 * 3_600_000).toISOString();
|
|
42
|
-
expect(zonedDate(at)).toBe(old.slice(0, 10));
|
|
43
|
-
expect(zonedStamp(at)).toBe(old.slice(0, 16).replace("T", " "));
|
|
44
|
-
}
|
|
45
|
-
expect(zonedToday()).toBe(zonedDate(new Date()));
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test("New York resolves spring gaps later and fall folds to the first occurrence", () => {
|
|
49
|
-
setTimeZone("America/New_York");
|
|
50
|
-
expect(zonedInstant("2026-03-08", "02:30").toISOString()).toBe(
|
|
51
|
-
"2026-03-08T07:30:00.000Z",
|
|
52
|
-
);
|
|
53
|
-
expect(zonedStamp(zonedInstant("2026-03-08", "02:30"))).toBe(
|
|
54
|
-
"2026-03-08 03:30",
|
|
55
|
-
);
|
|
56
|
-
expect(zonedInstant("2026-11-01", "01:30").toISOString()).toBe(
|
|
57
|
-
"2026-11-01T05:30:00.000Z",
|
|
58
|
-
);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("handles +14 and half-hour offsets", () => {
|
|
62
|
-
setTimeZone("Pacific/Kiritimati");
|
|
63
|
-
expect(zonedInstant("2026-07-01", "09:15").toISOString()).toBe(
|
|
64
|
-
"2026-06-30T19:15:00.000Z",
|
|
65
|
-
);
|
|
66
|
-
setTimeZone("America/St_Johns");
|
|
67
|
-
expect(zonedInstant("2026-01-01", "09:15").toISOString()).toBe(
|
|
68
|
-
"2026-01-01T12:45:00.000Z",
|
|
69
|
-
);
|
|
70
|
-
expect(zonedInstant("2026-07-01", "09:15").toISOString()).toBe(
|
|
71
|
-
"2026-07-01T11:45:00.000Z",
|
|
72
|
-
);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("daily, weekly, and interval recurrence stay at wall-clock time across DST", () => {
|
|
76
|
-
setTimeZone("America/New_York");
|
|
77
|
-
const before = new Date("2026-03-07T15:00:00Z");
|
|
78
|
-
const daily = parseRecurrence(
|
|
79
|
-
{ time: "09:00", start_date: "2026-03-07" },
|
|
80
|
-
before,
|
|
81
|
-
);
|
|
82
|
-
expect(nextRun(daily, before)?.toISOString()).toBe(
|
|
83
|
-
"2026-03-08T13:00:00.000Z",
|
|
84
|
-
);
|
|
85
|
-
expect(
|
|
86
|
-
nextRun(daily, new Date("2026-03-08T13:00:00Z"))?.toISOString(),
|
|
87
|
-
).toBe("2026-03-09T13:00:00.000Z");
|
|
88
|
-
expect(
|
|
89
|
-
nextRun(daily, new Date("2026-10-31T13:00:00Z"))?.toISOString(),
|
|
90
|
-
).toBe("2026-11-01T14:00:00.000Z");
|
|
91
|
-
const weekly = parseRecurrence(
|
|
92
|
-
{ time: "09:00", weekdays: ["sun"] },
|
|
93
|
-
before,
|
|
94
|
-
);
|
|
95
|
-
expect(nextRun(weekly, before)?.toISOString()).toBe(
|
|
96
|
-
"2026-03-08T13:00:00.000Z",
|
|
97
|
-
);
|
|
98
|
-
expect(
|
|
99
|
-
nextRun(weekly, new Date("2026-03-08T13:00:00Z"))?.toISOString(),
|
|
100
|
-
).toBe("2026-03-15T13:00:00.000Z");
|
|
101
|
-
const interval = parseRecurrence(
|
|
102
|
-
{ time: "09:00", every_days: 2, start_date: "2026-03-07" },
|
|
103
|
-
before,
|
|
104
|
-
);
|
|
105
|
-
expect(nextRun(interval, before)?.toISOString()).toBe(
|
|
106
|
-
"2026-03-09T13:00:00.000Z",
|
|
107
|
-
);
|
|
108
|
-
expect(
|
|
109
|
-
nextRun(
|
|
110
|
-
{ kind: "once", date: "2026-11-01", time: "01:30" },
|
|
111
|
-
new Date("2026-10-31T12:00:00Z"),
|
|
112
|
-
)?.toISOString(),
|
|
113
|
-
).toBe("2026-11-01T05:30:00.000Z");
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test("the wording of every catalog derives from the configured zone, with none special", () => {
|
|
117
|
-
const errors = () => [
|
|
118
|
-
() => parseRecurrence({ at: "soon" }, new Date()),
|
|
119
|
-
() => parseRecurrence({ time: "soon" }, new Date()),
|
|
120
|
-
];
|
|
121
|
-
const messagesFor = (locale: "en" | "zh-TW") => {
|
|
122
|
-
setLocale(locale, { assistant: "Roundtable", root: "roundtable" });
|
|
123
|
-
return errors().map((run) => {
|
|
124
|
-
try {
|
|
125
|
-
run();
|
|
126
|
-
} catch (error) {
|
|
127
|
-
return String(error);
|
|
128
|
-
}
|
|
129
|
-
throw new Error("expected the recurrence to be refused");
|
|
130
|
-
});
|
|
131
|
-
};
|
|
132
|
-
setTimeZone("Europe/Berlin");
|
|
133
|
-
expect(messagesFor("en")).toEqual([
|
|
134
|
-
expect.stringContaining('at must be Europe/Berlin time as "YYYY-MM-DD'),
|
|
135
|
-
expect.stringContaining('"HH:MM", Europe/Berlin) to repeat'),
|
|
136
|
-
]);
|
|
137
|
-
setTimeZone("America/Argentina/Buenos_Aires");
|
|
138
|
-
expect(messagesFor("zh-TW")).toEqual([
|
|
139
|
-
expect.stringContaining('at must be Buenos Aires time as "YYYY-MM-DD'),
|
|
140
|
-
expect.stringContaining('"HH:MM", Buenos Aires) to repeat'),
|
|
141
|
-
]);
|
|
142
|
-
setTimeZone("UTC");
|
|
143
|
-
expect(scheduleToolSpecs()[0]?.description).toContain("UTC time");
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test("relative once scheduling preserves the second occurrence of a fall-back hour", () => {
|
|
147
|
-
setTimeZone("America/New_York");
|
|
148
|
-
const now = new Date("2026-11-01T06:10:00Z");
|
|
149
|
-
const once = parseRecurrence({ in_minutes: 20 }, now);
|
|
150
|
-
expect(nextRun(once, now)?.toISOString()).toBe("2026-11-01T06:30:00.000Z");
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("tool descriptions take the zone when built, in the wording of the locale", () => {
|
|
154
|
-
setTimeZone("Asia/Taipei");
|
|
155
|
-
expect(scheduleToolSpecs()[0]?.description).toContain(
|
|
156
|
-
"set Asia/Taipei time",
|
|
157
|
-
);
|
|
158
|
-
setLocale("zh-TW", { assistant: "Roundtable", root: "roundtable" });
|
|
159
|
-
expect(scheduleToolSpecs()[0]?.description).toContain("set Taipei time");
|
|
160
|
-
setLocale("en", { assistant: "Roundtable", root: "roundtable" });
|
|
161
|
-
setTimeZone("America/New_York");
|
|
162
|
-
expect(scheduleToolSpecs()[0]?.description).toContain(
|
|
163
|
-
"America/New_York time",
|
|
164
|
-
);
|
|
165
|
-
});
|
|
166
|
-
});
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { ConfigError } from "./domain/errors.ts";
|
|
3
|
-
import {
|
|
4
|
-
CORE_TOOL_TIERS,
|
|
5
|
-
parseToolTiers,
|
|
6
|
-
toolsForTier,
|
|
7
|
-
toolTiers,
|
|
8
|
-
} from "./tool-tiers.ts";
|
|
9
|
-
|
|
10
|
-
describe("default tiers", () => {
|
|
11
|
-
const tiers = toolTiers();
|
|
12
|
-
|
|
13
|
-
test("members may look, read, and message agents", () => {
|
|
14
|
-
for (const tool of [
|
|
15
|
-
"agent_list",
|
|
16
|
-
"agent_get",
|
|
17
|
-
"message_agent",
|
|
18
|
-
"channel_read",
|
|
19
|
-
"ask_user",
|
|
20
|
-
"web_search",
|
|
21
|
-
])
|
|
22
|
-
expect(tiers.minTier(tool)).toBe("member");
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("every speaker may keep a memory of their own", () => {
|
|
26
|
-
for (const tool of ["memory_add", "memory_search", "memory_remove"])
|
|
27
|
-
expect(tiers.minTier(tool)).toBe("member");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("creating, editing, archiving, and arranging agents and groups need an admin", () => {
|
|
31
|
-
for (const tool of [
|
|
32
|
-
"agent_create",
|
|
33
|
-
"agent_update",
|
|
34
|
-
"agent_avatar",
|
|
35
|
-
"group_create",
|
|
36
|
-
"group_update",
|
|
37
|
-
"archive",
|
|
38
|
-
"channel_arrange",
|
|
39
|
-
"agent_skills",
|
|
40
|
-
"schedule_create",
|
|
41
|
-
"delegate_task",
|
|
42
|
-
])
|
|
43
|
-
expect(tiers.minTier(tool)).toBe("admin");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("the shell, notifying the owner, Discord administration, and any tool nobody named stay with the owner", () => {
|
|
47
|
-
for (const tool of [
|
|
48
|
-
"bash",
|
|
49
|
-
"write",
|
|
50
|
-
"notify_owner",
|
|
51
|
-
"discord_ban",
|
|
52
|
-
"some_plugin_tool",
|
|
53
|
-
])
|
|
54
|
-
expect(tiers.minTier(tool)).toBe("owner");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("every tool an agent carries from the core is named on purpose", () => {
|
|
58
|
-
expect(Object.keys(CORE_TOOL_TIERS)).toEqual(
|
|
59
|
-
expect.arrayContaining(["agent_create", "skill_create", "schedule_list"]),
|
|
60
|
-
);
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe("toolsForTier", () => {
|
|
65
|
-
const tiers = toolTiers();
|
|
66
|
-
const all = ["agent_list", "agent_create", "bash", "some_plugin_tool"];
|
|
67
|
-
|
|
68
|
-
test("each tier gets the tools at or below it, in order", () => {
|
|
69
|
-
expect(toolsForTier(all, "member", tiers)).toEqual(["agent_list"]);
|
|
70
|
-
expect(toolsForTier(all, "admin", tiers)).toEqual([
|
|
71
|
-
"agent_list",
|
|
72
|
-
"agent_create",
|
|
73
|
-
]);
|
|
74
|
-
expect(toolsForTier(all, "owner", tiers)).toEqual(all);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("an operator's map lowers or raises a tool", () => {
|
|
78
|
-
const custom = toolTiers({ bash: "admin", agent_list: "owner" });
|
|
79
|
-
expect(toolsForTier(all, "member", custom)).toEqual([]);
|
|
80
|
-
expect(toolsForTier(all, "admin", custom)).toEqual([
|
|
81
|
-
"agent_create",
|
|
82
|
-
"bash",
|
|
83
|
-
]);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test("a plugin's declared tier is read at use time; the operator's still wins", () => {
|
|
87
|
-
const table = toolTiers({ plugin_b: "owner" });
|
|
88
|
-
expect(table.minTier("plugin_a")).toBe("owner");
|
|
89
|
-
table.declare("a", { plugin_a: "member" });
|
|
90
|
-
table.declare("b", { plugin_b: "member" });
|
|
91
|
-
expect(table.minTier("plugin_a")).toBe("member");
|
|
92
|
-
expect(table.minTier("plugin_b")).toBe("owner");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test("a tool two plugins declare names both plugins and the fix", () => {
|
|
96
|
-
const table = toolTiers();
|
|
97
|
-
table.declare("a", { shared_tool: "member" });
|
|
98
|
-
expect(() => table.declare("b", { shared_tool: "member" })).toThrow(
|
|
99
|
-
"plugin b: tool shared_tool is already defined by plugin a. Rename one of the two tools.",
|
|
100
|
-
);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
describe("parseToolTiers", () => {
|
|
105
|
-
test("reads tool=tier pairs", () => {
|
|
106
|
-
expect(parseToolTiers("bash=admin, web_search=member", "X")).toEqual({
|
|
107
|
-
bash: "admin",
|
|
108
|
-
web_search: "member",
|
|
109
|
-
});
|
|
110
|
-
expect(parseToolTiers(undefined, "X")).toEqual({});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test("refuses a pair that is not a tier", () => {
|
|
114
|
-
expect(() => parseToolTiers("bash=root", "X")).toThrow(ConfigError);
|
|
115
|
-
expect(() => parseToolTiers("bash", "X")).toThrow(ConfigError);
|
|
116
|
-
});
|
|
117
|
-
});
|
package/src/entries.test.ts
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { expect, test } from "bun:test";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
|
-
import * as main from "./index.ts";
|
|
5
|
-
import * as testing from "./testing.ts";
|
|
6
|
-
|
|
7
|
-
// Types disappear at runtime. Update this list AND the changelog for every entry change.
|
|
8
|
-
export const MAIN_EXPORTS = [
|
|
9
|
-
"NotLinkedError",
|
|
10
|
-
"PluginError",
|
|
11
|
-
"Roundtable",
|
|
12
|
-
"ToolRefusal",
|
|
13
|
-
"definePlugin",
|
|
14
|
-
"defineRoundtable",
|
|
15
|
-
"defineTool",
|
|
16
|
-
];
|
|
17
|
-
export const MAIN_TYPE_EXPORTS = [
|
|
18
|
-
"AgentSeed",
|
|
19
|
-
"ChannelClaim",
|
|
20
|
-
"Contribution",
|
|
21
|
-
"DefinedRoundtable",
|
|
22
|
-
"EventHandlers",
|
|
23
|
-
"HoldRule",
|
|
24
|
-
"InteractionContribution",
|
|
25
|
-
"Migration",
|
|
26
|
-
"PluginContext",
|
|
27
|
-
"PromptSection",
|
|
28
|
-
"PromptTurn",
|
|
29
|
-
"RoundtableConfig",
|
|
30
|
-
"RoundtableOptions",
|
|
31
|
-
"RoundtablePlugin",
|
|
32
|
-
"Service",
|
|
33
|
-
"SessionTool",
|
|
34
|
-
"Speaker",
|
|
35
|
-
"Tier",
|
|
36
|
-
"ToolContribution",
|
|
37
|
-
"ToolSpec",
|
|
38
|
-
"ToolTurn",
|
|
39
|
-
"TurnEndEvent",
|
|
40
|
-
"TurnEvent",
|
|
41
|
-
];
|
|
42
|
-
export const TESTING_EXPORTS = ["testPlugin"];
|
|
43
|
-
export const TESTING_TYPE_EXPORTS = [
|
|
44
|
-
"RecordedEvent",
|
|
45
|
-
"TestPluginOptions",
|
|
46
|
-
"TestPluginResult",
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
/** Source-level names include type-only exports, which Object.keys cannot observe. */
|
|
50
|
-
function entryNames(path: string): string[] {
|
|
51
|
-
const source = readFileSync(path, "utf8");
|
|
52
|
-
if (path.endsWith("index.ts"))
|
|
53
|
-
return [...source.matchAll(/export(?: type)?\s*\{([^}]+)\}/gs)]
|
|
54
|
-
.flatMap((match) =>
|
|
55
|
-
(match[1] ?? "")
|
|
56
|
-
.split(",")
|
|
57
|
-
.map((name) => name.trim())
|
|
58
|
-
.filter(Boolean),
|
|
59
|
-
)
|
|
60
|
-
.sort();
|
|
61
|
-
return [
|
|
62
|
-
...source.matchAll(
|
|
63
|
-
/export (?:async )?(?:interface|function|type|class|const) (\w+)/g,
|
|
64
|
-
),
|
|
65
|
-
]
|
|
66
|
-
.map((match) => match[1] ?? "")
|
|
67
|
-
.sort();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
test("the public entries have exactly the snapshotted runtime exports", () => {
|
|
71
|
-
expect(Object.keys(main).sort()).toEqual(MAIN_EXPORTS);
|
|
72
|
-
expect(Object.keys(testing).sort()).toEqual(TESTING_EXPORTS);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("type-only names are snapshotted and every public name is in the changelog", () => {
|
|
76
|
-
expect(entryNames(resolve(import.meta.dir, "index.ts"))).toEqual(
|
|
77
|
-
[...MAIN_EXPORTS, ...MAIN_TYPE_EXPORTS].sort(),
|
|
78
|
-
);
|
|
79
|
-
expect(entryNames(resolve(import.meta.dir, "testing.ts"))).toEqual(
|
|
80
|
-
[...TESTING_EXPORTS, ...TESTING_TYPE_EXPORTS].sort(),
|
|
81
|
-
);
|
|
82
|
-
const local = resolve(import.meta.dir, "../packaging/CHANGELOG.md");
|
|
83
|
-
const changelog = readFileSync(
|
|
84
|
-
existsSync(local) ? local : resolve(import.meta.dir, "../CHANGELOG.md"),
|
|
85
|
-
"utf8",
|
|
86
|
-
);
|
|
87
|
-
for (const name of [
|
|
88
|
-
...MAIN_EXPORTS,
|
|
89
|
-
...MAIN_TYPE_EXPORTS,
|
|
90
|
-
...TESTING_EXPORTS,
|
|
91
|
-
...TESTING_TYPE_EXPORTS,
|
|
92
|
-
])
|
|
93
|
-
expect(changelog).toContain(`\`${name}\``);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("the package export map names exactly the existing entries", () => {
|
|
97
|
-
const template = resolve(import.meta.dir, "../packaging/package.json.tmpl");
|
|
98
|
-
const packageFile = existsSync(template)
|
|
99
|
-
? template
|
|
100
|
-
: resolve(import.meta.dir, "../package.json");
|
|
101
|
-
const manifest = JSON.parse(readFileSync(packageFile, "utf8")) as {
|
|
102
|
-
exports: Record<string, string>;
|
|
103
|
-
};
|
|
104
|
-
expect(manifest.exports).toEqual({
|
|
105
|
-
".": "./src/index.ts",
|
|
106
|
-
"./testing": "./src/testing.ts",
|
|
107
|
-
});
|
|
108
|
-
for (const target of Object.values(manifest.exports))
|
|
109
|
-
expect(
|
|
110
|
-
Bun.file(resolve(import.meta.dir, "..", target)).size,
|
|
111
|
-
).toBeGreaterThan(0);
|
|
112
|
-
});
|