gencode-ai 0.1.0
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/.env.example +11 -0
- package/CLAUDE.md +70 -0
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/dist/agent/agent.d.ts +84 -0
- package/dist/agent/agent.d.ts.map +1 -0
- package/dist/agent/agent.js +233 -0
- package/dist/agent/agent.js.map +1 -0
- package/dist/agent/index.d.ts +6 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +6 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/types.d.ts +47 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +5 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/cli/components/App.d.ts +14 -0
- package/dist/cli/components/App.d.ts.map +1 -0
- package/dist/cli/components/App.js +395 -0
- package/dist/cli/components/App.js.map +1 -0
- package/dist/cli/components/CommandSuggestions.d.ts +13 -0
- package/dist/cli/components/CommandSuggestions.d.ts.map +1 -0
- package/dist/cli/components/CommandSuggestions.js +32 -0
- package/dist/cli/components/CommandSuggestions.js.map +1 -0
- package/dist/cli/components/Header.d.ts +9 -0
- package/dist/cli/components/Header.d.ts.map +1 -0
- package/dist/cli/components/Header.js +13 -0
- package/dist/cli/components/Header.js.map +1 -0
- package/dist/cli/components/Input.d.ts +13 -0
- package/dist/cli/components/Input.d.ts.map +1 -0
- package/dist/cli/components/Input.js +27 -0
- package/dist/cli/components/Input.js.map +1 -0
- package/dist/cli/components/Logo.d.ts +2 -0
- package/dist/cli/components/Logo.d.ts.map +1 -0
- package/dist/cli/components/Logo.js +8 -0
- package/dist/cli/components/Logo.js.map +1 -0
- package/dist/cli/components/Messages.d.ts +37 -0
- package/dist/cli/components/Messages.d.ts.map +1 -0
- package/dist/cli/components/Messages.js +106 -0
- package/dist/cli/components/Messages.js.map +1 -0
- package/dist/cli/components/ModelSelector.d.ts +13 -0
- package/dist/cli/components/ModelSelector.d.ts.map +1 -0
- package/dist/cli/components/ModelSelector.js +72 -0
- package/dist/cli/components/ModelSelector.js.map +1 -0
- package/dist/cli/components/Spinner.d.ts +12 -0
- package/dist/cli/components/Spinner.d.ts.map +1 -0
- package/dist/cli/components/Spinner.js +45 -0
- package/dist/cli/components/Spinner.js.map +1 -0
- package/dist/cli/components/index.d.ts +12 -0
- package/dist/cli/components/index.d.ts.map +1 -0
- package/dist/cli/components/index.js +12 -0
- package/dist/cli/components/index.js.map +1 -0
- package/dist/cli/components/theme.d.ts +31 -0
- package/dist/cli/components/theme.d.ts.map +1 -0
- package/dist/cli/components/theme.js +36 -0
- package/dist/cli/components/theme.js.map +1 -0
- package/dist/cli/index-legacy.d.ts +7 -0
- package/dist/cli/index-legacy.d.ts.map +1 -0
- package/dist/cli/index-legacy.js +431 -0
- package/dist/cli/index-legacy.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +116 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/ink-cli.d.ts +7 -0
- package/dist/cli/ink-cli.d.ts.map +1 -0
- package/dist/cli/ink-cli.js +105 -0
- package/dist/cli/ink-cli.js.map +1 -0
- package/dist/cli/session-picker.d.ts +16 -0
- package/dist/cli/session-picker.d.ts.map +1 -0
- package/dist/cli/session-picker.js +280 -0
- package/dist/cli/session-picker.js.map +1 -0
- package/dist/cli/ui.d.ts +61 -0
- package/dist/cli/ui.d.ts.map +1 -0
- package/dist/cli/ui.js +364 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +6 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/manager.d.ts +31 -0
- package/dist/config/manager.d.ts.map +1 -0
- package/dist/config/manager.js +65 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/types.d.ts +22 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +6 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/index.d.ts +10 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +9 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/init.d.ts +20 -0
- package/dist/memory/init.d.ts.map +1 -0
- package/dist/memory/init.js +332 -0
- package/dist/memory/init.js.map +1 -0
- package/dist/memory/manager.d.ts +85 -0
- package/dist/memory/manager.d.ts.map +1 -0
- package/dist/memory/manager.js +234 -0
- package/dist/memory/manager.js.map +1 -0
- package/dist/memory/types.d.ts +74 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +6 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/permissions/index.d.ts +7 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +6 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/permissions/manager.d.ts +32 -0
- package/dist/permissions/manager.d.ts.map +1 -0
- package/dist/permissions/manager.js +79 -0
- package/dist/permissions/manager.js.map +1 -0
- package/dist/permissions/types.d.ts +14 -0
- package/dist/permissions/types.d.ts.map +1 -0
- package/dist/permissions/types.js +17 -0
- package/dist/permissions/types.js.map +1 -0
- package/dist/providers/anthropic.d.ts +20 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +185 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/gemini.d.ts +21 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +241 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/index.d.ts +34 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +72 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai.d.ts +19 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +221 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/types.d.ts +125 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +6 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/session/index.d.ts +6 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +6 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/manager.d.ts +101 -0
- package/dist/session/manager.d.ts.map +1 -0
- package/dist/session/manager.js +295 -0
- package/dist/session/manager.js.map +1 -0
- package/dist/session/types.d.ts +39 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +10 -0
- package/dist/session/types.js.map +1 -0
- package/dist/tools/builtin/bash.d.ts +7 -0
- package/dist/tools/builtin/bash.d.ts.map +1 -0
- package/dist/tools/builtin/bash.js +80 -0
- package/dist/tools/builtin/bash.js.map +1 -0
- package/dist/tools/builtin/edit.d.ts +7 -0
- package/dist/tools/builtin/edit.d.ts.map +1 -0
- package/dist/tools/builtin/edit.js +32 -0
- package/dist/tools/builtin/edit.js.map +1 -0
- package/dist/tools/builtin/glob.d.ts +7 -0
- package/dist/tools/builtin/glob.d.ts.map +1 -0
- package/dist/tools/builtin/glob.js +36 -0
- package/dist/tools/builtin/glob.js.map +1 -0
- package/dist/tools/builtin/grep.d.ts +7 -0
- package/dist/tools/builtin/grep.d.ts.map +1 -0
- package/dist/tools/builtin/grep.js +59 -0
- package/dist/tools/builtin/grep.js.map +1 -0
- package/dist/tools/builtin/read.d.ts +7 -0
- package/dist/tools/builtin/read.d.ts.map +1 -0
- package/dist/tools/builtin/read.js +29 -0
- package/dist/tools/builtin/read.js.map +1 -0
- package/dist/tools/builtin/write.d.ts +7 -0
- package/dist/tools/builtin/write.d.ts.map +1 -0
- package/dist/tools/builtin/write.js +24 -0
- package/dist/tools/builtin/write.js.map +1 -0
- package/dist/tools/index.d.ts +38 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +32 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/registry.d.ts +22 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +71 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/types.d.ts +62 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +126 -0
- package/dist/tools/types.js.map +1 -0
- package/docs/README.md +16 -0
- package/docs/proposals/0001-web-fetch-tool.md +293 -0
- package/docs/proposals/0002-web-search-tool.md +306 -0
- package/docs/proposals/0003-task-subagents.md +333 -0
- package/docs/proposals/0004-plan-mode.md +338 -0
- package/docs/proposals/0005-todo-system.md +299 -0
- package/docs/proposals/0006-memory-system.md +539 -0
- package/docs/proposals/0007-context-management.md +429 -0
- package/docs/proposals/0008-checkpointing.md +327 -0
- package/docs/proposals/0009-hooks-system.md +343 -0
- package/docs/proposals/0010-mcp-integration.md +382 -0
- package/docs/proposals/0011-custom-commands.md +374 -0
- package/docs/proposals/0012-ask-user-question.md +317 -0
- package/docs/proposals/0013-multi-edit-tool.md +345 -0
- package/docs/proposals/0014-lsp-tool.md +478 -0
- package/docs/proposals/0015-ls-tool.md +407 -0
- package/docs/proposals/0016-kill-shell-tool.md +455 -0
- package/docs/proposals/0017-background-tasks.md +489 -0
- package/docs/proposals/0018-parallel-tool-execution.md +415 -0
- package/docs/proposals/0019-session-enhancements.md +462 -0
- package/docs/proposals/0020-session-summarization.md +447 -0
- package/docs/proposals/0021-skills-system.md +409 -0
- package/docs/proposals/0022-plugin-system.md +467 -0
- package/docs/proposals/0023-permission-enhancements.md +470 -0
- package/docs/proposals/0024-keyboard-shortcuts.md +443 -0
- package/docs/proposals/0025-cost-tracking.md +447 -0
- package/docs/proposals/0026-git-integration.md +475 -0
- package/docs/proposals/0027-enhanced-read-tool.md +514 -0
- package/docs/proposals/0028-enhanced-bash-tool.md +511 -0
- package/docs/proposals/0029-notebook-edit-tool.md +413 -0
- package/docs/proposals/0030-plugin-marketplace.md +360 -0
- package/docs/proposals/0031-command-suggestions.md +295 -0
- package/docs/proposals/0032-ide-integrations.md +328 -0
- package/docs/proposals/0033-enterprise-deployment.md +221 -0
- package/docs/proposals/0034-sandboxing.md +273 -0
- package/docs/proposals/0035-auto-updater.md +311 -0
- package/docs/proposals/0036-enhanced-glob-tool.md +267 -0
- package/docs/proposals/0037-enhanced-grep-tool.md +360 -0
- package/docs/proposals/0038-interactive-cli-ui.md +373 -0
- package/docs/proposals/0039-streaming-enhancements.md +359 -0
- package/docs/proposals/0040-multi-provider-enhancements.md +369 -0
- package/docs/proposals/README.md +84 -0
- package/docs/proposals/TEMPLATE.md +57 -0
- package/docs/proposals/research/claude-code-research.md +307 -0
- package/examples/agent-demo.ts +115 -0
- package/examples/basic.ts +166 -0
- package/package.json +50 -0
- package/src/agent/agent.ts +276 -0
- package/src/agent/index.ts +6 -0
- package/src/agent/types.ts +62 -0
- package/src/cli/components/App.tsx +565 -0
- package/src/cli/components/CommandSuggestions.tsx +58 -0
- package/src/cli/components/Header.tsx +36 -0
- package/src/cli/components/Input.tsx +60 -0
- package/src/cli/components/Logo.tsx +16 -0
- package/src/cli/components/Messages.tsx +210 -0
- package/src/cli/components/ModelSelector.tsx +135 -0
- package/src/cli/components/Spinner.tsx +72 -0
- package/src/cli/components/index.ts +21 -0
- package/src/cli/components/theme.ts +36 -0
- package/src/cli/index.tsx +136 -0
- package/src/config/index.ts +7 -0
- package/src/config/manager.ts +77 -0
- package/src/config/types.ts +25 -0
- package/src/index.ts +86 -0
- package/src/permissions/index.ts +7 -0
- package/src/permissions/manager.ts +97 -0
- package/src/permissions/types.ts +29 -0
- package/src/providers/anthropic.ts +224 -0
- package/src/providers/gemini.ts +295 -0
- package/src/providers/index.ts +97 -0
- package/src/providers/openai.ts +261 -0
- package/src/providers/types.ts +181 -0
- package/src/session/index.ts +6 -0
- package/src/session/manager.ts +354 -0
- package/src/session/types.ts +49 -0
- package/src/tools/builtin/bash.ts +92 -0
- package/src/tools/builtin/edit.ts +37 -0
- package/src/tools/builtin/glob.ts +42 -0
- package/src/tools/builtin/grep.ts +67 -0
- package/src/tools/builtin/read.ts +34 -0
- package/src/tools/builtin/write.ts +27 -0
- package/src/tools/index.ts +36 -0
- package/src/tools/registry.ts +83 -0
- package/src/tools/types.ts +172 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
# Proposal: Background Tasks & TaskOutput
|
|
2
|
+
|
|
3
|
+
- **Proposal ID**: 0017
|
|
4
|
+
- **Author**: mycode team
|
|
5
|
+
- **Status**: Draft
|
|
6
|
+
- **Created**: 2025-01-15
|
|
7
|
+
- **Updated**: 2025-01-15
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
Implement a comprehensive background task system with a TaskOutput tool for retrieving results from asynchronous operations. This enables non-blocking execution of long-running tasks like builds, tests, and subagent operations.
|
|
12
|
+
|
|
13
|
+
## Motivation
|
|
14
|
+
|
|
15
|
+
Currently, mycode executes all operations synchronously, blocking the conversation:
|
|
16
|
+
|
|
17
|
+
1. **Blocking execution**: Long builds freeze the conversation
|
|
18
|
+
2. **No parallel work**: Can't work while tests run
|
|
19
|
+
3. **Timeout issues**: Long tasks may timeout
|
|
20
|
+
4. **Poor UX**: User waits with no progress indication
|
|
21
|
+
5. **Lost results**: Can't check output of past commands
|
|
22
|
+
|
|
23
|
+
A background task system enables async operations with result retrieval.
|
|
24
|
+
|
|
25
|
+
## Claude Code Reference
|
|
26
|
+
|
|
27
|
+
Claude Code provides TaskOutput tool for background task management:
|
|
28
|
+
|
|
29
|
+
### TaskOutput Tool
|
|
30
|
+
```typescript
|
|
31
|
+
TaskOutput({
|
|
32
|
+
task_id: "agent-abc123",
|
|
33
|
+
block: true, // Wait for completion (default: true)
|
|
34
|
+
timeout: 30000 // Max wait time in ms
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Background Execution Pattern
|
|
39
|
+
```typescript
|
|
40
|
+
// Launch background task
|
|
41
|
+
Task({
|
|
42
|
+
description: "Run tests",
|
|
43
|
+
prompt: "Execute the full test suite",
|
|
44
|
+
subagent_type: "Bash",
|
|
45
|
+
run_in_background: true
|
|
46
|
+
})
|
|
47
|
+
// Returns: { task_id: "agent-abc123", output_file: "/path/to/output.log" }
|
|
48
|
+
|
|
49
|
+
// Check status later
|
|
50
|
+
TaskOutput({
|
|
51
|
+
task_id: "agent-abc123",
|
|
52
|
+
block: false // Non-blocking check
|
|
53
|
+
})
|
|
54
|
+
// Returns current status and partial output
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Key Characteristics
|
|
58
|
+
- Works with Task tool and Bash tool
|
|
59
|
+
- Supports blocking and non-blocking modes
|
|
60
|
+
- Configurable timeout up to 10 minutes
|
|
61
|
+
- Returns task status and output
|
|
62
|
+
- Available via /tasks command
|
|
63
|
+
|
|
64
|
+
## Detailed Design
|
|
65
|
+
|
|
66
|
+
### API Design
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// src/tasks/types.ts
|
|
70
|
+
type TaskStatus = 'pending' | 'running' | 'completed' | 'error' | 'cancelled';
|
|
71
|
+
type TaskType = 'bash' | 'agent' | 'remote';
|
|
72
|
+
|
|
73
|
+
interface BackgroundTask {
|
|
74
|
+
id: string;
|
|
75
|
+
type: TaskType;
|
|
76
|
+
description: string;
|
|
77
|
+
status: TaskStatus;
|
|
78
|
+
started_at: Date;
|
|
79
|
+
completed_at?: Date;
|
|
80
|
+
output_file: string;
|
|
81
|
+
exit_code?: number;
|
|
82
|
+
error?: string;
|
|
83
|
+
metadata?: Record<string, unknown>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface TaskOutputInput {
|
|
87
|
+
task_id: string;
|
|
88
|
+
block?: boolean; // Wait for completion (default: true)
|
|
89
|
+
timeout?: number; // Max wait time in ms (default: 30000, max: 600000)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface TaskOutputOutput {
|
|
93
|
+
success: boolean;
|
|
94
|
+
task_id: string;
|
|
95
|
+
status: TaskStatus;
|
|
96
|
+
output?: string; // Full or partial output
|
|
97
|
+
exit_code?: number;
|
|
98
|
+
error?: string;
|
|
99
|
+
elapsed_ms?: number; // Time since task started
|
|
100
|
+
truncated?: boolean; // Output was truncated
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// src/tools/task-output/task-output-tool.ts
|
|
106
|
+
const taskOutputTool: Tool<TaskOutputInput> = {
|
|
107
|
+
name: 'TaskOutput',
|
|
108
|
+
description: `Retrieve output from a running or completed background task.
|
|
109
|
+
|
|
110
|
+
Parameters:
|
|
111
|
+
- task_id: The ID of the background task
|
|
112
|
+
- block: If true (default), wait for task completion
|
|
113
|
+
- timeout: Maximum wait time in milliseconds (default: 30000, max: 600000)
|
|
114
|
+
|
|
115
|
+
Use block=false for non-blocking status check.
|
|
116
|
+
Use /tasks command to see all running/completed tasks.
|
|
117
|
+
|
|
118
|
+
Returns task status, output content, and exit code if completed.
|
|
119
|
+
`,
|
|
120
|
+
parameters: z.object({
|
|
121
|
+
task_id: z.string(),
|
|
122
|
+
block: z.boolean().optional().default(true),
|
|
123
|
+
timeout: z.number().min(0).max(600000).optional().default(30000)
|
|
124
|
+
}),
|
|
125
|
+
execute: async (input, context) => { ... }
|
|
126
|
+
};
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Implementation Approach
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
// src/tasks/task-manager.ts
|
|
133
|
+
class TaskManager {
|
|
134
|
+
private tasks: Map<string, BackgroundTask> = new Map();
|
|
135
|
+
private processes: Map<string, ChildProcess> = new Map();
|
|
136
|
+
private outputDir: string;
|
|
137
|
+
|
|
138
|
+
constructor(outputDir = '~/.mycode/tasks') {
|
|
139
|
+
this.outputDir = expandPath(outputDir);
|
|
140
|
+
fs.mkdirSync(this.outputDir, { recursive: true });
|
|
141
|
+
this.loadExistingTasks();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async createBashTask(command: string, description: string): Promise<BackgroundTask> {
|
|
145
|
+
const id = generateTaskId('bash');
|
|
146
|
+
const outputFile = path.join(this.outputDir, `${id}.log`);
|
|
147
|
+
|
|
148
|
+
const task: BackgroundTask = {
|
|
149
|
+
id,
|
|
150
|
+
type: 'bash',
|
|
151
|
+
description,
|
|
152
|
+
status: 'running',
|
|
153
|
+
started_at: new Date(),
|
|
154
|
+
output_file: outputFile
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Spawn process
|
|
158
|
+
const child = spawn('bash', ['-c', command], {
|
|
159
|
+
detached: true,
|
|
160
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Setup output logging
|
|
164
|
+
const logStream = fs.createWriteStream(outputFile);
|
|
165
|
+
child.stdout?.pipe(logStream);
|
|
166
|
+
child.stderr?.pipe(logStream);
|
|
167
|
+
|
|
168
|
+
// Track completion
|
|
169
|
+
child.on('exit', (code) => {
|
|
170
|
+
task.status = code === 0 ? 'completed' : 'error';
|
|
171
|
+
task.completed_at = new Date();
|
|
172
|
+
task.exit_code = code ?? -1;
|
|
173
|
+
this.saveTasks();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
this.tasks.set(id, task);
|
|
177
|
+
this.processes.set(id, child);
|
|
178
|
+
this.saveTasks();
|
|
179
|
+
|
|
180
|
+
return task;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async createAgentTask(
|
|
184
|
+
subagentType: string,
|
|
185
|
+
prompt: string,
|
|
186
|
+
description: string
|
|
187
|
+
): Promise<BackgroundTask> {
|
|
188
|
+
const id = generateTaskId('agent');
|
|
189
|
+
const outputFile = path.join(this.outputDir, `${id}.json`);
|
|
190
|
+
|
|
191
|
+
const task: BackgroundTask = {
|
|
192
|
+
id,
|
|
193
|
+
type: 'agent',
|
|
194
|
+
description,
|
|
195
|
+
status: 'running',
|
|
196
|
+
started_at: new Date(),
|
|
197
|
+
output_file: outputFile,
|
|
198
|
+
metadata: { subagentType, prompt }
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Run agent in separate context
|
|
202
|
+
this.runAgentAsync(id, subagentType, prompt, outputFile);
|
|
203
|
+
|
|
204
|
+
this.tasks.set(id, task);
|
|
205
|
+
this.saveTasks();
|
|
206
|
+
|
|
207
|
+
return task;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async getOutput(
|
|
211
|
+
taskId: string,
|
|
212
|
+
options: { block: boolean; timeout: number }
|
|
213
|
+
): Promise<TaskOutputOutput> {
|
|
214
|
+
const task = this.tasks.get(taskId);
|
|
215
|
+
if (!task) {
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
task_id: taskId,
|
|
219
|
+
status: 'error',
|
|
220
|
+
error: 'Task not found'
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// If blocking, wait for completion
|
|
225
|
+
if (options.block && task.status === 'running') {
|
|
226
|
+
await this.waitForCompletion(taskId, options.timeout);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Read output
|
|
230
|
+
const output = await this.readOutput(task.output_file);
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
success: true,
|
|
234
|
+
task_id: taskId,
|
|
235
|
+
status: task.status,
|
|
236
|
+
output,
|
|
237
|
+
exit_code: task.exit_code,
|
|
238
|
+
elapsed_ms: Date.now() - task.started_at.getTime()
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private async waitForCompletion(taskId: string, timeout: number): Promise<void> {
|
|
243
|
+
const start = Date.now();
|
|
244
|
+
const task = this.tasks.get(taskId);
|
|
245
|
+
if (!task) return;
|
|
246
|
+
|
|
247
|
+
while (task.status === 'running' && Date.now() - start < timeout) {
|
|
248
|
+
await new Promise(r => setTimeout(r, 500));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async cancelTask(taskId: string): Promise<boolean> {
|
|
253
|
+
const task = this.tasks.get(taskId);
|
|
254
|
+
const process = this.processes.get(taskId);
|
|
255
|
+
|
|
256
|
+
if (!task || task.status !== 'running') return false;
|
|
257
|
+
|
|
258
|
+
if (process && !process.killed) {
|
|
259
|
+
process.kill('SIGTERM');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
task.status = 'cancelled';
|
|
263
|
+
task.completed_at = new Date();
|
|
264
|
+
this.saveTasks();
|
|
265
|
+
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
listTasks(filter?: { status?: TaskStatus; type?: TaskType }): BackgroundTask[] {
|
|
270
|
+
let tasks = Array.from(this.tasks.values());
|
|
271
|
+
|
|
272
|
+
if (filter?.status) {
|
|
273
|
+
tasks = tasks.filter(t => t.status === filter.status);
|
|
274
|
+
}
|
|
275
|
+
if (filter?.type) {
|
|
276
|
+
tasks = tasks.filter(t => t.type === filter.type);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return tasks.sort((a, b) => b.started_at.getTime() - a.started_at.getTime());
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
cleanup(maxAge: number = 24 * 60 * 60 * 1000): number {
|
|
283
|
+
const cutoff = Date.now() - maxAge;
|
|
284
|
+
let removed = 0;
|
|
285
|
+
|
|
286
|
+
for (const [id, task] of this.tasks) {
|
|
287
|
+
if (task.status !== 'running' && task.started_at.getTime() < cutoff) {
|
|
288
|
+
// Delete output file
|
|
289
|
+
try { fs.unlinkSync(task.output_file); } catch {}
|
|
290
|
+
this.tasks.delete(id);
|
|
291
|
+
removed++;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
this.saveTasks();
|
|
296
|
+
return removed;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export const taskManager = new TaskManager();
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### CLI Command: /tasks
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// src/cli/commands/tasks.ts
|
|
307
|
+
function tasksCommand(args: string[]): void {
|
|
308
|
+
const tasks = taskManager.listTasks();
|
|
309
|
+
|
|
310
|
+
if (tasks.length === 0) {
|
|
311
|
+
console.log('No background tasks.');
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
console.log('\nBackground Tasks:');
|
|
316
|
+
console.log('─'.repeat(70));
|
|
317
|
+
|
|
318
|
+
for (const task of tasks) {
|
|
319
|
+
const elapsed = formatDuration(Date.now() - task.started_at.getTime());
|
|
320
|
+
const statusIcon = {
|
|
321
|
+
running: '⏳',
|
|
322
|
+
completed: '✓',
|
|
323
|
+
error: '✗',
|
|
324
|
+
cancelled: '⊘',
|
|
325
|
+
pending: '○'
|
|
326
|
+
}[task.status];
|
|
327
|
+
|
|
328
|
+
console.log(`${statusIcon} ${task.id.padEnd(15)} ${task.description.slice(0, 30).padEnd(32)} ${elapsed}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
console.log('─'.repeat(70));
|
|
332
|
+
console.log('Use TaskOutput tool to get task output.');
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### File Changes
|
|
337
|
+
|
|
338
|
+
| File | Action | Description |
|
|
339
|
+
|------|--------|-------------|
|
|
340
|
+
| `src/tasks/types.ts` | Create | Task type definitions |
|
|
341
|
+
| `src/tasks/task-manager.ts` | Create | Task lifecycle management |
|
|
342
|
+
| `src/tasks/index.ts` | Create | Module exports |
|
|
343
|
+
| `src/tools/task-output/task-output-tool.ts` | Create | TaskOutput tool |
|
|
344
|
+
| `src/tools/task-output/index.ts` | Create | Module exports |
|
|
345
|
+
| `src/tools/index.ts` | Modify | Register TaskOutput tool |
|
|
346
|
+
| `src/cli/commands/tasks.ts` | Create | /tasks command |
|
|
347
|
+
| `src/cli/index.ts` | Modify | Register /tasks command |
|
|
348
|
+
|
|
349
|
+
## User Experience
|
|
350
|
+
|
|
351
|
+
### Launch Background Task
|
|
352
|
+
```
|
|
353
|
+
User: Run the tests but don't wait for them
|
|
354
|
+
|
|
355
|
+
Agent: I'll start the tests in the background.
|
|
356
|
+
|
|
357
|
+
[Bash: command="npm test", run_in_background=true]
|
|
358
|
+
|
|
359
|
+
┌─ Background Task Started ─────────────────────────┐
|
|
360
|
+
│ Task ID: bash-a1b2c3d4 │
|
|
361
|
+
│ Command: npm test │
|
|
362
|
+
│ Status: Running │
|
|
363
|
+
│ Output: ~/.mycode/tasks/bash-a1b2c3d4.log │
|
|
364
|
+
└───────────────────────────────────────────────────┘
|
|
365
|
+
|
|
366
|
+
You can continue working. Use /tasks to check status.
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Check Task Status (Non-blocking)
|
|
370
|
+
```
|
|
371
|
+
User: How are those tests doing?
|
|
372
|
+
|
|
373
|
+
Agent: [TaskOutput: task_id="bash-a1b2c3d4", block=false]
|
|
374
|
+
|
|
375
|
+
┌─ Task Status ─────────────────────────────────────┐
|
|
376
|
+
│ Task ID: bash-a1b2c3d4 │
|
|
377
|
+
│ Status: Running (2m 15s elapsed) │
|
|
378
|
+
│ Recent Output: │
|
|
379
|
+
│ PASS src/tools/edit.test.ts │
|
|
380
|
+
│ PASS src/tools/glob.test.ts │
|
|
381
|
+
│ Running: src/agent/agent.test.ts... │
|
|
382
|
+
└───────────────────────────────────────────────────┘
|
|
383
|
+
|
|
384
|
+
Tests are still running (45/60 passed so far).
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Wait for Completion
|
|
388
|
+
```
|
|
389
|
+
Agent: [TaskOutput: task_id="bash-a1b2c3d4", block=true, timeout=60000]
|
|
390
|
+
|
|
391
|
+
┌─ Task Completed ──────────────────────────────────┐
|
|
392
|
+
│ Task ID: bash-a1b2c3d4 │
|
|
393
|
+
│ Status: Completed │
|
|
394
|
+
│ Exit Code: 0 │
|
|
395
|
+
│ Duration: 3m 42s │
|
|
396
|
+
│ Summary: │
|
|
397
|
+
│ 60 tests passed │
|
|
398
|
+
│ 0 tests failed │
|
|
399
|
+
│ Coverage: 87.3% │
|
|
400
|
+
└───────────────────────────────────────────────────┘
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### List All Tasks
|
|
404
|
+
```
|
|
405
|
+
User: /tasks
|
|
406
|
+
|
|
407
|
+
Background Tasks:
|
|
408
|
+
┌────────────────────────────────────────────────────────────────┐
|
|
409
|
+
│ Status ID Description Elapsed │
|
|
410
|
+
├────────────────────────────────────────────────────────────────┤
|
|
411
|
+
│ ✓ bash-a1b2c3d4 Run npm test 3m 42s │
|
|
412
|
+
│ ⏳ agent-e5f6g7h8 Explore auth code 45s │
|
|
413
|
+
│ ✗ bash-i9j0k1l2 Build docker image 12m 3s │
|
|
414
|
+
└────────────────────────────────────────────────────────────────┘
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Alternatives Considered
|
|
418
|
+
|
|
419
|
+
### Alternative 1: Promise-based Only
|
|
420
|
+
Use JavaScript promises without task registry.
|
|
421
|
+
|
|
422
|
+
**Pros**: Simpler implementation
|
|
423
|
+
**Cons**: No persistence, no /tasks visibility
|
|
424
|
+
**Decision**: Rejected - Registry provides better UX
|
|
425
|
+
|
|
426
|
+
### Alternative 2: Worker Threads
|
|
427
|
+
Use Node.js worker threads.
|
|
428
|
+
|
|
429
|
+
**Pros**: Better isolation
|
|
430
|
+
**Cons**: More complex, limited shell access
|
|
431
|
+
**Decision**: Deferred - Can add for CPU-intensive tasks
|
|
432
|
+
|
|
433
|
+
### Alternative 3: External Task Queue
|
|
434
|
+
Use Redis/BullMQ for task management.
|
|
435
|
+
|
|
436
|
+
**Pros**: Robust, distributed
|
|
437
|
+
**Cons**: Heavy dependency, overkill for CLI
|
|
438
|
+
**Decision**: Rejected - Keep it simple
|
|
439
|
+
|
|
440
|
+
## Security Considerations
|
|
441
|
+
|
|
442
|
+
1. **Task Isolation**: Each task runs in separate process
|
|
443
|
+
2. **Output Sanitization**: Don't expose sensitive data in logs
|
|
444
|
+
3. **Resource Limits**: Limit concurrent tasks and output size
|
|
445
|
+
4. **Cleanup**: Regular cleanup of old task files
|
|
446
|
+
5. **Permission**: Only access own tasks
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
const LIMITS = {
|
|
450
|
+
maxConcurrentTasks: 10,
|
|
451
|
+
maxOutputSize: 10 * 1024 * 1024, // 10MB
|
|
452
|
+
taskRetention: 24 * 60 * 60 * 1000 // 24 hours
|
|
453
|
+
};
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
## Testing Strategy
|
|
457
|
+
|
|
458
|
+
1. **Unit Tests**:
|
|
459
|
+
- Task creation and tracking
|
|
460
|
+
- Output retrieval
|
|
461
|
+
- Blocking vs non-blocking
|
|
462
|
+
- Timeout handling
|
|
463
|
+
|
|
464
|
+
2. **Integration Tests**:
|
|
465
|
+
- Background bash execution
|
|
466
|
+
- Agent task execution
|
|
467
|
+
- Concurrent tasks
|
|
468
|
+
- Cleanup logic
|
|
469
|
+
|
|
470
|
+
3. **Manual Testing**:
|
|
471
|
+
- Long-running tasks
|
|
472
|
+
- Task cancellation
|
|
473
|
+
- /tasks command
|
|
474
|
+
|
|
475
|
+
## Migration Path
|
|
476
|
+
|
|
477
|
+
1. **Phase 1**: Basic TaskManager and TaskOutput tool
|
|
478
|
+
2. **Phase 2**: /tasks CLI command
|
|
479
|
+
3. **Phase 3**: Agent background execution
|
|
480
|
+
4. **Phase 4**: Task persistence across sessions
|
|
481
|
+
5. **Phase 5**: Task notifications on completion
|
|
482
|
+
|
|
483
|
+
Integrates with KillShell (0016) and Enhanced Bash (0028).
|
|
484
|
+
|
|
485
|
+
## References
|
|
486
|
+
|
|
487
|
+
- [Node.js Child Process](https://nodejs.org/api/child_process.html)
|
|
488
|
+
- [Claude Code TaskOutput Documentation](https://code.claude.com/docs/en/tools)
|
|
489
|
+
- [BullMQ - Node.js Job Queue](https://docs.bullmq.io/)
|