@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,355 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { parseAgentFile } from "./agent-file.ts";
3
+ import { listHostAgents, type HostAgent } from "./host-agents.ts";
4
+ import type { LibraryItem } from "@guild/protocol";
5
+ import { parseSandbox, type Sandbox } from "./harness.ts";
6
+ import {
7
+ hostContext,
8
+ type SubAgentRef,
9
+ type ToolContext,
10
+ type ToolOutcome,
11
+ } from "./tools.ts";
12
+
13
+ const OUTPUT_CAP = 16_000;
14
+ const DEFAULT_WORKER: SubAgentRef = {
15
+ name: "worker",
16
+ slug: "worker",
17
+ description: "Implementation executor. Smallest correct change, then verify.",
18
+ instructions:
19
+ "Role: implementation executor. Make the smallest correct change. Verify before claiming done. Stay inside the assignment.",
20
+ readOnly: false,
21
+ };
22
+
23
+ export function refFromLibrary(item: LibraryItem): SubAgentRef {
24
+ const parsed = parseAgentFile(item.body, item.slug || item.name);
25
+ return {
26
+ name: item.name || parsed.name,
27
+ slug: item.slug || parsed.name,
28
+ description: item.description || parsed.description,
29
+ instructions: parsed.instructions || item.body,
30
+ readOnly: parsed.readOnly,
31
+ source: item.source === "catalog" ? "catalog" : "user",
32
+ };
33
+ }
34
+
35
+ export function refFromHost(item: HostAgent): SubAgentRef {
36
+ return {
37
+ name: item.name,
38
+ slug: item.slug,
39
+ description: item.description,
40
+ instructions: item.instructions,
41
+ readOnly: item.readOnly,
42
+ path: item.path,
43
+ source: "host",
44
+ };
45
+ }
46
+
47
+ export function mergeSpawnRefs(
48
+ guild: SubAgentRef[],
49
+ host: SubAgentRef[],
50
+ ): SubAgentRef[] {
51
+ const user = guild.filter((item) => item.source === "user");
52
+ const catalog = guild.filter((item) => item.source !== "user");
53
+ const seen = new Set<string>();
54
+ const out: SubAgentRef[] = [];
55
+ for (const item of [...user, ...host, ...catalog]) {
56
+ const key = item.slug.toLowerCase();
57
+ if (seen.has(key)) continue;
58
+ seen.add(key);
59
+ out.push(item);
60
+ }
61
+ return out;
62
+ }
63
+
64
+ export function listSpawnRefs(guildItems: LibraryItem[]): SubAgentRef[] {
65
+ return mergeSpawnRefs(
66
+ guildItems.map(refFromLibrary),
67
+ listHostAgents().map(refFromHost),
68
+ );
69
+ }
70
+
71
+ function agentKey(value: string): string {
72
+ return value.trim().replace(/^\/+/, "").toLowerCase();
73
+ }
74
+
75
+ /** Parent read_only cannot escalate via spawn. Explorer from full_access keeps run. */
76
+ export function childSpawnPolicy(
77
+ parentSandbox: ToolContext["sandbox"],
78
+ agentReadOnly: boolean,
79
+ ): { sandbox: Sandbox; allowWrite: boolean } {
80
+ const parent = parseSandbox(parentSandbox);
81
+ if (parent === "read_only") {
82
+ return { sandbox: "read_only", allowWrite: false };
83
+ }
84
+ return { sandbox: parent, allowWrite: !agentReadOnly };
85
+ }
86
+
87
+ export function resolveSubagent(
88
+ name: string,
89
+ agents: SubAgentRef[],
90
+ ): SubAgentRef {
91
+ const want = agentKey(name || "worker");
92
+ const hit = agents.find(
93
+ (item) => agentKey(item.name) === want || agentKey(item.slug) === want,
94
+ );
95
+ if (hit) return hit;
96
+ if (!name.trim() || want === "worker" || want === "default") return DEFAULT_WORKER;
97
+ return {
98
+ ...DEFAULT_WORKER,
99
+ name: name.trim() || DEFAULT_WORKER.name,
100
+ instructions: `${DEFAULT_WORKER.instructions}\n\nThe caller asked for agent "${name.trim()}". No matching library entry; work as a general worker.`,
101
+ };
102
+ }
103
+
104
+ const CHILD_TOOLS = `You ARE already running on the user's local computer (Guild).
105
+ Tools: run, read, write, list, skill, image_gen. You cannot spawn subagents.
106
+ Never say you cannot access this machine. Check [exit code: N] on every run.
107
+ Independent searches: emit multiple tool calls in one round; they run in parallel.`;
108
+
109
+ const CHILD_TOOLS_RO = `You ARE already running on the user's local computer (Guild).
110
+ Tools: run, read, list, skill. You cannot write files and cannot spawn subagents.
111
+ Read-only. Never edit, patch, or create files. Check [exit code: N] on every run.
112
+ Independent searches: emit multiple tool calls in one round; they run in parallel.`;
113
+
114
+ export const SPAWN_MAX_PARALLEL = 8;
115
+ export const SPAWN_CONCURRENCY = 4;
116
+
117
+ export type SpawnJob = {
118
+ prompt: string;
119
+ name: string;
120
+ description: string;
121
+ };
122
+
123
+ /** Devin luna-explore / Pi scout → Guild explorer. */
124
+ export function spawnProfile(raw: string): string {
125
+ const key = raw.trim().toLowerCase();
126
+ if (!key) return "";
127
+ if (key === "luna-explore" || key === "explore" || key === "scout") {
128
+ return "explorer";
129
+ }
130
+ if (key === "luna-general" || key === "general") return "worker";
131
+ if (key === "luna-reviewer") return "reviewer";
132
+ return raw.trim();
133
+ }
134
+
135
+ function recordOf(value: unknown): Record<string, unknown> | null {
136
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
137
+ return value as Record<string, unknown>;
138
+ }
139
+
140
+ function flagTrue(value: unknown): boolean {
141
+ return value === true || value === "true";
142
+ }
143
+
144
+ function flagFalse(value: unknown): boolean {
145
+ return value === false || value === "false";
146
+ }
147
+
148
+ function oneJob(raw: Record<string, unknown>): SpawnJob {
149
+ const prompt = String(raw.prompt || raw.task || "").trim();
150
+ const name = spawnProfile(
151
+ String(raw.profile || raw.name || raw.agent || raw.subagent_type || ""),
152
+ );
153
+ const description = String(raw.title || raw.description || "").trim();
154
+ return { prompt, name, description };
155
+ }
156
+
157
+ function handlesOf(ctx: ToolContext) {
158
+ if (!ctx.spawnHandles) ctx.spawnHandles = new Map();
159
+ return ctx.spawnHandles;
160
+ }
161
+
162
+ function startBackground(job: SpawnJob, ctx: ToolContext) {
163
+ const id = randomUUID();
164
+ const title = job.description || job.name || "worker";
165
+ const profile = job.name || "worker";
166
+ const handle: {
167
+ id: string;
168
+ title: string;
169
+ profile: string;
170
+ done: Promise<ToolOutcome>;
171
+ outcome?: ToolOutcome;
172
+ } = {
173
+ id,
174
+ title,
175
+ profile,
176
+ done: Promise.resolve({ text: "", isError: false }),
177
+ };
178
+ handle.done = spawnSubagent({ ...job, ctx }).then((outcome) => {
179
+ handle.outcome = outcome;
180
+ return outcome;
181
+ });
182
+ handlesOf(ctx).set(id, handle);
183
+ return handle;
184
+ }
185
+
186
+ function ackBackground(
187
+ rows: { id: string; title: string; profile: string }[],
188
+ ): string {
189
+ return rows
190
+ .map(
191
+ (row) =>
192
+ `agent_id: ${row.id}\ntitle: ${row.title}\nprofile: ${row.profile}\nstatus: running\nCall read_spawn with this agent_id (block=true) before the final reply.`,
193
+ )
194
+ .join("\n\n");
195
+ }
196
+
197
+ /** Pi subagent: single {prompt|task, name|agent} or parallel tasks[]. */
198
+ export function spawnJobs(args: Record<string, unknown>): SpawnJob[] {
199
+ if (Array.isArray(args.tasks) && args.tasks.length) {
200
+ return args.tasks.map((item) => oneJob(recordOf(item) || {}));
201
+ }
202
+ return [oneJob(args)];
203
+ }
204
+
205
+ async function mapWithConcurrency<T, R>(
206
+ items: T[],
207
+ concurrency: number,
208
+ fn: (item: T, index: number) => Promise<R>,
209
+ ): Promise<R[]> {
210
+ if (!items.length) return [];
211
+ const limit = Math.max(1, Math.min(concurrency, items.length));
212
+ const out: R[] = new Array(items.length);
213
+ let next = 0;
214
+ await Promise.all(
215
+ Array.from({ length: limit }, async () => {
216
+ while (true) {
217
+ const i = next++;
218
+ if (i >= items.length) return;
219
+ out[i] = await fn(items[i], i);
220
+ }
221
+ }),
222
+ );
223
+ return out;
224
+ }
225
+
226
+ export async function runSpawnJobs(
227
+ args: Record<string, unknown>,
228
+ ctx: ToolContext,
229
+ ): Promise<ToolOutcome> {
230
+ const jobs = spawnJobs(args);
231
+ if (!jobs.length || jobs.some((job) => !job.prompt)) {
232
+ return { text: "spawn needs a prompt or task", isError: true };
233
+ }
234
+ if (jobs.length > SPAWN_MAX_PARALLEL) {
235
+ return {
236
+ text: `Too many parallel tasks (${jobs.length}). Max is ${SPAWN_MAX_PARALLEL}.`,
237
+ isError: true,
238
+ };
239
+ }
240
+ const background = flagTrue(args.background) || flagTrue(args.is_background);
241
+ if (background) {
242
+ const started = jobs.map((job) => startBackground(job, ctx));
243
+ return { text: ackBackground(started), isError: false };
244
+ }
245
+ if (jobs.length === 1) {
246
+ return spawnSubagent({ ...jobs[0], ctx });
247
+ }
248
+ const results = await mapWithConcurrency(jobs, SPAWN_CONCURRENCY, (job) =>
249
+ spawnSubagent({ ...job, ctx }),
250
+ );
251
+ const failed = results.filter((row) => row.isError).length;
252
+ const body = results
253
+ .map((row, i) => {
254
+ const label = jobs[i].description || jobs[i].name || "worker";
255
+ const status = row.isError ? "failed" : "completed";
256
+ return `### [${label}] ${status}\n\n${row.text}`;
257
+ })
258
+ .join("\n\n---\n\n");
259
+ return {
260
+ text: `Parallel: ${results.length - failed}/${results.length} succeeded\n\n${body}`,
261
+ isError: failed === results.length,
262
+ };
263
+ }
264
+
265
+ export async function readSpawn(
266
+ args: Record<string, unknown>,
267
+ ctx: ToolContext,
268
+ ): Promise<ToolOutcome> {
269
+ const id = String(args.agent_id || args.id || "").trim();
270
+ if (!id) return { text: "read_spawn needs agent_id", isError: true };
271
+ const handle = handlesOf(ctx).get(id);
272
+ if (!handle) {
273
+ return {
274
+ text: `unknown agent_id ${id}. It must come from a background spawn in this turn.`,
275
+ isError: true,
276
+ };
277
+ }
278
+ if (flagFalse(args.block) && !handle.outcome) {
279
+ return {
280
+ text: `agent_id: ${handle.id}\ntitle: ${handle.title}\nprofile: ${handle.profile}\nstatus: running`,
281
+ isError: false,
282
+ };
283
+ }
284
+ const outcome = await handle.done;
285
+ return {
286
+ text: `# ${handle.title}\nagent_id: ${handle.id}\nprofile: ${handle.profile}\nstatus: ${
287
+ outcome.isError ? "failed" : "completed"
288
+ }\n\n${outcome.text}`,
289
+ isError: outcome.isError,
290
+ };
291
+ }
292
+
293
+ export async function spawnSubagent(input: {
294
+ prompt: string;
295
+ name?: string;
296
+ description?: string;
297
+ ctx: ToolContext;
298
+ }): Promise<ToolOutcome> {
299
+ if ((input.ctx.spawnDepth ?? 0) >= 1) {
300
+ return {
301
+ text: "subagents cannot spawn subagents (depth 1, same as Grok)",
302
+ isError: true,
303
+ };
304
+ }
305
+ const prompt = input.prompt.trim();
306
+ if (!prompt) return { text: "spawn needs a prompt", isError: true };
307
+ const dataDir = input.ctx.dataDir;
308
+ if (!dataDir) return { text: "spawn needs a dataDir", isError: true };
309
+ const agents = input.ctx.subagents?.length
310
+ ? input.ctx.subagents
311
+ : listSpawnRefs([]);
312
+ const agent = resolveSubagent(input.name || "worker", agents);
313
+ const child = childSpawnPolicy(input.ctx.sandbox, agent.readOnly);
314
+ const { llmComplete } = await import("./llm.ts");
315
+ const label = (input.description || agent.name).trim();
316
+ const system = [
317
+ agent.instructions,
318
+ hostContext(),
319
+ child.allowWrite ? CHILD_TOOLS : CHILD_TOOLS_RO,
320
+ ]
321
+ .filter(Boolean)
322
+ .join("\n\n");
323
+ const result = await llmComplete({
324
+ dataDir,
325
+ env: input.ctx.env,
326
+ system,
327
+ messages: [{ role: "user", content: prompt }],
328
+ temperature: 0.3,
329
+ role: "spawn",
330
+ tools: true,
331
+ skills: input.ctx.skills,
332
+ toolCtx: {
333
+ skills: input.ctx.skills,
334
+ subagents: agents,
335
+ dataDir,
336
+ env: input.ctx.env,
337
+ spawnDepth: 1,
338
+ allowWrite: child.allowWrite,
339
+ sandbox: child.sandbox,
340
+ workspace: input.ctx.workspace,
341
+ dispatch: input.ctx.dispatch,
342
+ signal: input.ctx.signal,
343
+ },
344
+ });
345
+ if (!result) {
346
+ return { text: "subagent had no model available", isError: true };
347
+ }
348
+ const body = result.text.trim() || "(empty)";
349
+ const clipped =
350
+ body.length > OUTPUT_CAP ? `${body.slice(0, OUTPUT_CAP)}\n… truncated …` : body;
351
+ return {
352
+ text: `# ${label}\nagent: ${agent.name}${agent.readOnly ? " · read-only" : ""}\nmodel: ${result.model}\n\n${clipped}`,
353
+ isError: false,
354
+ };
355
+ }