@kevin5251984/guild 0.2.12

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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/bin/guildd.mjs +20 -0
  3. package/cordis.yml +24 -0
  4. package/package.json +52 -0
  5. package/src/agent-file.ts +125 -0
  6. package/src/browser.ts +668 -0
  7. package/src/catalog/default-bots.ts +263 -0
  8. package/src/catalog/skills.ts +128 -0
  9. package/src/catalog/subagents.ts +70 -0
  10. package/src/chat-parts.ts +71 -0
  11. package/src/cli-args.ts +75 -0
  12. package/src/cli.ts +60 -0
  13. package/src/compact.ts +355 -0
  14. package/src/cordis.d.ts +40 -0
  15. package/src/db.ts +653 -0
  16. package/src/generate.ts +673 -0
  17. package/src/handlers.ts +1623 -0
  18. package/src/harness.ts +326 -0
  19. package/src/host-agents.ts +137 -0
  20. package/src/host-browse.ts +199 -0
  21. package/src/host-skills.ts +150 -0
  22. package/src/image-gen.ts +270 -0
  23. package/src/index.ts +12 -0
  24. package/src/llm.ts +993 -0
  25. package/src/mcp.ts +563 -0
  26. package/src/memory.ts +159 -0
  27. package/src/mention.ts +176 -0
  28. package/src/oauth.ts +1474 -0
  29. package/src/plugins/api.ts +8 -0
  30. package/src/plugins/chat.ts +31 -0
  31. package/src/plugins/harness.ts +77 -0
  32. package/src/plugins/llm.ts +50 -0
  33. package/src/plugins/mcp.ts +58 -0
  34. package/src/plugins/memory.ts +42 -0
  35. package/src/plugins/oauth.ts +47 -0
  36. package/src/plugins/server.ts +126 -0
  37. package/src/plugins/store.ts +29 -0
  38. package/src/plugins/tools.ts +79 -0
  39. package/src/public/buddy.js +432 -0
  40. package/src/public/chat.css +3045 -0
  41. package/src/public/chat.html +5834 -0
  42. package/src/public/favicon-16.png +0 -0
  43. package/src/public/favicon-16.svg +10 -0
  44. package/src/public/favicon-32.png +0 -0
  45. package/src/public/favicon.ico +0 -0
  46. package/src/public/favicon.svg +13 -0
  47. package/src/public/i18n.js +663 -0
  48. package/src/public/index.html +143 -0
  49. package/src/public/library.html +678 -0
  50. package/src/public/mcp-add.html +126 -0
  51. package/src/public/md.js +332 -0
  52. package/src/public/rpg/inn-street.jpg +0 -0
  53. package/src/public/settings.html +795 -0
  54. package/src/public/skills-add.html +212 -0
  55. package/src/public/studio.html +1181 -0
  56. package/src/public/style.css +1678 -0
  57. package/src/public/subagents-add.html +152 -0
  58. package/src/router.ts +978 -0
  59. package/src/send-budget.ts +52 -0
  60. package/src/server.ts +1 -0
  61. package/src/skill-import.ts +250 -0
  62. package/src/slash.ts +15 -0
  63. package/src/start.ts +103 -0
  64. package/src/store.ts +1208 -0
  65. package/src/subagent.ts +355 -0
  66. package/src/tools.ts +818 -0
  67. package/src/trajectory.ts +339 -0
  68. package/src/usage.ts +111 -0
  69. package/vendor/protocol/package.json +19 -0
  70. package/vendor/protocol/src/index.ts +159 -0
