dylanebert commited on
Commit
12673bf
Β·
1 Parent(s): 7a07363

planning tool

Browse files
src/lib/server/context.md CHANGED
@@ -5,19 +5,20 @@ WebSocket server with LangGraph agent for AI-assisted game development.
5
  ## Key Components
6
 
7
  - **api.ts** - WebSocket message routing with abort handling
8
- - **langgraph-agent.ts** - LangGraph agent with buffered streaming and abort signals
9
- - **tools.ts** - Editor manipulation: full read/write, line-based reading, text/regex search, search-replace editing
 
10
  - **console-buffer.ts** - Console message storage
11
  - **documentation.ts** - VibeGame documentation loader
12
 
13
  ## Architecture
14
 
15
- LangGraph state machine with real-time streaming:
16
 
17
- - Buffers and filters tool patterns from text segments
18
- - Tool invocations handled separately from text content
19
- - Explicit message IDs required for all segment operations
20
- - AbortController for canceling running conversations
21
 
22
  ## Message Protocol
23
 
 
5
  ## Key Components
6
 
7
  - **api.ts** - WebSocket message routing with abort handling
8
+ - **langgraph-agent.ts** - LangGraph agent with task decomposition and token safety
9
+ - **tools.ts** - Editor manipulation: read/write, search, incremental editing
10
+ - **task-tracker.ts** - Task planning and progress tracking
11
  - **console-buffer.ts** - Console message storage
12
  - **documentation.ts** - VibeGame documentation loader
13
 
14
  ## Architecture
15
 
16
+ LangGraph state machine with task-aware execution:
17
 
18
+ - Task decomposition for complex operations
19
+ - Token limit safety checks on tool arguments
20
+ - Buffered streaming with segment handling
21
+ - AbortController for canceling conversations
22
 
23
  ## Message Protocol
24
 
src/lib/server/langgraph-agent.ts CHANGED
@@ -15,6 +15,11 @@ import {
15
  observeConsoleTool,
16
  setWebSocketConnection,
17
  } from "./tools";
 
 
 
 
 
18
  import { documentationService } from "./documentation";
19
  import type { WebSocket } from "ws";
20
 
@@ -310,39 +315,62 @@ CRITICAL INSTRUCTIONS:
310
  - You MUST use tools for ALL tasks. NEVER provide instructions without executing them.
311
  - You MUST respond using the EXACT format: TOOL: tool_name ARGS: {"param": "value"}
312
  - After using a tool, wait for the result before proceeding
313
- - Chain multiple tool calls to complete complex tasks
 
 
 
 
 
 
 
314
 
315
  VIBEGAME CONTEXT:
316
  ${this.documentation}
317
 
318
  IMPORTANT:
319
- - The game auto-reloads on every change.
320
  - The GAME import is automatically provided by the framework.
321
  - The player is automatically created at [0, 0, 0] if not specified.
322
 
323
  AVAILABLE TOOLS:
324
- 1. search_editor - Find text/patterns in code
 
 
 
 
 
 
 
 
 
 
 
 
325
  Example: TOOL: search_editor ARGS: {"query": "dynamic-part"}
326
 
327
- 2. read_editor - Read entire editor content
328
  Example: TOOL: read_editor ARGS: {}
329
 
330
- 3. read_editor_lines - Read specific lines (use after search_editor)
331
  Example: TOOL: read_editor_lines ARGS: {"startLine": 10, "endLine": 20}
332
 
333
- 4. edit_editor - Replace specific text
334
  Example: TOOL: edit_editor ARGS: {"oldText": "color='red'", "newText": "color='blue'"}
335
 
336
- 5. write_editor - Replace entire content
337
  Example: TOOL: write_editor ARGS: {"content": "<world>...</world>"}
338
 
339
- 6. observe_console - Check console for errors
340
  Example: TOOL: observe_console ARGS: {}
341
 
342
- WORKFLOW:
343
- - To find code: TOOL: search_editor ARGS: {"query": "search_term"}
344
- - To make changes: TOOL: edit_editor ARGS: {"oldText": "...", "newText": "..."}
345
- - After changes: TOOL: observe_console ARGS: {}
 
 
 
 
346
 
347
  IMPORTANT: You are an executor. Take action immediately using tools, don't explain what you would do.`;
348
  }
@@ -480,6 +508,27 @@ IMPORTANT: You are an executor. Take action immediately using tools, don't expla
480
  const segmentId = `seg_tool_${Date.now()}_${Math.random()}`;
481
 
