super-subagents 1.0.1
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/.windsurf/plans/persist-tasks-by-cwd.md +113 -0
- package/CHANGELOG.md +10 -0
- package/CLAUDE.md +67 -0
- package/README.md +124 -0
- package/build/config/timeouts.d.ts +12 -0
- package/build/config/timeouts.d.ts.map +1 -0
- package/build/config/timeouts.js +21 -0
- package/build/config/timeouts.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +116 -0
- package/build/index.js.map +1 -0
- package/build/models.d.ts +11 -0
- package/build/models.d.ts.map +1 -0
- package/build/models.js +22 -0
- package/build/models.js.map +1 -0
- package/build/services/client-context.d.ts +31 -0
- package/build/services/client-context.d.ts.map +1 -0
- package/build/services/client-context.js +44 -0
- package/build/services/client-context.js.map +1 -0
- package/build/services/copilot-switch.d.ts +36 -0
- package/build/services/copilot-switch.d.ts.map +1 -0
- package/build/services/copilot-switch.js +226 -0
- package/build/services/copilot-switch.js.map +1 -0
- package/build/services/process-spawner.d.ts +9 -0
- package/build/services/process-spawner.d.ts.map +1 -0
- package/build/services/process-spawner.js +475 -0
- package/build/services/process-spawner.js.map +1 -0
- package/build/services/retry-queue.d.ts +35 -0
- package/build/services/retry-queue.d.ts.map +1 -0
- package/build/services/retry-queue.js +125 -0
- package/build/services/retry-queue.js.map +1 -0
- package/build/services/task-manager.d.ts +124 -0
- package/build/services/task-manager.d.ts.map +1 -0
- package/build/services/task-manager.js +584 -0
- package/build/services/task-manager.js.map +1 -0
- package/build/services/task-persistence.d.ts +29 -0
- package/build/services/task-persistence.d.ts.map +1 -0
- package/build/services/task-persistence.js +158 -0
- package/build/services/task-persistence.js.map +1 -0
- package/build/templates/index.d.ts +11 -0
- package/build/templates/index.d.ts.map +1 -0
- package/build/templates/index.js +30 -0
- package/build/templates/index.js.map +1 -0
- package/build/tools/batch-spawn.d.ts +69 -0
- package/build/tools/batch-spawn.d.ts.map +1 -0
- package/build/tools/batch-spawn.js +150 -0
- package/build/tools/batch-spawn.js.map +1 -0
- package/build/tools/cancel-task.d.ts +21 -0
- package/build/tools/cancel-task.d.ts.map +1 -0
- package/build/tools/cancel-task.js +44 -0
- package/build/tools/cancel-task.js.map +1 -0
- package/build/tools/clear-tasks.d.ts +21 -0
- package/build/tools/clear-tasks.d.ts.map +1 -0
- package/build/tools/clear-tasks.js +43 -0
- package/build/tools/clear-tasks.js.map +1 -0
- package/build/tools/force-start.d.ts +21 -0
- package/build/tools/force-start.d.ts.map +1 -0
- package/build/tools/force-start.js +38 -0
- package/build/tools/force-start.js.map +1 -0
- package/build/tools/get-status.d.ts +31 -0
- package/build/tools/get-status.d.ts.map +1 -0
- package/build/tools/get-status.js +384 -0
- package/build/tools/get-status.js.map +1 -0
- package/build/tools/list-tasks.d.ts +26 -0
- package/build/tools/list-tasks.d.ts.map +1 -0
- package/build/tools/list-tasks.js +74 -0
- package/build/tools/list-tasks.js.map +1 -0
- package/build/tools/recover-task.d.ts +29 -0
- package/build/tools/recover-task.d.ts.map +1 -0
- package/build/tools/recover-task.js +91 -0
- package/build/tools/recover-task.js.map +1 -0
- package/build/tools/resume-task.d.ts +29 -0
- package/build/tools/resume-task.d.ts.map +1 -0
- package/build/tools/resume-task.js +43 -0
- package/build/tools/resume-task.js.map +1 -0
- package/build/tools/retry-task.d.ts +21 -0
- package/build/tools/retry-task.d.ts.map +1 -0
- package/build/tools/retry-task.js +52 -0
- package/build/tools/retry-task.js.map +1 -0
- package/build/tools/simulate-rate-limit.d.ts +25 -0
- package/build/tools/simulate-rate-limit.d.ts.map +1 -0
- package/build/tools/simulate-rate-limit.js +69 -0
- package/build/tools/simulate-rate-limit.js.map +1 -0
- package/build/tools/spawn-task.d.ts +57 -0
- package/build/tools/spawn-task.d.ts.map +1 -0
- package/build/tools/spawn-task.js +113 -0
- package/build/tools/spawn-task.js.map +1 -0
- package/build/tools/stream-output.d.ts +29 -0
- package/build/tools/stream-output.d.ts.map +1 -0
- package/build/tools/stream-output.js +96 -0
- package/build/tools/stream-output.js.map +1 -0
- package/build/types.d.ts +75 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +12 -0
- package/build/types.js.map +1 -0
- package/build/utils/format.d.ts +29 -0
- package/build/utils/format.d.ts.map +1 -0
- package/build/utils/format.js +81 -0
- package/build/utils/format.js.map +1 -0
- package/build/utils/sanitize.d.ts +63 -0
- package/build/utils/sanitize.d.ts.map +1 -0
- package/build/utils/sanitize.js +28 -0
- package/build/utils/sanitize.js.map +1 -0
- package/build/utils/task-id-generator.d.ts +10 -0
- package/build/utils/task-id-generator.d.ts.map +1 -0
- package/build/utils/task-id-generator.js +22 -0
- package/build/utils/task-id-generator.js.map +1 -0
- package/docs/timeout-durability.md +28 -0
- package/package.json +39 -0
- package/plans/timeout-durability/00-overview.md +38 -0
- package/plans/timeout-durability/01-analysis.md +37 -0
- package/plans/timeout-durability/decisions.md +22 -0
- package/plans/timeout-durability/resources.md +24 -0
- package/plans/timeout-durability/step-01-timeout-flow.md +27 -0
- package/plans/timeout-durability/step-02-root-cause-map.md +26 -0
- package/plans/timeout-durability/step-03-state-schema.md +26 -0
- package/plans/timeout-durability/step-04-messaging-recovery.md +27 -0
- package/src/config/timeouts.ts +22 -0
- package/src/index.ts +129 -0
- package/src/models.ts +23 -0
- package/src/services/client-context.ts +49 -0
- package/src/services/copilot-switch.ts +269 -0
- package/src/services/process-spawner.ts +548 -0
- package/src/services/retry-queue.ts +151 -0
- package/src/services/task-manager.ts +667 -0
- package/src/services/task-persistence.ts +175 -0
- package/src/templates/index.ts +35 -0
- package/src/templates/super-coder.mdx +519 -0
- package/src/templates/super-planner.mdx +558 -0
- package/src/templates/super-researcher.mdx +394 -0
- package/src/templates/super-tester.mdx +688 -0
- package/src/tools/batch-spawn.ts +179 -0
- package/src/tools/cancel-task.ts +58 -0
- package/src/tools/clear-tasks.ts +52 -0
- package/src/tools/force-start.ts +48 -0
- package/src/tools/get-status.ts +480 -0
- package/src/tools/list-tasks.ts +83 -0
- package/src/tools/recover-task.ts +112 -0
- package/src/tools/resume-task.ts +51 -0
- package/src/tools/retry-task.ts +72 -0
- package/src/tools/simulate-rate-limit.ts +84 -0
- package/src/tools/spawn-task.ts +135 -0
- package/src/tools/stream-output.ts +101 -0
- package/src/types.ts +86 -0
- package/src/utils/format.ts +83 -0
- package/src/utils/sanitize.ts +35 -0
- package/src/utils/task-id-generator.ts +25 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from 'fs';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { TaskState, TaskStatus } from '../types.js';
|
|
6
|
+
|
|
7
|
+
const STORAGE_DIR_NAME = '.super-agents';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get the storage directory path (~/.super-agents/)
|
|
11
|
+
*/
|
|
12
|
+
export function getStorageDir(): string {
|
|
13
|
+
return join(homedir(), STORAGE_DIR_NAME);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get MD5 hash of a string
|
|
18
|
+
*/
|
|
19
|
+
export function hashCwd(cwd: string): string {
|
|
20
|
+
return createHash('md5').update(cwd).digest('hex');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the storage file path for a given cwd
|
|
25
|
+
* Returns: ~/.super-agents/{md5(cwd)}.json
|
|
26
|
+
*/
|
|
27
|
+
export function getStoragePath(cwd: string): string {
|
|
28
|
+
return join(getStorageDir(), `${hashCwd(cwd)}.json`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Ensure storage directory exists
|
|
33
|
+
*/
|
|
34
|
+
function ensureStorageDir(): boolean {
|
|
35
|
+
try {
|
|
36
|
+
const dir = getStorageDir();
|
|
37
|
+
if (!existsSync(dir)) {
|
|
38
|
+
mkdirSync(dir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(`[task-persistence] Failed to create storage directory: ${error}`);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Serialize tasks for persistence (excludes non-serializable fields)
|
|
49
|
+
*/
|
|
50
|
+
function serializeTasks(tasks: TaskState[]): string {
|
|
51
|
+
const serializable = tasks.map(task => {
|
|
52
|
+
const { process, ...rest } = task as TaskState & { process?: unknown };
|
|
53
|
+
return rest;
|
|
54
|
+
});
|
|
55
|
+
return JSON.stringify(serializable, null, 2);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Mark orphaned running/pending tasks as failed (server crashed)
|
|
60
|
+
* Note: RATE_LIMITED tasks are preserved for auto-retry
|
|
61
|
+
*/
|
|
62
|
+
function applyTaskDefaults(task: TaskState): TaskState {
|
|
63
|
+
const startTime = task.startTime || new Date().toISOString();
|
|
64
|
+
return {
|
|
65
|
+
...task,
|
|
66
|
+
startTime,
|
|
67
|
+
lastHeartbeatAt: task.lastHeartbeatAt ?? startTime,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function recoverOrphanedTasks(tasks: TaskState[]): TaskState[] {
|
|
72
|
+
return tasks.map(task => {
|
|
73
|
+
// Keep rate-limited tasks as-is for auto-retry
|
|
74
|
+
if (task.status === TaskStatus.RATE_LIMITED) {
|
|
75
|
+
return applyTaskDefaults(task);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Mark running/pending as failed (server crashed)
|
|
79
|
+
if (task.status === TaskStatus.RUNNING || task.status === TaskStatus.PENDING) {
|
|
80
|
+
const updated: TaskState = {
|
|
81
|
+
...task,
|
|
82
|
+
status: TaskStatus.FAILED,
|
|
83
|
+
endTime: new Date().toISOString(),
|
|
84
|
+
error: 'Server restarted - task was interrupted',
|
|
85
|
+
timeoutReason: 'server_restart',
|
|
86
|
+
timeoutContext: {
|
|
87
|
+
detectedBy: 'startup_recovery',
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
return applyTaskDefaults(updated);
|
|
91
|
+
}
|
|
92
|
+
return applyTaskDefaults(task);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Save tasks to disk with atomic write
|
|
98
|
+
*/
|
|
99
|
+
export function saveTasks(cwd: string, tasks: TaskState[]): boolean {
|
|
100
|
+
if (!ensureStorageDir()) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const filePath = getStoragePath(cwd);
|
|
105
|
+
const tempPath = `${filePath}.tmp`;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const data = serializeTasks(tasks);
|
|
109
|
+
|
|
110
|
+
// Atomic write: write to temp file, then rename
|
|
111
|
+
writeFileSync(tempPath, data, 'utf-8');
|
|
112
|
+
renameSync(tempPath, filePath);
|
|
113
|
+
|
|
114
|
+
return true;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error(`[task-persistence] Failed to save tasks: ${error}`);
|
|
117
|
+
|
|
118
|
+
// Clean up temp file if it exists
|
|
119
|
+
try {
|
|
120
|
+
if (existsSync(tempPath)) {
|
|
121
|
+
unlinkSync(tempPath);
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
// Ignore cleanup errors
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Load tasks from disk
|
|
133
|
+
* Returns empty array if file doesn't exist or is corrupted
|
|
134
|
+
* Marks orphaned running tasks as failed
|
|
135
|
+
*/
|
|
136
|
+
export function loadTasks(cwd: string): TaskState[] {
|
|
137
|
+
const filePath = getStoragePath(cwd);
|
|
138
|
+
|
|
139
|
+
if (!existsSync(filePath)) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const data = readFileSync(filePath, 'utf-8');
|
|
145
|
+
const tasks = JSON.parse(data) as TaskState[];
|
|
146
|
+
|
|
147
|
+
if (!Array.isArray(tasks)) {
|
|
148
|
+
console.error('[task-persistence] Invalid tasks file format, starting fresh');
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Recover orphaned tasks (server crashed while they were running)
|
|
153
|
+
return recoverOrphanedTasks(tasks);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error(`[task-persistence] Failed to load tasks (corrupted?), starting fresh: ${error}`);
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Delete storage file for a cwd (for testing/cleanup)
|
|
162
|
+
*/
|
|
163
|
+
export function deleteStorage(cwd: string): boolean {
|
|
164
|
+
const filePath = getStoragePath(cwd);
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
if (existsSync(filePath)) {
|
|
168
|
+
unlinkSync(filePath);
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error(`[task-persistence] Failed to delete storage: ${error}`);
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
export const TASK_TYPES = {
|
|
8
|
+
'super-coder': 'super coder for all coding tasks',
|
|
9
|
+
'super-planner': 'super planner for all planning tasks',
|
|
10
|
+
'super-researcher': 'super researcher for answering any question',
|
|
11
|
+
'super-tester': 'super tester to test stuff properly',
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export type TaskType = keyof typeof TASK_TYPES;
|
|
15
|
+
export const TASK_TYPE_IDS = Object.keys(TASK_TYPES) as TaskType[];
|
|
16
|
+
|
|
17
|
+
const cache = new Map<TaskType, string>();
|
|
18
|
+
|
|
19
|
+
export function isValidTaskType(type: string): type is TaskType {
|
|
20
|
+
return type in TASK_TYPES;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function applyTemplate(taskType: TaskType, userPrompt: string): string {
|
|
24
|
+
if (!cache.has(taskType)) {
|
|
25
|
+
try {
|
|
26
|
+
cache.set(taskType, readFileSync(join(__dirname, `${taskType}.mdx`), 'utf8'));
|
|
27
|
+
} catch {
|
|
28
|
+
return userPrompt;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const template = cache.get(taskType)!;
|
|
32
|
+
return template.includes('{{user_prompt}}')
|
|
33
|
+
? template.replace('{{user_prompt}}', userPrompt)
|
|
34
|
+
: `${template}\n\n---\n\n${userPrompt}`;
|
|
35
|
+
}
|