@@ -0,0 +1,8 @@
1
+ import type { Context } from "cordis";
2
+
3
+ function apply(ctx: Context) {
4
+ void ctx.server.listen();
5
+ }
6
+ Object.assign(apply, { inject: ["server", "store", "chat", "llm", "harness"] });
7
+
8
+ export default apply;
@@ -0,0 +1,31 @@
1
+ import { Service, type Context } from "cordis";
2
+ import {
3
+ chatReply,
4
+ generateMarkdown,
5
+ type ChatReply,
6
+ type GenerateKind,
7
+ type GeneratedMarkdown,
8
+ } from "../generate.ts";
9
+
10
+ export class ChatService extends Service {
11
+ static inject = ["store", "llm", "harness"];
12
+
13
+ constructor(ctx: Context) {
14
+ super(ctx, "chat");
15
+ }
16
+
17
+ reply(input: Parameters<typeof chatReply>[0]): Promise<ChatReply> {
18
+ return this.ctx.harness.turn(input);
19
+ }
20
+
21
+ generate(kind: GenerateKind, prompt: string): Promise<GeneratedMarkdown> {
22
+ return generateMarkdown(
23
+ kind,
24
+ prompt,
25
+ this.ctx.store.env,
26
+ this.ctx.store.dataDir,
27
+ );
28
+ }
29
+ }
30
+
31
+ export default ChatService;
@@ -0,0 +1,77 @@
1
+ import { Service, type Context } from "cordis";
2
+ import { chatReply, type ChatReply } from "../generate.ts";
3
+ import { guildEnvOf } from "../start.ts";
4
+ import {
5
+ policyFor,
6
+ runAgentLoop,
7
+ type HarnessPolicy,
8
+ type Sandbox,
9
+ } from "../harness.ts";
10
+ import type { ToolContext, ToolOutcome } from "../tools.ts";
11
+
12
+ export type HarnessConfig = {
13
+ sandbox?: Sandbox;
14
+ workspace?: string;
15
+ };
16
+
17
+ export class HarnessService extends Service {
18
+ static inject = ["store", "tools"];
19
+ private readonly config: HarnessConfig;
20
+
21
+ constructor(ctx: Context, config: HarnessConfig = {}) {
22
+ super(ctx, "harness");
23
+ this.config = config;
24
+ }
25
+
26
+ policy(position?: string): HarnessPolicy {
27
+ return policyFor(guildEnvOf(this.ctx), {
28
+ sandbox: this.config.sandbox,
29
+ workspace: this.config.workspace,
30
+ position,
31
+ });
32
+ }
33
+
34
+ sandbox(position?: string): Sandbox {
35
+ return this.policy(position).sandbox;
36
+ }
37
+
38
+ workspace(): string {
39
+ return this.policy().workspace;
40
+ }
41
+
42
+ dispatch(
43
+ name: string,
44
+ args: Record<string, unknown>,
45
+ toolCtx: ToolContext,
46
+ ): Promise<ToolOutcome> {
47
+ return this.ctx.tools.execute(name, args, toolCtx);
48
+ }
49
+
50
+ loop = runAgentLoop;
51
+
52
+ async turn(
53
+ input: Parameters<typeof chatReply>[0],
54
+ ): Promise<ChatReply> {
55
+ const policy = policyFor(guildEnvOf(this.ctx), {
56
+ sandbox: input.sandbox ?? this.config.sandbox,
57
+ workspace: input.workspace ?? this.config.workspace,
58
+ position: input.position,
59
+ });
60
+ const mcp = this.ctx.get("mcp");
61
+ const mcpTools =
62
+ input.mcpTools !== undefined
63
+ ? input.mcpTools
64
+ : mcp
65
+ ? await mcp.toolRefs()
66
+ : [];
67
+ return chatReply({
68
+ ...input,
69
+ sandbox: policy.sandbox,
70
+ workspace: policy.workspace,
71
+ mcpTools,
72
+ dispatch: (name, args, toolCtx) => this.dispatch(name, args, toolCtx),
73
+ });
74
+ }
75
+ }
76
+
77
+ export default HarnessService;
@@ -0,0 +1,50 @@
1
+ import { Service, type Context } from "cordis";
2
+ import type { AuxRole, ModelRef, ModelsFile } from "@guild/protocol";
3
+ import {
4
+ llmComplete,
5
+ mergeModelsFile,
6
+ publicModels,
7
+ readModelsFile,
8
+ resolveLlm,
9
+ } from "../llm.ts";
10
+
11
+ export class LlmService extends Service {
12
+ static inject = ["store"];
13
+
14
+ constructor(ctx: Context) {
15
+ super(ctx, "llm");
16
+ }
17
+
18
+ readModels(): ModelsFile {
19
+ return readModelsFile(this.ctx.store.dataDir);
20
+ }
21
+
22
+ mergeModels(patch: Partial<ModelsFile>): ModelsFile {
23
+ return mergeModelsFile(this.ctx.store.dataDir, patch);
24
+ }
25
+
26
+ publicModels(env: NodeJS.ProcessEnv = this.ctx.store.env) {
27
+ return publicModels(this.ctx.store.dataDir, env);
28
+ }
29
+
30
+ resolve(
31
+ env: NodeJS.ProcessEnv = this.ctx.store.env,
32
+ role?: AuxRole | "chat",
33
+ prefer?: ModelRef | null,
34
+ ) {
35
+ return resolveLlm(this.ctx.store.dataDir, env, role, prefer);
36
+ }
37
+
38
+ complete(
39
+ input: Omit<Parameters<typeof llmComplete>[0], "dataDir"> & {
40
+ dataDir?: string;
41
+ },
42
+ ) {
43
+ return llmComplete({
44
+ ...input,
45
+ dataDir: input.dataDir ?? this.ctx.store.dataDir,
46
+ });
47
+ }
48
+ }
49
+
50
+ export default LlmService;
@@ -0,0 +1,58 @@
1
+ import { Service, type Context } from "cordis";
2
+ import {
3
+ callMcpTool,
4
+ importHostMcp,
5
+ listGuildMcp,
6
+ listHostMcp,
7
+ listMcpToolRefs,
8
+ removeGuildMcp,
9
+ upsertGuildMcp,
10
+ type McpLaunch,
11
+ } from "../mcp.ts";
12
+
13
+ export class McpService extends Service {
14
+ static inject = ["store", "tools"];
15
+
16
+ constructor(ctx: Context) {
17
+ super(ctx, "mcp");
18
+ ctx.tools.registerPrefix("mcp__", (name, args, toolCtx) => {
19
+ const dataDir = toolCtx.dataDir ?? this.dataDir;
20
+ if (!dataDir) return { text: "mcp needs a dataDir", isError: true };
21
+ return callMcpTool(dataDir, name, args, toolCtx.mcpTools ?? []);
22
+ });
23
+ }
24
+
25
+ get dataDir(): string {
26
+ return this.ctx.store.dataDir;
27
+ }
28
+
29
+ list() {
30
+ return listGuildMcp(this.dataDir);
31
+ }
32
+
33
+ listHost() {
34
+ return listHostMcp();
35
+ }
36
+
37
+ upsert(name: string, launch: McpLaunch) {
38
+ return upsertGuildMcp(this.dataDir, name, launch);
39
+ }
40
+
41
+ importHost(hostId: string) {
42
+ return importHostMcp(this.dataDir, hostId);
43
+ }
44
+
45
+ remove(name: string) {
46
+ return removeGuildMcp(this.dataDir, name);
47
+ }
48
+
49
+ toolRefs() {
50
+ return listMcpToolRefs(this.dataDir);
51
+ }
52
+
53
+ call(name: string, args: Record<string, unknown>) {
54
+ return callMcpTool(this.dataDir, name, args);
55
+ }
56
+ }
57
+
58
+ export default McpService;
@@ -0,0 +1,42 @@
1
+ import { Service, type Context } from "cordis";
2
+ import { harvestBotMemory, harvestChannelMemory } from "../memory.ts";
3
+
4
+ export class MemoryService extends Service {
5
+ static inject = ["store", "llm"];
6
+
7
+ constructor(ctx: Context) {
8
+ super(ctx, "memory");
9
+ ctx.on("guild/turn-complete", (turn) => {
10
+ const store = ctx.store.guild;
11
+ const env = ctx.store.env;
12
+ const prefer = store.getBot(turn.botId)?.model ?? null;
13
+ void harvestBotMemory({
14
+ store,
15
+ botId: turn.botId,
16
+ userMessage: turn.userText,
17
+ reply: turn.reply,
18
+ env,
19
+ prefer,
20
+ }).catch(() => {});
21
+ const room = store.getRoom(turn.roomId);
22
+ if (room?.kind === "channel") {
23
+ void harvestChannelMemory({
24
+ store,
25
+ roomId: turn.roomId,
26
+ userMessage: turn.userText,
27
+ replies: [
28
+ {
29
+ handle: store.getBot(turn.botId)?.handle,
30
+ author: turn.botId,
31
+ body: turn.reply,
32
+ },
33
+ ],
34
+ env,
35
+ prefer,
36
+ }).catch(() => {});
37
+ }
38
+ });
39
+ }
40
+ }
41
+
42
+ export default MemoryService;
@@ -0,0 +1,47 @@
1
+ import { Service, type Context } from "cordis";
2
+ import {
3
+ completeLogin,
4
+ listSubscriptions,
5
+ logoutOAuth,
6
+ pollLogin,
7
+ startLogin,
8
+ storedAccessToken,
9
+ } from "../oauth.ts";
10
+
11
+ export class OAuthService extends Service {
12
+ static inject = ["store"];
13
+
14
+ constructor(ctx: Context) {
15
+ super(ctx, "oauth");
16
+ }
17
+
18
+ get dataDir(): string {
19
+ return this.ctx.store.dataDir;
20
+ }
21
+
22
+ list() {
23
+ return listSubscriptions(this.dataDir);
24
+ }
25
+
26
+ start(id: string) {
27
+ return startLogin(this.dataDir, id);
28
+ }
29
+
30
+ poll(id: string) {
31
+ return pollLogin(this.dataDir, id);
32
+ }
33
+
34
+ complete(id: string, input: { code?: string; url?: string }) {
35
+ return completeLogin(this.dataDir, id, input);
36
+ }
37
+
38
+ logout(id: string) {
39
+ return logoutOAuth(this.dataDir, id);
40
+ }
41
+
42
+ token(id: string) {
43
+ return storedAccessToken(this.dataDir, id);
44
+ }
45
+ }
46
+
47
+ export default OAuthService;
@@ -0,0 +1,126 @@
1
+ import http from "node:http";
2
+ import { Service, type Context } from "cordis";
3
+ import {
4
+ DEFAULT_GUILD_HOST,
5
+ DEFAULT_GUILD_PORT,
6
+ } from "@guild/protocol";
7
+ import { handleRequest } from "../router.ts";
8
+ import { guildEnvOf } from "../start.ts";
9
+
10
+ export type ServerConfig = {
11
+ host?: string;
12
+ port?: number;
13
+ };
14
+
15
+ export type ListeningInfo = {
16
+ host: string;
17
+ port: number;
18
+ dataDir: string;
19
+ };
20
+
21
+ export class ServerService extends Service {
22
+ static inject = ["store", "chat"];
23
+ readonly node: http.Server;
24
+ host = "";
25
+ port = 0;
26
+ private readonly config: ServerConfig;
27
+ private readonly listening: Promise<ListeningInfo>;
28
+ private resolveListening!: (info: ListeningInfo) => void;
29
+ private rejectListening!: (error: Error) => void;
30
+ private started = false;
31
+
32
+ constructor(ctx: Context, config: ServerConfig = {}) {
33
+ super(ctx, "server");
34
+ this.config = config;
35
+ this.listening = new Promise<ListeningInfo>((resolve, reject) => {
36
+ this.resolveListening = resolve;
37
+ this.rejectListening = reject;
38
+ });
39
+ void this.listening.catch(() => {});
40
+
41
+ const env = () => guildEnvOf(ctx);
42
+ this.node = http.createServer((req, res) => {
43
+ const store = ctx.store.guild;
44
+ void handleRequest(req, res, store, env(), {
45
+ mcp: Boolean(ctx.get("mcp")),
46
+ oauth: Boolean(ctx.get("oauth")),
47
+ harvest: false,
48
+ onTurnComplete: (turn) => {
49
+ ctx.emit("guild/turn-complete", turn);
50
+ },
51
+ turn: (input) => ctx.chat.reply(input),
52
+ });
53
+ });
54
+ ctx.effect(() => {
55
+ return () =>
56
+ new Promise<void>((resolve) => {
57
+ this.rejectListening(new Error("disposed"));
58
+ if (!this.node.listening) {
59
+ resolve();
60
+ return;
61
+ }
62
+ this.node.close(() => resolve());
63
+ });
64
+ });
65
+ }
66
+
67
+ whenListening(): Promise<ListeningInfo> {
68
+ return this.listening;
69
+ }
70
+
71
+ listen(): Promise<ListeningInfo> {
72
+ if (this.started) return this.listening;
73
+ this.started = true;
74
+ const env = guildEnvOf(this.ctx);
75
+ const host = env.GUILD_HOST ?? this.config.host ?? DEFAULT_GUILD_HOST;
76
+ const rawPort = env.GUILD_PORT ?? this.config.port ?? DEFAULT_GUILD_PORT;
77
+ const port = Number(rawPort);
78
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
79
+ const error = new Error(`invalid GUILD_PORT: ${env.GUILD_PORT}`);
80
+ this.rejectListening(error);
81
+ throw error;
82
+ }
83
+ this.node.once("error", (error) => {
84
+ this.rejectListening(error);
85
+ });
86
+ const dataDir = this.ctx.store.dataDir;
87
+ this.node.listen(port, host, () => {
88
+ const address = this.node.address();
89
+ if (address === null || typeof address === "string") {
90
+ this.rejectListening(new Error("guildd failed to bind a TCP port"));
91
+ return;
92
+ }
93
+ this.host = address.address;
94
+ this.port = address.port;
95
+ const info: ListeningInfo = {
96
+ host: this.host,
97
+ port: this.port,
98
+ dataDir,
99
+ };
100
+ this.ctx.emit("guild/listening", info);
101
+ this.resolveListening(info);
102
+ });
103
+ return this.listening;
104
+ }
105
+ }
106
+
107
+ export function listenGuildServer(
108
+ server: http.Server,
109
+ host: string,
110
+ port: number,
111
+ ): Promise<{ host: string; port: number }> {
112
+ return new Promise((resolve, reject) => {
113
+ server.once("error", reject);
114
+ server.listen(port, host, () => {
115
+ server.off("error", reject);
116
+ const address = server.address();
117
+ if (address === null || typeof address === "string") {
118
+ reject(new Error("guildd failed to bind a TCP port"));
119
+ return;
120
+ }
121
+ resolve({ host: address.address, port: address.port });
122
+ });
123
+ });
124
+ }
125
+
126
+ export default ServerService;
@@ -0,0 +1,29 @@
1
+ import { Service, type Context } from "cordis";
2
+ import { defaultDataDir, GuildStore } from "../store.ts";
3
+ import { guildEnvOf } from "../start.ts";
4
+
5
+ export type StoreConfig = {
6
+ dataDir?: string;
7
+ };
8
+
9
+ export class StoreService extends Service {
10
+ readonly guild: GuildStore;
11
+ readonly env: NodeJS.ProcessEnv;
12
+
13
+ constructor(ctx: Context, config: StoreConfig = {}) {
14
+ super(ctx, "store");
15
+ this.env = guildEnvOf(ctx);
16
+ const dataDir =
17
+ this.env.GUILD_HOME ?? config.dataDir ?? defaultDataDir(this.env);
18
+ this.guild = new GuildStore(dataDir);
19
+ ctx.effect(() => () => {
20
+ this.guild.close();
21
+ });
22
+ }
23
+
24
+ get dataDir(): string {
25
+ return this.guild.dataDir;
26
+ }
27
+ }
28
+
29
+ export default StoreService;
@@ -0,0 +1,79 @@
1
+ import { Service, type Context } from "cordis";
2
+ import { closeBrowser } from "../browser.ts";
3
+ import { gateTool } from "../harness.ts";
4
+ import {
5
+ builtinExecute,
6
+ BUILTIN_TOOL_NAMES,
7
+ type ToolContext,
8
+ type ToolOutcome,
9
+ } from "../tools.ts";
10
+
11
+ export type NamedToolHandler = (
12
+ args: Record<string, unknown>,
13
+ ctx: ToolContext,
14
+ ) => Promise<ToolOutcome> | ToolOutcome;
15
+
16
+ export type PrefixToolHandler = (
17
+ name: string,
18
+ args: Record<string, unknown>,
19
+ ctx: ToolContext,
20
+ ) => Promise<ToolOutcome> | ToolOutcome;
21
+
22
+ export class ToolsService extends Service {
23
+ static inject = ["store"];
24
+ private readonly named = new Map<string, NamedToolHandler>();
25
+ private readonly prefixes = new Map<string, PrefixToolHandler>();
26
+
27
+ constructor(ctx: Context) {
28
+ super(ctx, "tools");
29
+ for (const name of BUILTIN_TOOL_NAMES) {
30
+ this.register(name, (args, toolCtx) => builtinExecute(name, args, toolCtx));
31
+ }
32
+ ctx.effect(() => () => {
33
+ void closeBrowser();
34
+ });
35
+ }
36
+
37
+ has(name: string): boolean {
38
+ if (this.named.has(name)) return true;
39
+ for (const prefix of this.prefixes.keys()) {
40
+ if (name.startsWith(prefix)) return true;
41
+ }
42
+ return false;
43
+ }
44
+
45
+ register(name: string, handler: NamedToolHandler): () => void {
46
+ this.named.set(name, handler);
47
+ const undo = () => {
48
+ if (this.named.get(name) === handler) this.named.delete(name);
49
+ };
50
+ this.ctx.effect(() => undo);
51
+ return undo;
52
+ }
53
+
54
+ registerPrefix(prefix: string, handler: PrefixToolHandler): () => void {
55
+ this.prefixes.set(prefix, handler);
56
+ const undo = () => {
57
+ if (this.prefixes.get(prefix) === handler) this.prefixes.delete(prefix);
58
+ };
59
+ this.ctx.effect(() => undo);
60
+ return undo;
61
+ }
62
+
63
+ async execute(
64
+ name: string,
65
+ args: Record<string, unknown>,
66
+ toolCtx: ToolContext = {},
67
+ ): Promise<ToolOutcome> {
68
+ const refused = gateTool(name, args, toolCtx);
69
+ if (refused) return refused;
70
+ const named = this.named.get(name);
71
+ if (named) return named(args, toolCtx);
72
+ for (const [prefix, handler] of this.prefixes) {
73
+ if (name.startsWith(prefix)) return handler(name, args, toolCtx);
74
+ }
75
+ return { text: `unknown tool: ${name}`, isError: true };
76
+ }
77
+ }
78
+
79
+ export default ToolsService;