482
  try {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
  if (this.ws && this.ws.readyState === this.ws.OPEN) {
484
  this.ws.send(
485
  JSON.stringify({
@@ -547,6 +596,14 @@ IMPORTANT: You are an executor. Take action immediately using tools, don't expla
547
  }
548
  } else if (call.name === "observe_console") {
549
  result = await observeConsoleTool.func("");
 
 
 
 
 
 
 
 
550
  } else {
551
  result = `Unknown tool: ${call.name}`;
552
  }
 
15
  observeConsoleTool,
16
  setWebSocketConnection,
17
  } from "./tools";
18
+ import {
19
+ planTasksTool,
20
+ updateTaskTool,
21
+ viewTasksTool,
22
+ } from "./task-tracker";
23
  import { documentationService } from "./documentation";
24
  import type { WebSocket } from "ws";
25
 
 
315
  - You MUST use tools for ALL tasks. NEVER provide instructions without executing them.
316
  - You MUST respond using the EXACT format: TOOL: tool_name ARGS: {"param": "value"}
317
  - After using a tool, wait for the result before proceeding
318
+ - For complex tasks, use plan_tasks FIRST to break down the work
319
+ - Keep tool arguments concise - prefer multiple small edits over one large edit
320
+
321
+ TASK DECOMPOSITION RULES:
322
+ - For any task requiring 3+ changes, use plan_tasks FIRST
323
+ - Break large code changes into smaller, focused edits
324
+ - Each edit_editor call should modify ONE logical section (max ~20 lines)
325
+ - Mark tasks as in_progress when starting, completed when done
326
 
327
  VIBEGAME CONTEXT:
328
  ${this.documentation}
329
 
330
  IMPORTANT:
331
+ - The game auto-reloads on every change.
332
  - The GAME import is automatically provided by the framework.
333
  - The player is automatically created at [0, 0, 0] if not specified.
334
 
335
  AVAILABLE TOOLS:
336
+
337
+ TASK MANAGEMENT:
338
+ 1. plan_tasks - Break complex work into steps (USE FIRST for multi-step tasks!)
339
+ Example: TOOL: plan_tasks ARGS: {"tasks": ["Find the player object", "Add jump ability", "Test the changes"]}
340
+
341
+ 2. update_task - Mark task progress
342
+ Example: TOOL: update_task ARGS: {"taskId": 1, "status": "in_progress"}
343
+
344
+ 3. view_tasks - See current task list
345
+ Example: TOOL: view_tasks ARGS: {}
346
+
347
+ EDITOR TOOLS:
348
+ 4. search_editor - Find text/patterns in code
349
  Example: TOOL: search_editor ARGS: {"query": "dynamic-part"}
350
 
351
+ 5. read_editor - Read entire editor content
352
  Example: TOOL: read_editor ARGS: {}
353
 
354
+ 6. read_editor_lines - Read specific lines (use after search_editor)
355
  Example: TOOL: read_editor_lines ARGS: {"startLine": 10, "endLine": 20}
356
 
357
+ 7. edit_editor - Replace specific text (KEEP EDITS SMALL - max ~20 lines per call)
358
  Example: TOOL: edit_editor ARGS: {"oldText": "color='red'", "newText": "color='blue'"}
359
 
360
+ 8. write_editor - Replace entire content (ONLY for new files or complete rewrites)
361
  Example: TOOL: write_editor ARGS: {"content": "<world>...</world>"}
362
 
363
+ 9. observe_console - Check console for errors
364
  Example: TOOL: observe_console ARGS: {}
365
 
366
+ WORKFLOW EXAMPLE:
367
+ User: "Add jumping to the player"
368
+ 1. TOOL: plan_tasks ARGS: {"tasks": ["Search for player code", "Add jump component", "Add jump controls", "Test jumping"]}
369
+ 2. TOOL: update_task ARGS: {"taskId": 1, "status": "in_progress"}
370
+ 3. TOOL: search_editor ARGS: {"query": "player"}
371
+ 4. TOOL: edit_editor ARGS: {"oldText": "...", "newText": "..."}
372
+ 5. TOOL: update_task ARGS: {"taskId": 1, "status": "completed"}
373
+ (continue with remaining tasks...)
374
 
375
  IMPORTANT: You are an executor. Take action immediately using tools, don't explain what you would do.`;
376
  }
 
508
  const segmentId = `seg_tool_${Date.now()}_${Math.random()}`;
509
 
510
  try {
511
+ const argString = JSON.stringify(call.args);
512
+ const estimatedTokens = argString.length / 4;
513
+
514
+ if (estimatedTokens > 1000 && (call.name === "edit_editor" || call.name === "write_editor")) {
515
+ console.warn(`Warning: Tool ${call.name} arguments are large (${estimatedTokens} estimated tokens)`);
516
+
517
+ if (call.name === "edit_editor" && call.args.oldText) {
518
+ const oldText = call.args.oldText as string;
519
+
520
+ if (oldText.split('\n').length > 20) {
521
+ results.push(
522
+ new ToolMessage({
523
+ content: `Error: The edit is too large (${oldText.split('\n').length} lines). Please break this into smaller edits of max 20 lines each. Use plan_tasks to organize multiple edits.`,
524
+ tool_call_id: segmentId,
525
+ name: call.name,
526
+ })
527
+ );
528
+ continue;
529
+ }
530
+ }
531
+ }
532
  if (this.ws && this.ws.readyState === this.ws.OPEN) {
533
  this.ws.send(
534
  JSON.stringify({
 
596
  }
597
  } else if (call.name === "observe_console") {
598
  result = await observeConsoleTool.func("");
599
+ } else if (call.name === "plan_tasks") {
600
+ result = await planTasksTool.func(call.args as { tasks: string[] });
601
+ } else if (call.name === "update_task") {
602
+ result = await updateTaskTool.func(
603
+ call.args as { taskId: number; status: "pending" | "in_progress" | "completed" }
604
+ );
605
+ } else if (call.name === "view_tasks") {
606
+ result = await viewTasksTool.func({});
607
  } else {
608
  result = `Unknown tool: ${call.name}`;
609
  }
src/lib/server/task-tracker.ts ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { DynamicStructuredTool } from "@langchain/core/tools";
2
+ import { z } from "zod";
3
+
4
+ interface Task {
5
+ id: number;
6
+ description: string;
7
+ status: "pending" | "in_progress" | "completed";
8
+ createdAt: Date;
9
+ completedAt?: Date;
10
+ }
11
+
12
+ class TaskTracker {
13
+ private tasks: Task[] = [];
14
+ private nextId = 1;
15
+
16
+ addTask(description: string): Task {
17
+ const task: Task = {
18
+ id: this.nextId++,
19
+ description,
20
+ status: "pending",
21
+ createdAt: new Date(),
22
+ };
23
+ this.tasks.push(task);
24
+ return task;
25
+ }
26
+
27
+ updateTaskStatus(id: number, status: Task["status"]): Task | null {
28
+ const task = this.tasks.find((t) => t.id === id);
29
+ if (task) {
30
+ task.status = status;
31
+ if (status === "completed") {
32
+ task.completedAt = new Date();
33
+ }
34
+ return task;
35
+ }
36
+ return null;
37
+ }
38
+
39
+ getTasks(): Task[] {
40
+ return [...this.tasks];
41
+ }
42
+
43
+ getActiveTasks(): Task[] {
44
+ return this.tasks.filter((t) => t.status !== "completed");
45
+ }
46
+
47
+ clear(): void {
48
+ this.tasks = [];
49
+ this.nextId = 1;
50
+ }
51
+
52
+ formatTaskList(): string {
53
+ if (this.tasks.length === 0) {
54
+ return "No tasks in the list.";
55
+ }
56
+
57
+ const statusEmoji = {
58
+ pending: "⏳",
59
+ in_progress: "πŸ”„",
60
+ completed: "βœ…",
61
+ };
62
+
63
+ return this.tasks
64
+ .map(
65
+ (t) =>
66
+ `${statusEmoji[t.status]} [${t.id}] ${t.description} (${t.status})`,
67
+ )
68
+ .join("\n");
69
+ }
70
+ }
71
+
72
+ const taskTracker = new TaskTracker();
73
+
74
+ export const planTasksTool = new DynamicStructuredTool({
75
+ name: "plan_tasks",
76
+ description:
77
+ "Plan and break down a complex task into smaller steps. Use this BEFORE starting any multi-step work to organize your approach.",
78
+ schema: z.object({
79
+ tasks: z
80
+ .array(z.string())
81
+ .min(1)
82
+ .describe(
83
+ "List of task descriptions in order of execution. Keep each task focused and achievable with a single tool call.",
84
+ ),
85
+ }),
86
+ func: async (input: { tasks: string[] }) => {
87
+ taskTracker.clear();
88
+ const createdTasks = input.tasks.map((desc) => taskTracker.addTask(desc));
89
+
90
+ return `Task plan created with ${createdTasks.length} tasks:\n${taskTracker.formatTaskList()}\n\nStart with task 1 and mark it as in_progress when you begin.`;
91
+ },
92
+ });
93
+
94
+ export const updateTaskTool = new DynamicStructuredTool({
95
+ name: "update_task",
96
+ description:
97
+ "Update the status of a task. Mark as 'in_progress' when starting, 'completed' when done.",
98
+ schema: z.object({
99
+ taskId: z.number().min(1).describe("The task ID to update"),
100
+ status: z
101
+ .enum(["pending", "in_progress", "completed"])
102
+ .describe("The new status for the task"),
103
+ }),
104
+ func: async (input: { taskId: number; status: Task["status"] }) => {
105
+ const task = taskTracker.updateTaskStatus(input.taskId, input.status);
106
+
107
+ if (!task) {
108
+ return `Error: Task ${input.taskId} not found.`;
109
+ }
110
+
111
+ const activeTasks = taskTracker.getActiveTasks();
112
+ const nextTask = activeTasks.find((t) => t.status === "pending");
113
+
114
+ let response = `Task ${task.id} marked as ${task.status}: "${task.description}"`;
115
+
116
+ if (input.status === "completed" && nextTask) {
117
+ response += `\n\nNext task: [${nextTask.id}] ${nextTask.description}`;
118
+ } else if (input.status === "completed" && activeTasks.length === 0) {
119
+ response += "\n\nAll tasks completed! πŸŽ‰";
120
+ }
121
+
122
+ response += `\n\nCurrent task list:\n${taskTracker.formatTaskList()}`;
123
+ return response;
124
+ },
125
+ });
126
+
127
+ export const viewTasksTool = new DynamicStructuredTool({
128
+ name: "view_tasks",
129
+ description: "View the current task list and their statuses.",
130
+ schema: z.object({}),
131
+ func: async () => {
132
+ return taskTracker.formatTaskList();
133
+ },
134
+ });
135
+
136
+ export const taskTrackerTools = [planTasksTool, updateTaskTool, viewTasksTool];
src/lib/server/tools.ts CHANGED
@@ -92,9 +92,9 @@ export const readEditorLinesTool = new DynamicStructuredTool({
92
  export const editEditorTool = new DynamicStructuredTool({
93
  name: "edit_editor",
94
  description:
95
- "Replace specific text in the editor - use for targeted changes after locating code with search_editor",
96
  schema: z.object({
97
- oldText: z.string().describe("The exact text to find and replace"),
98
  newText: z.string().describe("The text to replace it with"),
99
  }),
100
  func: async (input: { oldText: string; newText: string }) => {
@@ -154,9 +154,9 @@ export const editEditorTool = new DynamicStructuredTool({
154
  export const writeEditorTool = new DynamicStructuredTool({
155
  name: "write_editor",
156
  description:
157
- "Replace entire editor content - use for creating new files or complete rewrites",
158
  schema: z.object({
159
- content: z.string().describe("The code content to write to the editor"),
160
  }),
161
  func: async (input: { content: string }) => {
162
  currentEditorContent = input.content;
@@ -320,6 +320,8 @@ export const observeConsoleTool = new DynamicTool({
320
  },
321
  });
322
 
 
 
323
  export const tools = [
324
  readEditorTool,
325
  readEditorLinesTool,
@@ -327,4 +329,5 @@ export const tools = [
327
  editEditorTool,
328
  writeEditorTool,
329
  observeConsoleTool,
 
330
  ];
 
92
  export const editEditorTool = new DynamicStructuredTool({
93
  name: "edit_editor",
94
  description:
95
+ "Replace specific text in the editor - use for SMALL, targeted changes (max ~20 lines). For large changes, use multiple edit_editor calls with plan_tasks",
96
  schema: z.object({
97
+ oldText: z.string().describe("The exact text to find and replace (keep small - max ~20 lines)"),
98
  newText: z.string().describe("The text to replace it with"),
99
  }),
100
  func: async (input: { oldText: string; newText: string }) => {
 
154
  export const writeEditorTool = new DynamicStructuredTool({
155
  name: "write_editor",
156
  description:
157
+ "Replace entire editor content - use ONLY for creating new files or complete rewrites. For modifications, use edit_editor with plan_tasks instead",
158
  schema: z.object({
159
+ content: z.string().describe("The complete code content to write to the editor"),
160
  }),
161
  func: async (input: { content: string }) => {
162
  currentEditorContent = input.content;
 
320
  },
321
  });
322
 
323
+ import { taskTrackerTools } from "./task-tracker";
324
+
325
  export const tools = [
326
  readEditorTool,
327
  readEditorLinesTool,
 
329
  editEditorTool,
330
  writeEditorTool,
331
  observeConsoleTool,
332
+ ...taskTrackerTools,
333
  ];