@townco/agent 0.1.23 → 0.1.24

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.
@@ -384,8 +384,8 @@ export function makeHttpTransport(agent) {
384
384
  const writer = inbound.writable.getWriter();
385
385
  await writer.write(encoder.encode(`${JSON.stringify(body)}\n`));
386
386
  writer.releaseLock();
387
- // Wait for response with 30 second timeout
388
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Request timeout")), 30000));
387
+ // Wait for response with ten minute timeout
388
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Request timeout")), 10 * 60 * 1000));
389
389
  try {
390
390
  const response = await Promise.race([responsePromise, timeoutPromise]);
391
391
  logger.debug("POST /rpc - Response received", { id });
@@ -157,30 +157,32 @@ export class LangchainAgent {
157
157
  if (toolCall.id == null) {
158
158
  throw new Error(`Tool call is missing id: ${JSON.stringify(toolCall)}`);
159
159
  }
160
+ // TODO: re-add this suppression of the todo_write tool call when we
161
+ // are rendering the agent-plan update in the UIs
160
162
  // If this is a todo_write tool call, yield an agent-plan update
161
- if (toolCall.name === "todo_write" && toolCall.args?.todos) {
162
- const entries = toolCall.args.todos.flatMap((todo) => {
163
- const validation = todoItemSchema.safeParse(todo);
164
- if (!validation.success) {
165
- // Invalid todo - filter it out
166
- return [];
167
- }
168
- return [
169
- {
170
- content: validation.data.content,
171
- status: validation.data.status,
172
- priority: "medium",
173
- },
174
- ];
175
- });
176
- yield {
177
- sessionUpdate: "plan",
178
- entries: entries,
179
- };
180
- // Track this tool call ID to suppress tool_call notifications
181
- todoWriteToolCallIds.add(toolCall.id);
182
- continue;
183
- }
163
+ //if (toolCall.name === "todo_write" && toolCall.args?.todos) {
164
+ // const entries = toolCall.args.todos.flatMap((todo: unknown) => {
165
+ // const validation = todoItemSchema.safeParse(todo);
166
+ // if (!validation.success) {
167
+ // // Invalid todo - filter it out
168
+ // return [];
169
+ // }
170
+ // return [
171
+ // {
172
+ // content: validation.data.content,
173
+ // status: validation.data.status,
174
+ // priority: "medium" as const,
175
+ // },
176
+ // ];
177
+ // });
178
+ // yield {
179
+ // sessionUpdate: "plan",
180
+ // entries: entries,
181
+ // };
182
+ // // Track this tool call ID to suppress tool_call notifications
183
+ // todoWriteToolCallIds.add(toolCall.id);
184
+ // continue;
185
+ //}
184
186
  yield {
185
187
  sessionUpdate: "tool_call",
186
188
  toolCallId: toolCall.id,
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Initialize Claude Code workspace integration by creating AGENTS.town.md
3
+ * and adding references to it in CLAUDE.md and AGENTS.md.
4
+ *
5
+ * @param targetPath - The root directory of the Town project
6
+ * @throws Error if target path doesn't exist
7
+ */
8
+ export declare function initForClaudeCode(targetPath: string): Promise<void>;
@@ -0,0 +1,58 @@
1
+ import { existsSync } from "node:fs";
2
+ import { appendFile, readFile, writeFile } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+ /**
8
+ * Initialize Claude Code workspace integration by creating AGENTS.town.md
9
+ * and adding references to it in CLAUDE.md and AGENTS.md.
10
+ *
11
+ * @param targetPath - The root directory of the Town project
12
+ * @throws Error if target path doesn't exist
13
+ */
14
+ export async function initForClaudeCode(targetPath) {
15
+ // Verify target path exists
16
+ if (!existsSync(targetPath)) {
17
+ throw new Error(`Target path does not exist: ${targetPath}`);
18
+ }
19
+ // Source template directory (dot-claude in the scaffold package)
20
+ const templateDir = join(__dirname, "templates", "dot-claude");
21
+ const appendTemplatePath = join(templateDir, "CLAUDE-append.md");
22
+ if (!existsSync(appendTemplatePath)) {
23
+ throw new Error(`Claude template not found: ${appendTemplatePath}. This is a bug in the Town CLI.`);
24
+ }
25
+ // Handle AGENTS.town.md and references
26
+ const claudeMdPath = join(targetPath, "CLAUDE.md");
27
+ const agentsMdPath = join(targetPath, "AGENTS.md");
28
+ const agentsTownMdPath = join(targetPath, "AGENTS.town.md");
29
+ const townContent = await readFile(appendTemplatePath, "utf-8");
30
+ // Create AGENTS.town.md with the Town-specific content
31
+ await writeFile(agentsTownMdPath, townContent, "utf-8");
32
+ console.log("✓ Created AGENTS.town.md");
33
+ // Add reference to AGENTS.town.md in both CLAUDE.md and AGENTS.md
34
+ const referenceText = "\n\nThis repo uses the Town agent SDK -- please reference @AGENTS.town.md for more information\n";
35
+ // Handle CLAUDE.md
36
+ if (existsSync(claudeMdPath)) {
37
+ // CLAUDE.md exists, append reference
38
+ await appendFile(claudeMdPath, referenceText);
39
+ console.log("✓ Added Town SDK reference to CLAUDE.md");
40
+ }
41
+ else {
42
+ // CLAUDE.md doesn't exist, create it with just the reference
43
+ await writeFile(claudeMdPath, `${referenceText.trim()}\n`, "utf-8");
44
+ console.log("✓ Created CLAUDE.md with Town SDK reference");
45
+ }
46
+ // Handle AGENTS.md
47
+ if (existsSync(agentsMdPath)) {
48
+ // AGENTS.md exists, append reference
49
+ await appendFile(agentsMdPath, referenceText);
50
+ console.log("✓ Added Town SDK reference to AGENTS.md");
51
+ }
52
+ else {
53
+ // AGENTS.md doesn't exist, create it with just the reference
54
+ await writeFile(agentsMdPath, `${referenceText.trim()}\n`, "utf-8");
55
+ console.log("✓ Created AGENTS.md with Town SDK reference");
56
+ }
57
+ console.log("✓ Claude Code workspace initialized");
58
+ }
@@ -16,4 +16,5 @@ export interface ScaffoldResult {
16
16
  * Scaffold a new agent package
17
17
  */
18
18
  export declare function scaffoldAgent(options: ScaffoldOptions): Promise<ScaffoldResult>;
19
+ export { initForClaudeCode } from "./claude-scaffold";
19
20
  export { scaffoldProject } from "./project-scaffold";
@@ -94,5 +94,6 @@ function runBunInstall(agentPath) {
94
94
  });
95
95
  });
96
96
  }
97
+ export { initForClaudeCode } from "./claude-scaffold";
97
98
  // Export project scaffolding
98
99
  export { scaffoldProject } from "./project-scaffold";
@@ -1,6 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { mkdir, writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
+ import { initForClaudeCode } from "./claude-scaffold.js";
4
5
  /**
5
6
  * Generate project package.json
6
7
  */
@@ -299,6 +300,8 @@ export async function scaffoldProject(options) {
299
300
  }
300
301
  // Run bun install
301
302
  await runBunInstall(projectPath);
303
+ // Initialize Claude Code workspace integration
304
+ await initForClaudeCode(projectPath);
302
305
  return {
303
306
  success: true,
304
307
  path: projectPath,
@@ -0,0 +1,71 @@
1
+ # Town Agent SDK
2
+ This project has code for agents developed using the Town agent SDK. The
3
+ structure of this repository is:
4
+ - `agents/`: code for main agent loop and configuration
5
+ - `tools/`: code for function-based tools that are shared by agents
6
+
7
+ ## Writing custom tools
8
+ You may add one of the built-in tools to the agent SDK (e.g. `web_search`), or
9
+ you may implement your own. To do this:
10
+ 1. add a file `tools/<tool-name>.ts` with the following structure:
11
+ ```
12
+ import * as z from 'zod';
13
+
14
+ export const schema = z.object({
15
+ a: z.number().describe("First number"),
16
+ b: z.number().describe("Second number"),
17
+ });
18
+
19
+ export const name = "add_two_numbers";
20
+ export const description = "Add two numbers together and return the result";
21
+
22
+ export default function addTwoNumbers(input: unknown) {
23
+ const { a, b } = input as { a: number; b: number };
24
+ return { result: a + b };
25
+ }
26
+ ```
27
+ where
28
+ - `schema` is a zod schema for the tool input
29
+ - `name` is the name of the tool (given to the LLM)
30
+ - `description` describes the tool (given to the LLM)
31
+ - default-exported function containing the body of the function
32
+
33
+ 2. Modify the agent code to import the `createTool` utility and the new tool
34
+ ```
35
+ import { createTool } from '@townco/agent/utils';
36
+ import * as newCoolTool from '../../tools/new-cool-tool';
37
+ ```
38
+ and modify the agent's tools list
39
+ ```
40
+ const agent: AgentDefinition = {
41
+ model: "claude-sonnet-4-5-20250929",
42
+ systemPrompt: "You are a helpful assistant.\n",
43
+ tools: ["todo_write", "web_search", createTool(newCoolTool)],
44
+ };
45
+ ```
46
+
47
+ ## Configuring MCPs
48
+ You may configure stdio and streamable HTTP-based MCPs. Here is an example:
49
+ ```
50
+ const agent: AgentDefinition = {
51
+ model: "claude-sonnet-4-5-20250929",
52
+ systemPrompt: "You are a helpful assistant.\n",
53
+ tools: ["todo_write", "web_search"],
54
+ mcps: [
55
+ {
56
+ transport: "http",
57
+ name: "foobar",
58
+ url: "http://town.com:8080/mcp",
59
+ headers: {
60
+ 'Authorization': `Bearer ${process.env.MCP_API_KEY}`
61
+ },
62
+ },
63
+ {
64
+ transport: 'stdio',
65
+ name: 'local-mcp',
66
+ command: 'node',
67
+ args: ['path/to/local/mcp.js'],
68
+ },
69
+ ],
70
+ };
71
+ ```